预训练与微调:大模型如何“学习知识”?
引言:知识的两阶段习得
Transformer架构的千亿参数模型(如GPT、BERT)并非直接针对具体任务训练,而是通过预训练(Pre-training)与微调(Fine-tuning)两阶段实现知识的泛化与迁移。预训练阶段从海量无标注数据中学习语言的通用模式,微调阶段则针对下游任务进行参数校准。本文将从数学原理和代码实现角度,拆解这一过程的底层逻辑。
一、预训练:无监督学习的知识沉淀
-
核心目标函数
预训练的核心是通过自监督任务(Self-supervised Learning)从无标注数据中提取语义特征。以BERT为例,其采用**掩码语言模型(MLM)和下一句预测(NSP)**双任务驱动:
• MLM:随机掩码输入token(如15%概率),模型预测被掩码部分:
L MLM = − ∑ i ∈ M log P ( x i ∣ x \ M ) \mathcal{L}_{\text{MLM}} = -\sum_{i \in M} \log P(x_i | x_{\backslash M}) LMLM=−i∈M∑logP(xi∣x\M)
其中 ( M ) 为掩码位置集合,( x_{\backslash M} ) 为未掩码上下文。
• NSP:判断两个句子是否为连续关系:
L NSP = − log P ( y ∣ CLS ) \mathcal{L}_{\text{NSP}} = -\log P(y | \text{CLS}) LNSP=−logP(y∣CLS)
其中 ( y \in {0,1} ) 表示是否为相邻句,CLS为分类标记的嵌入向量。数学意义:MLM迫使模型理解双向上下文,NSP增强句子级语义建模能力。
-
代码实现(MLM损失函数)
class MLMLoss(nn.Module): def __init__(self, vocab_size): super().__init__() self.criterion = nn.CrossEntropyLoss(ignore_index=-100) def forward(self, logits, masked_labels): # logits: (batch_size, seq_len, vocab_size) # masked_labels: (batch_size, seq_len),未掩码位置为-100 loss = self.criterion(logits.view(-1, logits.size(-1)), masked_labels.view(-1)) return loss
关键点:仅计算被掩码位置的损失,忽略其他位置。
二、微调:任务适配的参数校准
-
微调策略
• 全参数微调:直接更新所有参数,适用于数据量充足的任务(如GLUE)。
• 参数高效微调(Parameter-Efficient Fine-tuning, PEFT):
◦ Adapter:在Transformer层中插入小型神经网络模块,仅训练新增参数。
◦ LoRA(Low-Rank Adaptation):对权重矩阵进行低秩分解,优化低秩增量矩阵:
W = W 0 + Δ W = W 0 + B A T ( B ∈ R d × r , A ∈ R r × d ) W = W_0 + \Delta W = W_0 + BA^T \quad (B \in \mathbb{R}^{d \times r}, A \in \mathbb{R}^{r \times d}) W=W0+ΔW=W0+BAT(B∈Rd×r,A∈Rr×d)
其中 ( r \ll d ),参数量减少至 ( 2rd )。 -
LoRA的代码实现
class LoRALayer(nn.Module): def __init__(self, in_dim, out_dim, rank=8): super().__init__() self.A = nn.Parameter(torch.randn(in_dim, rank)) self.B = nn.Parameter(torch.zeros(rank, out_dim)) self.original_weight = nn.Parameter(torch.Tensor(out_dim, in_dim)) # 预训练权重 def forward(self, x): delta_W = torch.matmul(self.A, self.B) # (in_dim, out_dim) return F.linear(x, self.original_weight + delta_W.T)
优势:冻结原始参数,仅优化 ( A ) 和 ( B ),显存占用降低90%以上。
三、知识迁移的数学本质
-
特征复用与任务对齐
• 预训练模型的参数 ( \theta_{\text{pre}} ) 编码了语言空间的通用特征,微调通过优化 ( \theta_{\text{pre}} + \Delta\theta ) 使其适应目标任务的分布:
θ final = arg min θ ∑ ( x , y ) ∈ D task L ( f θ ( x ) , y ) \theta_{\text{final}} = \arg\min_{\theta} \sum_{(x,y) \in D_{\text{task}}} \mathcal{L}(f_\theta(x), y) θfinal=argθmin(x,y)∈Dtask∑L(fθ(x),y)
• 梯度视角:微调初期,低层梯度主要调整局部特征(如词法),高层梯度聚焦于语义组合(如句法、逻辑)。 -
跨任务泛化能力来源
• 表征空间对齐:预训练模型的特征空间覆盖广泛的语言现象,微调通过少量样本调整分类边界(如情感极性、实体类型)。
• 注意力模式迁移:预训练中学习到的注意力头(如指代消解、语法依赖)可直接复用至下游任务。
四、实战案例:BERT的文本分类微调
-
模型结构修改
class BertForClassification(nn.Module): def __init__(self, bert_model, num_labels): super().__init__() self.bert = bert_model # 预训练BERT模型 self.classifier = nn.Linear(bert_model.config.hidden_size, num_labels) def forward(self, input_ids, attention_mask): outputs = self.bert(input_ids, attention_mask) cls_embedding = outputs.last_hidden_state[:, 0, :] # 取CLS标记 logits = self.classifier(cls_embedding) return logits
设计逻辑:利用CLS标记的聚合表示作为分类特征。
-
训练流程
# 加载预训练模型 model = BertForClassification(pretrained_bert, num_labels=2) # 冻结部分层(可选) for param in model.bert.encoder.layer[:6].parameters(): param.requires_grad = False # 定义优化器 optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
调参技巧:
• 学习率通常设为预训练的1/10(如2e-5 vs. 预训练的2e-4)。
• 逐步解冻(Layer-wise Unfreezing)可提升稳定性。
五、预训练与微调的局限与改进
-
挑战
• 灾难性遗忘:微调可能覆盖预训练中的通用知识。
• 数据偏差放大:下游任务数据分布偏斜时,模型易过拟合。 -
改进方向
• 提示微调(Prompt Tuning):通过模板将任务转化为预训练目标的填空形式(如“情感:{text}。答案是:[MASK]”)。
• 对比学习微调:引入对比损失(Contrastive Loss),增强同类样本的表示一致性:
L contrast = − log e s ( z i , z j + ) / τ ∑ k = 1 N e s ( z i , z k − ) / τ \mathcal{L}_{\text{contrast}} = -\log \frac{e^{s(z_i, z_j^+)/\tau}}{\sum_{k=1}^N e^{s(z_i, z_k^-)/\tau}} Lcontrast=−log∑k=1Nes(zi,zk−)/τes(zi,zj+)/τ
其中 ( z_i ) 为锚点样本,( z_j^+ ) 为正例,( z_k^- ) 为负例。
总结:从“死记硬背”到“举一反三”
预训练通过无监督学习构建语言理解的“基础骨架”,微调则通过少量标注数据完成“肌肉塑造”,两者结合使大模型兼具通用性与特异性。未来,参数高效微调与多任务联合训练将是降低算力门槛、提升模型适应性的关键方向。
新时代农民工