
个人主页 for_ever_love__ 其他栏目: 我想学python了 其他栏目: iOS项目总结大全 其他栏目: iOS UI 文章目录线性回归与梯度下降——从零手写一个模型一、问题定义找一条最贴合的直线二、损失函数MSE三、手推梯度四、梯度下降一步步走到谷底五、学习率最重要的超参数加上学习率衰减六、批量梯度下降的三种形态七、多元线性回归特征不止一个八、和 sklearn 对比验证九、常见坑与注意事项十、本篇小结线性回归与梯度下降——从零手写一个模型承上上一篇《监督学习全景》我们知道回归用 MSE、数据集要三段划分。本篇本篇不用任何机器学习库从零用 NumPy 实现线性回归与梯度下降。启下下一篇《逻辑回归与 Softmax》把回归输出压成概率变成分类器。学完这一节你能动手做手推 MSE 的梯度并用数值法验证正确性实现 BGD、SGD、Mini-batch 三种梯度下降并比较说清为什么大模型求不出解析解、只能迭代训练上一篇我们把监督学习的地图铺开了。这一篇我们不用任何机器学习库只靠 NumPy 从零训练一个模型。为什么要这么做因为线性回归是最简单的神经网络——一个没有隐藏层、没有激活函数的网络。你今天搞懂它的训练过程后面理解几十亿参数的大模型只是规模上的差别原理完全一样。本篇你会亲手实现损失函数、梯度计算、参数更新、批量训练、学习率调度最后和 sklearn 对比验证。一、问题定义找一条最贴合的直线假设我们有一批数据房屋面积 (x) 和价格 (y)。想找一条直线ŷ w·x b让预测值尽量接近真实值。ŷ w·x bw权重斜率每平米值多少钱b偏置截距底价目标找到让损失最小的w和b。先造点带噪声的数据importnumpyasnpimportmatplotlib.pyplotasplt np.random.seed(42)n100X2*np.random.rand(n,1)# 面积单位100㎡0~2y43*Xnp.random.randn(n,1)*0.8# 真实关系 y 3x 4 噪声print(X 前5个:,X[:5].ravel().round(2))print(y 前5个:,y[:5].ravel().round(2))真实参数是w3, b4。我们的模型能不能把它们学出来二、损失函数MSEdefmse_loss(y_true,y_pred):returnnp.mean((y_pred-y_true)**2)defpredict(X,w,b):returnX wb# 矩阵写法支持任意特征数# 随便猜一组参数看看损失wnp.array([[0.0]])bnp.array([0.0])print(初始 loss:,round(float(mse_loss(y,predict(X,w,b))),4))w0, b0时 loss 很大约 40 多。我们的任务就是让它降下去。三、手推梯度损失L (1/n) · Σ (ŷᵢ - yᵢ)² ŷ X·w b用链式法则∂L/∂ŷ (2/n)(ŷ - y) ∂ŷ/∂w X → ∂L/∂w Xᵀ · ∂L/∂ŷ ∂ŷ/∂b 1 → ∂L/∂b Σ ∂L/∂ŷ写成矩阵形式非常简洁defgradients(X,y,w,b):nX.shape[0]y_predpredict(X,w,b)erry_pred-y# (n,1)dw(X.T err)*2/n# (features,1)dbnp.sum(err)*2/n# scalarlossnp.mean(err**2)returnloss,dw,db loss,dw,dbgradients(X,y,w,b)print(loss:,round(float(loss),4), dw:,dw.ravel(), db:,round(float(db),4))用数值梯度验证一下这个习惯要养成defnumerical_check(X,y,w,b,eps1e-6):_,dw_a,db_agradients(X,y,w,b)# 检查 dww1w.copy();w1[0,0]eps w2w.copy();w2[0,0]-eps dw_n(mse_loss(y,predict(X,w1,b))-mse_loss(y,predict(X,w2,b)))/(2*eps)# 检查 dbdb_n(mse_loss(y,predict(X,w,beps))-mse_loss(y,predict(X,w,b-eps)))/(2*eps)print(fdw 解析{dw_a[0,0]:.6f}数值{dw_n:.6f})print(fdb 解析{db_a:.6f}数值{db_n:.6f})numerical_check(X,y,w,b)两者一致说明梯度推导正确。四、梯度下降一步步走到谷底核心就一行w - lr * dw。deftrain(X,y,lr0.1,epochs200,verboseTrue):n,dX.shape wnp.zeros((d,1))bnp.zeros(1)history[]forepochinrange(epochs):loss,dw,dbgradients(X,y,w,b)w-lr*dw# 沿梯度反方向更新b-lr*db history.append(float(loss))ifverboseand(epoch%400orepochepochs-1):print(fepoch{epoch:3d}loss{loss:.4f}w{w.ravel().round(3)}b{b.round(3)})returnw,b,history w_trained,b_trained,histtrain(X,y,lr0.1,epochs200)print(\n最终参数: w ,w_trained.ravel(), b ,b_trained)print(真实参数: w [3.] b [4.])跑完你会看到w ≈ 3.0、b ≈ 4.0——模型把数据的真实规律学出来了。画出损失曲线plt.figure(figsize(7,4))plt.plot(hist,lw2)plt.xlabel(epoch);plt.ylabel(MSE loss)plt.title(Loss Curve)plt.grid(alpha0.3);plt.tight_layout();plt.show()一条平滑下降最后趋于平缓的曲线——这就是训练成功的样子。五、学习率最重要的超参数学习率决定每步走多大。走小了慢走大了发散。fig,axesplt.subplots(1,3,figsize(15,4))forax,lrinzip(axes,[0.01,0.1,1.2]):try:_,_,htrain(X,y,lrlr,epochs100,verboseFalse)ax.plot(h,colortab:blue)ax.set_title(flr{lr}最终loss{h[-1]:.3f})exceptExceptionase:ax.set_title(flr{lr}发散)ax.set_xlabel(epoch);ax.set_ylim(0,5)plt.tight_layout();plt.show()学习率现象处理太小0.001loss 缓慢下降几百轮还没收敛调大或增加 epoch合适0.1快速下降后收敛✅偏大1.0震荡loss 忽高忽低调小太大2发散loss 变 inf/nan立刻调小找学习率的实用技巧从 1e-3 开始每次 ×3 试1e-3 → 3e-3 → 1e-2 → 3e-2 …观察哪个量级 loss 降得最快又不炸。加上学习率衰减训练后期要小心地靠近谷底所以逐步减小步长deftrain_with_decay(X,y,lr0.5,epochs200,decay0.99):n,dX.shape wnp.zeros((d,1));bnp.zeros(1)history[]cur_lrlrforepochinrange(epochs):loss,dw,dbgradients(X,y,w,b)w-cur_lr*dw b-cur_lr*db cur_lr*decay# 指数衰减history.append(float(loss))returnw,b,history _,_,h_decaytrain_with_decay(X,y)print(带衰减最终 loss:,round(h_decay[-1],5))六、批量梯度下降的三种形态真实训练不会一次把所有数据喂进去有三种粒度deftrain_minibatch(X,y,lr0.1,epochs50,batch_size16,seed0):Mini-batch 梯度下降深度学习的事实标准rngnp.random.default_rng(seed)n,dX.shape wnp.zeros((d,1));bnp.zeros(1)history[]forepochinrange(epochs):idxrng.permutation(n)# 每轮打乱顺序forstartinrange(0,n,batch_size):batchidx[start:startbatch_size]Xb,ybX[batch],y[batch]loss,dw,dbgradients(Xb,yb,w,b)w-lr*dw b-lr*db history.append(float(mse_loss(y,predict(X,w,b))))returnw,b,history w_mb,b_mb,h_mbtrain_minibatch(X,y)print(Mini-batch 结果: w ,w_mb.ravel().round(3), b ,b_mb.round(3))方式每次用多少样本优点缺点BGD 批量全部梯度准确、稳定收敛大 dataset 内存爆炸、慢SGD 随机1 个更新快、能跳出局部最优梯度噪声大、震荡Mini-batch16/32/64/128兼顾可并行GPU 友好需调 batch size深度学习几乎都用 mini-batch因为 GPU 最擅长一次算一批矩阵乘法。大模型训练里 batch size 常常是 512 甚至更大。七、多元线性回归特征不止一个真实场景有很多特征。代码完全不变因为我们已经用了矩阵写法fromsklearn.datasetsimportfetch_california_housingfromsklearn.model_selectionimporttrain_test_splitfromsklearn.preprocessingimportStandardScaler datafetch_california_housing()Xh,yhdata.data,data.target.reshape(-1,1)X_train,X_test,y_train,y_testtrain_test_split(Xh,yh,test_size0.2,random_state42)# 必须标准化否则不同量纲的特征会让训练崩溃scalerStandardScaler()X_train_sscaler.fit_transform(X_train)X_test_sscaler.transform(X_test)w_h,b_h,_train(X_train_s,y_train,lr0.05,epochs300,verboseFalse)pred_testpredict(X_test_s,w_h,b_h)rmsenp.sqrt(mse_loss(y_test,pred_test))print(f手写线性回归 测试集 RMSE {rmse:.4f})八、和 sklearn 对比验证写对了没有拿官方实现对比fromsklearn.linear_modelimportLinearRegression,SGDRegressorfromsklearn.metricsimportmean_squared_error,r2_score skLinearRegression().fit(X_train_s,y_train)sk_predsk.predict(X_test_s)print(sklearn LinearRegression RMSE:,round(np.sqrt(mean_squared_error(y_test,sk_pred)),4))print(手写梯度下降 RMSE:,round(rmse,4))print(R² (sklearn):,round(r2_score(y_test,sk_pred),4))print(R² (手写) :,round(r2_score(y_test,pred_test),4))print(\n权重最大差异:,np.abs(sk.coef_.ravel()-w_h.ravel()).max())两者结果几乎一致差异在 1e-3 量级。说明我们的实现是正确的。有意思的是LinearRegression用的是正规方程解析解# 正规方程w (XᵀX)⁻¹ XᵀyX_augnp.c_[np.ones(len(X_train_s)),X_train_s]# 加一列 1 表示偏置w_closednp.linalg.pinv(X_aug.T X_aug) X_aug.T y_trainprint(正规方程 b ,w_closed[0].round(4), sklearn b ,round(sk.intercept_[0],4))解法原理适用正规方程一步解析求解(XᵀX)⁻¹Xᵀy特征 1万小数据集梯度下降迭代逼近任意规模深度学习的唯一选择大模型有几十亿参数XᵀX这个矩阵根本存不下几十亿 × 几十亿所以只能用梯度下降。这就是为什么你只听过训练大模型而没听过解方程求大模型。九、常见坑与注意事项坑现象解决忘了标准化loss 不降或 NaN先StandardScaler学习率太大loss 变 inf/nan降到 1/10 重试特征里有字符串UFuncTypeError先编码One-Hot没加偏置 b直线必须过原点拟合差记得加 bias 项只在训练集看指标上线效果差一定要看测试集形状搞错(100,1) vs (100,)统一用二维(n, d)形状提示np.mean(err**2)返回标量而err是(n,1)。矩阵乘法时注意X.T err得到(d,1)正好和w同形。十、本篇小结线性回归ŷ Xw b就是最简单的神经网络——没有隐藏层、没有激活函数。手动推导了 MSE 的梯度∂L/∂w 2Xᵀ(ŷ-y)/n并用数值梯度验证正确。梯度下降w - lr·dw是训练的核心动作学习率是最重要的超参数可用衰减策略精调。Mini-batch是深度学习的事实标准BGD/SGD/Mini-batch 三选一。正规方程 vs 梯度下降大模型参数太多求不出解析解只能迭代——这就是梯度下降不可替代的原因。手写实现与 sklearn 结果一致RMSE 差异 1e-3验证了正确性。下一篇逻辑回归与 Softmax把线性回归的输出压成概率就变成了分类器。我们会从零实现 Softmax 回归并揭示一个关键事实——大模型最后一层输出下一个词的概率用的正是 Softmax。本篇是《大模型开发从 0 到 1》专栏第 18 篇阶段 3「机器学习基础」第 2 篇。专栏文章按「分类专栏」归类顺序学习体验最佳。