1、跑通baseline
Datawahle的速通文档:Docs 里面包含了注册kaggle号,报名比赛,准备工作与环境搭建等等,以及初始代码。
速通之后上传csv文件,得到的acc为0.5774。但具体我并不了解是什么意思,以及其原理、代码都不是很能理解。
故我针对代码进行了详细的学习与解读。
2、代码学习与解读
数据准备:
主办方在第一阶段发布训练集和验证集。参赛者将使用训练集 (train_label.txt) 来训练模型,而验证集 (val_label.txt) 仅用于模型调优。(这里验证集的模型调优作用以及原理我还不是很理解)文件的每一行包含两个部分,分别是图片文件名和标签值(label=1 表示Deepfake图像,label=0 表示真实人脸图像)。
train_label.txt
img_name,target
3381ccbc4df9e7778b720d53a2987014.jpg,1
63fee8a89581307c0b4fd05a48e0ff79.jpg,0
val_label.txt
img_name,target
cd0e3907b3312f6046b98187fc25f9c7.jpg,1
aa92be19d0adf91a641301cfcce71e8a.jpg,0
在第二阶段,办方将发布测试集。在第二阶段,参赛者需要在系统中提交测试集的预测评分文件 (prediction.txt),主办方将在线反馈测试评分结果。文件的每一行包含两个部分,分别是图片文件名和模型预测的Deepfake评分(即样本属于Deepfake图像的概率值)。例如:
prediction.txt
img_name,y_pred
cd0e3907b3312f6046b98187fc25f9c7.jpg,1
aa92be19d0adf91a641301cfcce71e8a.jpg,0.5
环境准备
!pip install timm
timm库我去了解了一下,timm(pytorch image models)是一个包含多种图像识别模型的库,这些模型通常基于pytorch框架,并且提供预训练的权重,方便用户进行迁移学习或特征提取。里面目前内置预训练的模型有将近600个,包含efficientnet系列、resnet系列、vit系列、vgg系列、mobilenet系列,群里看大家聊天的时候都有提到这些模型,后面结合群里的V1采用了efficientnet_b1-32-3-full-unbalanced模型和数据,再次尝试了下发现上传csv文件得到的acc变成了0.9812。(后话,我运行的时候还不大了解)现在就来逐步了解一下~
导入要用到的库
import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True
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 import tqdm_notebook
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']
模型训练与验证
这里代码还挺长的,我们一步一步来看代码。
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__)
以下代码定义的这个类继承自Pytorch的Dataset类,用于加载和预处理图像数据及其对应的标签。
#以下定义的这个类继承自Pytorch的Dataset类,用于加载和预处理图像数据及其对应的标签。
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)
这段代码定义了validate函数,用于在模型的训练过程中,对模型在验证集上的性能进行评估的。它计算了模型在验证集上的平均损失(Loss)和准确率(Top-1准确率),这两个指标是衡量模型性能的重要标准。
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_notebook(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
#计算准确率。首先,通过argmax(1)获取每个预测的最大概率对应的索引(即预测的类别),然后将其与目标类别进行比较。最后,计算准确率的平均值并乘以100转换为百分比
#啊,有点没搞懂这句话。。就是评估模型的性能吧?判断预测正确的数?
losses.update(loss.item(), input.size(0)) #利用update方法更新平均损失和准确率
top1.update(acc, input.size(0)) #input.size(0)获取当前批次的样本数
# measure elapsed time
batch_time.update(time.time() - end)
end = time.time()
# TODO: this should also be done with the ProgressMeter
print(' * Acc@1 {top1.avg:.3f}'
.format(top1=top1))
return top1
这段代码定义了predict函数,用于对测试集进行预测,并可选地应用测试时增强TTA来提高预测地准确性。在本次整个实验中,我们采取的TTA=1。
#对测试集进行预测,并可选地应用测试时增强TTA来提高预测地准确性。我嘞个豆,这是什么意思?
def predict(test_loader, model, tta=10): #tta测试时增强的次数,默认为10
# switch to evaluate mode
model.eval()
test_pred_tta = None
for _ in range(tta):
test_pred = []
with torch.no_grad():
end = time.time()
for i, (input, target) in tqdm_notebook(enumerate(test_loader), total=len(test_loader)):
input = input.cuda()
target = target.cuda()
# compute output
output = model(input)
output = F.softmax(output, dim=1) #对输出应用softmax函数,将输出转换为概率分布
output = output.data.cpu().numpy() #将输出张量从GPU转移到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
进行训练循环,在所给定的数据集上训练一个模型。
#深度学习训练循环,用于在一个给定的数据集上训练一个模型。
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)
加载模型
导入timm库(timm库我在上面的环境准备上有介绍),然后导入efficientnet_b1模型,设置pretrained=True,即指定加载预训练的权重,提高效率。后面的num_classes=2,则表示了模型最后的全连接层是两个输出节点,因为我们这个任务是分辨图片是否为AI伪造的嘛->二分类任务~
然后再用model.cuda()将模型移动到GPU上,好吧,不知道为什么就是觉得这个看着很高级哇。
import timm
model = timm.create_model('efficientnet_b1', pretrained=True, num_classes=2)
#timm.create_model('efficientnet_b1',...):调用timm库中的create_model函数,用于创建efficientnet_b1模型。
#pretrained=True:这个参数指定是否加载预训练的权重。
#num_classes=2:这个参数指定了模型最后的全连接层(分类层)应该有多少个输出节点。这里设置为2,说明改模型被用于一个二分类任务。
model = model.cuda() #将模型移动到GPU上
epoch_num = 3
bs_value = 32
Pytorch训练循环实现
下面就是我们的pytorch训练循环的实现啦,这里的train_loader训练数据加载器中,里面有个二transforms.Compose()里面都是一系列的数据预处理步骤,图像随机水平翻转、随机垂直翻转等等都是数据增强的一种方法,这样可以增加数据集,减少训练过程中过拟合。
接下来还有个验证集数据加载器,但它没有数据增强这一操作。然后就是损失函数,优化器和学习调度器的设置,以及训练循环。
#数据加载器设置
#train_loader用于加载训练数据
train_loader = torch.utils.data.DataLoader(
FFDIDataset(train_label['path'], train_label['target'],
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=bs_value, shuffle=True, num_workers=4, pin_memory=True
)
#transforms.Compose包含了一系列数据预处理步骤,如调整图像大小、随机水平翻转、随机垂直翻转、转换为pytorch张量以及归一化。
#batch_size=bs_value 表示每个批次加载bs_value个样本
#shuffle=True表示在每个epoch开始时,数据将被随机打乱
#num_workers=4指定了用于数据加载的子进程数??这个我没理解
#pin_memory=True尝试将数据复制到CUDA的固定内存中,但仅当使用CUDA时才有效
#验证集数据加载器
val_loader = torch.utils.data.DataLoader(
FFDIDataset(val_label['path'], val_label['target'],
transforms.Compose([
transforms.Resize((256, 256)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
), batch_size=bs_value, shuffle=False, num_workers=4, pin_memory=True
) #验证集没有随机翻转等操作,因为它不需要数据增强
#损失函数、优化器和学习率调度器
criterion = nn.CrossEntropyLoss().cuda() #定义一个交叉熵损失函数,并将其移动到GPU上
optimizer = torch.optim.Adam(model.parameters(), 0.005) #Adam优化器更新模型参数,学习率设置为0.005
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=4, gamma=0.85)
#学习率调度器StepLR将在每个step_size个epoch后,将学习率乘以gamma(这里为0.85)
best_acc = 0.0
#训练循环
for epoch in range(epoch_num):
scheduler.step() #在每轮epoch前,调用学习率调度器的step()方法,更新优化器的学习率
print('Epoch: ', epoch)
train(train_loader, model, criterion, optimizer, epoch)
val_acc = validate(val_loader, model, criterion)
if val_acc.avg.item() > best_acc: 这行代码检查当前验证集上的平均准确率(val_acc.avg.item())是否高于迄今为止记录的最佳准确率(best_acc)。
best_acc = round(val_acc.avg.item(), 2) #使用round函数将其四舍五入到小数点后两位。
torch.save(model.state_dict(), f'./model_{best_acc}.pt')
test_loader = torch.utils.data.DataLoader(
FFDIDataset(val_label['path'], val_label['target'],
transforms.Compose([
transforms.Resize((256, 256)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
), batch_size=bs_value, shuffle=False, num_workers=4, pin_memory=True
)
val_label['y_pred'] = predict(test_loader, model, 1)[:, 1]
val_label[['img_name', 'y_pred']].to_csv('submit.csv', index=None)
终于把代码过完一遍了,呼~逐行读了代码,注释都在上面了。
上面那个链接里边,还有其他的模型以及其运行效果,给大家截图看看:
对比一下,然后我再原来的代码上也尝试了下上面几个模型以及参数,算是亲身体验到了模型以及超参数设置的重要性哇!好的先学到这咯~