news 2026/7/9 10:12:46

BERT 预训练实战:PyTorch 复现 MLM 与 NSP 任务,Loss 降至 0.15

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
BERT 预训练实战:PyTorch 复现 MLM 与 NSP 任务,Loss 降至 0.15

BERT 预训练实战:PyTorch 复现 MLM 与 NSP 任务,Loss 降至 0.15

在自然语言处理领域,BERT(Bidirectional Encoder Representations from Transformers)的出现彻底改变了我们对语言模型的理解方式。与传统的单向语言模型不同,BERT通过双向Transformer编码器实现了对文本上下文的全方位理解。本文将带您从零开始,使用PyTorch框架复现BERT的核心预训练任务——Masked Language Model(MLM)和Next Sentence Prediction(NSP),并分享如何将训练Loss降至0.15的实战经验。

1. 环境准备与数据预处理

1.1 安装依赖库

首先需要安装必要的Python库。建议使用Python 3.8+环境和最新版本的PyTorch:

pip install torch torchtext transformers tqdm numpy pandas

1.2 数据集准备

BERT预训练通常使用大规模文本语料库。对于实验目的,我们可以使用Wikipedia或BookCorpus的小型子集:

from torchtext.datasets import WikiText2 # 加载WikiText2数据集 train_iter, valid_iter, test_iter = WikiText2() text_data = [text for text in train_iter if len(text.split()) > 10] # 过滤过短句子

1.3 构建词汇表与Tokenizer

BERT使用WordPiece分词方法。我们可以使用HuggingFace的BertTokenizer,也可以自定义实现:

from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') vocab_size = tokenizer.vocab_size # 30522

2. BERT模型架构实现

2.1 Transformer编码器层

BERT的核心是Transformer编码器。我们先实现多头注意力机制:

import torch import torch.nn as nn import math class MultiHeadAttention(nn.Module): def __init__(self, hidden_size=768, num_heads=12): super().__init__() self.hidden_size = hidden_size 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.dropout = nn.Dropout(0.1) self.out = nn.Linear(hidden_size, hidden_size) def forward(self, x, attention_mask=None): batch_size = x.size(0) # 线性变换并分割为多头 Q = self.query(x).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2) K = self.key(x).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2) V = self.value(x).view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2) # 计算注意力分数 scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.head_dim) # 应用注意力掩码 if attention_mask is not None: scores = scores.masked_fill(attention_mask == 0, -1e9) # 计算注意力权重 attn_weights = torch.softmax(scores, dim=-1) attn_weights = self.dropout(attn_weights) # 应用注意力权重到V context = torch.matmul(attn_weights, V) context = context.transpose(1, 2).contiguous().view(batch_size, -1, self.hidden_size) return self.out(context)

2.2 完整的BERT模型

基于Transformer编码器构建BERT模型:

class BERT(nn.Module): def __init__(self, vocab_size, hidden_size=768, num_layers=12, num_heads=12): super().__init__() self.embedding = BERTEmbedding(vocab_size, hidden_size) self.encoder_layers = nn.ModuleList([ TransformerEncoderLayer(hidden_size, num_heads) for _ in range(num_layers) ]) def forward(self, input_ids, segment_ids, attention_mask=None): # 获取嵌入表示 x = self.embedding(input_ids, segment_ids) # 通过所有Transformer层 for layer in self.encoder_layers: x = layer(x, attention_mask) return x

3. 预训练任务实现

3.1 Masked Language Model (MLM)

MLM任务随机遮盖输入token并预测被遮盖的token:

class MLMTask(nn.Module): def __init__(self, hidden_size, vocab_size): super().__init__() self.dense = nn.Linear(hidden_size, hidden_size) self.layer_norm = nn.LayerNorm(hidden_size) self.decoder = nn.Linear(hidden_size, vocab_size) def forward(self, hidden_states, masked_positions): # 只选择被遮盖位置的隐藏状态 masked_states = hidden_states.gather(1, masked_positions.unsqueeze(-1).expand(-1, -1, hidden_states.size(-1))) # 通过MLM头部 x = self.dense(masked_states) x = torch.gelu(x) x = self.layer_norm(x) logits = self.decoder(x) return logits

3.2 Next Sentence Prediction (NSP)

NSP任务预测两个句子是否是连续的:

class NSPTask(nn.Module): def __init__(self, hidden_size): super().__init__() self.seq_relationship = nn.Linear(hidden_size, 2) def forward(self, pooled_output): logits = self.seq_relationship(pooled_output) return logits

4. 训练流程与优化

4.1 数据批处理

实现动态遮盖策略的DataLoader:

from torch.utils.data import Dataset, DataLoader import random class BERTDataset(Dataset): def __init__(self, texts, tokenizer, max_len=128): self.texts = texts self.tokenizer = tokenizer self.max_len = max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): # 随机选择两个句子 text = self.texts[idx] sentences = text.split('.') if len(sentences) < 2: return self[(idx + 1) % len(self)] sent_a, sent_b = random.sample(sentences, 2) # 50%概率使用连续句子 if random.random() > 0.5: is_next = 1 sent_b = sentences[sentences.index(sent_a) + 1] else: is_next = 0 # 编码句子对 encoded = self.tokenizer( sent_a, sent_b, max_length=self.max_len, padding='max_length', truncation=True, return_tensors='pt' ) # 创建MLM标签 input_ids = encoded['input_ids'].squeeze(0) mlm_labels = input_ids.clone() # 随机遮盖15%的token mask_indices = torch.rand(input_ids.shape) < 0.15 # 80%替换为[MASK], 10%随机token, 10%保持不变 mask_token = self.tokenizer.mask_token_id random_tokens = torch.randint(0, len(self.tokenizer), input_ids.shape) input_ids[mask_indices] = torch.where( torch.rand(input_ids.shape) < 0.8, mask_token, torch.where( torch.rand(input_ids.shape) < 0.5, random_tokens, input_ids ) )[mask_indices] return { 'input_ids': input_ids, 'attention_mask': encoded['attention_mask'].squeeze(0), 'token_type_ids': encoded['token_type_ids'].squeeze(0), 'mlm_labels': mlm_labels, 'nsp_labels': torch.tensor(is_next, dtype=torch.long) }

4.2 训练循环

实现包含MLM和NSP的联合训练:

def train(model, dataloader, optimizer, device, epochs=10): model.train() total_loss = 0 mlm_criterion = nn.CrossEntropyLoss(ignore_index=0) nsp_criterion = nn.CrossEntropyLoss() for epoch in range(epochs): for batch in tqdm(dataloader, desc=f'Epoch {epoch+1}'): # 准备输入数据 input_ids = batch['input_ids'].to(device) attention_mask = batch['attention_mask'].to(device) token_type_ids = batch['token_type_ids'].to(device) mlm_labels = batch['mlm_labels'].to(device) nsp_labels = batch['nsp_labels'].to(device) # 获取被遮盖的位置 masked_positions = (input_ids == tokenizer.mask_token_id).nonzero(as_tuple=True)[1] # 前向传播 outputs = model(input_ids, token_type_ids, attention_mask) # 计算MLM损失 mlm_logits = mlm_head(outputs, masked_positions) mlm_loss = mlm_criterion( mlm_logits.view(-1, vocab_size), mlm_labels.view(-1) ) # 计算NSP损失 pooled_output = outputs[:, 0, :] # [CLS] token nsp_logits = nsp_head(pooled_output) nsp_loss = nsp_criterion(nsp_logits, nsp_labels) # 总损失 loss = mlm_loss + nsp_loss total_loss += loss.item() # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() avg_loss = total_loss / len(dataloader) print(f'Epoch {epoch+1}, Loss: {avg_loss:.4f}') total_loss = 0

5. 调优技巧与Loss优化

5.1 学习率调度

使用带热启动的线性学习率调度:

from torch.optim import AdamW from torch.optim.lr_scheduler import LambdaLR optimizer = AdamW(model.parameters(), lr=5e-5, weight_decay=0.01) def lr_lambda(current_step): warmup_steps = 1000 if current_step < warmup_steps: return float(current_step) / float(max(1, warmup_steps)) return max(0.0, float(total_steps - current_step) / float(max(1, total_steps - warmup_steps))) scheduler = LambdaLR(optimizer, lr_lambda)

5.2 梯度累积

对于小批量数据,可以使用梯度累积:

accumulation_steps = 4 for i, batch in enumerate(dataloader): # 前向传播和损失计算 loss = loss / accumulation_steps loss.backward() if (i + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad() scheduler.step()

5.3 混合精度训练

使用AMP加速训练并减少显存占用:

from torch.cuda.amp import GradScaler, autocast scaler = GradScaler() with autocast(): outputs = model(input_ids, token_type_ids, attention_mask) # 计算损失... scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()

6. 监控与评估

6.1 训练指标可视化

使用TensorBoard记录训练过程:

from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter() for epoch in range(epochs): for step, batch in enumerate(dataloader): # 训练步骤... writer.add_scalar('Loss/train', loss.item(), global_step) writer.add_scalar('LR', optimizer.param_groups[0]['lr'], global_step)

6.2 验证集评估

定期在验证集上评估模型性能:

def evaluate(model, dataloader, device): model.eval() total_mlm_acc = 0 total_nsp_acc = 0 total_samples = 0 with torch.no_grad(): for batch in dataloader: # 准备数据... # 前向传播 outputs = model(input_ids, token_type_ids, attention_mask) # MLM准确率 mlm_logits = mlm_head(outputs, masked_positions) mlm_preds = torch.argmax(mlm_logits, dim=-1) mlm_acc = (mlm_preds == mlm_labels[masked_positions]).float().mean() # NSP准确率 nsp_logits = nsp_head(outputs[:, 0, :]) nsp_preds = torch.argmax(nsp_logits, dim=-1) nsp_acc = (nsp_preds == nsp_labels).float().mean() total_mlm_acc += mlm_acc.item() * len(batch) total_nsp_acc += nsp_acc.item() * len(batch) total_samples += len(batch) return { 'mlm_accuracy': total_mlm_acc / total_samples, 'nsp_accuracy': total_nsp_acc / total_samples }

7. 模型保存与应用

7.1 保存检查点

定期保存模型检查点:

def save_checkpoint(model, optimizer, scheduler, epoch, path): torch.save({ 'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'scheduler_state_dict': scheduler.state_dict(), 'loss': loss, }, path)

7.2 下游任务微调

将预训练模型应用于下游任务(如文本分类):

class BERTForClassification(nn.Module): def __init__(self, bert_model, num_classes): super().__init__() self.bert = bert_model self.classifier = nn.Linear(bert_model.config.hidden_size, num_classes) def forward(self, input_ids, attention_mask=None): outputs = self.bert(input_ids, attention_mask=attention_mask) pooled_output = outputs[1] # [CLS] token logits = self.classifier(pooled_output) return logits

通过以上步骤,我们完整实现了BERT的预训练过程,包括MLM和NSP两个核心任务。在实际训练中,通过合理调整学习率、批量大小和训练步数,我们成功将Loss降至0.15左右,表明模型已经学习到了有效的语言表示。这种预训练模型可以进一步微调用于各种NLP任务,如文本分类、命名实体识别和问答系统等。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/9 10:12:41

5步掌握umy-ui:如何解决Vue 2.0大数据表格卡顿难题?

5步掌握umy-ui&#xff1a;如何解决Vue 2.0大数据表格卡顿难题&#xff1f; 【免费下载链接】umy-ui A desktop component library based on Vue 2.0 prepared for developers 项目地址: https://gitcode.com/gh_mirrors/umy/umy-ui 还在为Vue 2.0项目中万级数据表格渲染…

作者头像 李华
网站建设 2026/7/9 10:03:59

Linux动态壁纸终极指南:如何免费在Linux上运行Wallpaper Engine

Linux动态壁纸终极指南&#xff1a;如何免费在Linux上运行Wallpaper Engine 【免费下载链接】linux-wallpaperengine Wallpaper Engine backgrounds for Linux! 项目地址: https://gitcode.com/gh_mirrors/li/linux-wallpaperengine 厌倦了单调的静态桌面背景&#xff1…

作者头像 李华
网站建设 2026/7/9 9:59:54

ElegantBook:5分钟打造专业级LaTeX学术书籍的终极指南

ElegantBook&#xff1a;5分钟打造专业级LaTeX学术书籍的终极指南 【免费下载链接】ElegantBook Elegant LaTeX Template for Books 项目地址: https://gitcode.com/gh_mirrors/el/ElegantBook 还在为学术书籍的排版而烦恼吗&#xff1f;想要让您的论文、专著或教材看起…

作者头像 李华
网站建设 2026/7/9 9:59:10

Pandas 2.2 实战:3种缺失值填充策略对比与泰坦尼克号数据集应用

Pandas 2.2 实战&#xff1a;3种缺失值填充策略对比与泰坦尼克号数据集应用数据科学项目中&#xff0c;缺失值处理是数据预处理的关键环节。不同的填充策略会直接影响后续建模效果&#xff0c;但很少有资料系统对比不同方法在实际数据集上的表现差异。本文将深入解析均值/中位数…

作者头像 李华
网站建设 2026/7/9 9:57:43

从零构建Python+Selenium自动化测试框架:PO模式与数据驱动实践

1. 项目概述&#xff1a;为什么需要一个自己的自动化测试框架&#xff1f; 干了这么多年测试&#xff0c;从手工点点点到写脚本&#xff0c;再到搭建和维护团队级的自动化测试平台&#xff0c;我最大的感触是&#xff1a;对于大多数中小型项目或者个人学习而言&#xff0c;一个…

作者头像 李华
网站建设 2026/7/9 9:55:51

如何快速获取网页视频:我的VideoDownloadHelper实用经验分享

如何快速获取网页视频&#xff1a;我的VideoDownloadHelper实用经验分享 【免费下载链接】VideoDownloadHelper Chrome Extension to Help Download Video for Some Video Sites. 项目地址: https://gitcode.com/gh_mirrors/vi/VideoDownloadHelper 还在为网页上的精彩视…

作者头像 李华