Hugging Face Transformers 入门教程

教程简介

零基础Hugging Face Transformers入门教程,涵盖HF生态系统概览、Pipeline快速上手、Tokenizer深入理解、模型加载保存、Datasets数据集处理、Trainer API训练、PEFT高效微调、推理优化、Transformers Agent等核心技能,配有完整NLP实战项目,适合AI开发者系统学习。

Hugging Face Transformers 入门教程

本教程面向零基础用户,系统介绍 Hugging Face Transformers 库的核心功能与实际应用。通过大量代码示例和详细讲解,帮助你从零开始掌握现代 NLP 和大模型开发的基础技能。


第一章:Hugging Face 生态系统概述

1.1 什么是 Hugging Face

Hugging Face 成立于 2016 年,最初是一家专注于聊天机器人的初创公司。随着时间的推移,它逐渐发展成为全球最重要的人工智能开源社区和技术平台之一。今天,当我们提到 Hugging Face 时,通常指的是由该公司维护的一整套开源工具链和社区生态,它们共同构成了当前最活跃的 AI 开发者社区。

Hugging Face 的核心理念是"民主化 AI"——让更多的开发者、研究人员和企业能够便捷地使用最先进的 AI 模型。这一理念通过其提供的开源库、预训练模型库和协作平台得以实现。无论你是经验丰富的机器学习工程师,还是刚刚入门的编程新手,都可以在 Hugging Face 生态中找到适合自己的工具和资源。

Hugging Face 的官方网站地址是 https://huggingface.co ,在该平台上你可以浏览和下载预训练模型、数据集以及各种演示应用。该平台目前已经成为 AI 领域最活跃的社区之一,每天都有来自全球各地的研究人员和开发者上传新的模型和数据集。

1.2 核心组件介绍

Hugging Face 生态系统由多个紧密集成的组件构成,每个组件负责特定的功能域。理解这些组件之间的关系是高效使用该平台的关键。

Transformers 库

这是 Hugging Face 最核心的 Python 库,提供了数千种预训练模型的统一接口。它支持 PyTorch、TensorFlow 和 JAX 三种主流深度学习框架,让用户可以轻松加载、训练和部署各种模型。Transformers 库覆盖了自然语言处理(NLP)、计算机视觉(CV)、音频处理、多模态任务等多个领域。你可以用几行代码就完成文本分类、情感分析、文本生成、图像识别等复杂任务。

Transformers 库的设计哲学是"一行代码加载模型"。无论你想使用 BERT 做文本分类、使用 GPT-2 做文本生成,还是使用 CLIP 做图文匹配,都可以通过相似的 API 模式来实现。这种一致性大大降低了学习不同模型架构的成本。

Datasets 库

Datasets 库是一个高效的数据集管理和加载工具。它提供了对数千个公开数据集的统一访问接口,并且针对大规模数据集进行了内存优化。该库支持流式加载(streaming),允许你在不完全下载数据集的情况下进行迭代处理。同时,它还提供了丰富的数据预处理和转换功能,帮助你快速完成数据准备工作。

Datasets 库的核心优势在于其内存映射(memory-mapping)技术。当你加载一个大型数据集时,数据不会全部读入内存,而是通过内存映射的方式按需读取。这使得在普通个人电脑上处理数十 GB 的数据集成为可能。

Tokenizers 库

Tokenizers 是一个高性能的分词库,用 Rust 语言编写核心逻辑以确保极致的运行速度。它支持多种分词算法,包括 BPE(字节对编码)、WordPiece、Unigram 等。Tokenizers 库是 Transformers 库中所有分词器的底层实现,提供了训练自定义分词器的能力。

Hub(模型中心)

Hugging Face Hub 是一个在线平台,用于存储和共享模型、数据集以及演示应用(Spaces)。截至目前,Hub 上已有超过数十万个模型和数万个数据集。任何人都可以将自己的模型上传到 Hub 上供社区使用,也可以轻松下载他人分享的模型。Hub 还支持模型版本管理、模型卡片(Model Card)文档化、以及在线推理演示等功能。

Accelerate 库

Accelerate 库简化了分布式训练和混合精度训练的配置过程。无论你是在单 GPU、多 GPU 还是 TPU 集群上训练模型,Accelerate 都能帮助你以最小的代码修改实现硬件资源的充分利用。它抽象了底层的分布式通信细节,让你专注于模型逻辑本身。

PEFT 库

PEFT(Parameter-Efficient Fine-Tuning,参数高效微调)库实现了多种高效微调方法,如 LoRA、Prefix Tuning、P-Tuning 等。这些方法通过只训练模型的一小部分参数来实现微调,大大降低了计算资源和内存的需求。在大模型时代,PEFT 技术使得在消费级 GPU 上微调数十亿参数的模型成为可能。

TRL 库

TRL(Transformer Reinforcement Learning)库专注于基于人类反馈的强化学习(RLHF)训练流程。它实现了 SFT(监督微调)、奖励模型训练和 PPO(近端策略优化)等完整的 RLHF 训练管线。许多开源大语言模型的对齐训练都使用了 TRL 库。

1.3 生态系统的协作关系

这些组件并非孤立存在,而是形成了紧密的工作流。一个典型的大模型开发流程通常涉及以下步骤:

  1. 使用 Datasets 库加载和处理训练数据
  2. 使用 Transformers 库加载预训练模型和分词器
  3. 使用 PEFT 库进行高效微调(可选)
  4. 使用 Trainer API 或 Accelerate 进行分布式训练
  5. 使用 TRL 库进行人类反馈对齐(可选)
  6. 将训练好的模型上传到 Hub 进行分享和部署

1.4 为什么选择 Hugging Face

选择 Hugging Face 作为你的 AI 开发平台有以下几个核心优势:

首先,社区活跃度极高。Hugging Face 社区每天都有新的模型和数据集被上传,你可以轻松找到适用于各种任务的预训练模型。其次,API 设计统一。不同模型之间的接口高度一致,学习成本低。第三,文档完善。官方提供了详尽的文档、教程和示例代码。第四,免费资源丰富。Hub 上的大部分模型和数据集都可以免费使用。最后,工业界广泛采用。从初创公司到科技巨头,众多企业都在生产环境中使用 Hugging Face 的工具。

1.5 本教程的学习路线

本教程将按照由浅入深的顺序,从最基础的 Pipeline 使用开始,逐步深入到分词器原理、模型架构、数据处理、训练优化等进阶主题。每个章节都包含大量可运行的代码示例,建议你在阅读的同时动手实践。


第二章:环境搭建与安装配置

2.1 Python 环境准备

在开始使用 Transformers 库之前,你需要确保系统中安装了合适版本的 Python。Transformers 库要求 Python 3.8 或更高版本,推荐使用 Python 3.10 或 3.11 以获得最佳兼容性。

检查 Python 版本

打开终端或命令行工具,输入以下命令检查当前 Python 版本:

python --version
# 或者
python3 --version

如果你的系统中没有安装 Python,或者版本过低,建议通过以下方式安装:

方式一:使用系统包管理器

# Ubuntu/Debian
sudo apt update
sudo apt install python3 python3-pip python3-venv

# macOS (使用 Homebrew)
brew install python@3.11

# Windows (使用 Chocolatey)
choco install python --version=3.11

方式二:使用 Anaconda/Miniconda

Anaconda 是一个流行的 Python 发行版,特别适合数据科学和机器学习开发。Miniconda 是其轻量版本,只包含 conda 包管理器和 Python。

# 下载并安装 Miniconda (Linux)
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh

# 创建专用环境
conda create -n hf-tutorial python=3.11
conda activate hf-tutorial

2.2 创建虚拟环境

强烈建议在虚拟环境中安装 Transformers 及其依赖,以避免与系统中其他 Python 项目产生包冲突。

使用 venv 创建虚拟环境

# 创建虚拟环境
python3 -m venv ~/hf-env

# 激活虚拟环境 (Linux/macOS)
source ~/hf-env/bin/activate

# 激活虚拟环境 (Windows)
# hf-env\\Scripts\\activate

# 确认激活成功
which python
# 应输出虚拟环境中的 Python 路径

使用 conda 创建虚拟环境

conda create -n hf-env python=3.11 -y
conda activate hf-env

2.3 安装 Transformers 库

安装 Transformers 库有多种方式,根据你的使用场景选择合适的方法。

基础安装

如果你只需要使用 Transformers 的核心功能,执行以下命令即可:

pip install transformers

这会安装 Transformers 库及其必要的依赖,包括 huggingface-hubfilelocknumpypackagingpyyamlregexrequestssafetensorstokenizerstqdm 等。

安装 PyTorch 后端

Transformers 库本身不包含深度学习框架,你需要单独安装 PyTorch、TensorFlow 或 JAX 作为计算后端。推荐使用 PyTorch:

# CPU 版本
pip install torch

# CUDA 11.8 版本 (NVIDIA GPU)
pip install torch --index-url https://download.pytorch.org/whl/cu118

# CUDA 12.1 版本
pip install torch --index-url https://download.pytorch.org/whl/cu121

完整安装(推荐)

推荐使用以下命令进行完整安装,包含常用的附加依赖:

pip install transformers[torch]

或者安装所有可选依赖:

pip install transformers[all]

从源码安装(开发者)

如果你希望使用最新的开发版本,或者需要修改库的源码,可以从 GitHub 仓库安装:

pip install git+https://github.com/huggingface/transformers.git

2.4 安装附加组件

根据你的具体需求,可能还需要安装以下附加组件:

# Datasets 库(数据集处理)
pip install datasets

# Accelerate 库(分布式训练)
pip install accelerate

# PEFT 库(高效微调)
pip install peft

# Evaluate 库(评估指标)
pip install evaluate

# TRL 库(强化学习训练)
pip install trl

# 一站式安装
pip install transformers datasets accelerate peft evaluate trl torch

2.5 验证安装

安装完成后,运行以下 Python 代码验证安装是否成功:

import transformers
import torch

# 打印版本信息
print(f"Transformers 版本: {transformers.__version__}")
print(f"PyTorch 版本: {torch.__version__}")
print(f"CUDA 是否可用: {torch.cuda.is_available()}")

if torch.cuda.is_available():
    print(f"GPU 设备: {torch.cuda.get_device_name(0)}")
    print(f"CUDA 版本: {torch.version.cuda}")

如果你看到类似以下的输出,说明安装成功:

Transformers 版本: 4.40.0
PyTorch 版本: 2.2.0
CUDA 是否可用: True
GPU 设备: NVIDIA GeForce RTX 3080
CUDA 版本: 11.8

2.6 Hugging Face Hub 认证配置

虽然不强制要求,但配置 Hugging Face Hub 的认证令牌可以让你访问更多功能,例如上传模型、使用私有模型、以及获得更高的 API 调用速率限制。

获取访问令牌

  1. 访问 https://huggingface.co 并注册账号
  2. 登录后,点击右上角头像,选择"Settings"
  3. 在左侧菜单中选择"Access Tokens"
  4. 点击"New token"创建一个新的令牌
  5. 选择令牌类型(推荐选择 "write" 权限)

配置令牌

from huggingface_hub import login

# 方式一:交互式登录(会提示输入令牌)
login()

# 方式二:直接传入令牌
login(token="hf_your_token_here")

或者使用命令行工具:

pip install huggingface_hub
huggingface-cli login

2.7 GPU 环境配置

如果你的机器配备了 NVIDIA GPU,正确配置 CUDA 环境可以大幅提升训练和推理速度。

检查 NVIDIA 驱动

nvidia-smi

如果显示 GPU 信息和驱动版本,说明驱动已正确安装。

验证 PyTorch CUDA 支持

import torch

print(f"CUDA 可用: {torch.cuda.is_available()}")
print(f"CUDA 版本: {torch.version.cuda}")
print(f"cuDNN 版本: {torch.backends.cudnn.version()}")
print(f"GPU 数量: {torch.cuda.device_count()}")

# 简单的 GPU 计算测试
if torch.cuda.is_available():
    device = torch.device("cuda")
    x = torch.randn(1000, 1000, device=device)
    y = torch.matmul(x, x.T)
    print(f"GPU 计算测试通过,结果形状: {y.shape}")

2.8 国内镜像配置

由于网络原因,国内用户在访问 Hugging Face Hub 时可能会遇到速度较慢的问题。以下是几种加速方案:

方案一:使用镜像环境变量

# 设置 Hugging Face 镜像
export HF_ENDPOINT=https://hf-mirror.com

# 永久生效(写入 bashrc)
echo 'export HF_ENDPOINT=https://hf-mirror.com' >> ~/.bashrc
source ~/.bashrc

方案二:在 Python 中设置

import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"

# 之后正常使用 Transformers 库即可
from transformers import AutoModel
model = AutoModel.from_pretrained("bert-base-chinese")

方案三:使用 pip 镜像源

# 清华大学镜像源
pip install transformers -i https://pypi.tuna.tsinghua.edu.cn/simple

# 阿里云镜像源
pip install transformers -i https://mirrors.aliyun.com/pypi/simple/

方案四:手动下载模型

# 安装 git lfs
sudo apt install git-lfs
git lfs install

# 克隆模型仓库
git clone https://hf-mirror.com/bert-base-chinese

2.9 IDE 和工具推荐

为了获得更好的开发体验,推荐以下工具:

VS Code:推荐安装 Python 扩展、Jupyter 扩展和 Hugging Face 扩展。

Jupyter Notebook/Lab:特别适合交互式探索和调试模型。

pip install jupyterlab
jupyter lab

Google Colab:如果本地没有 GPU 资源,Google Colab 提供免费的 GPU 使用时间,是学习和实验的理想选择。


第三章:Pipeline 快速上手

3.1 什么是 Pipeline

Pipeline 是 Transformers 库提供的最高层级的 API 抽象。它将模型加载、数据预处理、模型推理和结果后处理封装在一个简洁的接口中,让用户可以用最少的代码完成各种 AI 任务。对于初学者来说,Pipeline 是接触 Transformers 库的最佳入口;对于有经验的开发者,Pipeline 也是快速原型验证的利器。

Pipeline 的核心设计理念是"开箱即用"。你不需要了解模型的具体架构、不需要手动处理分词、不需要关心输出格式——只需指定任务类型和输入数据,Pipeline 会自动处理所有中间步骤。

3.2 Pipeline 的基本用法

创建 Pipeline

from transformers import pipeline

# 方式一:指定任务名称
classifier = pipeline("sentiment-analysis")

# 方式二:指定任务和模型
classifier = pipeline("sentiment-analysis", model="uer/roberta-base-finetuned-chinanews-chinese")

# 方式三:指定设备(GPU/CPU)
classifier = pipeline("sentiment-analysis", device=0)  # 使用第一个 GPU

使用 Pipeline 进行推理

# 单条文本推理
result = classifier("这部电影真的很精彩,我非常喜欢!")
print(result)
# [{'label': 'positive', 'score': 0.9998}]

# 批量推理
texts = [
    "今天的天气真好,心情很愉快。",
    "这个产品质量太差了,非常失望。",
    "服务态度一般,不太满意。",
]
results = classifier(texts)
for text, result in zip(texts, results):
    print(f"文本: {text}")
    print(f"结果: {result}")
    print()

3.3 文本分类 Pipeline

文本分类是最常见的 NLP 任务之一,Pipeline 支持多种分类场景。

情感分析

from transformers import pipeline

# 创建情感分析 Pipeline
sentiment_analyzer = pipeline(
    "sentiment-analysis",
    model="lxyuan/distilbert-base-multilingual-cased-sentiments-student"
)

# 分析单条文本
result = sentiment_analyzer("I absolutely love this product! It exceeded my expectations.")
print(result)
# [{'label': 'positive', 'score': 0.9876}]

# 分析多条文本
reviews = [
    "The food was amazing and the service was excellent!",
    "Terrible experience. Would not recommend to anyone.",
    "It was okay, nothing special but not bad either.",
]
results = sentiment_analyzer(reviews)
for review, result in zip(reviews, results):
    print(f"评论: {review}")
    print(f"情感: {result['label']}, 置信度: {result['score']:.4f}")
    print()

零样本分类

零样本分类允许你在不训练模型的情况下,使用自定义类别进行文本分类。

from transformers import pipeline

# 创建零样本分类 Pipeline
zero_shot_classifier = pipeline(
    "zero-shot-classification",
    model="MoritzLaurberg/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7"
)

# 定义候选标签
candidate_labels = ["科技", "体育", "娱乐", "教育", "财经"]

# 分类
text = "苹果公司今天发布了最新款的 MacBook Pro,搭载了全新的 M3 芯片。"
result = zero_shot_classifier(text, candidate_labels)

print(f"文本: {text}")
print("分类结果:")
for label, score in zip(result["labels"], result["scores"]):
    print(f"  {label}: {score:.4f}")

3.4 文本生成 Pipeline

文本生成是大语言模型的核心能力之一。

from transformers import pipeline

# 创建文本生成 Pipeline
generator = pipeline(
    "text-generation",
    model="Qwen/Qwen2-0.5B-Instruct",
    device_map="auto"
)

# 生成文本
prompt = "人工智能在未来十年将"
result = generator(
    prompt,
    max_new_tokens=200,
    do_sample=True,
    temperature=0.7,
    top_p=0.9,
    repetition_penalty=1.2,
)
print(result[0]["generated_text"])

对话式文本生成

from transformers import pipeline

chat_pipeline = pipeline(
    "text-generation",
    model="Qwen/Qwen2-0.5B-Instruct",
    device_map="auto"
)

messages = [
    {"role": "system", "content": "你是一个专业的编程助手,用简洁明了的中文回答问题。"},
    {"role": "user", "content": "请解释一下什么是递归,并给出一个简单的例子。"},
]

result = chat_pipeline(
    messages,
    max_new_tokens=500,
    do_sample=True,
    temperature=0.7,
)
print(result[0]["generated_text"][-1]["content"])

3.5 问答 Pipeline

问答 Pipeline 可以从给定的上下文中提取答案。

from transformers import pipeline

qa_pipeline = pipeline(
    "question-answering",
    model="uer/roberta-base-chinese-extractive-qa"
)

context = (
    "Hugging Face 是一家专注于自然语言处理技术的人工智能公司。"
    "该公司开发了 Transformers 开源库,支持数千种预训练模型。"
    "Transformers 库支持 PyTorch、TensorFlow 和 JAX 三大深度学习框架。"
    "Hugging Face Hub 上托管了数十万个模型和数万个数据集。"
)

question = "Hugging Face 是什么类型的公司?"
result = qa_pipeline(question=question, context=context)

print(f"问题: {question}")
print(f"答案: {result['answer']}")
print(f"置信度: {result['score']:.4f}")
print(f"答案位置: 字符 {result['start']} 到 {result['end']}")

3.6 命名实体识别 Pipeline

from transformers import pipeline

ner_pipeline = pipeline(
    "ner",
    model="uer/roberta-base-finetuned-cluener2020-chinese",
    aggregation_strategy="simple"
)

text = "张三在北京大学学习计算机科学,毕业后加入了腾讯公司工作。"
results = ner_pipeline(text)

print(f"文本: {text}")
print("识别到的实体:")
for entity in results:
    print(f"  实体: {entity['word']}")
    print(f"  类型: {entity['entity_group']}")
    print(f"  置信度: {entity['score']:.4f}")
    print()

3.7 翻译 Pipeline

from transformers import pipeline

translator_en_zh = pipeline(
    "translation",
    model="Helsinki-NLP/opus-mt-en-zh"
)

text_en = "Machine learning is transforming the way we interact with technology."
result = translator_en_zh(text_en)
print(f"原文: {text_en}")
print(f"译文: {result[0]['translation_text']}")

3.8 文本摘要 Pipeline

from transformers import pipeline

summarizer = pipeline(
    "summarization",
    model="csebuetnlp/mT5_multilingual_XLSum-base",
    tokenizer="csebuetnlp/mT5_multilingual_XLSum-base"
)

long_text = (
    "人工智能技术在近年来取得了飞速发展,特别是深度学习领域的突破,"
    "使得计算机在图像识别、自然语言处理、语音识别等多个任务上达到了"
    "甚至超越人类的水平。大语言模型的出现更是掀起了一场技术革命,"
    "GPT、BERT 等模型的发布改变了人们对 AI 能力的认知。"
    "这些模型通过在海量文本数据上进行预训练,获得了强大的语言理解和生成能力,"
    "可以完成翻译、摘要、问答、代码生成等多种任务。"
)

result = summarizer(long_text, max_length=100, min_length=30)
print(f"摘要: {result[0]['summary_text']}")

3.9 图像分类 Pipeline

from transformers import pipeline
from PIL import Image

# 创建图像分类 Pipeline
image_classifier = pipeline(
    "image-classification",
    model="google/vit-base-patch16-224"
)

# 对图像进行分类
# image = Image.open("cat.jpg")
# results = image_classifier(image)
# for result in results:
#     print(f"标签: {result['label']}, 置信度: {result['score']:.4f}")

3.10 Pipeline 高级配置

指定模型和分词器

from transformers import pipeline

pipe = pipeline(
    "sentiment-analysis",
    model="your-organization/your-model",
    tokenizer="your-organization/your-tokenizer",
)

设置推理参数

from transformers import pipeline

generator = pipeline("text-generation", model="Qwen/Qwen2-0.5B-Instruct")

results = generator(
    ["写一首关于春天的诗", "写一首关于秋天的诗"],
    batch_size=2,
    max_new_tokens=200,
    do_sample=True,
    temperature=0.8,
)

使用 Dataset 进行批量推理

from transformers import pipeline
from datasets import Dataset

classifier = pipeline("sentiment-analysis", device=0)

data = {"text": ["我很开心", "我很伤心", "今天天气不错"]}
dataset = Dataset.from_dict(data)

results = classifier(dataset["text"], batch_size=32)

3.11 自定义 Pipeline

你可以通过继承基类来创建自定义的 Pipeline:

from transformers import Pipeline

class SentimentWithKeywords(Pipeline):
    """自定义 Pipeline:返回情感分析结果及其关键词"""

    def _sanitize_parameters(self, **kwargs):
        preprocess_kwargs = {}
        if "max_length" in kwargs:
            preprocess_kwargs["max_length"] = kwargs["max_length"]
        return preprocess_kwargs, {}, {}

    def preprocess(self, inputs, max_length=512):
        return self.tokenizer(inputs, truncation=True, max_length=max_length, return_tensors="pt")

    def _forward(self, model_inputs):
        return self.model(**model_inputs)

    def postprocess(self, model_outputs):
        logits = model_outputs.logits
        scores = logits.softmax(dim=-1)
        labels = ["negative", "positive"]
        label_idx = scores.argmax().item()
        return {
            "label": labels[label_idx],
            "score": scores[0][label_idx].item(),
        }

3.12 Pipeline 使用注意事项

使用 Pipeline 时需要注意以下几点:

  1. 首次加载较慢:第一次创建 Pipeline 时需要下载模型文件,后续使用会从缓存加载,速度更快。
  2. 内存占用:较大的模型会占用较多的 GPU/内存资源,请根据硬件条件选择合适的模型。
  3. 输入长度限制:大多数模型有最大输入长度限制(通常为 512 或 1024 个 token),超长输入会被截断。
  4. 批处理效率:处理多条数据时,使用批处理比逐条处理效率更高。
  5. 模型选择:不同模型在不同任务和语言上的表现可能差异很大,建议根据实际需求选择合适的模型。

第四章:Tokenizer 深入理解

4.1 什么是 Tokenizer

Tokenizer(分词器)是自然语言处理中的一个关键组件,负责将人类可读的文本转换为模型可以理解的数字序列。这个过程被称为"编码"(encoding),而将数字序列还原为文本的过程则被称为"解码"(decoding)。理解 Tokenizer 的工作原理对于正确使用 Transformer 模型至关重要,因为模型只能处理数字输入,而 Tokenizer 是连接人类语言和模型输入的桥梁。

在 Transformer 模型中,Tokenizer 不仅仅是简单的分词工具。它还需要处理特殊标记的添加(如开始标记、结束标记、填充标记等)、文本的截断和填充、以及注意力掩码的生成等任务。这些预处理步骤直接影响模型的输入质量,进而影响模型的输出效果。

Tokenizer 的工作流程通常包括以下几个步骤:首先,将输入文本按照特定规则分割成子词(subword)单元;然后,将每个子词映射到其在词汇表中的唯一数字 ID;接着,根据需要在序列的首尾添加特殊标记;最后,对序列进行填充或截断以满足长度要求。整个过程对用户来说是透明的,但理解这些细节有助于你更好地调试和优化模型表现。

4.2 常见的分词算法

BPE(Byte Pair Encoding,字节对编码)

BPE 是目前最流行的分词算法之一,被 GPT 系列模型广泛使用。BPE 的基本思想是从单个字符开始,反复合并出现频率最高的字符对,直到达到预设的词汇表大小。这种数据驱动的分词方式能够自动学习到常见的子词模式。

BPE 的核心优势在于它能够在字符级别和单词级别之间找到一个平衡点。对于常见的单词,BPE 会将其作为一个完整的 token 处理;对于罕见的单词,BPE 会将其拆分为多个已知的子词。这种机制使得 BPE 能够处理任何输入文本,不会出现"未知词"的问题。BPE 的训练过程是完全数据驱动的,不需要人工干预,这使得它能够适应任何语言和领域的文本数据。

WordPiece

WordPiece 是 Google 提出的分词算法,被 BERT 系列模型使用。它与 BPE 类似,也是通过合并字符来构建词汇表,但合并的策略略有不同。WordPiece 选择的是能够最大化训练数据似然度的合并操作,而不是简单地选择出现频率最高的字符对。

WordPiece 使用 ## 前缀来标记子词的延续部分。例如,"playing" 可能被分词为 ["play", "##ing"],其中 "##ing" 表示它是前一个词的延续部分。这种设计使得解码时能够正确地将子词还原为完整的单词。

Unigram

Unigram 分词算法由 Google 的 Kudo 提出,它采用了一种与 BPE 和 WordPiece 完全不同的策略。Unigram 从一个较大的候选词汇表开始,通过不断移除对整体似然度影响最小的词来逐步缩减词汇表,直到达到目标大小。这种方法的优势在于它能够考虑全局最优的分词方案,而不是像 BPE 那样只进行局部贪心合并。

SentencePiece

SentencePiece 是 Google 开发的一个独立的分词工具,它直接在原始文本上进行分词,不需要预先进行文本规范化或语言特定的预处理。SentencePiece 支持 BPE 和 Unigram 两种分词算法,并且能够将空格也作为普通字符处理,用特殊的元符号(通常是下划线 _)来表示空格。这使得它特别适合处理中文、日文等不需要空格分隔单词的语言。

4.3 使用 AutoTokenizer

Transformers 库提供了 AutoTokenizer 类,它可以根据模型名称自动加载对应的分词器。这是推荐的分词器加载方式,因为它能自动选择正确的分词器类。

from transformers import AutoTokenizer

# 加载分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

# 查看分词器信息
print(f"词汇表大小: {tokenizer.vocab_size}")
print(f"模型最大输入长度: {tokenizer.model_max_length}")
print(f"特殊标记: {tokenizer.special_tokens_map}")

基本编码操作

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

text = "Hugging Face 是一个优秀的AI平台。"

# 编码
encoded = tokenizer(text)
print(f"输入 ID: {encoded['input_ids']}")
print(f"注意力掩码: {encoded['attention_mask']}")

# 解码
decoded = tokenizer.decode(encoded["input_ids"])
print(f"解码结果: {decoded}")

# 查看 token 列表
tokens = tokenizer.tokenize(text)
print(f"分词结果: {tokens}")

4.4 分词器的核心参数

padding(填充)

当处理批量数据时,同一 batch 中的序列长度可能不同。padding 参数用于将短序列填充到统一长度。

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

texts = ["你好世界", "今天天气真好", "我喜欢编程"]

# 不同策略的填充
# 填充到最长序列长度
encoded_padded = tokenizer(texts, padding=True)
print(f"填充后长度: {[len(ids) for ids in encoded_padded['input_ids']]}")

# 填充到固定长度
encoded_fixed = tokenizer(texts, padding="max_length", max_length=20)
print(f"固定长度: {[len(ids) for ids in encoded_fixed['input_ids']]}")

truncation(截断)

当输入文本超过模型的最大输入长度时,需要进行截断。截断策略可以根据需要选择从左侧或右侧截断。

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

long_text = "这是一段很长很长的文本。" * 100

# 启用截断
encoded = tokenizer(long_text, truncation=True, max_length=128)
print(f"截断后长度: {len(encoded['input_ids'])}")

# 指定截断方向
encoded_right = tokenizer(long_text, truncation=True, max_length=128, truncation_side="right")
encoded_left = tokenizer(long_text, truncation=True, max_length=128, truncation_side="left")

return_tensors(返回张量类型)

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

text = "你好世界"

# 返回 PyTorch 张量
encoded_pt = tokenizer(text, return_tensors="pt")
print(f"PyTorch 张量: {encoded_pt['input_ids'].shape}")

# 返回 NumPy 数组
encoded_np = tokenizer(text, return_tensors="np")
print(f"NumPy 数组: {encoded_np['input_ids'].shape}")

4.5 特殊标记

每种模型都有自己的特殊标记,用于标识文本的开始、结束、填充等。了解这些特殊标记对于正确使用模型非常重要。

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

# 查看所有特殊标记
print(f"CLS 标记: {tokenizer.cls_token} -> ID: {tokenizer.cls_token_id}")
print(f"SEP 标记: {tokenizer.sep_token} -> ID: {tokenizer.sep_token_id}")
print(f"PAD 标记: {tokenizer.pad_token} -> ID: {tokenizer.pad_token_id}")
print(f"UNK 标记: {tokenizer.unk_token} -> ID: {tokenizer.unk_token_id}")
print(f"MASK 标记: {tokenizer.mask_token} -> ID: {tokenizer.mask_token_id}")

# BERT 模型的编码会在首尾添加 [CLS] 和 [SEP]
text = "你好世界"
encoded = tokenizer(text)
tokens = tokenizer.convert_ids_to_tokens(encoded["input_ids"])
print(f"Token 序列: {tokens}")
# ['[CLS]', '你', '好', '世', '界', '[SEP]']

4.6 批量编码

在实际应用中,通常需要同时处理多条文本。Tokenizer 提供了便捷的批量编码功能,可以高效地处理变长序列。

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

texts = [
    "今天天气真好",
    "我喜欢用 Python 编程",
    "Hugging Face 社区很棒",
]

# 批量编码(自动填充到最长序列)
batch_encoded = tokenizer(
    texts,
    padding=True,
    truncation=True,
    max_length=128,
    return_tensors="pt"
)

print(f"输入 ID 形状: {batch_encoded['input_ids'].shape}")
print(f"注意力掩码形状: {batch_encoded['attention_mask'].shape}")

# 逐条解码
for i, text in enumerate(texts):
    decoded = tokenizer.decode(batch_encoded["input_ids"][i], skip_special_tokens=True)
    print(f"原文: {text}")
    print(f"解码: {decoded}")
    print()

4.7 快速分词器(Fast Tokenizer)

Transformers 库提供了两种分词器实现:Python 实现的慢速分词器和基于 Rust Tokenizers 库的快速分词器。快速分词器的速度通常是慢速分词器的数倍到数十倍。

from transformers import AutoTokenizer

# 加载分词器(默认使用快速版本)
fast_tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
print(f"是否为快速分词器: {fast_tokenizer.is_fast}")

# 获取 token 在原始文本中的位置
text = "Hugging Face 是一个优秀的AI平台"
encoded = fast_tokenizer(text, return_offsets_mapping=True)
print(f"偏移映射: {encoded['offset_mapping']}")
# 每个元素 (start, end) 表示 token 在原始文本中的字符位置范围

4.8 保存和加载自定义分词器

from transformers import AutoTokenizer

# 加载预训练分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

# 保存到本地目录
tokenizer.save_pretrained("./my_tokenizer")

# 从本地目录加载
loaded_tokenizer = AutoTokenizer.from_pretrained("./my_tokenizer")

# 验证一致性
text = "测试文本"
assert tokenizer(text)["input_ids"] == loaded_tokenizer(text)["input_ids"]
print("分词器保存和加载验证成功!")

4.9 训练自定义分词器

如果你需要处理特定领域的文本(如医学文献、法律文书、编程代码等),可能需要训练自定义分词器。Transformers 库底层使用的 Tokenizers 库提供了完整的分词器训练功能。

from tokenizers import Tokenizer, models, trainers, pre_tokenizers

# 初始化一个 BPE 分词器
tokenizer = Tokenizer(models.BPE())

# 设置预分词器(按空格分割)
tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()

# 配置训练器
trainer = trainers.BpeTrainer(
    vocab_size=10000,
    min_frequency=2,
    special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"]
)

# 准备训练语料
files = ["corpus_part1.txt", "corpus_part2.txt"]

# 训练分词器
tokenizer.train(files, trainer)

# 保存分词器
tokenizer.save("custom_tokenizer.json")

# 使用训练好的分词器
encoded = tokenizer.encode("这是一段测试文本")
print(f"Token: {encoded.tokens}")
print(f"ID: {encoded.ids}")

4.10 分词器最佳实践

在使用分词器时,建议遵循以下最佳实践:

  1. 始终使用与模型匹配的分词器:不同的模型使用不同的分词算法和词汇表,混用会导致错误的结果。
  2. 注意最大长度限制:了解你的模型支持的最大输入长度,并在编码时适当设置 truncation 参数。
  3. 批量处理时使用 padding=True:这样可以自动将所有序列填充到 batch 中最长序列的长度。
  4. 使用快速分词器:快速分词器的速度通常是慢速分词器的数倍到数十倍,推荐优先使用。
  5. 理解特殊标记:了解你的模型使用的特殊标记及其含义,这有助于正确构建输入。
  6. 处理未知词:了解分词器如何处理未知词(使用 UNK 标记还是子词拆分),这有助于你理解模型在处理罕见词汇时的表现。

第五章:模型加载与保存

5.1 模型加载基础

Transformers 库提供了统一的模型加载接口,无论你想使用哪种模型架构,都可以通过相似的 API 来加载。库中包含了数百种模型架构的实现,覆盖了文本分类、文本生成、图像分类、语音识别等多个领域。

使用 AutoModel 加载模型

from transformers import AutoModel, AutoTokenizer

# 加载预训练模型和分词器
model_name = "bert-base-chinese"
model = AutoModel.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# 查看模型结构
print(f"模型类型: {type(model).__name__}")
print(f"参数量: {model.num_parameters():,}")

# 使用模型进行推理
text = "你好,世界!"
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs)

print(f"输出形状: {outputs.last_hidden_state.shape}")
# 输出形状: torch.Size([1, 8, 768])  -> (batch_size, sequence_length, hidden_size)

使用特定模型类加载

from transformers import BertModel, BertTokenizer

# 直接使用特定模型类
model = BertModel.from_pretrained("bert-base-chinese")
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")

5.2 不同任务的模型加载

Transformers 库为不同的下游任务提供了专门的模型类。选择正确的模型类非常重要,因为不同任务需要不同的输出头(head)。

文本分类模型

from transformers import AutoModelForSequenceClassification, AutoTokenizer

# 加载用于文本分类的模型
model = AutoModelForSequenceClassification.from_pretrained(
    "bert-base-chinese",
    num_labels=3  # 指定分类类别数
)

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

# 推理
text = "这个产品非常好用"
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs)

# logits 包含每个类别的分数
print(f"分类 logits: {outputs.logits}")
print(f"logits 形状: {outputs.logits.shape}")  # (1, 3)

掩码语言模型

from transformers import AutoModelForMaskedLM, AutoTokenizer

model = AutoModelForMaskedLM.from_pretrained("bert-base-chinese")
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

# 预测被掩码的词
text = "今天天气[MASK]好。"
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs)

import torch
predictions = outputs.logits
mask_token_index = (inputs.input_ids == tokenizer.mask_token_id).nonzero(as_tuple=True)[1]
mask_token_logits = predictions[0, mask_token_index, :]
top_tokens = torch.topk(mask_token_logits, 5, dim=-1)

print("被掩码位置最可能的词:")
for token_id in top_tokens.indices[0]:
    token = tokenizer.decode([token_id])
    print(f"  {token}")

因果语言模型(文本生成)

from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2-0.5B-Instruct")
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-0.5B-Instruct")

prompt = "人工智能的未来发展趋势是"
inputs = tokenizer(prompt, return_tensors="pt")

# 生成文本
outputs = model.generate(
    **inputs,
    max_new_tokens=200,
    do_sample=True,
    temperature=0.7,
    top_p=0.9,
)

generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(generated_text)

5.3 模型配置

每个 Transformer 模型都有一个配置对象,定义了模型的架构参数。理解配置对象有助于你自定义模型架构或调试模型行为。

from transformers import AutoConfig, AutoModel

# 加载模型配置
config = AutoConfig.from_pretrained("bert-base-chinese")
print(config)
# 输出包含: hidden_size, num_attention_heads, num_hidden_layers 等参数

# 使用自定义配置创建模型
custom_config = AutoConfig.from_pretrained(
    "bert-base-chinese",
    hidden_size=512,       # 修改隐藏层大小
    num_hidden_layers=6,   # 修改层数
    num_attention_heads=8, # 修改注意力头数
)

# 从配置创建未初始化的模型
custom_model = AutoModel.from_config(custom_config)
print(f"自定义模型参数量: {custom_model.num_parameters():,}")

5.4 模型保存

训练完成后,你需要将模型保存到本地或上传到 Hub。Transformers 库支持多种保存格式。

保存到本地

from transformers import AutoModel, AutoTokenizer

model = AutoModel.from_pretrained("bert-base-chinese")
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

# 保存模型和分词器
save_directory = "./my_saved_model"
model.save_pretrained(save_directory)
tokenizer.save_pretrained(save_directory)

# 保存的目录包含以下文件:
# - config.json: 模型配置
# - model.safetensors 或 pytorch_model.bin: 模型权重
# - tokenizer.json: 分词器
# - tokenizer_config.json: 分词器配置
# - special_tokens_map.json: 特殊标记映射

保存为不同格式

from transformers import AutoModel
import torch

model = AutoModel.from_pretrained("bert-base-chinese")

# 保存为 safetensors 格式(推荐,更安全)
model.save_pretrained("./model_safetensors", safe_serialization=True)

# 保存为 PyTorch 格式
model.save_pretrained("./model_pytorch", safe_serialization=False)

5.5 模型上传到 Hub

将训练好的模型上传到 Hugging Face Hub 可以方便地与社区分享。

from transformers import AutoModel, AutoTokenizer

model = AutoModel.from_pretrained("bert-base-chinese")
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

# 上传到 Hub
model.push_to_hub("your-username/your-model-name")
tokenizer.push_to_hub("your-username/your-model-name")

5.6 半精度与量化加载

对于大型模型,可以使用半精度或量化技术减少内存占用。这在资源有限的环境下尤为重要。

from transformers import AutoModel
import torch

# 使用半精度(FP16)加载
model_fp16 = AutoModel.from_pretrained(
    "bert-base-chinese",
    torch_dtype=torch.float16
)

# 使用 BFloat16 加载
model_bf16 = AutoModel.from_pretrained(
    "bert-base-chinese",
    torch_dtype=torch.bfloat16
)

# 使用 8-bit 量化加载(需要 bitsandbytes 库)
# pip install bitsandbytes
# model_8bit = AutoModel.from_pretrained(
#     "big-model-name",
#     load_in_8bit=True,
#     device_map="auto"
# )

# 使用 4-bit 量化加载
# model_4bit = AutoModel.from_pretrained(
#     "big-model-name",
#     load_in_4bit=True,
#     device_map="auto"
# )

5.7 设备映射

对于大型模型,可能需要将模型的不同层分布到多个 GPU 上,以充分利用硬件资源。

from transformers import AutoModel

# 自动设备映射
model = AutoModel.from_pretrained(
    "big-model-name",
    device_map="auto"  # 自动分配到可用设备
)

# 查看设备映射
print(model.hf_device_map)

5.8 模型参数冻结

在微调场景中,通常需要冻结模型的部分参数,只训练特定的层。这可以减少计算量并防止过拟合。

from transformers import AutoModel

model = AutoModel.from_pretrained("bert-base-chinese")

# 冻结所有参数
for param in model.parameters():
    param.requires_grad = False

# 只解冻最后两层
for param in model.encoder.layer[-2:].parameters():
    param.requires_grad = True

# 统计可训练参数
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
total_params = sum(p.numel() for p in model.parameters())
print(f"可训练参数: {trainable_params:,} / {total_params:,} ({100 * trainable_params / total_params:.2f}%)")

5.9 模型缓存管理

Transformers 库会自动缓存下载的模型到本地目录。了解缓存机制有助于管理磁盘空间。

from transformers import AutoModel
from huggingface_hub import scan_cache_dir

# 查看缓存信息
cache_info = scan_cache_dir()
print(f"缓存目录大小: {cache_info.size_on_disk / (1024**3):.2f} GB")
print(f"缓存的模型数量: {len(cache_info.repos)}")

# 清理缓存可以使用命令行工具
# huggingface-cli delete-cache

第六章:数据集处理

6.1 Datasets 库简介

Datasets 是 Hugging Face 提供的高效数据集管理库,专门用于机器学习数据的加载、处理和存储。它基于 Apache Arrow 格式构建,具有极高的内存效率和处理速度。Datasets 库的核心设计理念是:让数据处理变得简单、高效且可复现。

在机器学习项目中,数据处理往往占据了大量开发时间。从数据下载、格式转换、清洗、预处理到最终的数据加载,每个环节都可能遇到各种问题。Datasets 库将这些环节统一在一个简洁的 API 下,极大地简化了数据处理的工作流程。

Datasets 库的核心优势包括:内存映射技术使你能在有限内存下处理超大数据集;流式加载模式允许你不下载全部数据就能迭代处理;统一的 API 让你轻松切换不同的数据集;与 Transformers 库的无缝集成使数据预处理变得非常便捷。

6.2 加载数据集

从 Hub 加载数据集

from datasets import load_dataset

# 加载数据集
dataset = load_dataset("imdb")
print(dataset)
# DatasetDict({
#     train: Dataset({features: ['text', 'label'], num_rows: 25000})
#     test: Dataset({features: ['text', 'label'], num_rows: 25000})
# })

# 查看数据样本
print(dataset["train"][0])
# {'text': 'This is a great movie...', 'label': 1}

# 查看特征
print(dataset["train"].features)
# {'text': Value(dtype='string'), 'label': ClassLabel(names=['neg', 'pos'])}

加载特定数据集分割

from datasets import load_dataset

# 只加载训练集
train_dataset = load_dataset("imdb", split="train")

# 加载部分数据
subset = load_dataset("imdb", split="train[:1000]")  # 前 1000 条
subset = load_dataset("imdb", split="train[:10%]")    # 前 10%
subset = load_dataset("imdb", split="train[10%:20%]")  # 10%-20%

流式加载(适合大型数据集)

from datasets import load_dataset

# 流式加载,不一次性下载全部数据
dataset = load_dataset("imdb", streaming=True)

for i, example in enumerate(dataset["train"]):
    print(example["text"][:100])
    if i >= 5:
        break

6.3 从本地文件加载数据集

from datasets import load_dataset

# 从 CSV 文件加载
dataset = load_dataset("csv", data_files="data/train.csv")

# 从 JSON 文件加载
dataset = load_dataset("json", data_files="data/train.jsonl")

# 从文本文件加载
dataset = load_dataset("text", data_files="data/corpus.txt")

# 从多个文件加载
dataset = load_dataset("csv", data_files={
    "train": "data/train.csv",
    "test": "data/test.csv"
})

6.4 从 Python 字典创建数据集

from datasets import Dataset, DatasetDict

# 从字典创建数据集
data = {
    "text": ["我喜欢编程", "今天天气很好", "机器学习很有趣"],
    "label": [1, 0, 1],
    "category": ["技术", "天气", "技术"],
}
dataset = Dataset.from_dict(data)
print(dataset)

# 创建包含训练集和测试集的 DatasetDict
train_data = {"text": ["样本1", "样本2"], "label": [0, 1]}
test_data = {"text": ["样本3", "样本4"], "label": [1, 0]}

dataset_dict = DatasetDict({
    "train": Dataset.from_dict(train_data),
    "test": Dataset.from_dict(test_data),
})

6.5 数据集操作

选择和过滤

from datasets import load_dataset

dataset = load_dataset("imdb", split="train")

# 选择前 N 条数据
subset = dataset.select(range(100))

# 按条件过滤
positive_reviews = dataset.filter(lambda x: x["label"] == 1)
long_reviews = dataset.filter(lambda x: len(x["text"]) > 1000)

print(f"正面评论数: {len(positive_reviews)}")
print(f"长评论数: {len(long_reviews)}")

列操作

from datasets import load_dataset

dataset = load_dataset("imdb", split="train")

# 添加新列
def add_text_length(example):
    example["text_length"] = len(example["text"])
    return example

dataset = dataset.map(add_text_length)

# 删除列
dataset = dataset.remove_columns(["text_length"])

# 重命名列
dataset = dataset.rename_column("label", "sentiment")

排序和打乱

from datasets import load_dataset

dataset = load_dataset("imdb", split="train")

# 按列排序
sorted_dataset = dataset.sort("label")

# 打乱数据
shuffled_dataset = dataset.shuffle(seed=42)

# 打乱并选择前 N 条
random_subset = dataset.shuffle(seed=42).select(range(1000))

6.6 数据预处理与映射

map 函数是 Datasets 库中最强大的功能之一,它允许你对数据集中的每个样本应用自定义的处理函数。

from datasets import load_dataset
from transformers import AutoTokenizer

dataset = load_dataset("imdb", split="train")
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

# 使用 map 进行分词
def tokenize_function(examples):
    return tokenizer(
        examples["text"],
        padding="max_length",
        truncation=True,
        max_length=512
    )

tokenized_dataset = dataset.map(tokenize_function, batched=True)
print(tokenized_dataset)

批量处理

# batched=True 可以显著提高处理速度
def process_batch(examples):
    texts = examples["text"]
    processed = []
    for text in texts:
        processed.append(text.lower().strip())
    examples["processed_text"] = processed
    return examples

processed_dataset = dataset.map(process_batch, batched=True, batch_size=1000)

多进程处理

# 使用多进程加速处理
tokenized_dataset = dataset.map(
    tokenize_function,
    batched=True,
    num_proc=4,  # 使用 4 个进程
)

6.7 数据集格式转换

from datasets import load_dataset

dataset = load_dataset("imdb", split="train")

# 设置输出格式为 PyTorch 张量
dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "label"])

# 设置为 Pandas DataFrame
dataset.set_format(type="pandas")
df = dataset[:10]
print(df)

# 重置格式
dataset.reset_format()

6.8 保存和加载处理后的数据集

from datasets import load_dataset, load_from_disk

dataset = load_dataset("imdb", split="train")

# 保存到磁盘(Arrow 格式)
dataset.save_to_disk("./processed_dataset")

# 从磁盘加载
loaded_dataset = load_from_disk("./processed_dataset")

# 保存为 CSV
dataset.to_csv("./dataset.csv")

# 保存为 JSON
dataset.to_json("./dataset.jsonl")

6.9 数据集与 DataLoader 集成

from datasets import load_dataset
from transformers import AutoTokenizer
from torch.utils.data import DataLoader

# 加载并处理数据集
dataset = load_dataset("imdb", split="train")
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

def tokenize_function(examples):
    return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=256)

tokenized_dataset = dataset.map(tokenize_function, batched=True)

# 设置 PyTorch 格式
tokenized_dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "label"])

# 创建 DataLoader
dataloader = DataLoader(tokenized_dataset, batch_size=16, shuffle=True)

# 迭代数据
for batch in dataloader:
    print(f"输入形状: {batch['input_ids'].shape}")
    print(f"标签: {batch['label']}")
    break

6.10 数据集处理最佳实践

  1. 先小规模试验:在处理完整数据集之前,先用 select(range(100)) 选择小规模数据测试你的处理逻辑。
  2. 使用 batched=True:批量处理比逐条处理快得多,尤其是在分词等操作中。
  3. 使用 num_proc:利用多进程加速数据处理。
  4. 缓存机制map 函数会自动缓存结果,避免重复计算。
  5. 内存映射:Datasets 使用 Apache Arrow 的内存映射技术,即使数据集很大也不会占用太多内存。
  6. 及时释放不需要的列:使用 remove_columns 删除不再需要的列以节省内存。
  7. 使用流式模式处理超大数据集:当数据集过大无法完全下载时,使用 streaming=True 进行流式处理。
  8. 设置随机种子:在使用 shuffle 时设置固定的随机种子以确保结果可复现。

第七章:Trainer API 训练实战

7.1 Trainer API 概述

Trainer 是 Transformers 库提供的高级训练 API,它封装了训练循环中的各种常见操作,包括前向传播、反向传播、梯度裁剪、学习率调度、混合精度训练、分布式训练等。使用 Trainer API,你可以在不编写大量训练代码的情况下,快速开始模型训练。

Trainer API 的核心优势在于其高度的可配置性和易用性。你可以通过 TrainingArguments 类来配置训练的各种超参数,通过 compute_metrics 函数来自定义评估指标,通过回调函数(Callback)来在训练的不同阶段插入自定义逻辑。Trainer 还内置了对 TensorBoard 和 Weights & Biases 等实验追踪工具的支持。

7.2 准备训练数据

在使用 Trainer 训练模型之前,需要准备好训练数据和评估数据。数据通常需要经过分词处理,并转换为模型可以接受的格式。

from datasets import load_dataset
from transformers import AutoTokenizer

# 加载数据集
dataset = load_dataset("imdb", split="train[:2000]")  # 使用小规模数据做演示

# 加载分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

# 数据预处理函数
def preprocess_function(examples):
    return tokenizer(
        examples["text"],
        truncation=True,
        padding="max_length",
        max_length=256
    )

# 应用预处理
tokenized_dataset = dataset.map(preprocess_function, batched=True)

# 划分训练集和验证集
split_dataset = tokenized_dataset.train_test_split(test_size=0.2, seed=42)
train_dataset = split_dataset["train"]
eval_dataset = split_dataset["test"]

print(f"训练集大小: {len(train_dataset)}")
print(f"验证集大小: {len(eval_dataset)}")

7.3 定义评估指标

import evaluate
import numpy as np

# 加载评估指标
accuracy_metric = evaluate.load("accuracy")

def compute_metrics(eval_pred):
    """计算评估指标"""
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)
    return accuracy_metric.compute(predictions=predictions, references=labels)

7.4 配置训练参数

TrainingArguments 类包含了训练过程中的所有可配置参数。合理设置这些参数对于获得良好的训练效果至关重要。

from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="./results",              # 输出目录
    num_train_epochs=3,                   # 训练轮数
    per_device_train_batch_size=16,       # 每设备训练批次大小
    per_device_eval_batch_size=16,        # 每设备评估批次大小
    warmup_steps=500,                     # 预热步数
    weight_decay=0.01,                    # 权重衰减
    logging_dir="./logs",                 # 日志目录
    logging_steps=50,                     # 每 50 步记录一次日志
    eval_strategy="epoch",                # 每个 epoch 评估一次
    save_strategy="epoch",                # 每个 epoch 保存一次
    load_best_model_at_end=True,          # 训练结束后加载最佳模型
    metric_for_best_model="accuracy",     # 用于选择最佳模型的指标
    report_to="none",                     # 不上报到外部工具(可选 "tensorboard")
    fp16=False,                           # 是否使用混合精度训练
    dataloader_num_workers=4,             # 数据加载器的工作进程数
    seed=42,                              # 随机种子
)

7.5 创建并运行训练

from transformers import AutoModelForSequenceClassification, Trainer

# 加载模型
model = AutoModelForSequenceClassification.from_pretrained(
    "bert-base-uncased",
    num_labels=2
)

# 创建 Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    compute_metrics=compute_metrics,
)

# 开始训练
trainer.train()

# 评估模型
eval_results = trainer.evaluate()
print(f"评估结果: {eval_results}")

7.6 使用回调函数

Trainer 支持通过回调函数在训练的不同阶段插入自定义逻辑。Transformers 内置了多种常用的回调函数。

from transformers import TrainerCallback, EarlyStoppingCallback

# 自定义回调:打印训练进度
class ProgressCallback(TrainerCallback):
    def on_log(self, args, state, control, logs=None, **kwargs):
        if logs:
            step = state.global_step
            if "loss" in logs:
                print(f"步骤 {step}: 损失 = {logs['loss']:.4f}")
            if "eval_accuracy" in logs:
                print(f"步骤 {step}: 验证准确率 = {logs['eval_accuracy']:.4f}")

# 使用早停回调
early_stopping = EarlyStoppingCallback(
    early_stopping_patience=3,   # 连续 3 次评估无改善则停止
    early_stopping_threshold=0.01  # 改善幅度阈值
)

# 创建带有回调的 Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    compute_metrics=compute_metrics,
    callbacks=[ProgressCallback(), early_stopping],
)

trainer.train()

7.7 学习率调度

学习率是训练过程中最重要的超参数之一。Trainer 内置了多种学习率调度策略。

from transformers import TrainingArguments

# 使用余弦退火调度
training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=10,
    per_device_train_batch_size=16,
    learning_rate=2e-5,
    lr_scheduler_type="cosine",       # 余弦退火
    warmup_ratio=0.1,                 # 预热比例
    weight_decay=0.01,
)

# 可选的调度类型:
# "linear": 线性衰减
# "cosine": 余弦退火
# "cosine_with_restarts": 带重启的余弦退火
# "polynomial": 多项式衰减
# "constant": 恒定学习率
# "constant_with_warmup": 预热后恒定

7.8 保存和加载训练状态

Trainer 支持保存和恢复训练状态,这对于长时间的训练任务非常有用。

from transformers import Trainer, AutoModelForSequenceClassification

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    compute_metrics=compute_metrics,
)

# 从检查点恢复训练
# trainer.train(resume_from_checkpoint="./results/checkpoint-500")

# 保存模型
trainer.save_model("./final_model")

# 加载保存的模型
loaded_model = AutoModelForSequenceClassification.from_pretrained("./final_model")

7.9 混合精度训练

混合精度训练通过使用半精度浮点数(FP16)来减少内存占用并加速训练,同时保持模型精度。

from transformers import TrainingArguments

# 启用 FP16 混合精度训练
training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=32,  # FP16 可以使用更大的批次
    fp16=True,                        # 启用 FP16
    fp16_opt_level="O1",              # 混合精度级别
)

# 使用 BFloat16(Ampere 架构 GPU 及以上)
training_args_bf16 = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=32,
    bf16=True,                        # 启用 BFloat16
)

7.10 使用 Trainer 进行预测

训练完成后,可以使用 Trainer 进行预测。

from transformers import Trainer

trainer = Trainer(model=model)

# 使用 trainer.predict 进行预测
predictions = trainer.predict(test_dataset)
print(f"预测 logits 形状: {predictions.predictions.shape}")
print(f"标签: {predictions.label_ids}")
print(f"指标: {predictions.metrics}")

# 将 logits 转换为预测标签
import numpy as np
pred_labels = np.argmax(predictions.predictions, axis=-1)

7.11 Trainer 最佳实践

  1. 合理设置批次大小:根据 GPU 内存调整 per_device_train_batch_size,如果内存不足可以使用梯度累积。
  2. 使用预热:设置 warmup_stepswarmup_ratio 可以帮助训练更稳定。
  3. 监控训练过程:使用 logging_steps 定期记录训练指标,使用 TensorBoard 可视化。
  4. 早停策略:使用 EarlyStoppingCallback 防止过拟合。
  5. 保存检查点:定期保存训练状态,以便在中断后恢复训练。
  6. 使用梯度累积:当 GPU 内存不足以支持大批次时,使用 gradient_accumulation_steps

第八章:手动训练循环

8.1 为什么需要手动训练循环

虽然 Trainer API 提供了便捷的训练接口,但在某些场景下,你需要完全控制训练过程的每一步。手动训练循环让你能够:

  1. 自定义损失函数和优化策略
  2. 实现特殊的梯度处理逻辑
  3. 在训练过程中插入复杂的业务逻辑
  4. 更好地理解模型训练的底层原理
  5. 实现 Trainer 不支持的训练范式

手动训练循环是深度学习开发者的基本功,理解它有助于你更好地使用高级 API 并在遇到问题时进行调试。

8.2 基本训练循环结构

一个标准的 PyTorch 训练循环包含以下步骤:

  1. 准备数据加载器
  2. 初始化模型和优化器
  3. 遍历数据批次
  4. 前向传播
  5. 计算损失
  6. 反向传播
  7. 更新参数
  8. 梯度清零
import torch
from torch.utils.data import DataLoader
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from datasets import load_dataset

# 1. 准备数据
dataset = load_dataset("imdb", split="train[:1000]")
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

def tokenize_fn(examples):
    return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=128)

tokenized = dataset.map(tokenize_fn, batched=True)
tokenized.set_format(type="torch", columns=["input_ids", "attention_mask", "label"])

dataloader = DataLoader(tokenized, batch_size=16, shuffle=True)

# 2. 初始化模型和优化器
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = AutoModelForSequenceClassification.from_pretrained(
    "bert-base-uncased", num_labels=2
).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5, weight_decay=0.01)

# 3. 训练循环
model.train()
for epoch in range(3):
    total_loss = 0
    for batch_idx, batch in enumerate(dataloader):
        # 将数据移到设备
        input_ids = batch["input_ids"].to(device)
        attention_mask = batch["attention_mask"].to(device)
        labels = batch["label"].to(device)

        # 4. 前向传播
        outputs = model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            labels=labels
        )
        loss = outputs.loss

        # 6. 反向传播
        loss.backward()

        # 梯度裁剪
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

        # 7. 更新参数
        optimizer.step()

        # 8. 梯度清零
        optimizer.zero_grad()

        total_loss += loss.item()

        if batch_idx % 50 == 0:
            print(f"Epoch {epoch+1}, Batch {batch_idx}: Loss = {loss.item():.4f}")

    avg_loss = total_loss / len(dataloader)
    print(f"Epoch {epoch+1} 完成, 平均损失: {avg_loss:.4f}")

8.3 学习率调度器

在手动训练循环中,你可以灵活地使用各种学习率调度策略。

from transformers import get_scheduler

# 创建学习率调度器
num_epochs = 3
num_training_steps = num_epochs * len(dataloader)

lr_scheduler = get_scheduler(
    name="cosine",                     # 调度类型
    optimizer=optimizer,
    num_warmup_steps=int(0.1 * num_training_steps),  # 10% 预热
    num_training_steps=num_training_steps,
)

# 在训练循环中使用
for epoch in range(num_epochs):
    for batch in dataloader:
        # ... 前向传播、反向传播 ...
        optimizer.step()
        lr_scheduler.step()  # 更新学习率
        optimizer.zero_grad()

        # 查看当前学习率
        current_lr = lr_scheduler.get_last_lr()[0]

8.4 梯度累积

当 GPU 内存不足以支持大批次时,可以使用梯度累积来模拟大批次训练。

import torch
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)

accumulation_steps = 4  # 每 4 步更新一次参数
effective_batch_size = 16 * accumulation_steps  # 等效批次大小

model.train()
optimizer.zero_grad()

for step, batch in enumerate(dataloader):
    input_ids = batch["input_ids"].to(device)
    attention_mask = batch["attention_mask"].to(device)
    labels = batch["label"].to(device)

    outputs = model(
        input_ids=input_ids,
        attention_mask=attention_mask,
        labels=labels
    )
    loss = outputs.loss / accumulation_steps  # 缩放损失
    loss.backward()

    if (step + 1) % accumulation_steps == 0:
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        optimizer.step()
        optimizer.zero_grad()

8.5 混合精度训练

使用 PyTorch 的 torch.cuda.amp 实现混合精度训练,可以加速训练并减少内存占用。

import torch
from torch.cuda.amp import autocast, GradScaler

model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
scaler = GradScaler()  # 梯度缩放器

model.train()
for epoch in range(3):
    for batch in dataloader:
        input_ids = batch["input_ids"].to(device)
        attention_mask = batch["attention_mask"].to(device)
        labels = batch["label"].to(device)

        optimizer.zero_grad()

        # 使用自动混合精度
        with autocast():
            outputs = model(
                input_ids=input_ids,
                attention_mask=attention_mask,
                labels=labels
            )
            loss = outputs.loss

        # 缩放损失并反向传播
        scaler.scale(loss).backward()

        # 更新参数
        scaler.unscale_(optimizer)
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        scaler.step(optimizer)
        scaler.update()

8.6 模型评估循环

import torch
import numpy as np

def evaluate_model(model, eval_dataloader, device):
    """评估模型"""
    model.eval()
    total_loss = 0
    all_predictions = []
    all_labels = []

    with torch.no_grad():
        for batch in eval_dataloader:
            input_ids = batch["input_ids"].to(device)
            attention_mask = batch["attention_mask"].to(device)
            labels = batch["label"].to(device)

            outputs = model(
                input_ids=input_ids,
                attention_mask=attention_mask,
                labels=labels
            )

            total_loss += outputs.loss.item()
            predictions = torch.argmax(outputs.logits, dim=-1)
            all_predictions.extend(predictions.cpu().numpy())
            all_labels.extend(labels.cpu().numpy())

    avg_loss = total_loss / len(eval_dataloader)
    accuracy = np.mean(np.array(all_predictions) == np.array(all_labels))

    return {
        "loss": avg_loss,
        "accuracy": accuracy,
        "predictions": all_predictions,
        "labels": all_labels,
    }

# 使用评估函数
eval_results = evaluate_model(model, eval_dataloader, device)
print(f"验证损失: {eval_results['loss']:.4f}")
print(f"验证准确率: {eval_results['accuracy']:.4f}")

8.7 完整的训练和评估流程

下面是一个整合了所有组件的完整训练流程:

import torch
from torch.utils.data import DataLoader
from transformers import (
    AutoModelForSequenceClassification,
    AutoTokenizer,
    get_scheduler
)
from datasets import load_dataset
import numpy as np

# 配置
MODEL_NAME = "bert-base-uncased"
NUM_EPOCHS = 3
BATCH_SIZE = 16
LEARNING_RATE = 2e-5
MAX_LENGTH = 128
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 准备数据
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
dataset = load_dataset("imdb", split="train[:2000]")

def tokenize_fn(examples):
    return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=MAX_LENGTH)

tokenized = dataset.map(tokenize_fn, batched=True)
split = tokenized.train_test_split(test_size=0.2, seed=42)

train_data = split["train"]
eval_data = split["test"]

train_data.set_format(type="torch", columns=["input_ids", "attention_mask", "label"])
eval_data.set_format(type="torch", columns=["input_ids", "attention_mask", "label"])

train_loader = DataLoader(train_data, batch_size=BATCH_SIZE, shuffle=True)
eval_loader = DataLoader(eval_data, batch_size=BATCH_SIZE)

# 初始化模型
model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME, num_labels=2).to(DEVICE)
optimizer = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE, weight_decay=0.01)

num_training_steps = NUM_EPOCHS * len(train_loader)
lr_scheduler = get_scheduler(
    "cosine",
    optimizer=optimizer,
    num_warmup_steps=int(0.1 * num_training_steps),
    num_training_steps=num_training_steps,
)

# 训练
best_accuracy = 0
for epoch in range(NUM_EPOCHS):
    # 训练阶段
    model.train()
    total_loss = 0
    for batch in train_loader:
        input_ids = batch["input_ids"].to(DEVICE)
        attention_mask = batch["attention_mask"].to(DEVICE)
        labels = batch["label"].to(DEVICE)

        outputs = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)
        loss = outputs.loss

        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()

        total_loss += loss.item()

    avg_train_loss = total_loss / len(train_loader)

    # 评估阶段
    eval_results = evaluate_model(model, eval_loader, DEVICE)

    print(f"Epoch {epoch+1}/{NUM_EPOCHS}")
    print(f"  训练损失: {avg_train_loss:.4f}")
    print(f"  验证损失: {eval_results['loss']:.4f}")
    print(f"  验证准确率: {eval_results['accuracy']:.4f}")

    # 保存最佳模型
    if eval_results["accuracy"] > best_accuracy:
        best_accuracy = eval_results["accuracy"]
        model.save_pretrained("./best_model")
        tokenizer.save_pretrained("./best_model")
        print(f"  保存最佳模型 (准确率: {best_accuracy:.4f})")

print(f"训练完成!最佳验证准确率: {best_accuracy:.4f}")

8.8 使用 Accelerate 简化分布式训练

手动编写分布式训练代码比较复杂,Accelerate 库可以帮助你简化这个过程。

from accelerate import Accelerator
from transformers import AutoModelForSequenceClassification, AutoTokenizer, get_scheduler
from torch.utils.data import DataLoader
import torch

# 初始化 Accelerator
accelerator = Accelerator(mixed_precision="fp16")  # 启用混合精度

# 准备组件
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)

# 使用 Accelerate 准备所有组件
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)

# 训练循环(代码与单 GPU 基本相同)
model.train()
for epoch in range(3):
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)  # 使用 accelerate 的反向传播
        optimizer.step()
        optimizer.zero_grad()

# 保存模型(Accelerate 会自动处理分布式保存)
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained("./model", save_function=accelerator.save)

第九章:PEFT 高效微调

9.1 什么是 PEFT

PEFT(Parameter-Efficient Fine-Tuning,参数高效微调)是一种新兴的模型微调范式。传统的全参数微调需要更新模型的所有参数,这对于大模型来说需要大量的计算资源和存储空间。PEFT 技术通过只训练模型的一小部分参数(通常不到总参数量的 1%),就能达到接近全参数微调的效果。

PEFT 的核心思想是:在预训练模型的基础上,引入少量的可训练参数,冻结原始模型的大部分参数,只训练这些新增的参数。这样做的好处是:大大减少了训练所需的计算资源和内存;降低了存储成本(只需要保存少量的可训练参数);减少了过拟合的风险;使得在消费级 GPU 上微调大模型成为可能。

9.2 常见的 PEFT 方法

LoRA(Low-Rank Adaptation)

LoRA 是目前最流行的 PEFT 方法之一。它通过在模型的注意力层中添加低秩矩阵来实现高效微调。具体来说,LoRA 将原始的权重更新矩阵分解为两个低秩矩阵的乘积,从而大大减少了可训练参数的数量。

LoRA 的关键超参数包括:

  • r:低秩矩阵的秩,通常设置为 8、16、32 或 64
  • lora_alpha:缩放因子,通常设置为秩的两倍
  • target_modules:需要添加 LoRA 的目标模块
  • lora_dropout:LoRA 层的 Dropout 比率

Prefix Tuning

Prefix Tuning 在模型的每一层注意力机制的 Key 和 Value 前添加可学习的前缀向量。这些前缀向量作为模型的"软提示",引导模型产生特定的输出。

P-Tuning v2

P-Tuning v2 在模型的每一层都添加可学习的提示嵌入,而不仅仅是在输入层。这使得它能够更有效地影响模型的深层表示。

IA3(Infused Adapter by Inhibiting and Amplifying Inner Activations)

IA3 通过学习缩放向量来调整模型的激活值,是一种参数量更少的 PEFT 方法。

9.3 使用 LoRA 微调模型

以下是使用 PEFT 库的 LoRA 方法微调模型的完整示例。

from transformers import AutoModelForSequenceClassification, AutoTokenizer
from peft import get_peft_model, LoraConfig, TaskType

# 加载基础模型
model = AutoModelForSequenceClassification.from_pretrained(
    "bert-base-uncased",
    num_labels=2
)

# 配置 LoRA
peft_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,   # 任务类型:序列分类
    r=16,                          # 低秩矩阵的秩
    lora_alpha=32,                 # 缩放因子
    lora_dropout=0.1,              # Dropout 比率
    target_modules=["query", "value"],  # 目标模块
)

# 应用 LoRA
model = get_peft_model(model, peft_config)

# 查看可训练参数
model.print_trainable_parameters()
# 输出示例: trainable params: 667,906 || all params: 109,483,778 || trainable%: 0.61

# 使用方式与普通模型相同
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

9.4 PEFT 模型的训练

PEFT 模型的训练方式与普通模型完全相同,你可以使用 Trainer API 或手动训练循环。

from transformers import TrainingArguments, Trainer
from datasets import load_dataset

# 准备数据
dataset = load_dataset("imdb", split="train[:2000]")
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

def tokenize_fn(examples):
    return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=128)

tokenized = dataset.map(tokenize_fn, batched=True)
split = tokenized.train_test_split(test_size=0.2, seed=42)

# 配置训练参数
training_args = TrainingArguments(
    output_dir="./peft_results",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    warmup_steps=100,
    weight_decay=0.01,
    logging_steps=50,
    eval_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    fp16=True,
)

# 创建 Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=split["train"],
    eval_dataset=split["test"],
    tokenizer=tokenizer,
)

# 训练
trainer.train()

9.5 保存和加载 PEFT 模型

PEFT 模型的保存和加载与普通模型有所不同。你只需要保存/加载 LoRA 权重,而不需要保存整个模型。

from peft import PeftModel, PeftConfig

# 保存 PEFT 模型(只保存 LoRA 权重)
model.save_pretrained("./peft_lora_weights")

# 加载 PEFT 配置
config = PeftConfig.from_pretrained("./peft_lora_weights")

# 加载基础模型
base_model = AutoModelForSequenceClassification.from_pretrained(
    config.base_model_name_or_path,
    num_labels=2
)

# 加载 PEFT 权重
model = PeftModel.from_pretrained(base_model, "./peft_lora_weights")

# 合并权重到基础模型(可选,用于推理加速)
merged_model = model.merge_and_unload()

9.6 使用不同的 PEFT 方法

Prefix Tuning

from peft import PrefixTuningConfig, get_peft_model, TaskType

config = PrefixTuningConfig(
    task_type=TaskType.SEQ_CLS,
    num_virtual_tokens=20,  # 虚拟 token 数量
    prefix_projection=True,  # 是否使用投影层
)

model = get_peft_model(base_model, config)
model.print_trainable_parameters()

P-Tuning v2

from peft import PromptEncoderConfig, get_peft_model, TaskType

config = PromptEncoderConfig(
    task_type=TaskType.SEQ_CLS,
    num_virtual_tokens=20,
    encoder_hidden_size=128,
)

model = get_peft_model(base_model, config)

IA3

from peft import IA3Config, get_peft_model, TaskType

config = IA3Config(
    task_type=TaskType.SEQ_CLS,
    target_modules=["key", "value", "output"],
    feedforward_modules=["output"],
)

model = get_peft_model(base_model, config)
model.print_trainable_parameters()

9.7 PEFT 用于大语言模型微调

PEFT 技术在大语言模型微调中尤为重要,因为它使得在消费级 GPU 上微调数十亿参数的模型成为可能。

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
import torch

# 4-bit 量化加载模型
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.float16,
    bnb_4bit_use_double_quant=True,
)

model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2-0.5B-Instruct",
    quantization_config=bnb_config,
    device_map="auto",
)

# 准备模型用于量化训练
model = prepare_model_for_kbit_training(model)

# 配置 LoRA
lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

9.8 PEFT 最佳实践

  1. 选择合适的秩(r):秩越大,模型容量越大,但训练成本也越高。通常从 8 或 16 开始,根据效果调整。
  2. 选择合适的目标模块:不同的模型架构有不同的模块名称。对于 Transformer 模型,通常选择注意力层的 query 和 value。
  3. 调整 alpha 和 dropoutlora_alpha 通常设置为秩的两倍,lora_dropout 在 0.05 到 0.1 之间。
  4. 使用量化+LoRA:对于特别大的模型,可以结合 4-bit 量化和 LoRA 以进一步减少资源需求。
  5. 监控训练过程:PEFT 模型的训练通常比全参数微调更快,但仍需要监控损失和指标以确保训练正常进行。

第十章:模型推理优化

10.1 推理优化概述

模型推理是将训练好的模型应用于实际数据的过程。在生产环境中,推理性能直接影响用户体验和系统成本。推理优化的目标是在保持模型精度的前提下,提高推理速度、降低延迟和减少资源消耗。

推理优化涉及多个层面:模型层面的优化(如量化、剪枝、蒸馏)、系统层面的优化(如批处理、缓存、并行计算)、以及框架层面的优化(如编译优化、算子融合)。本章将介绍 Transformers 生态中最常用的推理优化技术。

10.2 模型量化

量化是将模型权重从高精度浮点数(如 FP32)转换为低精度表示(如 INT8、INT4)的技术。量化可以显著减少模型大小和推理时的内存占用,同时通常只带来微小的精度损失。

使用 BitsAndBytes 进行量化

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

# 8-bit 量化配置
quantization_config_8bit = BitsAndBytesConfig(
    load_in_8bit=True,
)

# 4-bit 量化配置
quantization_config_4bit = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",           # NF4 量化类型
    bnb_4bit_compute_dtype=torch.float16, # 计算精度
    bnb_4bit_use_double_quant=True,       # 双重量化
)

# 加载量化模型
model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2-0.5B-Instruct",
    quantization_config=quantization_config_4bit,
    device_map="auto",
)

tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-0.5B-Instruct")

# 使用量化模型推理
inputs = tokenizer("人工智能的未来是", return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

10.3 模型编译优化

PyTorch 2.0 引入了 torch.compile 功能,可以通过编译优化显著提升推理速度。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2-0.5B-Instruct",
    torch_dtype=torch.float16,
    device_map="auto",
)

# 使用 torch.compile 优化模型
compiled_model = torch.compile(model, mode="reduce-overhead")

# 使用优化后的模型推理
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-0.5B-Instruct")
inputs = tokenizer("深度学习", return_tensors="pt").to(model.device)

# 第一次推理会比较慢(编译过程),后续推理会更快
with torch.no_grad():
    outputs = compiled_model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

10.4 使用 BetterTransformer

BetterTransformer 是 PyTorch 的一个后端优化,可以自动优化 Transformer 模型的注意力计算。

from transformers import AutoModel
import torch

model = AutoModel.from_pretrained("bert-base-chinese")

# 转换为 BetterTransformer
model = model.to_bettertransformer()

# 使用优化后的模型进行推理
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
inputs = tokenizer("测试文本", return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)

# 恢复原始模型
model = model.reverse_bettertransformer()

10.5 Flash Attention

Flash Attention 是一种高效的注意力实现,可以显著减少内存使用并提高计算速度。它通过优化 GPU 内存访问模式来实现加速。

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# 使用 Flash Attention 2
model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2-0.5B-Instruct",
    torch_dtype=torch.float16,
    attn_implementation="flash_attention_2",  # 启用 Flash Attention 2
    device_map="auto",
)

tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-0.5B-Instruct")
inputs = tokenizer("Flash Attention 可以", return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

10.6 KV Cache 优化

对于自回归生成任务,KV Cache 是一个重要的优化技术。它缓存已计算的 Key 和 Value 张量,避免在每一步生成时重复计算。

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2-0.5B-Instruct",
    torch_dtype=torch.float16,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-0.5B-Instruct")

# 使用 KV Cache 进行生成(默认已启用)
inputs = tokenizer("机器学习", return_tensors="pt").to(model.device)

# 生成时会自动使用 KV Cache
outputs = model.generate(
    **inputs,
    max_new_tokens=200,
    do_sample=True,
    temperature=0.7,
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

10.7 批量推理优化

批量推理可以充分利用 GPU 的并行计算能力,提高吞吐量。

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import time

model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2-0.5B-Instruct",
    torch_dtype=torch.float16,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-0.5B-Instruct")

# 设置 padding
tokenizer.pad_token = tokenizer.eos_token

# 批量输入
prompts = [
    "人工智能的未来是",
    "深度学习的核心是",
    "自然语言处理的目标是",
    "计算机视觉的应用包括",
]

# 批量编码
inputs = tokenizer(prompts, return_tensors="pt", padding=True).to(model.device)

# 批量生成
start_time = time.time()
with torch.no_grad():
    outputs = model.generate(
        **inputs,
        max_new_tokens=100,
        do_sample=True,
        temperature=0.7,
    )
elapsed = time.time() - start_time

# 批量解码
results = tokenizer.batch_decode(outputs, skip_special_tokens=True)
for prompt, result in zip(prompts, results):
    print(f"输入: {prompt}")
    print(f"输出: {result}")
    print()

print(f"批量生成耗时: {elapsed:.2f}秒")

10.8 使用 vLLM 进行高性能推理

vLLM 是一个专门为 LLM 推理优化的库,提供了 PagedAttention 等优化技术。

# vLLM 使用示例(需要单独安装: pip install vllm)
# from vllm import LLM, SamplingParams

# llm = LLM(model="Qwen/Qwen2-0.5B-Instruct")

# sampling_params = SamplingParams(
#     temperature=0.7,
#     top_p=0.9,
#     max_tokens=200,
# )

# prompts = ["人工智能的未来是", "深度学习的核心是"]
# outputs = llm.generate(prompts, sampling_params)

# for output in outputs:
#     print(f"输入: {output.prompt}")
#     print(f"输出: {output.outputs[0].text}")

10.9 使用 ONNX Runtime 优化推理

ONNX Runtime 是一个跨平台的推理引擎,可以优化 Transformer 模型的推理性能。

from transformers import AutoModelForSequenceClassification, AutoTokenizer
from optimum.onnxruntime import ORTModelForSequenceClassification
import torch

# 加载并导出为 ONNX 格式
model_id = "bert-base-chinese"
ort_model = ORTModelForSequenceClassification.from_pretrained(
    model_id,
    export=True,  # 自动导出为 ONNX
)

tokenizer = AutoTokenizer.from_pretrained(model_id)

# 使用 ONNX 模型推理
inputs = tokenizer("测试文本", return_tensors="pt")
outputs = ort_model(**inputs)
print(f"分类 logits: {outputs.logits}")

10.10 推理优化最佳实践

  1. 选择合适的量化方案:8-bit 量化通常精度损失较小,4-bit 量化可以进一步减少资源需求。
  2. 使用 Flash Attention:如果硬件支持,Flash Attention 可以显著提升性能。
  3. 批量推理:在处理多条数据时,使用批量推理可以提高吞吐量。
  4. 利用 KV Cache:对于自回归生成任务,确保 KV Cache 已启用。
  5. 监控内存使用:使用 torch.cuda.memory_summary() 监控 GPU 内存使用情况。
  6. 根据场景选择优化方案:低延迟场景和高吞吐场景可能需要不同的优化策略。

第十一章:Transformers Agent 与工具调用

11.1 什么是 Transformers Agent

Transformers Agent 是 Hugging Face 提供的一个实验性功能,它允许大语言模型(LLM)作为智能代理,根据用户的自然语言指令调用各种工具来完成任务。Agent 可以理解用户意图,选择合适的工具,组合多个工具调用,最终完成复杂的任务。

Agent 的工作流程是:接收用户的自然语言指令,分析指令并确定需要使用的工具,生成工具调用代码,执行工具调用,将结果返回给用户。这种模式使得用户无需编写代码就能完成各种复杂的任务,如图像处理、文本分析、数据转换等。

11.2 使用内置工具

Transformers Agent 提供了多种内置工具,涵盖文本处理、图像处理、音频处理等领域。

from transformers import HfAgent

# 创建 Agent
agent = HfAgent("https://api-inference.huggingface.co/models/bigcode/starcoder")

# 使用 Agent 执行任务
result = agent.run("请帮我生成一张日落海滩的图片")
print(result)

# 文本处理任务
result = agent.run("请总结以下文本:人工智能是计算机科学的一个分支...")
print(result)

11.3 自定义工具

你可以创建自定义工具来扩展 Agent 的能力。

from transformers import Tool

class WeatherTool(Tool):
    """获取天气信息的自定义工具"""

    name = "weather_checker"
    description = "获取指定城市的当前天气信息"
    inputs = {
        "city": {
            "type": "string",
            "description": "城市名称",
        }
    }
    output_type = "string"

    def __call__(self, city: str) -> str:
        # 这里是工具的实际实现
        # 在实际应用中,这里会调用天气 API
        weather_data = {
            "北京": "晴天,25°C",
            "上海": "多云,22°C",
            "广州": "阵雨,28°C",
        }
        return weather_data.get(city, f"未找到 {city} 的天气信息")

# 注册自定义工具
agent = HfAgent("https://api-inference.huggingface.co/models/bigcode/starcoder")
agent.toolbox["weather_checker"] = WeatherTool()

# 使用自定义工具
result = agent.run("请查看北京今天的天气")
print(result)

11.4 工具组合

Agent 可以组合多个工具来完成复杂任务。

from transformers import HfAgent

agent = HfAgent("https://api-inference.huggingface.co/models/bigcode/starcoder")

# Agent 会自动组合工具完成复杂任务
result = agent.run(
    "请搜索关于量子计算的最新新闻,然后总结成一段简短的摘要,"
    "最后将摘要翻译成英文。"
)
print(result)

11.5 使用本地模型作为 Agent

你也可以使用本地模型作为 Agent 的推理引擎,避免依赖外部 API。

from transformers import HfAgent, AutoModelForCausalLM, AutoTokenizer

# 加载本地模型
model_id = "Qwen/Qwen2-0.5B-Instruct"
model = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_id)

# 使用本地模型创建 Agent
# agent = HfAgent(model=model, tokenizer=tokenizer)

11.6 Agent 的实际应用场景

数据处理 Agent

# 使用 Agent 进行数据处理
result = agent.run(
    "读取 sales_data.csv 文件,计算每个月的总销售额,"
    "然后生成一个柱状图显示月度销售趋势。"
)

文档处理 Agent

# 使用 Agent 处理文档
result = agent.run(
    "读取这份PDF文档,提取其中的所有表格数据,"
    "将它们转换为JSON格式并保存。"
)

多模态任务 Agent

# 使用 Agent 进行多模态任务
result = agent.run(
    "分析这张图片中的内容,生成一段描述性文字,"
    "然后用这段文字生成一段配图的社交媒体帖子。"
)

11.7 Agent 的安全考虑

使用 Agent 时需要注意以下安全问题:

  1. 代码执行风险:Agent 可能会生成并执行代码,需要确保执行环境的安全性。
  2. 数据隐私:Agent 可能会访问敏感数据,需要控制其访问权限。
  3. 工具权限:限制 Agent 可以使用的工具范围,避免执行危险操作。
  4. 输入验证:对 Agent 的输入进行验证,防止注入攻击。

11.8 Agent 最佳实践

  1. 明确指令:给 Agent 提供清晰、具体的指令,减少歧义。
  2. 分步执行:对于复杂任务,将其分解为多个简单步骤。
  3. 验证结果:检查 Agent 的输出是否符合预期。
  4. 限制工具范围:只提供必要的工具,减少潜在风险。
  5. 监控执行过程:记录 Agent 的工具调用历史,便于调试和审计。

第十二章:综合实战项目

12.1 项目概述

本章将通过一个完整的实战项目来综合运用前面章节学到的知识。项目目标是构建一个中文情感分析系统,包括数据准备、模型训练、评估和部署的完整流程。这个项目涵盖了 Transformers 库的核心功能,是一个很好的学习实践案例。

12.2 项目需求分析

我们的中文情感分析系统需要实现以下功能:

  1. 对中文文本进行情感分类(正面/负面/中性)
  2. 支持批量处理
  3. 提供 API 接口供外部调用
  4. 具有良好的准确率和响应速度

12.3 数据准备

from datasets import Dataset, DatasetDict
from transformers import AutoTokenizer
import random

# 创建模拟数据集(实际项目中应使用真实数据)
def generate_sample_data():
    """生成模拟的中文情感分析数据"""
    positive_texts = [
        "这个产品真的很好用,强烈推荐!",
        "服务态度非常好,下次还会再来。",
        "质量超出了我的预期,非常满意。",
        "包装精美,物流也很快,好评!",
        "性价比很高,值得购买。",
        "体验非常好,五星好评。",
        "功能齐全,使用方便,推荐购买。",
        "客服态度很好,耐心解答了我所有的问题。",
    ]

    negative_texts = [
        "质量太差了,用了一天就坏了。",
        "服务态度很差,不推荐。",
        "与描述严重不符,要求退货。",
        "物流太慢了,等了一个星期。",
        "价格太贵了,性价比很低。",
        "包装破损,产品也有瑕疵。",
        "使用体验很差,不建议购买。",
        "售后处理很慢,很不满意。",
    ]

    neutral_texts = [
        "一般般吧,没什么特别的。",
        "还可以,基本符合预期。",
        "中规中矩,没有惊喜也没有失望。",
        "普通的产品,普通的体验。",
        "用着还行,但可以更好。",
        "差不多就是这个价位该有的水平。",
        "不功不过,凑合用吧。",
        "一般,没有特别的感觉。",
    ]

    data = []
    for text in positive_texts:
        data.append({"text": text, "label": 0})  # 0: 正面
    for text in negative_texts:
        data.append({"text": text, "label": 1})  # 1: 负面
    for text in neutral_texts:
        data.append({"text": text, "label": 2})  # 2: 中性

    random.shuffle(data)
    return data

# 生成数据
data = generate_sample_data()

# 创建数据集
dataset = Dataset.from_list(data)
dataset_dict = DatasetDict({
    "train": dataset.select(range(0, int(len(dataset) * 0.8))),
    "test": dataset.select(range(int(len(dataset) * 0.8), len(dataset))),
})

print(f"训练集大小: {len(dataset_dict['train'])}")
print(f"测试集大小: {len(dataset_dict['test'])}")

12.4 数据预处理

from transformers import AutoTokenizer

# 加载分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

# 定义预处理函数
def preprocess_function(examples):
    return tokenizer(
        examples["text"],
        truncation=True,
        padding="max_length",
        max_length=128,
    )

# 应用预处理
tokenized_datasets = dataset_dict.map(preprocess_function, batched=True)

# 设置输出格式
tokenized_datasets.set_format(
    type="torch",
    columns=["input_ids", "attention_mask", "label"]
)

print(f"预处理后的特征: {tokenized_datasets['train'].features}")
print(f"样本数据: {tokenized_datasets['train'][0]}")

12.5 模型训练

from transformers import (
    AutoModelForSequenceClassification,
    TrainingArguments,
    Trainer,
)
import evaluate
import numpy as np

# 加载模型
model = AutoModelForSequenceClassification.from_pretrained(
    "bert-base-chinese",
    num_labels=3,  # 3 个类别
)

# 定义评估指标
accuracy_metric = evaluate.load("accuracy")

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)
    return accuracy_metric.compute(predictions=predictions, references=labels)

# 配置训练参数
training_args = TrainingArguments(
    output_dir="./sentiment_model",
    num_train_epochs=5,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=8,
    warmup_steps=50,
    weight_decay=0.01,
    logging_steps=10,
    eval_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    metric_for_best_model="accuracy",
    report_to="none",
    seed=42,
)

# 创建 Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["test"],
    compute_metrics=compute_metrics,
    tokenizer=tokenizer,
)

# 开始训练
print("开始训练...")
trainer.train()

# 评估模型
eval_results = trainer.evaluate()
print(f"评估结果: {eval_results}")

12.6 模型保存与部署

from transformers import pipeline

# 保存模型
trainer.save_model("./final_sentiment_model")
tokenizer.save_pretrained("./final_sentiment_model")

# 创建推理 Pipeline
sentiment_pipeline = pipeline(
    "text-classification",
    model="./final_sentiment_model",
    tokenizer="./final_sentiment_model",
)

# 测试推理
test_texts = [
    "这个产品非常好用,强烈推荐!",
    "质量太差了,很不满意。",
    "一般般,还可以吧。",
]

print("推理测试:")
for text in test_texts:
    result = sentiment_pipeline(text)
    label_map = {0: "正面", 1: "负面", 2: "中性"}
    label = label_map.get(result[0]["label"], result[0]["label"])
    print(f"  文本: {text}")
    print(f"  情感: {label} (置信度: {result[0]['score']:.4f})")
    print()

12.7 使用 FastAPI 构建 API 服务

# api_server.py
from fastapi import FastAPI
from pydantic import BaseModel
from transformers import pipeline
from typing import List

app = FastAPI(title="中文情感分析 API")

# 加载模型
sentiment_pipeline = pipeline(
    "text-classification",
    model="./final_sentiment_model",
    tokenizer="./final_sentiment_model",
)

class TextInput(BaseModel):
    text: str

class BatchInput(BaseModel):
    texts: List[str]

class SentimentResult(BaseModel):
    text: str
    label: str
    score: float

LABEL_MAP = {0: "正面", 1: "负面", 2: "中性"}

@app.post("/analyze", response_model=SentimentResult)
async def analyze_sentiment(input_data: TextInput):
    """分析单条文本的情感"""
    result = sentiment_pipeline(input_data.text)[0]
    label_idx = int(result["label"].split("_")[-1])
    return SentimentResult(
        text=input_data.text,
        label=LABEL_MAP.get(label_idx, result["label"]),
        score=result["score"],
    )

@app.post("/analyze_batch", response_model=List[SentimentResult])
async def analyze_batch(input_data: BatchInput):
    """批量分析文本情感"""
    results = sentiment_pipeline(input_data.texts)
    output = []
    for text, result in zip(input_data.texts, results):
        label_idx = int(result["label"].split("_")[-1])
        output.append(SentimentResult(
            text=text,
            label=LABEL_MAP.get(label_idx, result["label"]),
            score=result["score"],
        ))
    return output

@app.get("/health")
async def health_check():
    """健康检查"""
    return {"status": "healthy"}

12.8 使用 Gradio 构建演示界面

# app.py
import gradio as gr
from transformers import pipeline

# 加载模型
sentiment_pipeline = pipeline(
    "text-classification",
    model="./final_sentiment_model",
    tokenizer="./final_sentiment_model",
)

LABEL_MAP = {0: "正面", 1: "负面", 2: "中性"}

def analyze(text):
    """分析文本情感"""
    if not text.strip():
        return "请输入文本"
    result = sentiment_pipeline(text)[0]
    label_idx = int(result["label"].split("_")[-1])
    label = LABEL_MAP.get(label_idx, result["label"])
    return f"情感: {label} | 置信度: {result['score']:.4f}"

# 创建 Gradio 界面
demo = gr.Interface(
    fn=analyze,
    inputs=gr.Textbox(lines=5, placeholder="请输入要分析的中文文本..."),
    outputs="text",
    title="中文情感分析系统",
    description="输入中文文本,系统将自动分析其情感倾向(正面/负面/中性)。",
    examples=[
        ["这个产品真的很好用,强烈推荐!"],
        ["质量太差了,很不满意。"],
        ["一般般,还可以吧。"],
    ],
)

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)

12.9 项目优化建议

  1. 使用更大的训练数据:当前使用的是模拟数据,实际项目应使用大规模的真实标注数据。
  2. 使用更强的预训练模型:可以尝试使用更大的中文预训练模型,如 RoBERTa-wwm-ext-large。
  3. 数据增强:使用同义词替换、回译等技术扩充训练数据。
  4. 模型集成:使用多个模型进行集成预测,提高准确率。
  5. 持续学习:建立反馈机制,持续收集用户反馈并更新模型。

第十三章:常见问题与调试技巧

13.1 安装与环境问题

问题一:安装后 import 报错

ImportError: cannot import name 'xxx' from 'transformers'

解决方案:这通常是因为安装的 Transformers 版本过旧。升级到最新版本:

pip install --upgrade transformers

问题二:CUDA 版本不匹配

RuntimeError: CUDA error: no kernel image is available for execution on the device

解决方案:确保 PyTorch 的 CUDA 版本与你的 GPU 驱动兼容。可以访问 PyTorch 官网查找对应版本的安装命令。

# 检查 CUDA 版本
nvcc --version
nvidia-smi

# 重新安装匹配的 PyTorch
pip install torch --index-url https://download.pytorch.org/whl/cu118

问题三:内存不足(OOM)

RuntimeError: CUDA out of memory

解决方案:

  1. 减小批次大小
  2. 使用梯度累积
  3. 使用混合精度训练(FP16)
  4. 使用模型量化
  5. 使用更大的 GPU 或多 GPU 训练
# 减小批次大小
training_args = TrainingArguments(
    per_device_train_batch_size=4,  # 从 16 减小到 4
    gradient_accumulation_steps=4,  # 使用梯度累积
    fp16=True,                       # 启用混合精度
)

13.2 模型加载问题

问题四:模型下载失败

ConnectionError: Couldn't reach server

解决方案:

  1. 检查网络连接
  2. 使用镜像源
  3. 手动下载模型
# 设置镜像源
export HF_ENDPOINT=https://hf-mirror.com

# 或使用代理
export HTTPS_PROXY=http://your-proxy:port

问题五:模型加载时内存不足

解决方案:使用量化加载或设备映射。

from transformers import AutoModel
import torch

# 使用半精度加载
model = AutoModel.from_pretrained(
    "model-name",
    torch_dtype=torch.float16,
    device_map="auto",
)

13.3 训练问题

问题六:训练损失不下降

可能原因和解决方案:

  1. 学习率过大或过小:尝试调整学习率,通常从 1e-5 到 5e-5 之间选择。
  2. 数据问题:检查数据是否正确加载和预处理。
  3. 标签错误:确认标签编码是否正确。
  4. 梯度消失/爆炸:使用梯度裁剪。
# 使用梯度裁剪
training_args = TrainingArguments(
    max_grad_norm=1.0,  # 梯度裁剪
    learning_rate=2e-5,
)

问题七:过拟合

解决方案:

  1. 增加训练数据
  2. 使用数据增强
  3. 增加 Dropout
  4. 使用权重衰减
  5. 使用早停策略
training_args = TrainingArguments(
    weight_decay=0.01,
    # 使用早停
)

问题八:评估指标异常

# 检查标签分布
from collections import Counter
label_counts = Counter(dataset["label"])
print(f"标签分布: {label_counts}")

# 检查预测分布
import numpy as np
pred_counts = Counter(np.argmax(predictions, axis=-1))
print(f"预测分布: {pred_counts}")

13.4 推理问题

问题九:推理速度慢

解决方案:

  1. 使用 GPU 加速
  2. 使用量化模型
  3. 使用 Flash Attention
  4. 使用批量推理
  5. 使用模型编译优化
# 使用 GPU
model = model.to("cuda")

# 使用量化
from transformers import BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(load_in_8bit=True)
model = AutoModelForCausalLM.from_pretrained(
    "model-name",
    quantization_config=quantization_config,
)

问题十:生成质量差

解决方案:

  1. 调整生成参数
  2. 使用更好的提示词
  3. 使用更大的模型
outputs = model.generate(
    **inputs,
    max_new_tokens=200,
    do_sample=True,
    temperature=0.7,      # 降低温度减少随机性
    top_p=0.9,
    repetition_penalty=1.1,  # 减少重复
)

13.5 数据问题

问题十一:数据加载慢

解决方案:

  1. 使用预处理缓存
  2. 使用多进程加载
  3. 使用内存映射
# 使用预处理缓存
tokenized_dataset = dataset.map(
    tokenize_function,
    batched=True,
    num_proc=4,
    cache_file_name="./cache/tokenized.arrow",
)

问题十二:数据不平衡

# 使用加权采样
from torch.utils.data import WeightedRandomSampler

# 计算类别权重
label_counts = Counter(dataset["label"])
total = sum(label_counts.values())
weights = {label: total / count for label, count in label_counts.items()}
sample_weights = [weights[label] for label in dataset["label"]]

sampler = WeightedRandomSampler(sample_weights, len(sample_weights))
dataloader = DataLoader(dataset, sampler=sampler, batch_size=16)

13.6 分布式训练问题

问题十三:多 GPU 训练不加速

解决方案:

  1. 确认数据加载不是瓶颈
  2. 调整批次大小
  3. 使用 Accelerate 简化分布式配置
# 使用 Accelerate 启动分布式训练
accelerate launch train.py

问题十四:梯度同步问题

# 使用 Accelerate 处理梯度同步
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader)

for batch in dataloader:
    outputs = model(**batch)
    loss = outputs.loss
    accelerator.backward(loss)
    optimizer.step()
    optimizer.zero_grad()

13.7 调试技巧

技巧一:使用小规模数据快速调试

# 使用小规模数据测试代码
small_dataset = dataset.select(range(100))

技巧二:打印中间结果

# 打印模型输入输出
print(f"输入形状: {inputs['input_ids'].shape}")
print(f"输出形状: {outputs.logits.shape}")
print(f"损失值: {outputs.loss.item()}")

技巧三:使用断点调试

# 在关键位置设置断点
import pdb; pdb.set_trace()

技巧四:监控训练过程

# 使用 TensorBoard
training_args = TrainingArguments(
    report_to="tensorboard",
    logging_dir="./logs",
    logging_steps=10,
)
# 启动 TensorBoard
tensorboard --logdir=./logs

技巧五:检查梯度

# 检查梯度是否正常
for name, param in model.named_parameters():
    if param.grad is not None:
        print(f"{name}: grad_mean={param.grad.mean():.6f}, grad_std={param.grad.std():.6f}")

技巧六:检查模型参数

# 统计模型参数
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"总参数: {total_params:,}")
print(f"可训练参数: {trainable_params:,}")
print(f"可训练比例: {100 * trainable_params / total_params:.2f}%")

13.8 常见错误代码速查表

错误类型 常见原因 解决方案
CUDA OOM GPU 内存不足 减小批次大小,使用量化,使用梯度累积
Shape Mismatch 输入输出维度不匹配 检查模型配置和数据预处理
NaN Loss 学习率过大或数据问题 降低学习率,检查数据
Connection Error 网络问题 使用镜像源,检查代理设置
Import Error 版本不兼容 升级或降级相关库
Key Error 数据格式错误 检查数据集特征名

13.9 性能优化检查清单

在遇到性能问题时,可以按照以下清单逐一检查:

  1. 确认是否使用了 GPU 加速
  2. 检查批次大小是否合理
  3. 确认数据加载不是瓶颈
  4. 检查是否启用了混合精度训练
  5. 确认模型是否使用了优化的注意力实现
  6. 检查是否有不必要的梯度计算
  7. 确认数据预处理是否在 GPU 上完成
  8. 检查是否有内存泄漏

附录

A. 常用命令速查

# 安装核心库
pip install transformers datasets accelerate peft

# 查看 GPU 状态
nvidia-smi

# 启动 TensorBoard
tensorboard --logdir=./logs

# 使用 Accelerate 启动训练
accelerate launch train.py

# 清理缓存
huggingface-cli delete-cache

B. 常用模型推荐

任务 推荐模型
中文文本分类 bert-base-chinese, RoBERTa-wwm-ext
中文文本生成 Qwen2, ChatGLM, Baichuan2
英文文本分类 bert-base-uncased, roberta-base
英文文本生成 GPT-2, Llama, Mistral
图像分类 vit-base-patch16-224, swin-base
文本到文本 t5-base, mT5

C. 学习资源

  1. Hugging Face 官方文档:https://huggingface.co/docs/transformers
  2. Hugging Face 课程:https://huggingface.co/learn
  3. GitHub 仓库:https://github.com/huggingface/transformers
  4. Hugging Face 论坛:https://discuss.huggingface.co
  5. Hugging Face Hub:https://huggingface.co/models

本教程到此结束。希望通过本教程的学习,你已经掌握了 Hugging Face Transformers 库的核心功能和使用方法。记住,最好的学习方式是动手实践。尝试将所学知识应用到你自己的项目中,遇到问题时查阅官方文档和社区资源。祝你在 AI 开发的道路上取得成功!

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录