ARTICLE DETAIL

资讯详情

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

深度学习核心概念系统梳理:从神经网络到训练实践

深度学习核心概念系统梳理:从神经网络到训练实践 翻了翻手头的学习笔记编号 deeplearningbook_021-1 这份正好是深度学习核心概念的系统梳理。我一直觉得深度学习入门最难的不是跑通一个模型而是把教材里那堆公式和框架里的接口对上号。很多人捧着经典教材啃了两周知道了什么叫反向传播但一打开 PyTorch 还是不知道该从哪下手这太正常了。这篇笔记想做的事很简单把深度学习最核心的几条主线——模型结构怎么搭、训练到底在训练什么、参数怎么调——用尽量直白的话串一遍适合正在系统啃理论、准备把基础打扎实的读者。就算你只有一点 Python 基础甚至还没写过一行模型代码顺着这条线走下来也会有一个清楚的整体框架。1. 先搞清楚深度学习到底解决的是什么问题1.1 从“人工挑特征”到“自动学表示”要理解深度学习得先回到传统机器学习的思路里去对比。十年前做图像识别大家的主流做法是“特征工程”先人工设计 SIFT、HOG 这些特征算子把图像里的关键信息提取成向量再用 SVM、随机森林这些分类器去做判断。问题在于这些特征是人拍脑袋想出来的换个数据集、换个任务场景效果可能就差一大截而且设计一个好的特征算子本身就是一门需要很多经验的玄学。深度学习换了个思路我不人为规定模型该看什么而是直接把原始数据喂进去让模型自己一层一层地抽象出有用的特征。这个能力有个专门的说法叫“表示学习”而深度学习就是表示学习里最有代表性的那一支。具体到行为上就是网络的前面几层学会去识别边缘、纹理这类低级特征中间层把边缘组合成部件、形状后面几层再把这些部件拼成完整的物体概念。整个过程不需要人干预模型自己就长出了一套从低级到高级的理解层次。1.2 “深度”到底深在哪很多人以为“深度”指的就是层数多这个理解没错但不太完整。更本质地说深度指的是非线性变换的反复堆叠。每一层网络本质上是在做一件事先对输入做一次线性变换加权求和加偏置再通过激活函数引入非线性。把这种“线性非线性”的组合反复叠加几十层甚至上百层模型就有了拟合极其复杂函数的能力。这能解决什么问题我举个直白的例子。假设你想让模型判断一张图里有没有猫传统方法你得绞尽脑汁描述“猫”长什么样尖耳朵、圆脸、有胡须……可这些规则一旦碰到角度刁钻、光照奇怪的图片就失效了。深度网络不需要你写任何规则你给它几万张标注好的“有猫/无猫”图片它会自己总结哦原来这些像素组合在一起就是猫。这背后的数学原理用一句话概括就是“多层复合函数”网络每一层都是上一层输出的函数整体上就是一个嵌套的函数复合体只要层数够深、容量够大理论上就能逼近任意复杂的输入输出关系。对比一下传统方法和深度学习的逻辑差异会非常明显对比维度传统机器学习深度学习特征来源人工设计依赖专家经验模型自动学习分层抽象数据要求中小规模数据即可通常需要大规模数据可解释性特征含义相对明确中间层特征难以直接解释算力依赖普通CPU基本够用重度依赖GPU并行计算2. 神经网络的骨架从单个神经元到完整网络2.1 一个神经元在做什么“计算”几乎所有神经网络的起点都是“人工神经元”它的核心操作特别朴素接收输入加权求和然后过一个非线性函数。写成公式就是y f(w·x b)其中x是输入向量w是权重b是偏置f是激活函数。把多个神经元排成一排就构成了一层网络多个层叠起来就变成了深度网络。这里关键的一点是激活函数必须是非线性的。如果不加激活函数或者用了线性激活那么不管堆多少层整个网络在数学上依然等价于一个线性变换表达能力极差。这就好比你想用一堆直线去拟合一条曲线无论你怎么拼接得到的还是折线永远不可能平滑。非线性激活函数才是让网络“弯”得起来的关键。我整理了几个常见激活函数的实际使用感受Sigmoid取值范围在0到1之间历史上很常用但有一个致命问题——两端梯度几乎为0。反向传播的时候梯度每经过一层就会因为乘上很小的导数而减小一点层一多浅层的参数几乎得不到更新这就叫梯度消失。现在很少在隐藏层用它了。Tanh取值范围在-1到1之间输出均值为0比Sigmoid收敛稍微好一点但依然存在饱和区的梯度消失问题。ReLUmax(0, x)正半轴梯度恒为1计算极快是目前隐藏层的默认选择。但它也不是没毛病如果某个神经元的输入总是落在负半轴它的梯度就会一直为0相当于这个神经元“死”掉了这叫“死神经元”问题。Leaky ReLU / ELU针对ReLU的死神经元问题做了改良负半轴保留一个很小的斜率或者用指数函数平滑过渡实践中也经常有更好的表现。关于怎么选我个人的习惯是隐藏层默认用 ReLU如果发现训练过程中很多神经元输出恒为0就换成 Leaky ReLU 或 ELU二分类输出层用 Sigmoid多分类用 Softmax回归任务输出层干脆不做任何非线性激活。2.2 网络结构输入层、隐藏层、输出层各管什么一个标准的全连接网络结构上分三块。输入层的神经元数量等于特征的维度比如一张 28×28 的灰度图片展开成一维就是 784 个数输入层就是 784 个神经元。输出层的神经元数量取决于任务二分类通常用 1 个神经元配 Sigmoid多分类有 K 个类别就用 K 个神经元配 Softmax回归任务一般是 1 个神经元直接输出数值。中间那些隐藏层才是真正决定模型表达能力的地方。隐藏层越多、每层神经元越多模型的“容量”越大能拟合的关系越复杂。但容量大不等于好模型太大会把训练数据里的噪声也背下来导致在没见过的新数据上表现稀烂这就是过拟合后面会细说。这里先记住一个核心矛盾深度和宽度决定表达能力的上限但也会放大训练难度和过拟合风险。用一个简单的维度变化来说明网络的流动过程。假设输入是[batch_size32, 784]经过第一层比如512个神经元后变成[32, 512]再经过第二层256个神经元变成[32, 256]最后接输出层变成[32, 10]。每一层都同时在做两件事对输入做线性变换再施加非线性激活。层与层之间的矩阵乘法就是“特征空间”的一次重新映射层数越深映射出来的空间就越抽象、越适合当前的任务。3. 训练一个网络到底在“训练”什么3.1 损失函数是你的“标尺”训练的本质是让模型输出的结果跟你期望的结果尽量接近但是“接近”需要一个量化的标准这就是损失函数。损失函数好比老师手里的评分细则你答得越好分就越低。训练的目标就是把这个分数压到最低。损失函数不是乱选的它必须跟任务类型匹配。我自己经常碰到有人用均方误差MSE做分类任务结果训练半天准确率就是上不去这其实是选错了尺子。分类任务的标准选择是交叉熵损失它的原理是把模型输出的数值转化成概率分布再去跟真实标签计算差异。交叉熵和概率天然匹配梯度下降时收敛速度也快。为什么分类不用MSE有两个原因一是MSE在配合Sigmoid输出层时梯度里会乘上 Sigmoid 的导数一旦进入饱和区导数接近0梯度消失会非常严重二是分类任务的本质是“概率比较”交叉熵直接衡量两个分布的差异比单纯距离更符合语义。3.2 反向传播链式法则的工程实现训练更新参数的流程可以概括成一个循环前向计算、反向传播、参数更新。前向计算很好理解就是数据从输入层流到输出层算出预测值和损失。真正让初学者头疼的是反向传播。反向传播说白了就是高等数学里的链式法则——只不过把求导过程放在计算图里从输出层往输入层一层一层地算回梯度。每个参数对损失的贡献有多大数学上就是损失对参数的偏导数而偏导数可以通过从后往前逐层传递的方式高效算出来。这就是“反向”两个字的由来。一个实用的理解技巧是前向传播决定模型猜得对不对反向传播决定每个参数应该往哪个方向调、调多少。如果你看不懂那些大段的数学推导可以试着手推一个两层的简单网络不需要多复杂就是输入特征、隐藏层、输出层各一层自己拿纸把前向公式写出来再对每个参数求偏导。这个动作我每次推荐给别人都强调一定要亲手推一遍哪怕照着答案抄一遍也行。推完之后你会觉得深度学习瞬间通透了不少。3.3 优化器让参数往正确方向走有了梯度剩下就是怎么用它来更新参数。最基础的方法是梯度下降公式就一行w w - 学习率 × 梯度。方向是让损失下降最快的方向步长则由学习率控制。学习率太大参数会在最优解附近来回震荡甚至直接发散学习率太小训练半天损失就是不下降让人怀疑人生。实际训练中我们一般不用纯梯度下降而是用它的改进版本。这里有个必须理解的点全量梯度下降每次要拿全部数据算一个平均梯度数据一多计算量太大而且容易陷入局部最优随机梯度下降虽然每次只拿一个样本更新但震荡太剧烈收敛很慢。**现在的默认做法是拿一小批mini-batch数据算梯度既利用了GPU的并行能力又能保证更新相对平滑。**批量大小一般取32、64、128这些值具体取值会影响训练速度和最终效果这个我后面还会讲。优化器层面的演进也很有意思。动量Momentum法的思路特别像物理里的小球从山坡上滚下来它会积攒历史梯度的信息如果某一方向持续下降就加速冲过去遇到坡底震荡也能用惯性冲出去有效减轻了SGD在沟壑中来回震荡的问题。后来出现的Adam更进一步在每次更新时自适应地调整每个参数的学习率对初始超参数的敏感度远低于SGD训练起来省心很多。我的经验是调试阶段先用Adam把训练跑通追求极致精度时再切回带动量的SGD并且把学习率调好。4. 实践里反复踩的坑与排查实录4.1 过拟合这是新手感受到的第一个“重锤”我见过太多次这样的场景训练几千个epoch后模型在训练集上的准确率已经99%了一拿到验证集上立马掉到80%以下这个巨大的落差就是过拟合的典型信号。**模型把训练数据里无关紧要的细节和噪声当成规律背了下来而不是学到可泛化的特征。**好比学生死记硬背了练习册上每道题的答案但题目稍微换个条件就不会做了。针对过拟合常用的手段有这几个早停训练过程中每轮都看验证集的表现一旦验证集损失开始上升就立即停止训练保留之前效果最好的模型。这是最简单、最不容易出错的手段。L2正则化权重衰减在损失函数里加一项“所有参数的平方和”迫使模型参数保持较小避免个别权重特别大导致的过拟合。PyTorch优化器里的weight_decay参数就是干这个的。Dropout训练时随机让一部分神经元失活强迫网络不依赖个别神经元相当于在训练多个“子网络”并取平均极大地减弱了神经元之间的固定协同模式。数据增强对图像做随机裁剪、翻转、颜色扰动用最少的标注数据生成更多有效的训练样本。我自己会先用早停兜底再根据验证集的实际差距决定要不要加Dropout和权重衰减。还有个经验如果训练集损失很高、验证集更高那是欠拟合是模型容量不够或者训练不充分如果训练集很低、验证集高那才是过拟合。这两类问题的方向完全相反别一上来就乱加正则化把欠拟合治成了更严重的欠拟合。4.2 梯度消失和梯度爆炸深网络的隐痛网络越深反向传播的路径就越长梯度在传递过程中不断被连乘。如果每层的梯度大小小于1经过几十层乘下来传到输入层的梯度会小到接近0浅层参数直接不更新这叫梯度消失。反过来如果每层梯度大于1连乘的结果会越滚越大最终产生一个极大值导致参数更新幅度过大训练直接发散这是梯度爆炸。解决这两类问题核心思想也是从“初始化”和“网络结构”两方面入手。初始化方面现在最常用的是Xavier 初始化也叫 Glorot 初始化适用于 Sigmoid/Tanh 类激活和He 初始化适用于 ReLU 类激活。它们的思想一致根据输入输出维度的规模来设置初始权重的方差让信号在网络中传播时既不被过度放大也不被过度缩小保持在一个相对稳定的范围里。结构层面的两个“神器”是Batch Normalization批归一化和残差连接。批归一化让每一层的输入分布稳定下来使得梯度在反向传播时的尺度更均匀实测下来对深层的收敛速度提升非常明显。残差连接则让网络多了一条“抄近道”的路径梯度可以直接通过跳层连接传回浅层这就是 ResNet 能轻松训练一百多层网络的关键机制。我第一次跑一个50层的网络时训练集损失卡在初始值附近很久不动调了学习率也没用最后发现是初始化方式选错了换成 He 初始化并加了 BatchNorm问题立刻缓解。深网络的训练往往不是推倒重来而是要在这些细节里找答案。4.3 学习率定生死最容易踩的调试雷区在所有的超参数里学习率可以说是最重要的一个重要性甚至高于网络结构。原因很简单学习率决定了参数每次更新的步幅步幅错了要么原地踏步要么一步迈过悬崖直接发散。我分享几个典型的“症状”以及对应的怀疑方向损失不下降或者下降极其缓慢学习率太小了试着把它调大一个数量级比如从0.001调到0.01。损失剧烈震荡来回跳动学习率太大了模型在最优解附近反复横跳试着调小一个数量级。损失先下降跳到NaN然后彻底爆炸学习率过大或者数据没做归一化可能导致梯度爆炸。把学习率调到0.0001甚至更小重新跑。现在训练比较大的模型时有个经验性的做法叫学习率预热warmup训练刚开始先用一个较小的学习率“热热身”再逐步增加到预设值最后再按既定策略衰减。这背后的原因是模型刚初始化的参数还很“脆”如果一上来就用很大的学习率猛冲很容易把参数推到局部极小或者直接击穿轻则收敛慢重则训练失败。我实测对比过加了warmup和cosine衰减之后大模型的最终效果能稳定提升一截而且训练过程更顺滑不会动不动就NaN。5. 怎么把知识变成能力一套可复制的学习路径5.1 理论、代码、项目三段走很多初学者最大的误区是“只看不动”。深度学习动手比看书重要得多但动手也得有个顺序。我的建议是理论先搭框架代码用来验证理解项目用来建立全局观。理论学习不用贪多找一本经典教材或者一门公开课把“网络结构、损失函数、反向传播、优化器、正则化”这几章啃明白就够用了。经典教材大家习惯叫它“花书”全名是《Deep Learning》前几章的理论推导非常扎实但第一次读不需要死磕每一行公式抓住主干逻辑才是重点。课程方面各大高校公开的深度学习课程也很多选一门口碑好的跟着听一遍对着讲义写笔记比自己瞎翻书效率高得多。代码实践我特别推荐一个很多公开课都强调过的经典练习不要用任何深度学习框架纯手写一个多层感知机自己实现前向传播和反向传播。用Python加NumPy就能做数据集就用MNIST手写数字。这个练习看起来简单但它逼你把反向传播的每一行公式都写出来做完之后你对网络的“血流方向”会有一个极其直观的感受。之后再切换到 PyTorch 或 TensorFlow你会发现框架里那些接口一下子有了具体的画面不再是一堆黑盒函数。5.2 用“最小项目”跑通全流程建立基本功手写反向传播之后就可以进入项目实践了。我强烈建议新手从一个小而完整的数据集开始比如图像分类用 CIFAR-10文本分类用 IMDB 影评。这类数据集规模不大、任务定义清晰而且社区讨论多你踩坑了很容易搜到解决方案。一个最小项目的标准流程大概是这样的用 PyTorch 定义一个简单的卷积网络或循环网络结构。写好数据加载和预处理流程注意训练集和验证集要分开不能混在一起。定义损失函数、优化器跑起来观察训练集和验证集损失的变化曲线。发现模型不收敛先查数据预处理是否正常、学习率是否合适再看网络结构有没有问题。收敛之后尝试给模型加正则化、调参记录一组对照实验的结果。这套流程走完你对深度学习实际开发的整个生命周期就有了完整的体感。之后再去复现一些经典论文、参加比赛或者开始处理更大规模的数据就会有章法得多。我有一个不算秘密的经验训练过程中每跑完一个epoch就把模型参数保存一次同时在代码里打印出完整的指标曲线。这能省下无数后悔的时间因为你永远不知道哪一步的改动是压垮训练的最后一根稻草。5.3 记好实验日志比多跑十次实验更重要最后这点是我最想提醒每个刚入门的人注意的一定要养成记录实验日志的习惯。很多人跑模型时随手改一个参数就重新训练过几天回头问自己“上次那个结果是怎么跑出来的”脑子里一片空白。我自己的做法是每个实验应用一个固定编号记录这轮实验网络结构、优化器、学习率、批量大小、数据增强方式、训练时间和最终指标。每次只改动一个变量其他全部保持跟上一轮一致这样每一轮实验的结论都干净可靠。我在实操中的体会是深度学习很多论文里看起来“高大上”的技巧落到自己的任务上到底有没有效最终还是得靠一轮一轮的对照实验来验证。没有实验日志的人调试就像在大雾里开车撞到墙才知道方向错了有了实验日志你至少能看到自己是从哪条路开过来的下一次改动能更有底气。这个笔记编号里的“021-1”对我来说意味着整个知识体系正在一步步搭建的过程中核心概念这块算是第一块完整的拼图后面如果继续往下拆会顺着卷积、循环网络、训练技巧这些方向再展开。深度学习这条路确实不轻松但只要把基本框架走扎实后面每往前一步都会觉得越来越有意思。
返回列表