从0开始掌握FCNN-全连接神经网络第一章神经网络是什么1.1 用调音量理解神经网络想象你有一个老式收音机上面有三个旋钮┌─────────────────────────┐ │ 收音机 │ │ │ │ [高音旋钮] ───────○ │ │ [中音旋钮] ─────○ │ │ [低音旋钮] ───○ │ │ │ │ [音量旋钮] │ │ ○────── │ │ │ └─────────────────────────┘神经网络 很多个旋钮的组合收音机神经网络旋钮权重Weight拧旋钮调整权重声音好听预测准确听广播调台训练过程核心思想神经网络就是找一堆旋钮拧到合适的位置让输出结果最接近正确答案。第二章从一个最简单的例子开始2.1 公里和里的换算你有一张表格实例公里里012124236规律很明显1公里 2里用公式表示里 2 × 公里但假设我们不知道这个规律让计算机自己猜出来。2.2 计算机的瞎猜过程计算机我不知道规律先随便猜一个吧 我猜里 1.5 × 公里 ← 初始猜测C1.5验证第一个数据输入1公里计算机预测1.5 × 1 1.5里实际答案2里错了2.3 计算误差误差 真实值 - 预测值 2 - 1.5 0.5 ← 差0.5里可视化实际值: 2里 ───────────────┐ │ 预测值: 1.5里 ───────┐ │ │ │ └────┘ ← 差距 0.52.4 修正猜测直觉预测值太小了1.5 2所以应该把系数调大一点。新系数 旧系数 修正量 1.5 0.1 1.6再验证1公里 × 1.6 1.6里误差 2 - 1.6 0.4← 误差变小了原来误差: 0.5 现在误差: 0.4 ✓ 改对了方向2.5 核心规律大误差 → 大修正小误差 → 小修正就像开车偏离车道很远 → 方向盘打大点稍微偏一点 → 方向盘微调第三章加入学习率——别调过头3.1 一个实际问题假设我们有两个数据点宽度长度类型3.01.0B1.03.0A画出来长度 ↑ 3 │ ● A(1,3) 2 │ 1 │ ● B(3,1) └────────→ 宽度 1 2 3我们想找一条线分开A和B长度 A × 宽度 A是斜率待求3.2 第一次训练初始猜测A 0.25预测线: 长度 0.25 × 宽度验证第一个点 B(3, 1.0)预测0.25 × 3 0.75实际1.0误差 1.0 - 0.75 0.25但我们不希望线正好过这个点而是希望稍微高一点给第二个点留空间。设期望值为 1.1新误差 期望值 - 预测值 1.1 - 0.75 0.35修正系数斜率变化量 误差 ÷ 宽度 0.35 ÷ 3 ≈ 0.1167 新斜率 0.25 0.1167 0.3667验证0.3667 × 3 1.1✓ 符合期望3.3 第二次训练用第二个点当前模型长度 0.3667 × 宽度验证 A(1, 3.0)预测0.3667 × 1 0.3667实际3.0误差巨大设期望值为 2.9误差 2.9 - 0.3667 2.5333 斜率变化量 2.5333 ÷ 1 2.5333 新斜率 0.3667 2.5333 2.9新模型长度 2.9 × 宽度问题出现了第一次训练后斜率 0.3667很小 第二次训练后斜率 2.9很大 画出来 长度 ↑ 3 │╲ ← 2.9x太陡了只照顾了A点 2 │ ╲ 1 │ ╲ ● B(3,1) ← B点被忽略了 └────────→ 宽度 1 2 33.4 问题根源每次只记住最后一个数据忘了前面的就像狗熊掰棒子掰一个丢一个。3.5 解决方案学习率Learning Rate核心思想别一次调太多慢慢调兼顾所有数据。原来新斜率 旧斜率 变化量 现在新斜率 旧斜率 学习率 × 变化量 ↑ 比如 0.5只调一半重新训练设学习率 L 0.5第一次训练误差 0.35 变化量 0.35 ÷ 3 0.1167 实际调整 0.5 × 0.1167 0.0583 ← 只调一半 新斜率 0.25 0.0583 0.3083第二次训练预测0.3083 × 1 0.3083 误差 2.9 - 0.3083 2.5917 变化量 2.5917 ÷ 1 2.5917 实际调整 0.5 × 2.5917 1.2958 ← 还是只调一半 新斜率 0.3083 1.2958 1.6042结果对比方法第一次后第二次后无学习率0.36672.9极端学习率0.50.30831.6042适中学习率0.5 的线 长度 ↑ 3 │ ╱ ● A(1,3) 2 │ ╱ 1 │╱ ● B(3,1) └────────→ 宽度 1 2 3学习率的作用像刹车防止调过头保留之前学到的知识。第四章从直线到神经网络4.1 直线是1个神经元输入 x ──→ [乘以A] ──→ 输出 y ↑ 权重A这就是最简单的神经网络1个输入1个输出1个权重。4.2 多个输入 多个神经元实际问题房价预测面积卧室数楼层房龄价格1003210500万150415800万多个因素共同决定价格面积 ──┐ 卧室数 ─┼─→ [神经网络] ──→ 价格 楼层 ──┤ 房龄 ──┘4.3 神经网络的乐高积木┌─────────────────────────────────────┐ │ 神经网络结构 │ │ │ │ 输入层 隐藏层 输出层 │ │ │ │ ⚪───→ ⚪───→ ⚪ │ │ ↘ ↗ ↓ ↖ ↗ │ │ ⚪───→ ⚪───→ ⚪ │ │ ↗ ↘ ↓ ↗ ↘ │ │ ⚪───→ ⚪───→ ⚪ │ │ │ │ (3个输入) (3个神经元) (2个输出) │ │ │ └─────────────────────────────────────┘层级作用比喻输入层接收原始数据眼睛看到的东西隐藏层提取特征、做计算大脑思考过程输出层给出最终结果嘴巴说出答案第五章神经网络的前向传播5.1 一个具体例子网络结构输入层2个节点输入1.0 和 0.5隐藏层2个节点输出层2个节点初始权重输入到隐藏层 W₁₁ 0.9 W₁₂ 0.2 W₂₁ 0.3 W₂₂ 0.8 隐藏层到输出层 W₁₁ 0.3 W₁₂ 0.7 W₂₁ 0.5 W₂₂ 0.15.2 第一步输入层 → 隐藏层隐藏层节点1的计算输入 节点1输出 1.0 节点2输出 0.5 加权求和 X (1.0 × 0.9) (0.5 × 0.3) 0.9 0.15 1.05 激活Sigmoid Y 1 ÷ (1 e^(-1.05)) 1 ÷ (1 0.3499) 1 ÷ 1.3499 ≈ 0.7408Sigmoid函数可视化Sigmoid函数 1.0 ┤ ╭────── │ ╭─╯ 0.5 ┤─────╭─╯ │ ╭─╯ 0.0 ┼──╯──────────→ -6 -3 0 3 6 输入X 特点 - 输入很大 → 输出接近1 - 输入很小 → 输出接近0 - 输入为0 → 输出为0.5隐藏层节点2的计算X (1.0 × 0.2) (0.5 × 0.8) 0.2 0.4 0.6 Y 1 ÷ (1 e^(-0.6)) 1 ÷ (1 0.5488) ≈ 0.6457隐藏层结果隐藏层节点1输出0.7408 隐藏层节点2输出0.64575.3 第二步隐藏层 → 输出层输出层节点1X (0.7408 × 0.3) (0.6457 × 0.5) 0.2222 0.3229 0.5451 Y Sigmoid(0.5451) ≈ 0.6331输出层节点2X (0.7408 × 0.7) (0.6457 × 0.1) 0.5186 0.0646 0.5832 Y Sigmoid(0.5832) ≈ 0.6418最终结果输出10.6331 输出20.6418第六章用矩阵让计算更高效6.1 为什么要用矩阵原始方法一个节点一个节点算太慢矩阵方法把所有计算打包成批量快递一次送完6.2 矩阵乘法是什么简单理解矩阵A (2×3) 矩阵B (3×2) 结果 (2×2) ┌───┬───┬───┐ ┌───┬───┐ ┌────┬────┐ │ 1 │ 2 │ 3 │ │ 7 │ 8 │ │ 50 │ 62 │ ├───┼───┼───┤ × ├───┼───┤ ├────┼────┤ │ 4 │ 5 │ 6 │ │ 9 │10 │ │122 │152 │ └───┴───┴───┘ ├───┼───┤ └────┴────┘ │11 │12 │ └───┴───┘ 计算规则 结果[0,0] 1×7 2×9 3×11 7 18 33 58... 等等让我重算 1×7 2×9 3×11 7 18 33 58 不对图中例子是50 1×7 2×9 3×11 7 18 33 58 让我检查原图... 原图可能有不同数值。 实际上标准计算 [1,2,3]·[7,9,11] 1×7 2×9 3×11 71833 58 [1,2,3]·[8,10,12] 1×8 2×10 3×12 82036 64 第二行 [4,5,6]·[7,9,11] 284566 139 [4,5,6]·[8,10,12] 325072 154 所以正确结果应该是 ┌────┬────┐ │ 58 │ 64 │ ├────┼────┤ │139 │154 │ └────┴────┘原文档中的数值可能有误这里给出标准计算方法。6.3 神经网络的前向传播 矩阵乘法输入矩阵 I 权重矩阵 W_input_hidden 隐藏层输入 X ┌─────┐ ┌─────┬─────┐ │ 1.0 │ │ 0.9 │ 0.2 │ ┌─────────┐ ├─────┤ × ├─────┼─────┤ │ 1.05 │ ← 隐藏层节点1的输入 │ 0.5 │ │ 0.3 │ 0.8 │ │ 0.6 │ ← 隐藏层节点2的输入 └─────┘ └─────┴─────┘ └─────────┘ [2×1] [2×2] [2×1] 然后对X应用Sigmoid得到隐藏层输出O_hidden公式X_hidden W_input_hidden × I O_hidden Sigmoid(X_hidden) X_output W_hidden_output × O_hidden O_output Sigmoid(X_output)6.4 三层网络的完整矩阵流程┌─────────┐ ┌─────────────────┐ ┌─────────┐ ┌─────────────────┐ ┌─────────┐ │ 输入 │ │ W_input_hidden │ │ 隐藏层 │ │ W_hidden_output │ │ 输出 │ │ │ ──→ │ [3×3]矩阵 │ ──→ │ │ ──→ │ [3×2]矩阵 │ ──→ │ │ │ I │ │ │ │ O_hidden│ │ │ │ O_output│ │ [3×1] │ │ │ │ [3×1] │ │ │ │ [2×1] │ └─────────┘ └─────────────────┘ └─────────┘ └─────────────────┘ └─────────┘ 步骤1: X_hidden W_input_hidden × I → [3×3] × [3×1] [3×1] 步骤2: O_hidden Sigmoid(X_hidden) → [3×1] 步骤3: X_output W_hidden_output × O_hidden → [2×3] × [3×1] [2×1] 步骤4: O_output Sigmoid(X_output) → [2×1]第七章误差反向传播——神经网络怎么学习7.1 核心问题神经网络有很多权重怎么知道每个权重该调多少比喻乐队演奏整体不好听怎么知道是小提琴手的问题还是鼓手的问题7.2 错误的分配方式方式1所有误差给所有权重❌就像不管谁错了全队一起罚站——不公平也学不到东西方式2误差平均分配❌就像不管贡献大小奖金平均分——打击积极性方式3按权重比例分配✓谁的影响大谁承担的责任就大7.3 正确的误差分配输出层误差e₁, e₂ 分配到隐藏层 隐藏节点1的误差 W₁₁×e₁ W₁₂×e₂ 隐藏节点2的误差 W₂₁×e₁ W₂₂×e₂ 其中W是隐藏层到输出层的权重可视化输出误差 e₁, e₂ ↓ ┌─────────┴─────────┐ ↓ ↓ W₁₁×e₁ W₂₁×e₁ ↓ ↓ 隐藏节点1 ←─────── 隐藏节点2 ↑ ↑ W₁₂×e₂ W₂₂×e₂ ↑ ↑ └─────────┬─────────┘ ↑ 输出误差反向流7.4 矩阵形式的反向传播误差反向传播公式 e_hidden W^T_hidden_output × e_output 其中 W^T 表示矩阵的转置行列互换转置可视化原矩阵 W 转置 W^T ┌───┬───┐ ┌───┬───┬───┐ │ a │ b │ → │ a │ c │ e │ ├───┼───┤ ├───┼───┼───┤ │ c │ d │ │ b │ d │ f │ ├───┼───┤ └───┴───┴───┘ │ e │ f │ └───┴───┘ [3×2] 变成 [2×3]第八章梯度下降——找到最优权重8.1 什么是梯度梯度 坡度 变化率想象你在山上想知道往哪个方向走下山最快️ 山顶 ╱ ╲ ╱ ╲ ← 你在这里 ╱ ↑ ╲ ╱ 最陡方向 ╲ ╱________________╲ 山脚梯度就是告诉你最陡的方向是哪里。8.2 梯度下降法目标找到函数的最小值误差最小 步骤 1. 随便选一个起点 2. 计算当前点的梯度坡度 3. 往梯度的反方向走一步下坡 4. 重复直到走不动为止可视化过程误差Y ↑ │ ╭─╮ │ ╱ ╲ ← 误差函数碗状 │ ╱ ╲ │ ╱ ★ ╲ ← 最小值我们要找的 │╱ ╲ └────────────→ 权重X 步骤1: 起点在右边坡度为正向上 → 往左走减小X 步骤2: 走到左边坡度为负向下 → 往右走增加X 步骤3: 反复震荡最终到达★8.3 为什么用平方误差有三种误差计算方式方式公式问题直接差(目标-实际)正负抵消5和-5的误差变成0绝对值|目标-实际|在最小值处不光滑像V字山谷平方(目标-实际)²✅ 光滑、可导、好优化平方误差的优势1. 永远为正不会抵消 2. 曲线光滑没有尖角 3. 越接近最小值坡度越小自然减速不会冲过头第九章权重更新公式推导9.1 目标找到当权重变化时误差怎么变化误差 E (目标值 t - 实际输出 o)² 我们要找∂E/∂W ? 误差对权重的导数9.2 链式法则——剥洋葱E (t - o)² o Sigmoid(x) x W × input 就像剥洋葱一层一层往里剥 ∂E/∂W (∂E/∂o) × (∂o/∂x) × (∂x/∂W)逐层计算第一层∂E/∂o误差对输出的导数E (t - o)² ∂E/∂o -2(t - o) -2 × 误差第二层∂o/∂x输出对输入的导数o Sigmoid(x) 1/(1e^(-x)) ∂o/∂x o × (1 - o) ← Sigmoid的神奇性质第三层∂x/∂W输入对权重的导数x W × input ... ∂x/∂W input ← 就是输入值9.3 最终公式∂E/∂W -2 × (t - o) × o × (1 - o) × input 简化去掉常数2不影响方向 ∂E/∂W -(t - o) × o × (1 - o) × input ↑ 误差 × Sigmoid导数 × 输入9.4 权重更新新权重 旧权重 - 学习率 × ∂E/∂W 即 ΔW -α × ∂E/∂W α × (t - o) × o × (1 - o) × input符号说明αalpha学习率控制步长大小(t - o)误差越大调得越多o(1-o)Sigmoid的导数在0.5附近最大两端趋近0input输入越大该权重影响越大调得越多第十章完整训练流程10.1 一个具体计算例子网络结构输入层2个节点隐藏层2个节点输出层1个节点当前状态输入1.0, 0.5隐藏层到输出层权重W₁ 2.0, W₂ 3.0目标输出1.0实际输出0.8假设10.2 计算输出层权重更新步骤1计算误差e t - o 1.0 - 0.8 0.2步骤2计算Sigmoid导数假设隐藏层输出o₁ 0.4, o₂ 0.5 输出层输入 X 2.0×0.4 3.0×0.5 0.8 1.5 2.3 输出 O Sigmoid(2.3) ≈ 0.909 Sigmoid导数 O × (1 - O) 0.909 × 0.091 ≈ 0.083步骤3计算梯度∂E/∂W₁ -e × O × (1-O) × o₁ -0.2 × 0.083 × 0.4 ≈ -0.00664 ∂E/∂W₂ -e × O × (1-O) × o₂ -0.2 × 0.083 × 0.5 ≈ -0.0083步骤4更新权重设学习率α0.1W₁_new W₁ - α × ∂E/∂W₁ 2.0 - 0.1 × (-0.00664) 2.0 0.000664 ≈ 2.00664 W₂_new 3.0 - 0.1 × (-0.0083) 3.0 0.00083 ≈ 3.0008310.3 计算输入层到隐藏层的权重更新步骤1反向传播误差到隐藏层e_hidden₁ W₁ × e_output 2.0 × 0.2 0.4 e_hidden₂ W₂ × e_output 3.0 × 0.2 0.6步骤2计算隐藏层Sigmoid导数假设隐藏层输入 X₁ 1.0×0.9 0.5×0.3 1.05 O₁ Sigmoid(1.05) ≈ 0.741 导数₁ 0.741 × (1-0.741) ≈ 0.192 X₂ 1.0×0.2 0.5×0.8 0.6 O₂ Sigmoid(0.6) ≈ 0.646 导数₂ 0.646 × (1-0.646) ≈ 0.229步骤3计算梯度并更新对于W₁₁输入1到隐藏1 ∂E/∂W₁₁ -e_hidden₁ × 导数₁ × 输入₁ -0.4 × 0.192 × 1.0 ≈ -0.0768 W₁₁_new 0.9 - 0.1 × (-0.0768) 0.9 0.00768 ≈ 0.9077第十一章PyTorch代码实现11.1 完整代码importtorchimporttorch.nnasnnimporttorch.nn.functionalasF# 定义神经网络 classSimpleNeuralNetwork(nn.Module): 简单的全连接神经网络 结构输入(2) → 隐藏(3) → 输出(1) def__init__(self):super(SimpleNeuralNetwork,self).__init__()# 第一层输入2个 → 隐藏3个self.hiddennn.Linear(2,3)# 第二层隐藏3个 → 输出1个self.outputnn.Linear(3,1)defforward(self,x):# 前向传播xF.sigmoid(self.hidden(x))# 隐藏层 Sigmoid激活xF.sigmoid(self.output(x))# 输出层 Sigmoid激活returnx# 准备数据 # 训练数据公里→里 的转换# 输入[公里数, 1.0]第二个值是偏置项# 输出[里数]train_data[(torch.tensor([1.0,1.0]),torch.tensor([2.0])),# 1公里 2里(torch.tensor([2.0,1.0]),torch.tensor([4.0])),# 2公里 4里(torch.tensor([3.0,1.0]),torch.tensor([6.0])),# 3公里 6里(torch.tensor([5.0,1.0]),torch.tensor([10.0])),# 5公里 10里]# 创建模型 modelSimpleNeuralNetwork()# 优化器使用随机梯度下降SGDoptimizertorch.optim.SGD(model.parameters(),lr0.1)# 损失函数均方误差criterionnn.MSELoss()# 训练 print(开始训练...)forepochinrange(5000):# 训练5000轮total_loss0forinputs,targetintrain_data:# 1. 清零梯度optimizer.zero_grad()# 2. 前向传播predictionmodel(inputs)# 3. 计算损失losscriterion(prediction,target)# 4. 反向传播自动计算梯度loss.backward()# 5. 更新权重optimizer.step()total_lossloss.item()# 每1000轮打印一次if(epoch1)%10000:print(f第{epoch1}轮平均损失:{total_loss/len(train_data):.6f})# 测试 print(\n训练完成测试预测)test_inputs[1.0,2.0,3.0,10.0]forkmintest_inputs:withtorch.no_grad():# 测试时不计算梯度input_tensortorch.tensor([km,1.0])predictionmodel(input_tensor)print(f{km}公里 {prediction.item():.2f}里)# 查看学到的权重print(\n学到的权重)forname,paraminmodel.named_parameters():print(f{name}:{param.data})11.2 代码解释代码作用对应前面章节nn.Linear(2, 3)定义全连接层权重矩阵F.sigmoid()激活函数Sigmoid章节optimizer.zero_grad()清零旧梯度防止累积loss.backward()反向传播第七章核心optimizer.step()更新权重梯度下降nn.MSELoss()均方误差平方误差章节第十二章常见问题和技巧12.1 学习率怎么选学习率效果比喻太大如1.0震荡不收敛开车速度太快在弯道来回冲出适中如0.1快速收敛正常驾驶太小如0.001收敛极慢蜗牛爬永远到不了动态衰减先快后慢先开车快到目的地时减速学习率衰减策略# 每轮乘以0.99逐渐减小schedulertorch.optim.lr_scheduler.ExponentialLR(optimizer,gamma0.99)forepochinrange(100):# ... 训练 ...scheduler.step()# 更新学习率12.2 过拟合怎么办现象训练时误差很小测试时误差很大训练误差 ↘ 测试误差 ↘↗ │ │ 0.1┤ ★ │ ★ ← 过拟合点 │ ╱ ╲ │ ╱ ╲ 0.5┤ ╱ ╲ │ ╱ ╲___ │ ╱ ╲___│ ╱ 1.0┤╱ │╱ └──────────→ 训练轮数解决方案方法原理代码早停测试误差上升时停止监控验证集正则化惩罚大权重weight_decay0.01Dropout随机关闭神经元nn.Dropout(0.5)更多数据见多识广数据增强12.3 权重初始化很重要坏的初始化所有权重一样 → 所有神经元学的一样 → 网络退化好的初始化# Xavier初始化适合Sigmoid/Tanhnn.init.xavier_uniform_(layer.weight)# Kaiming初始化适合ReLUnn.init.kaiming_uniform_(layer.weight,nonlinearityrelu)总结概念一句话解释神经网络很多旋钮的组合拧到合适位置前向传播数据从输入走到输出得到预测损失函数预测和答案的差距反向传播从输出倒推知道每个旋钮该拧多少梯度下降沿着下坡方向找到最低点学习率每次拧多大的幅度激活函数给神经网络加入非线性让它能拟合复杂曲线课后练习手动计算用本章的公式手动计算一轮权重更新不用代码。修改网络把隐藏层从3个节点改成5个观察训练速度和精度变化。换激活函数把Sigmoid换成ReLU观察梯度消失是否改善。可视化画出训练过程中损失值的变化曲线。挑战实现一个能识别手写数字0-9的神经网络使用MNIST数据集。恭喜你你已经掌握了全连接神经网络的核心原理。下一步可以学习卷积神经网络CNN处理图像任务