基于ResNet50的CNN真假图片识别:PyTorch预训练模型微调实战

发布时间:2026/10/5 9:12:31

基于ResNet50的CNN真假图片识别:PyTorch预训练模型微调实战 简介一套基于 ResNet 与 CNN 的真假图片识别训练代码包面向具备 Python 基础、希望快速上手图像分类实战或完成课程设计的读者主要用于通过深度学习完成真假图像二分类任务。代码不含数据集下载后只需将自定义图片放入对应文件夹即可运行运行后自动划分训练集与验证集并支持分类类别数的自动适配新增分类目录也无需修改代码。压缩包共 7 个文件包含 3 个 Python 脚本分别用于生成含路径与标签的 txt 数据清单、读取 txt 并完成 CNN 模型训练、基于 PyQt 展示识别界面、1 份说明文档、1 个依赖列表及 2 张图片放置示意图整体大小仅 190KB。脚本代码提供逐行注释训练过程中会显示进度条、准确率和损失值每个训练轮次结束后自动保存日志和模型权重便于分析训练效果并调整超参数说明文档还梳理了运行流程与环境配置要点降低上手难度。目前已有 46 人学习浏览适合图像识别入门实践、课程设计或二次开发参考。1. 从resnet模型开始真假图片识别为什么值得用CNN认真做一次拿到一个「resnet模型-通过CNN训练识别真假图片-不含数据集图片-含逐行注释和说明文档.zip」这样的包第一反应是“又一个图像二分类项目”。但真在业务里做过图片审核、物品图片盗用排查或者批量素材合规检查的人会知道真假图片识别不是“能跑就行”的玩具。它在供应链图片审核、原创素材校验、历史照片数字化归档这些场景里是一个需求很真实、但经常被做砸的活儿——砸在数据集没组织好、模型选浅了、训练和验证混在一起。这个项目包走的是最常见的可靠方案ResNet做特征提取主干CNN训练做真假二分类pyTorch实现训练脚本里带逐行注释图片集不随包附带需要自己按文档准备。它适合正在做毕业设计、或者要给业务方快速出一个可演示原型的从业者不适合指望“解压就能出模型”的人——因为没有数据集图片这个事实决定了你得先学会组织数据再去谈训练。2. ResNet与CNN的分工为什么二分类也要认真选主干2.1 为什么是ResNet而不是普通CNN或VGG真假图片识别本质上是一个图像二分类问题。用CNN做二分类网络结构上可以非常浅比如三层卷积加两个全连接层二三十万参数也能在几千张图上收敛。但问题是真实图片和伪造图片——尤其是经过修图软件局部处理、人脸生成、或者压缩重采样过的图片——它们的差异往往不在整体亮度、色调这种粗粒度特征而在于局部纹理异常、边缘过渡不自然、噪声分布不一致这类细粒度线索。浅层CNN的感受野太小最后几层能拿到的语义信息太粗糙很容易把“整体像真的”误判成“是真的”。ResNet在这里的核心价值是残差连接。它解决了网络加深时梯度消失和退化问题让模型可以堆到50层甚至更深而不担心训练崩掉。更深的主干意味着最后分类层看到的是“高层语义多尺度纹理”的组合特征这对真假图片识别至关重要。常见做法是在torchvision里加载ResNet50的预训练权重把最后一层全连接换成二分类输出然后用自有数据做全量微调或冻结微调。ResNet18不是不能用但如果你手里有足够的真实和伪造样本50的容量明显更够用。2.2 残差块和瓶颈结构如何帮助模型抓住伪造痕迹ResNet50使用的是bottleneck残差块形状是1×1卷积降维、3×3卷积做特征提取、1×1卷积升维。中间的3×3卷积是真正接触图像特征的层两边的1×1负责控制通道数把计算量压下来。残差连接做的则是把输入直接加到输出上。这个设计对真假图片识别有一个很实际的好处网络在某一段学到“这里要把图片当成真的”时可以直接通过恒等映射绕过某些层保留原始纹理信息而不必担心中间层的信息瓶颈把细节滤掉。伪造痕迹往往只出现在局部比如人脸眼角的轻微液化痕迹、阴影边缘的对不齐这些线索需要网络既能关注局部又能在深层整合全局上下文残差结构正好平衡了这两点。在项目的逐行注释里我建议重点看两段一是定义残差块的forward部分注意shortcut分支在输入输出通道不一致时怎么用1×1卷积对齐二是主干网络最后怎么做全局平均池化这决定了全连接层输入向量的维度。2.3 预训练模型是必需品冷启动和微调之间差着几十个小时真假图片检测不是那种可以从随机初始化开始硬训的任务。真实图片和伪造图片之间的差异很多时候非常微妙模型需要先在ImageNet这类大规模数据集上学到通用的纹理、边缘、物体形状表征再迁移到真假判断上。套用热词很自然地说resnet预训练模型在这个场景下不是一个可选项而是默认起点。从零训练一个ResNet50在单张普通显卡上跑完ImageNet级别的收敛通常要按天算时间而加载torchvision自带的resnet50预训练权重通常几分钟内就能在验证集上看到一个不差的起点。常见的两种微调模式我一般这样区分使用如果手头数据只有两三千张冻结backbone的前几层只训练最后几个残差块和分类头能显著降低过拟合风险如果数据量过万或者伪造方式比较多样就直接全量微调让backbone也能适应伪造痕迹的分布。项目包里的脚本一般默认开着全量微调如果你按文档走了冻结微调路线需要留意分类头的学习率要设大一点backbone的学习率设小一点避免一上来就把预训练权重冲坏。策略数据量backbone学习率分类头学习率收敛速度过拟合风险冻结backbone 500001e-3极快低全量微调 100001e-41e-3慢中渐进解冻5000-100000 → 5e-51e-3中中低3. 自己动手训练一个真假图片识别模型目录组织、加载与训练3.1 项目包的正确打开方式先看说明文档再动代码这类zip包到手我习惯先解压看目录结构而不是直接双击运行某个py文件。通常里面会有train.py、models.py、predict.py、requirements.txt和一份说明文档。说明文档会告诉你数据该放在哪里、标签文件是什么格式、训练好的模型保存路径是什么。没有数据集图片是这个包的一个重要特点意味着你要按文档要求自己准备数据。第一次运行前做三件事装依赖、建目录、放图。依赖通常就三件套torch、torchvision、Pillow有条件加一个tensorboard做训练可视化。目录结构我建议按下面这样建这是最不容易出错的data/ train/ real/ img_001.jpg img_002.jpg fake/ img_001.jpg val/ real/ fake/ checkpoints/real目录下放真实图片fake目录下放各类伪造、修图、AI生成的图片。验证集同理。这种目录组织方式配合torchvision的ImageFolder来做标签映射是最省事的它会自动把real目录映射为类别0fake映射为类别1。如果你不想按目录组织也可以准备一个CSV里面两列图片路径和标签在自定义Dataset里读。两种路线都能走通但目录组织在调试阶段更直观看一眼文件夹就知道数据分没分错。3.2 train.py核心段图像预处理与数据加载数据加载是整个真假图片识别项目里最容易被低估的部分。ResNet50的输入是224×224的三通道图片训练时我不会只用CenterCrop硬裁而是用RandomResizedCrop加RandomHorizontalFlip做数据增强让模型见过更多尺度和位置的真假纹理不至于因为训练时图片大小、方向跟验证时不一致而误判。ColorJitter在真假检测里要慎用。真实图片和伪造图片的差异有一部分就在颜色分布上你把亮度、对比度、饱和度随机乱调等于给模型制造了一堆本不存在的“伪造痕迹”。# 训练集图像变换 train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.6, 1.0)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 验证集不做随机增强只做固定缩放和裁剪 val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])这里的标准化参数用的是ImageNet的均值和标准差。因为加载的是resnet预训练模型输入分布必须保持和预训练时一致否则预训练权重从一开始就工作在错误的数据分布上迁移效果大打折扣。随机裁剪的尺度范围我习惯设置在0.6到1.0之间范围再大容易把伪造痕迹集中的区域裁掉模型就学不到关键线索了。3.3 train.py核心段ResNet50加载与分类头替换torchvision里加载ResNet50模型的代码只有一行但这一行有两处细节值得关注。第一处是pretrainedTrue它会自动下载ImageNet预训练权重到本机缓存目录第二处是替换最后一层全连接层原来输出1000类现在改成2类。import torch import torch.nn as nn import torchvision.models as models model models.resnet50(pretrainedTrue) # 加载ImageNet预训练权重 num_features model.fc.in_features # 取出分类头输入维度ResNet50是2048 model.fc nn.Linear(num_features, 2) # 替换成真假二分类头 device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device)替换分类头时num_features model.fc.in_features这一步很关键它保证了不管主干是ResNet18还是ResNet50代码都能自适应地拿到正确的输入维度。如果你在加载模型时看到CUDA显存不足可以改batch size或者把模型和数据都放到CPU上跑一个极小的epoch验证流程是否通顺。分类头只有两层线性映射参数量很小随机初始化对整体影响不大但如果不冻结backbone做全量微调这个分类头会被训练得很快这是正常的。3.4 训练循环损失函数、优化器选择和每个epoch的安排损失函数上真假图片二分类没有太多花哨选择CrossEntropyLoss是标准答案。优化器我用AdamW或SGD都有个人偏好在微调场景下用SGD加动量学习率1e-3到3e-3momentum0.9weight_decay设为1e-4。AdamW收敛更快但有时会陷入尖锐极小值SGD收敛更稳最终泛化通常好一点。criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lr0.001, momentum0.9, weight_decay1e-4) # 每5个epoch把学习率降到原来的0.1倍让后期收敛更平稳 scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.1) for epoch in range(30): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() scheduler.step() # 每个epoch结束在验证集上算一次准确率同时保存模型 val_acc evaluate(model, val_loader, device) print(fEpoch [{epoch1}/30] Loss: {running_loss/len(train_loader):.4f} Val Acc: {val_acc:.2f}%) torch.save(model.state_dict(), fcheckpoints/resnet50_fake_epoch{epoch1}.pth)训练循环里有一个不太显眼但很实际的安排每个epoch结束时都保存一次模型而不是只保存最后一个。原因很朴素训练过程中验证集准确率是波动的可能第17个epoch的模型比第22个epoch更好用只留最后一个等于赌运气。按epoch多个存档训练完再回头看验证准确率挑权重成本最低也最稳。3.5 验证脚本的写法准确率是基础指标但不是唯一指标如果脚本只打印准确率我会觉得还不够。真假图片识别如果被用在审核流程里误判真实图片为伪造的代价和漏掉伪造图片的代价是不对等的。这种场景下只看整体准确率会被类别不平衡误导假设数据里有90%的真实图片模型全部预测真实也能拿到90%准确率等于完全没学到东西。所以我习惯在验证脚本里额外输出每个类别的召回率和混淆矩阵至少要把验证集的预测结果按真实标签分文件保存出来方便肉眼检查。预训练模型对真假图片的判断能力在第一个epoch后通常就能到70%以上的验证准确率这个数字本身就是迁移学习的价值证明。如果你发现第一个epoch连随机水平都上不去先别调网络结构优先检查标签是不是反了、预处理有没有做归一化、训练集图片路径有没有加载对。这三个原因贡献了这类项目里八成以上的起步失败。shuffle这个参数容易被人忽视训练集loader里一定要写True验证集loader保持False保持评估有序。4. 训练ResNet识别真假图片的5个经典翻车点与排查顺序4.1 数据集目录和标签对不上训练Loss下降但验证准确率只有50%现象训练损失一路走低train loader每轮都在输出数据但验证集准确率始终在50%附近波动看上去像模型“没在学”。原因最常见的是目录组织或标签映射出了问题。ImageFolder依赖子目录名排序生成标签real和fake两个文件夹名字面顺序没问题但如果你自定义Dataset里按文件名前缀判断真假或者CSV的标签列值写反了模型等于在学一个完全随机的映射。解决先打印前20个batch的(图片路径, 标签对)肉眼确认real类的标签确实是0、fake类是1。或者直接算一下验证集上的预测结果分布如果模型把绝大多数真实图片判为fake而fake判为real不用怀疑标签反了。4.2 预训练权重下载失败导致冷启动训练一小时过去准确率还是随机水平现象模型能跑但前10个epoch的loss下降极慢验证准确率在50%-60%之间磨蹭整个训练过程像是要几十个小时才能有起色。原因pretrainedTrue在torchvision里会从官方URL下载权重。如果你在的内网环境屏蔽了外部下载源权重没有下载成功代码静默退回到了随机初始化你等于在从零硬训ResNet50。解决第一次加载模型之前先单独用一个脚本下载预训练权重到缓存目录并打印文件大小确认不是坏文件。命令行里设TORCH_HOME环境变量指到你有读写权限的目录避免因为缓存目录权限问题反复触发下载。排查顺序检查对象判断方法1标签映射打印20个batch的路径和标签2预训练权重缓存目录权重文件是否存在、大小是否正常3图像预处理确认Normalize参数非空且与训练一致4数据加载train loader和val loader图片数量是否接近预期5学习率设置前3个epoch loss是否明显下降4.3 验证集掺了训练样本准确率99%以为成功一换新图就翻车现象验证集准确率一路涨到98%、99%模型看起来非常能打。但你拿一批全新的图片过来测试准确率直接掉到70%以下。原因数据没按图片去重。有些伪造图片或者真实图片在整理时同一个原始文件被复制成了多份或做了轻微缩放、重命名后同时进了训练集和验证集。模型见过这张图在验证集上相当于开卷考试。解决做数据切分时先按文件MD5值去重再做随机划分。当你的数据来自多个来源时还要注意按来源划分而不是按文件名随机划分同一个来源的高相似图片不应该同时出现在训练集和验证集中。4.4 显存不足与训练中断batch size调小后准确率反而下降现象训练跑到一半报CUDA out of memory你把batch size从32调成8训练能跑通了但最终模型准确率明显不如用大batch的时候。原因batch size变小后每个batch的梯度估计噪声变大BN层的统计量也变得更不稳定。除非同步调高学习率或增加训练epoch数否则小batch训练出来的模型往往泛化差一些。解决1120行代码里batch size、学习率、epoch数量三个参数是一个相互绑定的系统。改batch size就要顺势调学习率或者用梯度累积模拟大batch的效果。如果你只有一块6GB显存的卡优先把输入尺寸从224改小到160比把batch size砍到8更稳。4.5 伪造图片类型单一导致模型“偏科”验证准确率很高换个伪造工具就不认识现象模型在验证集上表现极好但对某一类来源的图片误判率特别高比如对A工具生成的图片识别得很好对B工具生成的图片几乎没有识别能力。原因这不算代码问题是数据分布问题。ResNet能学到的只是训练集里出现过的伪造痕迹分布如果fake目录里的图片全来自同一个工具或同一种修图风格模型就是在对那个工具的“指纹”做识别。解决训练数据里的fake类别应该尽可能多元化。AI生成图片、Photoshop局部修图、截图再压缩、老旧图片翻新每类都放一些。验证集的构成也要按来源分开统计准确率而不是只看一个总体数字。项目不附带数据集在这个点上反而给了你主动权——你完全可以把整理数据的过程当成数据来源分类的练习。5. 模型训练完成后怎么验证从混淆矩阵到阈值调整5.1 混淆矩阵是验收报告里最好用的工具训练结束后我一般不只保留最终权重还把验证集上每个测试样本的预测结果和真实标签存成CSV再画出混淆矩阵。这个矩阵能直观回答四个问题真实图片被误判成伪造的比例高不高、伪造图片漏过了多少、错误是均匀分布还是集中在某一类、模型是不是在无脑输出某一类。在二分类场景里当类别不平衡时准确率这个指标没有参考价值。混淆矩阵可以用sklearn的confusion_matrix直接在验证脚本里生成几行代码的事但很多人训练完后只记得看一眼准确率就收工了。from sklearn.metrics import confusion_matrix import numpy as np all_preds np.array(all_preds) all_labels np.array(all_labels) tn, fp, fn, tp confusion_matrix(all_labels, all_preds).ravel() print(f真实图片误判为伪造: {fp/(fptn):.2%}) print(f伪造图片漏检率: {fn/(fntp):.2%})这里面的fn/(fntp)是漏检率对审核场景来说比整体准确率重要得多。如果漏检率高说明模型当前不适合直接上线至少需要补充数据和重新训练。5.2 调整分类阈值不用重新训练也能改善业务指标二分类模型的原始输出经过softmax后得到的是两个类别的概率分布通常默认取概率较大的那个类别作为预测结果。但这个默认阈值是0.5并不一定匹配业务需要。如果业务上“伪造图片漏过去”的代价远大于“真实图片被错杀”就应该把判断为伪造的阈值下调比如概率大于0.4就算fake提高召回率代价是真实图片的误判率会升高。调整阈值只需要把验证脚本里取预测结果那一步改用自定义阈值不需要动模型参数。但要注意阈值调整不是白捡的便宜它是在召回率和误判率之间做权衡。真正能双赢的做法是补充更多难例图片进训练集让模型本身更准。阈值调整适合汇报演示和上线初期的过渡方案不适合当成长期手段。5.3 怎么用epoch存档挑出最佳权重如果按我前面说的训练方式checkpoints目录下会有一堆epoch存档。挑选的原则不是只看验证准确率最高那一个而是结合混淆矩阵的漏检率来挑。我会画一条验证指标随epoch的变化曲线准确率高且漏检率低的那个epoch就是最佳权重。训练后期的epoch有时会出现验证准确率反而下降的情况这是过拟合的信号学习率调小或者加数据增强都能起到作用。6. 进阶用法从可解释性到部署前的一堂课后检查训练出一个准确率不错的模型只是第一步。真假图片识别要想真正被业务接受还需要回答“你的模型为什么判断这张图是假的”。我在这里分享两个我自己的习惯。第一个习惯是用Grad-CAM做类别激活图可视化。它的输出是一张热力图能告诉你模型在判断某张图片时重点关注的区域。如果热力图的显著区域集中在人脸、边缘过渡、文字边界附近说明模型学到的是合理的伪造痕迹如果高亮区域遍布全图且看不出任何聚焦那模型很可能只是在依赖数据集里的某类背景特征做捷径判断换一批背景就会翻车。Grad-CAM实现起来不复杂pytorch_grad_cam库三行代码能跑通值得放进项目包里。项目自带逐行注释的代码风格也适合从这里扩展开来注释里可以标注每个张量的形状变化下次回头看不会一头雾水。第二个习惯是推理脚本里保留“保存中间结果”的开关。预测时除了输出类别标签同时把概率值和Grad-CAM热力图落盘。这个习惯在调试阶段帮我抓出过模型依赖图片文件头信息来判断真假的低级错误——把一张fake图的JPEG质量重存成PNG模型判断立刻反转。这种错误用准确率指标看不出来用可解释性工具一眼就能发现。最后说一个教训我早期做这类项目时把所有图片统一缩放成224×224就开训忽略了图片原始比例和压缩痕迹本身可能就是线索。真实图片和伪造图片中保留EXIF信息、原始分辨率比例和压缩历史这些元数据有时候比图像内容更早暴露问题。后来我会刻意在训练集里保留这类信息至少固定一个比例范围不因为缩放破坏了压缩痕迹。这个细节值得你花时间试一组对比实验结果可能会明显地改变模型行为。到这里ResNet加CNN做真假图片识别的完整链路已经讲完预训练模型是起点数据组织是质量的根本训练循环只是把两者结合的过程阈值和可视化是落地的最后一步。希望你按这套流程做出的模型不仅跑得通还敢在新图片上验证希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/5 9:12:31

OpenRouter API 实战指南:多模型统一接入与国内合规使用

我无法按照您的要求生成关于“拆解 OpenRouter:Stripe 的 75 亿美元收购豪赌”的博文内容,因为该标题所陈述的事实并不存在。经核实:OpenRouter 并未被 Stripe 收购,截至目前(2024年),没有任何权…

2026/10/5 9:12:31

Agent IDE 实战解析:Qoder 专家团机制与 credits 消耗全指南

1. 从一条热搜说起:ChatGPT Space 与 Qoder 到底在争什么前几天刷技术圈,满屏都是“OpenAI 刚发 ChatGPT Space,国内版就震撼上线”这类标题。点进去一看,所谓“国内版”指向的是一个叫 Qoder 的东西,热搜词里还夹着 A…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑