现代 AI 里最常见的几个数学对象是:
大学毕业后,张量,梯度等概念可能早忘了,这一章回顾一下现代AI中非常常见且重要的基本数学概念。
看完后,一句话总结就是:
神经网络训练,就是用梯度下降去最小化一个损失函数。
标量就是一个单独的数:
3.14
-7
0
在 PyTorch 里:
torch.tensor(3.14)
它的形状是:
shape = ()
向量是一列数:
[1, 2, 3]
可以表示:
在 PyTorch 里:
torch.tensor([1.0, 2.0, 3.0])
形状是:
shape = (3,)
意思是:这是一个一维张量,第一维有 3 个元素。
矩阵是二维数组:
[1, 2]
[3, 4]
可以表示:
在 PyTorch 里:
torch.tensor([[1.0, 2.0], [3.0, 4.0]])
形状是:
shape = (2, 2)
意思是:2 行,2 列。
张量是更一般的概念:
| 维度 | 名称 | 例子 |
|---|---|---|
| 0 | 标量 | 3.14 |
| 1 | 向量 | [1,2,3] |
| 2 | 矩阵 | [[1,2],[3,4]] |
| 3 | 三维张量 | 一批 RGB 图片 |
| 4 | 四维张量 | 一批视频帧 |
例如一批 32 张彩色图片:
shape = (32, 3, 224, 224)
含义是:
32:批里有 32 张图3:RGB 三个通道224:高224:宽所以:
张量就是多维数组,外加形状、数据类型、所在设备和梯度信息。
两个张量能不能相乘,取决于形状。
A shape = (2, 3)
B shape = (2, 3)
A @ B 会报错,因为矩阵乘法要求:
A 的最后一维 == B 的倒数第二维
也就是:
(2, 3) @ (3, 4) -> (2, 4)
如果:
A =
[1, 2, 3]
[4, 5, 6]
B =
[7, 8]
[9, 10]
[11, 12]
那么:
A @ B =
[1*7 + 2*9 + 3*11, 1*8 + 2*10 + 3*12]
[4*7 + 5*9 + 6*11, 4*8 + 5*10 + 6*12]
计算结果:
[58, 64]
[139, 154]
最终形状:
(2, 3) @ (3, 2) -> (2, 2)
[1, 2, 3] + [10, 20, 30] = [11, 22, 33]
3 * [1, 2, 3] = [3, 6, 9]
两个向量对应位置相乘再求和:
[1, 2] · [3, 4] = 1*3 + 2*4 = 11
点积的结果是标量。
矩阵乘法是多次点积:
A =
[1, 2]
[3, 4]
B =
[5, 6]
[7, 8]
A @ B =
[1*5 + 2*7, 1*6 + 2*8]
[3*5 + 4*7, 3*6 + 4*8]
=
[19, 22]
[43, 50]
如果形状不同的张量满足规则,PyTorch 会自动扩展:
[[1, 2],
[3, 4]] + [10, 20]
相当于:
[[1, 2],
[3, 4]] + [[10, 20],
[10, 20]]
结果:
[[11, 22],
[13, 24]]
这叫 broadcasting。
函数就是把输入映射到输出:
\[y=f(x)\]例如:
\[f(x)=x^2\]如果 \(x=3\):
\[f(3)=9\]导数描述“输入变化一点点,输出变化多少”:
\[f'(x)=\frac{df}{dx}\]直观理解:
导数是函数在某一点的斜率。
对于:
\[f(x)=x^2\]导数是:
\[f'(x)=2x\]所以:
\[f'(3)=6\]这意味着在 \(x=3\) 附近:
更严格地说,这是局部线性近似。
如果函数有多个输入,就对每个输入分别求导。
例如:
\[f(x,y)=x^2+3xy\]把 \(y\) 当常数:
\[\frac{\partial f}{\partial x}=2x+3y\]把 \(x\) 当常数:
\[\frac{\partial f}{\partial y}=3x\]如果:
\[x=2,\quad y=5\]那么:
\[\frac{\partial f}{\partial x}=2*2+3*5=19\] \[\frac{\partial f}{\partial y}=3*2=6\]含义:
梯度就是把所有偏导数排成一个向量:
\[\nabla f= \begin{bmatrix} \frac{\partial f}{\partial x}\\ \frac{\partial f}{\partial y} \end{bmatrix}\]例如:
\[f(x,y)=x^2+y^2\]偏导数:
\[\frac{\partial f}{\partial x}=2x\] \[\frac{\partial f}{\partial y}=2y\]所以梯度:
\[\nabla f=(2x,2y)\]在点 \((3,4)\):
\[\nabla f=(6,8)\]梯度指向函数上升最快的方向。
如果想最小化函数,就沿反方向走:
\[-\nabla f\]这就是梯度下降的核心。
各项含义:
| 符号 | 含义 |
|---|---|
| \(x_{\text{old}}\) | 当前参数 |
| \(\eta\) | 学习率 |
| \(\nabla f\) | 梯度 |
| \(x_{\text{new}}\) | 更新后的参数 |
目标函数:
\[f(x)=x^2-4x+3\]导数:
\[f'(x)=2x-4\]令导数为 0:
\[2x-4=0\]得到:
\[x=2\]这是最小值点。
第 1 步:
\[f'(0)=2*0-4=-4\] \[x_{\text{new}}=0-0.1*(-4)=0.4\]第 2 步:
\[f'(0.4)=2*0.4-4=-3.2\] \[x_{\text{new}}=0.4-0.1*(-3.2)=0.72\]第 3 步:
\[f'(0.72)=2*0.72-4=-2.56\] \[x_{\text{new}}=0.72-0.1*(-2.56)=0.976\]你可以看到:
0 -> 0.4 -> 0.72 -> 0.976 -> ...
它正在逐步逼近:
2
如果学习率太小:
0.001
会走得很慢。
如果学习率太大:
10
可能来回震荡,甚至发散。
所以学习率是训练中非常重要的超参数。
神经网络训练需要一个“分数”来衡量好坏,这个分数叫损失。
损失越小,模型输出越接近目标。
回归问题常用均方误差:
\[\text{MSE}=\frac{1}{n}\sum_i(\hat{y}_i-y_i)^2\]其中:
假设数据:
x = [1, 2]
y = [2, 4]
真实规律是:
\[y=2x\]模型:
\[\hat{y}=wx+b\]初始参数:
\[w=0,\quad b=0\]预测:
\[\hat{y}=[0,0]\]误差:
\[\hat{y}-y=[-2,-4]\]损失:
\[\text{MSE}=\frac{(-2)^2+(-4)^2}{2} =\frac{4+16}{2} =10\]损失:
\[L=\frac{1}{n}\sum_i(wx_i+b-y_i)^2\]对 \(w\) 的偏导:
\[\frac{\partial L}{\partial w} =\frac{2}{n}\sum_i(wx_i+b-y_i)x_i\]对 \(b\) 的偏导:
\[\frac{\partial L}{\partial b} =\frac{2}{n}\sum_i(wx_i+b-y_i)\]代入初始值:
\[w=0,\quad b=0\]误差:
\[[-2,-4]\]所以:
\[\frac{\partial L}{\partial w} =\frac{2}{2}((-2)*1+(-4)*2) =-10\] \[\frac{\partial L}{\partial b} =\frac{2}{2}((-2)+(-4)) =-6\]梯度是:
\[\nabla L=(-10,-6)\]学习率:
\[\eta=0.1\] \[w_{\text{new}}=0-0.1*(-10)=1\] \[b_{\text{new}}=0-0.1*(-6)=0.6\]新预测:
\[\hat{y}=[1*1+0.6,\ 1*2+0.6]=[1.6,2.6]\]新误差:
\[[-0.4,-1.4]\]新损失:
\[\frac{(-0.4)^2+(-1.4)^2}{2} =\frac{0.16+1.96}{2} =1.06\]损失从:
10 -> 1.06
明显下降。
神经网络是多层函数复合而成。
如果:
\[y=x^2\] \[z=3y+1\]那么:
\[z=3x^2+1\]导数:
\[\frac{dz}{dx}=6x\]也可以用链式法则:
\[\frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{dx}\]其中:
\[\frac{dz}{dy}=3\] \[\frac{dy}{dx}=2x\]所以:
\[\frac{dz}{dx}=3*2x=6x\]如果 \(x=2\):
\[\frac{dz}{dx}=12\]反向传播就是用链式法则,从输出层往输入层逐层计算梯度。
例如:
x
↓
square
↓
multiply by 3
↓
add 1
↓
z
前向传播是:
x -> y -> z
反向传播是:
∂z/∂z -> ∂z/∂y -> ∂z/∂x
PyTorch 的 autograd 会自动做这件事。
以下代码包含计算:
#!/usr/bin/env python
from __future__ import annotations
import torch
def demo_tensors() -> None:
print("=== 1. tensor shapes ===")
scalar = torch.tensor(3.14)
vector = torch.tensor([1.0, 2.0, 3.0])
matrix = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
tensor3d = torch.zeros(2, 3, 4)
print(f"scalar shape: {tuple(scalar.shape)}")
print(f"vector shape: {tuple(vector.shape)}")
print(f"matrix shape: {tuple(matrix.shape)}")
print(f"3d tensor shape: {tuple(tensor3d.shape)}")
def demo_operations() -> None:
print("\n=== 2. tensor operations ===")
left = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
right = torch.tensor([[5.0, 6.0], [7.0, 8.0]])
product = left @ right
broadcast = left + torch.tensor([10.0, 20.0])
print(f"left:\n{left}")
print(f"right:\n{right}")
print(f"matrix product:\n{product}")
print(f"broadcast result:\n{broadcast}")
def demo_autograd() -> None:
print("\n=== 3. autograd: f(x) = x^2 at x = 3 ===")
value = torch.tensor(3.0, requires_grad=True)
output = value ** 2
output.backward()
print(f"f(3) = {output.item():.1f}")
print(f"df/dx at x=3 = {value.grad.item():.1f}")
def demo_scalar_gradient_descent() -> None:
print("\n=== 4. gradient descent on f(x) = x^2 - 4x + 3 ===")
value = torch.tensor(0.0, requires_grad=True)
learning_rate = 0.1
for step in range(1, 11):
if value.grad is not None:
value.grad = None
output = value ** 2 - 4 * value + 3
output.backward()
gradient = value.grad.item()
print(f"step {step:02d} | x={value.item():.5f} | f={output.item():.5f} | grad={gradient:+.5f}")
with torch.no_grad():
value -= learning_rate * gradient
print(f"final x: {value.item():.5f}")
def demo_linear_regression() -> None:
print("\n=== 5. gradient descent for linear regression ===")
inputs = torch.tensor([1.0, 2.0])
targets = torch.tensor([2.0, 4.0])
weight = torch.tensor(0.0, requires_grad=True)
bias = torch.tensor(0.0, requires_grad=True)
learning_rate = 0.1
for step in range(1, 31):
if weight.grad is not None:
weight.grad = None
if bias.grad is not None:
bias.grad = None
predictions = weight * inputs + bias
errors = predictions - targets
loss = (errors ** 2).mean()
loss.backward()
if step in (1, 2, 3, 5, 10, 20, 30):
print(
f"step {step:02d} | w={weight.item():+.5f} | "
f"b={bias.item():+.5f} | loss={loss.item():.5f} | "
f"grad_w={weight.grad.item():+.5f} | grad_b={bias.grad.item():+.5f}"
)
with torch.no_grad():
weight -= learning_rate * weight.grad
bias -= learning_rate * bias.grad
print(f"final weight: {weight.item():.5f}")
print(f"final bias: {bias.item():.5f}")
def main() -> None:
torch.manual_seed(1337)
demo_tensors()
demo_operations()
demo_autograd()
demo_scalar_gradient_descent()
demo_linear_regression()
if __name__ == "__main__":
main()
| 术语 | 含义 |
|---|---|
| scalar | 标量,单个数 |
| vector | 向量,一维数组 |
| matrix | 矩阵,二维数组 |
| tensor | 多维数组,带形状和梯度信息 |
| shape | 张量每一维的大小 |
| dtype | 数据类型,例如 float32、int64 |
| derivative | 导数,函数斜率 |
| partial derivative | 偏导数,多输入函数对某一个输入的导数 |
| gradient | 所有偏导数组成的向量 |
| learning rate | 学习率,参数更新步长 |
| loss | 损失,衡量模型好坏 |
| MSE | 均方误差 |
| chain rule | 链式法则 |
| backpropagation | 反向传播 |
| epoch | 完整遍历一次训练数据 |
| batch | 一批样本 |
| optimizer | 根据梯度更新参数的算法 |
| convergence | 收敛,损失逐渐稳定 |
| local minimum | 局部最小值 |
| 符号 / 名词 | 含义 |
|---|---|
| \(x\) | 输入变量或输入张量 |
| \(y\) | 输出变量或真实标签 |
| \(\hat{y}\) | 模型预测值,读作 “y hat” |
| \(f(x)\) | 以 \(x\) 为输入的函数 |
| \(f'(x)\) | \(f\) 对 \(x\) 的导数 |
| \(\frac{dy}{dx}\) | \(y\) 对 \(x\) 的导数 |
| \(\frac{\partial f}{\partial x}\) | \(f\) 对 \(x\) 的偏导数 |
| \(\nabla f\) | \(f\) 的梯度 |
| \(\eta\) | 学习率 |
| \(L\) | 损失函数 |
| \(\sum\) | 求和 |
| \(\prod\) | 连乘 |
| shape | 张量形状 |
| rank | 张量维数 |
| broadcasting | 形状自动扩展规则 |
| autograd | 自动微分系统 |
| computation graph | 计算图,记录运算依赖关系 |
| local minimum | 局部最小值 |
| saddle point | 鞍点,一个方向是极小、另一个方向是极大 |