目录
opencv、numpy、pandas、pillow、albumentations、pytorch、torchvision、matplotlib
- import pandas as pd # 用于数据处理和分析的库
- import numpy as np # 数值计算库,提供高效的多维数组操作
- import cv2 # OpenCV库,用于进行图像处理和计算机视觉任务
- import os # 提供与操作系统交互的功能,如文件路径操作
- import re # 用于执行正则表达式,进行字符串匹配和处理
-
- from PIL import Image # 用于图像打开、处理和保存
-
- import albumentations as A # 图像增强库,用于提高模型的泛化能力
- from albumentations.pytorch.transforms import ToTensorV2 # 将图像数据转换为PyTorch张量
-
- import torch # PyTorch基础库,提供多维数组(张量)和自动求导功能
- import torchvision # 提供图像视频处理工具,和模型训练或加载预训练模型
-
- from torchvision.models.detection.faster_rcnn import FastRCNNPredictor # Faster R-CNN的预测器部分
- from torchvision.models.detection import FasterRCNN # Faster R-CNN模型
- from torchvision.models.detection.rpn import AnchorGenerator # 生成锚点用于RPN(区域提议网络)
-
- from torch.utils.data import DataLoader, Dataset # 数据加载和批处理
- from torch.utils.data.sampler import SequentialSampler # 数据采样器,按顺序采样
-
- from matplotlib import pyplot as plt # 绘图库,用于数据可视化
- DIR_INPUT = '.' # 设置基本目录为当前目录
- DIR_TRAIN = f'{DIR_INPUT}/train' # 设置训练数据集的目录
- DIR_TEST = f'{DIR_INPUT}/test' # 设置测试数据集的目录
-
- train_df = pd.read_csv(f'{DIR_INPUT}/train.csv') # 使用pandas加载训练数据集的CSV文件
- train_df.shape # 显示加载的DataFrame的形状(即,行数和列数)
train_df
这个DataFrame中。DataFrame是pandas库中用于存储和操作结构化数据的主要数据结构。这部分代码是数据预处理和初步检查的基础部分,确保数据正确加载并且可以进一步用于训练模型。如果你需要更进一步的帮助,例如数据的可视化或预处理,请继续提供相关的代码或需求。
- # 在DataFrame中初始化四个新列,用于存储边界框的x, y坐标和宽度、高度
- train_df['x'] = -1
- train_df['y'] = -1
- train_df['w'] = -1
- train_df['h'] = -1
-
- # 定义一个函数,用于从字符串中提取边界框数据
- def expand_bbox(x):
- r = np.array(re.findall("([0-9]+[.]?[0-9]*)", x)) # 使用正则表达式提取数字
- if len(r) == 0: # 如果没有找到任何数字,返回默认值
- r = [-1, -1, -1, -1]
- return r
-
- # 将'bbox'列的字符串转换为具体的x, y, w, h数值,并更新到对应列
- train_df[['x', 'y', 'w', 'h']] = np.stack(train_df['bbox'].apply(lambda x: expand_bbox(x)))
- train_df.drop(columns=['bbox'], inplace=True) # 删除原始的'bbox'列
- train_df['x'] = train_df['x'].astype(np.float32) # 转换数据类型为浮点数
- train_df['y'] = train_df['y'].astype(np.float32)
- train_df['w'] = train_df['w'].astype(np.float32)
- train_df['h'] = train_df['h'].astype(np.float32)
-
- # 获取所有唯一的图像ID
- image_ids = train_df['image_id'].unique()
- valid_ids = image_ids[-665:] # 划分最后665个ID为验证集
- train_ids = image_ids[:-665] # 剩余的为训练集
-
- # 创建验证集和训练集的DataFrame
- valid_df = train_df[train_df['image_id'].isin(valid_ids)]
- train_df = train_df[train_df['image_id'].isin(train_ids)]
-
- # 输出验证集和训练集的形状(即,行数和列数)
- valid_df.shape, train_df.shape
这部分代码主要负责数据预处理和训练验证集的分割,这是模型训练前的重要步骤,确保数据的正确格式和合适的分布。
- class WheatDataset(Dataset):
- # 初始化方法
- def __init__(self, dataframe, image_dir, transforms=None):
- super().__init__()
- self.image_ids = dataframe['image_id'].unique() # 获取所有唯一的图像ID
- self.df = dataframe # 数据帧
- self.image_dir = image_dir # 图像文件的目录
- self.transforms = transforms # 图像变换(如增强)
-
- # 获取单个样本
- def __getitem__(self, index: int):
- image_id = self.image_ids[index] # 根据索引获取图像ID
- records = self.df[self.df['image_id'] == image_id] # 获取该图像ID的所有记录
-
- # 读取图像并转换颜色空间
- image = cv2.imread(f'{self.image_dir}/{image_id}.jpg', cv2.IMREAD_COLOR)
- image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)
- image /= 255.0 # 归一化图像数据
-
- # 计算边界框并调整格式
- boxes = records[['x', 'y', 'w', 'h']].values
- boxes[:, 2] = boxes[:, 0] + boxes[:, 2] # 转换宽度为右下角x坐标
- boxes[:, 3] = boxes[:, 1] + boxes[:, 3] # 转换高度为右下角y坐标
-
- area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])
- area = torch.as_tensor(area, dtype=torch.float32) # 计算每个框的面积
-
- labels = torch.ones((records.shape[0],), dtype=torch.int64) # 所有物体的标签为1(只有一类)
- iscrowd = torch.zeros((records.shape[0],), dtype=torch.int64) # 假设没有物体是重叠的
-
- # 构建目标字典,存储边界框等信息
- target = {}
- target['boxes'] = boxes
- target['labels'] = labels
- target['image_id'] = torch.tensor([index])
- target['area'] = area
- target['iscrowd'] = iscrowd
-
- # 如果有变换应用变换
- if self.transforms:
- sample = {
- 'image': image,
- 'bboxes': target['boxes'],
- 'labels': labels
- }
- sample = self.transforms(**sample)
- image = sample['image']
- target['boxes'] = torch.stack(tuple(map(torch.tensor, zip(*sample['bboxes'])))).permute(1, 0)
-
- return image, target, image_id
-
- # 返回数据集大小
- def __len__(self) -> int:
- return self.image_ids.shape[0]
这个类提供了从指定目录加载图像和标注,应用预处理和变换,以及格式化输出以供模型训练使用的完整工作流。
- # 定义训练时使用的图像变换
- def get_train_transform():
- return A.Compose([
- A.HorizontalFlip(p=0.5), # 以50%的概率水平翻转图像
- ToTensorV2(p=1.0) # 将图像转换为PyTorch的Tensor格式
- ], bbox_params={'format': 'pascal_voc', 'label_fields': ['labels']})
- # bbox_params指定了边界框的格式和相关标签字段
-
- # 定义验证时使用的图像变换
- def get_valid_transform():
- return A.Compose([
- ToTensorV2(p=1.0) # 验证时只进行Tensor转换,不进行其他增强
- ], bbox_params={'format': 'pascal_voc', 'label_fields': ['labels']})
- # 同样设置边界框的格式为Pascal VOC
ToTensorV2
用于确保图像数据以适合 PyTorch 处理的形式输入模型。bbox_params
参数确保在进行图像变换时,相应的边界框(bbox)也被正确地变换。'format': 'pascal_voc'
表示使用 Pascal VOC 格式(xmin, ymin, xmax, ymax),这是物体检测中常用的边界框格式。'label_fields': ['labels']
告诉 Albumentations 库标签数据在哪里,以便在处理时保持与边界框同步。
- # 加载一个预训练的Faster R-CNN模型,使用的是ResNet-50作为backbone,带有特征金字塔网络(FPN)
- model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)
-
- num_classes = 2 # 定义类别数:1个小麦类别加上背景
-
- # 获取分类器的输入特征数
- in_features = model.roi_heads.box_predictor.cls_score.in_features
-
- # 使用新的分类头来替换原来的预训练分类头
- model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
torchvision.models.detection
模块来加载一个预训练的 Faster R-CNN 模型,这里使用的是基于 ResNet-50 和 FPN 的结构。这种结构提供了良好的特征提取能力,非常适合复杂的视觉任务,如物体检测。num_classes
为 2,考虑到了一个目标类别(小麦)加上一个背景类别。在 Faster R-CNN 中,背景被视为一个独立的类别。in_features
),并以此创建一个新的 FastRCNNPredictor
,将其用作新的分类器头部。这样做的目的是将模型的输出调整为适应当前任务的类别数。这些更改确保了模型能够适应于特定的检测任务,同时也利用了预训练模型在通用任务(如 COCO 数据集上的物体检测)上获得的丰富特征和经验,这有助于提高模型在特定任务上的性能。
- class Averager:
- # 初始化方法
- def __init__(self):
- self.current_total = 0.0 # 初始化总和为0
- self.iterations = 0.0 # 初始化迭代次数为0
-
- # 向平均器发送新的值
- def send(self, value):
- self.current_total += value # 将新值加到总和中
- self.iterations += 1 # 迭代次数加1
-
- # 计算当前的平均值
- @property
- def value(self):
- if self.iterations == 0:
- return 0 # 如果没有迭代,返回0防止除以0
- else:
- return 1.0 * self.current_total / self.iterations # 计算平均值
-
- # 重置平均器
- def reset(self):
- self.current_total = 0.0 # 重置总和为0
- self.iterations = 0.0 # 重置迭代次数为0
Averager
实例时,都从零开始。@property
实现,它使得每次访问 .value
时,都会动态计算当前的平均值。这样做的好处是可以随时获取最新的平均值,而不必显式调用一个方法。Averager
实例可以在不同阶段(例如,新的训练周期开始时)重新使用,而无需创建新的实例。Averager
类是一个简洁而有效的工具,适用于训练过程中监控和报告指标的平均值,如每个批次或每个周期的平均损失。这对于调试和优化模型非常有帮助。
- # 自定义的数据批处理函数
- def collate_fn(batch):
- return tuple(zip(*batch)) # 将批量数据中的元素按组件重新组织
-
- # 创建训练数据集和验证数据集实例
- train_dataset = WheatDataset(train_df, DIR_TRAIN, get_train_transform())
- valid_dataset = WheatDataset(valid_df, DIR_TRAIN, get_valid_transform())
-
- # 随机打乱索引并转为列表形式
- indices = torch.randperm(len(train_dataset)).tolist()
-
- # 创建训练数据加载器
- train_data_loader = DataLoader(
- train_dataset,
- batch_size=4, # 每批处理4个图像
- shuffle=False, # 数据不进行额外的混洗(已经预先打乱)
- # num_workers=1, # 线程数(这行被注释,使用默认设置)
- collate_fn=collate_fn # 使用自定义的批处理函数
- )
-
- # 创建验证数据加载器
- valid_data_loader = DataLoader(
- valid_dataset,
- batch_size=4, # 同样每批4个图像
- shuffle=False, # 验证数据通常不需要混洗
- # num_workers=1, # 线程数(这行也被注释)
- collate_fn=collate_fn # 同样使用自定义的批处理函数
- )
collate_fn
函数通过 zip
重新组织批数据,适合处理包含多个组件(如图像和其标注信息)的数据结构。DataLoader
是 PyTorch 中用于加载数据的一个工具,它可以处理并行加载和数据批处理,使数据输入模型时更加高效。这里创建了两个数据加载器,一个用于训练,一个用于验证。设置 batch_size
为 4 表示每个批次处理 4 个图像。这些设置是进行有效训练和验证的关键,因为它们确保数据以适当的方式被批处理和提供给模型,同时也利用了 DataLoader
的多线程能力(虽然在这里具体的 num_workers
被注释了,可能是为了避免在某些环境下的问题,如单线程环境或者与特定硬件的兼容性问题)。
- # 确定使用的设备,如果GPU可用则使用GPU,否则使用CPU
- device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
-
- # 从训练数据加载器中获取第一个批次的数据
- images, targets, image_ids = next(iter(train_data_loader))
-
- # 将图像列表中的每张图像转移到设定的设备(GPU或CPU)
- images = list(image.to(device) for image in images)
-
- # 将每个目标字典中的所有张量也转移到设定的设备
- targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
-
- # 从第三个目标(索引为2)中提取边界框,并将其从GPU移动到CPU,并转换为整数类型的NumPy数组
- boxes = targets[2]['boxes'].cpu().numpy().astype(np.int32)
-
- # 将第三张图像(索引为2)的维度顺序从[C, H, W]调整为[H, W, C],并转移到CPU,转换为NumPy数组
- sample = images[2].permute(1,2,0).cpu().numpy()
这种数据处理方式典型地出现在准备数据进行模型训练或评估之前,确保数据在适当的设备上并以适合处理的格式。
- # 创建一个图和一个子图,设置图的大小为16x8英寸
- fig, ax = plt.subplots(1, 1, figsize=(16, 8))
-
- # 遍历边界框数组boxes,每个box代表一个边界框
- for box in boxes:
- # 使用OpenCV在图像sample上绘制矩形(边界框)
- cv2.rectangle(sample,
- (box[0], box[1]), # 矩形左上角
- (box[2], box[3]), # 矩形右下角
- (220, 0, 0), 3) # 矩形颜色为红色,线宽为3
-
- # 隐藏坐标轴
- ax.set_axis_off()
-
- # 在子图上显示图像
- ax.imshow(sample)
matplotlib.pyplot.subplots
创建一个图和一个子图。这里指定了子图的大小,使得图像可以更加清晰地展示。cv2.rectangle
在图像上绘制。这个函数接受左上角和右下角的坐标,并允许自定义边界框的颜色和线宽。imshow
方法将图像显示在子图上。set_axis_off
方法用于隐藏坐标轴,使图像显示更为美观,专注于内容本身。这种方式通常用于数据探索和结果展示阶段,可以直观地评估目标检测模型识别出的边界框是否准确,或者是否需要调整模型的训练过程中的参数。
- # 将模型移至之前选择的设备上(GPU或CPU)
- model.to(device)
-
- # 选择模型中所有需要梯度更新(可训练)的参数
- params = [p for p in model.parameters() if p.requires_grad]
-
- # 为选定的参数创建一个随机梯度下降优化器
- # 设置学习率为0.005,动量为0.9,权重衰减为0.0005
- optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)
-
- # 学习率调度器,这里被注释掉了,意味着学习率将保持不变
- # lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
- lr_scheduler = None
-
- # 设置训练的总轮数为2
- num_epochs = 2
None
,通常可以根据需要激活和配置。整体而言,这些设置对模型的训练过程至关重要,它们影响模型训练的效率和最终的性能。正确配置这些元素是确保模型能够有效学习和泛化的关键。
- # 创建一个用于跟踪平均损失的Averager实例
- loss_hist = Averager()
- itr = 1 # 初始化迭代计数器
-
- # 循环执行指定的训练轮次
- for epoch in range(num_epochs):
- loss_hist.reset() # 重置平均损失计算器
-
- # 从数据加载器中迭代每个批次
- for images, targets, image_ids in train_data_loader:
-
- # 将图像和目标张量移至设定的设备
- images = list(image.to(device) for image in images)
- targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
-
- # 计算模型对当前批次的损失
- loss_dict = model(images, targets)
-
- # 将损失字典中的所有损失相加得到总损失
- losses = sum(loss for loss in loss_dict.values())
- loss_value = losses.item() # 将损失转为Python浮点数
-
- # 向平均损失计算器发送当前损失
- loss_hist.send(loss_value)
-
- # 清除旧的梯度
- optimizer.zero_grad()
- # 反向传播损失以计算梯度
- losses.backward()
- # 根据梯度更新模型参数
- optimizer.step()
-
- # 每50次迭代输出一次当前的损失
- if itr % 50 == 0:
- print(f"Iteration #{itr} loss: {loss_value}")
-
- itr += 1 # 更新迭代计数器
-
- # 如果有设置学习率调度器,则更新学习率
- if lr_scheduler is not None:
- lr_scheduler.step()
-
- # 输出每个轮次的平均损失
- print(f"Epoch #{epoch} loss: {loss_hist.value}")
optimizer.step()
)更新模型的参数,目的是最小化损失函数。这些步骤确保了训练过程的有效性和可追踪性,是实现有效深度学习模型训练的关键环节。
- # 从验证数据加载器中获取一批数据
- images, targets, image_ids = next(iter(valid_data_loader))
-
- # 将图像和目标数据移动到设定的设备(GPU或CPU)
- images = list(img.to(device) for img in images)
- targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
-
- # 提取特定目标的边界框并转换为NumPy数组
- boxes = targets[1]['boxes'].cpu().numpy().astype(np.int32)
- # 调整图像数据的维度顺序,准备显示
- sample = images[1].permute(1,2,0).cpu().numpy()
-
- # 设置模型为评估模式
- model.eval()
- cpu_device = torch.device("cpu") # 创建一个CPU设备对象
-
- # 在当前批次的图像上运行模型进行预测
- outputs = model(images)
- # 将输出数据移动到CPU
- outputs = [{k: v.to(cpu_device) for k, v in t.items()} for t in outputs]
-
- # 创建一个图和一个子图,设置图的大小
- fig, ax = plt.subplots(1, 1, figsize=(16, 8))
-
- # 遍历边界框并在图像上绘制
- for box in boxes:
- cv2.rectangle(sample,
- (box[0], box[1]),
- (box[2], box[3]),
- (220, 0, 0), 3) # 使用红色绘制边界框
-
- # 隐藏坐标轴
- ax.set_axis_off()
- # 显示图像
- ax.imshow(sample)
这个过程是评估深度学习模型在实际任务中表现的常用方法,通过直接观察预测结果,可以更好地理解模型的行为和潜在的改进方向。