Datawhale AI 夏令营-多模态方向-Task3笔记

学习

数据增强是一种在机器学习和深度学习领域常用的技术,尤其是在处理图像和视频数据时。数据增强的目的是通过人工方式增加训练数据的多样性,从而提高模型的泛化能力,使其能够在未见过的数据上表现得更好。数据增强涉及对原始数据进行一系列的变换操作,生成新的训练样本。这些变换模拟了真实世界中的变化,对于图像而言,数据增强包括例如视角、光照、遮挡等情况,使得模型能够学习到更加鲁棒的特征表示。

如果数据增强的变换操作与目标任务的实际场景不符,比如在不需要旋转的图像任务中过度使用旋转,那么这些变换可能会引入无关的噪音。此外,过度的数据增强,比如极端的亮度调整、对比度变化或大量的噪声添加,可能会导致图像失真,使得模型难以学习到有效的特征。

在深度学习中,对输入数据进行归一化是一个标准步骤。归一化有助于加快模型的收敛速度,并提高数值稳定性。对于验证集,应该避免使用如随机翻转等可能引入不必要噪音的增强方法。通常,验证集只需要进行必要的预处理,如调整大小和归一化。

学习- 九月助教老师的图像数据增强方法实操代码

[九月]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()

这个函数用于绘制图像,并在图像上绘制边界框和分割掩码。

  1. 检查输入图像格式

    if not isinstance(imgs[0], list):
        imgs = [imgs]
    

    确保输入是二维网格,即使只有一行图像。

  2. 创建子图

    num_rows = len(imgs)
    num_cols = len(imgs[0])
    _, axs = plt.subplots(nrows=num_rows, ncols=num_cols, squeeze=False)
    

    根据图像的行数和列数创建相应数量的子图。

  3. 处理每个图像

    for row_idx, row in enumerate(imgs):
        for col_idx, img in enumerate(row):
            ...
    

    遍历每个图像,进行处理。

  4. 提取目标信息

    if isinstance(img, tuple):
        img, target = img
        ...
    

    检查图像是否包含目标信息(如边界框和分割掩码)。

  5. 转换图像格式

    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)
    

    将图像转换为适合显示的格式。

  6. 绘制边界框和分割掩码

    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)
    
  7. 显示图像

    ax.imshow(img.permute(1, 2, 0).numpy(), **imshow_kwargs)
    ax.set(xticklabels=[], yticklabels=[], xticks=[], yticks=[])
    
  8. 设置行标题(如果有):

    if row_title is not None:
        for row_idx in range(num_rows):
            axs[row_idx, 0].set(ylabel=row_title[row_idx])
    
  9. 调整布局

    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)

分析其中

从训练日志来看,模型的训练过程显示了以下特点:

  1. Loss(损失)变化:初期损失值较高,但随着训练的进行,损失值有逐渐降低的趋势。损失值的波动较大,可能与训练数据的复杂性或模型参数的更新有关。

  2. Acc@1(Top-1准确率):模型的Top-1准确率从训练的开始阶段几乎始终保持在100%。偶尔出现96.88%的准确率,可能是由于某些batch的训练数据较难,或是模型在特定阶段遇到了更大的挑战。

  3. 训练时间:每个batch的训练时间保持相对稳定,平均时间在0.183秒左右。这说明训练过程中的计算负担相对均匀。

  4. 异常值:在训练过程中,有几次出现了损失值非常高的情况(例如,第2500步的损失为0.1198),这种现象可能表明模型在这些步骤上遇到了一些不寻常的情况,如学习率设置不当或数据中的噪声。

可能的问题

  1. 学习率:损失的剧烈波动可能与学习率过高有关。尝试降低学习率以使损失更加平稳。

  2. 数据问题:检查数据是否存在问题,如标注错误或不平衡。异常的损失值可能与数据集中的噪声有关。

  3. 正则化:考虑使用正则化技术,如权重衰减(L2正则化),以减少模型在训练过程中的过拟合。

总结来说,训练过程显示了良好的Top-1准确率,后面进行打榜
获得在这里插入图片描述获得了0.9688的分数总体来说还不错,我也从中学到了很多,于此分享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ardor-G

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值