文章目录
一、 BERT模型概述
1.1 BERT模型介绍
BERT(Bidirectional Encoder Representations from Transformers) 是由Google在2018年提出的革命性预训练语言模型,其核心创新在于:
- 双向上下文理解:首次实现了真正意义上的双向语言表示学习
- 预训练-微调范式:通过大规模无标注数据预训练,再针对特定任务微调
- 掩码语言模型:作为预训练任务之一,使模型能够深入理解上下文
BERT的出现标志着NLP领域从特征工程向预训练模型的范式转变,其影响力深远,后续的RoBERTa、ALBERT、DistilBERT等模型都是基于BERT的改进。
1.2 BERT核心架构详解
BERT基于Transformer的Encoder部分构建,主要由以下组件组成:
- 词嵌入层:
- Token Embeddings:将词转换为向量
- Segment Embeddings:区分句子A和句子B
- Position Embeddings:表示词在序列中的位置
- Transformer Encoder层:
- 多层自注意力机制
- 前馈神经网络
- 层归一化和残差连接
1.3 关键技术创新
1、双向上下文表示
传统语言模型只能从左到右或从右到左学习上下文,而BERT通过掩码语言模型(MLM)实现了真正的双向表示:
输入:The cat sat on the mat
掩码:The cat sat on the [MASK]
预测:The cat sat on the mat
2、句对关系学习
BERT通过NSP任务学习句子间关系:
[CLS] I love Paris [SEP] Paris is the capital of France [SEP]
模型预测这两个句子是否是连续的。
1.4 模型变体
BERT主要有两个版本:
- BERT-Base:110M参数,12层Transformer,768隐藏层,12个注意力头
- BERT-Large:340M参数,24层Transformer,1024隐藏层,16个注意力头
1.5 BERT预训练任务
BERT采用两个无监督预训练任务:
1、掩码语言模型(MLM)
随机掩码输入中15%的词,然后预测这些被掩码的词:
原始:The quick brown fox jumps over the lazy dog
掩码:The quick [MASK] fox jumps over the lazy dog
预测:brown
2、下一句预测(NSP)
判断两个句子是否是连续的:
[CLS] How old are you? [SEP] I'm 20 years old [SEP]
预测:IsNext
1.6 BERT在机器翻译中的优势
- 双向上下文理解:BERT能够同时考虑左右上下文,更好地理解词义
- 预训练知识:利用大规模语料预训练,包含丰富的语言知识
- 多语言支持:多语言BERT版本可以处理多种语言对
- 灵活的架构:可以作为编码器或辅助组件集成到各种翻译系统中
1.7 局限性与改进方向
- 计算效率:BERT模型较大,推理速度较慢
- 改进:使用知识蒸馏、模型量化、参数共享等技术
- 长文本处理:标准BERT有最大序列长度限制
- 改进:使用Longformer、BigBird等处理长文本的变体
- 生成能力:BERT本身是编码器,不直接擅长生成任务
- 改进:结合解码器架构,如BART、T5等
- 领域适应性:通用预训练模型在特定领域可能表现不佳
- 改进:领域自适应预训练和微调
二、BERT在机器翻译中的应用
虽然BERT本身是编码器,不能直接用于生成式机器翻译,但它可以通过多种方式应用于翻译系统:
2.1 作为编码器组件
BERT可以作为编码器集成到Seq2Seq模型中:
输入句子 → BERT编码器 → 上下文表示 → 解码器 → 输出句子
2.2 双向编码器-单向解码器架构
结合BERT的编码能力和GPT风格解码器:
源语言 → BERT编码器 → 双向表示 → 单向解码器 → 目标语言
2.3 领域自适应
使用领域特定语料对BERT进行预训练,提高翻译质量:
通用BERT → 领域语料预训练 → 领域适配BERT → 翻译系统
2.4 多语言翻译
多语言BERT可以处理多种语言对:
[CLS] 源语言句子 [SEP] 目标语言句子 [SEP]
三、完整Python代码实现
3.1 环境准备
# 安装必要的库
!pip install transformers torch datasets sacremoses
3.2 使用预训练BERT进行机器翻译
import torch
from transformers import BertTokenizer, BertModel, BertConfig
from transformers import MarianMTModel, MarianTokenizer
from torch.nn import functional as F
# 加载多语言BERT模型和分词器
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
bert_model = BertModel.from_pretrained('bert-base-multilingual-cased')
# 加载预训练翻译模型
model_name = 'Helsinki-NLP/opus-mt-zh-en'
tokenizer_trans = MarianTokenizer.from_pretrained(model_name)
model_trans = MarianMTModel.from_pretrained(model_name)
def translate_with_bert(input_text, use_bert=True):
"""
使用BERT增强的机器翻译函数
参数:
input_text: 输入文本
use_bert: 是否使用BERT进行编码增强
返回:
翻译结果
"""
# 分词和编码
inputs = tokenizer_trans(input_text, return_tensors="pt", padding=True)
if use_bert:
# 使用BERT获取上下文表示
bert_inputs = tokenizer(input_text, return_tensors="pt", padding=True, truncation=True)
with torch.no_grad():
bert_outputs = bert_model(**bert_inputs)
# 获取[CLS]标记的表示作为句子表示
sentence_embedding = bert_outputs.last_hidden_state[:, 0, :]
# 将BERT表示添加到翻译模型输入
# 这里简单地将BERT表示与输入嵌入相加
inputs['encoder_hidden_state'] = sentence_embedding
# 生成翻译
with torch.no_grad():
translated = model_trans.generate(**inputs)
# 解码结果
result = tokenizer_trans.decode(translated[0], skip_special_tokens=True)
return result
# 示例使用
if __name__ == "__main__":
input_text = "人工智能正在改变我们的生活方式。"
print("输入文本:", input_text)
# 不使用BERT的翻译
result_without_bert = translate_with_bert(input_text, use_bert=False)
print("不使用BERT的翻译:", result_without_bert)
# 使用BERT的翻译
result_with_bert = translate_with_bert(input_text, use_bert=True)
print("使用BERT的翻译:", result_with_bert)
3.3 训练自定义BERT翻译模型
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel, AdamW, get_linear_schedule_with_warmup
from tqdm import tqdm, trange
import numpy as np
import random
# 设置随机种子
def set_seed(seed=42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
set_seed()
# 自定义数据集
class TranslationDataset(Dataset):
def __init__(self, src_texts, tgt_texts, src_tokenizer, tgt_tokenizer, max_length=128):
self.src_texts = src_texts
self.tgt_texts = tgt_texts
self.src_tokenizer = src_tokenizer
self.tgt_tokenizer = tgt_tokenizer
self.max_length = max_length
def __len__(self):
return len(self.src_texts)
def __getitem__(self, idx):
src_text = self.src_texts[idx]
tgt_text = self.tgt_texts[idx]
# 源语言编码
src_encoding = self.src_tokenizer(
src_text,
max_length=self.max_length,
padding='max_length',
truncation=True,
return_tensors='pt'
)
# 目标语言编码
tgt_encoding = self.tgt_tokenizer(
tgt_text,
max_length=self.max_length,
padding='max_length',
truncation=True,
return_tensors='pt'
)
return {
'input_ids': src_encoding['input_ids'].flatten(),
'attention_mask': src_encoding['attention_mask'].flatten(),
'labels': tgt_encoding['input_ids'].flatten(),
'decoder_attention_mask': tgt_encoding['attention_mask'].flatten()
}
# BERT翻译模型
class BertTranslator(nn.Module):
def __init__(self, bert_model_name, tgt_vocab_size, d_model=512, nhead=8, num_encoder_layers=6, num_decoder_layers=6, dim_feedforward=2048, dropout=0.1):
super(BertTranslator, self).__init__()
# BERT编码器
self.bert = BertModel.from_pretrained(bert_model_name)
self.bert.resize_token_embeddings(len(self.bert.config.vocab_size))
# 解码器
self.decoder = nn.TransformerDecoder(
nn.TransformerDecoderLayer(d_model, nhead, dim_feedforward, dropout),
num_decoder_layers
)
# 投影层
self.linear = nn.Linear(self.bert.config.hidden_size, tgt_vocab_size)
# 位置编码
self.positional_encoding = self._generate_positional_encoding(self.max_seq_length, d_model)
# 初始化参数
self._init_weights()
def _generate_positional_encoding(self, max_len, d_model):
position = torch.arange(max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
pe = torch.zeros(max_len, 1, d_model)
pe[:, 0, 0::2] = torch.sin(position * div_term)
pe[:, 0, 1::2] = torch.cos(position * div_term)
return pe
def _init_weights(self):
for p in self.decoder.parameters():
if p.dim() > 1:
nn.init.xavier_uniform_(p)
for p in self.linear.parameters():
if p.dim() > 1:
nn.init.xavier_uniform_(p)
def forward(self, src_input_ids, src_attention_mask, tgt_input_ids=None, tgt_attention_mask=None):
# BERT编码
bert_outputs = self.bert(input_ids=src_input_ids, attention_mask=src_attention_mask)
memory = bert_outputs.last_hidden_state # [batch_size, src_seq_len, hidden_size]
if tgt_input_ids is not None:
# 训练模式
# 创建目标位置编码
tgt_seq_len = tgt_input_ids.size(1)
tgt_pos = torch.arange(0, tgt_seq_len).unsqueeze(0).expand(tgt_input_ids.size(0), tgt_seq_len).to(src_input_ids.device)
tgt_pos_emb = self.bert.embeddings(tgt_input_ids)
# 解码
tgt_mask = self._generate_square_subsequent_mask(tgt_seq_len).to(src_input_ids.device)
output = self.decoder(
tgt=tgt_pos_emb,
memory=memory,
tgt_mask=tgt_mask,
memory_mask=None,
tgt_key_padding_mask=~tgt_attention_mask,
memory_key_padding_mask=~src_attention_mask
)
# 投影到词汇表
output = self.linear(output)
return output
else:
# 推理模式
return memory
def _generate_square_subsequent_mask(self, sz):
mask = (torch.triu(torch.ones(sz, sz)) == 1).transpose(0, 1)
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
return mask
# 训练函数
def train_model(model, dataloader, optimizer, scheduler, device, epochs=3):
model.train()
model.to(device)
for epoch in range(epochs):
total_loss = 0
progress_bar = tqdm(dataloader, desc=f'Epoch {epoch+1}/{epochs}')
for batch in progress_bar:
optimizer.zero_grad()
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
decoder_attention_mask = batch['decoder_attention_mask'].to(device)
# 前向传播
outputs = model(
src_input_ids=input_ids,
src_attention_mask=attention_mask,
tgt_input_ids=labels,
tgt_attention_mask=decoder_attention_mask
)
# 计算损失
loss = F.cross_entropy(
outputs.view(-1, outputs.size(-1)),
labels.view(-1),
ignore_index=model.tgt_tokenizer.pad_token_id
)
# 反向传播
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
scheduler.step()
total_loss += loss.item()
progress_bar.set_postfix({'loss': loss.item()})
avg_loss = total_loss / len(dataloader)
print(f'Epoch {epoch+1} - Average Loss: {avg_loss:.4f}')
# 评估函数
def evaluate_model(model, dataloader, device):
model.eval()
model.to(device)
total_loss = 0
with torch.no_grad():
for batch in tqdm(dataloader, desc='Evaluating'):
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
decoder_attention_mask = batch['decoder_attention_mask'].to(device)
outputs = model(
src_input_ids=input_ids,
src_attention_mask=attention_mask,
tgt_input_ids=labels,
tgt_attention_mask=decoder_attention_mask
)
loss = F.cross_entropy(
outputs.view(-1, outputs.size(-1)),
labels.view(-1),
ignore_index=model.tgt_tokenizer.pad_token_id
)
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
print(f'Evaluation Loss: {avg_loss:.4f}')
return avg_loss
# 示例使用
if __name__ == "__main__":
# 示例数据(实际应用中应使用更大的数据集)
src_texts = [
"人工智能正在改变我们的生活方式。",
"机器学习是人工智能的一个重要分支。",
"深度学习在图像识别领域取得了巨大成功。"
]
tgt_texts = [
"Artificial intelligence is changing our way of life.",
"Machine learning is an important branch of artificial intelligence.",
"Deep learning has achieved great success in the field of image recognition."
]
# 初始化分词器
src_tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
tgt_tokenizer = MarianTokenizer.from_pretrained('Helsinki-NLP/opus-mt-zh-en')
# 创建数据集和数据加载器
dataset = TranslationDataset(src_texts, tgt_texts, src_tokenizer, tgt_tokenizer)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
# 创建模型
model = BertTranslator(
bert_model_name='bert-base-multilingual-cased',
tgt_vocab_size=len(tgt_tokenizer),
d_model=768 # BERT隐藏层大小
)
# 设置优化器和学习率调度器
optimizer = AdamW(model.parameters(), lr=5e-5, eps=1e-8)
total_steps = len(dataloader) * 3 # 3个epoch
scheduler = get_linear_schedule_with_warmup(
optimizer,
num_warmup_steps=int(0.1 * total_steps),
num_training_steps=total_steps
)
# 训练模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
train_model(model, dataloader, optimizer, scheduler, device, epochs=3)
# 评估模型
evaluate_model(model, dataloader, device)
# 保存模型
torch.save(model.state_dict(), 'bert_translator.pth')
print("模型已保存为 bert_translator.pth")
3.4 使用预训练BERT进行序列到序列翻译
from transformers import BertTokenizer, BertModel, EncoderDecoderModel, AdamW, get_linear_schedule_with_warmup
import torch
from torch.utils.data import Dataset, DataLoader
import torch.nn.functional as F
# 创建自定义数据集
class TranslationDataset(Dataset):
def __init__(self, src_texts, tgt_texts, src_tokenizer, tgt_tokenizer, max_length=128):
self.src_texts = src_texts
self.tgt_texts = tgt_texts
self.src_tokenizer = src_tokenizer
self.tgt_tokenizer = tgt_tokenizer
self.max_length = max_length
def __len__(self):
return len(self.src_texts)
def __getitem__(self, idx):
src_text = self.src_texts[idx]
tgt_text = self.tgt_texts[idx]
# 源语言编码
src_encoding = self.src_tokenizer(
src_text,
max_length=self.max_length,
padding='max_length',
truncation=True,
return_tensors='pt'
)
# 目标语言编码
tgt_encoding = self.tgt_tokenizer(
tgt_text,
max_length=self.max_length,
padding='max_length',
truncation=True,
return_tensors='pt'
)
return {
'input_ids': src_encoding['input_ids'].flatten(),
'attention_mask': src_encoding['attention_mask'].flatten(),
'labels': tgt_encoding['input_ids'].flatten(),
'decoder_attention_mask': tgt_encoding['attention_mask'].flatten()
}
# 训练函数
def train_bert2bert(model, dataloader, optimizer, scheduler, device, epochs=3):
model.train()
model.to(device)
for epoch in range(epochs):
total_loss = 0
progress_bar = tqdm(dataloader, desc=f'Epoch {epoch+1}/{epochs}')
for batch in progress_bar:
optimizer.zero_grad()
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
decoder_attention_mask = batch['decoder_attention_mask'].to(device)
# 前向传播
outputs = model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels,
decoder_attention_mask=decoder_attention_mask
)
loss = outputs.loss
# 反向传播
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
scheduler.step()
total_loss += loss.item()
progress_bar.set_postfix({'loss': loss.item()})
avg_loss = total_loss / len(dataloader)
print(f'Epoch {epoch+1} - Average Loss: {avg_loss:.4f}')
# 示例使用
if __name__ == "__main__":
# 示例数据(实际应用中应使用更大的数据集)
src_texts = [
"人工智能正在改变我们的生活方式。",
"机器学习是人工智能的一个重要分支。",
"深度学习在图像识别领域取得了巨大成功。"
]
tgt_texts = [
"Artificial intelligence is changing our way of life.",
"Machine learning is an important branch of artificial intelligence.",
"Deep learning has achieved great success in the field of image recognition."
]
# 初始化分词器
src_tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
tgt_tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
# 创建数据集和数据加载器
dataset = TranslationDataset(src_texts, tgt_texts, src_tokenizer, tgt_tokenizer)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
# 创建BERT2BERT模型
model = EncoderDecoderModel.from_encoder_decoder_pretrained(
"bert-base-multilingual-cased",
"bert-base-multilingual-cased"
)
# 设置特殊token
model.config.decoder_start_token_id = tgt_tokenizer.cls_token_id
model.config.pad_token_id = tgt_tokenizer.pad_token_id
model.config.vocab_size = model.config.encoder.vocab_size
# 设置生成参数
model.config.max_length = 128
model.config.min_length = 56
model.config.no_repeat_ngram_size = 3
model.config.early_stopping = True
model.config.length_penalty = 2.0
# 设置优化器和学习率调度器
optimizer = AdamW(model.parameters(), lr=5e-5, eps=1e-8)
total_steps = len(dataloader) * 3 # 3个epoch
scheduler = get_linear_schedule_with_warmup(
optimizer,
num_warmup_steps=int(0.1 * total_steps),
num_training_steps=total_steps
)
# 训练模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
train_bert2bert(model, dataloader, optimizer, scheduler, device, epochs=3)
# 保存模型
model.save_pretrained("bert2bert-translator")
src_tokenizer.save_pretrained("bert2bert-translator")
tgt_tokenizer.save_pretrained("bert2bert-translator")
print("模型已保存为 bert2bert-translator")
# 测试翻译
model.eval()
input_text = "人工智能正在改变我们的生活方式。"
input_ids = src_tokenizer(input_text, return_tensors="pt").input_ids.to(device)
# 生成翻译
outputs = model.generate(input_ids)
translation = tgt_tokenizer.decode(outputs[0], skip_special_tokens=True)
print(f"输入: {input_text}")
print(f"翻译: {translation}")