当前位置: 首页 > news >正文

CSDN博客-第1天-单神经元反向传播

【深度学习入门 Day 1】手算一个神经元:从矩阵乘法到反向传播

本文记录我学习深度学习第 1 天的内容:从矩阵乘法、线性层、sigmoid 激活函数开始,手算一个单神经元的前向传播、损失函数、链式法则、梯度下降和参数更新。

文章目录

  • 一、为什么从单个神经元开始?
  • 二、矩阵乘法:神经网络的基本计算单元
  • 三、从线性层到单个神经元
  • 四、前向传播:算出模型输出
  • 五、损失函数:模型错得有多严重?
  • 六、反向传播:用链式法则算梯度
  • 七、梯度下降:真正更新一次参数
  • 八、用 NumPy 写出训练循环
  • 九、补充:为什么不直接把 y 改成 0.5?
  • 十、今日总结

一、为什么从单个神经元开始?

深度学习看起来很复杂,但它的核心可以先压缩成一句话:

深度学习就是用梯度下降优化一个由矩阵运算和非线性函数组成的复合函数。

拆开来看,至少包含几件事:

  • 数据和参数通常表示成向量、矩阵或张量。
  • 前向传播就是一串矩阵乘法、加法和激活函数。
  • 损失函数衡量模型预测和真实答案之间的差距。
  • 反向传播用链式法则计算每个参数应该怎么改。
  • 梯度下降根据梯度更新参数,让损失逐步下降。

今天我们不急着上大模型,也不急着写复杂网络。先把一个最小单元:单个 sigmoid 神经元手算清楚。


二、矩阵乘法:神经网络的基本计算单元

先看一个形状问题:

A 的形状是 (3, 4) B 的形状是 (4, 2)

那么:

A @ B 的形状是 (3, 2)

原因是矩阵乘法要求:

左矩阵的列数 = 右矩阵的行数

也就是:

(3, 4) @ (4, 2) -> (3, 2)

口诀可以记成:

内维相同,外维成形

神经网络中的线性层,本质上就是矩阵乘法加偏置:

y = Wx + b

其中:

  • x是输入特征。
  • W是权重矩阵。
  • b是偏置项。
  • y是线性变换后的输出。

三、从线性层到单个神经元

一个最简单的神经元可以写成:

z = w1*x1 + w2*x2 + b a = sigmoid(z)

其中:

  • z是线性加权结果。
  • a是经过激活函数之后的输出。
  • sigmoid会把任意实数压缩到(0, 1)

sigmoid 函数定义为:

sigmoid(z) = 1 / (1 + exp(-z))

为什么需要激活函数?

如果只有线性层:

y = Wx + b

那么就算堆很多层,本质上仍然可以合并成一个大的线性变换。也就是说,没有激活函数,网络再深也只是线性模型。

所以:

线性层负责混合信息,激活函数负责引入非线性。


四、前向传播:算出模型输出

设定一个具体例子:

x1 = 2 x2 = 3 w1 = 0.5 w2 = -1 b = 1

先计算线性部分:

z = w1*x1 + w2*x2 + b = 0.5*2 + (-1)*3 + 1 = 1 - 3 + 1 = -1

再经过 sigmoid:

a = sigmoid(-1) = 1 / (1 + exp(1)) ≈ 0.269

如果这是一个二分类模型,并且阈值设置为0.5

a >= 0.5 -> 判断为 1 a < 0.5 -> 判断为 0

那么当前输出:

a = 0.269 < 0.5

所以模型会判断为:

0

五、损失函数:模型错得有多严重?

假设真实标签是:

y = 1

但模型输出:

a = 0.269

说明模型预测偏低。为了衡量错得多严重,先使用平方损失:

L = (a - y)^2

代入数值:

L = (0.269 - 1)^2 = (-0.731)^2 ≈ 0.534

这个损失不小。因为真实答案是1,而模型只输出了0.269,所以接下来训练的目标就是:让输出a变大。

由于:

a = sigmoid(z)

而 sigmoid 是单调递增函数,所以想让a变大,就要让z变大。


六、反向传播:用链式法则算梯度

现在开始计算参数应该怎么改。

已知:

L = (a - y)^2 a = sigmoid(z) z = w1*x1 + w2*x2 + b

对于w1,计算链条是:

w1 -> z -> a -> L

所以根据链式法则:

dL/dw1 = dL/da * da/dz * dz/dw1

1. 计算 dL/da

L = (a - y)^2

a求导:

dL/da = 2(a - y)

代入:

dL/da = 2(0.269 - 1) = -1.462

这个负号很有意义:它表示如果a增大,损失会下降。

2. 计算 da/dz

sigmoid 的导数是:

da/dz = a(1 - a)

代入:

da/dz = 0.269 * (1 - 0.269) = 0.269 * 0.731 ≈ 0.197

3. 计算 dz/dw1、dz/dw2、dz/db

因为:

z = w1*x1 + w2*x2 + b

所以:

dz/dw1 = x1 = 2 dz/dw2 = x2 = 3 dz/db = 1

4. 合成梯度

w1

dL/dw1 = -1.462 * 0.197 * 2 ≈ -0.576

w2

dL/dw2 = -1.462 * 0.197 * 3 ≈ -0.864

b

dL/db = -1.462 * 0.197 * 1 ≈ -0.288

七、梯度下降:真正更新一次参数

梯度下降的更新公式是:

参数 = 参数 - 学习率 * 梯度

设学习率:

lr = 0.1

旧参数是:

w1 = 0.5 w2 = -1 b = 1

梯度是:

dL/dw1 ≈ -0.576 dL/dw2 ≈ -0.864 dL/db ≈ -0.288

更新w1

w1_new = 0.5 - 0.1 * (-0.576) = 0.5576

更新w2

w2_new = -1 - 0.1 * (-0.864) = -0.9136

更新b

b_new = 1 - 0.1 * (-0.288) = 1.0288

注意:梯度为负数时,参数会变大。

这是因为:

参数 = 参数 - 学习率 * 负梯度

也就是:

参数 = 参数 + 一个正数

更新后模型有没有变好?

用新参数重新计算z

z_new = 0.5576*2 + (-0.9136)*3 + 1.0288 = 1.1152 - 2.7408 + 1.0288 = -0.5968

旧的:

z_old = -1

新的:

z_new = -0.5968

z变大了,对应的 sigmoid 输出也变大:

sigmoid(-1) ≈ 0.269 sigmoid(-0.5968) ≈ 0.355

虽然还没超过0.5,但已经朝真实标签1的方向靠近了。


八、用 NumPy 写出训练循环

把刚才的手算过程写成代码:

importnumpyasnp x=np.array([2.0,3.0])y=1.0w=np.array([0.5,-1.0])b=1.0lr=0.1defsigmoid(z):return1/(1+np.exp(-z))forstepinrange(20):# forwardz=np.dot(w,x)+b a=sigmoid(z)loss=(a-y)**2# backwarddL_da=2*(a-y)da_dz=a*(1-a)dL_dw=dL_da*da_dz*x dL_db=dL_da*da_dz# updatew=w-lr*dL_dw b=b-lr*dL_dbprint(f"step={step:02d}, "f"loss={loss:.6f}, "f"a={a:.6f}, "f"w={w}, "f"b={b:.6f}")

其中:

z=np.dot(w,x)+b

对应手算公式:

z = w1*x1 + w2*x2 + b

而:

dL_dw=dL_da*da_dz*x

会一次性得到:

[dL/dw1, dL/dw2]

这就是 NumPy 向量化的好处:不用分别写w1w2,而是直接对整个权重向量操作。


九、补充:为什么不直接把 y 改成 0.5?

在理解这段程序时,很容易产生一个想法:

既然 sigmoid 输出很难真正等于 1,那把y改成0.5会不会更合适?

答案是:不一定,要看任务目标是什么。

如果这是一个标准二分类任务,标签通常是:

y = 1 表示正类 y = 0 表示负类

这时y = 0.5不是更合适,而是变成了另一种含义:它表示一个模糊标签,或者说模型希望输出“介于正类和负类之间”的概率。

对于 sigmoid 来说:

sigmoid(z) = 0.5 <=> z = 0

而我们一开始:

z = -1 a = sigmoid(-1) ≈ 0.269

如果设置:

y = 1

训练会推动z持续变大,让a尽量接近1

如果设置:

y = 0.5

训练则会推动z-10靠近,让a接近0.5

所以两者不是谁更“准确”,而是任务目标不同:

y = 1:希望这个样本属于正类 y = 0:希望这个样本属于负类 y = 0.5:希望模型输出一个中间概率

这也引出一个很重要的概念:

标签不是随便取的,标签定义了模型到底在学什么。

当前代码只有一个样本:

x=np.array([2.0,3.0])y=1.0

因此它学到的不是通用规律,而是让这个样本的输出更接近1。如果要让模型真正学习分类边界,就需要多个样本:

x = [2, 3], y = 1 x = [1, 1], y = 0 x = [3, 4], y = 1 x = [0, 2], y = 0 ...

这样训练出来的wb才更像是在学习一条分类规则,而不是只记住一个点。


十、今日总结

今天最重要的不是记住某个具体数字,而是把训练流程串起来:

1. 前向传播: z = w1*x1 + w2*x2 + b a = sigmoid(z) 2. 计算损失: L = (a - y)^2 3. 反向传播: dL/dw = dL/da * da/dz * dz/dw 4. 参数更新: param = param - lr * gradient

可以把这一天的核心压缩成一句话:

神经网络训练,就是先前向算预测,再反向算每个参数对损失的影响,最后沿着让损失下降的方向更新参数。

下一步可以继续做两件事:

  • 用 NumPy 跑 20 轮训练,观察 loss 是否下降、输出 a 是否上升。
  • 把平方损失换成二分类更常用的交叉熵损失,继续推导梯度。

课后自测

  1. 为什么没有激活函数时,多层神经网络仍然只是线性模型?
  2. 为什么dL/dw1可以拆成dL/da * da/dz * dz/dw1
  3. 当梯度是负数时,为什么参数更新后会变大?
  4. 在本例中,为什么w2-1变成-0.9136也叫“增大”?
http://www.jsqmd.com/news/1093226/

相关文章:

  • 计算机二级基础知识-计算机体系结构
  • 中小微企业建站首选!PageAdmin CMS,零代码搞定官网运维
  • chunk重叠overlap设多少:切断上下文的坑
  • 支持多端生成的AI开发软件怎么选?功能对比指南
  • AI编程新范式:Skills技能库如何提升Claude、Cursor代码生成质量
  • AI Agent开发实战:从零构建一个能自主规划任务的智能体
  • Python学习笔记·第24天:Pandas数据清洗——缺失值、重复值与透视表实战
  • 使用visual studio和ai制作ppt
  • AI 学习助手:基于 HarmonyOS ArkTS 的智能学习伴侣开发实践
  • 第一批被龙虾气到的人出现了
  • Vue3 项目从开发到上线:环境变量、打包优化与 Nginx 部署全流程
  • 相处的艺术:尊重与边界
  • 企业知识图谱的拐点: 当本体工程遇上 LLM 与 MCP
  • Spring Boot 自定义 Starter 机制
  • GPT-5.6 Sol预览解读:max推理、ultra多Agent与分层安全栈
  • 剑指offer-79、最⻓不含重复字符的
  • Codex Linux 教程:从安装配置到卸载清理全流程指南
  • 基于Anthropic-Cybersecurity-Skills构建网络安全AI智能体实战指南
  • FontForge字体设计完全指南:从入门到精通掌握专业字体编辑
  • GPT-5.6系列模型发布遇阻:OpenAI面临多国监管审批,Claude Fable 5重返引发全球讨论
  • Vibe Coding 实战复盘:一个人 + AI,从零打造会聊天的个人主页
  • 关于多线程归并排序的性能瓶颈与优化方案的技术7
  • HFSS求解设置实战解析:从驱动求解到本征模求解的核心配置
  • 数据中心电力模块的发展趋势对数据中心建设有哪些影响?
  • 目前自动评价系统问题---------会卡在一些异常的地方
  • XCP协议:从总线标定到汽车ECU数据交互的核心
  • GoChatIAI -Go语言AI应用服务平台(1)
  • 2026论文双降终极榜单:10款降AI率网站,查重降重+降AIGC一次通关
  • IntelliJ IDEA 之工程模块管理
  • Java的java.lang.foreign访问