transformers迁移学习
🤔 什么是迁移学习?
迁移学习是一种机器学习技术,其核心思想是将在一个任务(源任务)上学习到的知识,应用到另一个相关但不同的任务(目标任务)上。
- 从零开始训练:模型需要从零学习所有特征,这通常需要海量的标注数据和巨大的计算成本。
- 迁移学习:模型已经通过预训练掌握了通用的特征(例如,理解语言的基本语法、识别图像的边缘和纹理),我们只需要在此基础上进行"微调"(Fine-tuning),使其适应新的特定任务。
这种方法尤其适合数据量有限或计算资源不足的场景,能让你快速构建出高性能的模型。
NLP常见预训练模型分类
NLP预训练模型根据架构和任务场景,可分为文本理解 和文本生成两大类,核心区别在于模型结构(编码器/解码器)和训练目标。
1. 文本理解类模型
- 架构特点 :基于双向编码器,通过上下文双向信息建模,擅长捕捉文本语义关联。
- 代表模型:
- BERT:经典双向模型,预训练任务为"掩码语言建模(MLM)"和"下一句预测(NSP)",适合文本分类、命名实体识别(NER)、语义匹配等理解类任务。
- RoBERTa:BERT改进版,优化训练策略(如动态掩码、更大批次),效果更优。
- DistilBERT:BERT的"蒸馏版",参数量减少40%,推理速度提升60%,适合资源受限场景。
- ALBERT:通过"参数共享"和"因式分解"压缩模型,在保持性能的同时显著减小体积。
2. 文本生成类模型
- 架构特点 :基于解码器(自回归)或编码器-解码器,通过"从左到右"的生成式训练,擅长文本创作。
- 代表模型:
- GPT/GPT-2:纯解码器架构,自回归生成(根据前文预测下一个词),适合文本生成、对话、故事创作等,是主流大模型(如GPT-3、ChatGPT)的基础架构。
- T5:编码器-解码器架构,将所有NLP任务统一为"文本到文本"格式(如"翻译:英文→中文"),适合翻译、问答、摘要等生成类任务。
主流预训练模型参数对比
以下表格总结了常见模型的架构参数、训练特点和应用场景,核心差异在于层数、特征维度、参数量 及训练语料。
| 模型家族 | 模型名称 | 层数 | 特征维度 | 注意力头数 | 参数量 | 训练语料/特点 | 典型应用场景 |
|---|---|---|---|---|---|---|---|
| BERT | bert-base-uncased | 12 | 768 | 12 | ~110M | 英文,不区分大小写 | 文本分类、情感分析、NER |
| BERT | bert-large-uncased | 24 | 1024 | 16 | ~340M | 英文,不区分大小写 | 高精度文本理解任务 |
| BERT | bert-base-chinese | 12 | 768 | 12 | ~110M | 中文字符级 | 中文分类、NER、匹配 |
| GPT | openai-gpt | 12 | 768 | 12 | ~110M | 英文,自回归 | 文本生成、语言建模 |
| GPT-2 | gpt2 | 12 | 768 | 12 | ~117M | 英文,GPT-2语料 | 对话生成、续写 |
| GPT-2 | gpt2-xl | 48 | 1600 | 25 | ~1558M | 大规模语料 | 高质量文本生成 |
| Transformer-XL | transfo-xl-wt103 | 18 | 1024 | 16 | ~257M | 长文档建模 | 长文本生成、语言模型 |
| RoBERTa | roberta-base | 12 | 768 | 12 | ~125M | BERT改进版 | 文本分类、匹配 |
| RoBERTa | roberta-large | 24 | 1024 | 16 | ~355M | 更大规模训练 | 高精度NLP任务 |
| DistilBERT | distilbert-base-uncased | 6 | 768 | 12 | ~66M | 蒸馏模型 | 轻量级文本分类 |
| ALBERT | albert-base-v1 | 12 | 768 | 12 | ~12M | 参数共享 | 资源受限场景 |
| T5 | t5-base | 12 | 768 | 12 | ~220M | Encoder-Decoder | 翻译、问答 |
| T5 | t5-large | 24 | 1024 | 16 | ~770M | 大模型 | 高质量生成任务 |
关键结论:
- BERT系列(如
bert-base-chinese)是中文理解任务的首选;- GPT系列(如
gpt2)是文本生成的核心架构;- 模型参数量越大(如
gpt2-xl),生成能力越强,但推理成本越高。
常见的NLP任务
| 任务 | 核心说明 | 输出形式 / 备注 |
|---|---|---|
| 文本分类任务 | 对文本内容进行类别判断,如情感分类、商品分类。通常属于有监督学习。 | 输出类别标签(及其概率),结果依赖训练样本标签。 |
| 特征提取任务 | 返回文本的向量表示(语义特征),常作为下游任务输入。 | 可分为: 1) 不带任务头:输出隐藏层特征; 2) 带任务头:按具体任务输出(如分类/填空)。 |
| 完形填空任务 | 又称遮蔽语言建模(MLM),预测被 [MASK] 遮蔽位置的词。 |
输出被遮蔽位置的候选词及概率。 |
| 阅读理解任务 | 又称抽取式问答:输入"上下文+问题",模型从上下文中抽取答案。 | 输出答案文本(通常附带起止位置或置信度)。 |
| 文本摘要任务 | 输入一段较长文本,生成简短概括内容。 | 输出摘要文本(可为抽取式或生成式)。 |
| NER 任务 | 命名实体识别,识别人名(PER)、地名(LOC)、组织(ORG)等。 | 本质是序列标注/分类任务,常用 BIO 标注:B-(实体开始)、I-(实体内部)、O(非实体)。 |
如何使用 Transformers 进行迁移学习
Transformers 库提供了多种使用方式,从极简的"一键调用"到灵活的"手动微调",可以满足不同层次的需求。
1. 快速上手:使用 Pipeline
pipeline 是 Transformers 库中最高级别的抽象,它将数据预处理、模型推理和后处理等步骤全部封装起来。你只需要几行代码就能完成一个复杂的 NLP 任务,非常适合快速验证想法或构建原型。
例如,进行中文情感分析:
python
from transformers import pipeline
# 创建一个情感分析管道,会自动下载模型和分词器
classifier = pipeline("sentiment-analysis", model="bert-base-chinese")
# 直接进行预测
result = classifier("这个产品真的非常好用,性价比超高!")
print(result)
# 输出示例: [{'label': 'POSITIVE', 'score': 0.9996}]
2. 标准流程:使用 AutoModel 和 Trainer
当你需要更多的控制权,例如修改模型结构、自定义训练循环或处理自己的数据集时,就需要使用 AutoModel 和 Trainer。这是进行迁移学习微调的标准流程。
核心步骤如下:
- 加载预训练模型和分词器 :使用
AutoTokenizer和AutoModelForSequenceClassification等类,根据任务类型加载模型。 - 准备和预处理数据:使用分词器对你的数据集进行编码。
- 配置训练参数 :使用
TrainingArguments来设置学习率、批大小、训练轮数等。 - 启动训练 :使用
Trainer类来执行训练循环。
python
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer
from datasets import load_dataset # 假设使用 Hugging Face 的 datasets 库加载数据
# 1. 加载预训练模型和分词器
model_name = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2) # 假设是二分类任务
# 2. 准备和预处理数据 (此处仅为示例)
def tokenize_function(examples):
"""
examples["text"]: 函数假设你的 CSV 文件中有一列名为 "text"。分词器会提取这一列的内容进行处理。
truncation=True: 截断。如果某段文本的长度超过了模型允许的最大长度(例如 BERT 的 512 个 token),超出的部分会被自动切掉。
padding=True: 填充。如果批次中的文本长度不一致,这个参数会自动用填充符号(如 [PAD])将较短的文本补齐,使它们长度一致,以便模型进行批量处理。
"""
return tokenizer(
examples["text"],
truncation=True,
padding="max_length", # 强制填充到最大长度,而不是动态填充
max_length=512 # 显式指定最大长度(BERT通常是512,根据模型调整)
)
# 假设 dataset 是你的数据集,例如从 load_dataset("your_dataset") 加载
# dataset = load_dataset("your_dataset")
# tokenized_datasets = dataset.map(tokenize_function, batched=True)
# 3. 配置训练参数
training_args = TrainingArguments(
output_dir="./checkpoints", # 模型检查点保存目录
num_train_epochs=3, # 训练轮数
per_device_train_batch_size=16, # 每个设备的训练批大小
learning_rate=2e-5, # 学习率
fp16=torch.cuda.is_available(), # 如果GPU支持,启用混合精度训练以加速
logging_steps=10, # 每隔多少步记录一次日志
save_strategy="epoch", # 每个epoch保存一次模型
)
# 4. 初始化 Trainer 并开始训练
# trainer = Trainer(
# model=model,
# args=training_args,
# train_dataset=tokenized_datasets["train"], # 指定训练集
# )
# trainer.train()
# 保存模型
# trainer.save_model("./checkpoints/final_model")
# 关键步骤】显式保存分词器到同一个目录
# tokenizer.save_pretrained("./checkpoints/final_model")
# 测试单条文本
# 加载训练好的模型
# model_path = "./checkpoints/final_model" # 或者你保存的具体路径
# loaded_tokenizer = AutoTokenizer.from_pretrained(model_path)
# loaded_model = AutoModelForSequenceClassification.from_pretrained(model_path)
# def predict_single_text(text):
# # 1. 分词 (必须放回模型所在的设备,如 cuda 或 cpu)
# inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True).to(model.device)
#
# # 2. 推理 (关闭梯度计算以节省内存)
# with torch.no_grad():
# outputs = model(**inputs)
# # 获取概率最高的类别索引
# predicted_class_id = outputs.logits.argmax(-1).item()
#
# return predicted_class_id
#
#
# # 测试
# text_to_test = "这部电影真的太棒了,演员演技在线!"
# result = predict_single_text(text_to_test)
# print(f"输入: '{text_to_test}' -> 预测类别: {result}")
3.指定模型类:使用SpecificModel
"具体模型"其实就是指那些已经训练好、可以直接拿来用的"成品"。在 Hugging Face 的体系里,它处于最底层,代表了某一个特定的、独一无二的模型实例。
具体模型是指具有特定权重、特定配置、针对特定语言或任务微调过的预训练模型实例。
python
from transformers import BertTokenizer, BertForSequenceClassification
# 手动加载BERT模型(与AutoModel效果一致,但需指定类)
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
model = BertForSequenceClassification.from_pretrained("bert-base-chinese", num_labels=2)
核心总结
- 模型选择 :理解类任务选BERT系列,生成类任务选GPT/T5系列;中文任务优先
bert-base-chinese。 - 库的使用 :快速验证用
Pipeline,通用开发用AutoModel,深度定制用SpecificModel。 - 迁移学习本质:所有预训练模型均基于"预训练+微调"范式,通过少量数据即可适配新任务,大幅降低训练成本。
🌰基于 Hugging Face Transformers 库和 PyTorch 框架情感分类模型
流程可以分为以下几个关键步骤:
- 数据准备
- 准备好你的中文情感分类数据集,通常包含文本和对应的标签(如正面/负面)。
- 将数据集划分为训练集和验证集。
- 模型与分词器加载
- 从模型库中加载预训练的
bert-base-chinese模型和对应的分词器。 - 在预训练模型的基础上,添加一个用于分类的头部(例如,一个线性层),以适应你的分类类别数量。
- 从模型库中加载预训练的
- 数据预处理
- 使用分词器将原始文本转换为模型可以理解的输入格式,这通常包括:
- input_ids: 词汇在词表中的索引。
- attention_mask: 用于区分真实词汇和填充部分的掩码。
- token_type_ids: 用于区分句子对中的不同句子(单句分类任务中可省略)。
- 将处理后的数据和标签封装成 PyTorch 的数据集(Dataset)和数据加载器(DataLoader)。
- 使用分词器将原始文本转换为模型可以理解的输入格式,这通常包括:
- 模型微调
- 定义训练参数,例如:
- 训练轮数 (
num_train_epochs) - 批大小 (
per_device_train_batch_size) - 学习率 (
learning_rate) - 权重衰减 (
weight_decay) 等。
- 训练轮数 (
- 使用训练集数据对模型进行训练,并在验证集上监控模型性能,防止过拟合。
- 定义训练参数,例如:
python
import torch
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertForSequenceClassification, AdamW, get_linear_schedule_with_warmup
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import pandas as pd
import numpy as np
from tqdm import tqdm
# --- 1. 配置与超参数 ---
MODEL_NAME = 'bert-base-chinese' # 预训练模型名称
MAX_LEN = 128 # 文本最大长度
BATCH_SIZE = 16 # 批大小
EPOCHS = 3 # 训练轮数
LEARNING_RATE = 2e-5 # 学习率
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# --- 2. 数据准备 (模拟数据) ---
# 在实际场景中,这里应该是 pd.read_csv('your_data.csv')
data = {
'text': [
"这个手机太好用了,屏幕清晰,运行速度快,强烈推荐!",
"非常糟糕的体验,物流慢,东西也是坏的,千万别买。",
"质量一般般吧,对得起这个价格,没什么特别的。",
"真的很喜欢,完全超出期望值,发货速度非常快。",
"垃圾产品,不仅难用还贵,客服态度也不好。",
"东西收到了,包装很好,是正品,下次还会光顾的。",
"太差劲了,根本没法用,浪费钱。",
"手感不错,做工精细,性价比很高的一款产品。"
],
'label': [1, 0, 0, 1, 0, 1, 0, 1] # 1: 正面, 0: 负面
}
# 为了演示效果,我们将数据复制多份(实际训练需要更多数据)
df = pd.DataFrame(data).loc[np.repeat(data.keys(), 100)].reset_index(drop=True)
# 划分训练集和验证集
train_df, val_df = train_test_split(df, test_size=0.2, random_state=42)
# --- 3. 自定义 Dataset 类 ---
class SentimentDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_len):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self):
return len(self.texts)
def __getitem__(self, item):
text = str(self.texts[item])
label = self.labels[item]
# 使用 BERT 分词器进行编码
encoding = self.tokenizer.encode_plus(
text,
add_special_tokens=True, # 添加 [CLS] 和 [SEP]
max_length=self.max_len, # 截断或填充长度
padding='max_length', # 填充到最大长度
truncation=True, # 超长截断
return_attention_mask=True, # 返回 attention mask
return_tensors='pt', # 返回 PyTorch 张量
)
return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': torch.tensor(label, dtype=torch.long)
}
# --- 4. 初始化 Tokenizer 和 DataLoader ---
tokenizer = BertTokenizer.from_pretrained(MODEL_NAME)
def create_data_loader(df, tokenizer, max_len, batch_size):
ds = SentimentDataset(
texts=df.text.to_numpy(),
labels=df.label.to_numpy(),
tokenizer=tokenizer,
max_len=max_len
)
return DataLoader(ds, batch_size=batch_size, num_workers=0) # num_workers=0 for Windows compatibility
train_data_loader = create_data_loader(train_df, tokenizer, MAX_LEN, BATCH_SIZE)
val_data_loader = create_data_loader(val_df, tokenizer, MAX_LEN, BATCH_SIZE)
# --- 5. 模型加载 (迁移学习核心) ---
# 加载预训练模型,并指定分类头的输出类别数量 (num_labels=2)
model = BertForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2)
model = model.to(DEVICE)
# --- 6. 优化器与学习率调度 ---
optimizer = AdamW(model.parameters(), lr=LEARNING_RATE, correct_bias=False)
total_steps = len(train_data_loader) * EPOCHS
scheduler = get_linear_schedule_with_warmup(
optimizer,
num_warmup_steps=0,
num_training_steps=total_steps
)
# --- 7. 训练与评估函数 ---
def train_epoch(model, data_loader, optimizer, device, scheduler):
model.train()
losses = []
correct_predictions = 0
# 使用 tqdm 显示进度条
for d in tqdm(data_loader, desc="Training"):
input_ids = d["input_ids"].to(device)
attention_mask = d["attention_mask"].to(device)
labels = d["labels"].to(device)
# 前向传播
outputs = model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels
)
loss = outputs.loss
logits = outputs.logits
# 计算准确率
_, preds = torch.max(logits, dim=1)
correct_predictions += torch.sum(preds == labels)
losses.append(loss.item())
# 反向传播与优化
loss.backward()
optimizer.step()
scheduler.step()
optimizer.zero_grad()
return correct_predictions.double() / len(data_loader.dataset), np.mean(losses)
def eval_model(model, data_loader, device):
model.eval()
losses = []
correct_predictions = 0
with torch.no_grad():
for d in data_loader:
input_ids = d["input_ids"].to(device)
attention_mask = d["attention_mask"].to(device)
labels = d["labels"].to(device)
outputs = model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels
)
loss = outputs.loss
logits = outputs.logits
_, preds = torch.max(logits, dim=1)
correct_predictions += torch.sum(preds == labels)
losses.append(loss.item())
return correct_predictions.double() / len(data_loader.dataset), np.mean(losses)
# --- 8. 执行训练循环 ---
print(f"使用设备: {DEVICE}")
for epoch in range(EPOCHS):
print(f'Epoch {epoch + 1}/{EPOCHS}')
print('-' * 10)
train_acc, train_loss = train_epoch(
model,
train_data_loader,
optimizer,
DEVICE,
scheduler
)
print(f'训练损失: {train_loss} 准确率: {train_acc.item()}')
val_acc, val_loss = eval_model(
model,
val_data_loader,
DEVICE
)
print(f'验证损失: {val_loss} 准确率: {val_acc.item()}')
print('-' * 10)
# --- 9. 单句预测函数 ---
def predict_sentiment(text):
model.eval()
encoding = tokenizer.encode_plus(
text,
add_special_tokens=True,
max_length=MAX_LEN,
return_tensors='pt',
truncation=True,
padding='max_length'
)
input_ids = encoding['input_ids'].to(DEVICE)
attention_mask = encoding['attention_mask'].to(DEVICE)
with torch.no_grad():
outputs = model(input_ids=input_ids, attention_mask=attention_mask)
_, prediction = torch.max(outputs.logits, dim=1)
sentiment = "正面" if prediction.item() == 1 else "负面"
print(f'文本: "{text}" -> 预测情感: {sentiment}')
# 测试预测
predict_sentiment("这个手机太棒了,拍照非常清晰!")
predict_sentiment("服务态度极差,以后再也不会来了。")
代码关键点解析
- 迁移学习的核心 (from_pretrained) :
BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2):这行代码不仅下载了预训练的 BERT 权重,还自动在 BERT 的顶部添加了一个线性分类层。- 微调 :在
train_epoch中,我们更新了整个模型的参数(包括 BERT 的底层参数和顶部的分类层参数)。
- 数据处理 (Tokenizer) :
- BERT 需要特定的输入格式:
input_ids(词的索引)和attention_mask(区分真实词和填充词)。 [CLS]标记:BERT 会在句子开头自动添加[CLS],模型通过这个标记对应的输出向量来进行分类。
- BERT 需要特定的输入格式:
- 设备管理 (DEVICE) :
- 代码自动检测是否有 GPU 可用。如果有 GPU,训练速度会比 CPU 快几十倍。
- 优化器 (AdamW) :
- 这是 Transformer 模型训练的标准优化器,配合学习率预热调度器可以获得更好的收敛效果。
应用到真实场景:
- 替换数据 :将代码中的
data字典替换为pd.read_csv('your_dataset.csv')。- 调整超参数 :根据你的数据量大小调整
BATCH_SIZE和EPOCHS。数据量大时,通常 2-4 个 Epoch 就足够了,防止过拟合。- 模型选择 :如果追求更高精度,可以将
MODEL_NAME替换为更大的模型,如hfl/chinese-roberta-wwm-ext。
进阶优化技巧
为了进一步提升效果或提高训练效率,可以考虑以下高级方法:
- 参数高效微调 (PEFT)
对于非常大的模型,全参数微调的成本很高。可以采用参数高效微调方法,只训练模型的一小部分参数,例如:- LoRA (Low-Rank Adaptation): 通过在模型层中注入可训练的低秩矩阵来调整模型。
- Adapter Tuning: 在模型的 Transformer 层之间插入小型的、可训练的"Adapter"模块。
- 数据清洗
在进行跨领域情感分类时,可以借鉴一些研究思路。例如,先利用注意力网络等方法学习文本的分布式表示,然后采用类噪声估计等方法检测并剔除源领域中可能导致"负迁移"的低质量样本,从而提升目标领域的分类效果。
完形填空(MLM)功能
将情感分类代码改为完形填空(Masked Language Modeling, MLM)功能,核心思路是从"文本分类"转变为"预测被遮蔽的词"。
核心变化点
- 模型变更 :从
BertForSequenceClassification改为BertForMaskedLM。 - 任务逻辑 :不再输入"文本+标签",而是输入"带
[MASK]的文本"。 - 输出处理:模型输出的是词汇表中每个词的概率,我们需要取出概率最高的前 N 个词作为预测结果。
代码
python
import torch
from transformers import BertTokenizer, BertForMaskedLM
# --- 1. 配置 ---
MODEL_NAME = 'bert-base-chinese'
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# --- 2. 加载模型与分词器 ---
# 加载预训练的分词器
tokenizer = BertTokenizer.from_pretrained(MODEL_NAME)
# 加载用于掩码语言建模(Masked Language Modeling)的预训练模型
# 注意:这里使用的是 BertForMaskedLM 而不是 BertForSequenceClassification
model = BertForMaskedLM.from_pretrained(MODEL_NAME).to(DEVICE)
# --- 3. 定义预测函数 ---
def predict_mask(text, top_k=5):
"""
预测句子中 [MASK] 位置的词
:param text: 包含 [MASK] 标记的输入文本
:param top_k: 返回概率最高的前 K 个候选词
"""
# 1. 文本编码
# encode 将文本转换为 tensor,并添加 [CLS] 和 [SEP] 标记
inputs = tokenizer.encode(text, return_tensors='pt').to(DEVICE)
# 找到 [MASK] 标记的索引位置
mask_token_index = torch.where(inputs == tokenizer.mask_token_id)[1]
# 2. 模型推理 (不需要计算梯度,加快速度)
with torch.no_grad():
outputs = model(inputs)
predictions = outputs.logits
# 3. 提取预测结果
# 获取 [MASK] 位置对应的预测向量
mask_token_logits = predictions[0, mask_token_index, :]
# 使用 softmax 将 logits 转换为概率
probabilities = torch.softmax(mask_token_logits, dim=-1)
# 获取概率最高的 top_k 个词的索引
top_k_tokens = torch.topk(probabilities, top_k, dim=1).indices[0].tolist()
# 4. 打印结果
print(f"句子: {text}")
print("-" * 30)
for token_id in top_k_tokens:
token = tokenizer.decode([token_id])
score = probabilities[0, token_id].item()
# 替换文本中的 [MASK] 进行展示
filled_sentence = text.replace(tokenizer.mask_token, token)
print(f"预测: {token:<10} 概率: {score:.4f} -> {filled_sentence}")
print("=" * 40)
# --- 4. 运行测试 ---
# 测试用例 1:常识推理
predict_mask("中国的首都是[MASK]。")
# 测试用例 2:语境理解
predict_mask("我想吃[MASK],因为它很甜。")
# 测试用例 3:成语/常用语填空
predict_mask("画蛇添[MASK]。")
代码详解
BertForMaskedLM:- 这是专门用于完形填空任务的模型架构。它的输出层维度是词表大小(对于中文 BERT 通常是 21128),代表每个可能的字或词出现的概率。
tokenizer.mask_token_id:- 我们需要找到输入句子中
[MASK]所在的位置,因为模型会对句子中所有位置进行预测,但我们只关心被遮住的那个位置。
- 我们需要找到输入句子中
torch.topk:- 这是一个高效获取概率最高前 N 个预测结果的方法。
tokenizer.decode:- 模型输出的是数字 ID,必须通过分词器转换回人类可读的中文字符。
展示了 BERT 最原始的能力:双向理解上下文 。它利用
[MASK]左右两边的信息来共同推断中间缺失的内容
微调策略与技巧
- 冻结层与解冻层:在微调初期,可以"冻结"预训练模型的大部分层(通常是底层),只训练顶部的分类头。这可以防止破坏预训练模型已经学到的通用特征。之后,可以逐步"解冻"更多层进行更精细的调整。
- 差异学习率 :为模型的不同部分设置不同的学习率。通常,预训练部分的参数使用较小的学习率(如
2e-5),而新添加的分类头可以使用较大的学习率(如1e-3)。 - 混合精度训练 (fp16) :在
TrainingArguments中设置fp16=True,可以利用 GPU 的 Tensor Cores 加速训练,并显著降低显存占用,从而允许使用更大的批大小。 - 数据增强:对于数据量较少的任务,对训练数据进行适当的增强(如文本的同义词替换、回译等)可以有效提升模型的泛化能力。
从训练到部署
模型训练完成后,可以将其保存并部署到生产环境中。
- 模型保存 :
model.save_pretrained("./my_finetuned_model")和tokenizer.save_pretrained("./my_finetuned_model")。 - 推理加速 :可以使用
ONNX Runtime等工具将模型转换为 ONNX 格式,以获得更快的推理速度。 - 模型量化:将模型权重从 FP32 转换为 INT8 等更低精度的格式,可以大幅压缩模型体积,适合在资源受限的边缘设备上部署。
- 服务化部署 :可以结合
FastAPI等框架,将模型封装成 RESTful API 服务,供其他应用调用。
🔍BERT和GPT架构对比
BERT 和 GPT 都是基于 Transformer 架构的预训练语言模型,但它们在设计哲学、模型结构和训练目标上存在根本性的差异,这直接决定了它们各自擅长的领域。简单来说,BERT 像一个"阅读理解"高手,擅长深度分析;而 GPT 则像一个"作家",擅长流畅创作。
1.模型架构
BERT (Encoder-Only)
BERT 的全称是"来自变换器的双向编码器表示"。它只采用了 Transformer 的编码器(Encoder)部分。编码器的核心任务是读取并理解输入文本,为每个词生成一个包含丰富上下文信息的表示向量。
GPT (Decoder-Only)
GPT 的全称是"生成式预训练变换器"。它只采用了 Transformer 的解码器(Decoder)部分。解码器的核心任务是根据已有的信息来生成下一个内容,非常适合自回归的文本生成任务。
2.注意力机制与上下文理解
BERT (双向上下文)
BERT 使用双向自注意力机制。在理解任何一个词时,模型可以同时"看到"它左边和右边的所有上下文信息。这种设计让 BERT 能够对词语的含义进行非常全面和深刻的理解,尤其擅长处理歧义词(例如,根据上下文判断"bank"是指"银行"还是"河岸")。
GPT (单向上下文)
GPT 使用带掩码的自注意力机制。在预测或生成一个词时,模型只能"看到"它左边的词(即已经生成的上文),而无法看到未来的信息。这种单向(从左到右)的信息流模拟了人类写作和说话的过程,使其在生成连贯、流畅的文本方面表现出色。
3.训练目标
BERT (完形填空)
BERT 的核心训练任务是掩码语言模型(MLM)。它会随机遮盖输入文本中约15%的词(例如,将 "The cat sat on the mat" 变为 "The cat sat on the [MASK]"),然后让模型根据完整的双向上下文来预测被遮盖的词。这就像一个"完形填空"练习,迫使模型深度理解整个句子的语义。
GPT (文本续写)
GPT 的训练目标是自回归语言建模。它的任务非常直接:给定一个词序列,预测下一个最可能出现的词。例如,输入"今天天气很",模型会学习预测下一个词是"好"。这个过程不断重复,从而实现文本的连续生成,就像一个"文本续写"练习。
应用场景
| 模型 | 核心能力 | 典型应用场景 |
|---|---|---|
| BERT | 深度理解 | 文本分类、情感分析、命名实体识别(NER)、问答系统(从文档中抽取答案)、语义匹配、搜索排序 |
| GPT | 流畅生成 | 文章写作、代码生成、对话聊天、内容摘要、机器翻译、创意文本生成 |
总结
| 对比维度 | BERT | GPT |
|---|---|---|
| 核心架构 | Transformer Encoder | Transformer Decoder |
| 注意力方向 | 双向 (可同时看前后文) | 单向 (只能看前文) |
| 训练目标 | 掩码语言模型 (MLM),类似"完形填空" | 自回归语言模型,类似"文本续写" |
| 能力侧重 | 擅长语言理解和分析 | 擅长语言生成和创作 |
| 形象类比 | 阅卷老师 | 作家 |
🔍Qwen架构-GPT改进
核心架构演进:基于GPT的优化
千问的底层逻辑依然是Decoder-only的自回归架构,但在具体实现上引入了多项关键技术改进,以解决标准GPT架构在效率和长上下文处理上的瓶颈。
关键技术改进
1. 注意力机制优化(GQA/MQA)
千问采用了分组查询注意力 (GQA)或多查询注意力(MQA)机制。
- 原理:让多个查询头共享同一组键值对,从而大幅减少推理时需要缓存的键值对数量。
- 优势:显著降低了显存占用,提升了推理速度,尤其是在处理长文本时效果明显。
| 机制 | 核心切分逻辑 | KV 组数关系 | 关键特征 | 类比 |
|---|---|---|---|---|
| MHA | Q、K、V 等头数切分 | Q头数 = KV头数 | 每个 Q 头拥有独立专属的 K、V,表达能力最强。 | 将羊肉Q、鸡肉K、鱼肉V都切成同样数量的薄片,下锅计算注意力,味道最丰富(表达能力强),但切菜太慢(计算和显存开销大)。 |
| GQA | Q 切细组,KV 切分组 | Q头数 = 4 * KV头数 | 组内共享,每组多头 Q 共用一组 K、V,平衡效果与速度。 | 将羊肉Q切成薄片,将鸡肉K和鱼肉V切成较厚的块,下锅计算注意力,既保证了备菜速度,又兼顾了味道(效果与效率的平衡)。 |
| MQA | 只切分 Q,不切分 KV | Q头数 >> KV头数=1 | 所有头的 Q 共用同一组 K、V,推理速度最快。 | 只切羊肉Q,鸡肉K和鱼肉V完全不切(只有1大块),下锅计算注意力,备菜极快,味道难以描述(表达能力差)。 |
- MHA:效果最好,但速度慢,显存占用高。
- MQA:速度最快,显存占用低,但效果损失较大。
- GQA:折中方案,在保持接近 MHA 效果的同时,大幅提升了推理速度(目前主流大模型如 Llama 2/3, Mistral 等多采用 GQA)。
2. 位置编码升级(RoPE)
千问使用旋转位置编码(RoPE)替代了GPT早期的绝对位置编码。
- 原理:将位置信息通过旋转变换融入到词向量中。
- 优势:赋予了模型更好的外推能力,使其能够处理比训练时更长的上下文序列。
3. 归一化层简化(RMSNorm)
千问使用均方根归一化(RMSNorm)替代了GPT中的层归一化(LayerNorm)。
- 原理:RMSNorm仅对输入的均方根进行归一化,省去了减去均值的步骤。
- 优势:计算更简单、更高效,同时能保持训练的稳定性。
4. 激活函数增强(SwiGLU)
千问在前馈网络中使用了SwiGLU激活函数,替代了GPT-2中的GELU。
- 原理:SwiGLU引入了门控机制,可以更精细地控制信息流。
- 优势:提升了模型的表达能力和收敛速度。
5. 中文分词优化
千问针对中文进行了专门的分词器设计,减少了中文字符被拆分成多个Token的情况。
- 优势:提高了中文处理的效率和准确性,降低了序列长度。