ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

logistic回归原理与实操:从sigmoid到交叉熵的二分类深度学习入门

logistic回归原理与实操:从sigmoid到交叉熵的二分类深度学习入门 说起深度学习课程里的“实验三”十有八九会撞上这样一句话用 logistic 回归预测二分类。我见过不少同学看到题目后的第一反应是“这算哪门子深度学习不就是个线性模型套了个激活函数吗”真等自己上手跑完才发现这个实验从头到尾踩的坑比后面好多大型网络实验加起来都要多。其实 logistic 回归是理解整个深度学习最理想的一颗钉子。从网络结构上看它就是一个没有隐藏层的单神经元模型输入特征做加权求和过一个 sigmoid 激活输出一个 0 到 1 之间的概率。这套流程你吃透了后续学多层感知机、CNN、RNN 时就会发现大家都是在同一个骨架上做文章只是把单神经元变成了大网络把线性加权换成了卷积或循环结构。当年我带一位零基础的 A 同学做这个实验他花了一个下午把前向传播、损失函数、梯度更新通了隔周再去看多层网络的反向传播明显比别人轻松一大截。这篇文章我会把这个实验从原理到实操完整拆开把 sigmoid 为什么长这样、交叉熵为什么比均方误差好用、数据预处理怎么做不泄露、训练曲线怎么看这些“老师不细讲但考试和工作都要考”的东西一次性说清楚。适合正在做课程实验的同学也适合想回头把这颗地基补扎实的自学者。1. 实验整体认知与设计思路1.1 实验三为什么偏偏选 logistic 回归很多课程的实验顺序都是精心排过的把 logistic 回归放在第三个实验刚好卡在“你已经会线性回归了但还没接触神经网络”的中间地带。它有两个身份对经典机器学习来说它是一个线性分类器对深度学习来说它是神经网络最朴素的雏形。从结构上看logistic 回归做的事情极其简单把每个特征乘上一个权重累加再加一个偏置然后过一个 sigmoid 函数把结果压到 0 到 1 之间。这个结构像什么就是一个最精简的神经元。学完这个单神经元下一步加一层隐藏层就是两层神经网络再加几层就变成了一个标准的多层感知机。所以实验安排这门课的人真实意图是让你先用最少的代码把前向传播、损失计算、梯度反传、参数更新这条完整流水线走一遍。这个实验的定位决定了它的学习重点不在模型本身的精度而在于理解训练过程。说实话二分类任务用 logistic 回归去跑很多时候精度也不会特别高尤其是数据不是线性可分的时候它的表现可能就是“够用”。但正因为模型简单所有中间过程都可以被清清楚楚地打印出来、画出来、算出来——权重怎么变、损失怎么降、决策边界怎么移动全部都直观可见。这是深网络给不了你的学习体验。1.2 二分类预测的建模思路二分类在现实中随处可见一封邮件是不是垃圾邮件一个用户会不会下单一个病人是不是得了某种疾病本质上都是“二选一”的判断题。这个实验要做的事情就是给出一组样本和它们的标签训练一个模型让模型在面对没见过的样本时能给出一个正确的二分类判断。这里有一个初学者很容易误解的地方模型输出并不是直接的 0 或 1而是一个概率值。比如模型对某条样本输出 0.87意思就是“我觉得它有 87% 的概率属于类别 1”。然后我们再人为约定一个阈值——通常是 0.5——概率大于 0.5 判定为类别 1否则判定为类别 0。后面我还会讲到这个阈值不是固定死的不同业务场景下调阈值可能比调模型参数还管用。整个实验的建模流程可以归纳成一条清晰的流水线数据准备拿到特征矩阵 X形状是 样本数×特征数和标签向量 y形状是 样本数×1。数据预处理标准化、划分训练集和测试集。模型定义初始化权重 W 和偏置 b写出前向传播函数。损失计算用交叉熵衡量预测概率与真实标签的差距。梯度计算与参数更新让模型在训练数据上一点点往正确的方向调整。评估在测试集上验证泛化效果。建议你在动手写代码之前先在纸上把这个流程走一遍把每一步的输入输出形状写清楚。很多人写着写着就乱了回头查 bug 半天最后发现是某个张量的维度没对上。2. 核心原理拆解sigmoid 与交叉熵的搭档逻辑2.1 从线性回归到概率输出sigmoid 到底在做什么先回忆一下线性回归给定特征 x1、x2…… 模型算出一个分数 z w1x1 w2x2 ... b。在回归任务里这个 z 可以直接作为预测值。但二分类任务不行——分类要输出的是概率概率有天然的取值范围必须在 0 到 1 之间而线性回归的 z 范围是从负无穷到正无穷直接拿来当概率是不合理的。sigmoid 函数就是来解决这个问题的公式是σ(z) 1 / (1 e^(-z))它的作用是把任意实数 z 压缩到 (0, 1) 区间。z 越大输出越接近 1z 越小输出越接近 0z 恰好为 0 时输出为 0.5。这个函数图像是一条平滑的 S 型曲线所以它本质上做的是一件非常优雅的事情把“证据的强度”转化成“某种结果发生的概率”。举个例子感受一下假设某个特征的权重是 0.8x 每增加一个单位z 就增加 0.8sigmoid 输出就会明显地向 1 靠拢。相当于模型每看到一点新的证据就它对类别 1 的信心就更足一点而且这种信心的增长不是线性的——靠近中间区域时变化快到了极值附近就饱和了这和人类的直觉也蛮契合的。在代码里实现它也非常简单def sigmoid(z): return 1 / (1 np.exp(-z))不过这里有个小细节如果你在损失函数里面用了 log(p)p 可能被算成 0log(0) 会直接报警。后面我会讲怎么用数值技巧规避。2.2 决策边界为什么是线性的把前向传播的公式展开来看p σ(w1x1 w2x2 ... b)当我们把判定阈值设为 0.5也就是 p 0.5 时预测为类别 1那实际上等价于看 z w1x1 w2x2 ... b 是否大于 0。z 0 这条线就是模型的决策边界。因为 z 是特征的线性组合所以 z 0 在二维空间里是一条直线在三维空间里是一个平面在高维空间里是一个超平面。这给 logistic 回归带来了两个鲜明的特点一是可解释性极强。每个特征的权重 W 的正负方向直接告诉我们这个特征对预测结果是正向推动还是反向推动绝对值大小代表影响强度。这是很多树模型和深度网络做不到的。二是模型容量有限。当数据本身线性不可分时比如两类样本像阴阳鱼一样纠缠在一起单纯一个线性决策边界是搞不定的。这时你会看到训练精度卡在一个不上不下的位置怎么调学习率都没用。这不是 bug而是模型表达能力的限制后续课程通常会通过引入隐藏层或者核技巧来解决。2.3 交叉熵为什么完胜均方误差训练模型需要定义一个损失函数来指导“往哪个方向调整参数”。二分类最常用的损失是交叉熵L -[y·log(p) (1-y)·log(1-p)]拆开看当真实标签 y 1 时损失变为 -log(p)p 越接近 1损失越小当 y 0 时损失变为 -log(1-p)p 越接近 0损失越小。换句话说模型越自信地猜错惩罚越大越接近正确答案损失越趋近于 0。这种“对数惩罚”的特性让模型不敢在错误的方向上过度自信。为什么不直接用均方误差 MSE (y - p)² 呢这是实验报告里特别爱问的一道题。关键在于梯度。把 sigmoid 代入 MSE 之后梯度里会冒出一个 σ(z) σ(z)·(1-σ(z)) 项。这个项有个致命弱点当 z 很大的时候σ 接近 1σ(1-σ) 接近 0当 z 很小的时候σ 接近 0σ(1-σ) 也接近 0。也就是说模型在预测得越极端、越自信的时候梯度反而越小训练变得极其缓慢这在深度学习中叫“梯度饱和”。而交叉熵配合 sigmoid 之后求导结果很漂亮梯度变成了 (p - y)·x。这个式子直观又合理——预测和真实标签差距越大梯度的幅度越大模型学得越快预测接近正确时梯度自然变小。这也是为什么深度学习里几乎不会用 MSE 来做分类任务的损失函数。所以你在实验里如果不小心把损失换成了 MSE会观察到训练半天损失都降不下去不是代码 bug而是数学选型出了问题。建议你在实验中分别用交叉熵和 MSE 各跑一次把损失下降曲线对比一下。这个对比实验做出来你对这个知识点的理解会比只看书深刻得多。3. 实操全流程从数据准备到训练闭环3.1 数据准备与预处理的几个细节做这个实验我建议你先不要直接上复杂数据集而是自己生成一批可分的二维模拟数据。好处是直观后面画决策边界时一眼就能看出效果。比如生成两堆数据类别 0 以某个点为中心呈高斯分布类别 1 在另一个点附近分布让它们之间有一条清晰的分界线。拿到原始数据后有几步预处理是整个实验成败的关键很多人第一步就跑偏了。特征标准化大概是这个实验里第二重要的预处理操作。如果 x1 的取值范围是 0 到 1而 x2 的范围是 0 到 10000那权重更新时会被大尺度的特征主导梯度下降会在某些方向上震荡收敛速度极慢。标准做法是把每个特征减去均值、除以标准差让数据分布在一个相近的尺度上。至于先划分数据集还是先标准化这里有一个很容易被忽略的坑。正确顺序是先把数据拆成训练集和测试集然后用训练集的均值和标准差去标准化训练集和测试集。绝对不要在完整数据集上先算均值方差再做标准化因为这样测试集的信息会偷偷混进训练阶段评估结果会虚高这种现象叫数据泄露。实验数据的随机性本就是这个实验要模拟的真实情况一旦泄露你后面调参得到的所有结论都不可信。我见过不止一个同学在报告里写“我的模型在测试集上准确率 98%”结果代码被细看后发现他是把数据整体标准化之后才划分的测试集。做实验不是只看数字而是要确保数字背后的逻辑站得住脚。划分数据时还有一个小建议二分类数据最好用分层采样保证训练集和测试集里的正负样本比例大致一致否则随机切分完全可能把某个类别的样本都切到测试集里去。3.2 模型定义与前向传播logistic 回归的前向传播说白了就是三行核心公式# 输入 X: (n_samples, n_features) # 权重 W: (n_features, 1) # 偏置 b: 标量 # 1. 线性加权 z X W b # 2. 通过 sigmoid 得到概率 p sigmoid(z)这里X W是矩阵乘法的标准写法如果你用的是带自动求导的深度学习框架这行代码基本不用改只是封装成网络层的写法会更简洁。但我始终建议至少手写一遍不借助自动求导的实现因为你只有亲手写出梯度才能真正理解反向传播的细节。参数初始化方面logistic 回归里把 W 设成全 0 理论上是可行的因为模型只有一个神经元不像多层网络那样存在对称性问题。但为了和后续深度学习实验保持习惯一致我还是建议用小的随机数初始化幅度控制在 0.01 左右。这样写的好处是你以后改造成多层网络时不需要重新适应初始化规则。构建完模型后先用一个样本做前向传播手动算一遍概率和损失确认维度都正确之后再开始训练。这个“先手工算一遍”的习惯能帮你省下大量调试时间。3.3 训练循环的搭建与监控训练过程是一个不断重复的循环核心动作可以归纳为三件事做一次前向传播、算一次损失、更新一次参数。用伪代码写出来非常清晰epochs 500 learning_rate 0.1 for epoch in range(epochs): # 前向 p sigmoid(X_train W b) # 损失加一个小 epsilon 防止 log(0) loss -np.mean(y * np.log(p 1e-8) (1 - y) * np.log(1 - p 1e-8)) # 梯度推导结果就是 X^T (p-y) 除以样本数 grad_w X_train.T (p - y) / n_samples grad_b np.mean(p - y) # 参数更新 W - learning_rate * grad_w b - learning_rate * grad_b # 每 20 轮打印一次状态 if epoch % 20 0: acc ((p 0.5).reshape(-1) y).mean() print(fepoch {epoch}, loss {loss:.4f}, acc {acc:.4f})关于这个梯度公式 grad_w X_train.T (p - y) / n_samples我第一次推出来的时候觉得挺惊喜的logistic 回归的梯度形式竟然和线性回归的梯度形式长得很像都是“(预测值 - 真实值) 再乘上特征”。这背后的逻辑是交叉熵和 sigmoid 这一对组合恰好让非线性激活函数带来的影响在梯度里被抵消掉了只剩下一项线性的误差信号。训练循环里有一个地方值得单独强调你应该同时记录训练集和验证集的损失与准确率。很多人只盯着训练集看看到一个漂亮的收敛曲线就以为大功告成其实训练集曲线再漂亮也不能说明泛化能力只有验证集曲线才能看出模型是否过拟合。还有 batch size 的概念。上面这个循环是每次把所有数据都拿进来更新一次梯度这叫全量梯度下降数据量小的时候没问题。如果数据量有几万条每次全量计算会很慢可以每次随机抽一小批数据比如 32 或 64 条来更新参数这就是 mini-batch 梯度下降。实验阶段用全量就够了但如果你将来做大模型训练会发现 mini-batch 几乎是标配。4. 训练调参与效果分析4.1 学习率怎么调别在玄学里浪费半天学习率大概是初学者在实验中花费时间最多、也最容易让人抓狂的一个超参数。我自己常用的策略是先把学习率固定在一个基准值上跑一遍观察损失曲线的形态再决定调大还是调小。学习率典型现象处理建议1.0 及以上损失剧烈震荡甚至出现 NaN立即调小 10 倍以上0.1正常收敛曲线顺滑可以作为默认起点0.01稳定下降但速度偏慢增加迭代轮数即可0.001下降非常缓慢效率低可能需要上万轮不建议看曲线时抓住一个核心信号损失曲线如果一路顺滑下降且最终平稳说明学习率基本合理如果先快速下降然后开始锯齿状抖动往往是学习率偏大如果从头到尾都平坦得几乎不动可能就是学习率太小或者特征没做标准化导致梯度太弱。迭代轮数也不是越大越好。模型在训练集上损失会一直下降但验证集的损失通常会先降后升。所以更严谨的做法是训练过程中每轮都记录验证集损失当验证集损失开始上升时说明继续训练已经没有意义了完全可以早停。这个技巧叫 early stopping在后面的深度学习课程中同样非常实用。4.2 过拟合与欠拟合在曲线上的表现很多人觉得 logistic 回归是线性模型应该不会过拟合这个想法不完全对。当特征数量远多于样本数量时logistic 回归照样会过拟合。欠拟合的特征很典型训练集损失很高训练集准确率也很低而且无论如何延长训练时间都上不去。这种情况多半是数据线性不可分或者特征确实和标签关系不大。解决方案要么是换更复杂的模型要么是构造更有效的特征单纯调参数是救不回来的。过拟合的特征正好相反训练集损失一路下降训练准确率高达 95% 以上但验证集准确率停在 75% 左右上不去验证集损失不降反升。这时可以在损失函数中加入正则项比如在交叉熵基础上加 λ/2·||W||²对应的梯度更新代码只需要多减一项 λ·W。这一步加进去你会观察到验证集表现有明显改善。这也是后续深度学习实验里 weight decay 概念的起源。4.3 评估指标别让准确率骗了你实验报告里如果只写一个准确率那你大概率会被追问一个问题如果测试集里 90% 是负样本10% 是正样本一个无脑把所有样本都预测为负样本的模型准确率是多少答案是 90%。所以准确率在类别不平衡的数据集上是非常具有欺骗性的。更完整的评估体系是看混淆矩阵以及由它引出的几个指标精确率 Precision TP / (TP FP)就是预测为正样本的那些里有多少是真的正样本。垃圾邮件过滤场景更看重这个误杀一封正常邮件比漏掉一封垃圾邮件严重得多。召回率 Recall TP / (TP FN)就是真实正样本里有多少被找出来了。疾病筛查场景更看重这个宁可多查几次也不能漏诊。F1 分数是两者的调和平均适合需要同时兼顾精确率和召回率的场景。还有一个在实验报告中很加分的指标是 AUC也就是 ROC 曲线下的面积。它衡量的是模型“随机抽一对正负样本把正样本排在前面的概率”所以它不受分类阈值的影响能更全面地反映模型的排序能力。AUC 在 0.5 附近说明模型跟瞎猜差不多越接近 1 说明区分能力越强。我建议你在实验里把准确率、精确率、召回率、F1、AUC 都算一遍然后写进报告顺便对比一下它们在当前数据集下的表现。这样一份实验报告交上去老师一眼就能看出你是真把二分类模型玩明白了而不是只会调一个包然后打印 accuracy。5. 常见问题与避坑实录5.1 损失不下降、甚至变成 NaN这是每次实验都有一半人会碰到的问题排查顺序很重要不要病急乱投医。第一件事看学习率。如果 loss 从第一轮开始就是 NaN那大概率是学习率设太大参数更新一步就飞出去了。先把学习率降到 0.001 试试如果恢复正常再逐步往上加。第二件事看特征尺度。没有标准化的数据特征范围如果达到几千z 的值也会变得很大sigmoid 直接饱和梯度小到几乎为 0损失变化慢得像蜗牛。这时你看到的就是 loss 在下降趋势上“一动不动”而不是上下震荡。第三件事检查数据本身。用代码检查一下 X 和 y 里有没有 NaN这个操作很多人会跳过但真实数据里确实容易出现空值。第四件事确认形状。如果 p 和 y 的 shape 不对loss 和梯度都可能算出一个奇怪的值。打印 p.shape 和 y.shape一眼就能发现。这里也顺便给自己留个好习惯在计算 log(p) 和 log(1-p) 时加一个极小值兜底比如 log(p 1e-8)防止 p 被算出恰好为 0 导致的无穷大问题。5.2 训练集损失在降验证集准确率却纹丝不动这个现象背后的原因通常有三个。数据划分有问题没有分层抽样导致验证集里某一类样本特别多模型学到的模式在验证集上根本不适用。标准化信息泄露直接在全体数据上做了标准化再划分数据集这种操作会让模型在验证集上的表现虚高然后在换一份真实数据测试时瞬间打回原形。样本量太少验证集只有几十条样本时评估结果方差会很大今天 80%明天 60%根本没有参考价值。最稳妥的方案是先划分训练集和测试集再只对训练集做标准化。划分时使用分层采样保证类别比例一致。如果数据量太少可以考虑用交叉验证来替代单次划分。5.3 准确率很高但 F1 很低怎么办准确率 85%、F1 却只有 30%这种情况在类别不平衡的数据集里非常典型。你可以想象一下一个数据集有 900 个负样本和 100 个正样本模型把所有样本都预测为负类准确率是 90%但召回率是 0%F1 自然低得可怜。想让模型真正学会抓少数类有两条路调整分类阈值。不要把阈值死定为 0.5可以把阈值调到 0.3 或更低让模型更容易判定为正类。你可以在验证集上画一条 precision-recall 随阈值变化的曲线看阈值放在哪个位置能让 F1 最高。给少数类更大的权重。在损失函数中给正样本乘以一个大于 1 的权重相当于人为放大正样本的犯错成本让模型更愿意去拟合少数类。这两条路在 logistic 回归里都可以轻松实现建议你都试一遍然后在报告里写清楚你对阈值的理解这会比单纯说“我调了学习率”更有说服力。5.4 一个救命调试技巧先过拟合一个小批量这是我自己用得非常多的调试套路。在训练之前先只取 8 个样本作为训练集跑一个小规模训练。如果代码逻辑没问题模型的损失应该会在几十个 epoch 内快速降到接近于 0训练准确率能到 100%。如果小批量数据都无法过拟合那说明你的前向传播、损失函数或者梯度更新里面一定有 bug先把这个 bug 找出来再说。这么做最大的意义在于它把“模型学习的逻辑”和“数据规模带来的问题”分开排查了。等小批量能过拟合再切回全量数据这时你面对的就只剩下学习率、正则化这些正经调参问题而不是跟一个写错符号的梯度公式搏斗两个小时。写到最后的一些实在话这个实验我前前后后重写过很多遍每写一遍都有新的理解。第一遍用深度学习框架跑通的时候我只知道调包很多细节是懵的第二遍强迫自己手写前向和梯度更新才算真正搞懂 sigmoid 和交叉熵为什么是一对好搭档第三遍加上正则化和评估指标才发现自己以前对实验报告的理解太浅了。我建议你也找时间做一次“手写版”的 logistic 回归哪怕只是抽出半天时间它的价值远比你跑来跑去调学习率大得多。最后再分享一个实战小技巧很多同学在这个实验里会把大量时间耗在调学习率上但实际操作中最有效的路径是先固定一个学习率、把整个流程跑通确保代码无误再回头调参。先保证流程完整再追求性能优化——这个顺序在后面的所有深度学习项目里都不会变。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表