神经网络基础、PyTorch 入门与 Transformer 架构剖析
2026-10-03 周五 AI 大模型微调基础 阅读约 30 分钟
本文目录
- 今日学习内容
- 第五阶段:从调用到训练
- 神经网络基础
- [PyTorch 入门实战](#PyTorch 入门实战)
- [Transformer 架构剖析](#Transformer 架构剖析)
- 踩坑记录
- 收获总结
- 明日计划
今日学习内容
今天是 Day 26,第五阶段(AI 大模型微调基础)的第一天 。前四个阶段我们完成了 Java 后端、Spring 全家桶、Vue3 前端、Python 编程、提示工程、LangChain 和 RAG 系统的学习。但从今天开始,我们要进入一个全新的领域 ------ 不再只是「调用」大模型,而是理解模型内部结构,甚至亲手训练和微调。
这就像从「开车」变成「修车」再变成「造车」。今天的学习目标非常明确:
- 神经网络基础:感知机、多层感知机(MLP)、激活函数、反向传播、梯度下降优化器 ------ 这是所有深度学习的基石
- PyTorch 入门:Tensor 张量操作、autograd 自动微分、nn.Module 模型定义、完整的训练循环模板
- Transformer 架构剖析:Self-Attention 自注意力机制、Multi-Head 多头注意力、Position Encoding 位置编码、Encoder-Decoder 结构
**为什么今天如此重要?**神经网络和 Transformer 是大模型微调的理论基础。如果你不理解反向传播,就无法理解 LoRA 为什么能微调;如果你不理解 Self-Attention,就无法理解为什么 Transformer 能处理长文本。今天的内容是整个第五阶段的地基,地基不牢,后面的 LoRA 微调、量化压缩都会变成空中楼阁。
第五阶段:从调用到训练
阶段总览
第五阶段共 7 天,核心目标是从「使用 API 调用大模型」跨越到「理解模型原理并亲手微调」。整个阶段分为四个模块:
| 天数 | 模块 | 核心内容 |
|---|---|---|
| Day 26 | 神经网络基础 + PyTorch + Transformer | 感知机/MLP/反向传播、Tensor/autograd/nn.Module、Self-Attention 机制 |
| Day 27 | HuggingFace Transformers | Pipeline API、Tokenizer 分词、模型加载与推理、模型仓库使用 |
| Day 28 | 数据集构建与预处理 | 数据清洗、中文分词、标注格式、DataLoader、数据增强 |
| Day 29 | LoRA / QLoRA 微调 | PEFT 参数高效微调、LoRA 低秩适配原理、QLoRA 量化微调 |
| Day 30-31 | 模型量化压缩 | INT8/INT4 量化、GPTQ/AWQ 方案、模型部署与推理优化 |
| Day 32 | 微调实战 + 阶段收官 | Llama-Factory 中文微调、文本分类全流程、模型评估与部署 |
学习心态转变:前四个阶段偏「应用开发」,第五阶段偏「算法理解」。不需要成为数学博士,但需要理解核心公式的含义和代码实现。建议多动手跑代码,感受到梯度下降的「直观」比推导公式重要得多。
神经网络基础
感知机:神经元的最小单元
感知机(Perceptron)是神经网络的基础单元,模拟生物神经元的工作方式。它接收多个输入,加权求和后经过一个激活函数输出结果:
数学表达:y = f(w₁x₁ + w₂x₂ + ... + wₙxₙ + b),其中 w 是权重,b 是偏置,f 是激活函数。
单个感知机只能解决线性可分问题(比如 AND/OR 逻辑),对于 XOR 这种非线性问题就无能为力了。这也正是多层感知机(MLP)诞生的原因。
perceptron.py --- 从零实现感知机Python
import numpy as np
class Perceptron:
"""单层感知机:只能解决线性可分问题"""
def __init__(self, input_size: int, lr: float = 0.01):
self.w = np.random.randn(input_size) * 0.01 # 权重初始化
self.b = 0.0 # 偏置
self.lr = lr
def forward(self, x: np.ndarray) -> int:
"""前向传播:加权求和 → 阶跃激活"""
z = np.dot(x, self.w) + self.b
return 1 if z >= 0 else 0
def train(self, X: np.ndarray, y: np.ndarray, epochs: int = 100):
"""感知机学习规则:y_pred ≠ y_true 时更新权重"""
for epoch in range(epochs):
errors = 0
for xi, yi in zip(X, y):
y_pred = self.forward(xi)
if y_pred != yi:
# 核心更新规则:w ← w + lr * (yi - y_pred) * xi
self.w += self.lr * (yi - y_pred) * xi
self.b += self.lr * (yi - y_pred)
errors += 1
if errors == 0:
print(f"收敛于第 {epoch + 1} 轮")
break
# 测试 AND 逻辑(线性可分)
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0,0,0,1]) # AND
p = Perceptron(input_size=2)
p.train(X, y)
print(f"AND 测试: {[p.forward(x) for x in X]}") # [0, 0, 0, 1] ✓
XOR 问题:感知机无法解决 XOR(异或)问题,因为 XOR 不是线性可分的。这正是多层感知机(MLP)需要引入隐藏层和非线性激活函数的原因。1969 年 Minsky 在《Perceptrons》中指出了这个局限,直接导致了 AI 的第一个寒冬。
多层感知机(MLP)与激活函数
MLP 在输入层和输出层之间加入了隐藏层,并引入非线性激活函数。没有激活函数,多层网络等价于单层(线性变换的复合仍是线性变换)。
| 激活函数 | 公式 | 特点 | 适用场景 |
|---|---|---|---|
| Sigmoid | σ(x) = 1/(1+e⁻ˣ) | 输出 (0,1),梯度消失严重 | 二分类输出层 |
| Tanh | tanh(x) = (eˣ-e⁻ˣ)/(eˣ+e⁻ˣ) | 输出 (-1,1),零中心,仍有梯度消失 | RNN 隐藏层 |
| ReLU | ReLU(x) = max(0,x) | 计算快,缓解梯度消失,但 Dead Neuron | 大部分隐藏层 |
| GELU | GELU(x) ≈ x·Φ(x) | 平滑 ReLU,BERT/GPT 默认 | Transformer 模型 |
mlp_numpy.py --- NumPy 手写 MLP(理解反向传播)Python
import numpy as np
class MLP:
"""两层 MLP:输入 → 隐藏层(ReLU) → 输出层(Sigmoid)"""
def __init__(self, in_dim: int, hidden: int, out_dim: int, lr: float = 0.1):
# He 初始化(ReLU 专用)
self.W1 = np.random.randn(in_dim, hidden) * np.sqrt(2.0 / in_dim)
self.b1 = np.zeros((1, hidden))
self.W2 = np.random.randn(hidden, out_dim) * np.sqrt(2.0 / hidden)
self.b2 = np.zeros((1, out_dim))
self.lr = lr
def _relu(self, x): return np.maximum(0, x)
def _relu_grad(self, x): return (x > 0).astype(float)
def _sigmoid(self, x): return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
def _binary_cross_entropy(self, y_pred, y_true):
eps = 1e-15
return -np.mean(y_true * np.log(y_pred + eps) + (1 - y_true) * np.log(1 - y_pred + eps))
def forward(self, X):
"""前向传播:记录中间值用于反向传播"""
self.z1 = X @ self.W1 + self.b1 # (N, hidden)
self.a1 = self._relu(self.z1) # ReLU 激活
self.z2 = self.a1 @ self.W2 + self.b2 # (N, out_dim)
self.a2 = self._sigmoid(self.z2) # Sigmoid 输出
return self.a2
def backward(self, X, y_true):
"""反向传播:链式法则计算梯度"""
m = X.shape[0]
# 输出层梯度:∂L/∂z2 = a2 - y_true(BCE+Sigmoid 合并简化)
dz2 = (self.a2 - y_true) / m
dW2 = self.a1.T @ dz2
db2 = np.sum(dz2, axis=0, keepdims=True)
# 隐藏层梯度:∂L/∂z1 = (dz2 @ W2^T) ⊙ ReLU'(z1)
dz1 = dz2 @ self.W2.T * self._relu_grad(self.z1)
dW1 = X.T @ dz1
db1 = np.sum(dz1, axis=0, keepdims=True)
# 梯度下降更新
self.W2 -= self.lr * dW2; self.b2 -= self.lr * db2
self.W1 -= self.lr * dW1; self.b1 -= self.lr * db1
def train(self, X, y, epochs=2000):
for i in range(epochs):
y_pred = self.forward(X)
loss = self._binary_cross_entropy(y_pred, y)
self.backward(X, y)
if i % 400 == 0:
print(f"Epoch {i}: loss = {loss:.4f}")
# 测试 XOR 问题(MLP 可以解决!)
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([[0],[1],[1],[0]]) # XOR
mlp = MLP(in_dim=2, hidden=4, out_dim=1, lr=0.5)
mlp.train(X, y)
print(f"XOR 预测: {mlp.forward(X).round().flatten()}") # [0. 1. 1. 0.] ✓
学习建议 :用 NumPy 手写一遍 MLP 的反向传播,比看十遍教程都有用。当你亲手推导出 dz1 = dz2 @ W2.T * ReLU'(z1) 这条链式法则时,整个深度学习的训练过程就「通了」。这也是面试中高频考点 ------ 「请手推反向传播」。
梯度下降优化器
梯度下降是神经网络训练的发动机。不同优化器的区别在于「怎么用梯度更新参数」:
| 优化器 | 核心思想 | 优点 | 缺点 |
|---|---|---|---|
| SGD | θ = θ - lr * ∇L | 简单直接 | 收敛慢、震荡大 |
| SGD + Momentum | 加入动量项 v = βv + ∇L | 加速收敛、减少震荡 | 多一个超参数 β |
| Adam | 动量 + 自适应学习率 | 收敛快、鲁棒性好 | 可能不收敛(用 AdamW 修正) |
| AdamW | Adam + 解耦权重衰减 | Transformer 训练标配 | 略慢于 Adam |
optimizers.py --- 优化器效果对比Python
import torch
import torch.nn as nn
import torch.optim as optim
# 构造一个简单的回归问题
torch.manual_seed(42)
X = torch.randn(100, 3)
true_w = torch.tensor([2.0, -1.5, 0.8])
y = X @ true_w + torch.randn(100) * 0.1
def train_with_optimizer(optimizer_cls, lr=0.01, epochs=500):
model = nn.Linear(3, 1)
# 重置权重,保证公平对比
model.weight.data = torch.randn(1, 3) * 0.1
model.bias.data.zero_()
optimizer = optimizer_cls(model.parameters(), lr=lr)
criterion = nn.MSELoss()
losses = []
for _ in range(epochs):
optimizer.zero_grad()
loss = criterion(model(X).squeeze(), y)
loss.backward()
optimizer.step()
losses.append(loss.item())
return losses[-1], model.weight.data
# 对比三种优化器
for name, opt in [("SGD", optim.SGD), ("Adam", optim.Adam), ("AdamW", optim.AdamW)]:
final_loss, weights = train_with_optimizer(opt, lr=0.05)
print(f"{name:>6}: loss={final_loss:.6f}, weights={weights.numpy().round(2)}")
# SGD: loss=0.009232, weights=[ 1.98 -1.49 0.79]
# Adam: loss=0.008615, weights=[ 2.01 -1.51 0.81]
# AdamW: loss=0.008701, weights=[ 2.00 -1.50 0.80]
PyTorch 入门实战
Tensor:PyTorch 的核心
Tensor 可以理解为「能在 GPU 上运行的 NumPy 数组」。它支持自动微分(autograd),这是深度学习框架区别于 NumPy 的关键。
tensor_basics.py --- Tensor 基础操作Python
import torch
# 创建 Tensor
a = torch.tensor([1.0, 2.0, 3.0]) # 从列表创建
b = torch.zeros(2, 3) # 全零矩阵
c = torch.randn(3, 4) # 标准正态分布
d = torch.arange(0, 10, 2) # [0, 2, 4, 6, 8]
# 属性
print(f"shape={c.shape}, dtype={c.dtype}, device={c.device}")
# 运算(与 NumPy 几乎一致)
e = a + 1; f = a * 2; g = torch.matmul(b, c.T) # 矩阵乘法
# GPU 支持
if torch.cuda.is_available():
a_gpu = a.cuda() # 或 a.to('cuda')
print(f"GPU: {a_gpu.device}")
# 与 NumPy 互转(共享内存!)
arr = a.numpy() # Tensor → ndarray
t = torch.from_numpy(arr) # ndarray → Tensor
autograd:自动微分
PyTorch 的 autograd 引擎会自动追踪所有操作,在 backward() 时计算梯度。这是神经网络训练的核心 ------ 你只需要定义前向传播,反向传播全自动。
autograd_demo.py --- 自动微分演示Python
import torch
# requires_grad=True 开启梯度追踪
x = torch.tensor([3.0], requires_grad=True)
w = torch.tensor([2.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)
# 前向计算:y = w * x + b
y = w * x + b # y = 2*3+1 = 7
# 反向传播:计算 ∂y/∂w, ∂y/∂x, ∂y/∂b
y.backward()
print(f"∂y/∂x = {x.grad}") # 2.0 (w)
print(f"∂y/∂w = {w.grad}") # 3.0 (x)
print(f"∂y/∂b = {b.grad}") # 1.0
# 梯度清零(每次 backward 前必须清零)
x.grad.zero_(); w.grad.zero_(); b.grad.zero_()
# 禁用梯度追踪(推理/评估时)
with torch.no_grad():
y = w * x + b # 不追踪计算图,省内存
nn.Module:模型定义
nn_module_demo.py --- 使用 nn.Module 定义神经网络Python
import torch
import torch.nn as nn
class SimpleClassifier(nn.Module):
"""三层神经网络:输入 → 隐藏 → 输出"""
def __init__(self, in_dim: int, hidden: int, num_classes: int):
super().__init__()
self.fc1 = nn.Linear(in_dim, hidden) # 全连接层
self.relu = nn.ReLU()
self.dropout = nn.Dropout(0.3) # 防止过拟合
self.fc2 = nn.Linear(hidden, num_classes)
# 初始化权重:好的初始化 = 成功的一半
def _init_weights(self):
for m in self.modules():
if isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight, mode='fan_in')
nn.init.zeros_(m.bias)
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x # 返回 logits,损失函数内部 softmax
完整训练循环:PyTorch 训练模板
一个标准的 PyTorch 训练循环包含五个步骤,这是所有深度学习项目的骨架:
train_loop.py --- 标准训练循环模板Python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
def train_one_epoch(model, loader, criterion, optimizer, device):
"""单轮训练:5 步标准流程"""
model.train()
total_loss, correct = 0, 0
for X, y in loader:
X, y = X.to(device), y.to(device)
# ① 梯度清零(每次 backward 前必须!)
optimizer.zero_grad()
# ② 前向传播
outputs = model(X)
# ③ 计算损失
loss = criterion(outputs, y)
# ④ 反向传播
loss.backward()
# ⑤ 参数更新
optimizer.step()
total_loss += loss.item() * X.size(0)
correct += (outputs.argmax(1) == y).sum().item()
return total_loss / len(loader.dataset), correct / len(loader.dataset)
def evaluate(model, loader, criterion, device):
"""评估:不计算梯度"""
model.eval()
total_loss, correct = 0, 0
with torch.no_grad():
for X, y in loader:
X, y = X.to(device), y.to(device)
outputs = model(X)
total_loss += criterion(outputs, y).item() * X.size(0)
correct += (outputs.argmax(1) == y).sum().item()
return total_loss / len(loader.dataset), correct / len(loader.dataset)
# 完整训练流程
def train(model, train_loader, val_loader, epochs=20, lr=1e-3):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=lr)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
best_acc = 0.0
for epoch in range(epochs):
train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device)
val_loss, val_acc = evaluate(model, val_loader, criterion, device)
scheduler.step()
if val_acc > best_acc:
best_acc = val_acc
torch.save(model.state_dict(), 'best_model.pt') # 保存最佳模型
print(f"Epoch {epoch+1:2d}: train_loss={train_loss:.4f} train_acc={train_acc:.3f} | val_loss={val_loss:.4f} val_acc={val_acc:.3f}")
print(f"Best val_acc: {best_acc:.3f}")
return model
训练循环五步法记忆口诀 :「清零 → 前向 → 损失 → 反向 → 更新」(zero_grad → forward → loss → backward → step)。面试时画训练流程图,这五步缺一不可。另外记得 model.train() 和 model.eval() 的切换,这会影响 Dropout 和 BatchNorm 的行为。
Transformer 架构剖析
为什么需要 Transformer?
在 Transformer 之前,NLP 领域的主流是 RNN/LSTM。但 RNN 有两个致命问题:无法并行计算 (必须按顺序处理 token)和长距离依赖(梯度消失/爆炸)。Transformer 通过 Self-Attention 机制一举解决了这两个问题,让每个 token 都能直接「看到」序列中的任意位置。
Self-Attention:自注意力机制
Self-Attention 的核心思想:每个词都和其他所有词计算相关性,然后按相关性加权融合信息。
三个关键矩阵:Q(Query,查询) 、K(Key,键) 、V(Value,值)。可以类比数据库查询:Q 是你想查什么,K 是每个条目的索引,V 是条目的实际内容。计算过程:
- Q = X · W_Q,K = X · W_K,V = X · W_V(三个线性变换)
- Attention Score = softmax(Q · K^T / √d_k)(缩放点积注意力)
- Output = Attention Score · V(加权求和)
self_attention.py --- 从零实现 Self-AttentionPython
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class SelfAttention(nn.Module):
"""单头自注意力:Scaled Dot-Product Attention"""
def __init__(self, d_model: int):
super().__init__()
self.d_model = d_model
# Q、K、V 的线性投影矩阵
self.W_Q = nn.Linear(d_model, d_model, bias=False)
self.W_K = nn.Linear(d_model, d_model, bias=False)
self.W_V = nn.Linear(d_model, d_model, bias=False)
def forward(self, x):
"""
x: (batch, seq_len, d_model)
返回: (batch, seq_len, d_model), attention_weights
"""
Q = self.W_Q(x) # (B, L, d)
K = self.W_K(x)
V = self.W_V(x)
# 计算注意力分数:Q @ K^T / sqrt(d_k)
d_k = Q.size(-1)
scores = Q @ K.transpose(-2, -1) / math.sqrt(d_k) # (B, L, L)
# Softmax 归一化 → 注意力权重
attn_weights = F.softmax(scores, dim=-1)
# 加权求和
output = attn_weights @ V # (B, L, d)
return output, attn_weights
# 演示:一个简单句子中的注意力
torch.manual_seed(42)
attn = SelfAttention(d_model=8)
# 模拟 4 个 token 的嵌入向量
x = torch.randn(1, 4, 8) # (batch=1, seq_len=4, d_model=8)
output, weights = attn(x)
print(f"注意力权重矩阵:\n{weights[0].detach().numpy().round(3)}")
# 每行之和 = 1.0(softmax 特性)
print(f"每行之和: {weights[0].sum(dim=-1).numpy()}") # [1. 1. 1. 1.]
Multi-Head Attention:多头注意力
单头注意力只关注一种关系模式,多头注意力则让模型从多个角度同时关注:比如一个头关注语法关系,另一个头关注语义关系,再一个头关注位置关系。
multi_head_attention.py --- 多头注意力实现Python
class MultiHeadAttention(nn.Module):
"""多头注意力:h 个 SelfAttention 并行计算"""
def __init__(self, d_model: int = 512, num_heads: int = 8):
super().__init__()
assert d_model % num_heads == 0, "d_model 必须能被 num_heads 整除"
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads # 每个头的维度
# 合并投影:一次矩阵乘法替代 h 次小矩阵乘法
self.W_QKV = nn.Linear(d_model, 3 * d_model, bias=False)
self.W_O = nn.Linear(d_model, d_model, bias=False) # 输出投影
def forward(self, x):
B, L, _ = x.shape
# 一次性计算 Q、K、V
qkv = self.W_QKV(x) # (B, L, 3*d_model)
Q, K, V = qkv.chunk(3, dim=-1)
# 拆成多头:(B, L, d_model) → (B, num_heads, L, d_k)
Q = Q.view(B, L, self.num_heads, self.d_k).transpose(1, 2)
K = K.view(B, L, self.num_heads, self.d_k).transpose(1, 2)
V = V.view(B, L, self.num_heads, self.d_k).transpose(1, 2)
# 缩放点积注意力(每个头独立计算)
scores = Q @ K.transpose(-2, -1) / math.sqrt(self.d_k)
attn = F.softmax(scores, dim=-1)
out = attn @ V # (B, num_heads, L, d_k)
# 合并多头:(B, num_heads, L, d_k) → (B, L, d_model)
out = out.transpose(1, 2).contiguous().view(B, L, self.d_model)
return self.W_O(out)
Position Encoding:位置编码
Self-Attention 本身是位置无关的 ------ 打乱输入顺序,输出只是相应打乱。但语言是有顺序的!Position Encoding 通过给每个位置添加一个唯一的向量来注入位置信息。Transformer 原始论文使用正弦/余弦编码:
position_encoding.py --- 正弦位置编码Python
class SinusoidalPositionEncoding(nn.Module):
"""Transformer 原始论文的位置编码方案"""
def __init__(self, d_model: int, max_len: int = 5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1).float()
# 频率随维度指数衰减:低维度编码短距离,高维度编码长距离
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term) # 偶数位用 sin
pe[:, 1::2] = torch.cos(position * div_term) # 奇数位用 cos
self.register_buffer('pe', pe.unsqueeze(0)) # (1, max_len, d_model)
def forward(self, x):
return x + self.pe[:, :x.size(1)] # 直接相加
**为什么用 sin/cos?**因为 sin(α+β) 可以表示为 sin(α)cos(β)+cos(α)sin(β),这种线性关系让模型能学习到相对位置信息。GPT 系列后来改用可学习的 Position Embedding,效果更好但失去了外推能力(不能处理超过训练长度的序列)。
Encoder-Decoder 完整结构
Transformer 原始架构包含 Encoder 和 Decoder 两部分。Encoder 处理输入序列,Decoder 生成输出序列。但现代大模型有不同变体:
| 模型 | 架构 | 特点 |
|---|---|---|
| BERT | Encoder-Only | 双向上下文理解,适合分类/抽取 |
| GPT | Decoder-Only | 自回归生成,用 Causal Mask 防止看到未来 |
| T5 / BART | Encoder-Decoder | 完整结构,适合翻译/摘要 |
学习重点:现在绝大多数大模型(GPT-4、DeepSeek、Qwen、Llama)都是 Decoder-Only 架构。理解 Causal Mask(因果掩码)是关键 ------ 在训练时,当前 token 只能看到它之前的 token,不能「偷看」未来。这是自回归生成的本质。
踩坑记录
坑 1:反向传播前忘记 optimizer.zero_grad()
现象 :训练 loss 不收敛,甚至越来越大。因为 PyTorch 默认会累加梯度(accumulate gradient),不清零会导致多次 batch 的梯度混在一起。
解决 :每个 batch 开始时调用 optimizer.zero_grad() 或 model.zero_grad()。如果使用梯度累积(大 batch 模拟),则在累积 N 步后再清零。
坑 2:训练和评估时忘记切换 model.train() / model.eval()
现象:评估时准确率不稳定,每次结果都不一样。因为 Dropout 在训练模式下会随机丢弃神经元,评估时应该关闭。
解决 :训练循环前调用 model.train(),评估循环前调用 model.eval()。BatchNorm 层也会受此影响。
坑 3:Tensor 在 CPU 和 GPU 之间来回拷贝
现象:训练速度极慢,GPU 利用率低。因为每次 forward 时数据在 CPU 上,PyTorch 隐式拷贝到 GPU 后又拷贝回来。
解决 :在训练循环开始时,将 model 和 data 一次性移到 GPU:model = model.to(device),X, y = X.to(device), y.to(device)。避免在循环中频繁 .cpu() 和 .cuda()。
坑 4:CrossEntropyLoss 的输入不需要 Softmax
现象 :模型输出先过 Softmax 再传入 CrossEntropyLoss,训练完全不收敛。因为 nn.CrossEntropyLoss 内部已经集成了 LogSoftmax + NLLLoss。
解决 :模型最后一层直接返回 logits(原始分数),不要加 Softmax。只有在推理时需要概率时才手动调用 F.softmax(output, dim=-1)。
收获总结
今日核心收获
今天是第五阶段的第一天,从「调用 API」正式迈入「理解模型内部」的领域。今天学到的每一个概念,都是后续 LoRA 微调、模型量化、推理优化的理论基础:
- 神经网络本质:多层线性变换 + 非线性激活函数,通过反向传播的链式法则自动计算梯度。用 NumPy 手写一遍 MLP 的反向传播,比看十遍教程都管用
- PyTorch 三件套:Tensor(数据载体 + GPU 支持)、autograd(自动微分引擎)、nn.Module(模型定义框架)。训练循环五步法:清零 → 前向 → 损失 → 反向 → 更新
- Transformer 核心:Self-Attention 让每个 token 能直接关注所有其他 token,Multi-Head 从多个角度并行关注,Position Encoding 注入位置信息。现代大模型(GPT/DeepSeek)都是 Decoder-Only 架构
- 思维转变:从「调包侠」到「理解原理」。知道为什么 LoRA 只微调低秩矩阵、为什么量化会影响精度、为什么需要预热学习率 ------ 这些都需要今天的基础知识
明日计划
Day 27 · HuggingFace Transformers 入门
- HuggingFace 生态概览:Models / Datasets / Spaces / Hub 四大组件
- Pipeline API:一行代码完成情感分析、文本生成、翻译、摘要等任务
- Tokenizer 分词器:BPE/WordPiece 原理、encode/decode、padding/truncation
- 模型加载与推理:AutoModel / AutoTokenizer 自动加载、from_pretrained 缓存机制
- 中文模型实战:Qwen2、ChatGLM 等国产模型的使用与对比