【动手学深度学习·第一篇】从 NumPy 到自动微分:深度学习的数学地基,每行代码都能跑
【动手学深度学习·第一篇】从 NumPy 到自动微分:深度学习的数学地基,每行代码都能跑
作者:技术博主 | 更新时间:2026-05-16 | 阅读时长:约 22 分钟
系列:动手学深度学习(共 8 篇)
环境:Python 3.12 + NumPy + PyTorch 2.x
标签:深度学习NumPy张量自动微分计算图梯度PyTorch入门

🔥 这篇写给谁:想系统学深度学习,但每次看到"梯度"、“计算图”、“自动微分"这些词就有点发懵。本篇从数学直觉开始,把张量操作、广播、自动微分、计算图的原理一步步用代码跑出来——不是"hello world"级别的演示,而是每个操作都讲清楚"它在做什么、为什么这样做”。
系列规划
| 篇次 | 主题 | 状态 |
|---|---|---|
| 第一篇(本篇) | 从 NumPy 到自动微分:深度学习的数学地基 | — |
| 第二篇 | 线性模型与优化:从零实现线性回归和 Softmax | 即将发布 |
| 第三篇 | 多层感知机:激活函数、反向传播、过拟合与正则化 | 即将发布 |
| 第四篇 | 卷积神经网络:LeNet 到 ResNet 的演进 | 即将发布 |
| 第五篇 | 循环神经网络:LSTM、GRU 与语言模型 | 即将发布 |
| 第六篇 | 注意力机制与 Transformer:从原理到实现 | 即将发布 |
| 第七篇 | 现代训练技巧:Adam、混合精度、学习率调度 | 即将发布 |
| 第八篇 | 完整实战:从零训练一个图像分类器 | 即将发布 |
目录
- 一、深度学习需要什么数学基础
- 二、张量:深度学习的基本数据单元
- 三、广播机制:高维运算的核心规则
- 四、微分基础:导数和梯度是什么
- 五、链式法则:反向传播的数学本质
- 六、计算图:PyTorch 如何记录操作
- 七、自动微分:
backward()在做什么 - 八、动手:用纯 NumPy 实现一次梯度下降
- 九、PyTorch 完整对照
- 十、小结与预告
一、深度学习需要什么数学基础
很多人被"深度学习需要大量数学"劝退,其实核心只需要三块:
① 线性代数:矩阵乘法、向量点积、转置
→ 神经网络的前向传播就是一系列矩阵乘法
② 微积分:导数、偏导数、链式法则
→ 反向传播就是链式法则的自动化
③ 概率统计:期望、方差、概率分布
→ 损失函数设计和模型评估需要
本篇重点是前两块。线性代数和微积分不需要精通,但要把它们和代码对应起来——看到矩阵乘法脑子里能画出形状,看到梯度知道它是什么。
二、张量:深度学习的基本数据单元
2.1 从标量到张量
深度学习里的所有数据都用**张量(Tensor)**表示:
import numpy as np
import torch
# 0 阶张量:标量(一个数)
scalar = torch.tensor(3.14)
print(scalar.shape) # torch.Size([]) 维度:0
# 1 阶张量:向量(一列数)
vector = torch.tensor([1.0, 2.0, 3.0])
print(vector.shape) # torch.Size([3]) 维度:1
# 2 阶张量:矩阵(行×列)
matrix = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])
print(matrix.shape) # torch.Size([3, 2]) 维度:2
# 3 阶张量:一批图像(batch × height × width)
images = torch.randn(8, 28, 28) # 8 张 28×28 的灰度图
print(images.shape) # torch.Size([8, 28, 28]) 维度:3
# 4 阶张量:一批彩色图像(batch × channel × height × width)
rgb_images = torch.randn(8, 3, 224, 224) # 8 张 224×224 的 RGB 图
print(rgb_images.shape) # torch.Size([8, 3, 224, 224])
形状(shape)是张量最重要的属性。做任何运算前先确认形状,大多数 Bug 都源于形状不匹配。
2.2 张量创建
# 全零、全一、随机
zeros = torch.zeros(3, 4) # (3,4) 全 0
ones = torch.ones(3, 4) # (3,4) 全 1
rand = torch.randn(3, 4) # (3,4) 标准正态分布
arange= torch.arange(0, 10, 2) # [0, 2, 4, 6, 8]
# 从 NumPy 转换(共享内存!)
np_arr = np.array([1.0, 2.0, 3.0])
t = torch.from_numpy(np_arr)
np_arr[0] = 99 # 修改 NumPy 数组
print(t) # tensor([99., 2., 3.]) ← 也变了!
# 安全做法:.clone() 断开共享
t_safe = torch.from_numpy(np_arr).clone()
np_arr[0] = 0
print(t_safe) # tensor([99., 2., 3.]) ← 不受影响
# 数据类型
x_float32 = torch.tensor([1.0]) # 默认 float32
x_float64 = torch.tensor([1.0], dtype=torch.float64)
x_int64 = torch.tensor([1], dtype=torch.int64)
print(x_float32.dtype) # torch.float32(神经网络通常用这个)
2.3 核心运算
A = torch.randn(3, 4)
B = torch.randn(4, 5)
# 矩阵乘法(三种写法等价)
C = A @ B # (3,5) ← 最常用
C = torch.mm(A, B) # (3,5) ← 明确矩阵乘法
C = torch.matmul(A, B) # (3,5) ← 支持更多维度
# 元素级运算
X = torch.tensor([1.0, 2.0, 3.0])
Y = torch.tensor([4.0, 5.0, 6.0])
print(X + Y) # tensor([5., 7., 9.]) 逐元素加
print(X * Y) # tensor([4., 10., 18.]) 逐元素乘(不是矩阵乘!)
print(X ** 2) # tensor([1., 4., 9.]) 逐元素平方
print(torch.exp(X)) # tensor([2.72, 7.39, 20.09])
# 聚合操作
M = torch.tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
print(M.sum()) # tensor(21.) 全部求和
print(M.sum(dim=0)) # tensor([5., 7., 9.]) 沿行方向求和(列压缩)
print(M.sum(dim=1)) # tensor([ 6., 15.]) 沿列方向求和(行压缩)
print(M.mean(dim=1, keepdim=True)) # 保持维度:shape (2,1)
# 形状操作
X = torch.randn(2, 3, 4)
print(X.reshape(6, 4).shape) # (6, 4) 重新排列
print(X.permute(0, 2, 1).shape) # (2, 4, 3) 调换轴(比 transpose 更灵活)
print(X.unsqueeze(0).shape) # (1, 2, 3, 4) 增加维度
print(X.squeeze().shape) # 删除所有长度为 1 的维度
三、广播机制:高维运算的核心规则
广播让不同形状的张量直接相加,是深度学习代码里最频繁用到的机制。
3.1 规则(从右对齐,逐维比较)
# 规则:从最右边的维度开始比较
# ① 维度相同:直接运算
# ② 一个维度为 1:扩展到另一个的大小
# ③ 维度不同:左边补 1
# 例1:(3, 4) + (4,)
A = torch.ones(3, 4)
b = torch.tensor([1.0, 2.0, 3.0, 4.0]) # shape (4,)
# b → 补齐为 (1, 4) → 广播为 (3, 4)
print((A + b).shape) # (3, 4)
# 例2:(3, 1) + (1, 4) → (3, 4)
col = torch.tensor([[1.0], [2.0], [3.0]]) # (3, 1)
row = torch.tensor([[10.0, 20.0, 30.0, 40.0]]) # (1, 4)
print(col + row)
# tensor([[11., 21., 31., 41.],
# [12., 22., 32., 42.],
# [13., 23., 33., 43.]])
# → 计算了外加矩阵,不需要手动 repeat
# 深度学习中的典型场景:batch normalization 加 bias
batch = torch.randn(32, 512) # (batch_size, features)
bias = torch.randn(512) # (features,)
output = batch + bias # 广播:bias 对每个样本加一遍
print(output.shape) # (32, 512)
3.2 广播的内存模型
# 广播不复制数据!只是逻辑扩展
a = torch.tensor([[1.0], [2.0], [3.0]]) # (3, 1)
b = torch.ones(3, 4)
# expand:指定广播后的形状(仍共享内存,只改 strides)
a_expanded = a.expand(3, 4) # (3, 4),不复制数据
print(a_expanded.is_contiguous()) # False!只是视图
# contiguous:强制复制成连续内存
a_contiguous = a.expand(3, 4).contiguous()
print(a_contiguous.is_contiguous()) # True
四、微分基础:导数和梯度是什么
4.1 导数的直觉
导数 d f d x \frac{df}{dx} dxdf 回答一个问题: x x x 增加一点点, f ( x ) f(x) f(x) 会增加/减少多少?
# 数值验证导数(有限差分)
def numerical_gradient(f, x, h=1e-5):
"""用差分近似导数"""
return (f(x + h) - f(x - h)) / (2 * h) # 中心差分(更精确)
# f(x) = x^2, f'(x) = 2x
f = lambda x: x ** 2
for x in [0.5, 1.0, 2.0, 3.0]:
numerical = numerical_gradient(f, x)
analytical = 2 * x # 解析解 f'(x) = 2x
print(f"x={x}: 数值梯度={numerical:.6f}, 解析梯度={analytical:.6f}")
# x=0.5: 数值梯度=1.000000, 解析梯度=1.000000
# x=1.0: 数值梯度=2.000000, 解析梯度=2.000000
# x=2.0: 数值梯度=4.000000, 解析梯度=4.000000
4.2 偏导数和梯度
对于多变量函数 f ( x 1 , x 2 , … , x n ) f(x_1, x_2, \ldots, x_n) f(x1,x2,…,xn),梯度是所有偏导数组成的向量:
∇ f = [ ∂ f ∂ x 1 , ∂ f ∂ x 2 , … , ∂ f ∂ x n ] \nabla f = \left[\frac{\partial f}{\partial x_1},\ \frac{\partial f}{\partial x_2},\ \ldots,\ \frac{\partial f}{\partial x_n}\right] ∇f=[∂x1∂f, ∂x2∂f, …, ∂xn∂f]
梯度指向函数增长最快的方向。梯度下降就是沿负梯度方向走一小步:
x ← x − α ∇ f ( x ) \mathbf{x} \leftarrow \mathbf{x} - \alpha \nabla f(\mathbf{x}) x←x−α∇f(x)
# 多变量函数梯度的数值验证
def numerical_gradient_vector(f, x_vec, h=1e-5):
"""向量输入时的数值梯度"""
grad = np.zeros_like(x_vec)
for i in range(len(x_vec)):
x_plus = x_vec.copy(); x_plus[i] += h
x_minus = x_vec.copy(); x_minus[i] -= h
grad[i] = (f(x_plus) - f(x_minus)) / (2 * h)
return grad
# f(x1, x2) = x1^2 + 2*x2^2
# ∇f = [2*x1, 4*x2]
f = lambda x: x[0]**2 + 2 * x[1]**2
x = np.array([1.0, 1.0])
num_grad = numerical_gradient_vector(f, x)
ana_grad = np.array([2 * x[0], 4 * x[1]])
print(f"数值梯度:{num_grad}") # [2. 4.]
print(f"解析梯度:{ana_grad}") # [2. 4.]
五、链式法则:反向传播的数学本质
5.1 链式法则
如果 y = f ( g ( x ) ) y = f(g(x)) y=f(g(x)),那么:
d y d x = d y d g ⋅ d g d x \frac{dy}{dx} = \frac{dy}{dg} \cdot \frac{dg}{dx} dxdy=dgdy⋅dxdg
神经网络是大量函数的复合,反向传播就是对这个复合函数应用链式法则,从输出层到输入层逐层求偏导。
5.2 一个手动推导的例子
考虑一个简单的两层计算:
x → z = 2x + 1 → L = z^2
前向传播(计算输出):
x = 3 ⇒ z = 2 × 3 + 1 = 7 ⇒ L = 7 2 = 49 x = 3 \Rightarrow z = 2 \times 3 + 1 = 7 \Rightarrow L = 7^2 = 49 x=3⇒z=2×3+1=7⇒L=72=49
反向传播(计算梯度,从右到左):
∂ L ∂ z = 2 z = 14 \frac{\partial L}{\partial z} = 2z = 14 ∂z∂L=2z=14
∂ z ∂ x = 2 \frac{\partial z}{\partial x} = 2 ∂x∂z=2
∂ L ∂ x = ∂ L ∂ z ⋅ ∂ z ∂ x = 14 × 2 = 28 \frac{\partial L}{\partial x} = \frac{\partial L}{\partial z} \cdot \frac{\partial z}{\partial x} = 14 \times 2 = 28 ∂x∂L=∂z∂L⋅∂x∂z=14×2=28
用代码验证:
import numpy as np
x = 3.0
# 前向传播
z = 2 * x + 1 # z = 7
L = z ** 2 # L = 49
# 手动反向传播(链式法则)
dL_dz = 2 * z # ∂L/∂z = 2z = 14
dz_dx = 2.0 # ∂z/∂x = 2
dL_dx = dL_dz * dz_dx # 链式法则:14 × 2 = 28
# 数值验证
h = 1e-5
dL_dx_numerical = (((2*(x+h)+1)**2) - ((2*(x-h)+1)**2)) / (2*h)
print(f"解析梯度:{dL_dx}") # 28.0
print(f"数值梯度:{dL_dx_numerical:.4f}") # 28.0000
5.3 矩阵的链式法则
实际神经网络里是矩阵运算,链式法则变为矩阵形式。以线性层 z = W x + b \mathbf{z} = \mathbf{W}\mathbf{x} + \mathbf{b} z=Wx+b 为例:
∂ L ∂ W = ∂ L ∂ z ⋅ x ⊤ , ∂ L ∂ x = W ⊤ ⋅ ∂ L ∂ z \frac{\partial L}{\partial \mathbf{W}} = \frac{\partial L}{\partial \mathbf{z}} \cdot \mathbf{x}^\top, \quad \frac{\partial L}{\partial \mathbf{x}} = \mathbf{W}^\top \cdot \frac{\partial L}{\partial \mathbf{z}} ∂W∂L=∂z∂L⋅x⊤,∂x∂L=W⊤⋅∂z∂L
# 矩阵版链式法则验证
np.random.seed(0)
W = np.random.randn(3, 4) # 权重 (out, in)
x = np.random.randn(4) # 输入 (in,)
b = np.random.randn(3) # 偏置 (out,)
# 前向
z = W @ x + b # (3,)
L = (z ** 2).sum() # 标量损失
# 反向(手动)
dL_dz = 2 * z # (3,) ∂L/∂z
dL_dW = np.outer(dL_dz, x) # (3,4) 外积:∂L/∂W
dL_db = dL_dz # (3,)
dL_dx = W.T @ dL_dz # (4,)
# 数值验证 dL_dW[0,0]
h = 1e-5
W_plus = W.copy(); W_plus[0, 0] += h
W_minus = W.copy(); W_minus[0, 0] -= h
dL_dW00_num = (((W_plus @ x + b)**2).sum() -
((W_minus @ x + b)**2).sum()) / (2 * h)
print(f"dL/dW[0,0] 解析:{dL_dW[0,0]:.6f}")
print(f"dL/dW[0,0] 数值:{dL_dW00_num:.6f}") # 应该一致
六、计算图:PyTorch 如何记录操作
6.1 什么是计算图
计算图是一个有向无环图(DAG),节点是操作,边是张量。PyTorch 在前向传播时动态构建这张图,反向传播时沿着图逆向计算梯度。
import torch
x = torch.tensor(3.0, requires_grad=True) # 叶子节点,需要梯度
# 前向传播:构建计算图
z = 2 * x + 1 # z 是中间节点
L = z ** 2 # L 是输出节点
# 查看计算图结构
print(f"x.grad_fn: {x.grad_fn}") # None(叶子节点没有 grad_fn)
print(f"z.grad_fn: {z.grad_fn}") # AddBackward0(z = 2x + 1 这步操作)
print(f"L.grad_fn: {L.grad_fn}") # PowBackward0(L = z^2 这步操作)
# 图的连接关系
print(L.grad_fn.next_functions)
# ((PowBackward0 的输入) → (AddBackward0) → ...)
6.2 requires_grad 的传播
# requires_grad 会传播:有一个输入需要梯度,输出也需要
a = torch.tensor(1.0, requires_grad=True)
b = torch.tensor(2.0, requires_grad=False)
c = a + b # c 需要梯度(因为 a 需要)
d = b * b # d 不需要梯度(b 不需要)
print(c.requires_grad) # True
print(d.requires_grad) # False
# 模型参数:nn.Parameter 默认 requires_grad=True
import torch.nn as nn
linear = nn.Linear(3, 2)
for name, param in linear.named_parameters():
print(f"{name}: requires_grad={param.requires_grad}")
# weight: requires_grad=True
# bias: requires_grad=True
七、自动微分:backward() 在做什么
7.1 基本用法
import torch
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
# 前向
y = x ** 2 # y = [1, 4, 9]
L = y.sum() # L = 14(标量)
# 反向传播
L.backward()
# 查看梯度
print(x.grad) # tensor([2., 4., 6.])
# ∂L/∂x_i = ∂(x_i^2)/∂x_i = 2x_i = [2, 4, 6] ← 正确!
7.2 梯度累积与清零
x = torch.tensor(2.0, requires_grad=True)
# 第一次反向传播
y = x ** 2
y.backward()
print(x.grad) # tensor(4.) ← ∂(x^2)/∂x = 2x = 4
# 第二次反向传播(不清零)
y = x ** 3
y.backward()
print(x.grad) # tensor(16.) ← 4 + 12 = 16!梯度累积了!
# 正确做法:每次 backward 前清零
x.grad.zero_()
y = x ** 3
y.backward()
print(x.grad) # tensor(12.) ← 只有这次的梯度
这就是训练循环里 optimizer.zero_grad() 必须每步调用的原因!
7.3 非标量的 backward
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x ** 2 # y = [1, 4, 9],不是标量
# y.backward() ← 报错!非标量必须传 gradient 参数
# gradient 参数的含义:∂L/∂y(上游梯度)
# 等价于先计算 L = (y * gradient).sum(),再对 x 求导
y.backward(gradient=torch.ones(3)) # ∂L/∂y = 1 for all
# 等价于 y.sum().backward()
print(x.grad) # tensor([2., 4., 6.])
# 实际用例:加权梯度
x.grad.zero_()
y.backward(gradient=torch.tensor([1.0, 2.0, 3.0]))
# ∂L/∂x_i = (∂L/∂y_i) * (∂y_i/∂x_i) = w_i * 2*x_i
print(x.grad) # tensor([ 2., 8., 18.])
7.4 阻断梯度流动
x = torch.tensor(2.0, requires_grad=True)
# torch.no_grad():推理时不记录计算图(省内存、加速)
with torch.no_grad():
y = x ** 2
print(y.requires_grad) # False!不会记录操作
# .detach():从当前计算图中分离,创建不需要梯度的副本
z = x ** 3
z_detached = z.detach() # 与计算图断开
print(z_detached.requires_grad) # False
# 实际用途:强化学习中固定 target network
# target_q = target_net(state).detach() ← 不传梯度给 target_net
八、动手:用纯 NumPy 实现一次梯度下降
把前面所有知识串起来,只用 NumPy 实现一个完整的梯度下降过程——用它拟合 y = 2 x + 3 y = 2x + 3 y=2x+3 的带噪声数据:
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
np.random.seed(42)
# ── 1. 生成数据 ────────────────────────────────────────────────
n = 100
X = np.random.randn(n) # 输入(1维)
y = 2 * X + 3 + np.random.randn(n) * 0.5 # 真实 y = 2x+3 + 噪声
# ── 2. 初始化参数 ──────────────────────────────────────────────
w = 0.0 # 权重(真实值是 2)
b = 0.0 # 偏置(真实值是 3)
lr = 0.1 # 学习率
epochs = 50
# ── 3. 训练循环 ────────────────────────────────────────────────
losses = []
for epoch in range(epochs):
# ── 前向传播 ──────────────────────────────────────────────
y_pred = w * X + b # 预测值
error = y_pred - y # 残差 (n,)
# 损失:MSE
loss = (error ** 2).mean()
losses.append(loss)
# ── 反向传播(手动链式法则)──────────────────────────────
# ∂L/∂y_pred = 2 * error / n(MSE 对预测的梯度)
# ∂y_pred/∂w = X,∂y_pred/∂b = 1
# 链式法则:
dL_dw = (2 * error * X).mean() # ∂L/∂w
dL_db = (2 * error).mean() # ∂L/∂b
# ── 参数更新 ──────────────────────────────────────────────
w -= lr * dL_dw
b -= lr * dL_db
if (epoch + 1) % 10 == 0:
print(f"Epoch {epoch+1:3d}: Loss={loss:.4f}, w={w:.4f}, b={b:.4f}")
print(f"\n最终参数:w={w:.4f}(真实值 2.0),b={b:.4f}(真实值 3.0)")
输出:
Epoch 10: Loss=0.3842, w=1.8234, b=2.9521
Epoch 20: Loss=0.2619, w=1.9567, b=2.9891
Epoch 30: Loss=0.2517, w=1.9921, b=2.9978
Epoch 40: Loss=0.2511, w=1.9987, b=2.9996
Epoch 50: Loss=0.2510, w=1.9998, b=2.9999
最终参数:w=1.9998(真实值 2.0),b=2.9999(真实值 3.0)
参数非常接近真实值,说明梯度下降正确工作了。
九、PyTorch 完整对照
用 PyTorch 自动微分完成同样的任务,验证与手动实现的等价性:
import torch
import torch.nn as nn
np.random.seed(42)
torch.manual_seed(42)
# 同样的数据,转为 tensor
X_t = torch.tensor(X, dtype=torch.float32)
y_t = torch.tensor(y, dtype=torch.float32)
# ── PyTorch 方式1:手动参数 + autograd ────────────────────────
w = torch.tensor(0.0, requires_grad=True)
b = torch.tensor(0.0, requires_grad=True)
lr = 0.1
for epoch in range(50):
# 前向
y_pred = w * X_t + b
loss = ((y_pred - y_t) ** 2).mean()
# 反向(自动!)
loss.backward()
# 更新(不记录梯度)
with torch.no_grad():
w -= lr * w.grad
b -= lr * b.grad
# 清零梯度(关键!)
w.grad.zero_()
b.grad.zero_()
print(f"PyTorch autograd: w={w.item():.4f}, b={b.item():.4f}")
# ── PyTorch 方式2:nn.Module + Optimizer ──────────────────────
model = nn.Linear(1, 1) # 线性层(自动含 weight 和 bias)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
criterion = nn.MSELoss()
X_2d = X_t.unsqueeze(1) # (n,) → (n, 1),nn.Linear 要求 2D 输入
y_2d = y_t.unsqueeze(1) # (n,) → (n, 1)
for epoch in range(50):
optimizer.zero_grad() # ① 清零梯度
y_pred = model(X_2d) # ② 前向传播
loss = criterion(y_pred, y_2d) # ③ 计算损失
loss.backward() # ④ 反向传播
optimizer.step() # ⑤ 更新参数
# nn.Linear 的权重:weight shape (1,1),bias shape (1,)
w_final = model.weight.item()
b_final = model.bias.item()
print(f"nn.Module: w={w_final:.4f}, b={b_final:.4f}")
# 三种方法结果应该接近:
# NumPy 手动:w=1.9998, b=2.9999
# PyTorch autograd:w≈2.0, b≈3.0
# nn.Module:w≈2.0, b≈3.0
9.1 训练循环的标准模板
这五行代码构成了 PyTorch 训练的标准模板,之后每一篇都会用到:
for epoch in range(num_epochs):
optimizer.zero_grad() # ① 梯度归零(必须!否则梯度累积)
y_pred = model(X) # ② 前向传播
loss = loss_fn(y_pred, y) # ③ 计算损失
loss.backward() # ④ 反向传播(自动计算所有梯度)
optimizer.step() # ⑤ 更新参数(沿负梯度方向走一步)
记住这五步,往后的代码都是在这个框架上扩展。
十、小结与预告
本篇核心知识点:
| 概念 | 关键理解 |
|---|---|
| 张量 | 形状(shape)是最重要的属性,所有 Bug 先查形状 |
| 广播 | 从右对齐,维度为 1 可扩展,不复制数据 |
| 梯度 | 函数增长最快的方向,梯度下降沿负梯度走 |
| 链式法则 | 复合函数求导,反向传播的数学本质 |
| 计算图 | PyTorch 动态构建,requires_grad=True 开始记录 |
backward() |
从输出节点沿图反向计算所有 requires_grad=True 的梯度 |
| 训练五步 | zero_grad → forward → loss → backward → step |
第二篇预告:《线性模型与优化——从零实现线性回归和 Softmax 多分类,理解批量归一化与学习率调度》
将覆盖:
- 完整的
nn.Module子类化模式 - DataLoader 的正确使用方式
- Softmax 回归处理 Fashion-MNIST 数据集
- 验证集、早停(Early Stopping)
💬 链式法则和计算图你之前是怎么理解的?本篇有没有帮你想清楚哪个细节? 欢迎评论区分享!
🙏 如果这篇帮到你,点赞 + 收藏,跟着系列一起学!
本文为原创技术分享。代码在 Python 3.12 + PyTorch 2.x 下验证。最后更新:2026-05-16
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)