目标:建立 Python 编程能力和数学理论基础,为后续深度学习和大模型学习打下根基。
前置要求:无,零基础可开始
本阶段知识依赖图
flowchart TD
A[Python基础] --> B[数学基础]
B --> C[机器学习入门]
A --> J["Jupyter Notebook<br/>贯穿始终的工具"]
B --> B1[微积分] --> B1a[梯度下降]
B --> B2[线性代数] --> B2a[矩阵运算]
B --> B3[概率论] --> B3a[最大似然估计]
C --> C1[线性回归]
C --> C2[逻辑回归]
C --> C3[正则化]
1. Python 编程基础
1.1 Python 为何是 AI 工程师的首选语言
Python之所以成为AI/ML领域的首选语言,核心原因有三:
- 语法简洁:接近伪代码,让你专注于算法逻辑而非语法细节
- 生态丰富:NumPy、Pandas、PyTorch、HuggingFace等核心库全部基于Python
- 社区庞大:遇到问题几乎都能找到解答
1.2 环境搭建
安装 Anaconda + VS Code,配置好开发环境。
什么是 Anaconda?
Anaconda 是一个 Python 发行版,它不仅仅是Python解释器,还包含:
- conda:包管理器(类似pip,但能管理Python版本和虚拟环境)
- 预装库:NumPy、Pandas、Matplotlib等常用科学计算库
- Jupyter Notebook:交互式编程环境
什么是虚拟环境?为什么需要它?
虚拟环境是Python的"沙盒",每个项目可以有独立的依赖包版本:
生活类比:想象你有两间厨房。A厨房需要烤箱,B厨房需要空气炸锅。如果只有一间厨房,两个设备可能冲突(插座不够、位置不够)。虚拟环境就像给每个项目分配一间独立的厨房,互不干扰。
# 创建虚拟环境(创建一间新"厨房")
conda create -n llm_study python=3.11
# 激活虚拟环境(进入这间厨房)
conda activate llm_study
# 安装常用包(放入你需要的"厨具")
pip install numpy pandas matplotlib jupyter
为什么需要虚拟环境? 假设项目A需要PyTorch 2.0,项目B需要PyTorch 1.13,没有虚拟环境就会冲突——就像两个人同时要往一个水杯里倒不同品牌的果汁。
VS Code配置
- 安装Python扩展(让VS Code"看得懂"Python代码)
- 安装Jupyter扩展(在VS Code中直接运行Jupyter Notebook)
- 配置conda环境作为解释器(告诉VS Code"用哪个虚拟环境")
1.3 Python 基础语法
数据类型——AI 中最常用的 5 种
类比理解:把AI模型想象成一个工厂,数据类型就是不同形状的原材料:
# 1. 整数和浮点数——模型参数、损失值、学习率都是数值
# 类比:工厂里的"数量"和"尺寸"——最基本的数据
learning_rate = 0.001 # 浮点数(小数)
epoch = 100 # 整数
# 2. 字符串——文本数据的基本单位
# 类比:工厂里的"标签"和"说明书"
text = "大模型改变了世界" # NLP中处理的就是字符串
# 3. 列表——最常用的数据结构,对应向量/张量的概念
# 类比:工厂里的"传送带"——有序排列的一组数据
weights = [0.1, 0.2, 0.3, 0.4] # 类似一个一维张量(向量)
batch_data = [[1, 2], [3, 4], [5, 6]] # 类似一个二维张量(矩阵)
# ↑ 这就是深度学习中"一个batch的数据"的雏形
# 4. 字典——存储模型配置、JSON数据
# 类比:工厂里的"配置表"——用名字查找对应的值
model_config = {
"hidden_size": 768, # 隐藏层维度
"num_layers": 12, # Transformer层数
"vocab_size": 30522 # 词表大小
}
# 5. 布尔值——控制流程
# 类比:工厂里的"开关"——控制程序走哪条路
is_training = True
if is_training:
print("训练模式:启用Dropout") # 训练时随机丢弃一些神经元
else:
print("推理模式:关闭Dropout") # 推理时所有神经元都要工作
控制流——程序的"大脑"
# for循环——遍历数据集的核心方式
# 类比:工厂的"流水线"——一个一个地处理数据
# 注意:dataloader、model 等变量在后面深度学习章节会详细介绍,这里先看结构
for batch in dataloader: # dataloader = 数据加载器,每次给你一批数据
loss = model(batch) # 模型处理这个batch,返回损失值
loss.backward() # 计算梯度(后面会详细讲)
# if-else——条件判断
# 类比:工厂的"分拣机"——根据条件走不同的分支
if loss < 0.01:
print("模型已收敛") # 损失足够小,可以停止训练了
elif loss < 0.1:
print("继续训练") # 还需要继续优化
else:
print("模型可能有问题") # 损失太大,可能需要检查数据或模型
# 列表推导式——Python特有的简洁写法,AI代码中非常常见
squares = [x**2 for x in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 一行代码 = 一个for循环 + 一个操作,是Python优雅的体现
1.4 函数与面向对象
函数——代码复用的基本单位
类比:函数就像一台"机器"——你给它输入,它给你输出。
# 函数在AI中的典型应用:定义损失函数
def cross_entropy_loss(predictions, targets):
"""
交叉熵损失函数——分类任务中最常用的损失函数
类比:这是一个"评分器"
- predictions:模型的预测结果(模型认为答案是什么)
- targets:真实标签(正确答案是什么)
- 返回值:预测和正确答案之间的"差距"(越小越好)
"""
loss = -sum(t * np.log(p) for t, p in zip(targets, predictions))
return loss / len(targets)
# 参数默认值——配置超参数的常用方式
# 超参数 = 人为设定的参数(不是模型自己学到的)
def train_model(model, lr=0.001, epochs=10, batch_size=32):
"""
lr: 学习率——每次更新参数的"步子大小"
epochs: 训练轮数——把整个数据集看几遍
batch_size: 批次大小——每次处理多少个样本
"""
for epoch in range(epochs):
# 训练逻辑
pass
面向对象——理解PyTorch模型的基础
类比:如果函数是一台"机器",那类就是"工厂"——它包含多台机器(方法)和原材料(属性)。
# PyTorch中的模型就是一个类!
# 理解类是理解nn.Module的基础
class SimpleModel:
def __init__(self, input_size, output_size):
# __init__ 方法:初始化模型参数(类似工厂的"建厂"过程)
# 这里定义工厂里有哪些"机器"和"原材料"
self.weights = [0.1] * input_size # 权重(模型要学习的参数)
self.bias = 0 # 偏置(另一个要学习的参数)
def forward(self, x):
# forward 方法:定义前向传播(数据如何流过工厂)
# 这就是工厂的"生产流程"
result = sum(w * xi for w, xi in zip(self.weights, x)) + self.bias
return result
# 使用
model = SimpleModel(input_size=3, output_size=1)
output = model.forward([1.0, 2.0, 3.0])
关键理解:PyTorch中的nn.Module就是基于这个原理。你定义的每个神经网络都是一个类:
__init__中定义网络有哪些层(“工厂里有哪些机器”)forward中定义数据如何流过这些层(“原材料如何经过每台机器”)
# PyTorch中的写法(对比上面的SimpleModel)
import torch.nn as nn
class SimpleModel(nn.Module):
def __init__(self, input_size, output_size):
super().__init__() # 调用父类的初始化
self.linear = nn.Linear(input_size, output_size) # 定义一个线性层
def forward(self, x):
return self.linear(x) # 数据流过线性层
1.5 Jupyter Notebook
为什么 AI 工程师离不开 Jupyter?
类比:如果普通的Python脚本是"写一封信然后寄出去",那Jupyter Notebook就是"面对面聊天"——你可以看到对方(代码)的即时反应。
- 交互式编程:可以逐个cell运行代码,实时看到结果(不用等整个程序跑完)
- 可视化集成:图表直接嵌入在代码旁边(就像在笔记本上画图一样自然)
- 文档化:Markdown和代码混排,方便记录实验过程(“实验笔记"和"实验数据"放在一起)
- 快速原型:不需要创建完整项目文件,快速测试想法(“先试试这个想法行不行”)
核心操作
Shift+Enter:运行当前cell(执行当前这段代码)Esc+A:在上方插入新cell(在上面加一段新代码或笔记)Esc+B:在下方插入新cellEsc+M:转为Markdown cell(写文字笔记)Esc+DD:删除当前cell
1.6 文件操作与常用库
NumPy 基础——一切数值计算的基石
为什么需要NumPy? Python原生的列表运算很慢。NumPy用C语言实现了底层运算,速度比纯Python快10-100倍。在深度学习中,我们每秒要进行数百万次矩阵运算,没有NumPy根本不可能。
import numpy as np
# 创建数组(张量的前身)
# 类比:向量 = 一条线上的点,矩阵 = 一个表格,3D张量 = 一摞表格
arr = np.array([1, 2, 3, 4, 5]) # 一维数组 ≈ 向量(1×5)
matrix = np.array([[1, 2], [3, 4]]) # 二维数组 ≈ 矩阵(2×2)
tensor_3d = np.ones((2, 3, 4)) # 三维数组 ≈ 3D张量(2×3×4)
# 矩阵运算——神经网络前向传播的核心
# 这是整个深度学习最基础的运算,务必理解!
A = np.array([[1, 2], [3, 4]]) # 2×2 矩阵
B = np.array([[5, 6], [7, 8]]) # 2×2 矩阵
C = A @ B # 矩阵乘法,等价于np.dot(A, B)
# C[0][0] = 1*5 + 2*7 = 19
# C[0][1] = 1*6 + 2*8 = 22
# C[1][0] = 3*5 + 4*7 = 43
# C[1][1] = 3*6 + 4*8 = 50
# 结果:[[19, 22], [43, 50]]
# 这就是神经网络中 "output = input @ weight + bias" 的基本操作
# 每一层神经网络的本质就是:输入 × 权重矩阵 + 偏置
# 广播机制——理解这个,就理解了PyTorch张量运算的一大半
# 类比:你有3个苹果,你朋友也有3个苹果,你们把苹果合在一起
# 不需要写循环,NumPy自动帮你"一对一对"地加
x = np.array([[1, 2, 3]]) # shape: (1, 3)
bias = np.array([[10, 20, 30]]) # shape: (1, 3)
result = x + bias # 自动广播,shape: (1, 3)
# result = [[11, 22, 33]]
# 广播规则:如果两个数组的shape不同,NumPy会自动"扩展"较小的数组
# 更贴近实际的例子:神经网络中"矩阵 + 偏置"
# 这就是每一层神经网络的前向传播操作
h = np.random.randn(32, 512) # shape: (32, 512) — 32个样本,每个512维
b = np.random.randn(512) # shape: (512,) — 偏置向量
output = h + b # 广播:(32, 512) + (512,) → 每一行都加上同一个偏置
# PyTorch 中 nn.Linear(512, 10) 内部就是做 output = input @ weight + bias
# 其中 bias 的加法就是利用了广播机制
# 广播的边界说明(很重要!):
# 1. 形状不兼容时会报错:如 (3,) + (4,) 无法广播,因为最右边维度3≠4
# np.array([1,2,3]) + np.array([1,2,3,4]) # ValueError!
# 2. 广播是"虚拟扩展",不实际复制数据,内存效率极高
# (32,512) + (512,) 不会真的创建32份(512,)的副本
# 3. 广播方向是从右往左对齐:(3,1)+(1,4)可以→结果(3,4),但(3,)+(4,)不行
# 只有维度相同或其中一个为1时才能广播
Pandas 基础——数据处理的瑞士军刀
类比:如果NumPy是"计算器”,那Pandas就是"Excel"——它擅长处理表格数据(有列名、有索引的数据)。
在 AI 项目中,数据通常以 CSV/Excel 格式存储,Pandas 是读取、清洗、预处理数据的首选工具。
import pandas as pd
import numpy as np
# ========== 1. 读取与查看数据 ==========
df = pd.read_csv("data.csv") # 读取CSV(数据集最常见的格式)
df.head() # 查看前5行("先看一眼数据长什么样")
df.shape # 查看维度,如 (1000, 5) = 1000行5列
df.dtypes # 查看每列的数据类型
df.describe() # 统计描述(均值、标准差、最小值、最大值等)
# ========== 2. 数据选择与筛选 ==========
df['age'] # 选择单列
df[['age', 'salary']] # 选择多列
df[df['age'] > 18] # 条件筛选:"只要年龄大于18的"
df.iloc[0:10] # 按行号选择前10行
# ========== 3. 处理缺失值——AI数据预处理的常见操作 ==========
df.isnull().sum() # 查看每列有多少缺失值
df.dropna() # 删除含缺失值的行(简单粗暴)
df.fillna(df.mean()) # 用均值填充缺失值(更常用)
# ========== 4. 特征工程——从原始数据提取有用信息 ==========
# 归一化:把数据缩放到 [0, 1] 范围(很多模型需要)
df['age_normalized'] = (df['age'] - df['age'].min()) / (df['age'].max() - df['age'].min())
# 标准化:把数据转换为均值0、标准差1(深度学习常用)
df['age_standardized'] = (df['age'] - df['age'].mean()) / df['age'].std()
# ========== 5. 转换为NumPy数组——喂给模型 ==========
X = df[['age', 'salary']].values # 提取特征矩阵(NumPy数组)
y = df['label'].values # 提取标签向量
# X 和 y 就可以直接喂给 PyTorch 或 sklearn 的模型了
为什么 Pandas 对 AI 工程师重要?
- 数据加载:90% 的 AI 项目从 CSV 文件开始,Pandas 是最方便的读取工具
- 数据清洗:真实数据总有缺失值、异常值、格式不统一等问题,Pandas 提供了一站式解决方案
- 特征工程:数据的质量往往比模型更重要——“Garbage in, garbage out”
模块小结:Python 编程基础
| 你学到了什么 | 为什么重要 |
|---|---|
| Python 基础语法 | 所有 AI 代码的载体 |
| 虚拟环境管理 | 避免包版本冲突 |
| 函数与面向对象 | PyTorch 模型就是一个类 |
| NumPy 矩阵运算 | 神经网络前向传播的核心 |
| Pandas 数据处理 | 数据清洗、特征工程的首选工具 |
| Jupyter Notebook | 交互式实验和可视化 |
2. 数学基础——微积分、线性代数与概率论
2.0 为什么 AI 需要微积分?
一句话回答:神经网络的训练过程就是"求导+更新"的循环。
更详细的解释:想象你在一座山上,想要找到山谷的最低点(最优解)。你需要知道两件事:
- 哪个方向是下坡?(导数告诉你方向)
- 应该走多远?(学习率决定步幅)
当你听到"反向传播算法"时,本质就是链式法则求导——计算每个参数对最终误差的"贡献度"。当你听到"梯度下降"时,本质就是沿着导数方向更新参数——让模型的预测越来越准。
2.1 导数与求导法则
导数的直觉理解
核心类比:速度计
想象你开车:
- 你的位置随时间变化(比如从0公里开到了100公里)
- 速度就是位置对时间的导数——它告诉你"位置变化有多快"
同理,在神经网络中:
损失函数随参数变化(参数调整后,误差会变大或变小)
导数就是损失对参数的"变化率"——它告诉你"参数应该往哪个方向调,调多少"
导数 > 0:函数在递增 → 参数应该减小(往左走,减少误差)
导数 < 0:函数在递减 → 参数应该增大(往右走,减少误差)
导数 = 0:到达极值点 → 可能是最优解(误差最小的地方)
更形象的类比:山谷寻宝
想象损失函数是一座山的地形图:
- 山的高度 = 当前误差(越高误差越大)
- 你的位置 = 当前参数值
- 你的目标 = 找到山谷最低点(最小误差)
导数就像一个"坡度指示器":
- 它告诉你脚下哪个方向最陡(梯度方向)
- 它告诉你坡有多陡(梯度大小)
你每走一步都看一眼指示器,然后往最陡的下坡方向走——这就是梯度下降!
核心求导公式(必须记住)
- 常数求导:$(c)' = 0$ ——常数不变化,导数为0
- 幂函数:$(x^n)' = nx^{n-1}$ ——$x^2$ 的导数是 $2x$,$x^3$ 的导数是 $3x^2$
- 指数函数:$(e^x)' = e^x$ ——$e^x$ 是唯一"导数等于自身"的函数!
- 对数函数:$(\ln x)' = 1/x$ ——对数函数的导数是倒数
- 三角函数:$(\sin x)' = \cos x$ ——正弦的导数是余弦
为什么要记住这些? 因为神经网络的激活函数(Sigmoid、ReLU、Tanh)都是由这些基本函数组合而成的。记住基本公式,就能用链式法则推导出任何复杂函数的导数。
链式法则——反向传播的数学基础
核心公式:如果 $y = f(g(x))$,则 $\frac{dy}{dx} = f'(g(x)) \cdot g'(x)$
类比:工厂流水线的责任追溯
想象一个工厂流水线:原材料 $x$ → 工序1($g(x) = z$,加工成半成品) → 工序2($f(z) = y$,加工成成品) → 最终产品 $y$
现在最终产品有缺陷(损失L很大),你想知道是谁的责任:
- 工序2的责任:$\frac{\partial L}{\partial z}$(成品的问题有多少是工序2造成的)
- 工序1的责任:$\frac{\partial L}{\partial x} = \frac{\partial L}{\partial z} \times \frac{\partial z}{\partial x}$(把工序2的责任"传回"工序1)
这就是链式法则——把最终的误差责任一层一层往回追溯!
AI中的例子——单个神经元:
网络结构:输入 $x$ → 线性变换 $z = wx + b$ → 激活 $a = \sigma(z)$ → 损失 $L$
求 $\frac{\partial L}{\partial w}$(用于更新 $w$):
- 步骤1:L对a的导数(损失对激活值的敏感度):$\frac{\partial L}{\partial a} = 2(a - y)$(如果L是均方误差)
- 步骤2:a对z的导数(激活函数的导数):$\frac{\partial a}{\partial z} = \sigma'(z) = a(1-a)$
- 步骤3:z对w的导数(线性变换的导数):$\frac{\partial z}{\partial w} = x$
链式法则组合:
$$ \frac{\partial L}{\partial w} = \frac{\partial L}{\partial a} \cdot \frac{\partial a}{\partial z} \cdot \frac{\partial z}{\partial w} = 2(a-y) \cdot a(1-a) \cdot x $$这就是反向传播的核心!每一步都很简单,但组合起来就能计算复杂网络的梯度。
扩展到多变量:当网络有多个参数时,每个参数都有自己的"责任链":
$$ \begin{aligned} \frac{\partial L}{\partial w_1} &= \frac{\partial L}{\partial z} \cdot \frac{\partial z}{\partial w_1} \\ \frac{\partial L}{\partial w_2} &= \frac{\partial L}{\partial z} \cdot \frac{\partial z}{\partial w_2} \\ \frac{\partial L}{\partial b} &= \frac{\partial L}{\partial z} \cdot \frac{\partial z}{\partial b} \end{aligned} $$- $w_1$ 的梯度:$\frac{\partial L}{\partial w_1}$
- $w_2$ 的梯度:$\frac{\partial L}{\partial w_2}$
- $b$ 的梯度:$\frac{\partial L}{\partial b}$
所有参数同时更新,这就是梯度下降的并行性。
动手计算:反向传播完整数值示例
学了链式法则,现在用一个2层网络手算反向传播的全过程,把"前向传播→计算梯度→更新参数→验证损失下降"串起来。
网络结构:1个输入神经元 → 1个隐藏神经元 → 1个输出
x → [线性: z₁=w₁x+b₁] → [激活: a₁=σ(z₁)] → [线性: z₂=w₂a₁+b₂] → ŷ → Loss
设定:
| 参数 | 初始值 |
|---|---|
| 输入 $x$ | 1.0 |
| 真实值 $y$ | 0.0 |
| 权重 $w_1$ | 0.5 |
| 偏置 $b_1$ | 0.0 |
| 权重 $w_2$ | 0.5 |
| 偏置 $b_2$ | 0.0 |
| 学习率 $\eta$ | 1.0 |
步骤1:前向传播(从左到右,计算每一层的输出)
| 计算步骤 | 公式 | 数值 |
|---|---|---|
| 隐藏层线性变换 | $z_1 = w_1 \cdot x + b_1$ | $0.5 \times 1.0 + 0.0 = 0.5$ |
| 隐藏层激活 | $a_1 = \sigma(z_1) = \frac{1}{1+e^{-0.5}}$ | $a_1 = 0.6225$ |
| 输出层线性变换 | $z_2 = w_2 \cdot a_1 + b_2$ | $0.5 \times 0.6225 + 0.0 = 0.3112$ |
| 预测值 | $\hat{y} = z_2$ | $0.3112$ |
| 均方误差损失 | $L = (y - \hat{y})^2$ | $(0.0 - 0.3112)^2 = 0.0969$ |
步骤2:反向传播(从右到左,用链式法则计算每个参数的梯度)
计算路径:$L$ → $\hat{y}$ → $z_2$ → $w_2, b_2, a_1$ → $z_1$ → $w_1, b_1$
| 梯度 | 链式法则展开 | 计算过程 | 数值 |
|---|---|---|---|
| $\frac{\partial L}{\partial \hat{y}}$ | $\frac{\partial L}{\partial \hat{y}}$ | $-2(y - \hat{y}) = -2(0 - 0.3112)$ | $0.6225$ |
| $\frac{\partial L}{\partial z_2}$ | $\frac{\partial L}{\partial \hat{y}} \cdot 1$ | $0.6225 \times 1$(输出层无激活函数,导数=1) | $0.6225$ |
| $\frac{\partial L}{\partial w_2}$ | $\frac{\partial L}{\partial z_2} \cdot a_1$ | $0.6225 \times 0.6225$ | $0.3875$ |
| $\frac{\partial L}{\partial b_2}$ | $\frac{\partial L}{\partial z_2} \cdot 1$ | $0.6225 \times 1$ | $0.6225$ |
| $\frac{\partial L}{\partial a_1}$ | $\frac{\partial L}{\partial z_2} \cdot w_2$ | $0.6225 \times 0.5$ | $0.3112$ |
| $\frac{\partial L}{\partial z_1}$ | $\frac{\partial L}{\partial a_1} \cdot \sigma'(z_1)$ | $0.3112 \times 0.6225 \times (1-0.6225)$ | $0.0732$ |
| $\frac{\partial L}{\partial w_1}$ | $\frac{\partial L}{\partial z_1} \cdot x$ | $0.0732 \times 1.0$ | $0.0732$ |
| $\frac{\partial L}{\partial b_1}$ | $\frac{\partial L}{\partial z_1} \cdot 1$ | $0.0732 \times 1$ | $0.0732$ |
注意:$\sigma'(z_1) = \sigma(z_1) \cdot (1 - \sigma(z_1)) = 0.6225 \times 0.3775 = 0.2350$,这就是为什么2.1节强调Sigmoid导数可以直接用函数值计算。
步骤3:参数更新(梯度下降)
$$ w_{\text{new}} = w_{\text{old}} - \eta \cdot \frac{\partial L}{\partial w} $$| 参数 | 旧值 | 梯度 | 更新 | 新值 |
|---|---|---|---|---|
| $w_2$ | 0.5000 | 0.3875 | $0.5 - 1.0 \times 0.3875$ | 0.1125 |
| $b_2$ | 0.0000 | 0.6225 | $0.0 - 1.0 \times 0.6225$ | -0.6225 |
| $w_1$ | 0.5000 | 0.0732 | $0.5 - 1.0 \times 0.0732$ | 0.4268 |
| $b_1$ | 0.0000 | 0.0732 | $0.0 - 1.0 \times 0.0732$ | -0.0732 |
步骤4:验证——用新参数重新前向传播,损失应该下降
| 步骤 | 计算 | 新值 | 旧值 | 变化 |
|---|---|---|---|---|
| $z_1$ | $0.4268 \times 1.0 + (-0.0732)$ | $0.3536$ | $0.5000$ | ↓ |
| $a_1$ | $\sigma(0.3536)$ | $0.5875$ | $0.6225$ | ↓ |
| $z_2$ | $0.1125 \times 0.5875 + (-0.6225)$ | $-0.5564$ | $0.3112$ | ↓ |
| $L$ | $(0 - (-0.5564))^2$ | $0.3096$ | $0.0969$ | ↑ |
损失从 0.0969 升到了 0.3096——变大了!这是因为学习率 $\eta = 1.0$ 太大,导致参数更新"过头"了。
换小学习率 $\eta = 0.1$ 再试:
| 参数 | 旧值 | 梯度 | 新值 |
|---|---|---|---|
| $w_2$ | 0.5000 | 0.3875 | $0.5 - 0.1 \times 0.3875 = 0.4612$ |
| $b_2$ | 0.0000 | 0.6225 | $0.0 - 0.1 \times 0.6225 = -0.0623$ |
| $w_1$ | 0.5000 | 0.0732 | $0.5 - 0.1 \times 0.0732 = 0.4927$ |
| $b_1$ | 0.0000 | 0.0732 | $0.0 - 0.1 \times 0.0732 = -0.0073$ |
重新前向传播:$z_1 = 0.4927 \times 1.0 - 0.0073 = 0.4854$,$a_1 = \sigma(0.4854) = 0.6191$,$z_2 = 0.4612 \times 0.6191 - 0.0623 = 0.2233$,$L = (0 - 0.2233)^2 = 0.0499$
损失从 0.0969 → 0.0499,下降了 48.5%!学习率合适时,一次更新就能显著降低损失。
这个例子说明了什么?
- 反向传播的本质就是链式法则:从损失出发,一层一层往回计算每个参数的"责任"
- 学习率至关重要:太大会导致损失反而增大,太小收敛太慢
- 每层的梯度都会经过Sigmoid的导数:如果网络很深,多个小于1的导数连乘,梯度会越来越小——这就是"梯度消失"问题,下一节会详细讨论
2.2 激活函数求导
为什么神经网络需要激活函数?
如果没有激活函数,神经网络无论有多少层,本质上都只是一个线性变换(矩阵连乘还是矩阵)。激活函数引入非线性,让网络能够拟合任意复杂的函数。
类比:线性变换就像用直尺画线——无论画多少条直线,都画不出曲线。激活函数就像给你的笔加了"弯曲能力",让你能画出任意形状。
常用激活函数对比
| 激活函数 | 公式 | 输出范围 | 优点 | 缺点 | 典型应用 |
|---|---|---|---|---|---|
| Sigmoid | $\sigma(x) = \frac{1}{1+e^{-x}}$ | (0, 1) | 输出可解释为概率 | 梯度消失;输出非零中心 | 二分类输出层 |
| Tanh | $\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}$ | (-1, 1) | 零中心化 | 仍有梯度消失 | 隐藏层(已较少使用) |
| ReLU | $\text{ReLU}(x) = \max(0, x)$ | [0, +∞) | 计算简单;缓解梯度消失 | 死亡ReLU(负区间梯度为0) | 隐藏层(默认选择) |
| Leaky ReLU | $\max(0.01x, x)$ | (-∞, +∞) | 解决死亡ReLU问题 | 多一个超参数 | 替代ReLU |
| GELU | $x \cdot \Phi(x)$ | (-0.17, +∞) | 平滑;LLM标准选择 | 计算稍慢 | Transformer/LLM |
| Swish/SiLU | $x \cdot \sigma(x)$ | (-0.28, +∞) | 平滑;自门控 | 计算稍慢 | 现代网络 |
为什么 ReLU 成为主流?
从上表可以看出,ReLU 已经取代 Sigmoid 成为隐藏层的默认激活函数。要理解这个转变,需要从三个角度分析:
1. 正区间梯度恒为 1,不消失
Sigmoid 的导数 $\sigma'(z) = \sigma(z)(1-\sigma(z))$,最大值仅为 0.25(当 $z=0$ 时)。在网络中每经过一层 Sigmoid,梯度至少衰减为原来的 1/4。经过 10 层后,梯度衰减为 $0.25^{10} \approx 10^{-6}$——这就是梯度消失。
ReLU 的导数在正区间恒为 1:$z > 0$ 时 $\frac{d}{dz}\text{ReLU}(z) = 1$。梯度通过 ReLU 层时不会衰减,这使得训练深层网络(如 ResNet-50、100+ 层)成为可能。
2. 计算效率极高
Sigmoid 需要计算指数函数 $e^{-x}$,这在 GPU 上是相对昂贵的操作。ReLU 只需要一次比较:$\max(0, x)$,计算量几乎可以忽略。在大规模训练中(数百万次前向传播),这个差距非常显著。
3. 稀疏激活
ReLU 在 $z \leq 0$ 时输出恒为 0,这意味着任意时刻只有部分神经元被激活(输出非零)。这种稀疏性有两个好处:
- 更高效的表示:每次只有部分神经元响应输入,网络学会让不同的神经元负责不同的特征,减少了冗余
- 天然的正则化效果:部分神经元被"关闭"相当于一种自适应的 Dropout,有助于防止过拟合
ReLU 的代价——死亡 ReLU 问题
当某个神经元的输入始终为负数时,ReLU 输出恒为 0,梯度也恒为 0——这个神经元"死了",再也无法被激活。原因可能是学习率太大,把权重更新到了负值区域。
解决方案包括:
- Leaky ReLU:负区间给一个小斜率(如 $0.01x$),保证梯度始终非零
- GELU/Swish:平滑版本的 ReLU,负区间有微小但非零的输出,是当前 Transformer 和大语言模型的标准选择
面试要点:回答"ReLU 和 Sigmoid 的区别?为什么 ReLU 成为主流?“时,从梯度特性、计算效率、稀疏激活三个角度展开,再补充死亡 ReLU 问题及解决方案,就是一个完整且有深度的回答。
Sigmoid 函数及其导数——完整推导
什么是Sigmoid? 它把任意实数"压缩"到0和1之间,就像一个"开关”——输入很大时输出接近1(开),输入很小时输出接近0(关)。
$$ \sigma(x) = \frac{1}{1 + e^{-x}} $$Sigmoid 导数的完整推导(这是面试常考题):
已知:$\sigma(x) = \frac{1}{1 + e^{-x}}$
设 $u = 1 + e^{-x}$,则 $\sigma(x) = \frac{1}{u} = u^{-1}$
根据链式法则:
$$ \begin{aligned} \sigma'(x) &= \frac{d}{dx} [u^{-1}] \\ &= -u^{-2} \cdot \frac{du}{dx} \\ &= -\frac{1}{u^2} \cdot \frac{d}{dx}[1 + e^{-x}] \\ &= -\frac{1}{u^2} \cdot (-e^{-x}) \\ &= \frac{e^{-x}}{(1 + e^{-x})^2} \end{aligned} $$现在我们把它写成用 $\sigma(x)$ 表示的形式:
$$ \begin{aligned} \sigma'(x) &= \frac{e^{-x}}{(1 + e^{-x})^2} \\ &= \frac{1 + e^{-x} - 1}{(1 + e^{-x})^2} \quad \text{(把分子拆开)} \\ &= \frac{1 + e^{-x}}{(1 + e^{-x})^2} - \frac{1}{(1 + e^{-x})^2} \\ &= \frac{1}{1 + e^{-x}} - \frac{1}{(1 + e^{-x})^2} \\ &= \sigma(x) - \sigma(x)^2 \\ &= \sigma(x) \cdot (1 - \sigma(x)) \end{aligned} $$最终结果:
$$ \sigma'(x) = \sigma(x) \cdot (1 - \sigma(x)) $$这个结果为什么重要?
- 导数可以直接用函数值计算——不需要重新算指数函数!
- 只要知道 $\sigma(x)$ 的值,就能直接得到导数
- 这在反向传播中节省了大量计算
Sigmoid的问题——梯度消失:
- 当 $x$ 很大时:$\sigma(x) \approx 1$,$\sigma'(x) = 1 \cdot (1-1) = 0$ → 梯度几乎为0
- 当 $x$ 很小时:$\sigma(x) \approx 0$,$\sigma'(x) = 0 \cdot (1-0) = 0$ → 梯度几乎为0
这意味着:当输入值太大或太小时,Sigmoid的梯度接近0,参数几乎不更新,网络"学不动了"。这就是"梯度消失"问题,是深度学习早期的一大难题,后来ReLU激活函数的出现解决了这个问题。
Softmax函数
什么是Softmax? 它把一组任意实数转换为概率分布(所有值>0,且和为1)。
类比:想象一个班级的考试成绩是[85, 90, 75],Softmax把它们转换为"每个学生成绩的相对概率"——成绩越高的学生,概率越大。
Softmax 公式:$\text{softmax}(x_i) = e^{x_i} / \sum e^{x_j}$
示例,输入:[2.0, 1.0, 0.1]
$$ \begin{aligned} e^{2.0} &= 7.39, \quad e^{1.0} = 2.72, \quad e^{0.1} = 1.11 \\ \text{总和} &= 7.39 + 2.72 + 1.11 = 11.22 \\ \text{Softmax} &= [7.39/11.22, \; 2.72/11.22, \; 1.11/11.22] \\ &= [0.659, \; 0.242, \; 0.099] \\ &\approx [65.9\%, \; 24.2\%, \; 9.9\%] \end{aligned} $$三个概率之和 = 100%
为什么要用 $e^x$ 而不是直接用 $x$?
- $e^x > 0$ 保证所有输出都是正数(概率不能为负)
- $e^x$ 是单调递增的(输入越大,输出越大——保持大小关系)
- $e^x$ 的导数是它自己(计算方便)
数值稳定性问题:如果输入值很大,比如 [1000, 1001, 1002],$e^{1000}$ 是一个巨大的数,会溢出!
解决方案:减去最大值。例如 $\text{softmax}([1000, 1001, 1002]) = \text{softmax}([-2, -1, 0])$,结果完全一样但不会溢出。PyTorch和所有深度学习框架都自动做了这个优化。
应用场景:分类任务的最后一层,输出每个类别的概率。
2.3 线性代数
向量——数据的基本表示
类比:向量就像一个"坐标"——它用一组数字来描述一个点的位置。
在AI中,一个数据样本通常表示为一个向量:
- 一张 $28 \times 28$ 的灰度图片 → 展平为784维向量(每个像素是一个维度)
- 一个句子的词嵌入 → 每个词是一个300维向量(用300个数字描述一个词的"含义")
为什么用向量?因为计算机只能处理数字。把现实世界的东西(图片、文字、声音)转换为向量,就是"表示学习"的核心任务。
矩阵乘法——神经网络的核心运算
矩阵乘法到底在做什么?
直觉1:矩阵是一组"变换规则"
想象你有一个2D图形(比如一个三角形)。矩阵乘法就是对这个图形做"变换"——旋转、缩放、剪切。
不同的矩阵 = 不同的变换。神经网络的权重矩阵 = 一种"语义变换"——把输入空间映射到输出空间。
直觉2:矩阵乘法是"批量线性组合"
$$ \begin{bmatrix} \text{output}_1 \\ \text{output}_2 \end{bmatrix} = \begin{bmatrix} w_{11} & w_{12} & w_{13} \\ w_{21} & w_{22} & w_{23} \end{bmatrix} \begin{bmatrix} \text{input}_1 \\ \text{input}_2 \\ \text{input}_3 \end{bmatrix} = \begin{bmatrix} w_{11}\cdot\text{input}_1 + w_{12}\cdot\text{input}_2 + w_{13}\cdot\text{input}_3 \\ w_{21}\cdot\text{input}_1 + w_{22}\cdot\text{input}_2 + w_{23}\cdot\text{input}_3 \end{bmatrix} $$每个输出 = 所有输入的加权求和。权重矩阵中的每个元素 $w_{ij}$ 表示"第j个输入对第i个输出的影响程度"。这就是神经网络前向传播的本质!
实际例子:神经网络的前向传播本质上就是一连串的矩阵乘法:
- 输入 $x$(1×784)× 权重 $W$(784×512)+ 偏置 $b$(1×512)= 隐藏层 $h$(1×512)
- 隐藏层 $h$(1×512)× 权重 $W$(512×10)+ 偏置 $b$(1×10)= 输出 $y$(1×10)
784维输入 → 512维隐藏层 → 10维输出(10个类别的概率)。每一层都在做:$\text{output} = \text{input} \cdot W + b$
为什么GPU擅长深度学习?
矩阵乘法的本质是"大量独立的乘加运算":$C[i][j] = \sum A[i][k] \cdot B[k][j]$
这些乘加运算是完全独立的——C[0][0]和C[1][1]的计算互不影响。
- CPU:少量核心(比如8个),每个核心很强,但只能同时算8个
- GPU:大量核心(比如几千个),每个核心较弱,但能同时算几千个
矩阵乘法正好适合GPU的"人海战术"!这就是为什么深度学习离不开GPU。
矩阵转置与逆矩阵——后续推导的基础
在后面的正规方程 $w = (X^TX)^{-1} \cdot X^Ty$ 中,你会看到两种矩阵运算:转置和逆矩阵。这里提前介绍。
矩阵转置:行列互换,$A^T$ 的第 $i$ 行第 $j$ 列 = $A$ 的第 $j$ 行第 $i$ 列。
$$ A = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}, \quad A^T = \begin{bmatrix} 1 & 3 \\ 2 & 4 \end{bmatrix} $$类比:把矩阵"翻转"一下——行变成列,列变成行。在正规方程中,$X^TX$ 的作用是把数据矩阵变成一个方阵(行数=列数),这样才能求逆。
逆矩阵:对于方阵 $A$,如果存在 $A^{-1}$ 使得 $A^{-1} \cdot A = I$($I$ 是单位矩阵,对角线为1其余为0),则 $A^{-1}$ 是 $A$ 的逆矩阵。
类比:逆矩阵就像"除法"——$A^{-1} \cdot A = I$ 就像 $\frac{1}{A} \cdot A = 1$。求解正规方程时,$(X^TX)^{-1}$ 的作用就是"除以" $X^TX$。
注意:不是所有矩阵都有逆矩阵(就像0没有倒数一样)。当矩阵行列式为0时,逆矩阵不存在。
特征值与特征向量——理解PCA降维
什么是特征值和特征向量?
类比:镜子的方向
想象你拿着一面镜子(矩阵A),向它照一束光(向量v):
- 大多数方向的光:照进去后方向会改变
- 特殊方向的光:照进去后方向不变,只是被拉伸或压缩
这个"特殊方向"就是特征向量,拉伸/压缩的比例就是特征值。
数学定义:对于矩阵A,如果 $A\mathbf{v} = \lambda\mathbf{v}$,则:
- $\mathbf{v}$ 是特征向量(方向不变的"特殊方向")
- $\lambda$ 是特征值(拉伸/压缩的比例)
示例:
$$ A = \begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix} $$求解过程:从 $A\mathbf{v} = \lambda\mathbf{v}$ 推导特征方程
$$ A\mathbf{v} = \lambda\mathbf{v} \implies (A - \lambda I)\mathbf{v} = 0 $$要有非零解 $\mathbf{v}$,必须 $\det(A - \lambda I) = 0$:
$$ \det\begin{bmatrix} 2-\lambda & 1 \\ 1 & 2-\lambda \end{bmatrix} = (2-\lambda)^2 - 1 = \lambda^2 - 4\lambda + 3 = (\lambda-1)(\lambda-3) = 0 $$解得 $\lambda_1 = 3$,$\lambda_2 = 1$。
验证:
- $\lambda = 3$:$(A - 3I)\mathbf{v} = \begin{bmatrix} -1 & 1 \\ 1 & -1 \end{bmatrix}\mathbf{v} = 0$ → $\mathbf{v} = \begin{bmatrix} 1 \\ 1 \end{bmatrix}$,$A\mathbf{v} = \begin{bmatrix} 3 \\ 3 \end{bmatrix} = 3\mathbf{v}$ ✅
- $\lambda = 1$:$(A - I)\mathbf{v} = \begin{bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix}\mathbf{v} = 0$ → $\mathbf{v} = \begin{bmatrix} 1 \\ -1 \end{bmatrix}$,$A\mathbf{v} = \begin{bmatrix} 1 \\ -1 \end{bmatrix} = 1\mathbf{v}$ ✅
为什么特征值/特征向量在AI中重要?
PCA降维的原理:
- 计算数据的协方差矩阵
- 求协方差矩阵的特征值和特征向量
- 特征值大的特征向量 = 数据变化最大的方向(“最重要的方向”)
- 只保留前k个最重要的方向,丢弃其他方向 → 数据从高维降到k维,同时保留了最多的信息
类比:你有一张3D的照片,PCA告诉你"最有信息量的拍摄角度"是什么。只从这个角度拍一张2D照片,虽然维度降低了,但信息损失最少。
SVD奇异值分解——矩阵的"DNA"
任意矩阵 A 可以分解为三个矩阵的乘积:$A = U \cdot \Sigma \cdot V^T$
其中:
- $U$:左奇异矩阵(“行空间的特征方向”)
- $\Sigma$:奇异值矩阵(对角矩阵,每个对角元素是一个奇异值)
- $V$:右奇异矩阵(“列空间的特征方向”)
动手计算:SVD 数值示例(2×2 矩阵)
$$A = \begin{bmatrix} 3 & 1 \\ 1 & 3 \end{bmatrix}$$Step 1:计算 $A^TA$ 的特征值和特征向量
$$A^TA = \begin{bmatrix} 10 & 6 \\ 6 & 10 \end{bmatrix}$$特征方程:$(10-\lambda)^2 - 36 = 0$ → $\lambda_1 = 16, \lambda_2 = 4$
奇异值:$\sigma_1 = \sqrt{16} = 4$,$\sigma_2 = \sqrt{4} = 2$
Step 2:求 $V$($A^TA$ 的特征向量)
$$V = \begin{bmatrix} 1/\sqrt{2} & 1/\sqrt{2} \\ 1/\sqrt{2} & -1/\sqrt{2} \end{bmatrix}$$Step 3:求 $U$($u_i = Av_i / \sigma_i$)
$$u_1 = \frac{1}{4}\begin{bmatrix} 3 & 1 \\ 1 & 3 \end{bmatrix}\begin{bmatrix} 1/\sqrt{2} \\ 1/\sqrt{2} \end{bmatrix} = \begin{bmatrix} 1/\sqrt{2} \\ 1/\sqrt{2} \end{bmatrix}, \quad u_2 = \begin{bmatrix} 1/\sqrt{2} \\ -1/\sqrt{2} \end{bmatrix}$$结果:$A = U\Sigma V^T = \begin{bmatrix} 1/\sqrt{2} & 1/\sqrt{2} \\ 1/\sqrt{2} & -1/\sqrt{2} \end{bmatrix} \begin{bmatrix} 4 & 0 \\ 0 & 2 \end{bmatrix} \begin{bmatrix} 1/\sqrt{2} & 1/\sqrt{2} \\ 1/\sqrt{2} & -1/\sqrt{2} \end{bmatrix}$
解读:两个奇异值 4 和 2 告诉我们这个矩阵在两个方向上的"拉伸"程度不同。如果只保留 $\sigma_1 = 4$(低秩近似),矩阵变成 $\begin{bmatrix} 2 & 2 \\ 2 & 2 \end{bmatrix}$——保留了主要模式,丢弃了细节。
SVD的直觉——信息压缩
类比:一张高清照片有1000×1000像素(100万个数字)。SVD告诉你:这100万个数字中,真正"重要"的信息可能只需要100个奇异值就能表达。
保留最大的k个奇异值,丢弃其他的:
- 用更少的数据表示原始信息(压缩)
- 保留了最重要的"模式"(去噪)
这就是为什么SVD被称为矩阵的"DNA"——它揭示了矩阵中最本质的结构。
SVD分解过程示意:
flowchart LR
A["原始矩阵 A<br/>(m×n)"] --> B["SVD 分解"]
B --> C["U (m×m)<br/>左奇异向量"]
B --> D["Σ (m×n)<br/>奇异值 σ₁≥σ₂≥...≥σₙ"]
B --> E["Vᵀ (n×n)<br/>右奇异向量"]
D --> F["截断前 k 个<br/>σ₁, σ₂, ..., σₖ"]
C --> G["低秩近似<br/>A ≈ Uₖ · Σₖ · Vₖᵀ"]
F --> G
E --> G
PCA降维的直觉:
| 阶段 | 操作 | 数据维度 | 保留信息 |
|---|---|---|---|
| 原始数据 | 100个特征 | 100维 | 100% |
| 计算协方差矩阵 | 求特征值 | — | — |
| 按特征值排序 | 选前3个主成分 | 3维 | 保留约95%方差 |
| 投影到新空间 | 数据×前3个特征向量 | 3维 | 95%信息 |
核心思想:100个特征中,前3个"最重要的方向"就能解释95%的数据变化。丢弃剩余97个特征,维度从100降到3,但只损失5%的信息。这就是PCA的威力——用更少的维度表达几乎全部的信息。
SVD在AI中的应用:
- 数据压缩:保留最大的k个奇异值,用更少的数据表示原始信息
- 推荐系统:协同过滤的核心算法(Netflix推荐电影用的就是SVD)
- PCA实现:SVD是PCA的数值稳定实现方式
SVD/PCA 的代价:SVD 计算复杂度 $O(mn^2)$($m$ 是样本数,$n$ 是特征数),大数据集计算量巨大;PCA 降维会丢失信息——丢弃的维度中可能包含对某些任务有用的信号;PCA 假设数据的主要结构是线性的,对非线性数据效果差(此时需要用 Kernel PCA 或自编码器)。
2.4 概率论基础
期望与方差
期望——数据的"重心"
$$ E[X] = \sum x \cdot P(x) $$类比:期望就像一根尺子上挂了一堆不同重量的砝码。砝码的位置 = $x$(取值),砝码的重量 = $P(x)$(概率)。期望 = 这根尺子平衡时的支点位置(重心)。
示例:骰子的期望 $= 1 \times \frac{1}{6} + 2 \times \frac{1}{6} + \cdots + 6 \times \frac{1}{6} = 3.5$。虽然骰子不会掷出3.5,但这是"平均值"的理论位置。
方差——数据的"分散程度"
$$ \text{Var}(X) = E[(X-\mu)^2] = E[X^2] - (E[X])^2 $$类比:两组学生的平均分都是80分
- A组:[79, 80, 81] → 方差很小(成绩很集中)
- B组:[50, 80, 110] → 方差很大(成绩很分散)
- 虽然平均分一样,但方差告诉我们"波动有多大"
在AI中的应用:
- BatchNorm:对每个mini-batch计算均值和方差,然后标准化数据
- 传统解释:每层输入分布变化导致训练困难(“内部协变量偏移”)
- 更准确的理解(2019年Santurkar等人提出):BatchNorm的核心作用是平滑损失曲面,使梯度变化更平稳,从而允许使用更大的学习率,加速收敛
- 简单来说:BatchNorm让损失函数的"地形"变得更平缓、更可预测,梯度下降走得更稳
- 权重初始化:初始化权重时需要控制方差
- 方差太大 → 信号爆炸(输出值越来越大)
- 方差太小 → 信号消失(输出值越来越小)
- 合适的方差 → 信号稳定传播
协方差——两个变量的"关系"
$$ \text{Cov}(X,Y) = E[(X-\mu_x)(Y-\mu_y)] $$协方差衡量两个变量的变化趋势:
- 正值:X增大时Y也增大(正相关,比如身高和体重)
- 负值:X增大时Y减小(负相关,比如温度和羽绒服销量)
- 零:X和Y没有线性关系(不一定独立!)
条件概率与贝叶斯定理
条件概率:在已知B发生的条件下,A发生的概率
$$ P(A|B) = \frac{P(A \cap B)}{P(B)} $$类比:在一个班里,$P(\text{及格}) = \text{及格人数}/\text{总人数}$。$P(\text{及格}|\text{男同学}) = \text{及格的男同学}/\text{所有男同学}$。条件概率就是"缩小范围后的概率"。
贝叶斯定理——从结果推原因
$$ P(A|B) = \frac{P(B|A) \cdot P(A)}{P(B)} $$类比:你看到地上有水(结果B),想判断是"下雨了"还是"洒水车经过"(原因A)
- $P(\text{下雨}|\text{有水}) = P(\text{有水}|\text{下雨}) \cdot P(\text{下雨}) / P(\text{有水})$
- 如果今天是晴天,$P(\text{下雨})$ 很小,所以"下雨"的可能性不大
- 如果洒水车经常经过,$P(\text{洒水车})$ 很大,所以"洒水车"的可能性更大
贝叶斯定理 = 用"先验知识"($P(A)$)+ “新证据”($P(B|A)$)更新你的判断($P(A|B)$)
动手计算:医学检测中的贝叶斯(经典反直觉案例)
这个例子被称为"假阳性悖论",是贝叶斯定理最经典的展示——它揭示了直觉判断和概率推理之间的巨大差距。
场景设定:
- 某种疾病的患病率(先验概率):$P(\text{有病}) = 1\%$,即每100人中有1人患病
- 检测的灵敏度(真阳性率):$P(\text{阳性}|\text{有病}) = 99\%$——有病的人检测为阳性
- 检测的特异度(真阴性率):$P(\text{阴性}|\text{无病}) = 99\%$——无病的人检测为阴性
- 由此得出假阳性率:$P(\text{阳性}|\text{无病}) = 1\%$
问题:一个人检测为阳性,他实际患病的概率是多少?
直觉答案:很多人会猜 99%——毕竟检测准确率是 99% 嘛。
贝叶斯计算:
$$ P(\text{有病}|\text{阳性}) = \frac{P(\text{阳性}|\text{有病}) \cdot P(\text{有病})}{P(\text{阳性})} $$其中分母 $P(\text{阳性})$ 用全概率公式展开:
$$ P(\text{阳性}) = P(\text{阳性}|\text{有病}) \cdot P(\text{有病}) + P(\text{阳性}|\text{无病}) \cdot P(\text{无病}) $$代入数字:
$$ P(\text{阳性}) = 0.99 \times 0.01 + 0.01 \times 0.99 = 0.0099 + 0.0099 = 0.0198 $$最终结果:
$$ P(\text{有病}|\text{阳性}) = \frac{0.99 \times 0.01}{0.0198} = \frac{0.0099}{0.0198} = 0.5 = 50\% $$结果只有 50%,远低于直觉的 99%!
为什么?关键在于"先验概率很低":
假设检测了 10000 人:
- 有病的人:$10000 \times 1\% = 100$ 人,其中检测为阳性的:$100 \times 99\% = 99$ 人
- 无病的人:$10000 \times 99\% = 9900$ 人,其中检测为阳性的(误诊):$9900 \times 1\% = 99$ 人
- 阳性总人数:$99 + 99 = 198$ 人
- 真正有病的比例:$99 / 198 = 50\%$
因为健康人基数远大于患病者,即使假阳性率只有 1%,误诊的绝对人数也和真正患病的人数相当。
AI 中的启示:
- 先验概率($P(A)$)的作用巨大——不能忽略"背景知识"
- 在信息检索中:即使检索算法精确率很高,如果相关文档本身占比很小,检索到的结果中仍有大量噪音
- 在垃圾邮件过滤中:需要同时考虑"正常邮件的先验比例"和"过滤器的假阳性率"
最大似然估计(MLE)——损失函数的理论基础
核心思想:选择让观测数据出现概率最大的参数。
类比:侦探推理
场景:一个硬币被抛了10次,结果是7次正面、3次反面。问题:这枚硬币正面朝上的概率p是多少?
最大似然估计的回答:选择让"7正3反"这个结果最可能出现的p值。
似然函数:$L(p) = C(10,7) \cdot p^7 \cdot (1-p)^3$
我们要求让 $L(p)$ 最大的 $p$。
取对数(方便计算):$\log L = 7\log(p) + 3\log(1-p) + \text{常数}$
对 $p$ 求导并令其为 0:$\frac{7}{p} - \frac{3}{1-p} = 0$,解得 $p = \frac{7}{10} = 0.7$
结论:最大似然估计认为 $p = 0.7$,也就是正面概率是70%。这很直觉——10次中7次正面,所以概率大概是70%。
MLE 的完整数学推导(一维高斯分布):
假设数据服从正态分布 $N(\mu, \sigma^2)$,观测到 $n$ 个数据点:$x_1, x_2, \ldots, x_n$
似然函数(所有数据点概率的乘积):
$$ L(\mu, \sigma^2) = \prod_i P(x_i | \mu, \sigma^2) = \prod_i \frac{1}{\sqrt{2\pi\sigma^2}} \cdot \exp\left(-\frac{(x_i-\mu)^2}{2\sigma^2}\right) $$取对数(把乘法变加法,方便求导):
$$ \log L = \sum_i \left[ -\frac{1}{2}\log(2\pi\sigma^2) - \frac{(x_i-\mu)^2}{2\sigma^2} \right] $$对 $\mu$ 求导并令其为0:
$$ \frac{\partial \log L}{\partial \mu} = \sum_i \frac{x_i - \mu}{\sigma^2} = 0 \implies \sum_i x_i - n\mu = 0 \implies \mu^* = \frac{1}{n}\sum_i x_i = \text{样本均值} $$结论:高斯分布的MLE估计 = 样本均值。这很直觉——“最可能的中心位置"就是所有数据点的平均值。
关键联系——为什么交叉熵损失函数是合理的:
交叉熵损失 = 负对数似然。当你最小化交叉熵时,你实际上在最大化似然——让模型的参数使得观测到的训练数据出现的概率最大。
这就是为什么交叉熵是分类任务的标准损失函数——它有坚实的概率论基础。
模块小结:微积分与概率论基础
| 你学到了什么 | 为什么重要 |
|---|---|
| 导数与链式法则 | 反向传播的数学基础 |
| Sigmoid 导数推导 | 理解梯度消失问题 |
| Softmax 函数 | 多分类的概率输出 |
| 线性代数核心概念 | 前向传播 = 矩阵乘法 |
| 特征值/SVD | PCA 降维和数据压缩 |
| 期望与方差 | 数据分布的量化描述 |
| 协方差 | 变量间关系的度量 |
| 条件概率与贝叶斯 | 从结果推原因 |
| 最大似然估计 | 损失函数的理论来源 |
3. 最优化算法
前面两节我们学了导数、链式法则和梯度的数学定义,但还没有回答一个关键问题:怎么用梯度来真正训练模型? 本节将把数学工具转化为可执行的优化算法——从最简单的梯度下降出发,讨论学习率的选择、三种梯度下降变体,以及凸优化与非凸优化的本质区别。
3.1 梯度下降——最重要的优化算法
梯度的直觉
类比:蒙眼下山
想象你被蒙上眼睛,站在一座山上,想要找到山谷的最低点:
- 你唯一能做的就是用脚感受脚下的坡度
- 每一步都往最陡的下坡方向走
- 走的步幅由学习率控制
梯度就是那个"最陡的方向”:
- 在一维中:梯度 = 导数(一个数字)
- 在多维中:梯度 = 所有偏导数组成的向量(指向最陡上升方向)
梯度方向:函数值增长最快的方向(上坡方向)。梯度下降:沿着梯度的反方向走(下坡方向)。
其中:
- $\theta$:模型参数(你所在的位置)
- $\eta$:学习率(步幅大小)
- $\nabla L(\theta)$:损失函数对参数的梯度(坡度指示器)
- 减号:因为我们想减少损失,所以往梯度的反方向走
学习率——步幅的艺术
- 学习率太大 → 步子太大,可能跨过最低点,来回震荡。就像下山时跑得太快,冲过了山谷,跑到对面山上去了。
- 学习率太小 → 步子太小,收敛极慢。就像下山时每次只挪一厘米,要走到天黑。
- 学习率刚好 → 稳定收敛到最优解。就像以合适的速度下山,稳步到达谷底。
常见策略:
- 从0.001开始(一个比较安全的起点)
- 观察loss曲线:如果震荡就减小,如果下降太慢就增大
- 高级策略:学习率调度(先大后小,就像下山时先大步走,接近谷底时小步挪)
动手计算:梯度下降完整数值示例
学了导数和链式法则,现在用一个最简单的例子手算梯度下降,看看"求导→更新"到底是怎么工作的。
目标:找到函数 $y = (x - 3)^2$ 的最小值。
这个函数的最小值显然在 $x = 3$ 处($y = 0$)。我们假装不知道答案,用梯度下降来找。
设定:初始值 $x_0 = 0$,学习率 $\eta = 0.1$
第 1 步:计算梯度
$$ \frac{dy}{dx} = 2(x - 3) = 2(0 - 3) = -6 $$更新:$x_1 = x_0 - \eta \cdot \frac{dy}{dx} = 0 - 0.1 \times (-6) = 0.6$
第 2 步:重复
$$ \frac{dy}{dx} = 2(0.6 - 3) = -4.8 $$更新:$x_2 = 0.6 - 0.1 \times (-4.8) = 1.08$
第 3 步到第 5 步:继续重复
| 步骤 | $x$ | $y = (x-3)^2$ | 梯度 $2(x-3)$ | 更新 $x \leftarrow x - 0.1 \times \text{梯度}$ |
|---|---|---|---|---|
| 0 | 0.00 | 9.00 | -6.00 | 0.60 |
| 1 | 0.60 | 5.76 | -4.80 | 1.08 |
| 2 | 1.08 | 3.69 | -3.84 | 1.46 |
| 3 | 1.46 | 2.37 | -3.07 | 1.77 |
| 4 | 1.77 | 1.51 | -2.46 | 2.02 |
| 5 | 2.02 | 0.96 | -1.97 | 2.21 |
观察规律:
- 梯度为负 → $x$ 增大(往右走,靠近最小值)
- 随着 $x$ 接近 3,梯度绝对值越来越小 → 步子越来越小(自动减速)
- 如果 $x$ 超过 3,梯度变正 → $x$ 减小(往左走,自动修正)
这就是梯度下降的"自动导航"特性——无论从哪里出发,只要学习率合适,都会逐步逼近最优解。
学习率的影响:
- $\eta = 0.1$:稳定收敛,步子适中(上表)
- $\eta = 0.5$:步子太大,可能在最优解附近来回震荡
- $\eta = 1.0$:直接发散!$x$ 越来越远离最优解($x_1 = 0 - 1 \times (-6) = 6$,$x_2 = 6 - 1 \times 6 = 0$,来回跳)
- $\eta = 0.01$:非常稳定但收敛极慢
这就是为什么学习率是深度学习中最重要的超参数之一。
三种梯度下降方法——详细对比
| 方法 | 描述 | 类比 | 优缺点 |
|---|---|---|---|
| 1. 批量梯度下降(BGD) | 每次用全部数据计算梯度 | 你有一亿个数据点,每走一步都要看一亿个"坡度指示器"取平均 | 优点:梯度方向最准确,一定能收敛到局部最优;缺点:数据量大时极慢 |
| 2. 随机梯度下降(SGD) | 每次只用1个样本计算梯度 | 你只看一个"坡度指示器"就决定方向 | 优点:快;缺点:方向不稳定 |
| 3. 小批量梯度下降(MBGD) | 每次用一小批数据(如32/64/128个样本)计算梯度 | 你听32个人的建议取平均,既不太慢也不太偏 | 优点:兼顾速度和稳定性;实际训练中最常用! |
为什么实际训练用小批量而不用全量?——问题→方案→代价分析
| 维度 | 全量梯度下降(BGD) | 小批量梯度下降(MBGD) |
|---|---|---|
| 问题 | 数据集有100万样本,每走一步都要算100万个梯度再取平均——一步要跑遍整个数据集,极慢 | 每次只用32个样本计算梯度,速度快了约3万倍 |
| 方案 | 梯度方向最精确(因为用了所有数据的平均) | 梯度方向有噪声(只用了32个样本的估计),但方向大致正确 |
| 代价 | 计算量巨大,无法在大数据集上使用 | 梯度有随机性,收敛路径会"抖动";需要调batch size |
| 额外好处 | 无 | 梯度的随机噪声反而有助于跳出局部最优(相当于自带"探索"能力) |
batch size的选择:32、64、128、256是最常见的选择。太小(如1)→ 噪声太大,训练不稳定;太大(如全量)→ 失去随机性的好处,且GPU显存可能不够。实践中通常从32开始,根据GPU显存调整。
对线性回归应用梯度下降——完整推导
线性回归模型:$\hat{y} = wx + b$
损失函数(均方误差):$L = \frac{1}{n} \sum_i (y_i - \hat{y}_i)^2 = \frac{1}{n} \sum_i (y_i - wx_i - b)^2$
目标:找到 $w$ 和 $b$,使 $L$ 最小。
对 $w$ 求偏导:
$$ \begin{aligned} \frac{\partial L}{\partial w} &= \frac{1}{n} \sum_i 2(y_i - wx_i - b)(-x_i) \\ &= -\frac{2}{n} \sum_i x_i(y_i - wx_i - b) \end{aligned} $$对 $b$ 求偏导:
$$ \begin{aligned} \frac{\partial L}{\partial b} &= \frac{1}{n} \sum_i 2(y_i - wx_i - b)(-1) \\ &= -\frac{2}{n} \sum_i (y_i - wx_i - b) \end{aligned} $$更新规则:
$$ \begin{aligned} w_{\text{new}} &= w_{\text{old}} - \eta \cdot \frac{\partial L}{\partial w} \\ b_{\text{new}} &= b_{\text{old}} - \eta \cdot \frac{\partial L}{\partial b} \end{aligned} $$重复这个过程直到收敛(损失不再明显下降)。
3.2 凸优化
什么是凸函数?
类比:碗和碗碎片
- 凸函数像一个碗——你把弹珠放进去,它总会滚到最低点。不管弹珠从哪个位置开始,最终都会到达同一个最低点(全局最优)。
- 非凸函数像一堆碗碎片——弹珠可能卡在某个凹陷处(局部最优),而不是到达真正的最低点(全局最优)。
凸优化重要因为:凸函数的局部最小值 = 全局最小值 → 梯度下降一定能找到最优解,不用担心"卡在局部最优"。
凸函数的数学定义: 对于任意两点 $x_1, x_2$ 和任意 $t \in [0,1]$:
$$ f(t \cdot x_1 + (1-t) \cdot x_2) \leq t \cdot f(x_1) + (1-t) \cdot f(x_2) $$直觉:函数图像上任意两点的连线,都在函数图像上方(或重合)。
判断方法:
- 一维:二阶导数 $f''(x) \geq 0$(开口向上)
- 多维:Hessian矩阵半正定
为什么凸优化在AI中重要?
- 线性回归的损失函数(均方误差)是凸的 → 有唯一最优解
- 逻辑回归的损失函数(交叉熵)是凸的 → 有唯一最优解
- 神经网络的损失函数是非凸的 → 只能找到局部最优
好消息:实践中,深度学习的非凸损失函数的局部最优通常也足够好。坏消息:理论上不能保证找到全局最优。实际做法:用SGD + 各种优化技巧(动量、Adam等),通常能找到好的解。
拉格朗日乘子法与KKT条件
问题:如何在有约束的情况下求最优解?
类比:在围栏内找最低点
- 无约束优化:在整个山上找最低点(随便走)
- 有约束优化:只能在围栏内找最低点(不能走出围栏)
拉格朗日乘子法的核心思想:把"有约束"问题转换为"无约束"问题。在目标函数上加一个"惩罚项"——如果你违反约束,惩罚项就会变大。
数学形式:
- 原始问题:minimize $f(x)$,subject to $g(x) = 0$
- 构造拉格朗日函数:$\mathcal{L}(x, \lambda) = f(x) + \lambda \cdot g(x)$
- $f(x)$:我们要最小化的目标
- $\lambda \cdot g(x)$:约束的"惩罚"
- $\lambda$(拉格朗日乘子):惩罚的"力度"
分别对 $x$ 和 $\lambda$ 求导并令其为0:
$$ \frac{\partial \mathcal{L}}{\partial x} = 0 \quad \text{(最优解的条件)} \qquad \frac{\partial \mathcal{L}}{\partial \lambda} = 0 \quad \text{(约束条件,即 } g(x) = 0 \text{)} $$动手计算:拉格朗日乘子法数值示例
问题:minimize $f(x, y) = x^2 + y^2$(到原点的距离平方),subject to $x + y = 4$(约束:两点之和为 4)
构造拉格朗日函数:$\mathcal{L}(x, y, \lambda) = x^2 + y^2 + \lambda(x + y - 4)$
对三个变量求导令其为 0:
$$ \frac{\partial \mathcal{L}}{\partial x} = 2x + \lambda = 0 \implies x = -\lambda/2 $$$$ \frac{\partial \mathcal{L}}{\partial y} = 2y + \lambda = 0 \implies y = -\lambda/2 $$$$ \frac{\partial \mathcal{L}}{\partial \lambda} = x + y - 4 = 0 $$代入约束:$-\lambda/2 + (-\lambda/2) = 4$ → $\lambda = -4$ → $x = 2, y = 2$
解读:在约束 $x + y = 4$ 下,到原点最近的点是 $(2, 2)$,最小距离平方为 $2^2 + 2^2 = 8$。拉格朗日乘子 $\lambda = -4$ 表示"如果约束放松 1 单位(从 4 变为 5),最优值会减少约 4"——这就是"影子价格"的含义。
在AI中的应用:SVM的对偶问题就是用拉格朗日乘子法推导的。
模块小结:最优化算法
| 你学到了什么 | 为什么重要 |
|---|---|
| 梯度下降 | 模型训练的核心算法,所有深度学习的基础 |
| 学习率调节 | 控制训练稳定性和速度,最重要的超参数 |
| BGD/SGD/MBGD选择 | 数据小(<1万)→ BGD;数据大 → MBGD(batch=32起步);在线学习 → SGD |
| 凸 vs 非凸 | 线性回归/逻辑回归是凸的→保证全局最优;神经网络是非凸的→实践足够好 |
| 学习率调参实践 | 从0.001开始,观察loss曲线:震荡→减小,下降太慢→增大 |
| 凸优化与拉格朗日 | 有约束优化→转为无约束问题,SVM对偶问题的基础 |
| 最大似然估计 | 损失函数的理论来源,交叉熵=负对数似然 |
4. 机器学习基础
前面我们建立了 Python 编程能力(§1)、数学理论基础(§2)和优化算法工具(§3),现在终于可以把它们组合起来,解决真正的机器学习问题了。本节从最简单的线性回归出发,逐步引入正则化、逻辑回归和交叉熵损失——这些是从"数学公式"走向"AI 模型"的关键桥梁。
4.1 线性回归
线性回归的直觉
类比:在散点图上画一条"最好"的线
给你一堆数据点(比如房屋面积和房价),你想找到一条直线来描述它们的关系:
$$ y = wx + b $$目标:找到 $w$(斜率)和 $b$(截距),使得这条线"最贴近"所有数据点。“最贴近” = 预测值和真实值的差距最小。
正规方程——直接求解
思路:既然我们想要"最小化误差",那就直接对损失函数求导,令导数为0,解方程。
矩阵形式:$y = Xw + e$($e$ 是误差)。损失函数:$L = \|e\|^2 = \|y - Xw\|^2$
展开:
$$ L = (y - Xw)^T (y - Xw) = y^Ty - y^TXw - w^TX^Ty + w^TX^TXw = y^Ty - 2w^TX^Ty + w^TX^TXw $$对 $w$ 求导并令其为0:
$$ \frac{\partial L}{\partial w} = -2X^Ty + 2X^TXw = 0 \implies X^TXw = X^Ty \implies w = (X^TX)^{-1} \cdot X^Ty $$这就是正规方程!直接一步算出最优解。
优点:一步到位,不需要迭代
缺点:
- 需要计算矩阵逆 $(X^TX)^{-1}$,当特征维度很高时(如10000+),计算量巨大($O(n^3)$)
- 当 $X^TX$ 不可逆时(比如特征之间有线性关系),无法求解
正规方程 vs 梯度下降——问题→方案→代价分析
| 维度 | 正规方程 | 梯度下降 |
|---|---|---|
| 问题 | 特征数=10000时,$X^TX$是10000×10000矩阵,求逆计算量 $O(10^{12})$,实践中不可接受 | 需要手动调学习率,可能收敛慢或震荡 |
| 方案 | 一步到位:$w=(X^TX)^{-1}X^Ty$,无需迭代 | 逐步迭代:每次沿梯度方向走一小步,直到收敛 |
| 代价 | 矩阵求逆 $O(n^3)$,大数据集无法使用;$X^TX$不可逆时无法求解 | 需要调学习率、迭代次数;非凸问题可能陷入局部最优 |
| 适用场景 | 特征数 < 10000,数据量适中 | 特征数大、数据量大、在线学习 |
简单记忆:数据小→正规方程(省心),数据大→梯度下降(省时)。
动手计算:正规方程完整数值示例
学了正规方程的推导,现在用一个小数据集手算 $w = (X^TX)^{-1}X^Ty$ 的全过程。
场景:3套房子,2个特征(面积、房龄),预测房价
| 样本 | 面积 $x_1$(百㎡) | 房龄 $x_2$(年) | 房价 $y$(万元) |
|---|---|---|---|
| 1 | 1 | 3 | 300 |
| 2 | 2 | 1 | 500 |
| 3 | 3 | 2 | 600 |
步骤1:构造矩阵
$$ X = \begin{bmatrix} 1 & 1 & 3 \\ 1 & 2 & 1 \\ 1 & 3 & 2 \end{bmatrix}, \quad y = \begin{bmatrix} 300 \\ 500 \\ 600 \end{bmatrix} $$注意第一列全1是偏置项(对应截距 $b$)。
步骤2:计算 $X^TX$
$$ X^TX = \begin{bmatrix} 1 & 1 & 1 \\ 1 & 2 & 3 \\ 3 & 1 & 2 \end{bmatrix} \begin{bmatrix} 1 & 1 & 3 \\ 1 & 2 & 1 \\ 1 & 3 & 2 \end{bmatrix} = \begin{bmatrix} 3 & 6 & 6 \\ 6 & 14 & 11 \\ 6 & 11 & 14 \end{bmatrix} $$手算验证:$X^TX[1][1] = 1+1+1 = 3$,$X^TX[1][2] = 1+2+3 = 6$,$X^TX[2][2] = 1+4+9 = 14$,$X^TX[2][3] = 3+2+6 = 11$
步骤3:计算 $(X^TX)^{-1}$
行列式 $\det(X^TX) = 3(14 \times 14 - 11 \times 11) - 6(6 \times 14 - 11 \times 6) + 6(6 \times 11 - 14 \times 6) = 3 \times 75 - 6 \times 18 + 6 \times (-18) = 225 - 108 - 108 = 9$
$$ (X^TX)^{-1} = \frac{1}{9} \begin{bmatrix} 75 & -18 & -18 \\ -18 & 6 & 3 \\ -18 & 3 & 6 \end{bmatrix} $$步骤4:计算 $X^Ty$
$$ X^Ty = \begin{bmatrix} 1 & 1 & 1 \\ 1 & 2 & 3 \\ 3 & 1 & 2 \end{bmatrix} \begin{bmatrix} 300 \\ 500 \\ 600 \end{bmatrix} = \begin{bmatrix} 1400 \\ 3100 \\ 2600 \end{bmatrix} $$手算验证:$X^Ty[1] = 300+500+600 = 1400$,$X^Ty[2] = 300+1000+1800 = 3100$,$X^Ty[3] = 900+500+1200 = 2600$
步骤5:计算 $w = (X^TX)^{-1} \cdot X^Ty$
$$ w = \frac{1}{9} \begin{bmatrix} 75 & -18 & -18 \\ -18 & 6 & 3 \\ -18 & 3 & 6 \end{bmatrix} \begin{bmatrix} 1400 \\ 3100 \\ 2600 \end{bmatrix} = \begin{bmatrix} b \\ w_1 \\ w_2 \end{bmatrix} $$计算过程:
- $b = (75 \times 1400 - 18 \times 3100 - 18 \times 2600) / 9 = (105000 - 55800 - 46800) / 9 = 2400 / 9 \approx 266.7$
- $w_1 = (-18 \times 1400 + 6 \times 3100 + 3 \times 2600) / 9 = (-25200 + 18600 + 7800) / 9 = 1200 / 9 \approx 133.3$
- $w_2 = (-18 \times 1400 + 3 \times 3100 + 6 \times 2600) / 9 = (-25200 + 9300 + 15600) / 9 = -300 / 9 \approx -33.3$
解读:$w_1 \approx 133.3$ 表示每增加1百㎡面积,房价增加约133万元;$w_2 \approx -33.3$ 表示房龄每增加1年,房价减少约33万元(新房更贵)。
步骤6:验证——用学到的参数预测
预测第1套房:$\hat{y} = 266.7 + 133.3 \times 1 + (-33.3) \times 3 = 266.7 + 133.3 - 99.9 = 300.1 \approx 300$ ✅
预测第2套房:$\hat{y} = 266.7 + 133.3 \times 2 + (-33.3) \times 1 = 266.7 + 266.6 - 33.3 = 500.0$ ✅
预测第3套房:$\hat{y} = 266.7 + 133.3 \times 3 + (-33.3) \times 2 = 266.7 + 399.9 - 66.6 = 600.0$ ✅
三个样本的预测值与实际值完全吻合——这是因为正规方程找到的就是最小化均方误差的精确解。
与梯度下降的对比:正规方程一步到位,不需要调学习率,不需要迭代。但当特征数=10000时,$X^TX$ 是10000×10000矩阵,求逆的计算量为 $O(10000^3) = 10^{12}$——这在实践中不可接受,必须用梯度下降。
最小二乘法
$$ L(w,b) = \frac{1}{n} \sum (y_i - wx_i - b)^2 $$“最小二乘"的名字来源:最小化"误差的平方和”。“二乘” = 平方(误差的平方),“最小” = 让这个平方和最小。
对L求导令其为0,就是正规方程。几何意义:找到一条线,使所有数据点到这条线的"垂直距离的平方和"最小。
4.2 正则化
为什么需要正则化?
类比:考试作弊 vs 真正学会
过拟合就像"死记硬背":
- 训练数据(练习题)上表现很好——因为把答案都背下来了
- 测试数据(考试题)上表现很差——因为没见过的题就不会了
正则化就像"惩罚死记硬背":
- 不仅要求模型在训练数据上表现好
- 还要求模型"尽量简单"(不要死记硬背)
- 简单的模型泛化能力更好(理解了原理就能做新题)
偏差-方差权衡(Bias-Variance Tradeoff)——正则化的理论基础
理解正则化为什么有效,需要先理解偏差和方差:
| 概念 | 类比 | 直觉 |
|---|---|---|
| 偏差(Bias) | 射手的瞄准点偏离靶心 | 模型太简单,系统性地"偏"了(欠拟合) |
| 方差(Variance) | 射手每次射击的弹孔很分散 | 模型太复杂,对训练数据的微小变化过度敏感(过拟合) |
| 总误差 | 偏差² + 方差 + 噪声 | 两者此消彼长,需要找到平衡点 |
具体例子:
- 高偏差模型(线性模型拟合非线性数据):无论用哪组训练数据,都画出一条直线——始终"偏"了,但很稳定
- 高方差模型(高阶多项式拟合少量数据):完美穿过每个训练点——但换一组数据就会画出完全不同的曲线
正则化的作用:在偏差和方差之间找平衡。增加正则化强度($\lambda$ 增大)→ 模型更简单 → 偏差增大,方差减小。减小正则化强度($\lambda$ 减小)→ 模型更复杂 → 偏差减小,方差增大。最优的 $\lambda$ 使得总误差最小。
L1正则化(Lasso)
L1正则化的损失函数:$L_{\text{total}} = L_{\text{original}} + \lambda \cdot \sum |w_i|$
L1的效果:使部分权重变为恰好0,实现特征选择。
为什么L1能使权重变为0?——几何直觉
想象一个2D空间($w_1, w_2$):
- 损失函数的等高线是椭圆形的
- L1的约束区域是菱形($|w_1| + |w_2| \leq c$)
椭圆和菱形最容易在"角点"相交。角点恰好在坐标轴上($w_1 = 0$ 或 $w_2 = 0$)→ 最优解中某些权重恰好为0 → 特征选择。
类比:L1像一把"剪刀",会直接把不重要的特征"剪掉"(权重变为0)。
数值示例:假设损失函数 $L = (w - 2)^2$,最优解 $w^* = 2$。
- 加 L1 惩罚($\lambda = 1$):$L_{\text{total}} = (w-2)^2 + |w|$。求导令其为0($w > 0$ 时):$2(w-2) + 1 = 0$,解得 $w = 1.5$。权重被"压缩"了 0.5。
- 加 L2 惩罚($\lambda = 1$):$L_{\text{total}} = (w-2)^2 + w^2$。求导令其为0:$2(w-2) + 2w = 0$,解得 $w = 1.0$。权重被"压缩"了 1.0。
- 如果 $\lambda$ 足够大,L1 可以让 $w$ 恰好变为 0,而 L2 只能让 $w$ 无限趋近 0。
L2正则化(Ridge)
L2正则化的损失函数:$L_{\text{total}} = L_{\text{original}} + \lambda \cdot \sum w_i^2$
L2的效果:使所有权重趋近于0但不等于0,防止某个特征权重过大。
为什么L2不会使权重变为0?——几何直觉
想象一个2D空间($w_1, w_2$):
- 损失函数的等高线是椭圆形的
- L2的约束区域是圆形($w_1^2 + w_2^2 \leq c$)
椭圆和圆形的交点通常不在坐标轴上 → 权重趋近于0但不会恰好为0。
类比:L2像一个"压缩弹簧",把所有权重往小的方向压,但不会压到0。
L1 vs L2 对比
| 特性 | L1 (Lasso) | L2 (Ridge) |
|---|---|---|
| 效果 | 特征选择(稀疏解) | 权重衰减(平滑解) |
| 权重会变为0? | 会(部分权重=0) | 不会(权重趋近0但不等于0) |
| 适用场景 | 特征很多,需要筛选 | 特征都有用,防止过拟合 |
| 几何形状 | 菱形约束 | 圆形约束 |
| 导数 | sign(w)(在0处不可导) | 2w(处处可导,优化更方便) |
正则化的工程取舍——问题 → 方案 → 代价
| 设计选择 | 试图解决的问题 | 方案如何起作用 | 引入的代价或折中 |
|---|---|---|---|
| L2 正则化 | 权重过大导致模型对单个特征过度敏感,泛化差 | 对 $w^2$ 惩罚,让梯度中出现持续的“回拉力”,像弹簧一样把权重压向 0 | 不能直接得到稀疏解,重要性较低的特征仍保留小权重 |
| L1 正则化 | 特征空间庞大但只有少数特征真正重要,模型解释性差 | 对 $ | w |
| 正则化强度 $\lambda$ | 模型可能只追求训练误差最小,导致过拟合 | 在损失函数中显式加入复杂度惩罚,让模型在“拟合数据”与“保持简单”之间权衡 | $\lambda$ 太大会欠拟合,太小则正则化效果不足,需要通过验证集调参 |
| 早停(Early Stopping) | 训练轮数过多会让模型记忆训练数据噪音 | 在验证损失停止下降时提前终止训练,相当于限制模型有效容量 | 需要独立验证集,且停止时机敏感,需要监控验证曲线 |
直觉总结:L2 像"弹簧",持续拉扯所有权重;L1 像"恒定摩擦",容易让不重要权重彻底停下;$\lambda$ 和早停则分别从显式约束和训练过程两个角度限制模型复杂度。
正则化的本质——奥卡姆剃刀
不加正则化:模型只追求"拟合训练数据"(最小化训练误差)
加正则化:模型同时追求"拟合训练数据" + “保持简单”(最小化训练误差 + 惩罚复杂度)
这就是奥卡姆剃刀原则:在同样能解释数据的模型中,选择最简单的那个。
为什么简单更好?
- 简单的模型更不容易"死记硬背"训练数据
- 简单的模型对新数据的泛化能力更强
- 简单的模型更容易理解和调试
4.3 逻辑回归
逻辑回归不是回归,是分类!
名字的由来:逻辑回归使用了"逻辑函数"(即Sigmoid函数),所以叫"逻辑回归"。但它实际上是一个分类算法,不是回归算法。
- 线性回归:输出连续值(如房价=150万),$y = wx + b$,输出范围是 $(-\infty, +\infty)$
- 逻辑回归:输出概率(如垃圾邮件概率=0.95),$y = \sigma(wx + b)$,输出范围是 $(0, 1)$,表示概率
核心区别:在输出层加了一个Sigmoid函数。
Sigmoid函数——将实数映射为概率
$$ \sigma(z) = \frac{1}{1 + e^{-z}} $$$$ \sigma(0) = 0.5, \quad \sigma(5) \approx 0.993, \quad \sigma(-5) \approx 0.007 $$分类规则:$\sigma(z) \geq 0.5$ → 预测为正类(类别1);$\sigma(z) < 0.5$ → 预测为负类(类别0)
交叉熵损失函数——完整推导
为什么用交叉熵而不是均方误差?
这个问题在面试中经常被问到,让我们从头推导。
推导起点:最大似然估计
假设:
- 样本i的真实标签:$y_i \in \{0, 1\}$
- 模型预测样本i为正类的概率:$p_i = \sigma(w \cdot x_i + b)$
对于单个样本 $i$:
- 若 $y_i = 1$:$P(y_i | x_i) = p_i$(预测为正类的概率)
- 若 $y_i = 0$:$P(y_i | x_i) = 1 - p_i$(预测为负类的概率)
合并为一个公式:
$$ P(y_i | x_i) = p_i^{y_i} \cdot (1 - p_i)^{(1 - y_i)} $$(当 $y_i = 1$ 时就是 $p_i$,当 $y_i = 0$ 时就是 $1 - p_i$)
整个数据集的似然:
$$ L = \prod_i P(y_i | x_i) = \prod_i \left[ p_i^{y_i} \cdot (1 - p_i)^{(1 - y_i)} \right] $$取对数(连乘变连加,方便计算):
$$ \log L = \sum_i \left[ y_i \cdot \log(p_i) + (1 - y_i) \cdot \log(1 - p_i) \right] $$最大似然 = 最大化 $\log L$;最小化负对数似然 = 最小化 $-\log L$
交叉熵损失:
$$ L_{CE} = -\frac{1}{n} \sum_i \left[ y_i \cdot \log(p_i) + (1 - y_i) \cdot \log(1 - p_i) \right] $$这就是交叉熵损失函数的由来——它不是凭空设计的,而是从最大似然估计自然推导出来的!
为什么不用均方误差(MSE)?
MSE损失:$L_{MSE} = \frac{1}{n} \sum_i (y_i - p_i)^2$
问题:当Sigmoid的输入 $z$ 很大或很小时,Sigmoid的梯度接近0。MSE的梯度中包含 $\sigma'(z)$,所以MSE的梯度也会接近0 → 参数更新极慢 → 训练不动!
交叉熵的梯度:$\frac{\partial L_{CE}}{\partial w} = \frac{1}{n} \sum_i (p_i - y_i) \cdot x_i$
这个梯度中不包含 $\sigma'(z)$!→ 梯度大小只和预测误差 $(p_i - y_i)$ 成正比 → 预测越差,梯度越大,更新越快 → 训练更高效!
这就是为什么分类任务用交叉熵而不是MSE——交叉熵的梯度形式更简洁,避免了Sigmoid梯度消失的问题。
动手计算:交叉熵损失数值示例
学了交叉熵的推导,现在代入具体数字,看看"预测正确"和"预测错误"时损失差多少。
场景:二分类任务,真实标签 $y = 1$(正类)
| 模型预测概率 $p$ | 交叉熵 $-[y \cdot \log(p) + (1-y) \cdot \log(1-p)]$ | 直觉 |
|---|---|---|
| $p = 0.99$ | $-[1 \times \log(0.99) + 0] = 0.0101$ | 非常自信且正确 → loss极低 |
| $p = 0.90$ | $-[1 \times \log(0.90) + 0] = 0.1054$ | 比较自信且正确 → loss较低 |
| $p = 0.50$ | $-[1 \times \log(0.50) + 0] = 0.6931$ | 完全不确定 → loss中等 |
| $p = 0.10$ | $-[1 \times \log(0.10) + 0] = 2.3026$ | 很自信但错了 → loss很高 |
| $p = 0.01$ | $-[1 \times \log(0.01) + 0] = 4.6052$ | 极度自信且错了 → loss极高 |
观察规律:
- 预测越准,损失越小:$p = 0.99$ 时损失只有 0.01,几乎为零
- 预测越离谱,损失越大:$p = 0.01$ 时损失高达 4.61,是正确预测的 460 倍
- 损失是"非线性惩罚":从 $p = 0.10$ 到 $p = 0.01$,概率只差了 0.09,但损失翻了一倍——交叉熵对"非常错误的预测"施加了指数级的惩罚
与MSE对比:
| 预测 $p$ | 真实 $y$ | 交叉熵 | MSE |
|---|---|---|---|
| 0.99 | 1 | 0.0101 | 0.0001 |
| 0.50 | 1 | 0.6931 | 0.2500 |
| 0.01 | 1 | 4.6052 | 0.9801 |
当预测完全错误($p = 0.01$)时,交叉熵的惩罚(4.61)远大于MSE(0.98)。这意味着交叉熵在模型犯大错时会给出更强的梯度信号,迫使模型更快修正——这正是上一节分析的"交叉熵梯度不包含 $\sigma'(z)$“的直观体现。
Softmax回归(多分类)
- 二分类:Sigmoid → 输出1个概率 $p$,另一个类别的概率是 $1-p$
- 多分类:Softmax → 输出 $k$ 个概率($k$ 为类别数),且概率之和为1。Softmax 的计算原理见第 2.2 节。
Softmax回归的损失函数也是交叉熵,只是推广到多分类:
$$ L = -\frac{1}{n} \sum_i \sum_k y_{ik} \cdot \log(p_{ik}) $$其中 $y_{ik}$ 是one-hot编码(只有正确类别的位置是1,其余是0)。
模块小结:机器学习基础
| 你学到了什么 | 为什么重要 |
|---|---|
| 线性回归与正规方程 | 最基础的监督学习模型 |
| L1/L2 正则化 | 防止过拟合的核心技术 |
| 逻辑回归 | 二分类的基础模型 |
| 交叉熵损失 | 分类任务的标准损失函数 |
4.4 动手实践:scikit-learn 完整示例
理论讲完了,现在用代码把前面学到的知识串起来。以下是一个完整的机器学习流程:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, mean_squared_error
# ========== 1. 准备数据 ==========
# 模拟数据:房屋面积(平方米)→ 房价(万元)
np.random.seed(42)
X = np.random.rand(100, 1) * 100 + 20 # 面积:20-120 平方米
y = X.flatten() * 3 + np.random.randn(100) * 10 + 50 # 房价 ≈ 3×面积 + 50 + 噪声
# ========== 2. 划分训练集和测试集 ==========
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# 训练集:80个样本(用来学习参数)
# 测试集:20个样本(用来评估泛化能力——模型从未见过的数据)
# ========== 3. 特征标准化 ==========
scaler = StandardScaler() # 均值0、标准差1(第3.1节学的方差知识)
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# ========== 4. 训练线性回归模型 ==========
model = LinearRegression() # 内部用正规方程 w = (X^TX)^{-1} X^Ty 求解
model.fit(X_train, y_train)
print(f"学到的参数:斜率 w = {model.coef_[0]:.2f},截距 b = {model.intercept_:.2f}")
# ========== 5. 预测与评估 ==========
y_pred = model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
print(f"均方误差 MSE = {mse:.2f}") # 越小越好
模型评估指标速查:
| 指标 | 适用场景 | 公式 | 含义 |
|---|---|---|---|
| 准确率(Accuracy) | 分类 | 正确数 / 总数 | 整体预测正确的比例 |
| 精确率(Precision) | 分类 | TP / (TP+FP) | 预测为正的样本中,真正为正的比例 |
| 召回率(Recall) | 分类 | TP / (TP+FN) | 所有正样本中,被正确找出的比例 |
| F1-Score | 分类 | 2×P×R / (P+R) | 精确率和召回率的调和平均 |
| MSE | 回归 | $\frac{1}{n}\sum(y-\hat{y})^2$ | 预测值与真实值的平均平方误差 |
| $R^2$ | 回归 | $1 - \frac{\text{SS}_{\text{res}}}{\text{SS}_{\text{tot}}}$ | 模型解释了多少数据的变异(1=完美) |
什么时候用哪个?
- 回归任务(预测连续值)→ MSE、$R^2$
- 分类任务(类别均衡)→ Accuracy
- 分类任务(类别不均衡,如欺诈检测)→ Precision、Recall、F1
4.5 阶段总结
本阶段核心公式速查表
| # | 名称 | 公式 |
|---|---|---|
| 1 | 梯度下降 | $\theta = \theta - \eta \cdot \nabla L$ |
| 2 | 线性回归 | $y = Xw + b$ |
| 3 | 正规方程 | $w = (X^T X)^{-1} \cdot X^T y$ |
| 4 | Sigmoid | $\sigma(z) = \frac{1}{1 + e^{-z}}$ |
| 5 | Sigmoid 导数 | $\sigma'(z) = \sigma(z) \cdot (1 - \sigma(z))$ |
| 6 | 交叉熵 | $L = -[y \cdot \log(p) + (1-y) \cdot \log(1-p)]$ |
| 7 | Softmax | $\text{softmax}(z_i) = e^{z_i} / \sum e^{z_j}$ |
| 8 | L2 正则化 | $L_{\text{total}} = L + \lambda \cdot \sum w^2$ |
| 9 | 贝叶斯 | $P(A \mid B) = P(B \mid A) \cdot P(A) / P(B)$ |
| 10 | 最大似然 | $\theta^* = \arg\max \sum \log P(x_i \mid \theta)$ |
| 11 | 链式法则 | $\frac{dy}{dx} = \frac{dy}{du} \cdot \frac{du}{dx}$ |
| 12 | SVD | $A = U \cdot \Sigma \cdot V^T$ |
推荐补充资源
| 知识点 | 推荐资源 | 说明 |
|---|---|---|
| Python | 《Python编程:从入门到实践》 | 零基础友好 |
| NumPy | NumPy官方Quickstart | 快速上手 |
| 微积分 | 3Blue1Brown《微积分的本质》 | 可视化理解,B站有中文字幕 |
| 线性代数 | 3Blue1Brown《线性代数的本质》 | 神级教程,必看 |
| 概率论 | 可汗学院概率论课程 | 零基础友好 |
| 机器学习 | 吴恩达《机器学习》课程 | 经典入门课程 |
下一篇预告:本文建立了 Python 编程和数学基础。在速通 AI(二):深度学习与 PyTorch中,你将看到今天学的矩阵乘法如何在 GPU 上通过 PyTorch 加速,Sigmoid 和 ReLU 如何组成神经网络,梯度下降如何真正训练一个模型——从"数学理论"走向"动手实践”。