学习
数据增强是一种在机器学习和深度学习领域常用的技术,尤其是在处理图像和视频数据时。数据增强的目的是通过人工方式增加训练数据的多样性,从而提高模型的泛化能力,使其能够在未见过的数据上表现得更好。数据增强涉及对原始数据进行一系列的变换操作,生成新的训练样本。这些变换模拟了真实世界中的变化,对于图像而言,数据增强包括例如视角、光照、遮挡等情况,使得模型能够学习到更加鲁棒的特征表示。
如果数据增强的变换操作与目标任务的实际场景不符,比如在不需要旋转的图像任务中过度使用旋转,那么这些变换可能会引入无关的噪音。此外,过度的数据增强,比如极端的亮度调整、对比度变化或大量的噪声添加,可能会导致图像失真,使得模型难以学习到有效的特征。
在深度学习中,对输入数据进行归一化是一个标准步骤。归一化有助于加快模型的收敛速度,并提高数值稳定性。对于验证集,应该避免使用如随机翻转等可能引入不必要噪音的增强方法。通常,验证集只需要进行必要的预处理,如调整大小和归一化。
学习- 九月助教老师的图像数据增强方法实操代码
[九月]Deepfake-FFDI-plot_transforms_illustrations: https://www.kaggle.com/code/chg0901/deepfake-ffdi-plot-transforms-illustrations
让我们逐部分代码进行详细解释:
导入库
import matplotlib.pyplot as plt
import torch
from torchvision.utils import draw_bounding_boxes, draw_segmentation_masks
from torchvision import tv_tensors
from torchvision.transforms.v2 import functional as F
import matplotlib.pyplot as plt
:导入Matplotlib库的pyplot模块,用于绘制图形。import torch
:导入PyTorch库,主要用于深度学习和张量操作。from torchvision.utils import draw_bounding_boxes, draw_segmentation_masks
:导入两个工具函数,用于在图像上绘制边界框和分割掩码。from torchvision import tv_tensors
:导入TorchVision中的tv_tensors模块,用于处理图像数据。from torchvision.transforms.v2 import functional as F
:导入TorchVision v2版本中的functional模块并重命名为F,用于图像变换的功能函数。
定义绘图函数
def plot(imgs, row_title=None, **imshow_kwargs):
if not isinstance(imgs[0], list):
# Make a 2d grid even if there's just 1 row
imgs = [imgs]
num_rows = len(imgs)
num_cols = len(imgs[0])
_, axs = plt.subplots(nrows=num_rows, ncols=num_cols, squeeze=False)
for row_idx, row in enumerate(imgs):
for col_idx, img in enumerate(row):
boxes = None
masks = None
if isinstance(img, tuple):
img, target = img
if isinstance(target, dict):
boxes = target.get("boxes")
masks = target.get("masks")
elif isinstance(target, tv_tensors.BoundingBoxes):
boxes = target
else:
raise ValueError(f"Unexpected target type: {type(target)}")
img = F.to_image(img)
if img.dtype.is_floating_point and img.min() < 0:
# Poor man's re-normalization for the colors to be OK-ish. This
# is useful for images coming out of Normalize()
img -= img.min()
img /= img.max()
img = F.to_dtype(img, torch.uint8, scale=True)
if boxes is not None:
img = draw_bounding_boxes(img, boxes, colors="yellow", width=3)
if masks is not None:
img = draw_segmentation_masks(img, masks.to(torch.bool), colors=["green"] * masks.shape[0], alpha=.65)
ax = axs[row_idx, col_idx]
ax.imshow(img.permute(1, 2, 0).numpy(), **imshow_kwargs)
ax.set(xticklabels=[], yticklabels=[], xticks=[], yticks=[])
if row_title is not None:
for row_idx in range(num_rows):
axs[row_idx, 0].set(ylabel=row_title[row_idx])
plt.tight_layout()
这个函数用于绘制图像,并在图像上绘制边界框和分割掩码。
-
检查输入图像格式:
if not isinstance(imgs[0], list): imgs = [imgs]
确保输入是二维网格,即使只有一行图像。
-
创建子图:
num_rows = len(imgs) num_cols = len(imgs[0]) _, axs = plt.subplots(nrows=num_rows, ncols=num_cols, squeeze=False)
根据图像的行数和列数创建相应数量的子图。
-
处理每个图像:
for row_idx, row in enumerate(imgs): for col_idx, img in enumerate(row): ...
遍历每个图像,进行处理。
-
提取目标信息:
if isinstance(img, tuple): img, target = img ...
检查图像是否包含目标信息(如边界框和分割掩码)。
-
转换图像格式:
img = F.to_image(img) if img.dtype.is_floating_point and img.min() < 0: img -= img.min() img /= img.max() img = F.to_dtype(img, torch.uint8, scale=True)
将图像转换为适合显示的格式。
-
绘制边界框和分割掩码:
if boxes is not None: img = draw_bounding_boxes(img, boxes, colors="yellow", width=3) if masks is not None: img = draw_segmentation_masks(img, masks.to(torch.bool), colors=["green"] * masks.shape[0], alpha=.65)
-
显示图像:
ax.imshow(img.permute(1, 2, 0).numpy(), **imshow_kwargs) ax.set(xticklabels=[], yticklabels=[], xticks=[], yticks=[])
-
设置行标题(如果有):
if row_title is not None: for row_idx in range(num_rows): axs[row_idx, 0].set(ylabel=row_title[row_idx])
-
调整布局:
plt.tight_layout()
下载并显示图像
!wget https://mirror.coggle.club/image/tyler-swift.jpg
orig_img = Image.open('/kaggle/working/tyler-swift.jpg')
plt.axis('off')
plt.imshow(orig_img)
plt.show()
!wget ...
:下载图像。orig_img = Image.open(...)
:打开下载的图像。plt.axis('off')
:关闭坐标轴显示。plt.imshow(orig_img)
:显示图像。plt.show()
:展示图像。
几何变换示例
padded_imgs = [v2.Pad(padding=padding)(orig_img) for padding in (3, 10, 30, 50)]
plot([orig_img] + padded_imgs)
v2.Pad(padding=padding)
:使用不同的填充大小对图像进行填充。plot([orig_img] + padded_imgs)
:显示原始图像和填充后的图像。
其他变换如 Resize
, CenterCrop
, FiveCrop
, RandomPerspective
, RandomRotation
等类似,分别使用不同的变换方法处理图像并展示结果。
光度变换示例
gray_img = v2.Grayscale()(orig_img)
plot([orig_img, gray_img], cmap='gray')
v2.Grayscale()
:将图像转换为灰度图。plot([orig_img, gray_img], cmap='gray')
:显示原始图像和灰度图。
其他变换如 ColorJitter
, GaussianBlur
, RandomInvert
等类似,分别使用不同的光度变换方法处理图像并展示结果。
增强变换示例
policies = [v2.AutoAugmentPolicy.CIFAR10, v2.AutoAugmentPolicy.IMAGENET, v2.AutoAugmentPolicy.SVHN]
augmenters = [v2.AutoAugment(policy) for policy in policies]
imgs = [
[augmenter(orig_img) for _ in range(4)]
for augmenter in augmenters
]
row_title = [str(policy).split('.')[-1] for policy in policies]
plot([[orig_img] + row for row in imgs], row_title=row_title)
v2.AutoAugmentPolicy
:使用不同的自动增强策略。v2.AutoAugment(policy)
:基于策略自动增强图像。plot(...)
:展示增强后的图像。
其他增强变换如 RandAugment
, TrivialAugmentWide
, AugMix
等类似,分别使用不同的增强方法处理图像并展示结果。
随机应用变换示例
hflipper = v2.RandomHorizontalFlip(p=0.5)
transformed_imgs = [hflipper(orig_img) for _ in range(4)]
plot([orig_img] + transformed_imgs)
v2.RandomHorizontalFlip(p=0.5)
:以50%的概率水平翻转图像。plot([orig_img] + transformed_imgs)
:显示原始图像和翻转后的图像。
其他随机变换如 RandomVerticalFlip
, RandomApply
等类似,分别使用不同的随机方法处理图像并展示结果。
实际尝试更改代码
尝试以九月老师的[九月0.98766]Deepfake-FFDI-Way to Get Top Scores]代码进行实操并修改
以下是您的代码的逐步讲解,解释每段代码的功能和作用:
!wc -l /kaggle/input/deepfake/phase1/trainset_label.txt
!wc -l /kaggle/input/deepfake/phase1/valset_label.txt
!ls /kaggle/input/deepfake/phase1/trainset/ | wc -l
!ls /kaggle/input/deepfake/phase1/valset/ | wc -l
这些命令用于统计文件和目录中行数和文件数量。具体结果为:
trainset_label.txt
文件有 524430 行valset_label.txt
文件有 147364 行trainset
目录有 524429 个文件valset
目录有 147363 个文件
from PIL import Image
Image.open('/kaggle/input/deepfake/phase1/trainset/63fee8a89581307c0b4fd05a48e0ff79.jpg')
该代码段用于打开并显示指定路径下的一张图片,检查文件是否正常存在。
import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True
设置 PyTorch 随机数种子以及 CUDA 后端配置,以确保结果的可重复性和训练速度优化。
import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset
import timm
import time
import pandas as pd
import numpy as np
import cv2
from PIL import Image
from tqdm.notebook import tqdm
导入所需的库和模块,用于模型构建、数据预处理和加载、以及进度条显示等。
train_label = pd.read_csv('/kaggle/input/deepfake/phase1/trainset_label.txt')
val_label = pd.read_csv('/kaggle/input/deepfake/phase1/valset_label.txt')
train_label['path'] = '/kaggle/input/deepfake/phase1/trainset/' + train_label['img_name']
val_label['path'] = '/kaggle/input/deepfake/phase1/valset/' + val_label['img_name']
train_label['target'].value_counts()
val_label['target'].value_counts()
train_label.head(10)
读取训练集和验证集的标签文件,并将图片路径添加到 DataFrame 中。同时检查标签的分布和前几行数据。
class AverageMeter(object):
"""Computes and stores the average and current value"""
def __init__(self, name, fmt=':f'):
self.name = name
self.fmt = fmt
self.reset()
def reset(self):
self.val = 0
self.avg = 0
self.sum = 0
self.count = 0
def update(self, val, n=1):
self.val = val
self.sum += val * n
self.count += n
self.avg = self.sum / self.count
def __str__(self):
fmtstr = '{name} {val' + self.fmt + '} ({avg' + self.fmt + '})'
return fmtstr.format(**self.__dict__)
定义一个工具类AverageMeter
,用于计算和存储当前值和平均值,用于记录训练过程中的各项指标。
class ProgressMeter(object):
def __init__(self, num_batches, *meters):
self.batch_fmtstr = self._get_batch_fmtstr(num_batches)
self.meters = meters
self.prefix = ""
def pr2int(self, batch):
entries = [self.prefix + self.batch_fmtstr.format(batch)]
entries += [str(meter) for meter in self.meters]
print('\t'.join(entries))
def _get_batch_fmtstr(self, num_batches):
num_digits = len(str(num_batches // 1))
fmt = '{:' + str(num_digits) + 'd}'
return '[' + fmt + '/' + fmt.format(num_batches) + ']'
定义一个工具类ProgressMeter
,用于显示训练和验证过程中每个批次的进度信息。
def validate(val_loader, model, criterion):
batch_time = AverageMeter('Time', ':6.3f')
losses = AverageMeter('Loss', ':.4e')
top1 = AverageMeter('Acc@1', ':6.2f')
progress = ProgressMeter(len(val_loader), batch_time, losses, top1)
# switch to evaluate mode
model.eval()
with torch.no_grad():
end = time.time()
for i, (input, target) in tqdm(enumerate(val_loader), total=len(val_loader)):
input = input.cuda()
target = target.cuda()
# compute output
output = model(input)
loss = criterion(output, target)
# measure accuracy and record loss
acc = (output.argmax(1).view(-1) == target.float().view(-1)).float().mean() * 100
losses.update(loss.item(), input.size(0))
top1.update(acc, input.size(0))
# measure elapsed time
batch_time.update(time.time() - end)
end = time.time()
print(' * Acc@1 {top1.avg:.3f}'.format(top1=top1))
return top1
定义验证函数validate
,对模型进行验证,并计算损失和准确率等指标。
def predict(test_loader, model, tta=10):
# switch to evaluate mode
model.eval()
test_pred_tta = None
for _ in range(tta):
test_pred = []
with torch.no_grad():
for i, (input, target) in tqdm(enumerate(test_loader), total=len(test_loader)):
input = input.cuda()
target = target.cuda()
# compute output
output = model(input)
output = F.softmax(output, dim=1)
output = output.data.cpu().numpy()
test_pred.append(output)
test_pred = np.vstack(test_pred)
if test_pred_tta is None:
test_pred_tta = test_pred
else:
test_pred_tta += test_pred
return test_pred_tta
定义预测函数predict
,使用测试集进行预测,并进行多次测试时间增强(TTA)以提高预测稳定性。
def train(train_loader, model, criterion, optimizer, epoch):
batch_time = AverageMeter('Time', ':6.3f')
losses = AverageMeter('Loss', ':.4e')
top1 = AverageMeter('Acc@1', ':6.2f')
progress = ProgressMeter(len(train_loader), batch_time, losses, top1)
# switch to train mode
model.train()
end = time.time()
for i, (input, target) in enumerate(train_loader):
input = input.cuda(non_blocking=True)
target = target.cuda(non_blocking=True)
# compute output
output = model(input)
loss = criterion(output, target)
# measure accuracy and record loss
losses.update(loss.item(), input.size(0))
acc = (output.argmax(1).view(-1) == target.float().view(-1)).float().mean() * 100
top1.update(acc, input.size(0))
# compute gradient and do SGD step
optimizer.zero_grad()
loss.backward()
optimizer.step()
# measure elapsed time
batch_time.update(time.time() - end)
end = time.time()
if i % 100 == 0:
progress.pr2int(i)
定义训练函数train
,对模型进行训练,并计算损失和准确率等指标。
class FFDIDataset(Dataset):
def __init__(self, img_path, img_label, transform=None):
self.img_path = img_path
self.img_label = img_label
if transform is not None:
self.transform = transform
else:
self.transform = None
def __getitem__(self, index):
img = Image.open(self.img_path[index]).convert('RGB')
if self.transform is not None:
img = self.transform(img)
return img, torch.from_numpy(np.array(self.img_label[index]))
def __len__(self):
return len(self.img_path)
定义自定义数据集类FFDIDataset
,用于加载和预处理图像数据。
import os
epoch_num = 5
bs_value = 32
model_path = "/kaggle/input/0-98766-deepfake-ffdi-way-to-get-top-scores/model_89.15.pt"
model = timm.create_model('efficientnet_b1', pretrained=True, num_classes=2)
if os.path.exists(model_path):
model.load_state_dict(torch.load(model_path))
print("使用以往的模型继续训练")
else:
print("使用hugging face预训练模型")
model = model.cuda()
加载预训练模型,并检查是否存在已训练的模型文件,如果存在则继续训练,否则使用预训练模型进行训练。
train_loader = torch.utils.data.DataLoader(
FFDIDataset(
train_label.path.values,
train_label.target.values,
transforms.Compose([
transforms.Resize(256),
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
),
batch_size=bs_value,
shuffle=True,
num_workers=2,
pin_memory=True,
drop_last=True
)
val_loader = torch.utils.data.DataLoader(
FFDIDataset(
val_label.path.values,
val_label.target.values,
transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
),
batch_size=bs_value,
shuffle=False,
num_workers=2,
pin_memory=True,
drop_last=True
)
定义训练集和验证集的DataLoader
,用于加载和预处理数据,进行批处理和数据增强。
criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
定义损失函数和优化器,使用交叉熵损失函数和Adam优化器。
# 开始训练和验证
for epoch in range(epoch_num):
print('Start epoch [%d / %d]' % (epoch + 1, epoch_num))
train(train_loader, model, criterion, optimizer, epoch)
acc = validate(val_loader, model, criterion)
torch.save(model.state_dict(), "model.pt")
开始训练和验证模型,保存训练好的模型参数。
尝试第一次采用使用hugging face预训练模型进行训练
保存获得Acc为82.686的模型
然后采用此模型进行第二轮训练,部分训练数据如下:
[14000/16389] Time 0.182 ( 0.183) Loss 5.8159e-02 (2.3477e-02) Acc@1 96.88 ( 99.19)
[14100/16389] Time 0.183 ( 0.183) Loss 2.0699e-03 (2.3494e-02) Acc@1 100.00 ( 99.19)
[14200/16389] Time 0.176 ( 0.183) Loss 5.0336e-03 (2.3522e-02) Acc@1 100.00 ( 99.18)
[14300/16389] Time 0.184 ( 0.183) Loss 2.5032e-04 (2.3501e-02) Acc@1 100.00 ( 99.19)
[14400/16389] Time 0.183 ( 0.183) Loss 3.6649e-03 (2.3466e-02) Acc@1 100.00 ( 99.19)
[14500/16389] Time 0.189 ( 0.183) Loss 1.2751e-01 (2.3451e-02) Acc@1 96.88 ( 99.19)
[14600/16389] Time 0.196 ( 0.183) Loss 1.6087e-02 (2.3449e-02) Acc@1 100.00 ( 99.19)
[14700/16389] Time 0.179 ( 0.183) Loss 1.7729e-02 (2.3467e-02) Acc@1 100.00 ( 99.19)
[14800/16389] Time 0.183 ( 0.183) Loss 6.5048e-03 (2.3454e-02) Acc@1 100.00 ( 99.19)
[14900/16389] Time 0.182 ( 0.183) Loss 3.5290e-03 (2.3533e-02) Acc@1 100.00 ( 99.19)
[15000/16389] Time 0.182 ( 0.183) Loss 2.4541e-02 (2.3580e-02) Acc@1 100.00 ( 99.18)
[15100/16389] Time 0.183 ( 0.183) Loss 1.7415e-02 (2.3537e-02) Acc@1 100.00 ( 99.19)
[15200/16389] Time 0.176 ( 0.183) Loss 1.3802e-01 (2.3550e-02) Acc@1 96.88 ( 99.19)
[15300/16389] Time 0.187 ( 0.183) Loss 2.7930e-03 (2.3509e-02) Acc@1 100.00 ( 99.19)
[15400/16389] Time 0.185 ( 0.183) Loss 7.1786e-02 (2.3463e-02) Acc@1 96.88 ( 99.19)
[15500/16389] Time 0.179 ( 0.183) Loss 1.9947e-02 (2.3497e-02) Acc@1 100.00 ( 99.19)
[15600/16389] Time 0.172 ( 0.183) Loss 1.1708e-02 (2.3438e-02) Acc@1 100.00 ( 99.19)
[15700/16389] Time 0.179 ( 0.183) Loss 9.2193e-03 (2.3412e-02) Acc@1 100.00 ( 99.19)
[15800/16389] Time 0.188 ( 0.183) Loss 1.0713e-03 (2.3441e-02) Acc@1 100.00 ( 99.19)
[15900/16389] Time 0.185 ( 0.183) Loss 1.0080e-03 (2.3407e-02) Acc@1 100.00 ( 99.19)
[16000/16389] Time 0.184 ( 0.183) Loss 2.4409e-03 (2.3406e-02) Acc@1 100.00 ( 99.19)
[16100/16389] Time 0.202 ( 0.183) Loss 2.0523e-04 (2.3398e-02) Acc@1 100.00 ( 99.19)
[16200/16389] Time 0.176 ( 0.183) Loss 2.2787e-05 (2.3417e-02) Acc@1 100.00 ( 99.19)
[16300/16389] Time 0.184 ( 0.183) Loss 1.3638e-01 (2.3406e-02) Acc@1 93.75 ( 99.19)
分析其中
从训练日志来看,模型的训练过程显示了以下特点:
-
Loss(损失)变化:初期损失值较高,但随着训练的进行,损失值有逐渐降低的趋势。损失值的波动较大,可能与训练数据的复杂性或模型参数的更新有关。
-
Acc@1(Top-1准确率):模型的Top-1准确率从训练的开始阶段几乎始终保持在100%。偶尔出现96.88%的准确率,可能是由于某些batch的训练数据较难,或是模型在特定阶段遇到了更大的挑战。
-
训练时间:每个batch的训练时间保持相对稳定,平均时间在0.183秒左右。这说明训练过程中的计算负担相对均匀。
-
异常值:在训练过程中,有几次出现了损失值非常高的情况(例如,第2500步的损失为0.1198),这种现象可能表明模型在这些步骤上遇到了一些不寻常的情况,如学习率设置不当或数据中的噪声。
可能的问题
-
学习率:损失的剧烈波动可能与学习率过高有关。尝试降低学习率以使损失更加平稳。
-
数据问题:检查数据是否存在问题,如标注错误或不平衡。异常的损失值可能与数据集中的噪声有关。
-
正则化:考虑使用正则化技术,如权重衰减(L2正则化),以减少模型在训练过程中的过拟合。
总结来说,训练过程显示了良好的Top-1准确率,后面进行打榜
获得获得了0.9688的分数总体来说还不错,我也从中学到了很多,于此分享