AI 数学基础
机器学习(尤其是深度学习)建立在数学之上。本教程用直观 + 代码的方式讲解 AI 需要的三大数学支柱:线性代数(数据表示)、微积分(模型优化)、概率论(不确定性建模)。
数学在 AI 中的角色
| 分支 | 在 AI 中的用途 |
|---|---|
| 线性代数 | 数据用向量/矩阵表示,神经网络计算 |
| 微积分 | 梯度下降优化模型参数 |
| 概率统计 | 处理不确定性、损失函数设计 |
| 信息论 | 交叉熵、KL 散度(损失函数) |
| 最优化 | 找到最小化损失的最佳参数 |
数据的形状
数据的形状:理解 AI 数据的基本组织方式:
| 形状 | 名称 | 示例 |
|---|---|---|
| 标量(Scalar) | 0 维 | 1.5、42 |
| 向量(Vector) | 1 维 | [1.0, 2.5, 3.0] |
| 矩阵(Matrix) | 2 维 | [[1,2],[3,4]] |
| 张量(Tensor) | 3+ 维 | 图像 (H,W,C) |
import numpy as np
# 标量
s = np.array(5)
print(s.shape) # ()
# 向量
v = np.array([1, 2, 3])
print(v.shape) # (3,)
# 矩阵
m = np.array([[1, 2], [3, 4]])
print(m.shape) # (2, 2)
# 张量(如一张 28x28 彩色图:高,宽,通道)
img = np.zeros((28, 28, 3))
print(img.shape) # (28, 28, 3)
# 一批图片(批量大小, 高, 宽, 通道)
batch = np.zeros((32, 28, 28, 3))
print(batch.shape) # (32, 28, 28, 3)向量运算
向量运算:逐元素计算与广播:
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 逐元素运算
print(a + b) # [5 7 9]
print(a - b) # [-3 -3 -3]
print(a * b) # [4 10 18](逐元素乘,非向量点积)
print(a / b) # [0.25 0.4 0.5]
# 标量广播
print(a + 10) # [11 12 13]
print(a * 2) # [2 4 6]
# 常用函数
print(np.sum(a)) # 6
print(np.mean(a)) # 2.0
print(np.max(a)) # 3
print(np.sqrt(a)) # [1. 1.41 1.73]
# 条件筛选
print(a[a > 1]) # [2 3]
# 向量范数(长度)
print(np.linalg.norm(a)) # sqrt(1+4+9)≈3.74向量的点积
点积(Dot Product):向量间最核心的运算:
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 点积 = 对应元素相乘后求和
dot = np.dot(a, b)
# 1*4 + 2*5 + 3*6 = 32
print(dot)
# 等价写法
print(a @ b) # 32(@ 运算符)
print(np.sum(a * b)) # 32
# 几何意义:衡量两向量相似程度
# 点积大 = 方向接近(相似)
# 点积为 0 = 垂直(无关)
# 应用:神经网络中的加权求和
weights = np.array([0.5, 0.3, 0.2])
features = np.array([10, 20, 30])
output = weights @ features # 加权和
print(output) # 5 + 6 + 6 = 17
# 余弦相似度(归一化点积)
def cosine(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(cosine(np.array([1,0]), np.array([0.9,0.1]))) # 接近 1W @ x + b:权重矩阵与输入向量做点积。向量的模长
向量的模长(范数):衡量向量大小:
import numpy as np
v = np.array([3, 4])
# L2 范数(欧几里得长度)
l2 = np.linalg.norm(v)
print(l2) # 5.0(勾股定理 3²+4²=5²)
# 手动计算
print(np.sqrt(np.sum(v ** 2))) # 5.0
# L1 范数(绝对值之和)
l1 = np.sum(np.abs(v))
print(l1) # 7
# L∞ 范数(最大值)
linf = np.max(np.abs(v))
print(linf) # 4
# 单位向量(归一化)
u = v / np.linalg.norm(v)
print(u) # [0.6 0.8]
print(np.linalg.norm(u)) # 1.0(长度为 1)
# 应用:正则化
# L2 正则化(权重衰减)惩罚权重范数,防止过拟合矩阵运算
矩阵运算:神经网络的基本计算单元:
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 逐元素运算
print(A + B) # [[6 8],[10 12]]
print(A * B) # [[5 12],[21 32]](逐元素乘)
# 矩阵乘法(关键!)
# (2x2) @ (2x2) = (2x2)
C = A @ B
# [[1*5+2*7, 1*6+2*8],
# [3*5+4*7, 3*6+4*8]]
print(C) # [[19 22],[43 50]]
# 形状要求:A(m,n) @ B(n,p) = C(m,p)
# 内维度必须相等
# 神经网络层:输入 x(3,) 权重 W(4,3) 偏置 b(4,)
x = np.array([1.0, 2.0, 3.0])
W = np.random.randn(4, 3)
b = np.random.randn(4)
output = W @ x + b
print(output.shape) # (4,)
# 批量矩阵乘:X(32,3) @ W(3,4) = (32,4)
X = np.random.randn(32, 3)
outputs = X @ W.T + b # 注意转置
print(outputs.shape) # (32, 4)A @ B 是矩阵乘法,A * B 是逐元素乘法,千万别混淆。矩阵的转置
矩阵的转置(Transpose):交换行列:
import numpy as np
A = np.array([[1, 2, 3],
[4, 5, 6]])
# 转置 (2x3) -> (3x2)
print(A.T)
# [[1 4]
# [2 5]
# [3 6]]
# 手动理解
# A[i][j] -> A.T[j][i]
# 性质
# (A.T).T = A
# (A @ B).T = B.T @ A.T
# 应用 1:权重转置
W = np.random.randn(3, 4) # 4 个特征 -> 3 个神经元
x = np.random.randn(4)
y = W @ x # (3,)
# 应用 2:批量数据
X = np.random.randn(100, 5) # 100 样本 × 5 特征
# 需要 5x3 权重时
W = np.random.randn(5, 3)
out = X @ W # (100, 3) 直接批量计算
# 应用 3:协方差矩阵计算
Xc = X - X.mean(axis=0)
cov = Xc.T @ Xc / (len(X) - 1)
print(cov.shape) # (5, 5)W @ x 与 x.T @ W.T 常需要相互转换。特殊矩阵
特殊矩阵:单位矩阵、对角矩阵、对称矩阵:
import numpy as np
# 单位矩阵 I(对角线为 1,其余 0)
I = np.eye(3)
print(I)
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# 性质:A @ I = A(矩阵乘单位阵不变)
# 对角矩阵
D = np.diag([1, 2, 3])
print(D)
# [[1 0 0]
# [0 2 0]
# [0 0 3]]
# 取对角线
print(np.diag(D)) # [1 2 3]
# 对称矩阵(A = A.T)
S = np.array([[1, 2], [2, 3]])
print(S == S.T) # 全 True
# 上三角/下三角
U = np.triu([[1,2,3],[4,5,6],[7,8,9]])
L = np.tril([[1,2,3],[4,5,6],[7,8,9]])
# 应用:协方差矩阵一定是对称矩阵
# 单位矩阵用于逆矩阵计算:A @ A.inv = IA·I = A。线性方程组
线性方程组:求解 Ax = b:
import numpy as np
# 方程组:
# 2x + y = 5
# x - y = 1
A = np.array([[2, 1],
[1, -1]])
b = np.array([5, 1])
# 求解
x = np.linalg.solve(A, b)
print(x) # [2. 1.](x=2, y=1)
# 验证
print(A @ x) # [5. 1.] == b ✓
# 逆矩阵方法(理论上等价)
A_inv = np.linalg.inv(A)
x2 = A_inv @ b
print(x2) # [2. 1.]
# 检查条件数(数值稳定性)
print(np.linalg.cond(A))
# 最小二乘(数据多于未知数时)
# 拟合直线 y = ax + b
points = np.array([[1,2],[2,3],[3,5],[4,4]])
A = np.column_stack([points[:,0], np.ones(len(points))])
b = points[:,1]
coeff, residuals, rank, s = np.linalg.lstsq(A, b, rcond=None)
print("斜率a=", coeff[0], "截距b=", coeff[1])A^T A x = A^T b。矩阵的指纹
矩阵的指纹:特征值与特征向量:
import numpy as np
A = np.array([[2, 1], [1, 2]])
# 特征分解
eigenvalues, eigenvectors = np.linalg.eig(A)
print("特征值:", eigenvalues) # [3. 1.]
print("特征向量:\n", eigenvectors)
# 每一列是一个特征向量
# 验证:A @ v = λ * v
v = eigenvectors[:, 0]
lam = eigenvalues[0]
print(np.allclose(A @ v, lam * v)) # True
# 特征值含义:该方向上的"拉伸倍数"
# |λ| > 1 拉伸,|λ| < 1 压缩
# 应用 1:PCA 主成分分析
# 特征值最大的方向 = 方差最大的方向
# 应用 2:矩阵的秩、迹
print(np.linalg.matrix_rank(A)) # 2
print(np.trace(A)) # 4(对角线之和)
# 谱半径(最大特征值绝对值)
print(np.max(np.abs(eigenvalues))) # 3.0奇异值分解(SVD)
奇异值分解(SVD):最通用的矩阵分解:
import numpy as np
A = np.array([[1, 2, 3],
[4, 5, 6]])
# SVD: A = U Σ V^T
U, S, Vt = np.linalg.svd(A)
print("U:", U.shape) # (2, 2)
print("S:", S) # 奇异值 [9.5 0.7]
print("Vt:", Vt.shape) # (3, 3)
# 重建 A
Sigma = np.zeros((2, 3))
Sigma[:2, :2] = np.diag(S)
rebuilt = U @ Sigma @ Vt
print(np.allclose(A, rebuilt)) # True
# 低秩近似(压缩):只保留前 k 个奇异值
k = 1
A_k = U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :]
print(A_k) # 用 1 个奇异值近似原矩阵
# 奇异值大小 = 信息量
# 应用 1:PCA(SVD 实现)
X = np.random.randn(100, 5)
Xc = X - X.mean(axis=0)
U, S, Vt = np.linalg.svd(Xc)
principal = Xc @ Vt.T[:, :2] # 降到 2 维
# 应用 2:图像压缩(保留大奇异值)
# 应用 3:推荐系统(矩阵补全)向量空间与基
向量空间与基:
| 概念 | 含义 |
|---|---|
| 向量空间 | 所有向量构成的集合(满足加法和数乘封闭) |
| 线性无关 | 任一向量不能用其他向量线性表出 |
| 基(Basis) | 线性无关且能张成整个空间的向量组 |
| 维度 | 基中向量的个数 |
| 线性变换 | 保持加法和数乘的映射(如矩阵乘法) |
import numpy as np
# 检查向量是否线性相关
v1 = np.array([1, 2])
v2 = np.array([2, 4]) # v2 = 2*v1,相关
v3 = np.array([1, 3]) # 与 v1 无关
A = np.array([v1, v2]).T
print(np.linalg.matrix_rank(A)) # 1(秩=1,相关)
A2 = np.array([v1, v3]).T
print(np.linalg.matrix_rank(A2)) # 2(无关,构成基)
# 标准基
I = np.eye(2)
# e1 = [1,0], e2 = [0,1] 张成整个 2D 空间
# 用基表示向量
v = np.array([3, 5])
# v = 3*e1 + 5*e2
coeff = np.linalg.solve(I, v)
print(coeff) # [3. 5.]
# 子空间:原空间的子集(如平面中的一条线)函数与极限
函数与极限:AI 优化的数学基础:
import numpy as np
import matplotlib.pyplot as plt
# 函数:输入 -> 输出 的映射
def f(x):
return x**2 - 4*x + 3 # 抛物线
# 数值极限(趋近 0)
def limit(f, x0, eps=1e-6):
return (f(x0 + eps) - f(x0)) / eps
print(limit(f, 2)) # 约 0(x=2 处导数为 0,极值点)
# 连续函数 vs 不连续
x = np.linspace(-2, 6, 100)
y = f(x)
plt.plot(x, y)
plt.axhline(0, color='black', linewidth=0.5)
plt.show()
# 极限思想:h -> 0 时 (f(x+h)-f(x))/h 的趋近值就是导数
# 数值演示
h = 1e-5
x0 = 1.0
deriv = (f(x0 + h) - f(x0)) / h
print(deriv) # 约 -2(f'(1) = 2*1-4 = -2)导数入门
导数入门:衡量函数变化率:
import numpy as np
# 导数定义:f'(x) = lim(h->0) (f(x+h)-f(x))/h
def numerical_derivative(f, x, h=1e-6):
return (f(x + h) - f(x - h)) / (2 * h) # 中心差分
# 例:f(x) = x²
f = lambda x: x**2
print(numerical_derivative(f, 3)) # 约 6(2x=6 ✓)
# 常见函数的导数
# (x^n)' = n*x^(n-1)
# (e^x)' = e^x
# (ln x)' = 1/x
# (sin x)' = cos x
# 导数 = 切线的斜率
# 导数为 0 处 = 极值点(局部最小/最大)
# 应用:梯度下降找最小值
def gradient_descent(f, df, start, lr=0.1, steps=100):
x = start
for _ in range(steps):
x -= lr * df(x) # 沿负梯度方向走
return x
f = lambda x: (x - 3)**2 + 1 # 最小值在 x=3
df = lambda x: 2 * (x - 3) # 导数
print(gradient_descent(f, df, 0)) # 约 3.0 ✓常用求导规则
常用求导规则:
| 规则 | 公式 | 示例 |
|---|---|---|
| 常数 | (c)' = 0 | (5)' = 0 |
| 幂函数 | (xⁿ)' = nxⁿ⁻¹ | (x³)' = 3x² |
| 常数倍 | (cf)' = cf' | (3x²)' = 6x |
| 加法 | (f+g)' = f'+g' | (x²+x)' = 2x+1 |
| 乘积 | (fg)' = f'g+fg' | (x²·eˣ)' = 2xeˣ+x²eˣ |
| 链式 | (f(g(x)))' = f'(g)·g'(x) | (sin x²)' = cos(x²)·2x |
import numpy as np
# 数值验证:乘积法则
f = lambda x: x**2
g = lambda x: np.exp(x)
# (f*g)' 在 x=1
def num_deriv(h, x, eps=1e-6):
return (h(x+eps) - h(x-eps)) / (2*eps)
x = 1.0
product_rule = num_deriv(f, x)*g(x) + f(x)*num_deriv(g, x)
print(product_rule) # 数值结果
print(num_deriv(lambda t: f(t)*g(t), x)) # 直接算(应相等)
# 链式法则验证
h = lambda t: f(g(t)) # (x²) 中 x=e^x -> e^(2x)
# 链式: h' = f'(g)*g' = 2*e^x * e^x = 2e^(2x)
print(num_deriv(h, x))
print(2 * np.exp(2*x))偏导数
偏导数:多变量函数的导数:
import numpy as np
# f(x, y) = x² + 3xy + y²
def f(x, y):
return x**2 + 3*x*y + y**2
# 偏导数:对一个变量求导,其他视为常数
# ∂f/∂x = 2x + 3y
# ∂f/∂y = 3x + 2y
def partial_x(x, y, h=1e-6):
return (f(x+h, y) - f(x-h, y)) / (2*h)
def partial_y(x, y, h=1e-6):
return (f(x, y+h) - f(x, y-h)) / (2*h)
print(partial_x(1, 2)) # 2*1 + 3*2 = 8
print(partial_y(1, 2)) # 3*1 + 2*2 = 7
# 梯度 = 所有偏导数组成的向量
grad = np.array([partial_x(1, 2), partial_y(1, 2)])
print(grad) # [8. 7.]
# 几何意义:梯度指向函数上升最快的方向
# 负梯度指向下降最快的方向(梯度下降)
# 机器学习中的损失函数就是多变量函数
# L(w1, w2, ..., wn),对每个 w 求偏导得到梯度梯度
梯度(Gradient):多变量函数的"导数向量":
import numpy as np
# 梯度 = 各偏导数的向量,指向上升最快方向
def numerical_gradient(f, x, h=1e-6):
grad = np.zeros_like(x)
for i in range(len(x)):
x_plus = x.copy(); x_plus[i] += h
x_minus = x.copy(); x_minus[i] -= h
grad[i] = (f(x_plus) - f(x_minus)) / (2*h)
return grad
# 例:f(x, y) = x² + y²(碗状,最小值在原点)
f = lambda v: v[0]**2 + v[1]**2
x = np.array([3.0, 4.0])
grad = numerical_gradient(f, x)
print(grad) # [6. 8.](指向远离原点方向)
# 梯度下降:沿负梯度迭代
def gradient_descent(f, start, lr=0.1, steps=50):
x = start.copy()
for _ in range(steps):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
result = gradient_descent(f, np.array([3.0, 4.0]))
print(result) # 接近 [0, 0] ✓
# 梯度可视化
import matplotlib.pyplot as plt
xx, yy = np.meshgrid(np.linspace(-3, 3, 20), np.linspace(-3, 3, 20))
zz = xx**2 + yy**2
plt.contour(xx, yy, zz)
# 梯度箭头指向等高线最密处(山脊方向)
plt.show()链式法则
链式法则:复合函数求导(反向传播的数学核心):
import numpy as np
# 复合函数:y = f(g(h(x)))
# 例:y = sin(exp(x²))
# 数值法直接求导
def num_deriv(f, x, h=1e-6):
return (f(x+h) - f(x-h)) / (2*h)
# 链式法则分解
# 令 a = x²
# b = exp(a)
# y = sin(b)
# dy/dx = dy/db * db/da * da/dx
# = cos(b) * exp(a) * 2x
x = 0.5
a = x**2
b = np.exp(a)
y = np.sin(b)
# 链式逐项
dydb = np.cos(b)
dbda = np.exp(a)
dadx = 2 * x
dy_dx_chain = dydb * dbda * dadx
# 直接数值验证
dy_dx_num = num_deriv(lambda t: np.sin(np.exp(t**2)), x)
print(dy_dx_chain, dy_dx_num) # 应接近
# 这就是反向传播:从输出到输入逐层乘导数
# 前向: x -> a -> b -> y
# 反向: dy -> db -> da -> dx
# 误差信号从输出"反传"回每一层参数泰勒展开
泰勒展开:用多项式近似函数:
import numpy as np
# 泰勒公式:f(x) ≈ f(a) + f'(a)(x-a) + f''(a)(x-a)²/2! + ...
# 一阶近似(切线):f(x) ≈ f(a) + f'(a)(x-a)
# 这是梯度下降的直觉来源
# 例:e^x 在 x=0 处的泰勒展开
def exp_taylor(x, n):
s = 0.0
term = 1.0
for k in range(n):
s += term
term *= x / (k + 1) # x^k / k!
return s
print(np.exp(1.0)) # 2.71828
print(exp_taylor(1.0, 10)) # 2.71828...(逼近)
# 一阶近似(梯度下降依据)
# f(x) ≈ f(x0) + f'(x0)(x - x0)
# 令 x = x0 - lr*f'(x0),则 f 下降
# 二阶近似(牛顿法依据)
# f(x) ≈ f(x0) + f'(x0)Δx + 0.5*f''(x0)Δx²
# 应用:损失函数局部近似
# 优化算法(SGD/Momentum/Adam)都是在泰勒展开框架下设计
x = np.linspace(-1, 1, 100)
y_true = np.exp(x)
y_approx = 1 + x # 一阶
plt.plot(x, y_true, label="e^x")
plt.plot(x, y_approx, '--', label="1+x(一阶)")
plt.legend()
plt.show()概率入门
概率入门:量化不确定性:
import numpy as np
# 概率:事件发生的可能性 [0, 1]
# P(正面) = 0.5
# 频率估计:多次实验统计
trials = 10000
heads = np.random.binomial(1, 0.5, trials)
print(heads.mean()) # 约 0.5
# 概率公理
# P(A) ∈ [0,1]
# P(必然事件) = 1
# P(A ∪ B) = P(A) + P(B) - P(A ∩ B)
# 联合概率 P(A, B):同时发生
# 条件概率 P(A|B):B 发生下 A 的概率
# P(A|B) = P(A, B) / P(B)
# 独立事件:P(A, B) = P(A)P(B)
# 例:两次掷硬币独立
p_two_heads = 0.5 * 0.5
print(p_two_heads) # 0.25
# 实验验证独立性
r1 = np.random.binomial(1, 0.5, 10000)
r2 = np.random.binomial(1, 0.5, 10000)
joint = ((r1 == 1) & (r2 == 1)).mean()
print(joint) # 约 0.25 ✓
# 机器学习中的应用:模型输出就是概率
# P(垃圾|邮件) = 0.95 -> 判定为垃圾贝叶斯定理
贝叶斯定理:由果推因:
import numpy as np
# 贝叶斯公式:
# P(A|B) = P(B|A) * P(A) / P(B)
# 后验 = 似然 * 先验 / 证据
# 经典例子:疾病检测
# 患病率 P(病) = 0.01
# 检测准确率 P(阳|病) = 0.99
# 误报率 P(阳|健康) = 0.05
p_disease = 0.01
p_pos_given_disease = 0.99
p_pos_given_healthy = 0.05
# P(阳) = P(阳|病)P(病) + P(阳|健康)P(健康)
p_pos = (p_pos_given_disease * p_disease
+ p_pos_given_healthy * (1 - p_disease))
# 后验:检测阳性时真患病的概率
p_disease_given_pos = (p_pos_given_disease * p_disease) / p_pos
print(p_disease_given_pos) # 约 0.167!
# 惊人结论:准确率 99% 的检测,阳性时真患病率仅 16.7%
# 原因:患病率太低(先验),大量误报
# 机器学习中的贝叶斯
# 朴素贝叶斯分类器:P(类别|特征) ∝ P(特征|类别)P(类别)
# 贝叶斯更新(随着数据到来持续修正信念)
prior = 0.5 # 初始信念
for obs in [1, 1, 1]: # 三次正面
# P(H|正面) = P(正|H)*P(H) / P(正)
p_heads = 0.6 # 假设硬币有偏
likelihood = p_heads if obs == 1 else 1 - p_heads
evidence = likelihood * prior + (1 - likelihood) * (1 - prior)
prior = likelihood * prior / evidence
print(prior) # 信念不断更新离散随机变量
离散随机变量:
import numpy as np
# 离散随机变量:取有限个/可数个值
# 例:掷骰子 X ∈ {1,2,3,4,5,6}
# 概率质量函数 PMF:P(X=x)
probs = np.ones(6) / 6
print(probs) # 每个 1/6
# 采样
samples = np.random.choice([1,2,3,4,5,6], size=10000, p=probs)
print(np.mean(samples)) # 约 3.5
# 常用离散分布
# 1. 伯努利(0/1)
x = np.random.binomial(1, 0.7, 1000)
# 2. 二项分布(n 次试验成功 k 次)
k = np.random.binomial(10, 0.5, 1000)
print(k.mean()) # 约 5
# 3. 泊松分布(单位时间事件次数)
lam = 3
events = np.random.poisson(lam, 1000)
print(events.mean()) # 约 3
# 期望(加权平均)
E = np.sum(np.arange(1,7) * probs)
print(E) # 3.5
# 概率分布求和为 1
print(probs.sum()) # 1.0
# 应用:语言模型输出 token 概率分布
# 神经网络 softmax 输出就是离散概率分布连续随机变量
连续随机变量:
import numpy as np
# 连续随机变量:取值连续(如身高、时间)
# 概率密度函数 PDF:曲线下面积为概率
# 正态分布(高斯):最常用
mu, sigma = 0, 1
x = np.random.normal(mu, sigma, 10000)
print(x.mean(), x.std()) # 约 0, 1
# PDF 计算
def gaussian(x, mu, sigma):
return np.exp(-(x-mu)**2 / (2*sigma**2)) / np.sqrt(2*np.pi*sigma**2)
print(gaussian(0, 0, 1)) # 0.399(峰值)
# 68-95-99.7 规则
# P(|x-mu| < sigma) ≈ 68%
# P(|x-mu| < 2sigma) ≈ 95%
# 均匀分布
u = np.random.uniform(-1, 1, 1000)
# 指数分布(等待时间)
exp = np.random.exponential(1.0, 1000)
# 连续变量概率:区间积分
# P(a < X < b) = ∫_a^b f(x) dx
# 模拟计算
samples = np.random.normal(0, 1, 100000)
p_between = ((samples > -1) & (samples < 1)).mean()
print(p_between) # 约 0.68 ✓
# 应用:数据标准化假设数据来自正态分布
# 白噪声、初始化权重都常用正态分布期望、方差与协方差
期望、方差与协方差:
import numpy as np
data = np.array([2, 4, 4, 4, 5, 5, 7, 9])
# 期望(均值):数据中心
E = data.mean()
print(E) # 5.0
# 方差:数据离散程度
var = data.var()
print(var) # 4.0
# 标准差
std = data.std()
print(std) # 2.0
# 期望性质
# E[aX + b] = aE[X] + b
a, b = 2, 3
print(np.mean(a*data + b)) # a*5 + 3 = 13 ✓
# 方差性质
# Var[aX] = a²Var[X]
print(np.var(a*data)) # 4*4 = 16 ✓
# 协方差:两个变量的"同向变化"程度
x = np.array([1, 2, 3, 4, 5])
y = np.array([2, 4, 5, 4, 5])
cov = np.cov(x, y)[0, 1]
print(cov) # 正 = 同向
# 相关系数(归一化协方差,[-1,1])
corr = np.corrcoef(x, y)[0, 1]
print(corr)
# 协方差矩阵(多变量)
X = np.random.randn(100, 3)
cov_mat = np.cov(X.T)
print(cov_mat.shape) # (3, 3)
# 对角线 = 各变量方差,非对角线 = 协方差
# 应用:数据标准化(z-score)
z = (data - E) / std
print(z.mean(), z.std()) # 0, 1最大似然估计
最大似然估计(MLE):找最可能产生数据的参数:
import numpy as np
# 核心思想:选择使观测数据出现概率最大的参数
# 假设身高服从正态分布 N(μ, σ),用数据估计 μ
heights = np.array([170, 172, 175, 168, 180, 173, 169, 176])
# 1. 定义似然函数:P(数据 | 参数)
def log_likelihood(mu):
# 对数似然(log 后求和,方便计算)
sigma = np.std(heights)
return np.sum(-0.5 * ((heights - mu) / sigma)**2
- np.log(sigma) - 0.5*np.log(2*np.pi))
# 2. 扫描 mu,找似然最大的
mus = np.linspace(160, 190, 1000)
lls = [log_likelihood(m) for m in mus]
best_mu = mus[np.argmax(lls)]
print(best_mu) # 约 172.875(= 均值!)
# 结论:正态分布下 MLE 的 μ = 样本均值
print(heights.mean()) # 172.875 ✓
# 例 2:抛硬币估计正面概率 p
heads, trials = 7, 10
# 似然 L(p) = p^7 * (1-p)^3
ps = np.linspace(0.01, 0.99, 990)
ll = 7*np.log(ps) + 3*np.log(1-ps)
best_p = ps[np.argmax(ll)]
print(best_p) # 约 0.7 = 7/10 ✓
# 机器学习中的 MLE
# 线性回归 = 高斯噪声假设下的 MLE
# 逻辑回归 = 伯努利分布下的 MLE
# 神经网络损失(MSE/交叉熵)都能从 MLE 推导出来信息量与熵
信息量与熵:
import numpy as np
# 信息量:事件越不可能发生,信息量越大
# I(x) = -log2(P(x))
# 例:太阳升起(P≈1)信息量≈0
# 彩票中奖(P≈1e-7)信息量巨大
p_common = 0.99
p_rare = 0.01
print(-np.log2(p_common)) # 0.014(几乎无信息)
print(-np.log2(p_rare)) # 6.64(信息量大)
# 熵:分布的"平均信息量"/不确定性
# H(X) = -Σ P(x) log P(x)
def entropy(p):
p = np.array(p)
return -np.sum(p * np.log2(p))
# 确定事件:熵为 0
print(entropy([1.0, 0.0])) # 0.0
# 均匀分布:熵最大(最不确定)
print(entropy([0.5, 0.5])) # 1.0
# 偏斜分布:熵较小
print(entropy([0.9, 0.1])) # 0.47
# 三分类对比
print(entropy([1/3, 1/3, 1/3])) # 1.585(最大)
print(entropy([1.0, 0, 0])) # 0(最小)
# 应用 1:决策树用"信息增益"选特征(熵减少最多)
# 应用 2:交叉熵损失 = 预测分布 vs 真实分布的差异
# 应用 3:压缩编码(熵 = 最优编码长度下界)交叉熵与 KL 散度
交叉熵与 KL 散度:衡量分布差异:
import numpy as np
# 交叉熵:H(p, q) = -Σ p(x) log q(x)
# KL 散度:D(p||q) = Σ p(x) log(p(x)/q(x))
# 关系:H(p,q) = H(p) + D(p||q)
def cross_entropy(p, q):
p, q = np.array(p), np.array(q)
return -np.sum(p * np.log(q))
def kl(p, q):
p, q = np.array(p), np.array(q)
return np.sum(p * np.log(p / q))
# 真实分布(如真实标签:图片是猫)
p = np.array([0.0, 1.0, 0.0]) # 猫
# 预测分布
q_good = np.array([0.05, 0.9, 0.05]) # 好预测
q_bad = np.array([0.8, 0.1, 0.1]) # 差预测
print(cross_entropy(p, q_good)) # 0.105
print(cross_entropy(p, q_bad)) # 2.303
# 完美预测:交叉熵 = 0(对应 p 的熵)
print(cross_entropy(p, p)) # 0.0
# 分类损失就是交叉熵!
# PyTorch 的 CrossEntropyLoss = softmax + 交叉熵
# KL 散度:q 拟合 p 的"额外代价"
print(kl(p, q_good)) # 小
print(kl(p, q_bad)) # 大
# 性质:D(p||q) >= 0,仅 p=q 时 = 0
# KL 不对称:D(p||q) != D(q||p)
# 应用:变分自编码器 VAE 的损失含 KL 散度
# 生成模型训练 = 最小化预测分布与真实分布差异优化问题
优化问题:找最小/最大值:
import numpy as np
# 无约束优化:找 f(x) 最小值
# f(x) = x² - 6x + 10(抛物线,最小值在 x=3)
def f(x):
return x**2 - 6*x + 10
# 方法 1:解析法(求导=0)
# f'(x) = 2x - 6 = 0 -> x = 3
# 方法 2:数值搜索
xs = np.linspace(-10, 10, 10000)
best_x = xs[np.argmin([f(x) for x in xs])]
print(best_x) # 3.0
# 方法 3:梯度下降
def grad(x):
return 2*x - 6
x = 0.0
lr = 0.1
for _ in range(100):
x -= lr * grad(x)
print(x) # 3.0 ✓
# 有约束优化:满足约束下的最值
# 例:x + y = 5,最小化 x² + y²
# 拉格朗日乘子法
# L = x² + y² + λ(x + y - 5)
# 解得 x = y = 2.5
# 凸 vs 非凸
# 凸函数:只有一个全局最小(好优化)
# 非凸函数:多个局部最小(神经网络,难)
# 机器学习中的优化
# 损失函数 L(θ) 关于参数 θ 最小化
# θ* = argmin L(θ)
# 数据集越大,损失越平滑(凸性越好)
# 可视化损失面
xx, yy = np.meshgrid(np.linspace(-3,3,50), np.linspace(-3,3,50))
zz = xx**2 + yy**2 # 凸(碗形)
# 神经网络损失通常非凸(多峰)梯度下降法
梯度下降法:最核心的优化算法:
import numpy as np
# 梯度下降:沿负梯度迭代
# θ ← θ - η * ∇L(θ)
def f(x):
return x**2 + 2*x + 1 # 最小值在 x=-1
def df(x):
return 2*x + 2
def gradient_descent(start, lr, steps=100):
x = start
history = [x]
for _ in range(steps):
x -= lr * df(x)
history.append(x)
return x, history
# 学习率小:收敛慢但稳
x_small, _ = gradient_descent(3.0, 0.01, 100)
print(x_small) # 接近 -1 但慢
# 学习率合适:快速收敛
x_good, _ = gradient_descent(3.0, 0.1, 50)
print(x_good) # 约 -1 ✓
# 学习率太大:震荡甚至发散
x_big, hist = gradient_descent(3.0, 1.5, 5)
print(x_big) # 可能偏离
# 批量梯度下降(用全部数据)
# 随机梯度下降 SGD(每次一个样本)
# 小批量 Mini-batch(每次一批,实际最常用)
# 向量形式:多参数
def grad_descent_vec(f_grad, start, lr=0.1, steps=100):
theta = start.copy()
for _ in range(steps):
theta -= lr * f_grad(theta)
return theta
# 例:线性回归 y = wx + b
X = np.array([1., 2., 3., 4.])
y = np.array([2., 4., 6., 8.]) # y = 2x
def loss_grad(theta):
w, b = theta
pred = w*X + b
dw = np.mean(2*(pred - y)*X)
db = np.mean(2*(pred - y))
return np.array([dw, db])
theta = grad_descent_vec(loss_grad, np.array([0., 0.]), lr=0.01, steps=1000)
print(theta) # 约 [2.0, 0.0] ✓常用优化
常用优化算法:SGD、Momentum、Adam:
import numpy as np
# 目标:优化 f(x) = x² - 4x + 5(最小值 x=2)
def f(x): return x**2 - 4*x + 5
def df(x): return 2*x - 4
# 1. SGD(带动量版)
def sgd(start, lr=0.1, steps=100):
x = start
for _ in range(steps):
x -= lr * df(x)
return x
# 2. Momentum:利用历史梯度"惯性"
def momentum(start, lr=0.1, beta=0.9, steps=100):
x, v = start, 0
for _ in range(steps):
v = beta * v + (1 - beta) * df(x)
x -= lr * v
return x
# 3. Adam:自适应学习率(实际最常用)
def adam(start, lr=0.1, beta1=0.9, beta2=0.999, eps=1e-8, steps=100):
x, m, v = start, 0, 0
t = 0
for _ in range(steps):
t += 1
g = df(x)
m = beta1 * m + (1 - beta1) * g # 一阶矩
v = beta2 * v + (1 - beta2) * g**2 # 二阶矩
m_hat = m / (1 - beta1**t) # 偏差修正
v_hat = v / (1 - beta2**t)
x -= lr * m_hat / (np.sqrt(v_hat) + eps)
return x
print("SGD:", sgd(0))
print("Momentum:", momentum(0))
print("Adam:", adam(0)) # 都应接近 2
# 对比
# SGD : 简单,但方向噪声大
# Momentum : 加速、抑制震荡(下坡滚球)
# Adam : 自适应步长,收敛快,默认首选
# PyTorch: torch.optim.SGD / Adam
# TensorFlow: tf.keras.optimizers.Adam反向传播
反向传播:神经网络如何学习:
import numpy as np
# 一个 2-2-1 神经网络的前向与反向
# 输入 -> 隐藏 -> 输出
# 前向传播
x = np.array([1.0, 2.0])
W1 = np.array([[0.1, 0.2], [0.3, 0.4]]) # 2x2
b1 = np.array([0.0, 0.0])
W2 = np.array([[0.5, 0.6]]) # 1x2
b2 = np.array([0.0])
def relu(z): return np.maximum(0, z)
# 前向
h = relu(W1 @ x + b1) # 隐藏层
y_pred = W2 @ h + b2 # 输出
print("预测:", y_pred)
# 反向传播(手算梯度)
y_true = np.array([1.0])
loss = 0.5 * (y_pred - y_true)**2
# 输出层梯度
dy = (y_pred - y_true) # dL/dy
dW2 = dy.reshape(-1,1) * h.reshape(1,-1) # dL/dW2
db2 = dy
# 隐藏层梯度(链式)
dh = W2.T @ dy # dL/dh
dh[relu(h) <= 0] = 0 # relu 导数
dW1 = np.outer(dh, x) # dL/dW1
db1 = dh
# 参数更新(梯度下降)
lr = 0.1
W2 -= lr * dW2
W1 -= lr * dW1
b2 -= lr * db2
b1 -= lr * db1
# 用 PyTorch 自动验证(自动微分)
import torch
import torch.nn as nn
model = nn.Sequential(nn.Linear(2,2), nn.ReLU(), nn.Linear(2,1))
loss_fn = nn.MSELoss()
optim = torch.optim.SGD(model.parameters(), lr=0.1)
xt = torch.tensor([1.0, 2.0])
yt = torch.tensor([1.0])
pred = model(xt)
loss_v = loss_fn(pred, yt)
loss_v.backward() # 自动反向传播!
print("损失:", loss_v.item())图像就是矩阵
图像就是矩阵:
import numpy as np
import matplotlib.pyplot as plt
# 灰度图:一个二维矩阵,值 0-255
img = np.array([
[0, 0, 0, 255, 255, 255],
[0, 0, 0, 255, 255, 255],
[255, 255, 255, 0, 0, 0],
[255, 255, 255, 0, 0, 0],
])
# 显示
plt.imshow(img, cmap='gray')
plt.show()
# 彩色图:三维张量 (H, W, C)
# C=3: R, G, B 三个通道
rgb = np.zeros((100, 100, 3), dtype=np.uint8)
rgb[:, :, 0] = 255 # 红色通道满 -> 全红
plt.imshow(rgb)
plt.show()
# 图像操作 = 矩阵操作
import scipy.ndimage as ndimage
# 1. 翻转
flipped = img[::-1, :]
# 2. 转置(旋转 90 度效果)
rotated = img.T
# 3. 裁剪 = 切片
crop = img[1:3, 1:4]
# 4. 缩小 = 步长采样
down = img[::2, ::2]
# 5. 模糊 = 卷积(平均)
kernel = np.ones((3,3)) / 9
blurred = ndimage.convolve(img, kernel)
# 6. 边缘检测(卷积核)
sobel_x = np.array([[-1,0,1],[-2,0,2],[-1,0,1]])
edges = ndimage.convolve(img, sobel_x)
# 图像分类 = 矩阵 -> 类别
# CNN 就是学习合适的卷积核
print("图像形状:", img.shape)
print("像素值范围:", img.min(), img.max())余弦相似度做文本推荐
余弦相似度做文本推荐:
import numpy as np
# 词频向量(TF):每个词出现次数
docs = [
"机器学习 深度学习 神经网络",
"机器学习 数据 模型",
"体育 足球 比赛"
]
# 构建词表
words = set()
for d in docs:
words.update(d.split())
words = sorted(words)
print("词表:", words)
# 向量化
def vectorize(text):
v = np.zeros(len(words))
for w in text.split():
if w in words:
v[words.index(w)] += 1
return v
vectors = [vectorize(d) for d in docs]
# 余弦相似度
def cosine(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-8)
print("文档0 vs 文档1:", cosine(vectors[0], vectors[1])) # 高(都讲机器学习)
print("文档0 vs 文档2:", cosine(vectors[0], vectors[2])) # 低(不同主题)
# 查询推荐:找与查询最相似的文档
query = "机器学习 模型"
qv = vectorize(query)
scores = [(i, cosine(qv, v)) for i, v in enumerate(vectors)]
scores.sort(key=lambda x: -x[1])
print("推荐排序:", scores)
# 改进方向
# 1. TF-IDF 权重(突出关键词)
# 2. Word2Vec/GloVe 词向量
# 3. 神经网络表示(BERT 等)
# 工业实现
# from sklearn.feature_extraction.text import TfidfVectorizer
# from sklearn.metrics.pairwise import cosine_similarity
# vec = TfidfVectorizer()
# X = vec.fit_transform(docs)
# sim = cosine_similarity(X)PCA 给数据降维并可视化
PCA 给数据降维并可视化:
import numpy as np
import matplotlib.pyplot as plt
# 生成高维数据(4 维,但有结构)
np.random.seed(42)
n = 200
# 真实只有 2 个方向,其他是噪声
t = np.random.randn(n)
X = np.column_stack([
2*t + np.random.randn(n)*0.1, # 主方向 1
t + np.random.randn(n)*0.1, # 与 t 相关
np.random.randn(n)*0.5, # 噪声
np.random.randn(n)*0.5, # 噪声
])
# 手写 PCA
def pca(X, k):
# 1. 中心化
Xc = X - X.mean(axis=0)
# 2. 协方差矩阵
cov = Xc.T @ Xc / (len(X) - 1)
# 3. 特征分解
vals, vecs = np.linalg.eigh(cov)
# 4. 取最大的 k 个特征向量
idx = np.argsort(vals)[::-1][:k]
W = vecs[:, idx]
# 5. 投影
return Xc @ W, vals[idx]
X_pca, explained = pca(X, 2)
print("降维后:", X_pca.shape) # (200, 2)
print("解释方差:", explained)
# 用 sklearn 验证
from sklearn.decomposition import PCA
pca_sk = PCA(n_components=2)
X_sk = pca_sk.fit_transform(X)
print("sklearn 结果一致:", np.allclose(np.abs(X_pca), np.abs(X_sk), atol=1e-5))
# 可视化
plt.scatter(X_pca[:,0], X_pca[:,1], alpha=0.6)
plt.xlabel("PC1")
plt.ylabel("PC2")
plt.title("PCA 降维可视化")
plt.show()
# 解释方差比
print("PC1 占比:", pca_sk.explained_variance_ratio_[0])矩阵乘法实现神经网络前向传播
矩阵乘法实现神经网络前向传播:
import numpy as np
# 三层网络:输入(3) -> 隐藏(4) -> 输出(2)
# 用矩阵乘法一次性计算整批
# 批量输入 X: (batch=5, 3)
X = np.random.randn(5, 3)
# 权重
W1 = np.random.randn(3, 4) # (输入, 隐藏)
b1 = np.zeros(4)
W2 = np.random.randn(4, 2) # (隐藏, 输出)
b2 = np.zeros(2)
# 前向传播(批量)
Z1 = X @ W1 + b1 # (5, 4)
A1 = np.maximum(0, Z1) # ReLU
Z2 = A1 @ W2 + b2 # (5, 2)
# 输出(softmax 可加)
print("Z1:", Z1.shape)
print("A1:", A1.shape)
print("Z2:", Z2.shape)
# 为什么批量高效?
# 矩阵乘法 (5,3)@(3,4) 一次算 5 个样本
# 比循环 5 次 (3,)@(3,4) 快得多(BLAS 优化)
# 单样本等价验证
x0 = X[0]
z1 = W1.T @ x0 + b1 # 注意转置!
a1 = np.maximum(0, z1)
z2 = W2.T @ a1 + b2
print("批量的第0行 == 单样本:", np.allclose(Z2[0], z2))
# 学习:参数更新的梯度也是矩阵形状
# dW1: (3,4), dW2: (4,2)
# 框架自动处理维度匹配
# 用 PyTorch 一行
import torch
model = torch.nn.Sequential(
torch.nn.Linear(3, 4),
torch.nn.ReLU(),
torch.nn.Linear(4, 2),
)
out = model(torch.tensor(X, dtype=torch.float32))
print("PyTorch 输出:", out.shape)用导数找函数最小值
用导数找函数最小值:
import numpy as np
import matplotlib.pyplot as plt
# 目标:f(x) = x⁴ - 3x³ + 2 的最小值
def f(x): return x**4 - 3*x**3 + 2
def df(x): return 4*x**3 - 9*x**2
# 方法 1:求导 = 0(解析)
# 4x³ - 9x² = 0 -> x²(4x - 9) = 0 -> x=0 或 x=2.25
# 检查二阶导:f''(x) = 12x² - 18x
# f''(2.25) = 12*5.06 - 18*2.25 = 20.25 > 0 -> 极小值
# f''(0) = 0 -> 需进一步判断(拐点)
x_min_analytical = 2.25
print("解析解:", x_min_analytical, f(x_min_analytical))
# 方法 2:梯度下降(数值)
def gradient_descent(f, df, start, lr=0.01, steps=1000):
x = start
for _ in range(steps):
x -= lr * df(x)
return x
x_gd = gradient_descent(f, df, 0.5)
print("梯度下降:", x_gd, f(x_gd)) # 接近 2.25
# 方法 3:三分法/黄金分割(不用导数)
# 方法 4:scipy 优化
from scipy.optimize import minimize
res = minimize(f, x0=0.5)
print("scipy:", res.x, res.fun)
# 可视化
xs = np.linspace(-1, 3, 200)
plt.plot(xs, f(xs))
plt.scatter([x_min_analytical], [f(x_min_analytical)], color='red')
plt.xlabel("x"); plt.ylabel("f(x)")
plt.title("局部极小值 x=2.25")
plt.show()
# 注意:f(0)=2 是局部极值吗?
# 检查 f''(0)=0,x=0 是拐点不是极值
# 这说明:导数=0 不一定是最值点!手写梯度下降拟合一条直线
手写梯度下降拟合一条直线:
import numpy as np
import matplotlib.pyplot as plt
# 数据:房价 vs 面积(带噪声)
np.random.seed(42)
X = np.linspace(50, 100, 30)
y_true = 3 * X + 50 # 真实: y = 3x + 50
y = y_true + np.random.randn(30) * 20
# 模型: y = w*x + b,损失: MSE
# 梯度:
# dL/dw = mean(2*(wx+b - y)*x)
# dL/db = mean(2*(wx+b - y))
def fit_gd(X, y, lr=1e-4, epochs=1000):
w, b = 0.0, 0.0
n = len(X)
history = []
for _ in range(epochs):
pred = w * X + b
loss = np.mean((pred - y)**2)
dw = np.mean(2 * (pred - y) * X)
db = np.mean(2 * (pred - y))
w -= lr * dw
b -= lr * db
history.append(loss)
return w, b, history
w, b, hist = fit_gd(X, y)
print(f"w={w:.2f}, b={b:.2f}") # 接近 3, 50
# 预测
pred = w * X + b
print("MSE:", np.mean((pred - y)**2))
# 可视化
plt.scatter(X, y, alpha=0.5, label="数据")
plt.plot(X, pred, 'r-', label="拟合线")
plt.plot(X, y_true, 'g--', label="真实线")
plt.legend()
plt.show()
# 损失下降曲线
plt.plot(hist)
plt.xlabel("epoch")
plt.ylabel("loss")
plt.yscale('log')
plt.show()
# 注意:学习率要匹配数据量级
# 特征大 -> 学习率要小(或先标准化)可视化不同学习率的影响
可视化不同学习率的影响:
import numpy as np
import matplotlib.pyplot as plt
# 目标: f(x) = x²(最小值 0)
def f(x): return x**2
def df(x): return 2*x
def gd(start, lr, steps=50):
x = start
traj = [x]
for _ in range(steps):
x -= lr * df(x)
traj.append(x)
return traj
# 不同学习率
lrs = [0.01, 0.1, 0.9, 1.01]
colors = ['blue', 'green', 'orange', 'red']
xs = np.linspace(-1.2, 1.2, 200)
plt.plot(xs, f(xs), 'k-', alpha=0.3)
for lr, c in zip(lrs, colors):
traj = gd(1.0, lr)
final = traj[-1]
plt.plot(traj, [f(t) for t in traj], 'o-', color=c,
label=f'lr={lr} -> x={final:.2f}')
plt.xlabel("x")
plt.ylabel("f(x)")
plt.legend()
plt.show()
# 观察
# lr=0.01: 太慢,50 步还没到
# lr=0.1 : 合适,快速收敛
# lr=0.9 : 震荡但收敛(overshoot)
# lr=1.01: 发散!x 越来越大
# 经验法则
# 1. 先用 lr=0.01 或 0.001 试
# 2. 观察 loss 曲线:震荡=太大,不降=太小
# 3. 用学习率调度(衰减/余弦)提升效果
# 4. Adam 自带自适应,初始 lr=0.001 通常可用手动实现链式法则
手动实现链式法则:
import numpy as np
# 计算图:x -> a=x² -> b=3a -> y=sin(b)
# 前向:按顺序计算并缓存中间值
x = 1.0
a = x**2 # 1.0
b = 3 * a # 3.0
y = np.sin(b) # sin(3) ≈ 0.141
# 手动链式求导
# dy/db = cos(b)
dydb = np.cos(b)
# db/da = 3
dbda = 3
# da/dx = 2x
dadx = 2 * x
# 链式相乘
dydx = dydb * dbda * dadx
print("链式求导:", dydx)
# 数值验证
h = 1e-6
num = (np.sin(3*(x+h)**2) - np.sin(3*(x-h)**2)) / (2*h)
print("数值验证:", num)
# 反向传播 = 把"局部梯度"沿边回传
# 每个节点保存: 前向输出 + 局部导数
class Node:
def __init__(self, val, grad_fn=None, parents=None):
self.val = val
self.grad = 0.0
self.grad_fn = grad_fn # 计算局部梯度
self.parents = parents
# 计算图构建
x_node = Node(1.0)
a_node = Node(x_node.val**2, lambda: 2*x_node.val, [x_node])
b_node = Node(3*a_node.val, lambda: 3.0, [a_node])
y_node = Node(np.sin(b_node.val), lambda: np.cos(b_node.val), [b_node])
# 反向:y.grad = 1,逐层回传
y_node.grad = 1.0
b_node.grad = y_node.grad * y_node.grad_fn() # 1*cos(b)
a_node.grad = b_node.grad * b_node.grad_fn() # *3
x_node.grad = a_node.grad * a_node.grad_fn() # *2x
print("计算图反向:", x_node.grad) # 与 dydx 一致
# 这就是自动微分(Autograd)的原理!
# PyTorch 的 backward() 就是在图上做这件事loss.backward() 在做什么。抛硬币模拟贝叶斯更新
抛硬币模拟贝叶斯更新:
import numpy as np
import matplotlib.pyplot as plt
# 场景:未知硬币的正面概率 p
# 用观测数据持续更新对 p 的信念(Beta 分布)
# Beta 分布:参数 (alpha, beta)
# 先验 Beta(1, 1) = 均匀(无偏)
alpha, beta = 1, 1
# 模拟抛硬币(真实 p=0.7)
true_p = 0.7
n_flips = 100
results = np.random.binomial(1, true_p, n_flips)
# 贝叶斯更新:看到正面 alpha+1,反面 beta+1
# 后验 = Beta(alpha + 正面数, beta + 反面数)
def beta_pdf(x, a, b):
from scipy.special import beta as B
return x**(a-1) * (1-x)**(b-1) / B(a, b)
xs = np.linspace(0, 1, 200)
# 记录几个时间点
checkpoints = [0, 10, 50, 100]
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
for idx, cp in enumerate(checkpoints):
a = 1 + results[:cp].sum() if cp > 0 else 1
b = 1 + (cp - results[:cp].sum()) if cp > 0 else 1
ax = axes[idx//2, idx%2]
ax.plot(xs, beta_pdf(xs, a, b))
ax.axvline(true_p, color='red', ls='--', label='真实 p')
ax.set_title(f"{cp} 次后: α={a}, β={b}")
ax.legend()
plt.tight_layout()
plt.show()
# 结论:数据越多,信念越集中到真实值附近
# 这就是"贝叶斯更新":先验 -> 数据 -> 后验 -> 新先验
# 预测下一次正面概率(后验均值)
a, b = 1 + results.sum(), 1 + (n_flips - results.sum())
posterior_mean = a / (a + b)
print("后验均值:", posterior_mean) # 接近 0.7
print("频率估计:", results.mean())中心极限定理的可视化
中心极限定理的可视化:
import numpy as np
import matplotlib.pyplot as plt
# 中心极限定理:独立同分布随机变量的均值,趋近正态分布
# 不管原始分布是什么!
# 原始分布:均匀分布(远离正态)
uniform = np.random.uniform(0, 1, size=(10000, 30))
means = uniform.mean(axis=1) # 每组 30 个样本的均值
# 原始分布:指数分布(严重偏斜)
exp_data = np.random.exponential(1.0, size=(10000, 30))
exp_means = exp_data.mean(axis=1)
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
# 原始分布
axes[0,0].hist(uniform[:,0], bins=30)
axes[0,0].set_title("均匀分布(单样本)")
axes[0,1].hist(means, bins=30)
axes[0,1].set_title("30 个均匀样本的均值 → 正态!")
axes[1,0].hist(exp_data[:,0], bins=30)
axes[1,0].set_title("指数分布(单样本,严重偏斜)")
axes[1,1].hist(exp_means, bins=30)
axes[1,1].set_title("30 个指数样本的均值 → 正态!")
plt.tight_layout()
plt.show()
# 验证正态性
print("均值:", exp_means.mean(), "(接近原始均值 1.0)")
print("标准差:", exp_means.std(), "(≈ σ/√n = 1/√30)")
# 应用
# 1. 统计检验的前提(t 检验等)
# 2. 抽样调查:样本均值估计总体均值
# 3. 误差分析:很多随机误差叠加 -> 正态分布
# 4. 初始化神经网络权重用正态分布的依据朴素贝叶斯垃圾邮件分类器
朴素贝叶斯垃圾邮件分类器(手写实现):
import numpy as np
# 朴素贝叶斯:P(垃圾|词) ∝ P(词|垃圾) * P(垃圾)
# 假设词之间独立("朴素")
emails = [
("免费领取大奖 点击链接", 1), # 垃圾
("中奖通知 点击这里", 1), # 垃圾
("您的账户异常 立即处理", 1), # 垃圾
("会议通知 请准时参加", 0), # 正常
("项目周报 请查阅", 0), # 正常
("下午茶 一起参加", 0), # 正常
]
# 训练:统计词频
def train(emails):
vocab = set()
spam_words, ham_words = [], []
for text, label in emails:
words = text.split()
vocab.update(words)
(spam_words if label else ham_words).extend(words)
return vocab, spam_words, ham_words
vocab, spam, ham = train(emails)
def classify(text, vocab, spam, ham, alpha=1.0):
words = text.split()
n_spam, n_ham = len(spam), len(ham)
total = n_spam + n_ham
p_spam = n_spam / total # 先验
p_ham = n_ham / total
# 似然(拉普拉斯平滑防 0)
log_spam = np.log(p_spam)
log_ham = np.log(p_ham)
for w in words:
if w in vocab:
# P(词|垃圾) = (count + alpha) / (N + alpha*|V|)
p_w_spam = (spam.count(w) + alpha) / (n_spam + alpha*len(vocab))
p_w_ham = (ham.count(w) + alpha) / (n_ham + alpha*len(vocab))
log_spam += np.log(p_w_spam)
log_ham += np.log(p_w_ham)
return "垃圾" if log_spam > log_ham else "正常"
# 测试
print(classify("免费 点击链接 大奖", vocab, spam, ham)) # 垃圾
print(classify("会议通知 周报", vocab, spam, ham)) # 正常
# 用 sklearn 对比
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer
vec = CountVectorizer()
X = vec.fit_transform([e[0] for e in emails])
y = np.array([e[1] for e in emails])
clf = MultinomialNB()
clf.fit(X, y)
print("sklearn:", clf.predict(vec.transform(["免费 大奖 点击"])))手算交叉熵损失
手算交叉熵损失:
import numpy as np
# 场景:三分类(猫/狗/鸟),真实类别=猫
# 两个模型的预测
p_true = [0, 1, 0] # one-hot
pred_a = [0.7, 0.2, 0.1] # 模型 A 不自信
pred_b = [0.05, 0.9, 0.05] # 模型 B 更准
# 交叉熵: H = -Σ p_true * log(p_pred)
def ce(p, q):
return -np.sum(p * np.log(q))
print("模型A 损失:", ce(p_true, pred_a)) # -log(0.2) = 1.609
print("模型B 损失:", ce(p_true, pred_b)) # -log(0.9) = 0.105
# 完美预测
print("完美:", ce(p_true, [0, 1, 0])) # -log(1) = 0
# 最差(预测 0 概率给真实类)
print("最差:", ce(p_true, [0.5, 0.0, 0.5])) # 无穷大(log(0))
# 关键洞察:只有真实类别的概率影响损失!
# 因为 one-hot 中其他项为 0
# 简化:H = -log(p_pred[真实类别])
# softmax + 交叉熵
def softmax(z):
e = np.exp(z - np.max(z)) # 数值稳定
return e / e.sum()
logits_a = np.array([2.0, 1.0, 0.5]) # 未归一化
prob_a = softmax(logits_a)
print("softmax:", prob_a)
# PyTorch 的 CrossEntropyLoss = softmax + NLL
# 输入 logits,内部自动 softmax
import torch
loss_fn = torch.nn.CrossEntropyLoss()
loss = loss_fn(torch.tensor([logits_a]),
torch.tensor([1])) # 类别 1
print("PyTorch 损失:", loss.item()) # 与手算一致
# 为什么用交叉熵而不是 MSE?
# 交叉熵梯度不与 sigmoid 导数相乘(避免梯度消失)
# 分类问题默认用交叉熵可视化 KL 散度
可视化 KL 散度:
import numpy as np
import matplotlib.pyplot as plt
# KL 散度: D(p||q) = Σ p(x) log(p(x)/q(x))
# 衡量用 q 拟合 p 的代价(不对称!)
def kl(p, q):
p, q = np.array(p), np.array(q)
p = p / p.sum() # 归一化
q = q / q.sum()
return np.sum(p * np.log(p / q + 1e-10))
# 场景 1:p 为真实分布(双峰),q 为拟合
xs = np.linspace(-4, 4, 200)
p = np.exp(-xs**2) + 0.7*np.exp(-(xs-2)**2) # 双峰
p /= p.sum()
for mu in [-2, 0, 1.5]:
q = np.exp(-(xs-mu)**2)
q /= q.sum()
d = kl(p, q)
plt.plot(xs, q, label=f"q, μ={mu}, KL={d:.2f}")
plt.plot(xs, p, 'k--', linewidth=2, label="p(真实,双峰)")
plt.legend()
plt.show()
# 观察:KL 小 = 拟合好
# q 必须"覆盖" p 的所有峰才会小(D(p||q) 惩罚 q 缺失处)
# D(p||q) vs D(q||p)
# D(p||q): 期望在 p 下,惩罚 q 遗漏 p 的部分(mode covering)
# D(q||p): 期望在 q 下,惩罚 q 放在 p 空白处(mode seeking)
print("D(p||q):", kl(p, np.exp(-(xs-0.5)**2) / np.exp(-(xs-0.5)**2).sum()))
print("D(q||p):", kl(np.exp(-(xs-0.5)**2) / np.exp(-(xs-0.5)**2).sum(), p))
# 应用
# 1. 变分推断(VAE):最小化 KL 让近似后验接近真实
# 2. 蒸馏:学生网络分布逼近教师网络
# 3. 生成模型评估D(p||q) ≠ D(q||p),方向选择影响优化行为(mode covering vs seeking)。从零手写一个多元线性回归
从零手写一个多元线性回归:
import numpy as np
import matplotlib.pyplot as plt
# 数据:3 个特征预测房价
np.random.seed(42)
n = 200
X = np.random.randn(n, 3)
true_w = np.array([2.0, -1.0, 0.5])
true_b = 10.0
y = X @ true_w + true_b + np.random.randn(n) * 1.0
# 模型:y = Xw + b
# 损失:MSE = mean((Xw + b - y)²)
# 梯度:dL/dw = 2/n * X.T @ (Xw+b-y)
# dL/db = 2/n * sum(Xw+b-y)
def fit(X, y, lr=0.1, epochs=500):
n, d = X.shape
w = np.zeros(d)
b = 0.0
losses = []
for _ in range(epochs):
pred = X @ w + b
loss = np.mean((pred - y)**2)
err = pred - y
dw = 2/n * X.T @ err
db = 2/n * np.sum(err)
w -= lr * dw
b -= lr * db
losses.append(loss)
return w, b, losses
w, b, losses = fit(X, y, lr=0.1, epochs=1000)
print("学到的 w:", w.round(3)) # 接近 [2, -1, 0.5]
print("学到的 b:", b.round(3)) # 接近 10
# 评估
pred = X @ w + b
print("R²:", 1 - np.sum((y-pred)**2)/np.sum((y-y.mean())**2))
# 损失曲线
plt.plot(losses)
plt.xlabel("epoch"); plt.ylabel("MSE")
plt.yscale('log')
plt.show()
# 对比 sklearn
from sklearn.linear_model import LinearRegression
model = LinearRegression().fit(X, y)
print("sklearn w:", model.coef_.round(3))
print("sklearn b:", model.intercept_.round(3))
# 对比正规方程解(闭式解)
X_aug = np.column_stack([X, np.ones(n)])
theta = np.linalg.inv(X_aug.T @ X_aug) @ X_aug.T @ y
print("正规方程:", theta[:-1].round(3), theta[-1].round(3))从零手写一个二分类神经网络
从零手写一个二分类神经网络:
import numpy as np
# 二分类:逻辑回归等价于单神经元网络
# 但用 2 层网络演示完整流程
np.random.seed(42)
# 生成可分数据
n = 200
X = np.random.randn(n, 2)
y = ((X[:,0]**2 + X[:,1]**2) < 2).astype(float) # 圆形内=1
# 网络:2 -> 8 (ReLU) -> 1 (Sigmoid)
# 损失:二元交叉熵 BCE
# 优化:梯度下降
def sigmoid(z): return 1/(1 + np.exp(-z))
def relu(z): return np.maximum(0, z)
# 初始化
d_in, d_hidden, d_out = 2, 8, 1
W1 = np.random.randn(d_in, d_hidden) * 0.1
b1 = np.zeros(d_hidden)
W2 = np.random.randn(d_hidden, d_out) * 0.1
b2 = np.zeros(d_out)
def forward(X):
Z1 = X @ W1 + b1
A1 = relu(Z1)
Z2 = A1 @ W2 + b2
A2 = sigmoid(Z2)
return A1, A2
def train(X, y, lr=0.5, epochs=2000):
global W1, b1, W2, b2
losses = []
for _ in range(epochs):
A1, A2 = forward(X)
loss = -np.mean(y * np.log(A2+1e-8) + (1-y)*np.log(1-A2+1e-8))
# 反向传播
dZ2 = A2 - y.reshape(-1,1) # 输出梯度
dW2 = A1.T @ dZ2 / len(X)
db2 = dZ2.mean(axis=0)
dA1 = dZ2 @ W2.T
dZ1 = dA1 * (A1 > 0) # ReLU 导数
dW1 = X.T @ dZ1 / len(X)
db1 = dZ1.mean(axis=0)
# 更新
W1 -= lr*dW1; b1 -= lr*db1
W2 -= lr*dW2; b2 -= lr*db2
losses.append(loss)
return losses
losses = train(X, y)
print("最终损失:", losses[-1])
# 准确率
_, A2 = forward(X)
pred = (A2 > 0.5).astype(int).ravel()
print("准确率:", (pred == y).mean())
# 对比 sklearn
from sklearn.neural_network import MLPClassifier
clf = MLPClassifier(hidden_layer_sizes=(8,), activation='relu',
max_iter=500, random_state=42)
clf.fit(X, y)
print("sklearn:", clf.score(X, y))SGD vs Momentum vs Adam 优化器对比
SGD vs Momentum vs Adam 优化器对比:
import numpy as np
import matplotlib.pyplot as plt
# 一个病态二次函数(不同方向曲率差异大)
# 模拟深度学习中"峡谷"地形
def loss(x, y):
return x**2 + 10*y**2 # y 方向更陡
def grad(x, y):
return np.array([2*x, 20*y])
# SGD
def sgd(start, lr=0.1, steps=100):
x = start.copy(); traj=[x.copy()]
for _ in range(steps):
x -= lr * grad(*x)
traj.append(x.copy())
return np.array(traj)
# Momentum(减小震荡)
def momentum(start, lr=0.1, beta=0.9, steps=100):
x = start.copy(); v = np.zeros(2); traj=[x.copy()]
for _ in range(steps):
v = beta*v + (1-beta)*grad(*x)
x -= lr * v
traj.append(x.copy())
return np.array(traj)
# Adam(自适应每维步长)
def adam(start, lr=0.1, steps=100):
x = start.copy(); m=np.zeros(2); v=np.zeros(2)
traj=[x.copy()]; t=0
for _ in range(steps):
t += 1
g = grad(*x)
m = 0.9*m + 0.1*g
v = 0.999*v + 0.001*g**2
mh = m/(1-0.9**t); vh = v/(1-0.999**t)
x -= lr * mh/(np.sqrt(vh)+1e-8)
traj.append(x.copy())
return np.array(traj)
# 可视化
xx, yy = np.meshgrid(np.linspace(-2,2,100), np.linspace(-2,2,100))
plt.contour(xx, yy, loss(xx,yy), levels=20)
for name, traj in [("SGD", sgd(np.array([1.5,1.0]))),
("Momentum", momentum(np.array([1.5,1.0]))),
("Adam", adam(np.array([1.5,1.0])))]:
plt.plot(traj[:,0], traj[:,1], label=name, linewidth=2)
plt.legend()
plt.show()
# 观察
# SGD: 在峡谷壁来回震荡(y 方向太陡)
# Momentum: 利用惯性平滑路径
# Adam: 每维自适应步长,直达最优点
# 实际使用建议
# 1. 默认用 Adam (lr=1e-3)
# 2. 收敛慢时试 AdamW / 学习率调度
# 3. 微调预训练模型常用 SGD+Momentum
# 4. batch size 大时可配大学习率数据的形状 — 标量、向量、矩阵、张量
数据的形状 — 标量、向量、矩阵、张量(进阶总结):
import numpy as np
# 0 维:标量(单个值)
scalar = np.array(5)
print("标量:", scalar, scalar.shape, scalar.ndim)
# 1 维:向量(一串值)
vector = np.array([1, 2, 3])
print("向量:", vector.shape, vector.ndim) # (3,) 1
# 2 维:矩阵(表格)
matrix = np.array([[1, 2], [3, 4]])
print("矩阵:", matrix.shape, matrix.ndim) # (2,2) 2
# 3 维:张量(如单张彩色图 HWC)
tensor3 = np.zeros((28, 28, 3))
print("3维张量:", tensor3.shape) # (28,28,3)
# 4 维:一批数据 NHWC
tensor4 = np.zeros((32, 28, 28, 3))
print("4维张量:", tensor4.shape) # (32,28,28,3)
# 5 维:视频(帧序列)
tensor5 = np.zeros((8, 32, 28, 28, 3)) # (batch, frames, H, W, C)
# 维度操作
t = np.zeros((2, 3))
print(t.reshape(3, 2).shape) # (3,2)
print(t.T.shape) # (3,2)
print(t[:, None].shape) # (2,1,3) 加维度
print(t.squeeze().shape) # 压缩长度为1的维
print(np.expand_dims(t, 0).shape) # (1,2,3)
# 广播:不同形状相加
a = np.zeros((3, 1)) # 3x1
b = np.zeros((1, 4)) # 1x4
c = a + b # 广播为 3x4
print(c.shape) # (3, 4)
# 实际数据形状速查
# 表格数据: (样本数, 特征数)
# 图片: (批量, 高, 宽, 通道) [TF] 或 (批量, 通道, 高, 宽) [PyTorch]
# 文本: (批量, 序列长度, 词向量维度)
# 语音: (批量, 时间帧, 特征)
# PyTorch 通道在前: (N, C, H, W)
# TensorFlow 通道在后: (N, H, W, C)
# 转换: np.transpose(x, (0, 3, 1, 2))shape/ndim 检查数据维度。