在前面的章节中,我们深入学习了自注意力机制和Transformer架构,这些是现代预训练语言模型的基础。BERT和GPT作为最具代表性的预训练语言模型,分别采用了不同的训练策略,在各类自然语言处理任务中取得了突破性成果。
BERT(Bidirectional Encoder Representations from Transformers)采用双向编码器结构,通过掩码语言模型和下一句预测任务进行预训练。而GPT(Generative Pre-trained Transformer)采用解码器-only结构,通过自回归语言模型进行预训练。
本节将通过实战项目,带你深入了解BERT和GPT的原理、特点以及在实际任务中的应用方法。
BERT与GPT的核心差异
架构差异
graph TD
A[BERT] --> B[双向Transformer编码器]
C[GPT] --> D[单向Transformer解码器]
style A fill:#2a9d8f,stroke:#333
style B fill:#2a9d8f,stroke:#333
style C fill:#e76f51,stroke:#333
style D fill:#e76f51,stroke:#333
训练目标差异
-
BERT:
- 掩码语言模型(Masked Language Model, MLM):随机掩盖输入中的一些词,让模型预测被掩盖的词
- 下一句预测(Next Sentence Prediction, NSP):预测两个句子是否为连续的句子
-
GPT:
- 自回归语言模型:从左到右预测下一个词
应用场景差异
- BERT:更适合理解任务,如文本分类、问答、命名实体识别等
- GPT:更适合生成任务,如文本生成、对话系统等
BERT详解与实战
BERT的核心创新
BERT的主要创新在于双向编码和预训练+微调的范式:
- 双向编码:通过掩码机制,BERT能够同时利用一个词左右两侧的上下文信息
- 预训练+微调:在大规模语料上预训练,然后在特定任务上微调
BERT的输入表示
BERT的输入由三部分组成:
- Token Embeddings:词嵌入
- Segment Embeddings:句子嵌入(A句或B句)
- Position Embeddings:位置嵌入
graph LR
A[Token Embeddings] --> D[输入表示]
B[Segment Embeddings] --> D
C[Position Embeddings] --> D
style A fill:#a8dadc
style B fill:#457b9d
style C fill:#f4a261
style D fill:#e76f51
BERT实战:文本分类任务
让我们使用Hugging Face Transformers库来实践BERT在文本分类任务中的应用:
import torch
from transformers import BertTokenizer, BertForSequenceClassification, AdamW
from torch.utils.data import DataLoader, Dataset
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import pandas as pd
# 检查CUDA是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 创建示例数据集
class TextDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_length=128):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_length = max_length
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
text = str(self.texts[idx])
label = self.labels[idx]
encoding = self.tokenizer(
text,
truncation=True,
padding='max_length',
max_length=self.max_length,
return_tensors='pt'
)
return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': torch.tensor(label, dtype=torch.long)
}
# 创建示例数据
sample_texts = [
"这部电影真是太棒了,演员表演出色,剧情紧凑。",
"这个产品质量很差,完全不值得购买。",
"今天的天气真好,适合出去散步。",
"服务态度恶劣,再也不会来了。",
"这本书内容丰富,值得一读。",
"交通拥堵严重,上班迟到了。",
"新产品功能强大,用户体验很好。",
"餐厅环境脏乱,食物也不新鲜。"
]
sample_labels = [1, 0, 1, 0, 1, 0, 1, 0] # 1表示正面情感,0表示负面情感
# 加载预训练的BERT模型和分词器
model_name = 'bert-base-chinese' # 使用中文BERT模型
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertForSequenceClassification.from_pretrained(model_name, num_labels=2)
model.to(device)
# 创建数据集和数据加载器
dataset = TextDataset(sample_texts, sample_labels, tokenizer)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
# 设置优化器
optimizer = AdamW(model.parameters(), lr=2e-5)
# 训练函数
def train_model(model, dataloader, optimizer, epochs=3):
model.train()
for epoch in range(epochs):
total_loss = 0
for batch in dataloader:
# 将数据移到GPU
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
# 清零梯度
optimizer.zero_grad()
# 前向传播
outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
# 反向传播
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(dataloader)
print(f"Epoch {epoch+1}/{epochs}, Average Loss: {avg_loss:.4f}")
# 训练模型
print("开始训练BERT模型...")
train_model(model, dataloader, optimizer)
# 评估函数
def evaluate_model(model, texts, labels, tokenizer):
model.eval()
predictions = []
with torch.no_grad():
for text in texts:
inputs = tokenizer(text, return_tensors='pt', truncation=True,
padding='max_length', max_length=128).to(device)
outputs = model(**inputs)
predicted_class = torch.argmax(outputs.logits, dim=1).item()
predictions.append(predicted_class)
accuracy = accuracy_score(labels, predictions)
print(f"Accuracy: {accuracy:.4f}")
print("\nClassification Report:")
print(classification_report(labels, predictions, target_names=['Negative', 'Positive']))
return predictions
# 评估模型
print("\n评估模型性能...")
test_texts = [
"这个产品非常好用,推荐购买。",
"服务太差了,浪费时间。",
"今天心情不错,阳光明媚。"
]
test_labels = [1, 0, 1]
predictions = evaluate_model(model, test_texts, test_labels, tokenizer)
GPT详解与实战
GPT的核心特点
GPT系列模型的主要特点是:
- 单向语言模型:只能利用左侧上下文信息
- 生成能力强:擅长文本生成任务
- 零样本和少样本学习:能够在没有微调的情况下执行新任务
GPT实战:文本生成任务
让我们使用GPT模型进行文本生成任务:
from transformers import GPT2LMHeadModel, GPT2Tokenizer
import torch
# 检查CUDA是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 加载预训练的GPT-2模型和分词器(使用英文模型)
model_name = 'gpt2'
tokenizer = GPT2Tokenizer.from_pretrained(model_name)
model = GPT2LMHeadModel.from_pretrained(model_name)
model.to(device)
# 设置pad_token
tokenizer.pad_token = tokenizer.eos_token
# 文本生成函数
def generate_text(prompt, max_length=100, num_return_sequences=1):
# 编码输入文本
input_ids = tokenizer.encode(prompt, return_tensors='pt').to(device)
# 生成文本
with torch.no_grad():
outputs = model.generate(
input_ids,
max_length=max_length,
num_return_sequences=num_return_sequences,
temperature=0.7,
pad_token_id=tokenizer.eos_token_id,
do_sample=True,
top_k=50,
top_p=0.95,
)
# 解码生成的文本
generated_texts = []
for output in outputs:
generated_text = tokenizer.decode(output, skip_special_tokens=True)
generated_texts.append(generated_text)
return generated_texts
# 示例文本生成
prompts = [
"The future of artificial intelligence",
"Once upon a time",
"In a world where robots can think"
]
print("使用GPT-2生成文本:")
for prompt in prompts:
print(f"\nPrompt: {prompt}")
generated_texts = generate_text(prompt, max_length=50, num_return_sequences=1)
for i, text in enumerate(generated_texts):
print(f"Generated text {i+1}: {text}")
使用中文预训练模型
对于中文任务,我们可以使用专门的中文预训练模型:
# 使用中文BERT进行文本分类
from transformers import BertTokenizer, BertForSequenceClassification
# 加载中文BERT模型
chinese_bert_name = 'bert-base-chinese'
tokenizer = BertTokenizer.from_pretrained(chinese_bert_name)
model = BertForSequenceClassification.from_pretrained(chinese_bert_name, num_labels=2)
# 中文情感分析示例
chinese_texts = [
"这个电影真的很好看,剧情紧凑,演员演技在线。",
"产品质量太差了,完全不值这个价格。",
"今天天气不错,心情也很好。"
]
# 使用中文GPT模型进行文本生成
from transformers import GPT2LMHeadModel, GPT2Tokenizer
# 注意:需要使用专门的中文GPT模型,如CDial-GPT
# 这里仅作示例展示
try:
chinese_gpt_name = 'thu-coai/CDial-GPT_LCCC-base'
gpt_tokenizer = GPT2Tokenizer.from_pretrained(chinese_gpt_name)
gpt_model = GPT2LMHeadModel.from_pretrained(chinese_gpt_name)
print("成功加载中文GPT模型")
except:
print("无法加载中文GPT模型,使用英文模型替代")
gpt_tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
gpt_model = GPT2LMHeadModel.from_pretrained('gpt2')
微调预训练模型的最佳实践
数据准备
- 数据清洗:去除噪声和无关信息
- 数据增强:通过同义词替换、回译等方法增加数据多样性
- 数据平衡:确保各类别样本数量相对平衡
训练策略
- 学习率设置:通常使用较小的学习率(2e-5到5e-5)
- 批次大小:根据GPU内存调整,通常为16或32
- 训练轮数:通常为3-5轮,避免过拟合
模型选择建议
| 任务类型 | 推荐模型 | 理由 |
|---|---|---|
| 文本分类 | BERT | 双向编码,理解能力强 |
| 命名实体识别 | BERT | 上下文理解能力强 |
| 问答系统 | BERT | 能够理解问题和答案的关系 |
| 文本生成 | GPT | 自回归生成能力强 |
| 对话系统 | GPT | 生成自然流畅的对话 |
BERT与GPT的局限性
BERT的局限性
- 预训练任务与下游任务的差异:MLM任务与实际任务可能存在差异
- 计算资源消耗大:双向编码增加了计算复杂度
- 生成能力较弱:不适合直接用于文本生成任务
GPT的局限性
- 单向编码:无法利用右侧上下文信息
- 事实性问题:容易生成虚假信息
- 可控性差:难以控制生成内容的具体属性
总结
BERT和GPT作为预训练语言模型的代表,各自具有独特的优势和适用场景。本节我们:
- 深入理解了BERT和GPT的核心差异
- 学习了BERT在文本分类任务中的应用
- 掌握了GPT在文本生成任务中的使用方法
- 了解了中文预训练模型的使用
- 熟悉了微调预训练模型的最佳实践
预训练语言模型的出现极大地推动了自然语言处理领域的发展,掌握它们的使用方法对于构建现代NLP应用至关重要。
在下一节中,我们将深入探讨生成式AI的更多应用,包括对话系统和多模态生成模型。
练习题
- 在更大的数据集上微调BERT模型,观察性能提升
- 尝试不同的GPT模型(如GPT-3、ChatGPT)进行文本生成
- 比较BERT和RoBERTa在相同任务上的表现
- 研究如何使用预训练模型进行命名实体识别任务