前面的文章已经从零实现了 Transformer、Mini-GPT 和 RoPE。理解这些底层原理之后,一个很自然的问题是:

实际项目中,难道每次都要从头实现网络、训练 Tokenizer,再用海量数据预训练模型吗?

通常不需要。

很多任务可以从一个已经预训练好的模型出发,只添加或替换任务头,再使用自己的数据进行微调。Hugging Face 正是把“寻找模型、下载权重、统一调用、处理数据、训练评估和分享结果”串起来的一套生态。

不过,初学者很容易把下面这些概念混在一起:

  • Hugging Face 网站。
  • Hugging Face Hub。
  • transformers Python 库。
  • datasets Python 库。
  • pipeline()
  • AutoTokenizerAutoModel
  • Trainer
  • Spaces 与在线推理服务。

本文会先把它们拆开,再通过一个中文情感分类项目完整走通:

选择预训练模型
  -> 加载 Tokenizer
  -> 文本转成模型输入
  -> 加载任务模型
  -> 手动完成一次前向推理
  -> 构造 Dataset
  -> 批量 Tokenize
  -> 动态 Padding
  -> Trainer 微调与评估
  -> 保存到本地
  -> 重新加载并推理
  -> 可选上传到 Hub

Hugging Face生态总览

1. Hugging Face 到底是什么

Hugging Face 不是单独一个库,也不只是一个模型下载网站。可以从三个层面理解它。

1.1 社区与平台

Hugging Face Hub 是一个围绕机器学习资产构建的协作平台。常见资产包括:

  • Models:模型配置、Tokenizer、权重和 Model Card。
  • Datasets:训练、验证与测试数据,以及 Dataset Card。
  • Spaces:可以在线运行的机器学习演示或应用。

可以把 Hub 粗略理解为“面向机器学习项目的代码与资产仓库平台”,但模型仓库不只有代码,还包含体积很大的参数文件、任务元数据、许可证和在线推理信息。

1.2 Python 库生态

主要作用
transformers 加载和使用 Transformer 模型、Tokenizer、Trainer
datasets 加载、处理、缓存和流式读取数据集
huggingface_hub 搜索、下载、上传和管理 Hub 仓库
tokenizers 高性能 Tokenizer 实现
accelerate 设备管理、混合精度和分布式训练
evaluate 加载和计算评估指标
safetensors 安全、快速地保存和加载张量
peft LoRA 等参数高效微调方法

本文的主线使用前五项中的核心能力,并把 evaluate 作为可选工具介绍。

1.3 一组统一约定

Hugging Face 很重要的一点不是“收集了很多模型”,而是让大量不同架构能够使用相似接口:

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(model_id)

模型可以从 BERT 换成 RoBERTa,代码骨架仍然相似。差异主要由仓库中的 config.json、Tokenizer 配置与权重文件描述。

2. Hub、Transformers 和 PyTorch 的关系

这三个概念处在不同层次:

Hugging Face Hub
  保存与分发配置、Tokenizer、权重、说明文档

Transformers
  根据配置构造模型类并下载权重,提供统一高级 API

PyTorch
  执行张量运算、自动微分和参数更新

当我们执行:

model = AutoModelForSequenceClassification.from_pretrained(model_id)

背后大致发生:

  1. 根据 model_id 定位 Hub 仓库或本地目录。
  2. 读取 config.json
  3. 从配置识别模型架构。
  4. 实例化对应的 PyTorch nn.Module
  5. 下载并加载模型权重。
  6. 返回可以正常前向传播的模型对象。

因此,Transformers 没有替代 PyTorch。它是在 PyTorch 之上提供模型实现、配置系统、预训练权重和训练工具。

3. 模型仓库里有什么

一个典型的 Transformers 模型仓库可能包含:

README.md
config.json
model.safetensors
tokenizer.json
tokenizer_config.json
special_tokens_map.json
vocab.txt / vocab.json / merges.txt
generation_config.json

不同模型的文件并不完全相同。

Hugging Face模型仓库结构

3.1 README.md

在模型仓库中,它通常会渲染成 Model Card,应说明:

  • 模型适合做什么。
  • 不适合做什么。
  • 训练数据与训练方法。
  • 评估结果。
  • 已知偏差与限制。
  • 许可证。
  • 使用示例。

下载数高不代表模型适合你的任务。选择模型时,Model Card 应该是第一阅读入口。

3.2 config.json

它描述模型结构和任务相关配置,例如:

{
  "model_type": "bert",
  "hidden_size": 768,
  "num_hidden_layers": 12,
  "num_attention_heads": 12,
  "vocab_size": 21128,
  "num_labels": 2
}

实际字段由模型架构决定。

3.3 权重文件

常见格式包括:

model.safetensors
pytorch_model.bin

大型模型还可能被切成多个 shard,并使用索引文件记录每个参数所在位置。

3.4 Tokenizer 文件

Tokenizer 与模型必须匹配。它们共同决定:

  • 词表。
  • 子词切分规则。
  • 特殊 token。
  • padding 方向。
  • 最大长度等默认行为。

模型权重和 Tokenizer 随意混用,轻则效果异常,重则 token id 超出词表范围。

4. 三个容易混淆的词:架构、Checkpoint 和任务

4.1 架构 Architecture

架构描述模型骨架,例如:

BERT
RoBERTa
GPT-2
T5
ViT

4.2 Checkpoint

Checkpoint 是某个架构在特定训练阶段保存的参数和配置。例如:

google-bert/bert-base-chinese

它是一个具体 Hub 仓库 ID,不等于“所有 BERT”。

4.3 任务 Task

同一 Backbone 可以连接不同任务头:

BERT Backbone
  -> Sequence Classification Head  文本分类
  -> Token Classification Head     命名实体识别
  -> Question Answering Head        抽取式问答
  -> Masked LM Head                 掩码语言模型

这就是为什么 Transformers 提供多种 AutoModelFor... 类。

5. 安装与环境准备

建议为项目创建独立虚拟环境,再安装稳定发布版本:

python -m venv .venv

Windows PowerShell:

.\.venv\Scripts\Activate.ps1

安装 PyTorch 时应根据 CPU 或 CUDA 环境使用 PyTorch 官方给出的命令。安装 Hugging Face 相关库:

pip install transformers datasets accelerate safetensors

如果还要使用官方评估指标:

pip install evaluate

检查版本:

import torch
import transformers
import datasets

print("torch:", torch.__version__)
print("transformers:", transformers.__version__)
print("datasets:", datasets.__version__)
print("cuda available:", torch.cuda.is_available())

5.1 为什么要记录版本

Hugging Face 生态更新较快,不同大版本可能调整参数名、默认值或返回类型。真实项目应记录依赖:

pip freeze > requirements-lock.txt

教程代码无法替代项目自己的版本锁定。遇到 API 报错时,首先核对:

本地安装版本
官方文档所选版本
示例代码对应版本

6. 第一层 API:用 pipeline() 快速推理

pipeline() 是最方便的推理入口。以中文情感分析为例:

from transformers import pipeline

model_id = "uer/roberta-base-finetuned-jd-binary-chinese"

classifier = pipeline(
    task="text-classification",
    model=model_id,
)

results = classifier([
    "这个耳机音质清晰,佩戴也很舒服。",
    "包装破损,使用两天就无法开机。",
])

print(results)

首次运行通常会下载模型,之后优先从本地缓存读取。

6.1 不要猜测 LABEL_0 的含义

有些模型会输出:

LABEL_0
LABEL_1

不能想当然地认为 LABEL_1 一定表示正面。正确做法是查看 Model Card 和模型配置:

print(classifier.model.config.id2label)

模型作者若没有正确填写标签映射,使用者还需要结合训练数据说明确认语义。

6.2 pipeline 不是模型本身

pipeline 是一个推理工作流封装,大致包含三个阶段:

preprocess
  文本 -> Tokenizer -> 模型输入张量

forward
  张量 -> 模型 -> logits

postprocess
  logits -> Softmax -> 标签与分数

Pipeline内部三阶段

它适合:

  • 快速验证模型。
  • 编写原型。
  • 常规批量推理。

需要精确控制张量、损失函数或训练过程时,应继续下潜到 AutoTokenizerAutoModel

7. pipeline 中的任务字符串

常见任务包括:

任务 常见 task
文本分类 text-classification
文本生成 text-generation
命名实体识别 token-classification
抽取式问答 question-answering
掩码填空 fill-mask
摘要 summarization
翻译 translation 或模型对应任务
图像分类 image-classification
自动语音识别 automatic-speech-recognition

模型必须与任务兼容。不能把仅有 BERT Masked LM 头的 Checkpoint 当作自回归文本生成模型,也不能认为任何文本模型都能直接做分类。

7.1 显式指定模型

下面的写法虽然短:

classifier = pipeline("text-classification")

但它会选择一个默认模型。教学实验可以使用,正式项目最好显式指定:

classifier = pipeline(
    "text-classification",
    model="组织名/模型名",
    revision="明确的分支、标签或提交哈希",
)

这样更容易复现,也能避免默认模型改变带来的行为漂移。

8. 第二层 API:AutoTokenizer

Tokenizer 把字符串转换成模型能够接收的整数张量:

from transformers import AutoTokenizer

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

encoded = tokenizer(
    "Hugging Face 让模型复用更方便",
    return_tensors="pt",
)

for name, tensor in encoded.items():
    print(name, tensor.shape)
    print(tensor)

对 BERT 类模型,常见输出包括:

input_ids
attention_mask
token_type_ids  某些模型有,某些没有

8.1 input_ids

Tokenization 的概念流程为:

原始文本
  -> 规范化
  -> 子词切分
  -> 加特殊 token
  -> 查词表得到 token id

可以直接查看:

tokens = tokenizer.tokenize("我正在学习Hugging Face")
ids = tokenizer.convert_tokens_to_ids(tokens)

print(tokens)
print(ids)

也可以把完整编码转换回 token:

print(tokenizer.convert_ids_to_tokens(encoded["input_ids"][0]))

8.2 特殊 token

BERT 类 Tokenizer 常在序列两侧加入类似:

[CLS] 文本 token ... [SEP]

不同架构可能使用不同特殊 token。不要在业务代码里硬编码它们,应使用 Tokenizer 的配置:

print(tokenizer.cls_token, tokenizer.cls_token_id)
print(tokenizer.sep_token, tokenizer.sep_token_id)
print(tokenizer.pad_token, tokenizer.pad_token_id)

8.3 attention_mask

在常见文本模型中:

1 表示真实 token
0 表示 Padding token

模型利用它避免把补齐部分当成有效内容。

Tokenizer输出张量

9. Padding 与 Truncation

一个 batch 中的句子长度往往不同,但张量需要组成规则矩形。

batch = tokenizer(
    [
        "这个产品很好用。",
        "外观不错,但是续航时间没有达到宣传效果。",
    ],
    padding=True,
    truncation=True,
    max_length=64,
    return_tensors="pt",
)

9.1 padding=True

把当前 batch 中较短的样本补到最长样本的长度:

样本 A:8 token
样本 B:15 token

组成 batch 后都变成 15 token

9.2 padding="max_length"

所有样本都补到指定的 max_length。优点是形状固定,缺点是短句很多时会浪费计算。

9.3 truncation=True

当 token 数超过允许长度时截断。必须意识到:

截断不会“压缩理解”被删除的文本,它会直接丢弃超出部分。

如果关键信息出现在长文末尾,需要考虑滑动窗口、分块、长上下文模型或任务专用聚合策略。

10. 动态 Padding 为什么更节省计算

假设整个数据集最长为 512,但某个 batch 中最长只有 83。

固定 Padding:

[B, 512]

动态 Padding:

[B, 83]

Self-Attention 的主要计算与序列长度平方相关:

$$
O(T^2)
$$

因此避免不必要的 Padding 往往能节省明显的显存和计算。

Transformers 提供:

from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

它在组 batch 时才按照当前 batch 的最长序列进行补齐。

11. AutoModelAutoModelFor... 有什么区别

11.1 AutoModel

from transformers import AutoModel

backbone = AutoModel.from_pretrained(model_id)

通常只加载基础 Backbone,返回隐藏状态。例如 BERT 常见输出形状:

last_hidden_state: [B, T, H]

它适合:

  • 提取文本表示。
  • 自己设计任务头。
  • 研究中间隐藏状态。

11.2 AutoModelForSequenceClassification

from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(
    model_id,
    num_labels=2,
)

它在 Backbone 上增加文本分类头,输出:

logits: [B, num_labels]

11.3 常用 AutoModel 类

任务
AutoModel 基础隐藏表示
AutoModelForSequenceClassification 句子或文本分类
AutoModelForTokenClassification 每个 token 分类
AutoModelForQuestionAnswering 抽取答案起止位置
AutoModelForMaskedLM 预测被遮住的 token
AutoModelForCausalLM 自回归生成下一个 token
AutoModelForSeq2SeqLM 翻译、摘要等序列到序列生成

12. 手动完成一次分类推理

现在不使用 pipeline,把内部过程展开。

import torch
from transformers import (
    AutoModelForSequenceClassification,
    AutoTokenizer,
)

model_id = "uer/roberta-base-finetuned-jd-binary-chinese"

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(model_id)

texts = [
    "物流很快,商品质量也很好。",
    "用了几分钟就坏了,非常失望。",
]

inputs = tokenizer(
    texts,
    padding=True,
    truncation=True,
    return_tensors="pt",
)

model.eval()
with torch.inference_mode():
    outputs = model(**inputs)

logits = outputs.logits
probabilities = torch.softmax(logits, dim=-1)
predicted_ids = probabilities.argmax(dim=-1)

for text, class_id, probs in zip(
    texts,
    predicted_ids.tolist(),
    probabilities.tolist(),
):
    label = model.config.id2label[class_id]
    print(text)
    print(label, probs)

12.1 为什么模型输出 logits 而不是概率

分类头通常输出任意实数:

$$
z=[z_1,z_2,\ldots,z_C]
$$

Softmax 才把它们转换为概率:

$$
P(y=c\mid x)
=\frac{e^{z_c}}{\sum_{j=1}^{C}e^{z_j}}
$$

训练时交叉熵会在数值稳定的实现中直接接收 logits,所以不要在传给交叉熵之前手动 Softmax。

12.2 outputs 为什么可以点号访问

Transformers 模型通常返回 ModelOutput 的具体子类,既具有命名字段,又能在一定程度上像元组一样使用:

print(outputs.logits)

若传入 labels,分类模型通常还会返回:

print(outputs.loss)

13. 一次带标签的前向传播

labels = torch.tensor([1, 0])

model.train()
outputs = model(**inputs, labels=labels)

print(outputs.loss)
print(outputs.logits.shape)

内部概念流程为:

文本
  -> Tokenizer
  -> input_ids / attention_mask
  -> Transformer Backbone
  -> 分类头
  -> logits [B, C]
  -> 与 labels 计算 CrossEntropyLoss

只调用这一步不会更新参数。完整训练还需要:

optimizer.zero_grad()
outputs.loss.backward()
optimizer.step()

Trainer 会把这些训练循环工作统一封装起来。

14. Hugging Face Datasets 是什么

datasets 提供统一的数据加载和处理接口:

from datasets import load_dataset

dataset = load_dataset("数据集仓库ID")
print(dataset)

返回值经常是 DatasetDict

DatasetDict({
    train: Dataset(...)
    validation: Dataset(...)
    test: Dataset(...)
})

每个 split 都包含列名、特征类型和行数。

14.1 从本地文件加载

CSV:

dataset = load_dataset(
    "csv",
    data_files={
        "train": "data/train.csv",
        "validation": "data/validation.csv",
    },
)

JSON Lines:

dataset = load_dataset(
    "json",
    data_files={"train": "data/train.jsonl"},
)

纯文本:

dataset = load_dataset(
    "text",
    data_files={"train": "data/train.txt"},
)

14.2 从 Python 数据创建

教学项目可以直接使用:

from datasets import Dataset

train_dataset = Dataset.from_dict({
    "text": ["非常好用", "质量太差"],
    "label": [1, 0],
})

15. 使用 map() 批量 Tokenize

原始数据只有文本和标签:

text:  "这个产品很好用"
label: 1

定义预处理函数:

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

批量应用:

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

处理后会多出模型输入列:

text
label
input_ids
attention_mask
token_type_ids  视模型而定

15.1 batched=True 是什么

不使用批处理时,函数每次接收一条样本:

{"text": "一条文本", "label": 1}

使用 batched=True 后,每次接收一批列数据:

{
    "text": ["文本A", "文本B", ...],
    "label": [1, 0, ...],
}

Fast Tokenizer 能够高效处理一批字符串,通常比 Python 逐条调用更快。

15.2 map() 通常返回新数据集

Datasets 的处理方法通常不会原地修改原对象:

tokenized_dataset = dataset.map(...)

不要忘记接收返回值。

15.3 为什么预处理阶段不固定 Padding

这里有意不写:

padding="max_length"

我们保留不同长度的 input_ids,等到 Data Collator 组成 batch 时再动态 Padding。

16. Data Collator 在哪里工作

数据处理可以分成两层:

Dataset.map
  每条样本:文本 -> 长度不一的 token id

DataCollatorWithPadding
  每个 batch:多条样本 -> 补齐成规则张量

例如三条序列长度分别为:

7, 11, 9

组成 batch 后:

input_ids.shape = [3, 11]
attention_mask.shape = [3, 11]
labels.shape = [3]

Data Collator 只整理 batch,不负责模型前向和参数更新。

17. 用迁移学习完成中文情感分类

下面使用:

Backbone: google-bert/bert-base-chinese
任务: 二分类
标签: NEGATIVE / POSITIVE

教学数据规模很小,只用于确认代码和数据流正确:

train_texts = [
    "物流很快,包装完整,商品也很好用。",
    "音质清晰,佩戴几个小时也很舒服。",
    "屏幕显示细腻,系统运行十分流畅。",
    "客服回复及时,问题很快得到解决。",
    "续航表现不错,一整天使用没有压力。",
    "做工粗糙,刚拆开就发现了划痕。",
    "电池掉电特别快,完全无法正常使用。",
    "商品与描述不符,体验非常糟糕。",
    "包装已经破损,而且缺少配件。",
    "运行频繁卡顿,还出现了自动关机。",
]

train_labels = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]

真实项目应使用足够大、标签可靠且与目标分布一致的数据,并保留独立测试集。

18. 标签映射为什么必须写清楚

id2label = {
    0: "NEGATIVE",
    1: "POSITIVE",
}

label2id = {
    "NEGATIVE": 0,
    "POSITIVE": 1,
}

加载模型时传入:

model = AutoModelForSequenceClassification.from_pretrained(
    model_id,
    num_labels=2,
    id2label=id2label,
    label2id=label2id,
)

这样保存模型后,推理结果能够直接返回可读标签,而不是含义不明确的 LABEL_0

19. 为什么分类头会提示“部分权重未初始化”

google-bert/bert-base-chinese 是预训练 Backbone,并不是已经完成当前二分类任务的模型。

当使用 AutoModelForSequenceClassification 加载时:

BERT Backbone 参数
  <- 从预训练 Checkpoint 加载

新分类头参数
  <- 随机初始化

因此出现类似“classifier weights were not initialized”的提示通常是正常的。它是在提醒:

新任务头还没有训练,必须微调后才能用于当前标签预测。

如果加载的是已经完成相同分类任务的 Checkpoint,却出现大量 Backbone 权重不匹配,则需要检查模型类和 Checkpoint 是否选错。

20. TrainingArguments 管理什么

from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="outputs/hf-chinese-sentiment",
    learning_rate=2e-5,
    per_device_train_batch_size=4,
    per_device_eval_batch_size=4,
    num_train_epochs=3,
    weight_decay=0.01,
    eval_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    metric_for_best_model="accuracy",
    save_total_limit=2,
    logging_steps=1,
    report_to="none",
    seed=42,
)

常用参数含义:

参数 作用
output_dir Checkpoint 与训练输出目录
learning_rate 优化器学习率
per_device_train_batch_size 每个设备上的训练 batch size
num_train_epochs 遍历训练集次数
weight_decay 权重衰减
eval_strategy 何时评估
save_strategy 何时保存 Checkpoint
load_best_model_at_end 训练后恢复最佳 Checkpoint
metric_for_best_model 选择最佳模型的指标
report_to 实验记录后端;none 表示关闭

20.1 有效 batch size

如果有 $N$ 个设备,每个设备 batch size 为 $B$,梯度累积步数为 $G$,则每次参数更新对应的有效 batch size 约为:

$$
B_{effective}=N\times B\times G
$$

显存不足时,可以减小 per_device_train_batch_size,再使用:

gradient_accumulation_steps=4

但梯度累积不能消除单条超长序列带来的显存压力。

21. 评估函数 compute_metrics

二分类准确率可以直接实现,避免额外下载指标脚本:

import numpy as np


def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)
    accuracy = (predictions == labels).mean()
    return {"accuracy": float(accuracy)}

Trainer 在评估阶段会:

验证集 batch
  -> 模型前向
  -> 收集 logits 和 labels
  -> 调用 compute_metrics
  -> 得到 eval_accuracy

分类任务不应只看 Accuracy。类别不平衡时还应关注:

  • Precision。
  • Recall。
  • F1。
  • 混淆矩阵。
  • 每一类的样本数。

22. 创建 Trainer

from transformers import Trainer

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset["train"],
    eval_dataset=tokenized_dataset["validation"],
    processing_class=tokenizer,
    data_collator=data_collator,
    compute_metrics=compute_metrics,
)

再执行:

trainer.train()
metrics = trainer.evaluate()
print(metrics)

Trainer微调数据流

22.1 Trainer 帮我们做了什么

它封装了典型训练循环:

DataLoader 取 batch
  -> 移动到正确设备
  -> model(**batch)
  -> 读取 loss
  -> backward
  -> 梯度累积或裁剪
  -> optimizer.step
  -> learning rate scheduler.step
  -> 清梯度
  -> 日志、评估和保存

22.2 Trainer 会隐藏原理吗

工具会隐藏重复代码,但不应该隐藏理解。

至少应知道:

  • batch 中有哪些键。
  • 模型 forward() 接收哪些参数。
  • labels 如何产生 loss。
  • 优化器何时更新。
  • 评估指标基于什么数据计算。
  • 最佳模型按哪个指标选择。

理解这些以后,Trainer 是减少样板代码;不理解时,它就容易变成难以调试的黑箱。

23. 完整项目代码

完整脚本位于:

source/code/nlp-blog/huggingface_text_classification.py

运行:

python source/code/nlp-blog/huggingface_text_classification.py

第一次运行需要联网下载 google-bert/bert-base-chinese。下载大小、训练速度和显存占用取决于模型与环境。

脚本执行流程:

设置随机种子
  -> 构造本地 DatasetDict
  -> 加载 AutoTokenizer
  -> dataset.map 批量分词
  -> 创建 DataCollatorWithPadding
  -> 加载带分类头的 BERT
  -> 创建 TrainingArguments
  -> 创建 Trainer
  -> train + evaluate
  -> save_pretrained
  -> pipeline 从本地目录重新加载
  -> 输出新文本预测

24. 保存模型、Tokenizer 与配置

final_dir = "outputs/hf-chinese-sentiment/final"

trainer.save_model(final_dir)
tokenizer.save_pretrained(final_dir)

保存后的目录通常包含:

final/
  config.json
  model.safetensors
  tokenizer.json
  tokenizer_config.json
  special_tokens_map.json
  vocab.txt
  training_args.bin

24.1 为什么必须一起保存 Tokenizer

模型的 embedding 行号对应特定词表。只保存权重,不保存匹配的 Tokenizer,会让以后无法可靠复现输入 token id。

24.2 从本地目录重新加载

from transformers import pipeline

classifier = pipeline(
    "text-classification",
    model=final_dir,
    tokenizer=final_dir,
)

print(classifier("这个键盘手感很好。"))

from_pretrained() 中的“pretrained”不意味着一定联网。它既可以接收 Hub ID,也可以接收本地目录。

模型保存与重新加载

25. Checkpoint 与最终模型的区别

训练中间目录可能包含:

checkpoint-10/
checkpoint-20/

它们常包含:

  • 模型权重。
  • 优化器状态。
  • 学习率调度器状态。
  • 随机数状态。
  • Trainer 状态。

这些状态用于恢复训练。

最终推理目录通常只需要:

  • 模型配置与权重。
  • Tokenizer 或 Processor。
  • 推理需要的生成配置。
  • 标签映射和说明文档。

不要把“保存可推理模型”与“保存可无缝续训 Checkpoint”当成完全相同的需求。

26. 上传到 Hugging Face Hub

上传属于远程写操作,需要先创建账号和访问令牌。不要把令牌写进源代码或提交到 Git。

命令行登录:

hf auth login

使用 Trainer 上传:

trainer.push_to_hub(
    commit_message="Fine-tune Chinese sentiment classifier"
)

也可以直接上传目录:

from huggingface_hub import HfApi

api = HfApi()
api.upload_folder(
    folder_path=final_dir,
    repo_id="your-name/chinese-sentiment-demo",
    repo_type="model",
)

26.1 上传前应准备什么

至少应补充 Model Card:

模型用途
基础模型
训练数据来源与许可证
标签定义
训练超参数
评估方法与结果
局限、偏差与不适用场景
推理示例

本篇的小型手写数据只是教学样例,不适合直接发布为“可用于生产”的情感模型。

27. 下载缓存在哪里

默认情况下,Hub 文件会进入本地缓存。再次加载相同 revision 时,可以复用已有文件而不重复下载。

查看环境变量:

import os

print(os.getenv("HF_HOME"))
print(os.getenv("HF_HUB_CACHE"))

可以在运行前指定缓存根目录:

PowerShell:

$env:HF_HOME = "D:\hf-cache"

或者为单次加载指定:

tokenizer = AutoTokenizer.from_pretrained(
    model_id,
    cache_dir="D:/hf-cache",
)

27.1 离线模式

文件已经完整缓存后,可以启用离线模式:

$env:HF_HUB_OFFLINE = "1"

或在代码中要求只读取本地文件:

model = AutoModelForSequenceClassification.from_pretrained(
    model_id,
    local_files_only=True,
)

如果缓存缺少必要文件,离线加载仍然会失败。

28. CPU、GPU、dtype 与 device_map

28.1 普通小模型

手动推理时:

device = torch.device(
    "cuda" if torch.cuda.is_available() else "cpu"
)

model.to(device)
inputs = {name: value.to(device) for name, value in inputs.items()}

模型和输入必须位于同一设备。

28.2 pipeline 指定 GPU

classifier = pipeline(
    "text-classification",
    model=model_id,
    device=0,
)

device=0 通常表示第一张 CUDA GPU,device=-1 表示 CPU。

28.3 大模型自动分配

对于较大模型,可以结合 Accelerate:

model = AutoModelForCausalLM.from_pretrained(
    model_id,
    dtype="auto",
    device_map="auto",
)

但这不表示任何模型都能塞进有限显存。仍需考虑:

  • 权重占用。
  • KV Cache。
  • 激活值。
  • batch size 与序列长度。
  • 量化精度。

29. 文本生成:从 pipelinegenerate

29.1 使用 pipeline

from transformers import pipeline

generator = pipeline(
    "text-generation",
    model="openai-community/gpt2",
)

result = generator(
    "Machine learning is",
    max_new_tokens=30,
    do_sample=True,
    temperature=0.8,
    top_p=0.9,
)

print(result[0]["generated_text"])

这里使用英文 Prompt,是因为 GPT-2 的训练语言与 Tokenizer 更适合英文。选模型时必须检查语言适配性。

29.2 手动调用 generate

from transformers import AutoModelForCausalLM, AutoTokenizer

model_id = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)

inputs = tokenizer(
    "Machine learning is",
    return_tensors="pt",
)

output_ids = model.generate(
    **inputs,
    max_new_tokens=30,
    do_sample=True,
    temperature=0.8,
    top_p=0.9,
)

text = tokenizer.decode(
    output_ids[0],
    skip_special_tokens=True,
)
print(text)

29.3 max_new_tokensmax_length

max_new_tokens
  只限制新生成的 token 数

max_length
  限制输入 token + 新 token 的总长度

控制生成长度时,max_new_tokens 通常更直观。

29.4 聊天模型需要 Chat Template

聊天模型往往不是简单拼接:

system + user + assistant

它们使用训练时约定的特殊格式。应优先调用对应 Tokenizer 的:

tokenizer.apply_chat_template(...)

格式不匹配时,即使权重正确,模型表现也可能明显下降。

30. Model Card 应该怎么看

选择模型时建议依次检查:

  1. Task:模型是否真的为当前任务训练。
  2. Language:语言和领域是否匹配。
  3. License:是否允许你的研究或商业用途。
  4. Base model:模型从哪个 Checkpoint 微调而来。
  5. Dataset:训练数据是否可靠、是否接近目标分布。
  6. Metrics:在哪个测试集、使用什么指标得到结果。
  7. Limitations:已知偏差、风险和失败场景。
  8. Files:权重格式、大小与必要配置是否完整。
  9. Revision:项目是否需要锁定提交哈希。

同一个排行榜分数不一定能迁移到你的数据。最可靠的方法仍然是在自己保留的测试集上评估。

31. 安全与可复现性

31.1 谨慎使用 trust_remote_code=True

有些自定义模型需要执行仓库中的 Python 代码:

model = AutoModel.from_pretrained(
    model_id,
    trust_remote_code=True,
)

这意味着你明确允许远程仓库代码在本机运行。使用前应:

  • 检查仓库作者和代码。
  • 锁定 revision 到可信提交。
  • 在隔离环境中测试。
  • 不在含敏感凭据的环境中随意执行未知代码。

如果模型不需要该参数,就不要为了消除报错盲目开启。

31.2 锁定 revision

revision = "完整提交哈希"

tokenizer = AutoTokenizer.from_pretrained(
    model_id,
    revision=revision,
)

model = AutoModelForSequenceClassification.from_pretrained(
    model_id,
    revision=revision,
)

同一模型仓库的 main 分支可能更新。锁定 revision 有助于让未来运行读取相同文件。

31.3 不要泄露访问令牌

访问令牌不应:

  • 写进 Python 文件。
  • 写进 Notebook 输出。
  • 提交到 Git。
  • 放在公开日志或截图中。

应使用 Hugging Face CLI 登录、环境变量或受控的 Secret 管理系统。

32. Spaces 是什么

Spaces 用于托管机器学习演示或应用。常见选择包括:

  • Gradio。
  • Streamlit。
  • 静态 HTML。
  • Docker。

一个简单 Gradio 应用可能是:

import gradio as gr
from transformers import pipeline

classifier = pipeline(
    "text-classification",
    model="your-name/chinese-sentiment-demo",
)


def predict(text):
    return classifier(text)


demo = gr.Interface(
    fn=predict,
    inputs="text",
    outputs="json",
)

demo.launch()

需要区分:

Model Repo
  保存模型资产

Space Repo
  保存并运行应用

Inference Endpoint / Provider
  提供托管推理能力

它们解决的问题不同,价格、硬件和可用性也可能不同。

33. 常见错误

33.1 模型与 Tokenizer 不匹配

tokenizer = AutoTokenizer.from_pretrained(model_a)
model = AutoModel.from_pretrained(model_b)

除非明确知道二者兼容,否则应从同一个仓库或同一套发布资产加载。

33.2 任务模型类选错

文本分类应优先使用:

AutoModelForSequenceClassification

只使用 AutoModel 不会自动产生类别 logits。

33.3 忘记 truncation=True

长文本超过模型支持长度时会报维度错误或索引错误。设置截断后还要评估信息损失。

33.4 在整个数据集上固定 Padding 到最大长度

这会制造大量无效 token。优先考虑 DataCollatorWithPadding 的动态 Padding。

33.5 把 logits 当概率

logits 不要求位于 $[0,1]$,总和也不要求为 1。推理展示概率时再调用 Softmax。

33.6 训练时先手动 Softmax

交叉熵应接收 logits。提前 Softmax 会改变数值性质,并与模型内置损失的预期不一致。

33.7 忘记 model.eval()

手动推理时应关闭 Dropout 等训练行为:

model.eval()
with torch.inference_mode():
    outputs = model(**inputs)

pipeline 会处理常规推理模式,但手动调用时需要自己负责。

33.8 模型与输入不在同一设备

如果模型在 GPU、输入在 CPU,会出现 device mismatch。二者必须一起移动。

33.9 根据标签编号猜语义

始终检查:

model.config.id2label

并与 Model Card 和数据集标签说明对照。

33.10 使用测试集调超参数

正确职责是:

train       更新参数
validation  选择超参数和最佳 Checkpoint
test        最后一次无偏评估

33.11 小数据训练成功就宣称模型可用

训练 loss 下降只说明模型拟合了训练目标。还需要检查验证集、测试集、真实分布、偏差和失败样例。

33.12 盲目相信 device_map="auto"

它负责设备分配,不会自动解决所有显存、速度或量化问题,也不能替代容量估算。

33.13 直接执行未知远程代码

不要为了加载模型就无条件设置 trust_remote_code=True。先审查代码并锁定 revision。

33.14 只保存模型,遗漏 Tokenizer

最终发布目录应同时包含匹配的预处理配置和标签映射。

34. Hugging Face 没有替你解决什么

Hugging Face 能减少工程样板,但不会自动解决:

  • 任务定义是否正确。
  • 数据标签是否可靠。
  • 训练集是否泄露测试信息。
  • 许可证是否适合使用场景。
  • 模型偏差是否可接受。
  • 线上延迟和吞吐是否达标。
  • 长文本截断是否丢失关键信息。
  • 评估集是否代表真实用户分布。

模型调用变简单以后,数据、评估和安全反而更值得认真对待。

35. 推荐学习路线

可以按照下面顺序逐层深入:

1. 用 pipeline 跑通一个已有模型
2. 阅读该模型的 Model Card 和 config
3. 用 AutoTokenizer 查看 input_ids 与 attention_mask
4. 用 AutoModelFor... 手动完成前向传播
5. 理解 logits、Softmax 和标签映射
6. 用 Dataset.from_dict 构造小数据
7. 用 map 批量 Tokenize
8. 用 DataCollatorWithPadding 组成 batch
9. 用 Trainer 微调并评估
10. save_pretrained 后从本地重载
11. 加入独立测试集和完整指标
12. 再学习 Accelerate、PEFT、量化和部署

调试时优先打印:

print(dataset)
print(dataset.features)
print(tokenized_dataset["train"][0])
print(data_collator([tokenized_dataset["train"][0]]))
print(model.config)
print(model.config.id2label)

比起只盯着训练进度条,先确认数据字段、形状和标签含义往往更有效。

36. 总结

Hugging Face 的主线可以浓缩为:

Hub
  -> 找到模型、数据集与说明

AutoTokenizer / AutoProcessor
  -> 把原始输入转成模型张量

AutoModelFor...
  -> 按任务加载正确架构和预训练权重

pipeline
  -> 封装 preprocess、forward、postprocess

Datasets + Data Collator
  -> 加载、批处理并整理训练数据

Trainer
  -> 训练、评估、日志和 Checkpoint

save_pretrained / push_to_hub
  -> 保存、复用与分享

需要真正掌握的核心包括:

  • Hugging Face 是平台、库和约定组成的生态,不是单个模型。
  • Hub ID 指向具体 Checkpoint,不等于模型架构名称。
  • Tokenizer 必须与模型匹配。
  • pipeline 是高级推理封装,不是新的神经网络结构。
  • AutoModel 返回 Backbone 表示,AutoModelFor... 带任务头。
  • 分类模型输出 logits,概率需要通过 Softmax 得到。
  • Dataset.map 负责样本预处理,Data Collator 负责组成动态 Padding batch。
  • Trainer 封装训练循环,但数据、损失、指标和标签含义仍需自己理解。
  • 模型保存时应连同 Tokenizer、配置与标签映射一起保存。
  • Model Card、许可证、revision 和远程代码安全与模型效果同样重要。

最后,用一句话概括:

Hugging Face 把研究社区已有的模型资产变成统一、可复用的工程组件,而使用者仍然要为任务定义、数据质量、评估结论和安全边界负责。

完整项目代码:source/code/nlp-blog/huggingface_text_classification.py

参考资料

  1. Hugging Face Transformers Quickstart: https://huggingface.co/docs/transformers/quicktour
  2. Hugging Face Pipeline Guide: https://huggingface.co/docs/transformers/main/en/pipeline_tutorial
  3. Hugging Face AutoClass Guide: https://huggingface.co/docs/transformers/main/en/autoclass_tutorial
  4. Hugging Face Text Classification Guide: https://huggingface.co/docs/transformers/main/en/tasks/sequence_classification
  5. Hugging Face Padding and Truncation: https://huggingface.co/docs/transformers/main/en/pad_truncation
  6. Hugging Face Datasets Quickstart: https://huggingface.co/docs/datasets/main/en/quickstart
  7. Hugging Face Datasets Processing Guide: https://huggingface.co/docs/datasets/main/en/process
  8. Hugging Face Hub Model Cards: https://huggingface.co/docs/hub/en/model-cards
  9. Hugging Face Hub Cache Guide: https://huggingface.co/docs/huggingface_hub/en/guides/manage-cache
  10. Hugging Face Hub Upload Guide: https://huggingface.co/docs/huggingface_hub/en/guides/upload