🎯 目标:建立Python编程能力和数学理论基础,为后续深度学习和大模型学习打下根基。 📋 前置要求:无,零基础可开始


本阶段知识依赖图

Python基础 ──→ 数学基础 ──→ 机器学习入门
    │              │              │
    │              │              ├── 线性回归
    │              │              ├── 逻辑回归
    │              │              └── 正则化
    │              │
    │              ├── 微积分 ──→ 梯度下降
    │              ├── 线性代数 ──→ 矩阵运算
    │              └── 概率论 ──→ 最大似然估计
    └── Jupyter Notebook(贯穿始终的工具)

模块一:Python编程基础

为什么Python是AI工程师的第一语言?

Python之所以成为AI/ML领域的首选语言,核心原因有三:

  1. 语法简洁:接近伪代码,让你专注于算法逻辑而非语法细节
  2. 生态丰富:NumPy、Pandas、PyTorch、HuggingFace等核心库全部基于Python
  3. 社区庞大:遇到问题几乎都能找到解答

1.1 环境搭建

核心任务

安装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.2 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循环——遍历数据集的核心方式
# 类比:工厂的"流水线"——一个一个地处理数据
for batch in 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.3 函数与面向对象

函数——代码复用的基本单位

类比:函数就像一台"机器"——你给它输入,它给你输出。

# 函数在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.4 Jupyter Notebook

为什么AI工程师离不开Jupyter?

类比:如果普通的Python脚本是"写一封信然后寄出去",那Jupyter Notebook就是"面对面聊天"——你可以看到对方(代码)的即时反应。

  1. 交互式编程:可以逐个cell运行代码,实时看到结果(不用等整个程序跑完)
  2. 可视化集成:图表直接嵌入在代码旁边(就像在笔记本上画图一样自然)
  3. 文档化:Markdown和代码混排,方便记录实验过程(“实验笔记"和"实验数据"放在一起)
  4. 快速原型:不需要创建完整项目文件,快速测试想法(“先试试这个想法行不行”)

核心操作

  • Shift+Enter:运行当前cell(执行当前这段代码)
  • Esc+A:在上方插入新cell(在上面加一段新代码或笔记)
  • Esc+B:在下方插入新cell
  • Esc+M:转为Markdown cell(写文字笔记)
  • Esc+DD:删除当前cell

1.5 文件操作与常用库

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会自动"扩展"较小的数组

Pandas基础——数据处理的瑞士军刀

类比:如果NumPy是"计算器”,那Pandas就是"Excel"——它擅长处理表格数据(有列名、有索引的数据)。

import pandas as pd

# 读取CSV数据(数据集通常就是CSV格式,就像Excel表格)
df = pd.read_csv("data.csv")

# 基本操作
df.head()           # 查看前5行("先看一眼数据长什么样")
df.describe()       # 统计描述("数据的平均值、最大值、最小值是多少")
df['column']        # 选择列("只看这一列")
df[df['age'] > 18]  # 筛选("只要年龄大于18的")

模块二:微积分基础

为什么AI需要微积分?

一句话回答:神经网络的训练过程就是"求导+更新"的循环。

更详细的解释:想象你在一座山上,想要找到山谷的最低点(最优解)。你需要知道两件事:

  1. 哪个方向是下坡?(导数告诉你方向)
  2. 应该走多远?(学习率决定步幅)

当你听到"反向传播算法"时,本质就是链式法则求导——计算每个参数对最终误差的"贡献度"。当你听到"梯度下降"时,本质就是沿着导数方向更新参数——让模型的预测越来越准。

2.1 导数与求导法则

导数的直觉理解

核心类比:速度计

想象你开车:

  • 你的位置随时间变化(比如从0公里开到了100公里)
  • 速度就是位置对时间的导数——它告诉你"位置变化有多快"

同理,在神经网络中:

  • 损失函数随参数变化(参数调整后,误差会变大或变小)
  • 导数就是损失对参数的"变化率"——它告诉你"参数应该往哪个方向调,调多少"
导数 > 0:函数在递增 → 参数应该减小(往左走,减少误差)
导数 < 0:函数在递减 → 参数应该增大(往右走,减少误差)
导数 = 0:到达极值点 → 可能是最优解(误差最小的地方)

更形象的类比:山谷寻宝

想象损失函数是一座山的地形图:
- 山的高度 = 当前误差(越高误差越大)
- 你的位置 = 当前参数值
- 你的目标 = 找到山谷最低点(最小误差)

导数就像一个"坡度指示器":
- 它告诉你脚下哪个方向最陡(梯度方向)
- 它告诉你坡有多陡(梯度大小)

你每走一步都看一眼指示器,然后往最陡的下坡方向走——这就是梯度下降!

核心求导公式(必须记住)

1. 常数求导:(c)' = 0           ——常数不变化,导数为0
2. 幂函数:(x^n)' = n·x^(n-1)   ——x²的导数是2x,x³的导数是3x²
3. 指数函数:(e^x)' = e^x       ——e^x是唯一"导数等于自身"的函数!
4. 对数函数:(ln x)' = 1/x      ——对数函数的导数是倒数
5. 三角函数:(sin x)' = cos x   ——正弦的导数是余弦

为什么要记住这些? 因为神经网络的激活函数(Sigmoid、ReLU、Tanh)都是由这些基本函数组合而成的。记住基本公式,就能用链式法则推导出任何复杂函数的导数。

链式法则——反向传播的数学基础

核心公式:如果 y = f(g(x)),则 dy/dx = f'(g(x)) · g'(x)

类比:工厂流水线的责任追溯

想象一个工厂流水线:

原材料x → 工序1(加工成半成品) → 工序2(加工成成品) → 最终产品y
           g(x) = z               f(z) = y

现在最终产品有缺陷(损失L很大),你想知道是谁的责任:

  • 工序2的责任:∂L/∂z(成品的问题有多少是工序2造成的)
  • 工序1的责任:∂L/∂x = ∂L/∂z × ∂z/∂x(把工序2的责任"传回"工序1)

这就是链式法则——把最终的误差责任一层一层往回追溯!

AI中的例子——单个神经元

网络结构:输入x → 线性变换z = wx + b → 激活a = sigmoid(z) → 损失L

求L对w的导数(用于更新w):

步骤1:L对a的导数(损失对激活值的敏感度)
    ∂L/∂a = 2(a - y)  (如果L是均方误差)

步骤2:a对z的导数(激活函数的导数)
    ∂a/∂z = sigmoid'(z) = a(1-a)

步骤3:z对w的导数(线性变换的导数)
    ∂z/∂w = x

链式法则组合:
    ∂L/∂w = ∂L/∂a · ∂a/∂z · ∂z/∂w
           = 2(a-y) · a(1-a) · x

这就是反向传播的核心!每一步都很简单,但组合起来就能计算复杂网络的梯度。

扩展到多变量:当网络有多个参数时,每个参数都有自己的"责任链":

∂L/∂w₁ = ∂L/∂z · ∂z/∂w₁  (w₁的梯度)
∂L/∂w₂ = ∂L/∂z · ∂z/∂w₂  (w₂的梯度)
∂L/∂b  = ∂L/∂z · ∂z/∂b   (b的梯度)

所有参数同时更新,这就是梯度下降的并行性。

2.2 激活函数求导

Sigmoid函数及其导数——完整推导

什么是Sigmoid? 它把任意实数"压缩"到0和1之间,就像一个"开关"——输入很大时输出接近1(开),输入很小时输出接近0(关)。

Sigmoid: σ(x) = 1 / (1 + e^(-x))

Sigmoid导数的完整推导(这是面试常考题):

已知:σ(x) = 1 / (1 + e^(-x))

设 u = 1 + e^(-x),则 σ(x) = 1/u = u^(-1)

根据链式法则:
σ'(x) = d/dx [u^(-1)]
       = -u^(-2) · du/dx
       = -1/u² · d/dx[1 + e^(-x)]
       = -1/u² · (-e^(-x))
       = e^(-x) / (1 + e^(-x))²

现在我们把它写成用σ(x)表示的形式:
σ'(x) = e^(-x) / (1 + e^(-x))²
       = [1 + e^(-x) - 1] / (1 + e^(-x))²     ← 把分子拆开
       = (1 + e^(-x))/(1 + e^(-x))² - 1/(1 + e^(-x))²
       = 1/(1 + e^(-x)) - 1/(1 + e^(-x))²
       = σ(x) - σ(x)²
       = σ(x) · (1 - σ(x))

最终结果:σ'(x) = σ(x) · (1 - σ(x))

这个结果为什么重要?

  • 导数可以直接用函数值计算——不需要重新算指数函数!
  • 只要知道σ(x)的值,就能直接得到导数
  • 这在反向传播中节省了大量计算

Sigmoid的问题——梯度消失

当x很大时:σ(x) ≈ 1,σ'(x) = 1·(1-1) = 0  → 梯度几乎为0
当x很小时:σ(x) ≈ 0,σ'(x) = 0·(1-0) = 0  → 梯度几乎为0

这意味着:当输入值太大或太小时,Sigmoid的梯度接近0
→ 参数几乎不更新 → 网络"学不动了"
→ 这就是"梯度消失"问题,是深度学习早期的一大难题
→ 后来ReLU激活函数的出现解决了这个问题

Softmax函数

什么是Softmax? 它把一组任意实数转换为概率分布(所有值>0,且和为1)。

类比:想象一个班级的考试成绩是[85, 90, 75],Softmax把它们转换为"每个学生成绩的相对概率"——成绩越高的学生,概率越大。

Softmax: softmax(x_i) = e^(x_i) / Σe^(x_j)

示例:
输入:[2.0, 1.0, 0.1]
e^2.0 = 7.39,  e^1.0 = 2.72,  e^0.1 = 1.11
总和 = 7.39 + 2.72 + 1.11 = 11.22
Softmax = [7.39/11.22, 2.72/11.22, 1.11/11.22]
        = [0.659, 0.242, 0.099]
        ≈ [65.9%, 24.2%, 9.9%]  ← 三个概率之和 = 100%

为什么要用e^x而不是直接用x?

  1. e^x > 0 保证所有输出都是正数(概率不能为负)
  2. e^x 是单调递增的(输入越大,输出越大——保持大小关系)
  3. e^x 的导数是它自己(计算方便)

数值稳定性问题

如果输入值很大,比如 [1000, 1001, 1002]
e^1000 = 一个巨大的数 → 溢出!

解决方案:减去最大值
softmax([1000, 1001, 1002])
= softmax([1000-1002, 1001-1002, 1002-1002])
= softmax([-2, -1, 0])
= [e^(-2), e^(-1), e^0] / (e^(-2) + e^(-1) + e^0)
= [0.135, 0.368, 1.0] / 1.503
= [0.090, 0.245, 0.665]

结果完全一样!但不会溢出。
PyTorch和所有深度学习框架都自动做了这个优化。
  • 应用场景:分类任务的最后一层,输出每个类别的概率

2.3 线性代数

向量——数据的基本表示

类比:向量就像一个"坐标"——它用一组数字来描述一个点的位置。

在AI中,一个数据样本通常表示为一个向量:

一张28×28的灰度图片 → 展平为784维向量(每个像素是一个维度)
一个句子的词嵌入 → 每个词是一个300维向量(用300个数字描述一个词的"含义")

为什么用向量?因为计算机只能处理数字。
把现实世界的东西(图片、文字、声音)转换为向量,就是"表示学习"的核心任务。

矩阵乘法——神经网络的核心运算

矩阵乘法到底在做什么?

直觉1:矩阵是一组"变换规则"

想象你有一个2D图形(比如一个三角形)。
矩阵乘法就是对这个图形做"变换"——旋转、缩放、剪切。

不同的矩阵 = 不同的变换
神经网络的权重矩阵 = 一种"语义变换"——把输入空间映射到输出空间

直觉2:矩阵乘法是"批量线性组合"

[output₁]   [w₁₁ w₁₂ w₁₃] [input₁]   [w₁₁·input₁ + w₁₂·input₂ + w₁₃·input₃]
[output₂] = [w₂₁ w₂₂ w₂₃] [input₂] = [w₂₁·input₁ + w₂₂·input₂ + w₂₃·input₃]
                             [input₃]

每个输出 = 所有输入的加权求和
权重矩阵中的每个元素 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个类别的概率)

每一层都在做:output = input @ weight + bias

为什么GPU擅长深度学习?

矩阵乘法的本质是"大量独立的乘加运算":
C[i][j] = Σ A[i][k] * B[k][j]

这些乘加运算是完全独立的——C[0][0]和C[1][1]的计算互不影响。

CPU:少量核心(比如8个),每个核心很强,但只能同时算8个
GPU:大量核心(比如几千个),每个核心较弱,但能同时算几千个

矩阵乘法正好适合GPU的"人海战术"!
这就是为什么深度学习离不开GPU。

特征值与特征向量——理解PCA降维

什么是特征值和特征向量?

类比:镜子的方向

想象你拿着一面镜子(矩阵A),向它照一束光(向量v):

大多数方向的光:照进去后方向会改变
特殊方向的光:照进去后方向不变,只是被拉伸或压缩

这个"特殊方向"就是特征向量
拉伸/压缩的比例就是特征值
数学定义:对于矩阵A,如果 Av = λv,则:
- v 是特征向量(方向不变的"特殊方向")
- λ 是特征值(拉伸/压缩的比例)

示例:
A = [[2, 1], [1, 2]]
v = [1, 1] 是一个特征向量,因为 A·v = [3, 3] = 3·[1, 1],特征值 λ = 3
v = [1, -1] 是另一个特征向量,因为 A·v = [1, -1] = 1·[1, -1],特征值 λ = 1

为什么特征值/特征向量在AI中重要?

PCA降维的原理:
1. 计算数据的协方差矩阵
2. 求协方差矩阵的特征值和特征向量
3. 特征值大的特征向量 = 数据变化最大的方向("最重要的方向")
4. 只保留前k个最重要的方向,丢弃其他方向
→ 数据从高维降到k维,同时保留了最多的信息

类比:你有一张3D的照片,PCA告诉你"最有信息量的拍摄角度"是什么
     只从这个角度拍一张2D照片,虽然维度降低了,但信息损失最少

SVD奇异值分解——矩阵的"DNA"

任意矩阵 A 可以分解为三个矩阵的乘积:
A = U · Σ · V^T

其中:
- U:左奇异矩阵("行空间的特征方向")
- Σ:奇异值矩阵(对角矩阵,每个对角元素是一个奇异值)
- V:右奇异矩阵("列空间的特征方向")

SVD的直觉——信息压缩

类比:一张高清照片有1000×1000像素(100万个数字)
SVD告诉你:这100万个数字中,真正"重要"的信息可能只需要100个奇异值就能表达

保留最大的k个奇异值,丢弃其他的:
→ 用更少的数据表示原始信息(压缩)
→ 保留了最重要的"模式"(去噪)

这就是为什么SVD被称为矩阵的"DNA"——它揭示了矩阵中最本质的结构。

SVD在AI中的应用

  • 数据压缩:保留最大的k个奇异值,用更少的数据表示原始信息
  • 推荐系统:协同过滤的核心算法(Netflix推荐电影用的就是SVD)
  • PCA实现:SVD是PCA的数值稳定实现方式
  • 词嵌入:早期的LSA(潜在语义分析)就是用SVD做的

2.4 概率论基础

条件概率与贝叶斯定理

条件概率:在已知B发生的条件下,A发生的概率

P(A|B) = P(A∩B) / P(B)

类比:在一个班里,P(及格) = 及格人数/总人数
     P(及格|男同学) = 及格的男同学/所有男同学
     条件概率就是"缩小范围后的概率"

贝叶斯定理——从结果推原因

P(A|B) = P(B|A) · P(A) / P(B)

类比:你看到地上有水(结果B),想判断是"下雨了"还是"洒水车经过"(原因A)
- P(下雨|有水) = P(有水|下雨)·P(下雨) / P(有水)
- 如果今天是晴天,P(下雨)很小,所以"下雨"的可能性不大
- 如果洒水车经常经过,P(洒水车)很大,所以"洒水车"的可能性更大

贝叶斯定理 = 用"先验知识"(P(A))+ "新证据"(P(B|A))更新你的判断(P(A|B))

最大似然估计(MLE)——损失函数的理论基础

核心思想:选择让观测数据出现概率最大的参数。

类比:侦探推理

场景:一个硬币被抛了10次,结果是7次正面、3次反面。
问题:这枚硬币正面朝上的概率p是多少?

最大似然估计的回答:选择让"7正3反"这个结果最可能出现的p值。

似然函数:L(p) = C(10,7) · p^7 · (1-p)^3

我们要求让L(p)最大的p。
取对数(方便计算):log L = 7·log(p) + 3·log(1-p) + 常数
对p求导并令其为0:7/p - 3/(1-p) = 0
解得:p = 7/10 = 0.7

结论:最大似然估计认为p=0.7,也就是正面概率是70%
这很直觉——10次中7次正面,所以概率大概是70%。

MLE的完整数学推导(一维高斯分布)

假设数据服从正态分布 N(μ, σ²)
观测到n个数据点:x₁, x₂, ..., xₙ

似然函数(所有数据点概率的乘积):
L(μ,σ²) = ∏ᵢ P(xᵢ | μ, σ²)
         = ∏ᵢ [1/√(2πσ²)] · exp(-(xᵢ-μ)²/(2σ²))

取对数(把乘法变加法,方便求导):
log L = Σᵢ [-½log(2πσ²) - (xᵢ-μ)²/(2σ²)]

对μ求导并令其为0:
∂(log L)/∂μ = Σᵢ (xᵢ-μ)/σ² = 0
→ Σᵢ xᵢ - nμ = 0
→ μ* = (1/n)Σᵢ xᵢ = 样本均值!

结论:高斯分布的MLE估计 = 样本均值
这很直觉——"最可能的中心位置"就是所有数据点的平均值。

关键联系——为什么交叉熵损失函数是合理的

交叉熵损失 = 负对数似然

当你最小化交叉熵时,你实际上在最大化似然——
让模型的参数使得观测到的训练数据出现的概率最大。

这就是为什么交叉熵是分类任务的标准损失函数——它有坚实的概率论基础。

模块三:概率论与最优化

3.1 概率论进阶

期望与方差

期望——数据的"重心"

期望 E[X] = Σ x·P(x)

类比:期望就像一根尺子上挂了一堆不同重量的砝码
     砝码的位置 = x(取值),砝码的重量 = P(x)(概率)
     期望 = 这根尺子平衡时的支点位置(重心)

示例:骰子的期望 = 1×(1/6) + 2×(1/6) + ... + 6×(1/6) = 3.5
     虽然骰子不会掷出3.5,但这是"平均值"的理论位置

方差——数据的"分散程度"

方差 Var(X) = E[(X-μ)²] = E[X²] - (E[X])²

类比:两组学生的平均分都是80分
     A组:[79, 80, 81] → 方差很小(成绩很集中)
     B组:[50, 80, 110] → 方差很大(成绩很分散)
     虽然平均分一样,但方差告诉我们"波动有多大"

在AI中的应用

  • BatchNorm:对每个mini-batch计算均值和方差,然后标准化数据
    • 为什么?因为如果每层的输入分布都在变化(“内部协变量偏移”),网络很难学习
    • BatchNorm让每层的输入分布稳定下来,加速训练
  • 权重初始化:初始化权重时需要控制方差
    • 方差太大 → 信号爆炸(输出值越来越大)
    • 方差太小 → 信号消失(输出值越来越小)
    • 合适的方差 → 信号稳定传播

协方差——两个变量的"关系"

Cov(X,Y) = E[(X-μ_x)(Y-μ_y)]

协方差衡量两个变量的变化趋势:
- 正值:X增大时Y也增大(正相关,比如身高和体重)
- 负值:X增大时Y减小(负相关,比如温度和羽绒服销量)
- 零:X和Y没有线性关系(不一定独立!)

3.2 梯度下降——最重要的优化算法

梯度的直觉

类比:蒙眼下山

想象你被蒙上眼睛,站在一座山上,想要找到山谷的最低点:

  • 你唯一能做的就是用脚感受脚下的坡度
  • 每一步都往最陡的下坡方向
  • 走的步幅由学习率控制

梯度就是那个"最陡的方向":

  • 在一维中:梯度 = 导数(一个数字)
  • 在多维中:梯度 = 所有偏导数组成的向量(指向最陡上升方向)
梯度方向:函数值增长最快的方向(上坡方向)
梯度下降:沿着梯度的反方向走(下坡方向)

数学公式:θ_new = θ_old - η · ∇L(θ)

其中:
- θ:模型参数(你所在的位置)
- η:学习率(步幅大小)
- ∇L(θ):损失函数对参数的梯度(坡度指示器)
- 减号:因为我们想减少损失,所以往梯度的反方向走

学习率——步幅的艺术

学习率太大 → 步子太大,可能跨过最低点,来回震荡
             就像下山时跑得太快,冲过了山谷,跑到对面山上去了

学习率太小 → 步子太小,收敛极慢
             就像下山时每次只挪一厘米,要走到天黑

学习率刚好 → 稳定收敛到最优解
             就像以合适的速度下山,稳步到达谷底

常见策略:
- 从0.001开始(一个比较安全的起点)
- 观察loss曲线:如果震荡就减小,如果下降太慢就增大
- 高级策略:学习率调度(先大后小,就像下山时先大步走,接近谷底时小步挪)

三种梯度下降方法——详细对比

1. 批量梯度下降(BGD):
   每次用全部数据计算梯度
   类比:你有一亿个数据点,每走一步都要看一亿个"坡度指示器"取平均
   优点:梯度方向最准确,一定能收敛到局部最优
   缺点:数据量大时极慢(每步都要遍历全部数据)
   
2. 随机梯度下降(SGD):
   每次只用1个样本计算梯度
   类比:你只看一个"坡度指示器"就决定方向
   优点:快(每步只需要看一个样本)
   缺点:方向不稳定(就像只听一个人的建议,可能被误导)
   
3. 小批量梯度下降(MBGD):
   每次用一小批数据(如32/64/128个样本)计算梯度
   类比:你听32个人的建议取平均,既不太慢也不太偏
   优点:兼顾速度和稳定性
   实际训练中最常用!(batch_size通常为32/64/128/256)

对线性回归应用梯度下降——完整推导

线性回归模型:ŷ = wx + b
损失函数(均方误差):L = (1/n) Σᵢ (yᵢ - ŷᵢ)² = (1/n) Σᵢ (yᵢ - wxᵢ - b)²

目标:找到w和b,使L最小。

对w求偏导:
∂L/∂w = (1/n) Σᵢ 2(yᵢ - wxᵢ - b)·(-xᵢ)
       = -(2/n) Σᵢ xᵢ(yᵢ - wxᵢ - b)

对b求偏导:
∂L/∂b = (1/n) Σᵢ 2(yᵢ - wxᵢ - b)·(-1)
       = -(2/n) Σᵢ (yᵢ - wxᵢ - b)

更新规则:
w_new = w_old - η · ∂L/∂w
b_new = b_old - η · ∂L/∂b

重复这个过程直到收敛(损失不再明显下降)。

3.3 凸优化

什么是凸函数?

类比:碗和碗碎片

凸函数像一个碗——你把弹珠放进去,它总会滚到最低点。
不管弹珠从哪个位置开始,最终都会到达同一个最低点(全局最优)。

非凸函数像一堆碗碎片——弹珠可能卡在某个凹陷处(局部最优),
而不是到达真正的最低点(全局最优)。

凸优化重要因为:凸函数的局部最小值 = 全局最小值
→ 梯度下降一定能找到最优解
→ 不用担心"卡在局部最优"
凸函数的数学定义:
对于任意两点 x₁, x₂ 和任意 t ∈ [0,1]:
f(t·x₁ + (1-t)·x₂) ≤ t·f(x₁) + (1-t)·f(x₂)

直觉:函数图像上任意两点的连线,都在函数图像上方(或重合)

判断方法:
- 一维:二阶导数 f''(x) ≥ 0(开口向上)
- 多维:Hessian矩阵半正定

为什么凸优化在AI中重要?

线性回归的损失函数(均方误差)是凸的 → 有唯一最优解
逻辑回归的损失函数(交叉熵)是凸的 → 有唯一最优解
神经网络的损失函数是非凸的 → 只能找到局部最优

好消息:实践中,深度学习的非凸损失函数的局部最优通常也足够好
坏消息:理论上不能保证找到全局最优
实际做法:用SGD + 各种优化技巧(动量、Adam等),通常能找到好的解

拉格朗日乘子法与KKT条件

问题:如何在有约束的情况下求最优解?

类比:在围栏内找最低点

无约束优化:在整个山上找最低点(随便走)
有约束优化:只能在围栏内找最低点(不能走出围栏)

拉格朗日乘子法的核心思想:
把"有约束"问题转换为"无约束"问题
在目标函数上加一个"惩罚项"——如果你违反约束,惩罚项就会变大
数学形式:
原始问题:minimize f(x),subject to g(x) = 0

构造拉格朗日函数:L(x,λ) = f(x) + λ·g(x)
- f(x):我们要最小化的目标
- λ·g(x):约束的"惩罚"
- λ(拉格朗日乘子):惩罚的"力度"

分别对x和λ求导并令其为0:
∂L/∂x = 0  → 最优解的条件
∂L/∂λ = 0  → 约束条件(g(x)=0)

在AI中的应用:SVM的对偶问题就是用拉格朗日乘子法推导的

模块四:机器学习基础

4.1 线性回归

线性回归的直觉

类比:在散点图上画一条"最好"的线

给你一堆数据点(比如房屋面积和房价),你想找到一条直线来描述它们的关系:

y = wx + b

目标:找到w(斜率)和b(截距),使得这条线"最贴近"所有数据点
"最贴近" = 预测值和真实值的差距最小

正规方程——直接求解

思路:既然我们想要"最小化误差",那就直接对误差函数求导,令导数为0,解方程。

矩阵形式:y = Xw + e(e是误差)
损失函数:L = ||e||² = ||y - Xw||²

展开: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:
∂L/∂w = -2X^Ty + 2X^TXw = 0
→ X^TXw = X^Ty
→ w = (X^TX)^(-1) · X^Ty

这就是正规方程!直接一步算出最优解。

优点:一步到位,不需要迭代 缺点

  • 需要计算矩阵逆(X^TX)^(-1),当特征维度很高时(如10000+),计算量巨大(O(n³))
  • 当X^TX不可逆时(比如特征之间有线性关系),无法求解

什么时候用正规方程,什么时候用梯度下降?

特征数 < 10000:正规方程通常更快
特征数 > 10000:梯度下降通常更合适
数据量极大:梯度下降(可以每次只用一小批数据)

最小二乘法

损失函数:L(w,b) = (1/n) Σ(yᵢ - wxᵢ - b)²

"最小二乘"的名字来源:最小化"误差的平方和"
- "二乘" = 平方(误差的平方)
- "最小" = 让这个平方和最小

对L求导令其为0,就是正规方程。
几何意义:找到一条线,使所有数据点到这条线的"垂直距离的平方和"最小。

4.2 正则化

为什么需要正则化?

类比:考试作弊 vs 真正学会

过拟合就像"死记硬背":
- 训练数据(练习题)上表现很好——因为把答案都背下来了
- 测试数据(考试题)上表现很差——因为没见过的题就不会了

正则化就像"惩罚死记硬背":
- 不仅要求模型在训练数据上表现好
- 还要求模型"尽量简单"(不要死记硬背)
- 简单的模型泛化能力更好(理解了原理就能做新题)

L1正则化(Lasso)

L_total = L_original + λ · Σ|wᵢ|

L1的效果:使部分权重变为**恰好0**,实现特征选择

为什么L1能使权重变为0?——几何直觉

想象一个2D空间(w₁, w₂):
- 损失函数的等高线是椭圆形的
- L1的约束区域是菱形(|w₁| + |w₂| ≤ c)

椭圆和菱形最容易在"角点"相交
角点恰好在坐标轴上(w₁=0 或 w₂=0)
→ 最优解中某些权重恰好为0 → 特征选择

类比:L1像一把"剪刀",会直接把不重要的特征"剪掉"(权重变为0)

L2正则化(Ridge)

L_total = L_original + λ · Σ(wᵢ²)

L2的效果:使所有权重趋近于0但**不等于0**,防止某个特征权重过大

为什么L2不会使权重变为0?——几何直觉

想象一个2D空间(w₁, w₂):
- 损失函数的等高线是椭圆形的
- L2的约束区域是圆形(w₁² + w₂² ≤ c)

椭圆和圆形的交点通常不在坐标轴上
→ 权重趋近于0但不会恰好为0

类比:L2像一个"压缩弹簧",把所有权重往小的方向压,但不会压到0

L1 vs L2 对比

特性              L1 (Lasso)          L2 (Ridge)
────────────────────────────────────────────────
效果              特征选择(稀疏解)    权重衰减(平滑解)
权重会变为0?      会(部分权重=0)     不会(权重趋近0但≠0)
适用场景          特征很多,需要筛选    特征都有用,防止过拟合
几何形状          菱形约束              圆形约束
导数              |w|' = sign(w)       (w²)' = 2w
                  (在0处不可导)       (处处可导,优化更方便)

正则化的本质——奥卡姆剃刀

不加正则化:模型只追求"拟合训练数据"(最小化训练误差)
加正则化:模型同时追求"拟合训练数据" + "保持简单"(最小化训练误差 + 惩罚复杂度)

这就是奥卡姆剃刀原则:在同样能解释数据的模型中,选择最简单的那个

为什么简单更好?
- 简单的模型更不容易"死记硬背"训练数据
- 简单的模型对新数据的泛化能力更强
- 简单的模型更容易理解和调试

4.3 逻辑回归

逻辑回归不是回归,是分类!

名字的由来:逻辑回归使用了"逻辑函数"(即Sigmoid函数),所以叫"逻辑回归"。但它实际上是一个分类算法,不是回归算法。

线性回归:输出连续值(如房价=150万)
逻辑回归:输出概率(如垃圾邮件概率=0.95)

核心区别:在输出层加了一个Sigmoid函数
- 线性回归:y = wx + b → 输出范围是(-∞, +∞)
- 逻辑回归:y = σ(wx + b) → 输出范围是(0, 1),表示概率

Sigmoid函数——将实数映射为概率

σ(z) = 1 / (1 + e^(-z))

σ(0) = 0.5    → "不确定"
σ(5) ≈ 0.993  → "非常可能是"
σ(-5) ≈ 0.007 → "非常可能不是"

分类规则:
- σ(z) ≥ 0.5 → 预测为正类(类别1)
- σ(z) < 0.5 → 预测为负类(类别0)

交叉熵损失函数——完整推导

为什么用交叉熵而不是均方误差?

这个问题在面试中经常被问到,让我们从头推导。

推导起点:最大似然估计

假设:
- 样本i的真实标签:yᵢ ∈ {0, 1}
- 模型预测样本i为正类的概率:pᵢ = σ(w·xᵢ + b)

对于单个样本i:
如果 yᵢ = 1:P(yᵢ|xᵢ) = pᵢ        (预测为正类的概率)
如果 yᵢ = 0:P(yᵢ|xᵢ) = 1 - pᵢ    (预测为负类的概率)

合并为一个公式:
P(yᵢ|xᵢ) = pᵢ^yᵢ · (1-pᵢ)^(1-yᵢ)
(当yᵢ=1时就是pᵢ,当yᵢ=0时就是1-pᵢ)

整个数据集的似然:
L = ∏ᵢ P(yᵢ|xᵢ) = ∏ᵢ [pᵢ^yᵢ · (1-pᵢ)^(1-yᵢ)]

取对数(连乘变连加,方便计算):
log L = Σᵢ [yᵢ·log(pᵢ) + (1-yᵢ)·log(1-pᵢ)]

最大似然 = 最大化log L
最小化负对数似然 = 最小化 -log L

交叉熵损失:
L_CE = -(1/n) Σᵢ [yᵢ·log(pᵢ) + (1-yᵢ)·log(1-pᵢ)]

这就是交叉熵损失函数的由来——它不是凭空设计的,
而是从最大似然估计自然推导出来的!

为什么不用均方误差(MSE)?

MSE损失:L_MSE = (1/n) Σᵢ (yᵢ - pᵢ)²

问题:当Sigmoid的输入z很大或很小时,Sigmoid的梯度接近0
     MSE的梯度中包含σ'(z),所以MSE的梯度也会接近0
     → 参数更新极慢 → 训练不动!

交叉熵的梯度:∂L_CE/∂w = (1/n) Σᵢ (pᵢ - yᵢ) · xᵢ
     这个梯度中**不包含σ'(z)**!
     → 梯度大小只和预测误差(pᵢ - yᵢ)成正比
     → 预测越差,梯度越大,更新越快
     → 训练更高效!

这就是为什么分类任务用交叉熵而不是MSE——
交叉熵的梯度形式更简洁,避免了Sigmoid梯度消失的问题。

Softmax回归(多分类)

二分类:Sigmoid → 输出1个概率 p,另一个类别的概率是 1-p
多分类:Softmax → 输出k个概率(k为类别数),且概率之和为1

softmax(zᵢ) = e^(zᵢ) / Σⱼ e^(zⱼ)

示例:3个类别(猫、狗、鸟),模型输出 [2.0, 1.0, 0.1]
softmax = [0.659, 0.242, 0.099]
→ 65.9%概率是猫,24.2%概率是狗,9.9%概率是鸟

Softmax回归的损失函数也是交叉熵,只是推广到多分类:
L = -(1/n) Σᵢ Σₖ yᵢₖ · log(pᵢₖ)
其中yᵢₖ是one-hot编码(只有正确类别的位置是1,其余是0)

4.4 阶段总结

本阶段核心公式速查表

1. 梯度下降:θ = θ - η·∇L
2. 线性回归:y = Xw + b
3. 正规方程:w = (X^TX)^(-1)·X^Ty
4. Sigmoid:σ(z) = 1/(1+e^(-z))
5. Sigmoid导数:σ'(z) = σ(z)·(1-σ(z))
6. 交叉熵:L = -[y·log(p) + (1-y)·log(1-p)]
7. Softmax:softmax(zᵢ) = e^(zᵢ)/Σe^(zⱼ)
8. L2正则化:L_total = L + λ·Σw²
9. 贝叶斯:P(A|B) = P(B|A)·P(A)/P(B)
10. 最大似然:θ* = argmax Σlog P(xᵢ|θ)
11. 链式法则:dy/dx = dy/du · du/dx
12. SVD:A = U·Σ·V^T

📝 自测题

  1. Python:写一个函数,输入一个列表,返回其中所有偶数的平方和
  2. 微积分:求 f(x) = x³·eˣ 的导数
  3. 线性代数:计算两个2×2矩阵的乘积,并解释每一步在做什么
  4. 概率论:解释什么是最大似然估计,给出一维高斯分布的MLE推导
  5. 梯度下降:解释学习率过大和过小分别会带来什么问题,画出loss曲线示意图
  6. 线性回归:正规方程和梯度下降各有什么优缺点?什么时候用哪个?
  7. 逻辑回归:从最大似然估计推导出交叉熵损失函数
  8. 正则化:L1和L2正则化分别有什么效果?为什么L1能产生稀疏解?
  9. Sigmoid:推导Sigmoid的导数公式σ’(x) = σ(x)·(1-σ(x))
  10. 综合:解释神经网络的训练过程涉及哪些数学知识,它们各自起什么作用

📚 推荐补充资源

知识点推荐资源说明
Python《Python编程:从入门到实践》零基础友好
NumPyNumPy官方Quickstart快速上手
微积分3Blue1Brown《微积分的本质》可视化理解,B站有中文字幕
线性代数3Blue1Brown《线性代数的本质》神级教程,必看
概率论可汗学院概率论课程零基础友好
机器学习吴恩达《机器学习》课程经典入门课程