任务二:苹果病害数据加载与数据增强
- 难度/分值:中/2
打卡内容:
- 参赛选手名称:AppleDoctor
- 完成日期:2023.6.9
- 任务完成情况:
- 使用的编程语言:Python,Pytorch
- 实现的功能:
- 使用OpenCV或者PIL加载数据集
- 使用torchvision或者OpenCV实现图像分类任务的数据增强
背景介绍
本次打卡任务是 Coggle 30 Days of ML 中的第二项任务,要求完成苹果病害数据加载与数据增强。数据加载阶段,参赛选手需要编写代码来读取和处理提供的图像数据。数据增强阶段,选手可以使用各种图像处理技术和方法,如旋转、缩放、翻转、亮度调整等,来增强数据集的多样性和数量。
数据集准备
首先报名并下载数据集,以下是实践比赛地址:
- 赛题1:苹果病害图像识别
- 赛题2:建筑物变化检测
自定义数据集
首先,使用PyTorch作为示例,需要自定义数据集。为此,我定义了一个名为AppleDataset
的类。
# 定义Apple数据集的类 class AppleDataset(Dataset): def __init__(self, img_path, transform=None): """ 构造函数,初始化数据集的路径和数据增强操作 Args: - img_path: list类型,存储数据集中图像的路径 - transform: torchvision.transforms类型,数据增强操作 """ self.img_path = img_path self.transform = transform def __getitem__(self, index): """ 获取一个样本 Args: - index: 数据集中的索引 Returns: - img: Tensor类型,经过处理后的图像 - label: Tensor类型,标签 """ img = Image.open(self.img_path[index]) if self.transform is not None: img = self.transform(img) # 将类别名转换为数字标签 class_name = self.img_path[index].split('/')[-2] if class_name in ['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9']: label = ['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9'].index(class_name) else: label = -1 return img, torch.from_numpy(np.array(label)) def __len__(self): """ 获取数据集的大小 Returns: - size: int类型,数据集的大小 """ return len(self.img_path)
然后,我们可以使用这个自定义数据集类来加载数据。
np.random.shuffle(train_path) train_data = AppleDataset(train_path,) valid_data = AppleDataset(val_path,) print("类别: {}".format(classes_name)) print("训练集: {}".format(len(train_data)))
可以看到,训练集大约有1万张图片,这个信息在第一次打卡中已经提到过。
类别: ['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9'] 训练集: 10211
数据增强及可视化
接下来,让我们进行数据增强和可视化。
import matplotlib.pyplot as plt import torchvision.transforms.functional as TF # 定义一系列的图像增强操作 transformations = [ transforms.Resize((224, 224)), # 将图像大小调整为224*224 transforms.RandomHorizontalFlip(), # 以0.5的概率对图像进行水平翻转 transforms.RandomVerticalFlip(), # 以0.5的概率对图像进行垂直翻转 transforms.GaussianBlur(kernel_size=3), # 对图像进行高斯模糊,卷积核大小为3 transforms.ColorJitter(brightness=0.9), # 调整图像的亮度,亮度因子的范围为[0.1, 1.9] transforms.ColorJitter(contrast=0.9), # 调整图像的对比度,对比度因子的范围为[0.1, 1.9] transforms.ColorJitter(saturation=0.9), # 调整图像的饱和度,饱和度因子的范围为[0.1, 1.9] ] # 定义每个转换操作的标题 transformations_title = ['Resize', 'RandomHorizontalFlip', 'RandomVerticalFlip', 'RandomGaussianBlur', 'ColorJitter(brightness)', 'ColorJitter(contrast)', 'ColorJitter(saturation)', ] num_images = 5 # 可视化训练集的图像转换结果 fig, axes = plt.subplots(nrows=1, ncols=num_images, figsize=(12, 2)) # 显示原始图像 for i in range(num_images): img, label = train_data[i] ax = axes[i] ax.imshow(img) ax.set_title(f"Original d{label+1}") ax.axis('off') plt.show() # 显示每个转换操作的结果 for i, transform in enumerate(transformations): # 可视化训练集的图像转换结果 fig, axes = plt.subplots(nrows=1, ncols=num_images, figsize=(12, 2)) for j in range(num_images): img, label = train_data[j] ax = axes[j] transformed_img = transform(img) ax.imshow(transformed_img) ax.set_title(f"{transformations_title[i]}") ax.axis('off') plt.tight_layout() plt.show()
通过上述代码,我们定义了一系列的数据增强操作,并进行了可视化。可以看到,在不同的数据增强操作下,图像发生了变化。
在代码中,我们使用了以下数据增强操作:
调整图像大小:使用 transforms.Resize((224, 224)) 将图像大小调整为 224x224 像素。
随机水平翻转:使用 transforms.RandomHorizontalFlip() 以 0.5 的概率对图像进行水平翻转。
随机垂直翻转:使用 transforms.RandomVerticalFlip() 以 0.5 的概率对图像进行垂直翻转。
高斯模糊:使用 transforms.GaussianBlur(kernel_size=3) 对图像进行高斯模糊,卷积核大小为 3。
调整亮度:使用 transforms.ColorJitter(brightness=0.9) 调整图像的亮度,亮度因子的范围为 [0.1, 1.9]。
调整对比度:使用 transforms.ColorJitter(contrast=0.9) 调整图像的对比度,对比度因子的范围为 [0.1, 1.9]。
调整饱和度:使用 transforms.ColorJitter(saturation=0.9) 调整图像的饱和度,饱和度因子的范围为 [0.1, 1.9]。
以上操作使得图像在进行数据增强时产生了多样的变化效果。
数据加载+数据增强
下面是将数据增强操作应用于数据集的代码:
# 定义图像预处理的参数 image_mean = [0.4940, 0.4187, 0.3855] image_std = [0.2048, 0.1941, 0.1932] # 定义训练集的数据增强操作 transform_train = transforms.Compose([ transforms.Resize((224,224)), # 将图像大小调整为224*224 transforms.RandomHorizontalFlip(), # 以0.5的概率对图像进行水平翻转 transforms.RandomVerticalFlip(), # 以0.5的概率对图像进行垂直翻转 transforms.RandomApply([transforms.GaussianBlur(kernel_size=3)], p=0.1), # 以0.1的概率对图像进行高斯模糊,卷积核大小为3 transforms.RandomApply([transforms.ColorJitter(brightness=0.9)],p=0.5), # 以0.5的概率调整图像的亮度,亮度因子的范围为[0.1, 1.9] transforms.RandomApply([transforms.ColorJitter(contrast=0.9)],p=0.5), # 以0.5的概率调整图像的对比度,对比度因子的范围为[0.1, 1.9] transforms.RandomApply([transforms.ColorJitter(saturation=0.9),],p=0.5), # 以0.5的概率调整图像的饱和度,饱和度因子的范围为[0.1, 1.9] transforms.ToTensor(), # 将图像转换成张量 transforms.Normalize(image_mean, image_std), # 对图像进行标准化处理 ]) # 定义验证集的数据预处理操作 transform_valid = transforms.Compose([ transforms.Resize((224,224)), # 将图像大小调整为224*224 transforms.ToTensor(), # 将图像转换成张量 transforms.Normalize(image_mean, image_std), # 对图像进行标准化处理 ])
在上述代码中,我们定义了训练集和验证集的数据增强操作。transform_train 是训练集的数据增强操作,其中包括图像大小调整、水平翻转、垂直翻转、高斯模糊、亮度调整、对比度调整和饱和度调整等操作。transform_valid 是验证集的数据预处理操作,其中包括图像大小调整。
接下来,我们可以可视化经过数据增强操作后的图像。
# 可视化训练集 fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(10, 4)) for i in range(10): img, label = train_data[i] ax = axes[i // 5, i % 5] ax.imshow(img.permute(1, 2, 0)) ax.set_title(classes_name[label.item()]) ax.axis('off') plt.tight_layout() plt.show()
以上代码会显示经过数据增强后的训练集图像,但是我进行标准化,所以会看的非常奇怪
为了查看进行了哪些操作,我们可以进行反标准化操作:
def denormalize(img, mean, std): mean = torch.tensor(mean).reshape(3, 1, 1) std = torch.tensor(std).reshape(3, 1, 1) return img * std + mean # 可视化训练集 fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(10, 4)) for i in range(10): img, label = train_data[i] ax = axes[i // 5, i % 5] ax.imshow( denormalize(img, image_mean, image_std).permute(1, 2, 0)) ax.set_title(classes_name[label.item()]) ax.axis('off') plt.tight_layout() plt.show()
以上代码会显示经过反标准化操作后的训练集图像,以查看进行了哪些数据增强操作。
Mixup数据增强
接下来我们实现了Mixup数据增强方法。
首先,我们定义了一个Mixup类,它接受alpha和beta作为参数,这些参数用于定义Beta分布。Mixup类有一个__call__方法,用于执行Mixup操作。给定图像数据x和标签y,该方法将返回混合后的图像mixed_image、两个标签y1和y2以及混合系数lambd。
# 定义Mixup类 class Mixup: def __init__(self, alpha=1.5, beta=1.5): """ 构造函数,初始化Mixup的参数 Args: - alpha: float类型,beta分布的参数alpha - beta: float类型,beta分布的参数beta Returns: - None """ self.alpha = alpha self.beta = beta def __call__(self, x, y): """ 实现Mixup的操作 Args: - x: Tensor类型,图像数据 - y: Tensor类型,标签 Returns: - mixed_image: Tensor类型,混合后的图像数据 - y1: Tensor类型,标签1 - y2: Tensor类型,标签2 - lambd: Tensor类型,混合系数 """ image, label = x, y batch_size = image.size(0) # 生成混合系数 lambd = torch.Tensor([random.betavariate(self.alpha, self.beta)]).to(image.device) # 生成随机索引 index = torch.randperm(batch_size).to(image.device) # 进行Mixup操作 mixed_image = lambd * image + (1 - lambd) * image[index, :] # 生成对应的标签 y1, y2 = label, label[index] return mixed_image, y1, y2, lambd # 初始化Mixup的参数 mixup = Mixup(alpha=1.5, beta=1.5)
接下来,我们从训练集中获取一个batch的数据,并应用Mixup数据增强。
# 使用PyTorch的DataLoader加载训练集数据 train_loader = torch.utils.data.DataLoader( train_data, batch_size=8, shuffle=True, num_workers=0, pin_memory = True) # 获取一个batch的数据 images, labels = next(iter(train_loader)) # 进行数据混合 mixed_image, y1, y2, lambd = mixup(images,labels)
然后,我们对混合后的图像进行反标准化,并可视化结果。你可以观察到图像实际上是两张图像的混合。
# 反标准化混合图像 denorm_mixed_images = denormalize(mixed_image, image_mean, image_std) # 创建图像网格 num_images = len(denorm_mixed_images) fig, axes = plt.subplots(nrows=2, ncols=num_images//2, figsize=(12, 6)) # 显示混合图像 for i, ax in enumerate(axes.flatten()): mixed_img = TF.to_pil_image(denorm_mixed_images[i]) ax.imshow(mixed_img) ax.set_title(f"Mixed Image {i+1}") ax.axis('off') plt.tight_layout() plt.show()
最后,需要注意的是,经过Mixup数据增强后,损失函数也需要进行相应的调整。以下是一个模板示例,展示了如何在训练批次中应用Mixup。
# 在每个训练批次中应用Mixup inputs, labels = data # 获取训练数据及其标签 inputs, labels_a, labels_b, lam = mixup(inputs, labels) # 将数据和标签输入模型进行训练 outputs = model(inputs) loss = lam * criterion(outputs, labels_a) + (1 - lam) * criterion(outputs, labels_b) optimizer.zero_grad() loss.backward() optimizer.step()
在上述代码中,inputs 是训练数据,labels 是对应的标签。通过调用 mixup 函数,我们将输入数据进行Mixup操作,生成混合的输入数据 mixed_inputs,同时生成对应的标签 labels_a 和 labels_b。在训练过程中,我们使用混合数据 mixed_inputs 和相应的标签进行模型训练,并计算损失函数。注意,损失函数使用了Mixup系数 lam 进行加权计算。通过这种方式,你可以在训练过程中应用Mixup数据增强,并调整损失函数以适应混合图像的训练。
Mixup数据增强的核心思想是将两个样本按照一定的比例进行线性插值,生成一个新的样本,并将对应的标签也按照相同的比例进行插值。这种数据增强方法可以提升模型的鲁棒性和泛化能力,并且有助于防止过拟合。