2024 DataWhale AI 夏令营 DeepFake
付友友友 2024-08-01 11:01:01 阅读 56
首先,非常感谢DataWhale组织发起的AI夏令营活动,让众多AI爱好者汇聚交流产生思想上的碰撞。其次,感谢赛事官方主持赛事,提供增强AI代码的理解和编写能力的机会。最后,感谢各位大佬的代码或教程的分享,指引新手入门。
发起者:DataWhale
赛事链接:
Deepfake Image Detection:https://www.kaggle.com/competitions/multi-ffdiDeepfake报名:https://www.wjx.cn/vm/mh3Cxns.aspx图像数据集:http://zoloz-open.oss-cn-hangzhou.aliyuncs.com/waitan2024_deepfake_challenge%2F_%E8%B5%9B%E9%81%931%E5%AF%B9%E5%A4%96%E5%8F%91%E5%B8%83%E6%95%B0%E6%8D%AE%E9%9B%86%2Fphase1.tar.gz?Expires=1726603663&OSSAccessKeyId=LTAI5tAfcZDV5eCa1BBEJL9R&Signature=wFrzBHn5bhULqWzlZP7Z74p1g9c%3D
参考资料链接:
DataWhale学习指南:从零入门CV图像竞赛(Deepfake攻防)Baseline:https://www.kaggle.com/code/finlay/deepfake-ffdi-baselineStrong Baseline:https://www.kaggle.com/code/finlay/deepfake-ffdi-stronger-baselinetimm教程:https://www.kaggle.com/competitions/multi-ffdi/discussion/520812
Baseline
深度学习的研究旨在训练一个能满足需求的神经网络,因此本文主要围绕着Baseline中的训练代码进行展开分析。
1. 图像数据处理
图像数据集下载解压后文件存储结构如下所示:
phase 1
|— trainset
|— valset
|— trainset_label.txt
|— valset_label.txt
train_label = pd.read_csv('/kaggle/input/deepfake/phase1/trainset_label.txt')
val_label = pd.read_csv('/kaggle/input/deepfake/phase1/valset_label.txt')
Baseline代码是在Kaggle中的Notebook中运行,因此要读取trainset_label.txt和valset_label.txt需要以上述代码中的路径读取。使用train_label.head(5)
代码,打印一下txt存储的信息如下图所示:
可知txt中包含有图像文件名和图像文件标签。
<code> train_label['path'] = '/kaggle/input/deepfake/phase1/trainset/' + train_label['img_name']
val_label['path'] = '/kaggle/input/deepfake/phase1/valset/' + val_label['img_name']
写入图像路径,以便后续载入图像数据集。
2.载入图像数据集
baseline使用自定义的图像数据读取方法,需要输入一个图像文件路径的列表和一个图像文件标签的列表。自定义需要继承torch的Dataset,并且包含三个魔术方法:一个初始化方法__init__
,一个元素访问方法__getitem__
和一个长度检索方法__len__
。
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)
为了提升模型的泛化性需要对训练数据集进行增强处理。
对训练数据集采用的增强方法包含有:图像尺寸缩放至(256 x 256)、50%的概率水平、垂直翻转和正则化。每个批次随机读取40张图片。
对验证数据集只缩放图像尺寸至(256 x 256)和正则化。每个批次读取40张图像,无需随机读取。
此处并没有读取全量的训练数据集和验证数据集,因为整个数据集近25GB,使用全量进行训练会导致训练时间过长,因此为了减少模型训练时间,只采用了前1000张训练数据集和验证数据集。
train_loader = torch.utils.data.DataLoader(
FFDIDataset(train_label['path'].head(1000), train_label['target'].head(1000),
transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
), batch_size=40, shuffle=True, num_workers=4, pin_memory=True
)
val_loader = torch.utils.data.DataLoader(
FFDIDataset(val_label['path'].head(1000), val_label['target'].head(1000),
transforms.Compose([
transforms.Resize((256, 256)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
), batch_size=40, shuffle=False, num_workers=4, pin_memory=True
)
3. 定义损失函数、优化器
采用CrossEntropyLoss损失函数和Adam优化器,并使用调度器动态调整学习率。
CrossEntropyLoss损失函数常用于分类任务中,它能将预测的分类标签转换成概率,以对数的方式来预测错误程度。其公式如下所示:
L
(
y
,
y
^
)
=
−
∑
i
=
1
n
y
i
l
o
g
(
y
^
i
)
L(y,\hat y)=-\sum_{i=1}^ny_ilog(\hat y_i)
L(y,y^)=−i=1∑nyilog(y^i)
其中,
y
i
y_i
yi 是真实标签,
y
^
i
\hat y_i
y^i 是预测标签。
Adam优化器通过计算一阶和二阶矩估计来为每个参数自适应地调整学习率,初始学习率为0.005。
调度器在每4次训练后,使用gamma值与学习率相乘进行学习率的更新。
criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.Adam(model.parameters(), 0.005)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=4, gamma=0.85)
4.定义训练模型
使用timm库获取在ImageNet1K数据集上预训练的resnet18模型,将输出数量改为2。
model = timm.create_model('resnet18', pretrained=True, num_classes=2)
model = model.cuda()
5.训练过程
将训练过程的损失值、训练时间、精确率等信息省去后的代码如下。model.train()
设置模型为训练状态,for i, (input, target) in enumerate(train_loader)
从数据集中依次读取一个批次的图像和标签,output = model(input)
将图像输入模型得到预测值,loss = criterion(output, target)
再利用预测值和标签输入损失函数计算损失值。选择评价指标为准确率,公式如下:
A
C
C
=
T
P
+
T
N
T
P
+
T
N
+
F
P
+
F
N
ACC=\frac{TP+TN}{TP+TN+FP+FN}
ACC=TP+TN+FP+FNTP+TN
其中,TP为真正例,TN为真负例,FP为假正例,FN为假负例。output.argmax(1).view(-1) == target.float().view(-1)
可将Tensor类型中TP和TN所在的值转为1,TN和FN所在的值转为0,再使用mean
可求其总和除以平均值,即可得到准确率。
optimizer.zero_grad()
将优化器中的梯度清零,loss.backward()
损失进行梯度的反向传播,optimizer.step()
根据梯度更新模型参数。
def train(train_loader, model, criterion, optimizer, epoch):
# switch to train mode
model.train()
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
acc = (output.argmax(1).view(-1) == target.float().view(-1)).float().mean() * 100
# compute gradient and do SGD step
optimizer.zero_grad()
loss.backward()
optimizer.step()
Strong Baseline
strong baseline相较于baseline主要做了以下四个方面的改变:
训练数据集的增强方法;优化器的学习率大小改变选择的模型
1. 增强方法
Baseline和Strong Baseline的增强方法分别如下所示:
transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(),
transforms.ColorJitter(brightness=.5, hue=.3),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
不难发现,只增加了transforms.ColorJitter(brightness=.5, hue=.3)
一行代码,它会对图像的亮度和色调进行随机的变换,最多在原始图像中增强或减少50%的亮度和30%的色调。
2. 优化器的学习率大小
Baseline和Strong Baseline的优化器学习率分别如下所示:
optimizer = torch.optim.Adam(model.parameters(), 0.005)
optimizer = torch.optim.Adam(model.parameters(), 0.007)
降低学习率能减缓模型权重的更新,能更细致的找到局部最小值,但可能也会造成初期损失下降的较慢,导致迭代次数的增加。
3.模型的改变
Baseline和Strong Baseline的模型分别如下所示:
model = timm.create_model('resnet18', pretrained=True, num_classes=2)
model = timm.create_model('efficientnet_b1', pretrained=True, num_classes=2)
Baseline选择的resnet18模型,是一个卷积深度为18层的模型,它主要使用残差块解决了深度网络出现的梯度消失或爆炸导致的权重无法更新的问题。在深度学习中具有重要的意义。
Strong Baseline选择的efficientnet base1模型,相较于resnet18模型要更大,利用NAS神经网络结构搜索的方式平衡网络深度、宽度和分辨率,平衡模型的性能和效率。
提升性能的改进方法
根据实际情况,目前主要有以下三种提升性能的改进方法:
增强图像数据集;使用不同的优化器或设置不同的学习率;设计更合适的模型结构;
1.增强图像数据集
增强图像的目的是通过人工的手段丰富图像数据,以提高模型的泛化能力,在未经训练的图像上获得更优的性能,因此会对图像进行一定程度上的处理。
常用的数据增强方法包含有:
图像形状增强,例如改变图像的大小、旋转变换等图像颜色增强,例如改变亮度、色调等混合方法,例如Mixup、Cutmix
1.1 Mixup
mixup将两个不同的图像和标签按照一定的比例混合创造出一个全新的训练样本,比例以设置两图像的透明度alpha值后进行,并进行混合得到一个新图像,同理也得到一个新标签。优点如下:
增加数据多样性,混合不同的图像和标签,创建更多样化的训练样本,提高模型的鲁棒性;减少过拟合,降低模型对于部分特殊样本的依赖性,降低过拟合的可能性;提高泛化能力,学习到更加泛化的特征表示,提升模型的泛化能力。
1.2 Cutmix
cutmix通过将图像的部分剪切粘贴到另一图像上,创建出新的训练样本,根据裁切区域的大小调整两图像的标签。
此外,还可以使用深度学习方法进行数据增强,例如生成对抗网络GAN,其通过生成器和判别器的相互博弈,使生成器生成的数据越来越趋近真实数据,以次实现数据增强。
2.不同的优化器或不同的学习率
除了Adam优化器外,还有许多的优化器可以进行选择使用,例如SGD、AdamW等。
SGD随机梯度下降具有广泛的适用性,相较于其他优化器调整的参数较少,并且可以设置动量,防止因进入梯度的平坦期而导致的下降速度慢。
AdamW在Adam优化器的基础之上,增加了权重衰减,实现L2正则化,能提高模型的泛化能力,防止模型产生过拟合。
3. 设计新的神经网络
近年来,Vision Transformer(ViT)发展迅速,但其相较于卷积神经网络对数据集的规模要求较高,在大规模数据集上能表现出优越性,但在小规模数据集上表现并不一定比卷积神经网络更好。虽然在大规模数据集上预训练的ViT迁移到小规模数据集上能获得较好的表现,但赛事中要求预训练的数据集为ImageNet1k,因此直接使用ViT模型可能并不能获取较好的性能。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/ba4b6afb60ea404c94128f1cefa07a45.png
Vision Transformer的输入是一个一维的令牌嵌入序列,包含图像块和位置信息。卷积神经网络具有强大的特征提取能力,可利用卷积神经网络提取的特征图替代嵌入图像块。这样做的优点如下:
混合使用两种模型能够同时利用局部特征提取和全局信息建模的优势,提高模型的泛化能力和鲁棒性
但这对模型设计者的代码能力也是一个考验:
不仅要深入了解两种模型的原理和代码结构,还需要将两模型去除部分进行缝合。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。