Coggle 30 Days of ML 打卡任务二:苹果病害数据加载与数据增强
任务二:苹果病害数据加载与数据增强
- 难度/分值:中/2
打卡内容:
- 参赛选手名称:AppleDoctor
- 完成日期:2023.6.9
- 任务完成情况:
- 使用的编程语言:Python,Pytorch
- 实现的功能:
- 使用OpenCV或者PIL加载数据集
- 使用torchvision或者OpenCV实现图像分类任务的数据增强
背景介绍
本次打卡任务是 Coggle 30 Days of ML 中的第二项任务,要求完成苹果病害数据加载与数据增强。数据加载阶段,参赛选手需要编写代码来读取和处理提供的图像数据。数据增强阶段,选手可以使用各种图像处理技术和方法,如旋转、缩放、翻转、亮度调整等,来增强数据集的多样性和数量。
任务名称 | 难度/分值 |
---|---|
任务1:两个赛题数据可视化 | 低/1 |
任务2:苹果病害数据加载与数据增强 | 中/2 |
任务3:苹果病害模型训练与预测 | 中/2 |
任务4:苹果病害模型优化与多折训练 | 高/3 |
任务5:建筑物检测数据加载与数据增强 | 高/2 |
任务6:建筑物检测模型训练与预测 | 中/2 |
任务7:建筑物检测模型优化与多折训练 | 高/3 |
数据集准备
首先报名并下载数据集,以下是实践比赛地址:
- 赛题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数据增强的核心思想是将两个样本按照一定的比例进行线性插值,生成一个新的样本,并将对应的标签也按照相同的比例进行插值。这种数据增强方法可以提升模型的鲁棒性和泛化能力,并且有助于防止过拟合。