Deepfake是什么?
Deepfake是一种使用人工智能技术生成的伪造媒体,特别是视频和音频,它们看起来或听起来非常真实,但实际上是由计算机生成的。这种技术通常涉及到深度学习算法,特别是生成对抗网络(GANs),它们能够学习真实数据的特征,并生成新的、逼真的数据。
Deepfake技术虽然在多个领域展现出其创新潜力,但其滥用也带来了一系列严重的危害。在政治领域,Deepfake可能被用来制造假新闻或操纵舆论,影响选举结果和政治稳定。经济上,它可能破坏企业形象,引发市场恐慌,甚至操纵股市。法律体系也面临挑战,因为伪造的证据可能误导司法判断。此外,深度伪造技术还可能加剧身份盗窃的风险,成为恐怖分子的新工具,煽动暴力和社会动荡,威胁国家安全。
深度伪造技术通常可以分为四个主流研究方向:
-
面部交换专注于在两个人的图像之间执行身份交换;
-
面部重演强调转移源运动和姿态;
-
说话面部生成专注于在角色生成中实现口型与文本内容的自然匹配;
-
面部属性编辑旨在修改目标图像的特定面部属性。
Kaggle赛题背景和任务
- 背景:深度伪造技术的快速发展带来了新的挑战和安全威胁,比赛旨在开发和优化检测模型以提升Deepfake图像检测的准确性和鲁棒性。
- 任务:判断一张人脸图像是否为Deepfake图像,并输出其为Deepfake图像的概率评分。
数据集
- 训练集和验证集:提供的视频文件和对应标签,标签为0表示真实,1表示深度伪造。
Baseline 代码解析
1. 检查文件行数
使用 wc -l
命令统计文件的行数,用于确认训练集和验证集中的样本数量。
!wc -l /kaggle/input/ffdv-sample-dataset/ffdv_phase1_sample/train_label.txt
!wc -l /kaggle/input/ffdv-sample-dataset/ffdv_phase1_sample/val_label.txt
2. 显示视频
在 Jupyter Notebook 中嵌入并播放视频,用于检查视频数据是否正常。
from IPython.display import Video
Video("/kaggle/input/ffdv-sample-dataset/ffdv_phase1_sample/valset/00882a2832edbcab1d3dfc4cc62cfbb9.mp4", embed=True)
3. 安装必要库
安装所需的库,包括 moviepy、librosa、matplotlib、numpy 和 timm,这些库用于视频处理、音频处理、数据处理和模型训练。
!pip install moviepy librosa matplotlib numpy timm
4. 导入库和设置
导入所需的库并进行相关设置。torch.manual_seed(0) 设置随机种子以保证结果的可重复性。torch.backends.cudnn.deterministic 和 torch.backends.cudnn.benchmark 用于优化 GPU 运算性能。
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, glob, os
from PIL import Image
import moviepy.editor as mp
import librosa
5. 生成 MEL 频谱图
从视频文件中提取音频,生成 MEL 频谱图,并将其转换为图像格式,以便后续模型使用。
def generate_mel_spectrogram(video_path, n_mels=128, fmax=8000, target_size=(256, 256)):
# 提取音频
audio_path = 'extracted_audio.wav'
video = mp.VideoFileClip(video_path)
video.audio.write_audiofile(audio_path, verbose=False, logger=None)
# 加载音频文件
y, sr = librosa.load(audio_path)
# 生成MEL频谱图
S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=n_mels)
# 将频谱图转换为dB单位
S_dB = librosa.power_to_db(S, ref=np.max)
# 归一化到0-255之间
S_dB_normalized = cv2.normalize(S_dB, None, 0, 255, cv2.NORM_MINMAX)
# 将浮点数转换为无符号8位整型
S_dB_normalized = S_dB_normalized.astype(np.uint8)
# 缩放到目标大小
img_resized = cv2.resize(S_dB_normalized, target_size, interpolation=cv2.INTER_LINEAR)
return img_resized
6. 创建文件夹并生成图像
创建必要的文件夹,并将视频文件转换为 MEL 频谱图图像,分别存储在训练集和验证集文件夹中。
!mkdir ffdv_phase1_sample
!mkdir ffdv_phase1_sample/trainset
!mkdir ffdv_phase1_sample/valset
for video_path in glob.glob('/kaggle/input/ffdv-sample-dataset/ffdv_phase1_sample/trainset/*.mp4')[:400]:
mel_spectrogram_image = generate_mel_spectrogram(video_path)
cv2.imwrite('./ffdv_phase1_sample/trainset/' + video_path.split('/')[-1][:-4] + '.jpg', mel_spectrogram_image)
for video_path in glob.glob('/kaggle/input/ffdv-sample-dataset/ffdv_phase1_sample/valset/*.mp4'):
mel_spectrogram_image = generate_mel_spectrogram(video_path)
cv2.imwrite('./ffdv_phase1_sample/valset/' + video_path.split('/')[-1][:-4] + '.jpg', mel_spectrogram_image)
7. 平均计量器和进度计量器类
定义用于跟踪和显示训练和验证过程中指标的类,如损失和准确率。
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__)
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) + ']'
8. 验证函数
定义验证过程,计算模型在验证集上的损失和准确率。
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)
# 切换到评估模式
model.eval()
with torch.no_grad():
end = time.time()
for i, (input, target) in enumerate(val_loader):
input = input.cuda()
target = target.cuda()
# 计算输出
output = model(input)
loss = criterion(output, target)
# 记录损失和准确率
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))
# 记录消耗时间
batch_time.update(time.time() - end)
end = time.time()
print(' * Acc@1 {top1.avg:.3f}'.format(top1=top1))
return top1
9. 预测函数
定义预测过程,进行多次 TTA(Test Time Augmentation)预测并返回最终结果。
def predict(test_loader, model, tta=10):
# 切换到评估模式
model.eval()
test_pred_tta = None
for _ in range(tta):
test_pred = []
with torch.no_grad():
for i, (input, target) in enumerate(test_loader):
input = input.cuda()
target = target.cuda()
# 计算输出
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
10. 训练函数
定义训练过程,计算模型在训练集上的损失和准确率,并进行梯度下降优化。
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):
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()
if i % 100 == 0:
progress.pr2int(i)
11. 读取标签文件
读取训练集和验证集标签文件,并将视频文件名转换为相应的图像文件路径。
train_label = pd.read_csv("/kaggle/input/ffdv-sample-dataset/ffdv_phase1_sample/train_label.txt")
val_label = pd.read_csv("/kaggle/input/ffdv-sample-dataset/ffdv_phase1_sample/val_label.txt")
train_label['path'] = '/kaggle/working/ffdv_phase1_sample/trainset/' + train_label['video_name'].apply(lambda x: x[:-4] + '.jpg')
val_label['path'] = '/kaggle/working/ffdv_phase1_sample/valset/' + val_label['video_name'].apply(lambda x: x[:-4] + '.jpg')
train_label = train_label[train_label['path'].apply(os.path.exists)]
val_label = val_label[val_label['path'].apply(os.path.exists)]
12. 自定义数据集类
定义自定义数据集类,用于加载和预处理图像数据。
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)
13. 创建数据加载器
创建训练集和验证集的数据加载器,并应用图像预处理和数据增强。
train_loader = torch.utils.data.DataLoader(
FFDIDataset(train_label['path'].values, train_label['target'].values,
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=12, pin_memory=True
)
val_loader = torch.utils.data.DataLoader(
FFDIDataset(val_label['path'].values, val_label['target'].values,
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=10, pin_memory=True
)
14. 模型定义和优化器
定义模型(使用 resnet18
),损失函数,优化器和学习率调度器。进行训练和验证,并保存验证集上表现最好的模型。
model = timm.create_model('resnet18', pretrained=True, num_classes=2)
model = model.cuda()
criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.Adam(model.parameters(), 0.003)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=4, gamma=0.85)
best_acc = 0.0
for epoch in range(10):
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')
15. 生成提交文件
生成验证集的预测结果,并将预测结果与提交模板合并,生成最终的提交文件 submit.csv
。
val_pred = predict(val_loader, model, 1)[:, 1]
val_label["y_pred"] = val_pred
submit = pd.read_csv("/kaggle/input/multi-ffdv/prediction.txt.csv")
merged_df = submit.merge(val_label[['video_name', 'y_pred']], on='video_name', suffixes=('', '_df2'), how='left', )
merged_df['y_pred'] = merged_df['y_pred_df2'].combine_first(merged_df['y_pred'])
merged_df[['video_name', 'y_pred']].to_csv('submit.csv', index=None)
16.baseline结果
17.baseline改进
将当前的模型定义从ResNet18改为更强大的预训练模型,如EfficientNet-B3或ResNet50。这些模型在许多图像识别任务上表现优异,并且在很多深度伪造检测任务中也取得了很好的效果。
model = timm.create_model('efficientnet_b3', pretrained=True, num_classes=2)
model = model.cuda()
criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=2, verbose=True)
增加更多的数据增强手段,例如旋转、缩放、颜色抖动等,以增强模型的泛化能力。
train_loader = torch.utils.data.DataLoader(
FFDIDataset(train_label['path'].values, train_label['target'].values,
transforms.Compose([
transforms.Resize((256, 256)),
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(),
transforms.RandomRotation(20),
transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3, hue=0.3),
transforms.RandomResizedCrop(256, scale=(0.8, 1.0)), # 随机缩放
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
), batch_size=40, shuffle=True, num_workers=12, pin_memory=True
)
使用CutMix或MixUp等高级数据增强技术。
def cutmix_data(x, y, alpha=1.0):
lam = np.random.beta(alpha, alpha)
rand_index = torch.randperm(x.size()[0]).cuda()
target_a = y
target_b = y[rand_index]
bbx1, bby1, bbx2, bby2 = rand_bbox(x.size(), lam)
x[:, :, bbx1:bbx2, bby1:bby2] = x[rand_index, :, bbx1:bbx2, bby1:bby2]
lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (x.size()[-1] * x.size()[-2]))
return x, target_a, target_b, lam
def rand_bbox(size, lam):
W = size[2]
H = size[3]
cut_rat = np.sqrt(1. - lam)
cut_w = int(W * cut_rat)
cut_h = int(H * cut_rat)
cx = np.random.randint(W)
cy = np.random.randint(H)
bbx1 = np.clip(cx - cut_w // 2, 0, W)
bby1 = np.clip(cy - cut_h // 2, 0, H)
bbx2 = np.clip(cx + cut_w // 2, 0, W)
bby2 = np.clip(cy + cut_h // 2, 0, H)
return bbx1, bby1, bbx2, bby2
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):
input = input.cuda(non_blocking=True)
target = target.cuda(non_blocking=True)
# CutMix 数据增强
input, target_a, target_b, lam = cutmix_data(input, target)
optimizer.zero_grad()
output = model(input)
loss = lam * criterion(output, target_a) + (1 - lam) * criterion(output, target_b)
loss.backward()
optimizer.step()
# 计算准确率并记录损失
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))
# 记录消耗时间
batch_time.update(time.time() - end)
end = time.time()
if i % 100 == 0:
progress.pr2int(i)
结语:
在本次Baseline模型分析中,我们使用Pandas库读取并准备训练集和验证集的标签数据,将图片路径与标签结合。定义
generate_mel_spectrogram
函数,从视频文件中提取音频并生成MEL频谱图,转换为图像格式。实现train
、validate
和predict
函数,用于模型的训练、验证和预测。初始化resnet18
预训练模型,使用Adam优化器和交叉熵损失函数进行训练,并通过学习率调度器优化训练过程。在验证过程中,保存性能最佳的模型权重。最后,使用训练好的模型对验证集进行预测,生成submit.csv
文件并合并为最终提交文件。这些步骤确保了模型的有效训练和评估。在此基础上,我们对baseline做了一些改进:引入了更强大的预训练模型(如EfficientNet-B3),并增加了更多的数据增强手段(如CutMix)以提升模型的泛化能力。这些改进显著提高了模型的准确率和鲁棒性,为深度伪造检测提供了更有效的解决方案。
如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!
欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。
谢谢大家的支持!