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)
💡 提示
PyTorch/TensorFlow 中一切数据都是张量;shape(形状)决定运算规则。

向量运算

向量运算:逐元素计算与广播:

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
💡 提示
广播(broadcasting):不同形状的数组运算时,小数组自动扩展到大数组形状。

向量的点积

点积(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])))  # 接近 1
💡 提示
全连接层的核心就是 W @ 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 正则化(权重衰减)惩罚权重范数,防止过拟合
💡 提示
L2 范数对应欧氏距离;L1 范数产生稀疏解(特征选择)。

矩阵运算

矩阵运算:神经网络的基本计算单元:

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 = I
💡 提示
单位矩阵在数学公式中像"数字 1"一样存在:A·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
💡 提示
特征分解是 PCA、谱聚类、图神经网络的理论基础。

奇异值分解(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:推荐系统(矩阵补全)
💡 提示
SVD 是"万能的矩阵分解",PCA、推荐系统、图像压缩都基于它。

向量空间与基

向量空间与基:

概念含义
向量空间所有向量构成的集合(满足加法和数乘封闭)
线性无关任一向量不能用其他向量线性表出
基(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.]

# 子空间:原空间的子集(如平面中的一条线)
💡 提示
"向量空间"概念是理解线性变换、PCA 降维的钥匙。

函数与极限

函数与极限: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()
💡 提示
梯度下降本质是"一阶泰勒近似"下的最速下降;牛顿法用二阶(Hessian)更快但更贵。

概率入门

概率入门:量化不确定性:

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 -> 判定为垃圾
💡 提示
分类模型的输出通常是概率分布:如 [0.1, 0.9] 表示两类概率。

贝叶斯定理

贝叶斯定理:由果推因:

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
💡 提示
协方差矩阵是 PCA 的输入:它的特征向量就是主成分方向。

最大似然估计

最大似然估计(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:压缩编码(熵 = 最优编码长度下界)
💡 提示
熵 = 不确定性。信息增益 = 熵的减少量,是决策树/ID3 的核心。

交叉熵与 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 散度
# 生成模型训练 = 最小化预测分布与真实分布差异
💡 提示
交叉熵损失 = 分类模型的标准损失函数;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] ✓
💡 提示
学习率(learning rate)是调参第一步:太小收敛慢,太大不收敛。

常用优化

常用优化算法: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
💡 提示
Adam 是深度学习默认优化器;SGD+Momentum 在调优后期(微调)常更稳。

反向传播

反向传播:神经网络如何学习:

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())
💡 提示
"图像是矩阵"是计算机视觉的第一课:一切图像算法(CNN/滤波/增强)都在操作矩阵。

余弦相似度做文本推荐

余弦相似度做文本推荐:

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])
💡 提示
PCA 步骤:中心化 → 协方差 → 特征分解 → 投影。sklearn 一行搞定,但理解原理很重要。

矩阵乘法实现神经网络前向传播

矩阵乘法实现神经网络前向传播:

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 不一定是最值点!
💡 提示
导数=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 通常可用
⚠️ 注意
学习率是最重要的超参数:画 loss 曲线观察,震荡降 lr,停滞升 lr。

手动实现链式法则

手动实现链式法则:

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() 就是在图上做这件事
💡 提示
手动实现一次反向传播,就真正理解了 PyTorch 的 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 导数相乘(避免梯度消失)
# 分类问题默认用交叉熵
💡 提示
分类模型的损失 = -log(真实类别的预测概率):预测越准,损失越小。

可视化 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. 生成模型评估
💡 提示
KL 散度不对称: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))
💡 提示
线性回归三种解法:梯度下降(可扩展)、正规方程(小数据)、sklearn(工程用)。

从零手写一个二分类神经网络

从零手写一个二分类神经网络:

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 大时可配大学习率
💡 提示
优化器选择口诀:快速上手用 Adam,精细调优可换 SGD+Momentum。

数据的形状 — 标量、向量、矩阵、张量

数据的形状 — 标量、向量、矩阵、张量(进阶总结):

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))
📌 总结
掌握"形状思维"是 AI 编程的关键:随时用 shape/ndim 检查数据维度。
第 1 / 48 节