ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深度学习调参实战:超参数、Batch Norm与Softmax多分类

深度学习调参实战:超参数、Batch Norm与Softmax多分类 模型结构照抄论文数据也清洗干净了loss 却卡在 0.6 附近来回震荡下不去把学习率从 0.01 改成 0.001、又加了一层 Batch Normalization 之后第二天验证集指标直接掉到 0.15——这是我早期做深度神经网络时经常遇到的场景。那时候我把超参数调试理解成一种玄学觉得全靠运气和手感直到系统地把吴恩达《改善深度神经网络》第三周的内容完整过了一遍才意识到调参这件事其实有一套相当清晰的优先级和搜索策略背后全是可解释的道理。第三周的主线其实就三块超参数调试的策略、Batch 正则化Batch Normalization的机制以及编程框架的选型与使用。这篇笔记我会按自己后来实际工作的顺序来组织不只是复述课程里的结论而是把每一步为什么这么排为什么这么算拆开讲清楚顺带把当年踩过的坑一并交代适合已经写完第一个神经网络、正卡在调不出效果阶段的同学参考。1. 超参数调试为什么它在深度学习里不是调参玄学刚接触这门课时我对超参数调试这个章节名其实是有点不以为然的觉得无非就是列一堆参数然后挨个试。真正做完几个项目以后回头再看才发现这一节的价值不在于告诉你有多少个参数可调而在于告诉你这堆参数的权重完全不对等。课程里有一句话我印象很深在深度学习的早期很多人凭经验知道学习率很重要但到底有多重要、和其他参数差几个量级是很少有人量化说清楚的。超参数调试这一节做的就是把这个模糊的感觉变成一张可执行的优先级清单。1.1 从一次学习率设错导致的训练崩盘说起我先讲一个自己印象最深的例子。当时我在做一个多分类的表格数据任务网络不大三层全连接加 ReLU输入维度两百多。第一次训练时我把学习率设成了 0.1训练 loss 在前几十个 iteration 里直接冲到 3.0 以上然后变成 NaN。我当时的第一反应是数据里有脏值清洗了三遍数据、换了两套归一化方案都没用。后来把学习率降到 0.01loss 正常下降但很慢再降到 0.003收敛速度反而变快、最终精度也更高。整个排查过程浪费了我将近两天而问题根源只是学习率这个单一超参数的取值超出了这个网络的稳定区间。这件事让我彻底理解了课程里那个排序的用意。学习率 α 之所以被排在第一优先级是因为它直接决定了参数更新的步长步长太大损失函数会在最优点附近来回跳甚至发散步长太小训练会慢到让人怀疑网络结构有问题。它不像隐藏单元数或者层数那样可以大致差不多就行学习率错一个数量级整个训练结果可能是天壤之别。所以在实际项目里我会先固定其他所有参数只对学习率做一次粗粒度的对数扫描通常取[0.1, 0.03, 0.01, 0.003, 0.001]这几个值看哪个能让 loss 在前几百次迭代里稳定下降。1.2 超参数之间并不平等我的优先级排序表课程给出的优先级排序是学习率 α 最重要其次是动量项 β通常 0.9、隐藏单元数、mini-batch 大小再次是层数和学习率衰减至于 Adam 优化器里的 β1、β2 和 ε基本上用默认值就行几乎不需要调。这个排序我后来在自己的项目里验证过基本吻合但我想补充两点自己的理解。第一不需要调和不用管是两回事。Adam 的 β1 默认 0.9、β2 默认 0.999、ε 默认 1e-8这些默认值是大量实验积累出来的绝大多数情况下直接用就好。但你要知道它们各自控制什么β1 控制一阶矩梯度均值的指数加权平均β2 控制二阶矩梯度平方的指数加权平均ε 是为了防止除以零。知道了这层含义真遇到 Adam 在某类稀疏梯度任务上表现异常时你才知道该往哪个方向去动。第二优先级排序不是死的。如果你的任务是极小数据集、极深的网络那层数和正则化的权重自然会上升如果是超大 batch 的分布式训练mini-batch 大小和它的学习率缩放关系就会变成第一优先级。排序的真正意义是给你一个排查顺序当模型效果不达预期时先动排在前面的参数别一上来就去纠结那些排在末尾的细枝末节。2. 学习率、动量与网络规模超参数优先级的实操排序把优先级排出来只是第一步真正难的是理解每个参数为什么排在这个位置。这一节我想把三个最常被调整的参数——学习率、动量的 β、以及网络规模层数、隐藏单元数——背后的逻辑讲透这样你在面对具体问题时能自己判断该动哪个。2.1 学习率排第一的数学理由从梯度下降的更新公式 θ : θ - α·∂J/∂θ 就能看出来学习率 α 是唯一一个直接乘在梯度上的全局系数其他超参数要么改变梯度的计算方式如动量、优化器要么改变网络本身的结构层数、宽度。这意味着学习率的影响是全局且线性的误差曲面上每个方向的更新幅度都被它统一缩放。而深度网络的损失曲面通常是高度非凸、各方向曲率差异极大的一个对某个方向合适的学习率在另一个方向可能就过大或过小。我在实践中总结出一套判断标准如果训练 loss 在前几十次迭代里就迅速变小然后剧烈震荡说明学习率偏大如果 loss 下降得像蜗牛爬、每个 epoch 只掉一点点说明学习率偏小。一个比较实用的粗筛方法是从 0.01 附近起步每次乘 3 或除以 3 做扫描找到loss 稳定下降且没有发散的那个数量级再往细里调。这就是课程里说的由粗到精coarse to fine的具体落地。需要注意的是学习率并不是一个孤立的最优值。课程里明确提到如果你换用了 Adam 这类自适应优化器或者在网络里加入了 Batch Norm最优学习率的量级往往会发生变化——Batch Norm 本身就起到了一定的稳定每层输入分布的作用允许你用更大的学习率。提示换优化器或加 Batch Norm 之后一定要重新扫一遍学习率。我见过太多次加了 BN 结果反而变差的情况最后发现是学习率没跟着调。2.2 Adam 的三个 beta 与层数、隐藏单元的排位动量 β 排在第二位因为它控制的是用过去多少步梯度的指数加权平均来平滑当前更新。β 越大平滑越强、噪声越小但对变化方向的响应也越迟钝。0.9 这个默认值相当于用最近约 10 个梯度的平均因为 1/(1-β) 10这是一个在平滑性和响应性之间折中的经验值。课程里也提到如果你要调 β通常是在 0.9、0.99、0.999 这种量级上跳跃但这已经属于比较精细的调试了。隐藏单元数和层数的排位相对靠后原因在于它们改变的是模型的容量而容量问题通常不是最先暴露的。一个学习率设错的小网络效果会比学习率正确的大网络差得多。我在实际工作中调整容量的顺序一般是先用一个中等规模比如每层 128 或 256 单元、3 到 4 层的骨架跑通流程确认 loss 能下降、验证集指标合理然后才根据是否欠拟合来决定加深加宽根据是否过拟合来决定加正则化或者缩容量。这个顺序能避免你在一个本身就调不对的网络结构上反复折腾。2.3 一张可以直接抄的优先级对照表下面这张表是我自己整理并长期使用的把课程里的排序和我补充的实操经验放在一起方便你对照排查。优先级超参数常用默认值或范围调整时机1学习率 α0.0001 ~ 0.1对数扫描优先调整其他参数先固定2动量 β0.9可尝试 0.99训练震荡明显时考虑3隐藏单元数64 / 128 / 256 / 512判断欠拟合后调整4mini-batch 大小64 ~ 512影响训练速度与稳定性5网络层数3 ~ 10 层起步容量不足且数据充足时增加6学习率衰减视任务设置收敛后期提升精度7Adam 的 β1、β2、ε0.9 / 0.999 / 1e-8一般保持默认这张表的价值在于给你一个从哪开始的答案。很多人调参效率低本质上是因为没有顺序今天调调层数、明天换个优化器参数之间相互干扰永远找不到方向。按优先级从上往下扫每一步只动一个变量才能看清每个参数的真实影响。3. 随机搜索与由粗到精把算力花在正确的地方知道了哪些参数重要接下来就是怎么搜。课程里花了不小篇幅讲随机搜索相对网格搜索的优势我觉得这是整个第三周里最容易被低估、但实际收益最大的一节。3.1 网格搜索的隐性浪费网格搜索的思路很直观给每个超参数划定几个候选值然后做笛卡尔积把所有组合都试一遍。问题在于当超参数量超过两三个之后组合数会指数级增长而更致命的是——大部分维度其实不重要。设想两个超参数学习率很重要ε 几乎不影响结果。在 5×5 的网格里你会在 5 个不同的 ε 值上都试同一个学习率等于把一个有效实验重复了 5 遍浪费了 80% 的算力。随机搜索则不同在同样 25 次试验里学习率有机会取到 25 个不同的值虽然理论上可能重复但期望上覆盖的关键维度更广。课程里那张对比图讲的就是这个道理——当只有少数维度真正重要时随机搜索能用同样的预算探索到更多有效解。3.2 由粗到精的具体执行节奏搜索策略的第二个关键是由粗到精。我自己的执行节奏大概是这样的第一轮用随机搜索在很大的范围内撒点比如学习率在[1e-4, 1e-1]之间对数采样隐藏单元数在{64, 128, 256, 512}里取每个配置只跑很短的一段几百到一两千次迭代看曲线趋势。第二轮把第一轮里表现最好的那 3 到 5 个配置圈出来在它们附近缩小范围再采样比如学习率收敛到[1e-3, 1e-2]同时把每个配置跑得更久一些。第三轮基本锁定一到两个配置做完整的训练同时微调学习率衰减这类后期参数。这个节奏的核心逻辑是早期试错的成本低后期的精度要求高。用短训练来判断趋势虽然不够准但足以筛掉明显跑不起来的配置等你确认方向对了再投入完整算力整体效率比一上来就跑满每个配置高得多。3.3 对数均匀采样一段可以直接用的采样代码课程里特别强调了一点学习率、动量 β 这类参数不应该在原始尺度上均匀采样而应该在对数尺度上采样。原因很直接——学习率的有效范围跨越好几个数量级0.0001 和 0.001 之间的差别远比 0.1 和 0.101 之间的差别重要。如果在[0.0001, 1]上均匀采样你有 90% 的样本会落在 0.1 以上而那个区间往往早就发散或者震荡了。正确的做法是对指数均匀采样。下面是实现学习率对数采样的一个小函数import numpy as np def sample_log_uniform(r_min, r_max, size1): 在对数尺度上均匀采样。 r_min, r_max 是参数的上下界例如 1e-4 和 1e-1。 log_min np.log10(r_min) log_max np.log10(r_max) exponents np.random.uniform(log_min, log_max, size) return 10 ** exponents # 示例采样 5 个学习率 lr_candidates sample_log_uniform(1e-4, 1e-1, 5) print(lr_candidates)对于 β 这类接近 1 的参数采样方式要变一下。课程给出的技巧是不要直接在 β 上采样而是对1 - β采样。因为 β 在 0.9 到 0.999 之间变化时真正起作用的是1 - β也就是指数加权平均的有效窗口长度从约 10 到约 1000。所以可以先在[0.001, 0.1]上对数均匀采样1 - β再取β 1 - sample。提示这个对 1-β 采样的技巧非常实用很多人在调 Adam 的 β2 时直接在 0.99 到 0.9999 之间均匀采样结果绝大多数样本挤在无效区间本质上就是因为没做尺度变换。4. 熊猫模式还是鱼子酱模式算力预算决定调参节奏课程里用一个很形象的比喻讲两种调参组织方式熊猫模式babysitting one model即保姆式盯着一个模型和鱼子酱模式training many models in parallel即并行训练多个模型。这两个名字其实来自吴恩达自己的经历——他提到做熊猫繁育研究和鱼子酱产业的人都懂这种资源取舍的感觉。这两种模式没有绝对优劣关键是看你的算力预算。4.1 babysitting one model 适合什么样的任务熊猫模式指的是算力有限只能同时训练一个或极少几个模型于是你像照顾熊猫幼崽一样每隔一段时间就去看一眼训练曲线发现不对就立刻调整超参数、重启训练。这种模式的典型场景是大规模模型 有限算力比如训练一个很大的视觉模型一次完整训练要几天你根本没有资源同时跑十个。它的优势是你能对单条训练曲线做非常细致的观察和干预比如发现 loss 在第 3 个 epoch 突然抬头就能立刻判断是学习率偏大还是数据出了问题。缺点也很明显反馈周期长一次实验要等很久而且你的判断很容易被单次实验的噪声误导。我当年显卡资源紧张的时候就是这种模式一天最多跑两三个配置效率非常低。4.2 并行训练多模型的真实收益与代价鱼子酱模式则相反算力充足同时开十几个甚至几十个训练任务每个任务用不同的超参数配置跑完之后统一对比直接挑最好的。课程里说如果你有足够的计算资源这种方式在相同时间内能探索的配置数量是熊猫模式的十几倍找到好配置的概率自然也高得多。但它也有代价。第一是资源消耗大需要多卡或者集群支持第二是管理成本高几十个任务的日志、指标、模型文件如果不好好组织很容易乱成一锅粥第三是有些任务本身就不适合并行比如需要大量内存的超大模型或者需要人工逐步判断的探索性任务。4.3 我在两种模式之间的取舍经验我自己后来的做法是在两者之间找了个折中对关键参数用并行对细节参数用串行。具体来说我会先并行跑一批不同学习率的配置比如 5 到 8 个因为这是我判断最不确定、也最影响结果的维度确定学习率的大致区间后再用串行方式在最优配置附近微调隐藏单元数、正则化强度这些次要参数。这样既保证了关键维度的探索广度又不会把算力浪费在无关紧要的维度上。另外有一个很实用的经验无论用哪种模式都要把每次实验的超参数和对应结果结构化记录下来我用的是最简单的表格一行一个实验列包括学习率、优化器、层数、最终验证指标、训练耗时。坚持记录半年之后你会对自己任务里参数的敏感区间形成直觉后面很多调参决策可以凭经验直接跳过大量无效尝试。5. Batch Norm 的机制拆解它到底在哪一层做了什么Batch 正则化是第三周的第二个大主题也是我觉得这一周里最有实用价值的一块。它不只是又一个正则化技巧而是从根本上改变了网络内部激活值的分布带来的好处远超最初的预期。5.1 从输入归一化到隐藏层归一化的思路迁移先复习一个前置知识在训练网络之前我们通常会对输入特征做归一化也就是减去均值、除以标准差让每个特征都落在相近的尺度上。这一步之所以能加速训练是因为它把损失曲面的形状从细长的椭圆变成了更接近圆的形状梯度下降不用在狭窄方向上反复震荡。Batch Norm 的想法就是把这个思路从输入层推广到每一个隐藏层。既然归一化输入有效那为什么不把每一层的激活值也归一化一下呢具体来说对第 l 层的线性输出 z^[l]也就是激活函数的输入在一个 mini-batch 上计算它的均值和方差然后做标准化得到 z̃^[l]再送进激活函数。这样一来不管前面几层的参数怎么变每一层接收到的输入分布都相对稳定。这个思路听起来简单但实现上有几个细节必须搞对否则会出问题。第一个细节是归一化的对象是 z 还是 a。课程里采用的是归一化 z激活前的线性输出这也是大多数实现的默认做法。第二个细节是归一化要在 mini-batch 维度上做而不是在特征维度上——也就是对每个特征分别计算这个 mini-batch 内的均值和方差。5.2 mini-batch 上的均值方差计算与 gamma、beta 的引入归一化的公式不复杂。对一个 mini-batch先算第 l 层第 k 个神经元在线性输出上的均值μ (1/m) · Σ z_i再算方差σ² (1/m) · Σ (z_i - μ)²然后标准化z_norm (z - μ) / √(σ² ε)这里的 ε 是一个很小的常数比如 1e-8防止方差为零时除零。到这里还没结束。如果只是简单标准化会有一个副作用它强行把每层的输出限制在均值为 0、方差为 1 的分布上这可能限制了网络的表达能力。所以 Batch Norm 引入了两个可学习的参数γ 和 βz̃ γ · z_norm β这两个参数让网络自己决定要不要恢复原来的分布、恢复到什么程度。如果 γ 取标准化前的标准差、β 取均值就完全还原了归一化之前的值如果网络认为归一化后的分布更好它可以学出别的 γ、β 值。这是 Batch Norm 设计里非常巧妙的一点不是硬性规定归一化而是把归一化的程度变成可学习的。有一个容易搞错的细节当你在线性层里加了z W·a b之后再做 Batch Norm那个偏置 b 其实是多余的因为接下来的标准化会减掉均值b 的影响会被完全抵消掉。所以标准做法是在使用 Batch Norm 的层里去掉偏置项 b只保留 W或者把 b 设为零。这个坑我在第一次实现时踩过当时加了两层 BNloss 一直不下降排查半天才发现是偏置和 BN 的 β 在打架。5.3 把 Batch Norm 塞进网络的具体位置Batch Norm 在网络里的插入位置通常是在线性层之后、激活函数之前也就是a g(z̃)的顺序。这个顺序不是随便定的因为归一化需要在激活前的线性输出上做才能保证激活函数的输入分布稳定。如果放在激活之后就要对一个已经被非线性变换过的值做归一化效果会打折扣。在实际框架里这通常意味着一层写起来是这样# 以伪代码展示结构 z W a_prev # 线性层注意没有偏置 z_norm batch_norm(z, gamma, beta, eps) # 归一化并缩放平移 a relu(z_norm) # 激活用现代框架的话通常一行就搞定了比如tf.keras.layers.BatchNormalization()或者nn.BatchNorm1d()框架会自动处理 γ、β 的初始化和更新。但理解底层顺序对排查问题依然很重要。6. Batch Norm 为什么有效协变量偏移与那个意外的正则化知道怎么用是一回事理解为什么有效是另一回事。课程里给了两个解释协变量偏移covariate shift和正则化效应。这两个解释我自己在项目里都实际验证过尤其是第二个。6.1 协变量偏移的直观解释协变量偏移的意思是当你在训练网络时前面几层的参数不断更新导致后面几层接收到的输入分布一直在变化。用课程里的例子说就像你在教一个学生认猫但你给他看的照片风格一直在变——一会儿是黑白照一会儿是素描一会儿是卡通他很难学到一个稳定的判据。Batch Norm 通过对每一层的输入做归一化使得不管前面的参数怎么变每一层接收到的数据分布都保持相对稳定均值 0、方差 1再经过可学习的缩放平移。这就好比每一次都先把照片统一成同一种风格再给学生看学习效率自然提高。这个解释是我觉得最直观的也解释了为什么加了 Batch Norm 之后可以用更大的学习率——因为每层的输入分布稳定了大一点的步长也不容易把网络推偏。6.2 正则化效应和 mini-batch 大小的关系第二个解释更有意思Batch Norm 会带来轻微的正则化效果。原因是每一层的归一化是用当前 mini-batch 的均值和方差来算的而每个 mini-batch 的统计量都带有一点噪声。这相当于在每一层的激活值上引入了噪声有点像 Dropout 的效果。这个效应有一个很重要的推论mini-batch 越小噪声越大正则化效果越强。课程里提到如果你用很小的 batch size比如 8 或 16Batch Norm 的正则化作用会比较明显但如果你用很大的 batch比如 512这种噪声几乎可以忽略正则化效应也就基本消失了。理解这一点很关键不要把 Batch Norm 当成主力正则化手段它带来的正则化是副作用真正需要正则化时还是要靠 L2、Dropout 这些手段。还有一个实践中的注意点因为 Batch Norm 会带来正则化效应所以如果你同时用了 Dropout 和 Batch Norm两者的强度需要协调不然容易过正则化导致欠拟合。我一般会先只加 Batch Norm观察验证集和训练集的差距如果差距不大就不再加 Dropout。6.3 训练与测试行为不一致这个坑我踩过这是 Batch Norm 里最容易出错、也最需要强调的一点训练时和测试时均值和方差的来源是不一样的。训练时均值和方差来自当前 mini-batch。但测试时你往往是单条样本或者很小的 batch用它们算统计量会非常不稳定甚至没有意义一条样本的方差是 0。所以标准做法是训练过程中用指数加权平均的方式把每个 mini-batch 的均值和方差累积下来得到一个全局的估计值测试时直接用这个累积的估计值而不再用测试数据本身的统计量。这个机制在框架里通常是自动处理的但你必须知道它的存在否则会掉进一些奇怪的坑。我自己就遇到过一次在做推理服务时我手动拼了一个 batch 去做前向计算结果发现同一个样本单独推理和放在 batch 里推理输出结果不一样。排查了很久才想起来——那次我是在训练模式下跑的trainingTrue框架用当前 batch 自己算了统计量。把模式切到推理模式trainingFalse之后问题就消失了。提示部署模型时一定要确认 Batch Norm 层处于推理模式。用训练模式做推理除了结果不稳定还可能因为 batch 太小导致方差估计严重失真。7. Softmax 与多分类从二分类到 K 分类的那一步跨越第三周最后还讲了 Softmax 回归和编程框架。Softmax 虽然看起来只是一个输出层的变换但它是从二分类迈向多分类的关键一步实现上有几个点必须搞清楚。7.1 softmax 的数学表达与直觉二分类时输出层通常只有一个神经元加 Sigmoid输出一个 0 到 1 之间的概率。多分类时输出层要有 K 个神经元K 是类别数每个神经元的线性输出 z_i 通过 Softmax 变换成概率ŷ_i e^{z_i} / Σ_j e^{z_j}这个公式其实做两件事先把每个 z 通过指数函数变成正数然后归一化让所有输出之和为 1。这样就得到了一组合法的概率分布。它的直觉是赢者通吃的软版本——最大的 z 对应的类别会分到最大的概率但其他类别也保留了非零概率。有一个数值稳定性的细节直接算 e^{z_i}当 z_i 很大时会溢出。标准做法是在指数运算之前先减去所有 z 中的最大值这样最大的指数就是 e^0 1不会溢出而由于分子分母同除一个常数结果不变。这个技巧叫log-sum-exp 技巧几乎所有框架的 Softmax 实现里都会内置但如果你自己手写实现一定要记得加上。7.2 交叉熵损失与梯度推导的结果Softmax 对应的损失函数是交叉熵损失L -Σ_i y_i · log(ŷ_i)其中 y 是 one-hot 编码的真实标签只有真实类别那一项是 1其余是 0。所以这个损失实际上就是负的对真实类别预测概率取对数。预测概率越接近 1损失越接近 0预测概率越小损失越大。这个组合有一个非常漂亮的结论Softmax 加交叉熵的梯度对未归一化的输出 z 来说恰好是 ŷ - y。也就是说梯度就是预测概率减去真实标签形式极其简洁。这个结论和逻辑回归加交叉熵的结果完全一致不是巧合而是指数族分布加最大似然的自然结果。我在实现时不自己推导直接记住这个结果就够了但知道它从哪来能帮你在遇到梯度异常时快速定位。7.3 实现时容易搞错的两个细节第一个细节是标签的格式。交叉熵损失要求标签是 one-hot 编码或者框架里专门的整数标签接口如果你把整数标签直接喂给需要 one-hot 的损失函数会得到完全错误的结果。我见过有人因为这个原因模型训练了几百轮准确率始终在 1/K 附近徘徊。第二个细节是类别数与输出维度要对应。输出层神经元数必须等于类别数 K如果你把 K 搞错了比如把背景也算作一类却没在标签里体现损失函数不会报错但模型永远学不好。这个错误比较隐蔽排查方法是打印一下每一类的预测概率分布看看是否有某个类别的概率恒为零或恒为常数。8. 编程框架选型与 TensorFlow 上手从 placeholder 到现代写法第三周的最后一块内容是编程框架。课程里用的是 TensorFlow但讲的是 tf 1.x 时代的那套写法和现在的框架生态已经有不小差异。我这一节既讲课程里的核心概念也讲讲迁移到现代框架时该注意什么。8.1 选框架时我真正在意的几件事课程里提到选择框架的几个标准编程是否方便尤其是定义计算图和求导、运行速度、是否开源且维护良好。这几个标准放到今天依然成立但我会再加上几条自己更看重的。第一是生态和部署能力。训练出来的模型最终要落地框架能不能方便地导出、服务化、在移动端或服务端跑这个比训练时省不省事更重要。第二是调试体验。动态图和静态图的区别在调试时体现得非常明显动态图能让你像写普通 Python 一样打断点这在排查模型 bug 时是巨大的优势。第三是社区活跃度遇到问题时能不能快速搜到答案直接决定了你的开发效率。就个人体验来说做研究和快速实验时我更倾向于用动态图的框架写起来顺手、调试方便做大规模生产部署时会考虑框架的分布式训练和推理优化能力。这两者之间没有绝对的对错关键是看你的项目阶段。8.2 TensorFlow 的经典三件套placeholder、Variable、Session课程里讲的 TensorFlow 核心概念有三个placeholder占位符用来在运行时喂数据、Variable变量用来存放需要训练的参数、Session会话用来执行计算图。这套模式的核心思想是先定义计算图再执行。一个典型的训练流程大概是这样# 以课程中 tf 1.x 风格为例示意 X tf.placeholder(tf.float32, [None, n_x]) # 输入占位符 Y tf.placeholder(tf.float32, [None, n_y]) # 标签占位符 W1 tf.get_variable(W1, [n_x, n_h], initializertf.contrib.layers.xavier_initializer()) b1 tf.get_variable(b1, [n_h], initializertf.zeros_initializer()) Z1 tf.matmul(X, W1) b1 A1 tf.nn.relu(Z1) # 输出层 损失 Z2 tf.matmul(A1, W2) b2 cost tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logitsZ2, labelsY)) # 优化器 optimizer tf.train.AdamOptimizer(learning_rate0.001).minimize(cost) # 会话中执行 with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for epoch in range(num_epochs): _, c sess.run([optimizer, cost], feed_dict{X: batch_x, Y: batch_y})这套写法现在看起来有点繁琐但它把计算图定义和数据喂入清楚地分开了理解这个模型对理解深度学习框架的底层机制很有帮助。你要能看懂它才能理解为什么现代框架里的fit方法背后其实做了同样的事。8.3 从课程代码迁移到现代框架的建议如果你现在跟着这门课学我建议把注意力放在概念上而不是死记 tf 1.x 的 API。因为现在的框架已经全面转向了即时执行eager execution和 Keras 高层 API一行就能定义一层训练循环也更接近普通 Python 代码。具体迁移时我建议按这个顺序对照理解placeholder的角色被数据集对象和函数参数取代Variable的角色被层对象内部管理的可训练权重取代Session被直接调用模型前向取代optimizer.minimize(cost)被计算梯度再应用或框架提供的训练步骤取代。我自己的经验是把课程里那个完整的 TensorFlow 训练脚本手写一遍然后改用现代 API 重写一遍两边对照着看理解会深刻很多。这个过程大概花一个下午但它帮你建立的是框架到底在做什么的认知而不是某个 API 怎么写的记忆。这两者的差别在遇到新框架时特别明显——前者能让你快速上手任何框架后者只能让你在特定版本里打转。最后分享一个我在调参和 BN 上反复吃过亏之后养成的小习惯每次改动超参数或者网络结构都在实验记录里写清楚改了什么、预期是什么、实际结果是什么。Batch Norm 和超参数这类东西影响往往是交叉的只靠脑子记很容易把两次实验的差异归因错。老老实实记录是让调参从玄学变成工程的最短路径。
返回列表