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任务统一为“文本到文本”格式(如“翻译:英文→中文”),适合翻译、问答、摘要等生成类任务。

主流预训练模型参数对比

以下表格总结了常见模型的架构参数、训练特点和应用场景,核心差异在于层数、特征维度、参数量训练语料

模型家族模型名称层数特征维度注意力头数参数量训练语料/特点典型应用场景
BERTbert-base-uncased1276812~110M英文,不区分大小写文本分类、情感分析、NER
BERTbert-large-uncased24102416~340M英文,不区分大小写高精度文本理解任务
BERTbert-base-chinese1276812~110M中文字符级中文分类、NER、匹配
GPTopenai-gpt1276812~110M英文,自回归文本生成、语言建模
GPT-2gpt21276812~117M英文,GPT-2语料对话生成、续写
GPT-2gpt2-xl48160025~1558M大规模语料高质量文本生成
Transformer-XLtransfo-xl-wt10318102416~257M长文档建模长文本生成、语言模型
RoBERTaroberta-base1276812~125MBERT改进版文本分类、匹配
RoBERTaroberta-large24102416~355M更大规模训练高精度NLP任务
DistilBERTdistilbert-base-uncased676812~66M蒸馏模型轻量级文本分类
ALBERTalbert-base-v11276812~12M参数共享资源受限场景
T5t5-base1276812~220MEncoder-Decoder翻译、问答
T5t5-large24102416~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 任务,非常适合快速验证想法或构建原型。

例如,进行中文情感分析:

from transformers import pipeline

# 创建一个情感分析管道,会自动下载模型和分词器
classifier = pipeline("sentiment-analysis", model="bert-base-chinese")

# 直接进行预测
result = classifier("这个产品真的非常好用,性价比超高!")
print(result)
# 输出示例: [{'label': 'POSITIVE', 'score': 0.9996}]
2. 标准流程:使用 AutoModel 和 Trainer

当你需要更多的控制权,例如修改模型结构、自定义训练循环或处理自己的数据集时,就需要使用 AutoModelTrainer。这是进行迁移学习微调的标准流程。

核心步骤如下:

  1. 加载预训练模型和分词器:使用 AutoTokenizerAutoModelForSequenceClassification 等类,根据任务类型加载模型。
  2. 准备和预处理数据:使用分词器对你的数据集进行编码。
  3. 配置训练参数:使用 TrainingArguments 来设置学习率、批大小、训练轮数等。
  4. 启动训练:使用 Trainer 类来执行训练循环。
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 的体系里,它处于最底层,代表了某一个特定的、独一无二的模型实例。

具体模型是指具有特定权重、特定配置、针对特定语言或任务微调过的预训练模型实例

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 框架情感分类模型

流程可以分为以下几个关键步骤:

  1. 数据准备
    • 准备好你的中文情感分类数据集,通常包含文本和对应的标签(如正面/负面)。
    • 将数据集划分为训练集和验证集。
  2. 模型与分词器加载
    • 从模型库中加载预训练的 bert-base-chinese 模型和对应的分词器。
    • 在预训练模型的基础上,添加一个用于分类的头部(例如,一个线性层),以适应你的分类类别数量。
  3. 数据预处理
    • 使用分词器将原始文本转换为模型可以理解的输入格式,这通常包括:
      • input_ids: 词汇在词表中的索引。
      • attention_mask: 用于区分真实词汇和填充部分的掩码。
      • token_type_ids: 用于区分句子对中的不同句子(单句分类任务中可省略)。
    • 将处理后的数据和标签封装成 PyTorch 的数据集(Dataset)和数据加载器(DataLoader)。
  4. 模型微调
    • 定义训练参数,例如:
      • 训练轮数 (num_train_epochs)
      • 批大小 (per_device_train_batch_size)
      • 学习率 (learning_rate)
      • 权重衰减 (weight_decay) 等。
    • 使用训练集数据对模型进行训练,并在验证集上监控模型性能,防止过拟合。
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("服务态度极差,以后再也不会来了。")
代码关键点解析
  1. 迁移学习的核心 (from_pretrained)
    • BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2):这行代码不仅下载了预训练的 BERT 权重,还自动在 BERT 的顶部添加了一个线性分类层。
    • 微调:在 train_epoch 中,我们更新了整个模型的参数(包括 BERT 的底层参数和顶部的分类层参数)。
  2. 数据处理 (Tokenizer)
    • BERT 需要特定的输入格式:input_ids(词的索引)和 attention_mask(区分真实词和填充词)。
    • [CLS] 标记:BERT 会在句子开头自动添加 [CLS],模型通过这个标记对应的输出向量来进行分类。
  3. 设备管理 (DEVICE)
    • 代码自动检测是否有 GPU 可用。如果有 GPU,训练速度会比 CPU 快几十倍。
  4. 优化器 (AdamW)
    • 这是 Transformer 模型训练的标准优化器,配合学习率预热调度器可以获得更好的收敛效果。

应用到真实场景:

  1. 替换数据:将代码中的 data 字典替换为 pd.read_csv('your_dataset.csv')
  2. 调整超参数:根据你的数据量大小调整 BATCH_SIZEEPOCHS。数据量大时,通常 2-4 个 Epoch 就足够了,防止过拟合。
  3. 模型选择:如果追求更高精度,可以将 MODEL_NAME 替换为更大的模型,如 hfl/chinese-roberta-wwm-ext
进阶优化技巧

为了进一步提升效果或提高训练效率,可以考虑以下高级方法:

  • 参数高效微调 (PEFT)
    对于非常大的模型,全参数微调的成本很高。可以采用参数高效微调方法,只训练模型的一小部分参数,例如:
    • LoRA (Low-Rank Adaptation): 通过在模型层中注入可训练的低秩矩阵来调整模型。
    • Adapter Tuning: 在模型的 Transformer 层之间插入小型的、可训练的“Adapter”模块。
  • 数据清洗
    在进行跨领域情感分类时,可以借鉴一些研究思路。例如,先利用注意力网络等方法学习文本的分布式表示,然后采用类噪声估计等方法检测并剔除源领域中可能导致“负迁移”的低质量样本,从而提升目标领域的分类效果。

完形填空(MLM)功能

将情感分类代码改为完形填空(Masked Language Modeling, MLM)功能,核心思路是从“文本分类”转变为“预测被遮蔽的词”。

核心变化点
  1. 模型变更:从 BertForSequenceClassification 改为 BertForMaskedLM
  2. 任务逻辑:不再输入“文本+标签”,而是输入“带 [MASK] 的文本”。
  3. 输出处理:模型输出的是词汇表中每个词的概率,我们需要取出概率最高的前 N 个词作为预测结果。
代码
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]。")
代码详解
  1. BertForMaskedLM
    • 这是专门用于完形填空任务的模型架构。它的输出层维度是词表大小(对于中文 BERT 通常是 21128),代表每个可能的字或词出现的概率。
  2. tokenizer.mask_token_id
    • 我们需要找到输入句子中 [MASK] 所在的位置,因为模型会对句子中所有位置进行预测,但我们只关心被遮住的那个位置。
  3. torch.topk
    • 这是一个高效获取概率最高前 N 个预测结果的方法。
  4. 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流畅生成文章写作、代码生成、对话聊天、内容摘要、机器翻译、创意文本生成
总结
对比维度BERTGPT
核心架构Transformer EncoderTransformer Decoder
注意力方向双向 (可同时看前后文)单向 (只能看前文)
训练目标掩码语言模型 (MLM),类似“完形填空”自回归语言模型,类似“文本续写”
能力侧重擅长语言理解和分析擅长语言生成和创作
形象类比阅卷老师作家

🔍Qwen架构-GPT改进

核心架构演进:基于GPT的优化

千问的底层逻辑依然是Decoder-only的自回归架构,但在具体实现上引入了多项关键技术改进,以解决标准GPT架构在效率和长上下文处理上的瓶颈。

关键技术改进
1. 注意力机制优化(GQA/MQA)

千问采用了分组查询注意力(GQA)或多查询注意力(MQA)机制。

  • 原理:让多个查询头共享同一组键值对,从而大幅减少推理时需要缓存的键值对数量。
  • 优势:显著降低了显存占用,提升了推理速度,尤其是在处理长文本时效果明显。
机制核心切分逻辑KV 组数关系关键特征类比
MHAQ、K、V 等头数切分Q头数 = KV头数每个 Q 头拥有独立专属的 K、V,表达能力最强。将羊肉Q、鸡肉K、鱼肉V都切成同样数量的薄片,下锅计算注意力,味道最丰富(表达能力强),但切菜太慢(计算和显存开销大)。
GQAQ 切细组,KV 切分组Q头数 = 4 * KV头数组内共享,每组多头 Q 共用一组 K、V,平衡效果与速度。将羊肉Q切成薄片,将鸡肉K和鱼肉V切成较厚的块,下锅计算注意力,既保证了备菜速度,又兼顾了味道(效果与效率的平衡)。
MQA只切分 Q,不切分 KVQ头数 >> 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的情况。

  • 优势:提高了中文处理的效率和准确性,降低了序列长度。
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐