深度伪造检测实践记录

本文是 NIS3363 机器学习课程的课程大作业实践报告,实验内容为深度伪造检测相关的实践操作。

任务目标

本作业面向深度伪造检测任务,输入为人脸彩色图像,输出为二分类结果:真实图像记为 Real,标签为 1;伪造图像记为 Fake,标签为 0。根据作业要求,我分别实现了传统机器学习方法和深度学习方法,并完成训练、验证、测试、结果保存与分析。模型效果使用 Accuracy、F1 score 和 AUC 三项指标进行评价。

从工程实现角度看,本项目不仅需要得到最终分类结果,还需要覆盖完整实验流程,包括数据集解析、训练集与验证集划分、模型训练、测试评估、实验结果持久化以及训练曲线可视化。因此,本报告的重点不仅在于“选用了什么模型”,也在于“这些模型是如何被组织成可复现、可比较的实验流程”。

具体内容

整体流程

整个项目的执行流程可以概括为以下五个步骤。

  1. 通过 code/common.py 解析数据集目录,识别 train / validation / test 三类划分以及 fake / real 两类标签。代码支持大小写不同的目录别名,例如 TrainValidationTest
  2. 若数据集本身存在显式验证集,则直接使用;若不存在,则从训练集内部按给定比例随机划分验证集。当前课程提供的原始数据集在实际实验中采用的是第二种方式,截取自 Kaggle 上的 deepfake and real images 数据集。 deepfake and real images
  3. 分别执行传统机器学习训练脚本 train1.py 与深度学习训练脚本 train2.py。两类方法的训练结果都会写入 outputs/<output-name>/ 目录。
  4. 使用 test1.pytest2.py 在测试集上评估保存好的模型,并将测试指标写入对应输出目录中的 test.json
  5. 对 ViT 训练过程,额外使用 plot_train_metrics.pytrain2.py 内部绘图逻辑生成训练曲线图,用于辅助分析模型收敛过程。

因此,本项目并不是单纯调用现成模型得到分数,而是实现了从原始图像到最终实验记录的完整闭环。传统方法和深度学习方法共享数据集解析逻辑,但在特征表示、训练方式和推理策略上存在明显差异,这也构成了后续对比分析的基础。

实施方案

传统机器学习方法

传统方法的核心思想是先构造可解释的手工特征,再用线性分类器完成真假判别。相较于最初只使用灰度 HOG 的方案,当前实现对特征做了增强,将单张图像表示为以下三部分的拼接结果。

  1. 灰度 HOG 特征,用于刻画局部边缘、纹理方向和轮廓结构。
  2. LBP 纹理直方图,用于补充更细粒度的局部微纹理模式。
  3. RGB 三通道的均值与标准差,用于描述整体颜色统计信息。

之所以采用这种组合,是因为深伪图像的异常通常不仅体现为边缘扭曲,也可能表现为肤色分布不自然、局部纹理不一致或压缩痕迹异常。单一 HOG 虽然可解释性较好,但信息维度相对单薄,而 HOG + LBP + 颜色统计的组合能够在不引入复杂深模型的前提下提升表征能力。

在分类器选择上,项目使用 StandardScaler + LinearSVC 构成训练流水线。这样做有两个原因。第一,手工特征不同维度的取值范围不一致,标准化有助于稳定线性分类器的训练过程;第二,线性 SVM 在中等规模特征空间中训练速度较快,作为课程作业中的传统基线具有代表性。训练脚本先在训练子集上拟合、在验证子集上评估,随后再将训练集与验证集合并,重新拟合最终模型并保存,以便测试脚本直接加载。

深度学习方法

深度学习部分采用 Vision Transformer,参考了 MuqadasEjaz 的相关研究,具体为 timm 提供的 vit_base_patch16_224 骨干网络,并在其后接入自定义二分类头。

Deepfake Image Detection

与从头训练卷积网络相比,该方案具有两个明显优势:一是预训练主干具备更强的通用视觉表征能力,二是 Transformer 结构在处理局部纹理与全局空间关系时更灵活,更适合深伪检测这类既依赖局部瑕疵又依赖整体一致性的任务。

当前实现中的深度学习方案包括以下关键设计。

  1. 输入分辨率固定为 224×224,使用标准 ImageNet 均值和方差进行归一化。
  2. 训练阶段通过 Albumentations 进行数据增强,包括水平翻转、轻微旋转、亮度/对比度扰动、轻微模糊和 JPEG 压缩扰动。
  3. 分类头由 LayerNorm + Dropout + Linear + GELU + LayerNorm + Dropout + Linear 构成,比直接接一个线性层更有表达能力。
  4. 损失函数使用带类别权重和 label smoothing 的交叉熵,以缓解类别不均衡与过拟合风险。
  5. 优化器使用 AdamW,骨干网络和分类头分别设置不同学习率。
  6. 学习率调度采用 warmup + cosine 退火。
  7. 若检测到 CUDA,则自动启用 AMP 混合精度;若没有 GPU,则自动回退到 CPU 训练。
  8. 测试阶段默认启用水平翻转 TTA,并将原图预测与翻转图预测取平均,以提升稳定性。

需要说明的是,本项目虽然使用了公开预训练权重,但模型的数据读取、增强、分类头设计、训练循环、验证逻辑、测试逻辑、结果保存和曲线绘制均为自主实现,因此仍符合“模型设计、训练、测试流程需自主实现,可参考公开技术资料”的要求。

核心代码分析

在分析具体模型之前,有必要先说明项目整体代码组织方式。code/common.py 负责公共组件,包括:

  1. 数据集目录解析与大小写别名兼容。
  2. 训练集/验证集的构建逻辑。
  3. 传统特征提取。
  4. 统一的指标计算与 JSON 保存。
  5. 输出目录命名约定。

也就是说,两条实验路线虽然模型不同,但共享了同一套数据解析与结果落盘逻辑,保证了实验流程的一致性和可比较性。

传统方法核心代码分析

传统方法的关键实现集中在 common.pytrain1.py。下面这段代码体现了手工特征的核心构造方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def extract_hog_feature(path: Path, image_size: int = 96) -> np.ndarray:
    rgb = load_rgb_image(path, image_size=image_size)
    gray = np.dot(rgb[..., :3], np.array([0.2989, 0.5870, 0.1140], dtype=np.float32))

    hog_feature = hog(
        gray,
        orientations=9,
        pixels_per_cell=(8, 8),
        cells_per_block=(2, 2),
        block_norm="L2-Hys",
        transform_sqrt=True,
        feature_vector=True,
    ).astype(np.float32)

    lbp = local_binary_pattern((gray * 255.0).astype(np.uint8), P=16, R=2, method="uniform")
    lbp_hist, _ = np.histogram(lbp.ravel(), bins=np.arange(0, 19), range=(0, 18), density=True)
    color_stats = np.concatenate([rgb.mean(axis=(0, 1)), rgb.std(axis=(0, 1))], dtype=np.float32)
    return np.concatenate([hog_feature, lbp_hist.astype(np.float32), color_stats.astype(np.float32)])

这段实现与报告前文中的设计完全一致:先将图像统一缩放,再构造 HOG、LBP 和颜色统计特征,最后拼接成单个向量。当前默认 image_size=96,结合 9 个方向、8×8 cell 与 2×2 block,可得到较高维度但仍可管理的手工特征表示。

在训练环节,train1.py 中的核心分类器定义如下:

1
2
3
4
5
6
pipeline = Pipeline(
    [
        ("scaler", StandardScaler()),
        ("classifier", LinearSVC(C=args.svm_c, class_weight="balanced", dual="auto", random_state=42)),
    ]
)

这里的 StandardScaler 负责标准化特征,LinearSVC 则完成线性判别。class_weight="balanced" 可以在类别样本略有不均衡时自动调整类别权重。训练脚本会先在训练子集上拟合模型、在验证子集上计算指标,再将训练集与验证集合并,重新拟合最终模型并保存到 outputs/hog_svm/ 或指定输出目录下。这种“先验证、后全量重训”的流程,是传统方法中较常见的工程化写法。

深度学习方法核心代码分析

深度学习实现主要集中在 train2.py。从整体流程上看,它包含数据增强、模型定义、训练循环、验证逻辑、最佳 checkpoint 保存以及训练曲线绘制几个部分。

首先是模型结构定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
self.backbone = timm.create_model(
    model_name, pretrained=pretrained, num_classes=0, global_pool="token"
)
feature_dim = self.backbone.num_features
self.head = nn.Sequential(
    nn.LayerNorm(feature_dim),
    nn.Dropout(p=dropout),
    nn.Linear(feature_dim, 512),
    nn.GELU(),
    nn.LayerNorm(512),
    nn.Dropout(p=dropout * 0.67),
    nn.Linear(512, num_classes),
)

这里先通过 timm.create_model 建立 vit_base_patch16_224 主干,并移除默认分类头;随后再接入自定义二分类 head。相比直接使用线性头,这种两层结构更适合将预训练特征映射到当前任务空间。

其次是训练阶段的增强策略:

1
2
3
4
5
6
7
8
9
10
11
12
A.Compose(
    [
        A.Resize(image_size, image_size),
        A.HorizontalFlip(p=0.5),
        A.Rotate(limit=8, p=0.25),
        A.RandomBrightnessContrast(brightness_limit=0.15, contrast_limit=0.15, p=0.3),
        A.GaussianBlur(blur_limit=(3, 5), p=0.1),
        A.ImageCompression(quality_lower=75, quality_upper=100, p=0.25),
        A.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),
        ToTensorV2(),
    ]
)

这部分实现说明深度学习模型并不是简单地“把图片丢给 ViT”,而是显式模拟了深伪检测任务中常见的扰动来源,例如压缩、模糊和亮度变化。这些增强对于提升模型鲁棒性和泛化能力具有实际意义。

最后是训练循环中的关键优化设置。train2.py 中使用了类别权重、AdamW、warmup + cosine 调度、梯度裁剪以及 AMP。并且,脚本只会在验证准确率提升时保存 checkpoint,因此当前保存下来的 outputs/vit/model.pt 对应的是“最佳验证模型”,而不是最后一个 epoch 的模型。这一点也解释了为什么训练轮数较少时,最佳模型往往不出现在最后一轮。

测试结果与分析

结果来源说明

使用 deep_face/ 数据集进行训练和测试,得到的报告对应文件如下:

  1. 传统方法训练结果:outputs/hog_svm/train.json
  2. 传统方法测试结果:outputs/hog_svm/test.json
  3. 深度学习方法测试结果:outputs/vit/test.json
  4. 深度学习训练过程记录:outputs/vit/train.json

定量结果

deep_face/test/ 测试集上的最终结果如下表所示。

方法 Accuracy F1 AUC
HOG + LBP + Color + LinearSVC 68.05% 67.18% 0.7472
ViT-B/16 + 自定义分类头 94.87% 94.64% 0.9877

从表中可以看到,深度学习的 ViT 方法在三项指标上都显著优于传统方法,尤其是 AUC 从 0.7472 提升到了 0.9877,说明深度学习模型对真假图像的排序能力明显更强。

训练过程分析

根据 outputs/vit/train.json,ViT 模型当前实验共训练了 10 个 epoch,其中:

  1. 最佳验证准确率为 99.02%,随对应epoch 为第 9
  2. 第 1 轮训练准确率为 86.00%,验证准确率已达到 94.71%
  3. 第 4 轮训练准确率为 99.10%,验证准确率已达到 98.61%
  4. 第 10 轮训练准确率达到 99.92%,验证准确率为 99.01%

这说明模型在前两到三轮内已经完成了主要的任务适配,之后进入性能平台期。从学习率记录可见,分类头学习率在 warmup 后逐渐下降,训练过程整体平稳,没有出现明显发散现象。验证准确率在第 9 轮达到最高,而第 10 轮略有波动,这也是脚本保存“最佳验证模型”而不是“最后一轮模型”的原因。

ViT 训练曲线

从训练曲线来看,训练损失与验证损失都在前几轮快速下降,训练准确率和验证准确率同步上升,说明当前优化策略能够较快收敛。虽然训练集准确率高于验证集准确率,但两者差距并未异常扩大,而是保持在一个合理的范围内,因此判断不存在非常严重的过拟合。

方法差异分析

传统方法与深度学习方法之间的性能差异,主要来自以下几个方面。

第一,特征表达能力不同。传统方法依赖人工设计的 HOG、LBP 与颜色统计特征,这些特征在一定程度上能反映局部纹理和整体颜色分布,但表达能力仍然有限,难以完整描述深伪图像中的复杂细节。相比之下,ViT 可以在端到端训练中自动学习更高层次、更任务相关的表征。

第二,空间建模能力不同。手工特征通常以局部统计为主,缺乏跨区域的信息整合能力;而 ViT 通过 patch token 与全局注意力机制,能够同时利用局部纹理和整体结构线索,这对人脸深伪检测尤其重要。

第三,训练策略不同。深度学习方法引入了预训练初始化、增强策略、类别权重、label smoothing、学习率调度、TTA 等工程机制,而传统方法的训练流程相对简单。两类方法在工程复杂度上存在明显差异,这也是性能差异的重要来源。

当然,传统方法也并非没有价值。它的优点在于实现简单、可解释性较强、推理成本低。在资源极其有限或需要快速构造基线时,手工特征方法仍然有实际意义。但从本次作业的实验结果来看,对于深伪检测这种高复杂度视觉任务,深度学习方法显然更具优势。

工作总结

收获与体会

本次作业让我较完整地经历了两类图像分类技术路线的实现过程。传统机器学习部分强化了我对“特征设计”这一问题的理解:当模型本身表达能力有限时,特征工程往往直接决定了结果上限。深度学习部分则进一步说明,在复杂视觉任务中,数据增强策略、预训练骨干、优化器选择和推理策略往往与模型结构本身同样重要。

另外,从工程实践角度看,本次作业也让我意识到“能跑通一个模型”和“能形成可复现实验流程”并不是一回事。只有把路径约定、数据划分、结果保存、曲线绘制和测试输出都统一起来,实验结果才真正具有可追踪性和可比较性。

遇到的问题与解决思路

在传统方法部分,单独使用灰度 HOG 时效果不理想,因此我进一步加入了 LBP 与颜色统计特征,以增强对伪造纹理和颜色异常的刻画能力。虽然这种增强不足以追平深度学习模型,但确实提升了传统基线的完整性。

在深度学习部分,较大的模型和较长的训练时间带来了实验管理问题。为避免重复训练,我额外提供了独立的曲线绘图脚本,可以直接读取训练阶段保存的 JSON 结果并重建曲线图。这使得结果分析与模型训练过程解耦,也让实验记录更加规范。


深度伪造检测实践记录
https://youyeyejie.github.io/_posts/深度伪造检测实践记录/
作者
youyeyejie
发布于
2026年6月16日
更新于
2026年6月16日