ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

【零基础学AI】第 24 章——卷积网络与迁移学习

【零基础学AI】第 24 章——卷积网络与迁移学习

第 24 章 卷积网络与迁移学习

文章目录

  • 第 24 章 卷积网络与迁移学习
    • 🌈 关于《AI 零基础 36 讲》
    • 四个权重怎样扫过一张图
    • 池化留下什么,又丢掉什么
    • 看清本章网络的每个尺寸
    • 预训练给新任务带来什么
    • 冻结和微调分别改变哪些参数
    • 用同一份目标数据比较
    • 遇到报错时先查头和梯度

上一章把 8×8 图片展平成 64 个数。全连接层能够学习分类,但展平后的数组没有直接告诉网络哪些像素互相挨着。一个短横笔画可能出现在图片左边,也可能出现在右边。我们能否用同一组计算,在不同位置检查它。

卷积从这个问题开始。本章先算一个小矩阵,再训练一个小型卷积网络。随后把它在十类数字任务中学到的参数用于新的奇偶分类,比较从头训练、冻结迁移和微调的实际结果。全部步骤都在 CPU 上完成。

🌈 关于《AI 零基础 36 讲》

🧠零基础也能开始学 AI

《AI 零基础 36 讲》是一套面向初学者的 AI 入门教程。课程从认识和使用 AI 开始,逐步学习
Python、数据处理、机器学习与深度学习,再进入大模型应用开发,最后完成个人知识库助手的综合实践。

📖本篇是正式讲解文章

通过具体例子、原创图示和操作结果讲清概念。涉及编程的章节提供可运行代码,方便你边读边试。

🎯读完以后,动手检验

建议按专栏顺序学习,并完成对应的课后训练,检查自己能否独立运用本章知识。

博客专栏:《AI 零基础 36 讲》

四个权重怎样扫过一张图

先用一张 4×4 的原创教学矩阵,值为 0 至 3,暂时不考虑真实数字。卷积核为[[1, 0], [0, -1]]。把这个 2×2 核盖住左上角四个数,相应位置相乘后相加,就得到输出的第一格。

第一格计算为1×1 + 0×0 + 0×0 + 3×(-1) = -2。向右移动一格后,覆盖的输入变为[[0, 2], [3, 0]],输出为 0。核的权重没有变化,变化的是它覆盖的图片位置。行内算完后,向下移动,继续计算。

图使用步长 1,没有填充和偏置。4×4 输入只能放下 3×3 个完整的 2×2 窗口,所以输出为 3×3。这里把输出叫作特征图,同一组权重在不同位置产生不同响应。按 PyTorch 的实现,核没有在计算前翻转,严格说这一步采用互相关规则。

importnumpyasnpimporttorchfromexamples.conv_coreimportTEACHING_IMAGE,TEACHING_KERNEL,manual_correlation manual=manual_correlation(TEACHING_IMAGE,TEACHING_KERNEL)actual=torch.nn.functional.conv2d(torch.tensor(TEACHING_IMAGE)[None,None],torch.tensor(TEACHING_KERNEL)[None,None])print(manual)print(np.array_equal(manual,actual[0,0].numpy()))

输出三行为[-2, 0, 1]、[0, 1, 0]、[0, 0, 0],最后为True。随附逐格计算表列出了每格的输入与结果。手算的循环和 PyTorch 运算分别实现,二者逐值一致。

训练中的核由数据学习,通常无需手工规定它只查横线或竖线。某个核是否学到某种笔画,要结合响应实际观察,不能仅凭它叫“卷积核”就给它安排一个固定含义。多个输出通道使用多组核,因此同一张图能产生多张特征图。

池化留下什么,又丢掉什么

最大池化取一个小区域中的最大值。本章使用 2×2 窗口,步长也为 2。拿刚才的 4×4 输入做演示,四个互不重叠的区域分别留下 3、2、1、2,输出尺寸变成 2×2。

importtorchfromexamples.conv_coreimportTEACHING_IMAGE pooled=torch.nn.MaxPool2d(2)(torch.tensor(TEACHING_IMAGE)[None,None])print(pooled[0,0])

结果为[[3, 2], [1, 2]]。这是一个方便手算的单独池化例子。实际网络中的池化接收 ReLU 后的卷积特征图。

池化减少后续计算所需的空间尺寸,也会丢失区域内的部分位置与数值。一个区域中有明显响应时,最大值仍可能被保留,但它没有保存区域内所有细节。不能把这种处理理解成对任意平移或旋转都不敏感。

看清本章网络的每个尺寸

真实图片仍是第 23 章的 8×8 灰度数字。本章卷积层使用 8 组 3×3 核,步长 1,四周补一圈零,保持空间尺寸为 8×8。经过 ReLU 和 2×2 池化后,得到 8 张 4×4 特征图;展平为 128 个数,再经过 32 节点隐藏层,最后交给分类头。

importtorchfromexamples.conv_coreimportTinyCNN model=TinyCNN(2)value=torch.zeros(4,1,8,8)forlayerinmodel.features:value=layer(value)print(type(layer).__name__,tuple(value.shape))print("分类头",tuple(model.head(value).shape))

尺寸依次为(4, 8, 8, 8)、(4, 8, 8, 8)、(4, 8, 4, 4)、(4, 128)、(4, 32)、(4, 32),分类头输出(4, 2)。前面的 4 始终是图片数量。

卷积的特点是局部连接和参数共享。3×3 核一次计算附近的九个像素,同一组权重在整张图上重复使用。全连接层则为每个输入位置与输出节点建立各自的权重。卷积可保留邻近关系并复用局部计算,后面仍可以接全连接分类头。本章二类网络共 4274 个参数,反而多于上一章十类全连接网络的 2410 个参数,因为我们增加了层和通道。不同结构的具体参数量要算出来,不能仅凭“用了卷积”就断言整个模型更小。

预训练给新任务带来什么

预训练是在源任务上先学习一组参数。迁移时,把这些参数作为新任务的起点,或者固定它们,只训练新的分类部分。官方 迁移学习教程介绍了这两种常见用法。本章借用这个思路,采用自行训练的小模型,避免下载大型权重。

我们把 1797 张公开图按固定种子 2026 重新安排用途。每个数字抽 100 张用于十类数字预训练、12 张用于目标训练、20 张用于目标测试,剩余图片不用。这次角色划分与上一章的训练测试划分不同,但各角色原始编号互不重叠,数量如下。

角色图片数标签用途
源任务1000数字 0 至 9学习卷积与隐藏层参数
目标训练120偶数 0、奇数 1学习新分类任务
目标测试200偶数 0、奇数 1固定训练结束后评估
未使用477保留原标签不进入训练或评估

我们自己定义奇偶标签,并生成target_train/even、target_train/odd和相应测试目录,形成小型自定义图片集。图片内容仍来自公开数据,采用 CC BY 4.0,署名和转换说明见数据记录。自定义标签没有改变原数字真值,也没有生成新的真实测量数据。

源任务训练 30 轮后,把特征部分保存到pretrained-digits.pt。随后从文件读回特征,复制到目标网络,源任务的十类头丢弃,新建一个二类头。虽然奇偶答案可以通过已知数字标签算出,目标模型实际接收的只有图片。数字标签仅用于编制清单和核对答案,没有作为额外特征输入。

源任务训练集达到 1000/1000 正确,这只能说明它对源训练图拟合得好。本章没有另设源任务独立测试集,也没有声称这组自训练权重具备大型通用图片模型的能力。真正需要比较的是它到新任务以后表现如何。

冻结和微调分别改变哪些参数

冻结特征参数时,设置requires_grad=False,优化器只接收新分类头的参数。网络前向计算仍然经过全部特征层,只是这些参数不再根据目标损失求梯度、更新。

fromexamples.conv_coreimportTinyCNN,freeze_features model=TinyCNN(2)print(sum(p.numel()forpinmodel.parameters()))freeze_features(model)print(sum(p.numel()forpinmodel.parameters()ifp.requires_grad))

输出为4274和66。32×2 个权重加 2 个偏置,共 66 个目标头参数。本章冻结方案训练后,特征参数逐值未变,分类头确实改变。这个事实由参数比较验证,不靠运行时长猜测。

微调则允许已有特征继续适应目标数据。本章前 10 轮冻结特征,只训练二类头,学习率 0.01;后 10 轮解冻全部参数,学习率降低为 0.003,并重新建立优化器,纳入解冻的参数。如果只改requires_grad,却仍使用只包含分类头的旧优化器,特征不会因这次修改自动进入更新列表。

冻结参数与eval()也要分清。前者决定是否求参数梯度,后者控制部分层的训练行为。本章网络没有 BatchNorm 和 Dropout;换成含这些层的预训练模型时,还要考虑统计量和随机失活。

用同一份目标数据比较

三个方案的目标网络结构相同,目标划分、图片转换、批量 32、20 轮预算和随机种子 42 相同。二类头从完全相同的初始参数开始,每个方案每轮 4 批,末批 24 张,共 80 次更新。各方案按相同随机批次顺序训练。目标测试仅在固定训练结束后用于评估,没有用于挑轮次和学习率。

方案前 10 轮后 10 轮训练正确测试正确测试准确率测试损失
从头训练全部随机初始化,学习率 0.01全部更新,学习率 0.01116/120183/20091.5%约 0.225924
冻结迁移源特征固定,只更新新头继续只更新新头113/120182/20091.0%约 0.218500
分段微调源特征固定,只更新新头解冻全部,学习率 0.003113/120185/20092.5%约 0.191309

冻结方案比从头训练少答对一张,微调多答对两张。结果说明,在这次划分和固定预算下,微调略好;它没有证明迁移一定更好,也没有证明这两个百分点在其他划分上稳定存在。这是单次实验,若要做更强的效果结论,需要多次划分、训练种子和合适的不确定性评估。

目标训练阶段约为 0.106、0.078、0.096 秒,分别对应三个方案。源预训练还用了约 1.78 秒、1000 张源图片和 480 次更新。时间覆盖优化器准备、训练及逐轮训练集评估,不含库导入、图片读取、绘图和安装。这个小实验的毫秒差值会受设备状态影响,不能写成普遍速度保证。读者使用已经提供的源权重时可以省去重新预训练,但计算这组权重从何而来时,源成本仍需要算上。

比较表和完整指标见实验结果。三个模型分别保存,重载后在 200 张目标测试图上的原始分数逐值一致。

遇到报错时先查头和梯度

保留十类头去做奇偶预测时,程序可能不会立即因为交叉熵标签 0 和 1 报错,因为十类输出仍能接收这些编号。但另外八个输出类别没有目标含义。本章错误示例主动断言输出必须为二类,修复版替换成Linear(32, 2),输出(1, 2)。

如果把新分类头也一起冻结,损失就没有可求导的参数路径,调用backward()会失败。修复版只冻结特征,检查出特征梯度为空而分类头有梯度。两组完整报错、修复和真实结果见错误记录。

本章的轻量路线完整包含源预训练、新任务、冻结和微调,读者可以用普通 CPU 逐步查看。以后换成大型预训练模型时,还要核对源权重许可、输入通道、像素处理和原类别定义。先完成【零基础学AI】第 24 章课后练习与答案,下一章我们把输入换成文字,看看词语怎样变成数字和向量。

返回列表