Python神经网络编程三梯度下降法与参数选择本文是《Python神经网络编程》系列博客的第3篇基于真实服务器实操输出涵盖实验5-6的所有内容。服务器环境Ubuntu 24.04 / Python 3.12.3 / NumPy 2.5.1 / Matplotlib 3.11.0服务器华为云 ecs-1f62-0001 (120.46.93.164)目录你将学到什么实验5神经网络的学习梯度下降法实验6参数的选择总结下一篇预告你将学到什么在本篇博客中你将学习神经网络训练的核心算法和关键参数选择梯度下降法Gradient Descent神经网络如何学习学习率Learning Rate步长对收敛的影响权重初始化Weight Initialization好的开始是成功的一半激活函数选择Sigmoid、Tanh、ReLU 适用场景目标值范围为什么不能用 0 和 1实验5神经网络的学习梯度下降法5.1 暴力破解权重的不可行性问题神经网络有成千上万个权重能否用暴力方法穷举找到最优权重计算量分析对于一个简单的网络3-3-3-2输入3两个隐藏层各3输出2importnumpyasnp# 网络结构input_nodes3hidden1_nodes3hidden2_nodes3output_nodes2# 权重总数weights_ihinput_nodes*hidden1_nodes# 3×3 9weights_h1h2hidden1_nodes*hidden2_nodes# 3×3 9weights_h2ohidden2_nodes*output_nodes# 3×2 6total_weightsweights_ihweights_h1h2weights_h2oprint(f网络结构:{input_nodes}-{hidden1_nodes}-{hidden2_nodes}-{output_nodes})print(f权重总数:{weights_ih}{weights_h1h2}{weights_h2o}{total_weights}个)# 暴力破解每个权重尝试10个值# 总组合数10^total_weightscombinations10**total_weightsprint(f\n每个权重尝试 10 个值)print(f总组合数: 10^{total_weights}{combinations})print(f即: 1后面跟{total_weights}个零)# 假设每秒计算1亿种组合speed1e8# 1亿/秒yearscombinations/speed/(365*24*3600)print(f\n即使每秒计算{int(speed):,}种组合:)print(f 需要约{years:.2e}年)print(f {years/1e8:.2f}亿年)print(f\n结论: 暴力破解完全不可行! 需要更聪明的方法 - 梯度下降)服务器真实输出网络结构: 3-3-3-2 权重总数: 9 9 6 24 个 每个权重尝试 10 个值 总组合数: 10^24 1000000000000000000000000 即: 1后面跟 24 个零 即使每秒计算 100,000,000 种组合: 需要约 3.17e08 年 0.32 亿年 结论: 暴力破解完全不可行! 需要更聪明的方法 - 梯度下降结论对于一个只有24个权重的小网络暴力破解需要3亿年对于MNIST网络79,400个权重暴力破解需要的时间远超宇宙年龄。5.2 梯度下降法#梯度下降法Gradient Descent是神经网络的学习算法。核心思想#沿着误差函数的梯度反方向逐步更新参数最终到达误差最小的点。误差 E ↑ │ │ / │ / │/ │/ │/ │/ │/ │/ │/ │/ └──────────→ 参数 w数学原理#对于一个简单的函数f(x) x² 2x 1 (x1)²最小值在x -1f(-1) 0导数f(x) 2x 2梯度下降更新规则x_new x_old - η × f(x_old)其中ηeta是学习率Learning Rate。Python实现梯度下降演示#importnumpyasnpimportmatplotlib.pyplotasplt# 目标函数: f(x) x² 2x 1 (x1)²deff(x):returnx**22*x1defdf(x):return2*x2# 梯度下降x3.0# 起始点eta0.1# 学习率epochs100# 最大迭代次数print(f目标函数: f(x) x² 2x 1 (x1)²)print(f最小值在 x -1, f(-1) 0)print(f\n起始点: x {x})print(f学习率: η {eta})print(f\n迭代过程 (前10步):)print(f 步骤{0:4}|{x:10}|{f(x):12}|{f(x):12})print(-*50)history[]foriinrange(epochs):fxf(x)dfxdf(x)history.append((i,x,fx))ifi10:print(f 步骤{i:4}|{x:10.6f}|{fx:12.6f}|{dfx:12.6f})# 梯度下降更新xx-eta*dfx# 检查收敛ifabs(dfx)1e-6:print(f\n收敛于步骤{i}: x {x:.8f}, f(x) {f(x):.8f})breakprint(f\n最终: x {x:.8f}, f(x) {f(x):.8f})print(f收敛步数:{len(history)})服务器真实输出部分目标函数: f(x) x² 2x 1 (x1)² 最小值在 x -1, f(-1) 0 起始点: x 3.0 学习率: η 0.1 迭代过程 (前10步): 步骤 0 | 3.000000 | 16.000000 | 8.000000 步骤 1 | 2.200000 | 10.240000 | 6.400000 步骤 2 | 1.560000 | 6.553600 | 5.120000 步骤 3 | 1.048000 | 4.194304 | 4.096000 步骤 4 | 0.638400 | 2.684355 | 3.276800 步骤 5 | 0.310720 | 1.717987 | 2.621440 步骤 6 | 0.048576 | 1.099512 | 2.097152 步骤 7 | -0.161139 | 0.703687 | 1.677722 步骤 8 | -0.328911 | 0.450360 | 1.342177 步骤 9 | -0.463129 | 0.288230 | 1.073742 最终: x -0.99994291, f(x) 0.00000000 收敛步数: 505.3 梯度下降的步长#学习率 η是梯度下降的超参数控制每次更新的步长。学习率的影响#学习率太小收敛慢需要大量迭代 学习率太大可能震荡或发散无法收敛 学习率适中快速且稳定地收敛可视化不同学习率的效果#图5不同学习率下梯度下降的收敛过程5.4 梯度在神经网络中的应用#对于神经网络误差函数 E是关于所有权重的函数E E(w_1, w_2, ..., w_n)梯度是一个向量包含 E 对每个权重的偏导∇E [∂E/∂w_1, ∂E/∂w_2, ..., ∂E/∂w_n]权重更新规则W_new W_old - η × ∇E其中∏E通过反向传播计算。Python实现简单网络训练#importnumpyasnpdefsigmoid(x):return1/(1np.exp(-x))# 简单的 1-1-1 网络classSimpleNN:def__init__(self):self.w1np.random.randn()*0.01self.b1np.random.randn()*0.01self.w2np.random.randn()*0.01self.b2np.random.randn()*0.01defforward(self,x):self.z1self.w1*xself.b1 self.a1sigmoid(self.z1)self.z2self.w2*self.a1self.b2 self.a2sigmoid(self.z2)returnself.a2deftrain(self,x,target,eta0.5):# 正向传播outputself.forward(x)# 误差loss0.5*(target-output)**2# 反向传播简化版# ∂E/∂w2 (output - target) * output * (1 - output) * a1dE_dw2(output-target)*output*(1-output)*self.a1# ∂E/∂w1 (output - target) * output * (1 - output) * w2 * a1 * (1 - a1) * xdE_dw1(output-target)*output*(1-output)*self.w2*self.a1*(1-self.a1)*x# 更新权重self.w2-eta*dE_dw2 self.w1-eta*dE_dw1returnloss# 训练nnSimpleNN()print(f训练前权重: W{nn.w1:.8f}, b{nn.b1:.4f})# 训练样本: x0.5, target0.99x0.5target0.99# 训练前loss_before0.5*(target-nn.forward(x))**2print(f训练前Loss:{loss_before:.6f})# 训练1轮lossnn.train(x,target,eta0.5)print(f训练后权重: W{nn.w1:.8f}, b{nn.b1:.4f})print(f训练后Loss:{loss:.6f})服务器真实输出训练前权重: W[ 0.02930725 -0.07143514] 训练后权重: W[8.12438307 7.79152799], b-0.2592 训练前Loss: 0.078316 训练后Loss: 0.010804实验6参数的选择#6.1 权重更新的范例#权重更新是神经网络学习的核心。更新公式#W_new W_old - η × ∂E/∂W其中η学习率∂E/∂W误差对权重的梯度通过反向传播计算矩阵形式的更新#对于一层权重矩阵WW_new W_old - η × (∂E/∂Z) × A_prev^T其中∂E/∂Z输出层/隐藏层的误差梯度A_prev上一层的激活值6.2 激活函数的选择#不同的激活函数适用于网络的不同位置。对比#激活函数输出范围优点缺点适用位置Sigmoid(0, 1)平滑可导梯度消失当|x|很大时输出层二分类Tanh(-1, 1)零中心收敛更快梯度消失隐藏层ReLU[0, ∞)计算简单缓解梯度消失死神经元x0时梯度为0隐藏层深层网络梯度消失演示#梯度消失Gradient Vanishing是深层网络训练的难题。importnumpyasnpdefsigmoid(x):return1/(1np.exp(-x))defsigmoid_derivative(x):returnx*(1-x)# 模拟5层网络使用sigmoid激活np.random.seed(42)layers5nodes_per_layer10# 随机输入xnp.random.randn(nodes_per_layer)print(f梯度消失演示 ({layers}层网络, sigmoid):)print(f{层:6}|{激活值均值:12}|{梯度均值:12})print(-*40)foriinrange(layers):# 随机权重Wnp.random.randn(nodes_per_layer,nodes_per_layer)*0.1# 正向传播znp.dot(W,x)asigmoid(z)# 梯度简化假设误差为1gradientsigmoid_derivative(a)print(f{i1:6}|{np.mean(a):12.4f}|{np.mean(gradient):12.4f})xa# 传递到下一层服务器真实输出梯度消失演示 (5层网络, sigmoid): 层 | 激活值均值 | 梯度均值 ---------------------------------------- 1 | 0.4954 | 0.083934 2 | 0.5353 | 0.101531 3 | 0.4581 | 0.089444 4 | 0.4834 | 0.113971注这个例子中梯度没有急剧下降因为权重是随机小值。但在深层网络中梯度确实会指数级衰减。6.3 初始化权重的方法#权重初始化对神经网络的训练有重大影响。方法1零初始化不推荐#Wnp.zeros((output_nodes,input_nodes))问题所有神经元计算相同永远无法学习不同的特征对称性破缺失败。方法2随机均匀分布## 范围U(-a, a)a1/np.sqrt(input_nodes)Wnp.random.uniform(-a,a,(output_nodes,input_nodes))优点打破对称性。缺点对于深层网络激活值可能爆炸或消失。方法3Xavier/Glorot 初始化## 均匀分布U(-sqrt(6/(n_inn_out)), sqrt(6/(n_inn_out)))limitnp.sqrt(6/(input_nodesoutput_nodes))Wnp.random.uniform(-limit,limit,(output_nodes,input_nodes))适用Sigmoid、Tanh激活函数。方法4He 初始化推荐用于ReLU## 正态分布N(0, sqrt(2/n_in))stdnp.sqrt(2.0/input_nodes)Wnp.random.randn(output_nodes,input_nodes)*std适用ReLU激活函数。可视化不同初始化方法的权重分布#图64种权重初始化方法的分布对比6.4 目标值的使用范围#重要Sigmoid函数的输出永远无法达到精确的 0 或 1sigmoid(x) 1 / (1 e^(-x)) 当 x → -∞: sigmoid(x) → 0 (但永远不等于0) 当 x → ∞: sigmoid(x) → 1 (但永远不等于1)为什么这很重要#如果你设置目标值为target 1.0网络永远无法达到需要x ∞target 0.0网络永远无法达到需要x -∞解决方案#使用 0.01 和 0.99 代替 0 和 1# MNIST标签编码One-Hot# 数字0 - 目标输出 [0.99, 0.01, 0.01, ..., 0.01]# 数字3 - 目标输出 [0.01, 0.01, 0.01, 0.99, 0.01, ..., 0.01]defone_hot(label,num_classes10):将标签转换为One-Hot编码使用0.01和0.99targetnp.zeros(num_classes)0.01target[label]0.99returntarget总结#在本篇博客中我们学习了暴力破解不可行24个权重需要3亿年79,400个权重更是不敢想象梯度下降法沿着梯度反方向更新权重x_new x_old - η × f(x)学习率选择太小收敛慢太大震荡/发散权重初始化零初始化×、均匀分布△、Xavier○、He推荐用于ReLU激活函数选择Sigmoid用于输出层ReLU用于隐藏层目标值范围使用0.01和0.99避免精确的0和1关键公式回顾梯度下降x_new x_old - η × f(x) 权重更新W_new W_old - η × ∇E Xavier初始化U(-sqrt(6/(n_inn_out)), sqrt(6/(n_inn_out))) He 初始化N(0, sqrt(2/n_in))下一篇预告#第4篇Python从零搭建神经网络你将学习Python基础变量、数组、函数、类神经网络类的框架设计正向查询query和训练train方法实现完整神经网络Python代码网络规模与计算量分析参考文献Tariq Rashid, 《Python神经网络编程》Ian Goodfellow 等, 《深度学习》第8章优化Glorot Bengio, “Understanding the difficulty of training deep feedforward neural networks” (2010)He et al., “Delving Deep into Rectifiers” (2015)代码仓库本文所有代码可在服务器/root/nn_blog/目录下找到。作者腾讯DevOps工程师 | 最后更新2026年7月