ARTICLE DETAIL

资讯详情

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

逻辑回归与 Softmax——分类问题的数学骨架

逻辑回归与 Softmax——分类问题的数学骨架 个人主页 for_ever_love__ 其他栏目: 我想学python了 其他栏目: iOS项目总结大全 其他栏目: iOS UI 文章目录逻辑回归与 Softmax——分类问题的数学骨架一、从回归到分类需要压扁输出二、Sigmoid二分类的开关三、Softmax多分类的归一器为什么要减最大值温度参数重温四、Softmax 交叉熵梯度美到令人惊叹五、从零实现 Softmax 回归多分类5.1 前向与反向5.2 训练5.3 在测试集评估六、和 sklearn 对比七、加正则化抑制过拟合八、这和大模型的关系重点九、常见坑与注意事项十、本篇小结逻辑回归与 Softmax——分类问题的数学骨架承上上一篇《线性回归与梯度下降》我们让模型输出连续值。本篇本篇用 Sigmoid 与 Softmax 把它压成概率变成分类器——大模型输出层用的正是 Softmax。启下下一篇《决策树与随机森林》换一条完全不同的思路做预测选修视野。学完这一节你能动手做从零实现 Softmax 回归完成训练、评估并与 sklearn 对比理解「Softmax 交叉熵的梯度 p − y」为什么这么简洁看懂大模型最后一层 logits → softmax → 采样的完整链路上一篇我们让模型输出连续值回归。这一篇要解决分类怎么让模型说这是猫不是狗答案是一个极其重要的函数——Softmax。而我要提前剧透一个关键事实大模型预测下一个词时最后一步就是 Softmax。词表 5 万个词模型输出 5 万个分数logitsSoftmax 把它们变成概率然后采样出下一个词。所以这一篇是理解大模型输出层的最后一块拼图。一、从回归到分类需要压扁输出线性回归输出ŷ w·x b取值范围是(-∞, ∞)。但分类要的是概率必须在[0, 1]之间。解决方案套一个压缩函数。任务输出个数压缩函数损失函数二分类1 个正类概率SigmoidBCE多分类C 个C 个类的概率SoftmaxCross-Entropy二、Sigmoid二分类的开关importnumpyasnpdefsigmoid(z):return1/(1np.exp(-z))znp.array([-6.0,-2.0,0.0,2.0,6.0])print(z :,z)print(sigmoid:,sigmoid(z).round(4))zsigmoid(z)含义-60.0025几乎肯定是负类00.5五五开最不确定60.9975几乎肯定是正类Sigmoid 把任意实数压到 (0,1)且在 z0 处最敏感、两端饱和导数趋于 0。它的导数有个非常优美的性质让反向传播极其简洁defsigmoid_grad(z):ssigmoid(z)returns*(1-s)# 验证与数值导数一致eps1e-6numeric(sigmoid(zeps)-sigmoid(z-eps))/(2*eps)print(解析导数:,sigmoid_grad(z).round(6))print(数值导数:,numeric.round(6))σ σ(1-σ)——只用输出值就能算导数不需要保存输入。这是它当年的巨大优势。三、Softmax多分类的归一器defsoftmax(z):z: (C,) 或 (N, C)沿最后一维归一化znp.asarray(z,dtypefloat)zz-np.max(z,axis-1,keepdimsTrue)# ① 减最大值防溢出enp.exp(z)returne/np.sum(e,axis-1,keepdimsTrue)# ② 归一化和为 1logitsnp.array([2.0,1.0,0.1])psoftmax(logits)print(概率:,p.round(4), 和 ,p.sum())Softmax 做两件事用exp放大差距大的更大小的更小归一化成和为 1 的概率分布。为什么要减最大值badnp.exp(np.array([1000,1001,1002]))# 溢出print(直接 exp:,bad)# [inf inf inf]print(减最大值后:,softmax(np.array([1000,1001,1002])).round(4))数学上softmax(z) softmax(z - c)常数约掉但数值上减最大值能避免exp溢出。这是必须写在代码里的标准操作。温度参数重温defsoftmax_t(logits,T1.0):znp.asarray(logits,dtypefloat)/T zz-np.max(z)enp.exp(z)returne/np.sum(e)print(T0.5:,softmax_t(logits,0.5).round(3))print(T1.0:,softmax_t(logits,1.0).round(3))print(T2.0:,softmax_t(logits,2.0).round(3))这就是大模型 API 里temperature参数的实现——改的是 Softmax 的输入缩放。现在你知道调温度的时候底层在算什么了。四、Softmax 交叉熵梯度美到令人惊叹这是深度学习里最漂亮的一个结论。设 logits 为z真实类别为kone-hot 标签y则p softmax(z) L -log(p_k) ∂L/∂z p - y交叉熵对 logits 的梯度就是预测概率 减 真实标签。没有复杂的连乘一行搞定。defsoftmax_cross_entropy(logits,y_onehot):psoftmax(logits)loss-np.sum(y_onehot*np.log(np.clip(p,1e-12,1.0)))gradp-y_onehot# ← 就是这么简单returnloss,grad,p logitsnp.array([2.0,1.0,0.1])ynp.array([1.0,0.0,0.0])# 真实类别 第 0 类loss,grad,psoftmax_cross_entropy(logits,y)print(loss :,round(loss,4))print(probs:,p.round(4))print(grad :,grad.round(4))# 正类的梯度是负的要增大其余为正# 数值验证eps1e-6num_gradnp.zeros(3)foriinrange(3):lplogits.copy();lp[i]eps lmlogits.copy();lm[i]-eps l1,_,_softmax_cross_entropy(lp,y)l2,_,_softmax_cross_entropy(lm,y)num_grad[i](l1-l2)/(2*eps)print(数值grad:,num_grad.round(4))完全一致。这个p - y的简洁性是交叉熵成为分类标配损失的核心原因。五、从零实现 Softmax 回归多分类现在完整实现一个分类器。任务三分类的合成数据。fromsklearn.datasetsimportmake_classificationfromsklearn.model_selectionimporttrain_test_splitfromsklearn.preprocessingimportStandardScaler np.random.seed(0)X,ymake_classification(n_samples1500,n_features4,n_informative4,n_redundant0,n_classes3,random_state42)X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.2,random_state42,stratifyy)scalerStandardScaler()X_train_sscaler.fit_transform(X_train)X_test_sscaler.transform(X_test)defone_hot(y,C):outnp.zeros((len(y),C))out[np.arange(len(y)),y]1.0returnout C3Y_trainone_hot(y_train,C)# (N, 3)print(one-hot 样例:\n,Y_train[:3])5.1 前向与反向defforward(X,W,b):X:(N,d) W:(d,C) b:(C,) → logits:(N,C)returnX Wbdefcompute_loss_grad(X,Y,W,b):NX.shape[0]logitsforward(X,W,b)# (N, C)psoftmax(logits)# 每行归一化loss-np.sum(Y*np.log(np.clip(p,1e-12,1.0)))/N dz(p-Y)/N# (N, C) ← 核心梯度dWX.T dz# (d, C)dbnp.sum(dz,axis0)# (C,)returnloss,dW,db注意dW X.T dz形状(d,N) (N,C) → (d,C)与W同形。5.2 训练deftrain_softmax(X,Y,C,lr0.5,epochs500,batch_size64,seed0):rngnp.random.default_rng(seed)N,dX.shape Wnp.random.randn(d,C)*0.01bnp.zeros(C)history[]forepochinrange(epochs):idxrng.permutation(N)forsinrange(0,N,batch_size):bidxidx[s:sbatch_size]loss,dW,dbcompute_loss_grad(X[bidx],Y[bidx],W,b)W-lr*dW b-lr*dbifepoch%1000orepochepochs-1:full_loss,_,_compute_loss_grad(X,Y,W,b)accnp.mean(np.argmax(forward(X,W,b),axis1)np.argmax(Y,axis1))history.append((epoch,full_loss,acc))print(fepoch{epoch:3d}loss{full_loss:.4f}train_acc{acc:.3f})returnW,b,history W,b,histtrain_softmax(X_train_s,Y_train,C,lr0.5,epochs500)loss 一路下降train accuracy 会上升到 0.85 左右。5.3 在测试集评估fromsklearn.metricsimportaccuracy_score,classification_report,confusion_matrix probssoftmax(forward(X_test_s,W,b))prednp.argmax(probs,axis1)print(测试集 accuracy:,round(accuracy_score(y_test,pred),4))print(classification_report(y_test,pred))print(混淆矩阵:\n,confusion_matrix(y_test,pred))# 看一下模型对第一个样本的信心print(第1个样本各类概率:,probs[0].round(3), 真实:,y_test[0])六、和 sklearn 对比fromsklearn.linear_modelimportLogisticRegression sk_modelLogisticRegression(max_iter1000,C1.0)sk_model.fit(X_train_s,y_train)sk_predsk_model.predict(X_test_s)print(sklearn accuracy:,round(accuracy_score(y_test,sk_pred),4))print(手写 accuracy:,round(accuracy_score(y_test,pred),4))print(权重形状对比:,W.shape,sk_model.coef_.T.shape)两者准确率接近差 1% 以内源于正则化和优化器差异。我们的 Softmax 回归实现对了。注意 sklearn 的LogisticRegression默认带L2 正则C是正则强度的倒数C越小正则越强我们手写的没加——这会导致手写版在训练集上略高、测试集上略低正是过拟合的迹象。七、加正则化抑制过拟合defcompute_loss_grad_reg(X,Y,W,b,l21e-3):NX.shape[0]logitsforward(X,W,b)psoftmax(logits)loss-np.sum(Y*np.log(np.clip(p,1e-12,1.0)))/N loss0.5*l2*np.sum(W**2)# L2 正则项dz(p-Y)/N dWX.T dzl2*W# 梯度也要加 l2*Wdbnp.sum(dz,axis0)returnloss,dW,db W2,b2,_train_softmax(X_train_s,Y_train,C,lr0.5,epochs500)print(无正则 测试 acc:,round(accuracy_score(y_test,np.argmax(softmax(forward(X_test_s,W2,b2)),1)),4))正则项l2*W会持续把权重往 0 拉防止模型死记训练样本。下一篇会系统讲这件事。八、这和大模型的关系重点现在把视野拉回大模型# 模拟大模型最后一层词表 8 个词隐藏维度 6np.random.seed(1)vocab[我,爱,吃,苹果,鱼,猫,的,。]d_model6hiddennp.random.randn(1,d_model)# 当前上下文的隐藏状态 (1, 6)W_vocabnp.random.randn(d_model,len(vocab))# 输出投影矩阵 (6, 8)logitshidden W_vocab# (1, 8) 每个词的分数probssoftmax_t(logits[0],T0.8)# 变成概率forw,pinsorted(zip(vocab,probs),keylambdat:-t[1]):print(f{w}:{p:.1%})这就是大模型生成下一个词的全部流程上下文 → Transformer 若干层 → 隐藏状态 h ↓ logits h W_vocab (词表大小的分数) ↓ probs softmax(logits/T) (概率) ↓ next_token sample(probs) (采样)词表从 8 个变成 5 万个隐藏维度从 6 变成 12288但数学完全一样。你现在能读懂大模型推理代码里最关键的那一层了。为什么大模型训练慢因为要算 5 万类的 Softmax 和交叉熵还要对所有参数求梯度。业界为此发明了各种优化分层 Softmax、采样损失等。九、常见坑与注意事项坑现象解决Softmax 溢出nan或全 0先减np.maxlog(0)loss infnp.clip(p, 1e-12, 1)忘了除以 Nloss 随 batch 大小变化梯度除以batch_size标签没 one-hot形状不匹配one_hot()转换用 MSE 做分类收敛极慢换交叉熵权重初始化为 0对称性问题多层网络尤其严重随机小值初始化学习率过大loss 震荡Softmax 回归常用 0.1~1.0权重初始化为什么不能全 0单层 Softmax 回归还行但多层神经网络里全 0 初始化会导致所有神经元算出相同梯度、永远对称网络退化成一个神经元。所以必须随机初始化。十、本篇小结Sigmoid压到 (0,1) 做二分类导数σ(1-σ)便于反向传播。Softmax把任意实数向量变成概率分布实现时必须先减最大值防溢出除以温度 T 就是大模型的 temperature 参数。Softmax 交叉熵的梯度 p - y简洁到不可思议这也是交叉熵成为分类标配的原因。我们从零实现了完整的 Softmax 回归前向/反向/mini-batch/评估与 sklearn 准确率相当。大模型输出层就是一个超大 Softmaxlogits h W_vocab→softmax→ 采样。下一篇决策树与随机森林换一条完全不同的思路做预测——不靠梯度靠问一系列是非题。理解它你才能明白为什么神经网络不是万能的以及大模型为什么最终选择了神经网络这条路。本篇是《大模型开发从 0 到 1》专栏第 19 篇阶段 3「机器学习基础」第 3 篇。专栏文章按「分类专栏」归类顺序学习体验最佳。
返回列表