资讯动态

从GPT-1到ChatGPT:手把手拆解Transformer解码器预训练的核心代码(附PyTorch实现)

发布时间:2026/9/12 7:15:47 来源:尧图企业网站定制
从零构建GPT-1Transformer解码器实战指南与PyTorch完整实现当我们在2023年谈论大语言模型时ChatGPT、GPT-4这些名字已经家喻户晓。但回溯到2018年GPT-1的诞生才是这场革命的真正起点。与现在动辄千亿参数的大模型相比GPT-1的1.17亿参数显得微不足道但它确立的预训练-微调范式却成为了整个行业的黄金标准。本文将带你深入GPT-1的核心——Transformer解码器架构通过PyTorch代码实现让你不仅理解其原理更能亲手搭建这个改变NLP历史的模型。1. 环境准备与基础组件在开始构建GPT-1之前我们需要配置合适的开发环境。建议使用Python 3.8和PyTorch 1.12版本这些版本在兼容性和性能之间取得了良好平衡。conda create -n gpt1 python3.8 conda activate gpt1 pip install torch1.12.1 torchtext0.13.1 numpy tqdmGPT-1的核心是Transformer解码器堆叠我们先实现几个关键组件import torch import torch.nn as nn import math class LayerNorm(nn.Module): def __init__(self, hidden_size, eps1e-12): super().__init__() self.weight nn.Parameter(torch.ones(hidden_size)) self.bias nn.Parameter(torch.zeros(hidden_size)) self.eps eps def forward(self, x): mean x.mean(-1, keepdimTrue) std x.std(-1, keepdimTrue) return self.weight * (x - mean) / (std self.eps) self.bias注意GPT-1使用的是后归一化(Post-LayerNorm)这与后来Transformer常用的前归一化(Pre-LayerNorm)不同。这个细节对模型训练稳定性有重要影响。2. 自注意力机制实现自注意力是Transformer的核心让我们分解实现这个关键组件class SelfAttention(nn.Module): def __init__(self, hidden_size768, num_heads12): super().__init__() assert hidden_size % num_heads 0 self.num_heads num_heads self.head_dim hidden_size // num_heads self.query nn.Linear(hidden_size, hidden_size) self.key nn.Linear(hidden_size, hidden_size) self.value nn.Linear(hidden_size, hidden_size) self.proj nn.Linear(hidden_size, hidden_size) self.attn_dropout nn.Dropout(0.1) def forward(self, x, maskNone): batch_size, seq_len, hidden_size x.size() # 线性变换得到Q,K,V q self.query(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) k self.key(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) v self.value(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) # 计算注意力分数 scores torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim) if mask is not None: scores scores.masked_fill(mask 0, -1e9) attn torch.softmax(scores, dim-1) attn self.attn_dropout(attn) # 应用注意力权重到V上 out torch.matmul(attn, v).transpose(1, 2).contiguous() out out.view(batch_size, seq_len, hidden_size) return self.proj(out)关键参数说明参数名值说明hidden_size768GPT-1的隐层维度num_heads12多头注意力头数head_dim64每个注意力头的维度(768/12)attn_dropout0.1注意力权重dropout率3. Transformer解码器层构建有了自注意力机制我们现在可以构建完整的Transformer解码器层class TransformerBlock(nn.Module): def __init__(self, hidden_size768, num_heads12, ff_dim3072): super().__init__() self.ln1 LayerNorm(hidden_size) self.attn SelfAttention(hidden_size, num_heads) self.ln2 LayerNorm(hidden_size) self.mlp nn.Sequential( nn.Linear(hidden_size, ff_dim), nn.GELU(), nn.Linear(ff_dim, hidden_size), nn.Dropout(0.1) ) def forward(self, x, maskNone): # 后归一化架构 x x self.attn(self.ln1(x), mask) x x self.mlp(self.ln2(x)) return x提示GPT-1使用的GELU激活函数与后来流行的ReLU不同它更接近生物神经元的激活模式在数学上可以表示为GELU(x) xΦ(x)其中Φ是标准正态分布的累积分布函数。4. 完整GPT-1模型实现现在我们将所有组件组合成完整的GPT-1模型class GPT1(nn.Module): def __init__(self, vocab_size40478, max_len512, hidden_size768, num_layers12, num_heads12, ff_dim3072): super().__init__() self.token_emb nn.Embedding(vocab_size, hidden_size) self.pos_emb nn.Parameter(torch.zeros(1, max_len, hidden_size)) self.drop nn.Dropout(0.1) self.layers nn.ModuleList([ TransformerBlock(hidden_size, num_heads, ff_dim) for _ in range(num_layers) ]) self.ln_f LayerNorm(hidden_size) self.head nn.Linear(hidden_size, vocab_size, biasFalse) # 权重绑定输出层与输入嵌入共享权重 self.head.weight self.token_emb.weight # 初始化 self.apply(self._init_weights) def _init_weights(self, module): if isinstance(module, (nn.Linear, nn.Embedding)): module.weight.data.normal_(mean0.0, std0.02) if isinstance(module, nn.Linear) and module.bias is not None: module.bias.data.zero_() elif isinstance(module, LayerNorm): module.bias.data.zero_() module.weight.data.fill_(1.0) def forward(self, x, maskNone): batch_size, seq_len x.size() assert seq_len self.pos_emb.size(1), 序列长度超过最大位置编码长度 # 获取token嵌入并添加位置编码 tok_emb self.token_emb(x) pos_emb self.pos_emb[:, :seq_len, :] x self.drop(tok_emb pos_emb) # 通过所有Transformer层 for layer in self.layers: x layer(x, mask) x self.ln_f(x) logits self.head(x) return logits模型架构关键点权重绑定输出层的权重与输入嵌入层共享这减少了参数量并提高了训练稳定性位置编码GPT-1使用可学习的位置嵌入而非原始Transformer的正弦余弦编码初始化所有线性层和嵌入层使用均值为0、标准差为0.02的正态分布初始化5. 预训练目标实现GPT-1的核心是语言模型预训练我们实现其训练逻辑def train_step(model, batch, optimizer, device): model.train() inputs, targets batch inputs inputs.to(device) targets targets.to(device) # 创建因果掩码 seq_len inputs.size(1) mask torch.tril(torch.ones(seq_len, seq_len)).to(device) # 前向传播 logits model(inputs, mask) loss nn.CrossEntropyLoss()( logits.view(-1, logits.size(-1)), targets.view(-1) ) # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() return loss.item() def generate_text(model, tokenizer, prompt, max_len20, devicecpu): model.eval() tokens tokenizer.encode(prompt) input_ids torch.tensor([tokens]).to(device) for _ in range(max_len): # 创建因果掩码 seq_len input_ids.size(1) mask torch.tril(torch.ones(seq_len, seq_len)).to(device) # 获取下一个token的logits with torch.no_grad(): logits model(input_ids, mask) # 取最后一个token的logits并采样 next_token torch.argmax(logits[0, -1, :]).unsqueeze(0) input_ids torch.cat([input_ids, next_token.unsqueeze(0)], dim1) return tokenizer.decode(input_ids[0].tolist())注意GPT-1使用标准的自回归语言模型目标即预测下一个token。因果掩码确保模型在预测时只能看到前面的token不能看到未来的token。6. 下游任务微调策略GPT-1的强大之处在于其能够通过微调适应各种下游任务。我们实现其微调逻辑class GPT1ForClassification(nn.Module): def __init__(self, base_model, num_classes): super().__init__() self.transformer base_model self.classifier nn.Linear(base_model.hidden_size, num_classes) def forward(self, x, maskNone): # 获取最后一个token的表示 hidden_states self.transformer(x, mask) last_token hidden_states[:, -1, :] return self.classifier(last_token) def fine_tune(model, train_loader, val_loader, optimizer, num_epochs, device): best_acc 0.0 for epoch in range(num_epochs): model.train() for batch in train_loader: inputs, labels batch inputs, labels inputs.to(device), labels.to(device) # 创建因果掩码 seq_len inputs.size(1) mask torch.tril(torch.ones(seq_len, seq_len)).to(device) # 前向传播 outputs model(inputs, mask) loss nn.CrossEntropyLoss()(outputs, labels) # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() # 验证集评估 val_acc evaluate(model, val_loader, device) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pt) return best_acc不同任务的输入转换策略文本蕴含将前提和假设用分隔符$连接[前提]$[假设]语义相似度对两种顺序分别处理并相加def similarity_forward(model, text1, text2): input1 tokenize(f{text1}${text2}) input2 tokenize(f{text2}${text1}) out1 model(input1)[:, -1, :] out2 model(input2)[:, -1, :] return (out1 out2) / 2问答任务将上下文、问题和每个答案组合[上下文]$[问题]$[答案1] [上下文]$[问题]$[答案2] ...7. 模型训练技巧与优化训练GPT-1这样的大型语言模型需要特别注意以下技巧学习率调度使用带热启动的线性衰减学习率def get_lr(step, warmup_steps, total_steps, max_lr): if step warmup_steps: return max_lr * step / warmup_steps return max_lr * (1 - (step - warmup_steps) / (total_steps - warmup_steps))梯度裁剪防止梯度爆炸torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)混合精度训练节省显存并加速训练scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): logits model(inputs, mask) loss criterion(logits, targets) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()关键超参数设置参考超参数预训练值微调值批量大小6432学习率6e-45e-5预热步数4000100总步数1M10kDropout率0.10.1权重衰减0.010.018. 模型评估与结果分析在实现完整模型后我们需要评估其性能。GPT-1论文中报告了在多个任务上的结果自然语言推理任务表现数据集Accuracy提升幅度MNLI82.1%4.6%QNLI88.1%5.8%RTE56.0%0.2%问答与常识推理任务数据集Accuracy提升幅度RACE59.0%7.6%COPA78.6%9.2%语义相似度任务数据集Accuracy提升幅度STS-B80.0%15.0%MRPC82.3%6.7%可视化分析注意力模式可以帮助我们理解模型的工作原理def plot_attention(model, text, layer0, head0): tokens tokenizer.encode(text) inputs torch.tensor([tokens]).to(device) # 获取注意力权重 with torch.no_grad(): outputs model.transformer(inputs) attn model.transformer.layers[layer].attn.attn # 绘制热力图 plt.figure(figsize(10, 8)) sns.heatmap(attn[0, head].cpu().numpy(), annotTrue, fmt.2f) plt.xticks(range(len(tokens)), tokenizer.decode(tokens), rotation45) plt.yticks(range(len(tokens)), tokenizer.decode(tokens), rotation0) plt.show()通过这样的分析我们可以观察到模型如何建立长距离依赖关系以及不同注意力头如何专注于不同类型的语言模式。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价