Datawhale AI 夏令营2024·第二期(Deepfake攻防挑战赛-图像赛道)Task02

系列文章目录

Datawhale AI 夏令营2024·第二期(Deepfake攻防挑战赛-图像赛道)Task01
Datawhale AI 夏令营2024·第二期(Deepfake攻防挑战赛-图像赛道)Task03



PyTorch基础

小伙伴们,这里的代码部分是出自DataWhale的教程哦,大家可以多多关注一下😁😁😁

感谢DataWhale的教程

PyTorch 是一个开源的机器学习库,专注于深度学习任务。它由Facebook的人工智能研究小组开发并维护,提供了灵活、高效的张量计算(tensor computation)和深度学习模型构建工具。


一、什么是tensor?

Tensors张量是一种特殊的数据结构,它和数组还有矩阵十分相似。在Pytorch中,我们使用tensors来给模型的输入输出以及参数进行编码。 Tensors除了张量可以在gpu或其他专用硬件上运行来加速计算之外,其他用法类似于Numpy中的ndarrays。


二、创建一个tensor

直接从数据创建

import torch
import numpy as np
# 直接从数据创建
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
x_data.shape

我们查看模型的输出结果:
torch.Size([2, 2])

从Numpy创建

np_array = np.array(data)
x_data = torch.from_numpy(np_array)
x_data.shape

输出结果如下:
torch.Size([2, 2])
两种方法是一样的😁😁

使用函数创建形状相同的张量

  1. 一种是使用torch.ones_like(x)函数

创建的张量与x形状相同,同时所有值设为1

x_ones = torch.ones_like(x_data)  
print(f"Ones Tensor: \n {x_ones} \n")

输出结果如下:
Ones Tensor:
tensor([[1, 1],
                ~~~~~~~~~~~~~~~                [1, 1]])

  1. 一种是使用torch.rand_like(x, dtype=torch.float)函数

创建的张量与x形状相同,但元素的值是在[0, 1]区间内随机抽取。同时,dtype参数指定了张量的数据类型为浮点型。

x_rand = torch.rand_like(x_data, dtype=torch.float)  # overrides the datatype of x_data
print(f"Random Tensor: \n {x_rand} \n")

输出结果如下:
Random Tensor:
tensor([[0.1459, 0.1698],
              ~~~~~~~~~~~~~               [0.0881, 0.3301]])

  1. 使用torch.rand()直接创建
tensor = torch.rand(3, 4)

print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")

输出结果如下
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu


三、自动求梯度

1.构造一个函数

x = torch.tensor([[1, 2], [3, 4]], dtype=float, requires_grad=True)
print(x)

y = x + 2
print(y)
print(y.grad_fn)  

z = y * y * 3
out = z.mean()

print(z) 
print(out)  

这里有几点需要注意

  1. torch.tensor()中的requires_grad=True 表示要对x进行梯度计算
  2. y.grad_fn表示是y的梯度属性,比如是AddBackward,或者是MultiBackward

输出结果如下
tensor([[1., 2.],
              ~~~~~~~~~~~~~               [3., 4.]], dtype=torch.float64, requires_grad=True)
tensor([[3., 4.],
              ~~~~~~~~~~~~~               [5., 6.]], dtype=torch.float64, grad_fn=)
<AddBackward0 object at 0x7bce1652c820>
tensor([[ 27., 48.],
              ~~~~~~~~~~~~~               [ 75., 108.]], dtype=torch.float64, grad_fn=)
tensor(64.5000, dtype=torch.float64, grad_fn=)

于是,我们就构造好了y关于x的一个表达式
y = 3 4 ( x + 2 ) 2 y=\frac{3}{4} {(x+2)}^2 y=43(x+2)2

接着,对其求导,得到

2.计算梯度

d y d x = 3 2 ( x + 2 ) \frac{\mathrm{d}y}{\mathrm{d}x}=\frac{3}{2}(x+2) dxdy=23(x+2)

out.backward()
print(x.grad)  # out=0.25*Σ3(x+2)^2

这样,我们就可以愉快地求梯度了😊😊
tensor([[4.5000, 6.0000],
              ~~~~~~~~~~~~~               [7.5000, 9.0000]], dtype=torch.float64)


四、拟合函数

接下来,我们就进行一个小的回归任务,更好地理解梯度计算,从而使模型损失变小的流程👇👇👇

1.数据准备

导入matplotlib的相关包

import matplotlib.pyplot as plt

from IPython.display import set_matplotlib_formats
set_matplotlib_formats('svg')

# 画图就展示到notebook
%matplotlib inline

接着,我们构造一组数据

# numpy 数值计算、矩阵运算,CPU计算
# tensor 数值计算、矩阵运算 CPU 或 GPU计算

# 准备数据
x = np.linspace(0, 10, 100)
y = -3 * x + 4 + np.random.randint(-2, 2, size=100)
# y = -3x + 4
# y = wx + b

plt.scatter(x, y)

这里的x表示从[0, 10]的,包含100个等差值的array。 y = − 3 x + 4 y=-3x+4 y=3x+4同时在x基础上添加一些噪声,np.random.randint(-2, 2, size=100)表示在[-2, 2)之间生成随机的100个数。

输出结果如下

2.定义权重和偏差

我们首先定义一个用来拟合上面 y y y的函数 y ′ y^{\prime} y
y ′ = w x + b y^{\prime}=wx+b y=wx+b

# 需要计算得到的参数
w = torch.ones(1, requires_grad=True)
b = torch.ones(1, requires_grad=True)

# 数据
x_tensor = torch.from_numpy(x)
y_tensor = torch.from_numpy(y)

3.定义损失函数

接着,我们需要定义一个 l o s s loss loss

def rmse(label, pred):
    diff = label - pred
    return torch.sqrt((diff ** 2).mean())


pred = x_tensor * w + b
loss = rmse(y_tensor, pred)
loss  # 误差

这里采用的是 R M S E RMSE RMSE均方误差,其表示为:

R M S E = 1 m ∑ i = 1 m ( y ′ − y ) 2 RMSE=\sqrt{\frac{1}{m}\sum_{i=1}^m {(y^{\prime}-y)}^2} RMSE=m1i=1m(yy)2

4.模型训练

这一部分,我们会定义学习率大小,以便在完成梯度计算后,能够对当前的参数更新程度进行控制。下面执行20次的参数更新。

for _ in range(20):

    # 重新定义一下,梯度清空
    w = w.clone().detach().requires_grad_(True)
    b = b.clone().detach().requires_grad_(True)

    # 正向传播
    pred = x_tensor * w + b
    loss = rmse(y_tensor, pred)
    print(loss)

    loss.backward()
    w = w - w.grad * 0.05  # 学习率
    b = b - b.grad * 0.05

    # 正向传播、计算损失、计算梯度、参数更新
    # 多步的训练,随机梯度下降

输出结果如下:
tensor(21.0304, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(19.3321, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(17.6365, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(15.9444, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(14.2570, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(12.5762, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(10.9047, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(9.2475, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(7.6130, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(6.0182, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(4.5002, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(3.1508, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(2.1971, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(1.8786, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(1.8534, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(1.8452, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(1.8372, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(1.8293, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(1.8214, dtype=torch.float64, grad_fn=<SqrtBackward0>)
tensor(1.8135, dtype=torch.float64, grad_fn=<SqrtBackward0>)

可以看到损失在不断下降,同时,也可以看到 l o s s loss loss的梯度属性是SqrtBackward

接着,观察模型的拟合情况

pred = x_tensor * w + b

plt.scatter(x, y)
plt.plot(x, pred.data.numpy())
plt.legend(['y=-3x+4', 'Network'])

输出结果如下

使用预训练模型

一、准备数据集

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)

二、定义DataLoader

我们首先需要定义Deepfake图像的DataLoader

train_loader = torch.utils.data.DataLoader(
    FFDIDataset(train_label['path'].head(1000), train_label['target'].head(1000), 
            transforms.Compose([
                        transforms.Resize((224,224)),
                        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((224,224)),
                        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
)

test_loader = torch.utils.data.DataLoader(
    FFDIDataset(val_label['path'], val_label['target'], 
            transforms.Compose([
                        transforms.Resize((224,224)),
                        transforms.ToTensor(),
                        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ])
    ), batch_size=40, shuffle=False, num_workers=8, pin_memory=True
)

三.导入训练、验证和预测方法


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)

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
            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()

        # 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):
        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)
                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

四.选择模型

因为时间不太够了,我就试了一下ViT,还是1000条数据

import timm
model = timm.create_model('vit_base_patch32_clip_224', pretrained=True, num_classes=2)
model = model.cuda()

需要注意的是,这里需要按照模型名称,去改一下dataloader中的图片resize大小。

val_label['y_pred'] = predict(test_loader, model, 1)[:, 1]
val_label[['img_name', 'y_pred']].to_csv('submit.csv', index=None)

然后保存csv文件即可,但是可能因为ViT收敛较慢,导致这次的验证结果较低。🌝🌝🌝但是遇到一个奇怪的点是,这次submission的结果是0.493。不是AUC的结果可以是0.5以下嘛?😅😅暂时先搞不明白了。

五.模型训练

criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.Adam(model.parameters(), 0.005)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=4, gamma=0.85)
best_acc = 0.0
for epoch in range(2):
    scheduler.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')

模型融合

一、定义融合模型

这里选用了基本的ResNet18和VGG16进行融合,验证其有效性。👀👀

import torch
import timm
class ModelEnsemble(torch.nn.Module):
    def __init__(self, models):
        super().__init__()
        self.models = torch.nn.ModuleList(models)
    def forward(self, x):
        outputs = [model(x) for model in self.models]
        return torch.mean(torch.stack(outputs), dim=0)
# 加载多个预训练模型
model1 = timm.create_model('resnet18', pretrained=True, num_classes=2)
model2 = timm.create_model('vgg16', pretrained=True, num_classes=2)
# 创建模型融合
ensemble = ModelEnsemble([model1, model2])
ensemble = ensemble.cuda()

这里,我们定义完模型之后,记得将其移动到CUDA上面去。

二、重新定义DataLoader

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
)

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=40, shuffle=False, num_workers=8, pin_memory=True
)

这里我们还是把resize大小调回默认大小,和之前保持一致。

三、模型训练

这部分就是需要添加上刚才定义好的ensemble模型😁😁😁

criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.Adam(ensemble.parameters(), 0.005)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=4, gamma=0.85)
best_acc = 0.0
for epoch in range(2):
    scheduler.step()
    print('Epoch: ', epoch)

    train(train_loader, ensemble, criterion, optimizer, epoch)
    val_acc = validate(val_loader, ensemble, criterion)
    
    if val_acc.avg.item() > best_acc:
        best_acc = round(val_acc.avg.item(), 2)
        torch.save(ensemble.state_dict(), f'./ensemble_{best_acc}.pt')

真是服了,家人们。😂😂模型融合最后出来还是0.49。本来以为比之前的单个ResNet18要高点,不过这种可能就是有偶然性,就是还不明白的一点是为啥这能小于0.5呀。

总结

内容有点粗略,Task02惊险完成。😇😇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值