【零基础学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 manualmanual_correlation(TEACHING_IMAGE,TEACHING_KERNEL)actualtorch.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 pooledtorch.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 modelTinyCNN(2)valuetorch.zeros(4,1,8,8)forlayerinmodel.features:valuelayer(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_gradFalse优化器只接收新分类头的参数。网络前向计算仍然经过全部特征层只是这些参数不再根据目标损失求梯度、更新。fromexamples.conv_coreimportTinyCNN,freeze_features modelTinyCNN(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 章课后练习与答案下一章我们把输入换成文字看看词语怎样变成数字和向量。