系列文章目录
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])
两种方法是一样的😁😁
使用函数创建形状相同的张量
- 一种是使用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]])
- 一种是使用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]])
- 使用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)
这里有几点需要注意
- torch.tensor()中的requires_grad=True 表示要对x进行梯度计算
- 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=1∑m(y′−y)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惊险完成。😇😇