【大模型微调系列-03】 大模型数学基础直观入门

【大模型微调系列-03】 大模型数学基础直观入门

🎯 本章目标:不要害怕数学!我们将通过可视化和简单代码,让你像"看电影"一样理解深度学习的数学原理。记住,深度学习的数学其实就是"让计算机学会调整参数"的艺术。

3.1 理论讲解:深度学习的数学"积木块"

3.1.1 张量与矩阵------数据的"容器"

想象一下,你在整理衣柜。标量 就像一件衣服,向量 就像一排衣服,矩阵 就像一个衣柜的所有衣服,而张量就像整个房间所有衣柜的衣服。在深度学习中,张量就是我们存储和处理数据的"多维容器"。

为什么需要张量?

假设你要处理一张彩色图片:

  • 一个像素的亮度值 → 标量(0维)
  • 一行像素 → 向量(1维)
  • 一张灰度图 → 矩阵(2维)
  • 一张彩色图(RGB三通道)→ 3维张量
  • 100张彩色图一起处理 → 4维张量

标量
单个数字
温度: 25度C 向量
一维数组
一周温度 矩阵
二维表格
多城市温度表 3D张量
立体数据
RGB图像 4D张量
批量数据
100张图片

张量的关键属性
属性 含义 生活类比
形状(shape) 各维度的大小 衣柜的长×宽×高
数据类型(dtype) 存储的数字类型 整数价格 vs 小数重量
设备(device) 存储位置 仓库(CPU) vs 快递站(GPU)
python 复制代码
import torch
import numpy as np

# 从熟悉的Python列表开始
temperature = 25.5  # 标量
week_temps = [25.5, 26.1, 24.8, 25.9, 26.5, 27.0, 26.2]  # 向量

# 转换为PyTorch张量
tensor_scalar = torch.tensor(temperature)
tensor_vector = torch.tensor(week_temps)

print(f"标量张量: {tensor_scalar}, 形状: {tensor_scalar.shape}")
print(f"向量张量: {tensor_vector}, 形状: {tensor_vector.shape}")

# 创建一个矩阵(多个城市的周温度)
cities_temps = torch.tensor([
    [25.5, 26.1, 24.8, 25.9, 26.5, 27.0, 26.2],  # 北京
    [28.5, 29.1, 28.8, 29.9, 30.5, 31.0, 30.2],  # 上海
    [22.5, 23.1, 22.8, 23.9, 24.5, 25.0, 24.2],  # 成都
])
print(f"矩阵形状: {cities_temps.shape}")  # [3, 7] = 3个城市,7天
张量操作的直观理解

原始张量
shape: 2,3,4 reshape操作 新张量
shape: 6,4 新张量
shape: 2,12 新张量
shape: 24 为什么能变形? 总元素数不变
2*3*4 = 24

关键概念:张量就像橡皮泥,只要总体积(元素总数)不变,就可以揉成不同形状。这在神经网络中非常重要,因为不同层需要不同形状的输入。

3.1.2 线性变换与权重矩阵------神经网络的"调味配方"

想象你在调制鸡尾酒。每种基酒的比例(权重)决定了最终的口味。神经网络中的权重矩阵就像这个配方,它决定了输入如何变成输出。

矩阵乘法的几何意义

矩阵乘法本质上是对空间的变换。想象你有一张照片,矩阵乘法可以:

  • 缩放:让照片变大或变小
  • 旋转:让照片转动角度
  • 投影:把3D物体投影到2D平面

输出空间 变换矩阵 输入空间 矩阵乘法 y = Wx 输出向量
-1,4 权重矩阵W
旋转+缩放 输入向量
2,3

神经网络中的线性变换

在神经网络中,每一层都在做这样的事情:

复制代码
输出 = 权重矩阵 × 输入 + 偏置
y = Wx + b

这就像:

  • W(权重):每个特征的重要程度
  • x(输入):原始数据
  • b(偏置):基础分数(即使输入为0也有的输出)
python 复制代码
import torch
import matplotlib.pyplot as plt

# 创建一个简单的线性变换
input_features = 3  # 输入特征数(如:房屋面积、卧室数、位置评分)
output_features = 2  # 输出特征数(如:价格预测、投资价值)

# 权重矩阵:决定每个输入特征如何影响输出
W = torch.tensor([
    [0.5, 2.0, -1.0],   # 第一个输出的权重
    [1.0, -0.5, 3.0]    # 第二个输出的权重
])

# 输入数据
x = torch.tensor([100.0, 3.0, 8.0])  # [面积, 卧室数, 位置评分]

# 线性变换
y = torch.matmul(W, x)
print(f"输入: {x}")
print(f"输出: {y}")
print(f"解释: 第一个输出 = 0.5×100 + 2.0×3 + (-1.0)×8 = {y[0]}")

3.1.3 导数与梯度------找到"下山"的方向

想象你蒙着眼睛站在山坡上,想要走到山谷(最低点)。你能做的就是:

  1. 感受脚下的坡度(导数)
  2. 判断哪个方向最陡(梯度)
  3. 朝着下坡方向走一小步(梯度下降)
导数:变化率的度量

导数告诉我们"如果参数变化一点点,结果会变化多少"。
当前位置
参数w=2
损失L=4 计算导数 导数=4
意味着: w增加1
L约增加4 w减少1
L约减少4 所以应该
减小w!

梯度:多维空间的"指南针"

当有多个参数时,梯度就像一个指南针,指向函数增长最快的方向。我们要朝相反方向走(负梯度方向)才能找到最小值。

python 复制代码
# 可视化梯度下降过程
import numpy as np
import matplotlib.pyplot as plt

# 定义一个简单的损失函数(碗状)
def loss_function(w):
    return w**2

# 计算导数
def gradient(w):
    return 2*w

# 梯度下降过程
w = 5.0  # 初始位置
learning_rate = 0.1
history = [w]

for step in range(20):
    grad = gradient(w)
    w = w - learning_rate * grad  # 更新规则
    history.append(w)
    if step % 5 == 0:
        print(f"步骤 {step}: w={w:.3f}, 梯度={grad:.3f}, 损失={loss_function(w):.3f}")
链式法则:复合函数的"多米诺骨牌"

深度网络就像多层嵌套的函数。链式法则告诉我们如何计算最内层参数对最终输出的影响。
dL/dy dL/dz2 dL/da1 dL/dz1 dL/dW1 输入x 第一层
z1=W1x 激活
a1=ReLU z1 第二层
z2=W2a1 输出
y=softmax z2 损失L

就像推倒多米诺骨牌,每一块倒下都会影响下一块。链式法则让我们能够计算第一块骨牌(输入层参数)对最后一块(损失函数)的影响。

3.1.4 损失函数与优化------模型的"成绩单"

损失函数就像考试分数,告诉模型"你错了多少"。不同类型的问题需要不同的评分标准。

常见损失函数对比

损失函数类型 回归问题 分类问题 MSE均方误差
预测值与真实值
差距的平方 MAE平均绝对误差
预测值与真实值
差距的绝对值 交叉熵
预测概率分布与
真实分布的差异 Hinge Loss
SVM分类
间隔最大化

MSE vs 交叉熵的直观理解

MSE(均方误差):适合连续值预测

  • 预测房价:预测30万,实际32万 → 误差=(32-30)²=4
  • 特点:对大误差惩罚更重(平方放大)

交叉熵:适合分类问题

  • 预测是猫的概率:0.9,实际是猫 → 损失=-log(0.9)≈0.1(很小)
  • 预测是猫的概率:0.1,实际是猫 → 损失=-log(0.1)≈2.3(很大)
  • 特点:对错误但很自信的预测惩罚极重
python 复制代码
import torch
import torch.nn.functional as F

# MSE示例:预测温度
predicted_temp = torch.tensor([25.0, 26.5, 24.8])
actual_temp = torch.tensor([24.5, 27.0, 24.0])
mse_loss = F.mse_loss(predicted_temp, actual_temp)
print(f"MSE损失: {mse_loss:.4f}")

# 交叉熵示例:分类动物(猫、狗、鸟)
# 模型输出的原始分数(logits)
logits = torch.tensor([[2.0, 1.0, 0.1]])  # 认为是猫的可能性最大
target = torch.tensor([0])  # 真实标签:0代表猫
ce_loss = F.cross_entropy(logits, target)
print(f"交叉熵损失: {ce_loss:.4f}")

# 转换为概率看看
probs = F.softmax(logits, dim=1)
print(f"预测概率: 猫={probs[0,0]:.2f}, 狗={probs[0,1]:.2f}, 鸟={probs[0,2]:.2f}")
优化器:如何"下山"

优化器决定了我们如何利用梯度来更新参数。就像下山有不同策略:
SGD
随机梯度下降 最简单
沿梯度方向走 Momentum
动量 考虑惯性
像滚雪球 Adam
自适应 自动调节步长
智能导航

  • SGD:老老实实按梯度走,可能很慢
  • Momentum:考虑之前的运动方向,能冲过小坑
  • Adam:自适应调整每个参数的学习率,通常是最佳选择

3.1.5 Softmax与概率------把分数变成概率

Softmax就像"分蛋糕"------把模型的原始输出分数转换成和为1的概率分布。

为什么需要Softmax?

模型的原始输出可能是任意数字:

  • 猫:5.2
  • 狗:2.1
  • 鸟:-1.3

这些数字没有直观含义。Softmax将它们转换为概率:

  • 猫:92%
  • 狗:7%
  • 鸟:1%

现在我们能清楚地说:"模型认为这92%是猫"。
原始分数
logits 指数化
e^x 归一化
除以总和 概率分布
和为1 5.2, 2.1, -1.3 181.3, 8.2, 0.3 181.3/189.8=0.955
8.2/189.8=0.043
0.3/189.8=0.002 95.5%, 4.3%, 0.2%

温度参数:控制模型的"自信度"

温度(Temperature)就像调节模型的"性格":

  • 低温(T<1):让模型更自信、更确定
  • 高温(T>1):让模型更保守、更均匀
python 复制代码
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt

# 原始分数
logits = torch.tensor([3.0, 1.0, 0.2])

# 不同温度下的概率分布
temperatures = [0.5, 1.0, 2.0, 5.0]
fig, axes = plt.subplots(1, 4, figsize=(12, 3))

for idx, temp in enumerate(temperatures):
    probs = F.softmax(logits / temp, dim=0)
    axes[idx].bar(['类别A', '类别B', '类别C'], probs.numpy())
    axes[idx].set_title(f'温度={temp}')
    axes[idx].set_ylim([0, 1])
    
plt.suptitle('温度对Softmax概率分布的影响')
plt.tight_layout()
# plt.show()  # 如果在notebook中运行

print("温度越低,分布越尖锐(更确定)")
print("温度越高,分布越平滑(更不确定)")

3.1.6 反向传播流程------误差的"责任追溯"

反向传播就像侦探破案,从犯罪现场(输出误差)开始,一步步追溯每个嫌疑人(参数)的责任。

完整的前向+反向流程

参数更新 反向传播 前向传播 W2 = W2 - lr*dL/dW2 W1 = W1 - lr*dL/dW1 损失对输出梯度
dL/dy 输出对h2梯度
dL/dh2 h2对W2梯度
dL/dW2 h2对a1梯度
dL/da1 a1对h1梯度
dL/dh1 h1对W1梯度
dL/dW1 第一层
h1 = W1*x + b1 输入数据 x 激活函数
a1 = ReLU h1 第二层
h2 = W2*a1 + b2 输出
y = Softmax h2 计算损失
L = CrossEntropy y,target

梯度的反向流动

想象水从山顶(损失函数)流下来:

  1. 水流分叉:遇到加法节点,梯度复制
  2. 水流汇聚:遇到乘法节点,梯度相乘
  3. 水流调节:遇到激活函数,梯度被调节

每个参数受到的"水压"(梯度)决定了它需要调整多少。

python 复制代码
# 简单的反向传播示例
import torch

# 构建一个简单的计算图
x = torch.tensor(2.0, requires_grad=True)
w = torch.tensor(3.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)

# 前向传播
y = w * x + b  # y = 3*2 + 1 = 7
z = y ** 2     # z = 7^2 = 49
loss = z       # 损失就是z

# 反向传播
loss.backward()

print(f"前向计算: x={x.item()}, w={w.item()}, b={b.item()}")
print(f"中间结果: y={y.item()}, z={z.item()}")
print(f"\n梯度(责任分配):")
print(f"x的梯度: {x.grad.item()} (x改变1,loss改变{x.grad.item()})")
print(f"w的梯度: {w.grad.item()} (w改变1,loss改变{w.grad.item()})")
print(f"b的梯度: {b.grad.item()} (b改变1,loss改变{b.grad.item()})")
梯度消失和爆炸

在深层网络中,梯度可能会:

  • 消失:梯度越传越小,最后变成0(像电话传话,传到最后听不清)
  • 爆炸:梯度越传越大,最后变成无穷大(像雪崩,越滚越大)

解决方案:

  • 使用合适的激活函数(ReLU而非Sigmoid)
  • 批归一化(Batch Normalization)
  • 梯度裁剪(Gradient Clipping)
  • 残差连接(ResNet的核心思想)

3.2 实操案例

实操1:张量操作实战

python 复制代码
import torch
import numpy as np

# === 1. 创建张量的多种方式 ===
print("=== 张量创建方法 ===")

# 从Python列表
tensor_from_list = torch.tensor([1, 2, 3, 4])
print(f"从列表: {tensor_from_list}")

# 全零张量
zeros = torch.zeros(2, 3)
print(f"全零: \n{zeros}")

# 全一张量
ones = torch.ones(2, 3)
print(f"全一: \n{ones}")

# 随机张量
random = torch.randn(2, 3)  # 标准正态分布
print(f"随机: \n{random}")

# 等差数列
arange = torch.arange(0, 10, 2)  # 0到10,步长2
print(f"等差: {arange}")

# === 2. 张量形状操作 ===
print("\n=== 形状操作 ===")

x = torch.randn(4, 3)
print(f"原始形状: {x.shape}")

# reshape/view
x_reshaped = x.reshape(2, 6)
print(f"reshape后: {x_reshaped.shape}")

# squeeze去除维度为1的轴
x_with_1 = torch.randn(1, 3, 1, 4)
x_squeezed = x_with_1.squeeze()
print(f"squeeze前: {x_with_1.shape}, squeeze后: {x_squeezed.shape}")

# unsqueeze添加维度
x_expanded = x.unsqueeze(0)  # 在第0维添加
print(f"unsqueeze后: {x_expanded.shape}")

# === 3. 张量运算 ===
print("\n=== 基本运算 ===")

a = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
b = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32)

print(f"加法: \n{a + b}")
print(f"逐元素乘法: \n{a * b}")
print(f"矩阵乘法: \n{torch.matmul(a, b)}")

# === 4. 自动微分预览 ===
print("\n=== 自动微分 ===")

x = torch.tensor(2.0, requires_grad=True)
y = x ** 2 + 3 * x + 1
y.backward()
print(f"y = x^2 + 3x + 1, 当x=2时")
print(f"y的值: {y.item()}")
print(f"dy/dx: {x.grad.item()}")

实操2:构建迷你神经网络

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.pyplot as plt

# 定义一个简单的两层网络
class MiniNetwork(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super().__init__()
        self.layer1 = nn.Linear(input_size, hidden_size)
        self.layer2 = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        # 前向传播
        x = self.layer1(x)
        x = F.relu(x)  # 激活函数
        x = self.layer2(x)
        return x

# 创建网络
net = MiniNetwork(input_size=3, hidden_size=4, output_size=2)

# 生成模拟数据
batch_size = 5
x = torch.randn(batch_size, 3)
target = torch.randint(0, 2, (batch_size,))

# 前向传播
output = net(x)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")

# 计算损失
loss = F.cross_entropy(output, target)
print(f"损失值: {loss.item():.4f}")

# 反向传播
loss.backward()

# 查看梯度
for name, param in net.named_parameters():
    if param.grad is not None:
        print(f"{name}的梯度形状: {param.grad.shape}")

实操3:可视化梯度下降

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

# 定义一个简单的二次函数
def f(x):
    return (x - 3) ** 2 + 1

def gradient_f(x):
    return 2 * (x - 3)

# 梯度下降
x_history = []
loss_history = []

x = -2.0  # 起始点
learning_rate = 0.1
n_steps = 30

for i in range(n_steps):
    x_history.append(x)
    loss_history.append(f(x))
    
    # 计算梯度并更新
    grad = gradient_f(x)
    x = x - learning_rate * grad

# 可视化
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))

# 左图:函数曲线和优化路径
x_range = np.linspace(-3, 8, 100)
y_range = f(x_range)

ax1.plot(x_range, y_range, 'b-', label='f(x)=(x-3)²+1')
ax1.plot(x_history, loss_history, 'ro-', markersize=4, label='优化路径')
ax1.axvline(x=3, color='g', linestyle='--', label='最优解 x=3')
ax1.set_xlabel('参数 x')
ax1.set_ylabel('损失值')
ax1.set_title('梯度下降过程')
ax1.legend()
ax1.grid(True, alpha=0.3)

# 右图:损失值变化
ax2.plot(range(n_steps), loss_history, 'b-o', markersize=4)
ax2.set_xlabel('迭代步数')
ax2.set_ylabel('损失值')
ax2.set_title('损失值随迭代下降')
ax2.grid(True, alpha=0.3)

plt.tight_layout()
# plt.show()

print(f"起始点: x={x_history[0]:.2f}, 损失={loss_history[0]:.2f}")
print(f"终止点: x={x_history[-1]:.2f}, 损失={loss_history[-1]:.2f}")
print(f"最优解: x=3.00, 损失=1.00")

实操4:Softmax温度实验

python 复制代码
import torch
import torch.nn.functional as F

def apply_temperature_softmax(logits, temperature):
    """应用温度调节的softmax"""
    return F.softmax(logits / temperature, dim=0)

# 模拟模型输出
logits = torch.tensor([4.0, 2.0, 1.0, 0.5, 0.1])
classes = ['猫', '狗', '鸟', '鱼', '兔']

print("原始分数(logits):", logits.numpy())
print("\n不同温度下的概率分布:")
print("-" * 50)

temperatures = [0.5, 1.0, 2.0, 5.0]

for temp in temperatures:
    probs = apply_temperature_softmax(logits, temp)
    print(f"\n温度 T={temp}:")
    for cls, prob in zip(classes, probs):
        bar = '█' * int(prob * 50)
        print(f"  {cls}: {prob:.3f} {bar}")
    
    # 计算熵(不确定性度量)
    entropy = -torch.sum(probs * torch.log(probs + 1e-10))
    print(f"  熵值: {entropy:.3f} (越大越不确定)")

3.3 章节总结

核心要点思维导图

mindmap root((深度学习数学基础)) 张量 数据的容器 多维数组 形状操作 线性变换 矩阵乘法 权重和偏置 空间变换 梯度 导数 方向导数 链式法则 优化 损失函数 梯度下降 优化器选择 概率 Softmax 温度调节 概率分布 反向传播 误差传递 参数更新 梯度流动

与实际深度学习的联系

本章学习的数学概念在实际深度学习中的应用:

  1. 张量操作 → 数据预处理、批处理、特征工程
  2. 线性变换 → 全连接层、卷积层的基础
  3. 梯度计算 → 模型训练的核心机制
  4. 损失函数 → 评估模型性能、指导优化方向
  5. Softmax → 分类任务的概率输出
  6. 反向传播 → 自动计算所有参数的梯度

重要提示

必须掌握

  • 张量的基本概念和形状操作
  • 梯度下降的直观理解
  • 损失函数的作用
  • 前向传播和反向传播的流程

📚 了解即可

  • 具体的数学推导过程
  • 优化器的内部算法细节
  • 梯度消失/爆炸的数学原因

记住:深度学习的数学不是障碍,而是工具。就像开车不需要理解发动机的每个零件,使用深度学习也不需要证明每个数学定理。重要的是理解概念,知道什么时候用什么工具!