news 2026/7/9 2:36:37

python神经网络编程入门(五)----误差算出来了,权重到底怎么改?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
python神经网络编程入门(五)----误差算出来了,权重到底怎么改?

上一期,我们费了九牛二虎之力,终于搞懂了误差是怎么"甩锅"回去的。误差就像一张传单,从输出层一路传回了输入层,告诉每一个权重:"你错了多少!"

但光知道错了还不够啊!我们到底该怎么改?改多少?这就好比一个学生拿到了考卷,知道自己哪道题做错了(误差反向传播),但下一步呢?难道要瞎蒙一个正确答案吗?

当然不是!这一期,我们就来解决这个最核心、最激动人心的问题:如何利用这些误差,精准地调整每一个权重,让神经网络越学越聪明。

这背后的魔法,就叫——梯度下降


一、为什么不能"一步到位"?

让我们先冷静一下,问问自己:为啥我们不干脆点,直接用数学算出那个"完美"的权重组合呢?

想想看,我们的神经网络,哪怕只有3层,每层3个神经元,它要算的数学公式已经复杂到让人想哭。更别提那些有几百层、几百万个参数的现代网络了。

那不用数学算,我们用笨办法——穷举呢?

假设每个权重有1000种可能的值,对于一个有18个权重的网络,就有18000种组合。每测试一种要1秒钟,那更新一次就要5个小时!如果是一个有500个节点的网络,这个时间会飙升到1.6万年

所以,无论是精密的代数计算,还是愚蠢的暴力穷举,都走不通。我们需要一个更聪明、更实际的方法。

二、摸黑下山:梯度下降的智慧

想象一下这个场景:你被蒙上眼睛,扔在了一片连绵起伏的群山中。你的任务,是找到这片山区的最低点(也就是"全局最小值")。

你看不见路,手里只有一个能探测脚下几米地形的"手电筒"。你会怎么做?

正确的做法是:用脚探一探周围,找到最陡的斜坡,然后朝着下坡的方向迈出一小步。到了新地方,再重复这个动作——探路、下坡、迈步。就这样,一步一步,你终将抵达某个山谷的底部。

这个"摸黑下山"的过程,就是梯度下降的核心思想!

这里的"山",就是我们的误差函数;"你的位置",就是当前的权重组合;"最陡的下坡方向",就是梯度的反方向

三、为什么选"误差的平方"当导航地图?

在"摸黑下山"的比喻里,我们得先定义"山"在哪。这座"山"就是误差函数,它告诉我们当前的权重组合有多"糟糕"。

但我们该选哪个函数来当这座"山"呢?这里有三个常见的选项:

  1. 直接相减 (目标值 - 实际值):比如,两个节点的误差分别是+0.1-0.1,加起来等于0!这相当于告诉你"你没犯错",这显然是错误的导航。

  2. 取绝对值 (|目标值 - 实际值|):解决了正负抵消的问题。但它的图像像一个"V"字,在谷底有个尖角。如果你用梯度下降,会在那个尖角附近反复横跳,永远无法精准地停在最低点。

  3. 取平方 ((目标值 - 实际值)²):这是最常用的选择!因为它:

    • 图像平滑:像个光滑的碗,没有尖角,梯度下降可以稳稳地走到碗底。

    • 数学友好:对平方函数求导非常简单,这为后面的计算铺平了道路。

    • 自带"减速":越靠近谷底,坡度越平缓,你的步子会自动变小,不容易冲过头。

四、寻路神器:计算"梯度"

好,地图选好了(误差的平方),也知道了方法(梯度下降),那最关键的一步来了:我们怎么知道"最陡的下坡方向"在哪?

这个"最陡的下坡方向",在数学上就叫梯度。它是一个向量(可以理解为一个带方向的箭头),指明了误差函数上升最快的方向。而我们需要的,是它相反的方向——也就是负梯度方向

这个方向,就告诉了我们每一个权重具体应该调大还是调小,以及调多少最有效

这个计算过程,就是反向传播在做的事情。上一期我们费尽心思把误差传回去,就是为了计算这个"梯度"做准备。

五、解密核心公式:权重到底怎么改?

现在,我们要揭开神经网络最核心的秘密:权重更新的数学公式。别怕,我们一步步拆解它。

我们关注的是隐藏层输出层之间的一个权重,叫它w_{j,k}。这个公式告诉我们,w_{j,k}应该调整多少。

这个神奇的公式长这样:

Δw_{j,k} = -α × (目标值ₖ - 实际值ₖ) × S'(信号ₖ) × 输出ⱼ

看起来有点吓人?别怕,我们把它拆成四块,每一块都有非常直观的含义。

  1. (负的学习率):这是我们的"步长"控制器。

    • α(学习率):决定我们每次迈多大的步子。步子太大(α太大)容易冲过头;步子太小(α太小)则走得太慢。

    • 负号-:非常重要!因为我们要往下坡走,所以要在梯度的前面加个负号,反着来

  2. (目标值ₖ - 实际值ₖ):这是输出层的误差。误差越大,说明错得越离谱,需要调整的幅度自然就越大。

  3. S'(信号ₖ):这是激活函数的斜率

    • S代表S型函数(Sigmoid)。

    • 信号ₖ是进入输出节点的加权和。

    • 这个部分告诉你,在当前这个"信号"强度下,改变权重对输出的影响有多大。如果S型函数已经"饱和"了(比如输出接近0或1),那它的斜率就接近0,改变权重也没什么效果了。

  4. 输出ⱼ:这是前一个隐藏层节点的输出。这个权重连接的前一个节点贡献的信号越强,调整它对最终结果的影响就越大。

这个公式把误差、信号的敏感度和前一层的影响力完美地结合在一起,告诉我们每一个权重具体该怎么调。

六、从"一个"到"一片":矩阵的终极简化

我们刚刚搞定了一个权重的调整公式。但一个神经网络里有成千上万个权重,难道我们要用for循环一个一个算吗?

当然不!这时候,我们的老朋友——矩阵,又登场了。

我们可以把所有公式打包成一个优雅的矩阵运算:

权重更新矩阵 = -α × (误差矩阵) × (S'矩阵) × (上一层输出矩阵)ᵀ

七、最终:把一切都串起来

现在,我们把这一期和上一期的所有知识串起来,看看神经网络的一次完整训练循环是怎样的:

  1. 前向传播:输入数据,通过现有的权重,一路计算,得到预测结果。

  2. 计算误差:用"误差的平方"作为地图,计算预测结果和真实值的差距。

  3. 反向传播:从输出层开始,将误差"甩"回每一层,为计算梯度做准备。

  4. 计算梯度:用我们刚学的核心公式,计算出每一个权重的调整方向(梯度)。

  5. 更新权重:朝着梯度的反方向,迈出一小步(由学习率α控制),更新所有权重。

然后,拿着更新后的权重,再去处理下一个数据样本。如此循环往复,成千上万次。

这,就是神经网络学习的全部秘密!它没有在"思考",只是在不断重复"犯错-计算-调整"的过程,直到把误差这座"山"踩在脚下。

现在你已经掌握了神经网络最核心的训练算法——反向传播梯度下降。下一期,我们将把这些公式变成可以运行的代码,让你亲手训练出第一个能"学习"的神经网络!

代码片段1:

import numpy as np # ============================= 1. 设置固定数据(保证可复现) ============================= # 输入样本(来自前文) X = np.array([0.5, 0.3]) # 输入层 (2,) target = np.array([0.9, 0.1]) # 目标输出 (2,) # 初始权重(延续前文的数值) # 输入层 -> 隐藏层 权重矩阵 (行: 隐藏节点, 列: 输入节点) W_ih = np.array([ [0.1, 0.2], # 隐藏节点1 [0.3, 0.4] # 隐藏节点2 ]) # 隐藏层 -> 输出层 权重矩阵 (行: 输出节点, 列: 隐藏节点) W_ho = np.array([ [0.4, 0.3], # 输出节点1 [0.6, 0.8] # 输出节点2 ]) # 学习率 alpha = 0.1 # ============================= 2. 定义激活函数及其导数 ============================= def sigmoid(x): """S型激活函数""" return 1 / (1 + np.exp(-x)) def sigmoid_derivative(x): """S型函数的导数(假设x已经是经过激活后的输出,即 sigmoid(x))""" # 如果用输出值 o 表示,则导数为 o * (1 - o) return x * (1 - x) # ============================= 3. 前向传播 ============================= print("--- 前向传播 ---") # 隐藏层:加权和 + 激活 hidden_input = W_ih @ X # 进入隐藏层的信号 (2,) hidden_output = sigmoid(hidden_input) # 隐藏层输出 (2,) # 输出层:加权和 + 激活 final_input = W_ho @ hidden_output # 进入输出层的信号 (2,) final_output = sigmoid(final_input) # 网络最终输出 (2,) print(f"输入: {X}") print(f"隐藏层输出: {hidden_output}") print(f"网络输出: {final_output}") print(f"目标输出: {target}") # ============================= 4. 计算输出层误差 ============================= output_error = target - final_output # 输出层误差向量 (2,) print(f"\n输出层误差: {output_error}") # 总误差(平方和,可作监控) loss = np.sum(output_error ** 2) print(f"总误差 (平方和): {loss:.4f}") # ============================= 5. 反向传播:计算梯度并更新输出层权重 ============================= # 计算输出层梯度 # 公式: dE/dW_ho = - (target - output) * sigmoid'(input) * hidden_output # 其中 sigmoid'(input) = output * (1 - output) # 注意:因为我们要做梯度下降(减梯度),所以直接计算负梯度即可。 # 这里我们计算 delta_output = (output - target) * sigmoid'(input) , # 然后权重更新量 = - alpha * delta_output * hidden_output^T delta_output = (final_output - target) * sigmoid_derivative(final_output) # (2,) gradient_ho = np.outer(delta_output, hidden_output) # (2,2) 外积 # 更新权重 (梯度下降) W_ho_new = W_ho - alpha * gradient_ho print("\n--- 更新隐藏层->输出层权重 ---") print("更新前:\n", W_ho) print("梯度:\n", gradient_ho) print("更新后:\n", W_ho_new) # ============================= 6. 反向传播:计算隐藏层误差并更新输入层权重 ============================= # 隐藏层误差 = W_ho^T * delta_output (因为 delta_output 已经包含了输出层的梯度) # 注意:这里隐藏层没有目标值,误差由输出层反传回来。 hidden_error = W_ho.T @ delta_output # (2,) # 计算隐藏层梯度 # delta_hidden = hidden_error * sigmoid'(hidden_output) delta_hidden = hidden_error * sigmoid_derivative(hidden_output) # (2,) gradient_ih = np.outer(delta_hidden, X) # (2,2) 外积 # 更新权重 W_ih_new = W_ih - alpha * gradient_ih print("\n--- 更新输入层->隐藏层权重 ---") print("更新前:\n", W_ih) print("梯度:\n", gradient_ih) print("更新后:\n", W_ih_new) # ============================= 7. 验证更新后的效果(可选) ============================= # 用新权重再次前向传播,观察误差是否减小 hidden_input_new = W_ih_new @ X hidden_output_new = sigmoid(hidden_input_new) final_input_new = W_ho_new @ hidden_output_new final_output_new = sigmoid(final_input_new) new_error = target - final_output_new new_loss = np.sum(new_error ** 2) print("\n--- 更新后验证 ---") print(f"更新后网络输出: {final_output_new}") print(f"更新后误差: {new_error}") print(f"更新前总误差: {loss:.4f}") print(f"更新后总误差: {new_loss:.4f}") print(f"误差减小: {loss - new_loss:.4f}")

注意:由于初始权重是随机选取且网络规模很小,单次更新的效果可能不明显,甚至误差可能略微上升(因为步长α=0.1可能略大)。在实际训练中,经过多次迭代后误差会稳定下降。你可以将alpha调小(如0.01)并循环训练多次,即可看到更明显的效果。


代码结构说明:

步骤对应数学公式实现方式
前向传播O = sigmoid(W · X)矩阵乘法 +sigmoid()
输出层误差E = target - output向量减法
输出层梯度δ_out = (output - target) * sigmoid'(output)逐元素乘
权重梯度∇W_ho = δ_out · hidden^T外积np.outer
权重更新W_new = W - α · ∇W减法
隐藏层误差δ_hidden = (W_ho^T · δ_out) * sigmoid'(hidden)矩阵乘 + 逐元素乘
隐藏层权重梯度∇W_ih = δ_hidden · X^T外积

你可以在此基础上扩展成循环训练多轮,或者加入批量样本处理,但当前的代码已经完整展示了一次迭代的全流程,非常适合配合文章学习。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/9 2:35:37

模型训练全流程 —— 个人知识库笔记

一、训练全流程总览 模型的训练过程可以理解为: 确定模型架构 → 准备训练数据 → 模型训练(前向传播 → 误差计算 → 反向传播 → 收敛)→ 结束训练 二、确定模型架构 架构确定阶段需要做三类关键决策: Dense 还是 MoE 架构 选…

作者头像 李华
网站建设 2026/7/9 2:35:21

YOLO系列模型选型指南:从v10到v26的技术对比与迁移实践

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 1. 项目概述:YOLO系列模型选型困境与解决方案 在目标检测领域,YOLO系列模型始终保持着技术领先地位。作为从业…

作者头像 李华
网站建设 2026/7/9 2:33:49

常用Skills 记录

Skill解释说明链接StarsFirecrawl把网站抓取并转换成 LLM 友好的 Markdown/API 数据,适合做资料抓取、竞品分析、RAG 数据源。https://github.com/mendableai/firecrawl147,577awesome-mcp-serversMCP Server 大清单,适合作为 MCP 选型入口。https://git…

作者头像 李华
网站建设 2026/7/9 2:24:45

告别“缺少DLL“弹窗:3分钟搞定Windows程序运行环境配置

告别"缺少DLL"弹窗:3分钟搞定Windows程序运行环境配置 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾在打开某个游戏或软件时&am…

作者头像 李华
网站建设 2026/7/9 2:22:15

Claude Code隐写标记事件:HN千评热议背后的AI工具信任危机

一、开篇:一篇文章引发的千层浪2026 年 6 月 30 日,一名开发者在个人博客 thereallo.dev 上发表了一篇题为《Claude Code Is Steganographically Marking Requests》的文章。字数不多,技术分析却极为扎实——他反编译了 Claude Code 2.1.196 …

作者头像 李华
网站建设 2026/7/9 2:21:51

来自圳天帝的新人介绍

Hi 博客网的朋友们,我是一名目前以PLC自动化为职业的21岁男生,目前由于公司对单片机的需求转而向单片机学习,加上我本身也发现自己也对单片机嵌入式有一定兴趣,觉得这是一个不错的趣味的专业方向,所以我现在正在学习C语…

作者头像 李华