【Datawhale AI 夏令营2024--CV】baseline解读

一、赛题背景

        随着人工智能技术的迅猛发展,深度伪造技术(Deepfake)正成为数字世界中的一把双刃剑。这项技术不仅为创意内容的生成提供了新的可能性,同时也对数字安全构成了前所未有的挑战。Deepfake技术可以通过人工智能算法生成高度逼真的图像、视频和音频内容,这些内容看起来与真实的毫无二致。然而,这也意味着虚假信息、欺诈行为和隐私侵害等问题变得更加严重和复杂。

        为了应对这一挑战,我们举办了“外滩大会 - 全球Deepfake攻防挑战赛”。该挑战赛旨在邀请全球的参与者开发、测试和改进更加准确、有效和创新的检测模型,以应对各种类型的Deepfake攻击。这些模型将在真实世界的场景中进行测试,从而推动创新防御策略的发展,提高Deepfake识别的准确性。此次挑战赛不仅是对技术的比拼,更是对全球数字安全的一次重要贡献。我们期待着通过这次比赛,能够激发更多的创新思维和技术突破,共同应对Deepfake带来的安全威胁,保护数字世界的安全与真实性。

二、赛题任务

        在这个赛道中,比赛任务是判断一张人脸图像是否为Deepfake图像,并输出其为Deepfake图像的概率评分。参赛者需要开发和优化检测模型,以应对多样化的Deepfake生成技术和复杂的应用场景,从而提升Deepfake图像检测的准确性和鲁棒性。

三、赛题数据集

1、第一阶段

        在第一阶段,主办方将发布训练集和验证集。参赛者将使用训练集 (train_label.txt) 来训练模型,而验证集 (val_label.txt) 仅用于模型调优。文件的每一行包含两个部分,分别是图片文件名和标签值(label=1 表示Deepfake图像,label=0 表示真实人脸图像)。例如:

train_label.txt

img_name,target
3381ccbc4df9e7778b720d53a2987014.jpg,1
63fee8a89581307c0b4fd05a48e0ff79.jpg,0
7eb4553a58ab5a05ba59b40725c903fd.jpg,0
…

val_label.txt

img_name,target
cd0e3907b3312f6046b98187fc25f9c7.jpg,1
aa92be19d0adf91a641301cfcce71e8a.jpg,0
5413a0b706d33ed0208e2e4e2cacaa06.jpg,0
…

2、第二阶段

        在第一阶段结束后,主办方将发布测试集。在第二阶段,参赛者需要在系统中提交测试集的预测评分文件 (prediction.txt),主办方将在线反馈测试评分结果。文件的每一行包含两个部分,分别是图片文件名和模型预测的Deepfake评分(即样本属于Deepfake图像的概率值)。例如:

prediction.txt

img_name,y_pred
cd0e3907b3312f6046b98187fc25f9c7.jpg,1
aa92be19d0adf91a641301cfcce71e8a.jpg,0.5
5413a0b706d33ed0208e2e4e2cacaa06.jpg,0.5
…

3、第三阶段

        在第二阶段结束后,前30名队伍将晋级到第三阶段。在这一阶段,参赛者需要提交代码docker和技术报告。Docker要求包括原始训练代码和测试API(函数输入为图像路径,输出为模型预测的Deepfake评分)。主办方将检查并重新运行算法代码,以重现训练过程和测试结果。

三、评价指标

评估指标

比赛的性能评估主要使用ROC曲线下的AUC(Area under the ROC Curve)作为指标。AUC的取值范围通常在0.5到1之间。若AUC指标不能区分排名,则会使用TPR@FPR=1E-3作为辅助参考。

相关公式:

真阳性率 (TPR):

TPR = TP / (TP + FN)

假阳性率 (FPR):

FPR = FP / (FP + TN)

其中:

  • TP:攻击样本被正确识别为攻击;
  • TN:真实样本被正确识别为真实;
  • FP:真实样本被错误识别为攻击;
  • FN:攻击样本被错误识别为真实。

参考文献:Aghajan, H., Augusto, J. C., & Delgado, R. L. C. (Eds.). (2009). Human-centric interfaces for ambient intelligence. Academic Press.

四、baseline解析

# 统计行数
!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 

!pip install timm

from PIL import Image
Image.open('/kaggle/input/deepfake/phase1/trainset/63fee8a89581307c0b4fd05a48e0ff79.jpg')

  • import torch: 导入PyTorch库。
  • torch.manual_seed(0): 设置PyTorch的随机种子为0,这样可以保证每次运行时生成的随机数是固定的,有助于结果的复现性。
  • torch.backends.cudnn.deterministic = False: 如果使用了CuDNN(CUDA深度神经网络库),此行代码表示不使用确定性算法,可以提高性能。
  • torch.backends.cudnn.benchmark = True: 启用CuDNN的自动寻找最适合当前配置的高效算法,以提升性能。
import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True

导入了一些PyTorch和相关库的模块和函数,用于构建和训练深度学习模型。

  • torchvision.models:包含了常见的预训练模型,如AlexNet、ResNet等。
  • torchvision.transforms:包含常见的图像变换操作,如裁剪、旋转、缩放等。
  • torchvision.datasets:包含常见的数据集,如MNIST、CIFAR-10等。
  • torch.nn:定义了神经网络层的接口和功能。
  • torch.optim:包含了优化器,如SGD、Adam等。
  • torch.autograd.Variable:提供了自动求导机制的变量类型。
  • torch.utils.data.dataset.Dataset:定义了一个抽象的数据集类,用于自定义数据集。
  • timm:一个用于图像模型的库,提供了大量现代化的模型架构。
  • time:Python的时间处理库,通常用于计时或延时操作。
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

导入了一些常见的数据处理和图像处理相关的库。

  • pandas:用于数据操作和分析,例如读取CSV文件。
  • numpy:用于科学计算,支持多维数组和矩阵运算。
  • cv2:OpenCV库,用于图像处理和计算机视觉任务。
  • PIL.Image:Python Imaging Library,用于图像处理,如打开、保存、裁剪等。
  • tqdm_notebook:用于在Jupyter Notebook中显示进度条,便于监控长时间运行的任务进度。
import pandas as pd
import numpy as np
import cv2
from PIL import Image
from tqdm import tqdm_notebook

读取了CSV格式的标签文件,并为每条数据添加了图像路径的列。

  • pd.read_csv():使用pandas库读取CSV文件,将其转换为DataFrame格式。
  • train_label['path']val_label['path']:为每个数据样本添加了图像的完整路径,方便后续读取和处理图像数据。
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 DataFrame 中 target 列中每个不同取值的频数(即每个类别的样本数量)
train_label['target'].value_counts()

# 对 val_label DataFrame 中 target 列进行统计,获取每个不同类别的样本数量。
val_label['target'].value_counts()

# 一个 pandas DataFrame 的函数调用,用于查看 train_label DataFrame 的前 10 行数据
train_label.head(10)

2、模型训练与验证

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()        # 调用 reset 方法初始化对象

    def reset(self):
        self.val = 0        # 当前值初始化为 0
        self.avg = 0        # 平均值初始化为 0
        self.sum = 0        # 值的总和初始化为 0
        self.count = 0      # 更新次数计数初始化为 0

    def update(self, val, n=1):
        self.val = val            # 更新当前值为给定的 val
        self.sum += val * n       # 将 val * n 累加到总和 sum 中
        self.count += n           # 更新计数器 count,增加 n
        self.avg = self.sum / self.count    # 计算新的平均值

    def __str__(self):
        fmtstr = '{name} {val' + self.fmt + '} ({avg' + self.fmt + '})'
        return fmtstr.format(**self.__dict__)

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) + ']'    # 返回格式化后的批次信息字符串

def validate(val_loader, model, criterion):
# 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
    # 将模型设为评估模式,这会影响一些层(如批归一化层和 dropout),使其在评估时表现正常。
    model.eval()

    with torch.no_grad():
        end = time.time()    # 记录每个批次的开始时间
        for i, (input, target) in tqdm_notebook(enumerate(val_loader), total=len(val_loader)):    # 创建一个进度条来显示验证过程中的迭代进度
            # 将输入数据 input 和目标标签 target 移到 GPU 上进行加速计算
            input = input.cuda()
            target = target.cuda()

            # compute output
            # 使用模型 model 对输入 input 进行前向传播,得到输出 output
            output = model(input)    
            # 使用损失函数 criterion 计算模型输出 output 和目标标签 target 的损失值 loss
            loss = criterion(output, target)

            # measure accuracy and record loss
            # 计算模型在当前批次上的准确率
            acc = (output.argmax(1).view(-1) == target.float().view(-1)).float().mean() * 100

            # 使用 AverageMeter 更新损失和准确率的统计信息
            losses.update(loss.item(), input.size(0))
            top1.update(acc, input.size(0))

            # measure elapsed time
            # 使用 AverageMeter 更新每个批次的运行时间
            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
# 模型推理预测
def predict(test_loader, model, tta=10):
    # switch to evaluate mode
    model.eval()    # 将模型切换到评估模式
    
    test_pred_tta = None    # 初始化,用来存储测试时间增强后的预测结果
    for _ in range(tta):    # 开始进行测试时间增强(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)    # 使用深度学习模型 model 进行输入数据 input 的预测,得到模型的输出
                output = F.softmax(output, dim=1)    # 对模型的输出进行 softmax 操作,将其转换为概率分布。dim=1 表示在第一维(通常是类别维度)上进行 softmax 操作
                output = output.data.cpu().numpy()    # 将输出 output 转换为 NumPy 数组,并将其从 GPU 上移动到 CPU 上

                test_pred.append(output)    # 将当前批次的预测结果 output 添加到 test_pred 列表中
        test_pred = np.vstack(test_pred)    # 将 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)

    # 将模型设置为训练模式
    model.train()

    # 初始化计时器
    end = time.time()

    # 遍历训练数据集
    for i, (input, target) in enumerate(train_loader):

        # 将输入数据和目标标签移动到GPU上(如果可用),并设置为非阻塞操作
        input = input.cuda(non_blocking=True)
        target = target.cuda(non_blocking=True)

        # 计算模型的输出
        output = model(input)

        # 计算损失值
        loss = criterion(output, target)

        # 记录损失值
        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))

        # 梯度清零,进行反向传播和优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 测量经过的时间
        batch_time.update(time.time() - end)
        end = time.time()

        # 每隔100个batch打印一次训练进度
        if i % 100 == 0:
            progress.pr2int(i)
class FFDIDataset(Dataset):
    def __init__(self, img_path, img_label, transform=None):
        self.img_path = img_path
        self.img_label = img_label
        
        # 设置数据集的变换操作 transform
        if transform is not None:
            self.transform = transform
        else:
            self.transform = None
    
    def __getitem__(self, index):
        # 将其转换为 RGB 模式的 PIL.Image 对象
        img = Image.open(self.img_path[index]).convert('RGB')
        
        if self.transform is not None:
            img = self.transform(img)
        
        # 标签数据首先通过 np.array 转换为 NumPy 数组,然后再通过 torch.from_numpy 转换为 PyTorch 的 Tensor 类型
        return img, torch.from_numpy(np.array(self.img_label[index]))
    
    # 返回数据集的长度,即数据集中图像的数量
    def __len__(self):
        return len(self.img_path)

3、加载模型

import timm
model = timm.create_model('resnet18', pretrained=True, num_classes=2)
model = model.cuda()
train_loader = torch.utils.data.DataLoader(
    FFDIDataset(train_label['path'].head(1000), train_label['target'].head(1000), 
            transforms.Compose([
                        transforms.Resize((256, 256)),    # 将图像大小调整为256x256像素
                        transforms.RandomHorizontalFlip(),    # 随机水平翻转图像
                        transforms.RandomVerticalFlip(),    # 随机垂直翻转图像        
                        transforms.ToTensor(),    # 将图像转换为Tensor格式
                        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
    # batch_size=40:每个批次的样本数量为40。
    # shuffle=True:每个 epoch 开始时打乱数据,有助于模型学习。
    # num_workers=4:使用多进程加载数据,加快数据读取速度。
    # pin_memory=True:将数据加载到 CUDA 固定内存中,加快 GPU 加速
)
val_loader = torch.utils.data.DataLoader(
    FFDIDataset(val_label['path'].head(1000), val_label['target'].head(1000), 
            transforms.Compose([
                        transforms.Resize((256, 256)),    # 将图像大小调整为256x256像素
                        transforms.ToTensor(),    # 将图像转换为Tensor格式
                        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
)
# 定义了损失函数,这里使用交叉熵损失函数 CrossEntropyLoss
criterion = nn.CrossEntropyLoss().cuda()

# 使用 Adam 优化器来优化模型参数,学习率为 0.005
optimizer = torch.optim.Adam(model.parameters(), 0.005)

# 设置了学习率调度器,每 4 个 epoch 学习率乘以 0.85
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=4, gamma=0.85)

best_acc = 0.0
for epoch in range(2):
    scheduler.step()    # 调用学习率调度器的 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:
        best_acc = round(val_acc.avg.item(), 2)
        torch.save(model.state_dict(), f'./model_{best_acc}.pt')
# 创建了一个名为 test_loader 的数据加载器对象,用于在训练或评估期间批量加载数据
test_loader = torch.utils.data.DataLoader(

    # 将多个图像变换组合在一起的类,这里的变换按顺序作用于每个图像        
    FFDIDataset(val_label['path'], val_label['target'], 

            transforms.Compose([
                        transforms.Resize((256, 256)),    # 将输入图像大小调整为 (256, 256) 像素
                        transforms.ToTensor(),    # 将图像数据转换为 PyTorch 张量
                        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
    # batch_size=40:每个批次包含的图像数量为 40。
    # shuffle=False:不打乱数据顺序,用于验证数据集通常不需要打乱。
    # num_workers=4:用于数据加载的线程数目,这可以加速数据加载过程。
    # pin_memory=True:如果设为 True,数据将会被加载到 CUDA 的固定内存区域,这可以提升 GPU 数据加载的速度。
)

# 使用 predict 函数对验证数据集进行预测,并将预测结果存储在 val_label 数据帧的 'y_pred' 列中
# 选择所有行的第二列(索引为 1)作为预测概率
val_label['y_pred'] = predict(test_loader, model, 1)[:, 1]

# 将'img_name' 和 'y_pred' 两列保存到submit.csv文件中,不包括行索引
val_label[['img_name', 'y_pred']].to_csv('submit.csv', index=None)

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值