12. HuggingFace平台的使用

一、Hugging Face 生态简介

  Hugging Face 是一家提供开源 AI 工具和平台的公司,致力于简化预训练模型的使用,加速机器学习项目的开发与落地。最初以 Transformers 库闻名,该库极大地降低了使用 BERT、GPT、T5 等模型的门槛。如今,Hugging Face 已发展成为一个完整的 AI 开发生态系统,支持自然语言处理、计算机视觉、语音处理、多模态任务等多个领域。

  Hugging Face 的生态系统主要由两个核心部分组成:

【1】、Hugging Face Hub

  Hugging Face提供了一个集中式的开源平台,用于托管和分享模型、数据集和应用。官网地址为: https://huggingface.co ,国内镜像地址为: https://hf-mirror.com 。

【2】、工具链(Libraries)

  Hugging Face 提供了一套围绕预训练模型构建的工具库。这些组件彼此独立,又可以协同工作,覆盖了从数据处理到模型训练与推理的完整流程。

工具链

  各组件具体功能如下:

  • Datasets:用于加载和处理数据集的工具库。支持从在线仓库或本地文件(如 CSV、JSON)加载文本数据,并支持清洗、编码、切分等预处理操作。处理后的数据可直接用于模型训练,是连接原始数据与模型输入的重要桥梁。
  • Tokenizers:用于将文本转换为模型输入的工具。它支持文本分词、编码为 token ID,同时自动处理特殊符号、填充(padding)、attention mask 和句子对标记(token type ID)。分词器通常与模型配套使用,可通过统一接口加载。
  • Transformers:是 Hugging Face 最核心的库,用于加载、使用和微调各种预训练模型。该库统一了模型接口,支持数百种模型结构,如 BERT、GPT 等,用户可以通过一行代码 from_pretrained() 直接加载公开模型,快速用于推理或训练。

  我们在终端中输入 pip 指令安装 PyTorch。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -f 指定国内镜像源下载。

pip install torch torchvision -f https://mirrors.aliyun.com/pytorch-wheels/cu132

  国内常用的 pip 下载源列表:

二、模型的加载与使用

  我们在终端中输入 pip 指令安装 transformers 库。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载。

pip install transformers -i https://pypi.tuna.tsinghua.edu.cn/simple

  在使用 Hugging Face 生态中的预训练模型时,第一步往往是从 Hub 上选择一个合适的模型,然后将其加载到本地进行微调或推理。为了简化这一流程,Transformers 库提供了统一的模型加载接口 —— AutoModel,用于自动下载和加载模型。

transformers.AutoModel.from_pretrained(
    pretrained_model_name_or_path,                                              # 指定模型 ID 或本地路径
    *model_args, 
    **kwargs
)

  我们可以通过 cache_dir 关键字参数指定模型下载后的 缓存目录,如果不指定,会存放到 ~/user/.cahe 文件夹下,下次加载相同模型时会直接读取缓存,不再联网下载。通过 force_download 关键字参数控制是否 强制重新下载 模型,默认为 False。通过 local_files_only 关键字参数设置是否 仅使用本地文件,默认为 False。通过 torch_dtype 关键字参数指定加载模型权重的 数据类型。如果传入 "auto" 会自动选择最优精度。

import os

os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"                             # 镜像地址

HUGGING_FACE_DRIECTORY = os.path.join(os.getcwd(), "assets", "hugging_face")

# 必须到导入模块前设置环境变量,否则会报错
from transformers import AutoModel

if __name__ == "__main__":
    if not os.path.exists(HUGGING_FACE_DRIECTORY):
        os.makedirs(HUGGING_FACE_DRIECTORY)

    # 加载模型
    model = AutoModel.from_pretrained(
        "google-bert/bert-base-chinese",                                        # 模型名称
        cache_dir=HUGGING_FACE_DRIECTORY                                        # 缓存目录
    )

如果报如下错误:'[WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。' thrown while requesting HEAD https://huggingface.co/google-bert/bert-base-chinese/resolve/main/config.json Retrying in 1s [Retry 1/5].,那是因为我们无法可以直接访问它的网站,需要配置 HF_ENDPOINT 环境变量,访问的国内镜像网站。

必须到导入模块前设置环境变量,否则会报错如下错误:'[WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。' thrown while requesting HEAD https://huggingface.co/google-bert/bert-base-chinese/resolve/main/config.json Retrying in 1s [Retry 1/5].。

  我们下的文件中包含了 config.json 配置文件,该文件中 定义了模型的结构信息,Transformers 会据此识别模型类型(如 BERT),并自动实例化对应的模型类(如 BertModel)。这些模型类均继承自 PyTorch 的 torch.nn.Module,因此构建出的对象本质上是一个标准的神经网络模型。

  除了在线加载模型之外,我们还可以使用 from_pretrained() 也支持从本地路径加载模型,要求目录中包含 模型权重 model.safetensors 文件和 配置文件 config.json 文件。

import os

from transformers import AutoModel

BERT_BASE_CHINESE_MODEL_DRIECTORY = os.path.join(os.getcwd(), "assets", "hugging_face", "bert-base-chinese")

if __name__ == "__main__":
    if not os.path.exists(HUGGING_FACE_DRIECTORY):
        os.makedirs(HUGGING_FACE_DRIECTORY)

    # 加载模型
    model = AutoModel.from_pretrained(
        BERT_BASE_CHINESE_MODEL_DRIECTORY,                                      # 本地模型目录
    )

  AutoModel 只加载预训练模型的主干结构,不包含任何任务相关的输出层,适用于特征提取或自定义模型结构的场景。除此之外,Transformers 还提供了用于具体任务的专用模型类:AutoModelForXXX,这些类在模型主干的基础上,自动添加了适配任务的输出层(通常称为 “任务头” 或 Task Head),使模型能够直接用于分类、命名实体识别、问答等标准 NLP 任务的训练与推理,无需手动修改结构。

  常用的任务模型类如下:

模型类名称 适用任务类型 简要说明
AutoModelForCausalLM 自回归语言建模 用于根据已有文本逐字生成后续文本
AutoModelForSeq2SeqLM 序列到序列生成任务 适用于机器翻译、文本摘要、对话生成
AutoModelForSequenceClassification 文本分类 例如情感分析、主题分类、多标签分类
AutoModelForTokenClassification Token 级别标注任务 如命名实体识别(NER)
AutoModelForQuestionAnswering 抽取式问答 用于从上下文中找出答案的起始和结束位置
import os

os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"                             # 镜像地址

HUGGING_FACE_DRIECTORY = os.path.join(os.getcwd(), "assets", "hugging_face")

# 必须到导入模块前设置环境变量,否则会报错
from transformers import AutoModelForSequenceClassification

if __name__ == "__main__":
    if not os.path.exists(HUGGING_FACE_DRIECTORY):
        os.makedirs(HUGGING_FACE_DRIECTORY)

    # 加载模型,cache_dir 指定缓存目录
    model = AutoModelForSequenceClassification.from_pretrained(
        "google-bert/bert-base-chinese",                                        # 模型名称
        cache_dir=HUGGING_FACE_DRIECTORY,                                       # 缓存目录
        num_labels=3                                                            # 类别数
    )

  关键字参数 num_labels 用于 指定分类任务的类别数,默认值为 2。用于构建分类头的输出维度。

  上述代码得到的 model 的类型为 BertForSequenceClassification。模型结构包括:

  • BERT 编码器主干;
  • 一个线性层(任务头),用于输出每个类别的得分。

  由于通过 AutoModel 或 AutoModelForXXX 加载的模型,本质上是 PyTorch 的 torch.nn.Module 子类,其前向传播过程通过 forward() 方法实现,所以要了解某个模型支持哪些输入参数、返回哪些输出字段,最直接、最权威的方式就是查看其 forward() 方法定义。

  我们可以从官方网站:https://huggingface.co/docs/transformers/index 中查看模型的 API 文档,如果官方无法访问,则可以从镜像网站:https://hf-mirror.com/docs/transformers/index 中查看。

查看模型的API

  这里,我们以 BertModel 模型为例:

transformers.BertModel.forward(
    input_ids: Tensor | None = None,                                            # 输入索引
    attention_mask: Tensor | None = None,                                       # 注意力掩码
    token_type_ids: Tensor | None = None,                                       # 段嵌入索引
    position_ids: Tensor | None = None,                                         # 位置索引覆盖
    inputs_embeds: Tensor | None = None,                                        # 自定义嵌入输入
    encoder_hidden_states: Tensor | None = None,                                # 编码器隐藏状态
    encoder_attention_mask: Tensor | None = None,                               # 编码器掩码
    past_key_values: Cache | None = None,                                       # 缓存的 K/V 键值对
    use_cache: bool | None = None,                                              # 是否使用缓存
    **kwargs: **TransformersKwargs
) -> (tuple[Tensor] | BaseModelOutputWithPoolingAndCrossAttentions)

  参数 attention_mask 是 注意力掩码,其中 1 代表那些未被屏蔽的标记,对于那些被隐藏的标记。

  当 return_dict=True(默认)时,返回的对象包含:

  • last_hidden_state:最后一层 Transformer 输出的词级向量序列 (batch, seq_len, 768),用于 NER、序列标注等。
  • pooler_output:取 [CLS] 位置经过线性层 + Tanh 激活的句级向量 (batch, 768),常用于分类。
  • hidden_states:可选,开启 output_hidden_states 后返回,包含每层输出。
  • attentions:可选,开启 output_attentions 后返回,包含每层注意力矩阵。

三、Tokenizer 的加载与使用

  在 Hugging Face 的 Transformers 库中,每一个预训练模型都配套绑定有一个专用的 Tokenizer,它负责将原始文本转换为模型可以理解的输入格式(如 input_ids、attention_mask 等),是连接原始文本与模型计算之间的关键环节。这些 Tokenizer 通常集成了从文本到张量的全流程处理能力,主要包括以下几个方面:

  • 子词切分(subword tokenization):将输入文本拆分为子词单元。
  • 编码映射:将每个子词转换为对应的整数 ID,即 input_ids。
  • 添加特殊 Token:自动插入如 [CLS]、[SEP] 等任务相关的特殊符号。
  • 截断与补齐(truncation & padding):统一输入序列长度,构造批量输入。
  • 生成辅助输入:根据模型需求生成 attention_mask、``token_type_ids` 等附加字段。

  在 Transformers 库中,AutoTokenizer 用于 加载与指定模型配套的分词器。它会根据模型名称自动选择并实例化正确的分词器类型(如 BertTokenizer、``GPT2Tokenizer、T5Tokenizer 等)。AutoTokenizer 会根据提供的模型名称,从 Hugging Face Hub 上下载所需的文件资源,包括配置文件词表。这些文件会自动缓存到本地,默认路径是:~/user/.cache/huggingface/hub`。下次加载相同模型时会直接读取缓存,不再联网下载。

transformers.AutoTokenizer.from_pretrained(
    pretrained_model_name_or_path,                                              # 指定模型 ID 或本地路径
    *inputs, 
    **kwargs
) -> TokenizersBackend | SentencePieceBackend

  我们可以通过 cache_dir 关键字参数指定模型下载后的 缓存目录,如果不指定,会存放到 user/.cahe 文件夹下。通过 force_download 关键字参数控制是否 强制重新下载 模型,默认为 False。

  除了在线加载模型之外,AutoTokenizer.from_pretrained() 也支持从本地路径加载模型,要求目录中包含词表和配置文件。

  我们可以使用 tokenize() 方法 进行切词,使用 convert_tokens_to_ids() 方法 将 token 转换为 id,使用 convert_ids_to_tokens() 方法 将 id 转换为 token。

  我们还可以使用 encode() 方法 进行编码,编码 是将 tokenize + convert_tokens_to_ids 合并后的结果,通常还会自动添加特殊符号(如 [CLS] 和 [SEP]),除此之外,还支持 padding、``truncate等功能。我们还可以使用decode()` 方法 进行解码。解码 会将一个 token id 序列还原为对应的原始文本(或接近的文本)。

Tokenizer.encode(
    text: TextInput | PreTokenizedInput | EncodedInput,                         # 第一个待编码的序列
    text_pair: TextInput | PreTokenizedInput | EncodedInput | None = None,      # 可选的第二个待编码序列
    add_special_tokens: bool = True,                                            # 是否添加特殊 token
    padding: bool | str | PaddingStrategy = False,                              # 激活并控制 padding
    truncation: bool | str | TruncationStrategy | None = None,                  # 激活并控制截断
    max_length: int | None = None,                                              # 控制截断/padding 参数使用的最大长度
    stride: int = 0,                                                            # 与 max_length 结合使用,用于生成带有重叠的溢出 token
    padding_side: str | None = None,                                            # 模型应应用 padding 的一侧
    return_tensors: str | TensorType | None = None,                             # 是否返回特定框架的张量
    **kwargs 
) -> list[int], torch.Tensor, or np.ndarray

  参数 padding 用于 激活并控制 padding,接受以下值:

  • True 或 "longest":填充到批次中最长序列的长度(若只提供单个序列则不填充)。
  • "max_length":填充到 max_length 参数指定的最大长度,若未提供该参数则填充到模型的最大可接受输入长度。
  • False 或 "do_not_pad"(默认):不填充(即可以输出长度不同的批次序列)。

  参数 truncation 用于 激活并控制截断,接受以下值:

  • True 或 "longest_first":截断到 max_length 参数指定的最大长度,若未提供该参数则截断到模型的最大可接受输入长度。若提供的是序列对(或批次序列对),将逐 token 截断,从最长的序列中移除 token。
  • "only_first":截断到 max_length 参数指定的最大长度,若未提供该参数则截断到模型的最大可接受输入长度。若提供的是序列对(或批次序列对),仅截断第一个序列。
  • "only_second":截断到 max_length 参数指定的最大长度,若未提供该参数则截断到模型的最大可接受输入长度。若提供的是序列对(或批次序列对),仅截断第二个序列。

  参数 padding_side 是 模型应应用 padding 的一侧。应在 ["right", "left"] 之间选择。默认值从同名的类属性中选取。

  参数 return_tensors 若设置,将返回特定框架的张量,而非 Python 整数列表。可接受的值有:

  • "pt":返回 PyTorch torch.Tensor 对象。
  • "np":返回 NumPy numpy.ndarray 对象。
Tokenizer.decode(
    token_ids: int | list[int] | list[list[int]] | np.ndarray | torch.Tensor,   # 待解码的 token ID
    skip_special_tokens: bool = False,                                          # 是否在解码后的字符串中移除所有特殊 token
    **kwargs 
) -> Union[str, list[str]]                                                      # 解码后的字符串
import os

os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"                             # 镜像地址

HUGGING_FACE_DRIECTORY = os.path.join(os.getcwd(), "assets", "hugging_face")

# 必须到导入模块前设置环境变量,否则会报错
from transformers import AutoTokenizer

if __name__ == "__main__":
    if not os.path.exists(HUGGING_FACE_DRIECTORY):
        os.makedirs(HUGGING_FACE_DRIECTORY)

    # 加载分词器,cache_dir 指定缓存目录
    tokenizer = AutoTokenizer.from_pretrained(
        "google-bert/bert-base-chinese",                                        # 模型名称
        cache_dir=HUGGING_FACE_DRIECTORY                                        # 缓存目录
    )

    # ['你', '好', ',', '我', '是', '小', '樱', ',', '今', '后', '请', '多', '多', '关', '照', '。']
    text = "你好,我是小樱,今后请多多关照。"
    tokens = tokenizer.tokenize(text)                                           # 切词 
    print(tokens)

    # [872, 1962, 8024, 2769, 3221, 2207, 3569, 8024, 791, 1400, 6435, 1914, 1914, 1068, 4212, 511]
    ids = tokenizer.convert_tokens_to_ids(tokens)                               # 转换为ID
    print(ids)

    # ['你', '好', ',', '我', '是', '小', '樱', ',', '今', '后', '请', '多', '多', '关', '照', '。']
    tokens = tokenizer.convert_ids_to_tokens(ids)                               # 转换为词
    print(tokens)

    # [101, 872, 1962, 8024, 2769, 3221, 2207, 3569, 8024, 791, 1400, 6435, 1914, 1914, 1068, 4212, 511, 102]
    ids = tokenizer.encode(text)                                                # 编码
    print(ids)

    # [CLS] 你 好 , 我 是 小 樱 , 今 后 请 多 多 关 照 。 [SEP]
    text = tokenizer.decode(ids)                                                # 解码
    print(text)

  我们还可以使用 tokenizer() 方法直接构造模型所需的输入。

Tokenizer.__call__( 
    text: TextInput | PreTokenizedInput | list[TextInput] | list[PreTokenizedInput] | None = None,              # 待编码的单个序列或序列批次
    text_pair: TextInput | PreTokenizedInput | list[TextInput] | list[PreTokenizedInput] | None = None,         # 可选的第二个待编码序列
    text_target: TextInput | PreTokenizedInput | list[TextInput] | list[PreTokenizedInput] | None = None,       # 待编码为目标文本的单个序列或序列批次
    text_pair_target: TextInput | PreTokenizedInput | list[TextInput] | list[PreTokenizedInput] | None = None, # 可选的第二个待编码为目标文本的单个序列或序列批次
    add_special_tokens: bool = True,                                            # 编码时是否添加特殊 token
    padding: bool | str | PaddingStrategy = False,                              # 激活并控制 padding
    truncation: bool | str | TruncationStrategy | None = None,                  # 激活并控制截断
    max_length: int | None = None,                                              # 控制截断/padding 参数使用的最大长度
    stride: int = 0,                                                            # 与 max_length 结合使用,用于生成带有重叠的溢出 token
    is_split_into_words: bool = False,                                          # 输入是否已经预分词
    pad_to_multiple_of: int | None = None,                                      # 设置把序列填充到所提供值的倍数
    padding_side: str | None = None,                                            # 模型应应用 padding 的一侧
    return_tensors: str | TensorType | None = None,                             # 是否返回特定框架的张量
    return_token_type_ids: bool | None = None,                                  # 是否返回 token type IDs
    return_attention_mask: bool | None = None,                                  # 是否返回 attention mask
    return_overflowing_tokens: bool = False,                                    # 是否返回溢出的 token 序列
    return_special_tokens_mask: bool = False,                                   # 是否返回特殊 token 掩码信息
    return_offsets_mapping: bool = False,                                       # 是否返回每个 token 的 (char_start, char_end)
    return_length: bool = False,                                                # 是否返回编码后输入的长度
    verbose: bool = True,                                                       # 是否打印详细信息
    tokenizer_kwargs: dict[str, Any] | None = None,                             # 传递给 tokenizer 的其他参数
    **kwargs 
) -> BatchEncoding

  返回的 BatchEncoding 对象包含以下字段的字典:

  • input_ids: 待输入模型的 token id 列表。
  • token_type_ids:待输入模型的 token type id 列表(当 return_token_type_ids=True 或 "token_type_ids" 在 self.model_input_names 中时)。
  • attention_mask:指定模型应关注哪些 token 的索引列表(当 return_attention_mask=True 或 "attention_mask" 在 self.model_input_names 中时)。
  • overflowing_tokens:溢出的 token 序列列表(当指定了 max_length 且 return_overflowing_tokens=True 时)。
  • num_truncated_tokens:被截断的 token 数量(当指定了 max_length 且 return_overflowing_tokens=True 时)。
  • special_tokens_mask:由 0 和 1 组成的列表,1 表示添加的特殊 token,0 表示常规序列 token(当 add_special_tokens=True 且 return_special_tokens_mask=True 时)。
  • length:输入的长度(当 return_length=True 时)。
import os

os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"                             # 镜像地址

HUGGING_FACE_DRIECTORY = os.path.join(os.getcwd(), "assets", "hugging_face")

# 必须到导入模块前设置环境变量,否则会报错
from transformers import AutoTokenizer

if __name__ == "__main__":
    if not os.path.exists(HUGGING_FACE_DRIECTORY):
        os.makedirs(HUGGING_FACE_DRIECTORY)

    # 加载模型,cache_dir 指定缓存目录
    tokenizer = AutoTokenizer.from_pretrained(
        "google-bert/bert-base-chinese",                                        # 模型名称
        cache_dir=HUGGING_FACE_DRIECTORY                                        # 缓存目录
    )

    """
    {
        'input_ids': [101, 872, 1962, 8024, 2769, 3221, 2207, 3569, 8024, 791, 1400, 6435, 1914, 1914, 1068, 4212, 511, 102], 
        'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
        'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    }
    """
    text = "你好,我是小樱,今后请多多关照。"
    result = tokenizer(text)
    print(result)

四、Datasets 库

4.1、什么是 Datasets

  datasets 是 Hugging Face 提供的一个轻量级数据处理库,专为自然语言处理任务设计,能够高效地支持模型训练流程中的数据加载与预处理操作。它的主要特点如下:

  • 加载方便:支持读取本地文件(如 CSV、JSON),也支持加载在线公开数据集。
  • 结构清晰:数据集的内部结构类似表格,每条样本由若干字段组成。
  • 无缝协作:与 tokenizer 等 Hugging Face 模块高度集成,可直接构造模型输入。
  • 功能丰富:支持常见的数据处理操作,如批量映射(map())、字段筛选、训练/验证集划分(train_test_split())等。

  我们在终端中输入 pip 指令安装 datasets 库。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载。

pip install datasets -i https://pypi.tuna.tsinghua.edu.cn/simple

4.2、加载数据集

  datasets 库提供了统一的接口 datasets.load_dataset(),既支持从本地文件加载数据,也支持从 Hugging Face Hub 加载在线开源数据集。

datasets.load_dataset(
    path: str,                                                                  # 数据集路径或名称
    name: str | None = None,                                                    # 数据集的子集或配置名称
    data_dir: str | None = None,                                                # 数据集的根目录
    data_files: str | Sequence[str] | Mapping[str, str | Sequence[str]] | None = None,  # 指定源数据文件的路径
    split: None = None,                                                         # 指定加载数据集的哪个部分
    cache_dir: str | None = None,                                               # 指定读写缓存数据的目录
    features: Features | None = None,                                           # 数据集手动指定特征类型
    download_config: DownloadConfig | None = None,                              # 下载/生成模式
    download_mode: DownloadMode | str | None = None,                            # 特定的下载配置参数
    verification_mode: VerificationMode | str | None = None,                    # 验证模式
    keep_in_memory: bool | None = None,                                         # 是否将数据集完全加载到内存中
    save_infos: bool = False,                                                   # 是否将数据集的信息保存到本地磁盘
    revision: str | Version | None = None,                                      # 指定要加载的数据集版本
    token: bool | str | None = None,                                            # 用于访问 Hugging Face Hub 上私有数据集的认证令牌
    streaming: Literal[False] = False,                                          # 是否以流式方式加载数据集
    num_proc: int | None = None,                                                # 并行处理时使用的进程数量
    storage_options: dict[Unknown, Unknown] | None = None,                      # 传递给底层存储后端的额外选项
    **config_kwargs: Any
) -> DatasetDict: ...

  datasets.load_dataset() 支持多种本地文件格式,如 CSV、JSON、Parquet,并允许一次加载一个或多个文件。其基本语法如下:

from datasets import load_dataset

if __name__ == "__main__":
    dataset_dict = load_dataset(
        "csv", 
        data_files={
            "train": "./assets/data/train.csv",
            "test": "./assets/data/test.csv",
        }
    )

    """
    DatasetDict({
    `    train: Dataset({
            features: ['cat', 'label', 'review'],
            num_rows: 62774
        })
        test: Dataset({
            features: ['cat', 'label', 'review'],
            num_rows: 62774
        })
    })`
    """
    print(dataset_dict)

  此时返回的是一个包含两个 Dataset 的 DatasetDict,其中每个 Dataset 称为一个 split。如果我们只加载一个文件,此时返回的也是一个 DatasetDict,其中只包含默认命名为 "train" 的一个 Dataset。

  datasets.load_dataset() 返回的是一个 DatasetDict 对象,可以像字典一样通过 键名(如 "train")访问 split。Dataset 支持 索引 和 切片 操作来访问样本,也可以通过 字段名 访问某个字段的值:

from datasets import load_dataset

if __name__ == "__main__":
    dataset_dict = load_dataset(
        "csv", 
        data_files="./assets/data/online_shopping_10_cats.csv"
    )

    """
    DatasetDict({
        train: Dataset({
            features: ['cat', 'label', 'review'],
            num_rows: 62774
        })
    })
    """
    print(dataset_dict)

    """
    Dataset({
        features: ['cat', 'label', 'review'],
        num_rows: 62774
    })
    """
    print(dataset_dict["train"])

    """
    {
        'cat': "书籍"', 
        'label': 1, 
        'review' "……": 
    }
    """
    print(dataset_dict["train"][0])
    print(dataset_dict["train"][0]["label"])
    print(dataset_dict["train"][0]["review"])

    """
    {
        'cat':['书籍', '书籍', '书籍'], 
        'label': [1, 1, 1], 
        'review': [……]
    }
    """
    print(dataset_dict["train"][:3])
    print(dataset_dict["train"][:3]["label"])
    print(dataset_dict["train"][:3]["review"])

  Hugging Face Hub 提供了大量开源数据集,涵盖文本分类、问答、翻译、摘要等任务,可以在 官网( https://huggingface.co/datasets )或 HF 镜像站( https://hf-mirror.com/datasets )浏览与搜索。每个数据集页面都会提供示例代码,方便直接复制使用。

使用Datasets加载数据集

复制Datasets的代码

  执行上述代码时,数据集会自动从 Hugging Face Hub 下载,并缓存至本地用户目录,默认路径为 ~/user/.cache/huggingface/datasets/。后续再次使用时将自动从本地加载,无需联网或重复下载。加载完成后,返回一个 DatasetDict 对象,结构和使用方式与本地数据完全一致。

4.3、预处理数据集

  除了加载数据, datasets 库还支持常见的数据预处理操作,如编码文本、删除列、过滤样本、划分子集和设置张量格式。我们可以通过 Dataset.remove_columns() 方法 删除不再需要的字段,使用 Dataset.filter() 方法 筛选符合条件的样本,使用 Dataset.train_test_split() 方法 将单一数据集划分为训练集和验证集。

  我们还可以使用 Dataset.map() 方法与 tokenizer 配合,将原始文本批量编码为模型可用的输入格式(如 input_ids、attention_mask、token_type_ids 等)。 Dataset.map() 方法支持对整个数据集中的每一条样本或每一批样本进行统一处理,常用于文本编码(tokenizer)和数据字段转换。

Dataset.map(
    function: ((...) -> Unknown) | None = None,                                 # 要应用于数据集中每个样本(或批次)的函数
    with_indices: bool = False,                                                 # 是是否将样本的索引作为额外参数传递给 function
    with_rank: bool = False,                                                    # 是否将当前进程的编号作为额外参数传递给 function
    input_columns: str | list[str] | None = None,                               # 指定要传递给 function 的列名
    batched: bool = False,                                                      # 是否以批次方式处理数据
    batch_size: int | None = 1000,                                              # 当 batched=True 时,每个批次包含的样本数量
    drop_last_batch: bool = False,                                              # 当 batched=True 时,是否丢弃最后一批不足 batch_size 的样本
    remove_columns: str | list[str] | None = None,                              # 指定在应用映射并更新样本前,需要从数据集中移除的列名
    keep_in_memory: bool = False,                                               # 是否将映射后的数据集保留在内存中,而不是写入缓存文件
    load_from_cache_file: bool | None = None,                                   # 是否尝试从缓存文件中加载之前相同计算的结果
    cache_file_name: str | None = None,                                         # 为缓存文件提供自定义的名称,而不是使用自动生成的文件名
    writer_batch_size: int | None = 1000,                                       # 写入缓存文件时,每批写入的行数
    features: Features | None = None,                                           # 为缓存文件指定特定的 Features 结构,而不是使用自动生成的
    disable_nullable: bool = False,                                             # 是否禁止在数据表中使用空值
    fn_kwargs: dict[Unknown, Unknown] | None = None,                            # 传递给 function 的关键字参数字典
    num_proc: int | None = None,                                                # 生成缓存时使用的最大进程数,用于多进程加速
    suffix_template: str = "_{rank:05d}_of_{num_proc:05d}",                     # 当指定了 cache_file_name 时,此模板会作为后缀添加到每个基础文件名的末尾。格式字符串必须包含 rank 和 num_proc 字段。
    new_fingerprint: str | None = None,                                         # 数据集在变换后的新指纹
    desc: str | None = None,                                                    # 用于显示进度条的描述信息
    try_original_type: bool | None = True,                                      # 是否尝试使用原始数据类型
    on_mixed_types: Literal['use_json'] | None = "use_json"                     # 当数据集中包含不同类型的列时,如何处理
) -> Dataset
import os

os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"                             # 镜像地址

# 必须到导入模块前设置环境变量,否则会报错
from transformers import AutoTokenizer

from transformers import AutoTokenizer

from datasets import load_dataset

HUGGING_FACE_DRIECTORY = os.path.join(os.getcwd(), "assets", "hugging_face")
DATA_DRIECTORY = os.path.join(os.getcwd(), "assets", "data")

def dataset_encode(example):
    inputs = tokenizer(example["review"], padding="max_length", max_length=128, truncation=True)
    return inputs

if __name__ == "__main__":
    file_path = os.path.join(DATA_DRIECTORY, "online_shopping_10_cats.csv")
    dataset_dict = load_dataset("csv", data_files=file_path)
    
    tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-chinese", cache_dir=HUGGING_FACE_DRIECTORY)

    dataset = dataset_dict["train"]                                             # 获取数据集
    # features: {'cat': Value('large_string'), 'label': Value('int64'), 'review': Value('large_string')}
    print(f"features: {dataset.features}")
    print(f"length: {len(dataset)}")                                            # length: 62774

    dataset = dataset.filter(lambda x: x["review"] is not None)                 # 筛选出评论不为空的样本
    # features: {'cat': Value('large_string'), 'label': Value('int64'), 'review': Value('large_string')}
    print(f"features: {dataset.features}")
    print(f"length: {len(dataset)}")                                            # length: 62773

    dataset = dataset.filter(lambda x: x["cat"] == "书籍")                       # 筛选出书籍分类
    # features: {'cat': Value('large_string'), 'label': Value('int64'), 'review': Value('large_string')}
    print(f"features: {dataset.features}")
    print(f"length: {len(dataset)}")                                            # length: 3851

    dataset = dataset.remove_columns(["cat"])                                   # 删除分类列
    # features: {'label': Value('int64'), 'review': Value('large_string')}
    print(f"features: {dataset.features}")
    print(f"length: {len(dataset)}")                                            # length: 3851

    dataset_dict = dataset.train_test_split(test_size=0.2)                      # 划分训练集和测试集
    train_dataset = dataset_dict["train"]
    test_dataset = dataset_dict["test"]
    print(f"train_dataset length: {len(train_dataset)}")                        # train_dataset length: 3080
    print(f"test_dataset length: {len(test_dataset)}")                          # test_dataset length: 771

    train_dataset = train_dataset.map(dataset_encode, batched=True)             # 对训练集进行编码
    """
    {
        'label': 1, 
        'review': "……", 
        'input_ids': [……], 
        'token_type_ids': [……], 
        'attention_mask': [……]
    }
    """
    print(train_dataset[0])

4.4、保存数据集

  我们可以将处理后的数据可保存到本地,供后续训练或复用,避免重复预处理。Datasets 提供了多种保存方式,适用于不同场景:

数据格式 保存方法 适用对象
Arrow save_to_disk() Dataset 或 DatasetDict
CSV to_csv() 仅限 Dataset
JSON to_json() 仅限 Dataset

  Arrow 格式是 Hugging Face 官方推荐的数据持久化方式,既支持单个 Dataset 也支持多个子集的DatasetDict。我们可以使用 save_to_disk() 方法将数据保存为 Arrow 格式,使用 load_from_disk() 方法读取 Arrow 格式的数据。

import os

os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"                             # 镜像地址

# 必须到导入模块前设置环境变量,否则会报错
from transformers import AutoTokenizer

from datasets import load_dataset, load_from_disk

HUGGING_FACE_DRIECTORY = os.path.join(os.getcwd(), "assets", "hugging_face")
DATA_DRIECTORY = os.path.join(os.getcwd(), "assets", "data")

def dataset_encode(example):
    inputs = tokenizer(example["review"], padding="max_length", max_length=128, truncation=True)
    return inputs

if __name__ == "__main__":
    file_path = os.path.join(DATA_DRIECTORY, "online_shopping_10_cats.csv")
    dataset_dict = load_dataset("csv", data_files=file_path)

    tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-chinese", cache_dir=HUGGING_FACE_DRIECTORY)

    dataset = dataset_dict["train"]                                             # 获取数据集

    dataset = dataset.filter(lambda x: x["review"] is not None)                 # 筛选出评论不为空的样本

    dataset_dict = dataset.train_test_split(test_size=0.2)                      # 划分训练集和测试集
    train_dataset = dataset_dict["train"]
    test_dataset = dataset_dict["test"]

    directory = os.path.join(DATA_DRIECTORY, "dataset")
    dataset_dict.save_to_disk(directory)                                        # 保存数据集

    """
    DatasetDict({
        train: Dataset({
            features: ['cat', 'label', 'review'],
            num_rows: 50218
        })
        test: Dataset({
            features: ['cat', 'label', 'review'],
            num_rows: 12555
        })
    })
    """
    dataset_dict = load_from_disk(directory)                                    # 加载数据集
    print(dataset_dict)

    """
    Dataset({
        features: ['cat', 'label', 'review'],
        num_rows: 50218
    })
    """
    train_dataset = load_from_disk(os.path.join(directory, "train"))
    print(train_dataset)

4.5、集成 Dataloader

  经过预处理的 datasets.Dataset 对象可以直接与PyTorch的 DataLoader 集成使用。虽然它并非继承自 torch.utils.data.Dataset 类,但由于实现了 __len__() 和 __getitem__() 这两个核心接口,因此能够被 DataLoader 正确识别并进行批量迭代。在使用前,需要通过 Dataset.set_format() 方法将指定字段转换为张量格式以适配模型输入。

Dataset.set_format(
    type: str | None = None,                                                    # 指定输出数据的格式类型
    columns: list[Unknown] | None = None,                                       # 指定需要被格式化的列名列表
    output_all_columns: bool = False,                                           # 控制在输出时是否保留未格式化的列
    **format_kwargs: Unknown
) -> None

  参数 type 用于 指定输出数据的格式类型,可选值包括 "numpy"、"torch"、"tensorflow"、"pandas"、"arrow" 或 None。如果设置为 None 时,__getitem__() 将返回 原生的 Python 对象。如果设置为其他格式时,数据在返回时会被转换成对应的格式。

  参数 columns 用于 指定需要被格式化的列名列表。当为 None 时,__getitem__() 默认返回数据集中的所有列。当指定了列名列表后,只有这些列的数据会被应用指定的 type 格式进行转换。

import os

os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"                             # 镜像地址

from typing import cast

# 必须到导入模块前设置环境变量,否则会报错
from transformers import AutoTokenizer
from torch.utils.data import DataLoader
from torch.utils.data import Dataset as TorchDataset

from datasets import load_dataset

HUGGING_FACE_DRIECTORY = os.path.join(os.getcwd(), "assets", "hugging_face")
DATA_DRIECTORY = os.path.join(os.getcwd(), "assets", "data")

def dataset_encode(examples):
    inputs = tokenizer(examples["review"], padding="max_length", max_length=128, truncation=True)
    return inputs

if __name__ == "__main__":
    file_path = os.path.join(DATA_DRIECTORY, "online_shopping_10_cats.csv")
    dataset_dict = load_dataset("csv", data_files=file_path)

    tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-chinese", cache_dir=HUGGING_FACE_DRIECTORY)

    """
    Dataset({
        features: ['cat', 'label', 'review'],
        num_rows: 62774
    })
    """
    dataset = dataset_dict["train"]                                             # 获取数据集
    print(dataset)

    dataset = dataset.filter(lambda x: x["review"] is not None)                 # 筛选出评论不为空的样本

    """
    DatasetDict({
        train: Dataset({
            features: ['cat', 'label', 'review'],
            num_rows: 50218
        })
        test: Dataset({
            features: ['cat', 'label', 'review'],
            num_rows: 12555
        })
    })
    """
    dataset_dict = dataset.train_test_split(test_size=0.2)                      # 划分训练集和测试集
    print(dataset_dict)

    # Tokenization:将 review 文本转换为 input_ids 和 attention_mask
    dataset_dict = dataset_dict.map(
        dataset_encode, batched=True,
        remove_columns=["review", "cat"]                                        # 移除原始文本列,保留 label
    )

    """
    DatasetDict({
        train: Dataset({
            features: ['label', 'input_ids', 'token_type_ids', 'attention_mask'],
            num_rows: 50218
        })
        test: Dataset({
            features: ['label', 'input_ids', 'token_type_ids', 'attention_mask'],
            num_rows: 12555
        })
    })
    """
    print(dataset_dict)

    train_dataset = dataset_dict["train"]
    test_dataset = dataset_dict["test"]

    """
    Dataset({
        features: ['label', 'input_ids', 'token_type_ids', 'attention_mask'],
        num_rows: 50218
    })
    """
    print(train_dataset)

    """
    {
        'label': 0, 
        'input_ids': [……], 
        'token_type_ids': [……], 
        'attention_mask': [……]
    }
    """
    print(train_dataset[0])


    train_dataset.set_format(
        type="torch",                                                           # 指定输出为PyTorch张量
        columns=["input_ids", "attention_mask", "label"]                        # 需要转换的字段
    )

    """
    {
        'label': tensor(1), 
        'input_ids': tensor([……]), 
        'attention_mask': tensor([……])
    }
    """
    print(train_dataset[0])

    train_dataloader = DataLoader(cast(TorchDataset, train_dataset), batch_size=32, shuffle=True)

    for data_bated_dict in train_dataloader:
        """
        {
            'label': tensor([0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0]), 
            'input_ids': tensor([[ 101, 1355, 4385,  ...,    0,    0,    0],
                                 [ 101,  743, 2130,  ...,    0,    0,    0],
                                 [ 101, 3297, 1927,  ...,    0,    0,    0],
                                 ...,
                                 [ 101, 5885, 4281,  ...,    0,    0,    0],
                                 [ 101,  691, 6205,  ...,    0,    0,    0],
                                 [ 101, 6432, 1962,  ...,    0,    0,    0]]), 
            'attention_mask': tensor([[1, 1, 1,  ..., 0, 0, 0],
                                      [1, 1, 1,  ..., 0, 0, 0],
                                      [1, 1, 1,  ..., 0, 0, 0],
                                      ...,
                                      [1, 1, 1,  ..., 0, 0, 0],
                                      [1, 1, 1,  ..., 0, 0, 0],
                                      [1, 1, 1,  ..., 0, 0, 0]])
        }
        """
        print(data_bated_dict)
        break

五、电商评论情感分类案例

  我们可以从 GitHub 中下载电商评论数据集: https://github.com/InsaneLife/ChineseNLPCorpus 。如果我们无法访问可以使用 瓦特工具箱(Steam++官网) - Watt Toolkit 加速访问。

  我们点击 online_shopping_10_cats 那行的超链接跳转到电商评论,然后,我们下载电商评论数据集的压缩包。它的下载链接如下: https://github.com/SophonPlus/ChineseNlpCorpus/blob/master/datasets/online_shopping_10_cats/online_shopping_10_cats.zip 。

跳转电商评论数据集

下载电商评论的数据集压缩包

  我们可以在终端中使用 pip 安装 pandas、sklearn 机器学习库 和 jieba。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载。

  数据预处理:

import os

os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"                             # 镜像地址

DATA_DRIECTORY = os.path.join(os.getcwd(), "assets", "data")
DATASET_DRIECTORY = os.path.join(os.getcwd(), "assets", "dataset")
HUGGING_FACE_DRIECTORY = os.path.join(os.getcwd(), "assets", "hugging_face")
MODEL_DRIECTORY = os.path.join(os.getcwd(), "assets", "models")

MAX_SQE_LEN = 128
import os
import shutil

from datasets import ClassLabel
from transformers import AutoTokenizer

from datasets import load_dataset

def preprocess_data(file_path, test_size):
    dataset = load_dataset("csv", data_files=file_path)["train"]                # 加载数据集

    dataset = dataset.remove_columns(["cat"])                                   # 移除分类列
    dataset = dataset.filter(lambda x: x["review"] is not None)                 # 过滤掉评论为空的样本

    # 转换分类列的类型为 ClassLabel,names[0] 为 0,names[1] 为 1
    # 如果 label 原本值就是 0 和 1,那么转换后 0 映射到 negative,1 映射到 positive
    dataset = dataset.cast_column("label", ClassLabel(names=["negative", "positive"]))

    # 划分训练集与测试集,stratify_by_column 参数指定所选列进行分层抽样,要求该列必须是分类列
    dataset_dict = dataset.train_test_split(test_size, stratify_by_column="label")

    # 加载 BERT 模型的分词器
    tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-chinese", cache_dir=HUGGING_FACE_DRIECTORY)
    print("加载 BERT 模型的分词器成功")

    # 构建数据集
    dataset_dict = dataset_dict.map(lambda examples: dataset_encode(tokenizer, examples), batched=True, remove_columns=["review", "label"])
    print("构建数据集成功")

    file_path = os.path.join(DATASET_DRIECTORY, "online_shopping_dataset")

    # 如果旧数据集存在,先删除(避免 Windows 上 Arrow 文件损坏导致写入失败)
    if os.path.exists(file_path):
        shutil.rmtree(file_path)    
    
    if not os.path.exists(DATASET_DRIECTORY):
        os.makedirs(DATASET_DRIECTORY)

    # 保存数据集,不推荐保存为 Arrow 文件,因为 Arrow 文件在 Windows 上可能会损坏
    file_path = os.path.join(DATASET_DRIECTORY, "online_shopping_dataset")
    dataset_dict.save_to_disk(file_path)

    print("保存数据集成功")

def dataset_encode(tokenizer, examples):
    inputs = tokenizer(examples["review"], padding="max_length", max_length=MAX_SQE_LEN, truncation=True)
    inputs["labels"] = examples["label"]
    return inputs

if __name__ == "__main__":
    test_size = 0.2
    file_path = os.path.join(DATA_DRIECTORY, "online_shopping_10_cats.csv")

    preprocess_data(file_path, test_size)

  该案例采用基于 Bert 模型结构来实现 “电商评论情感分类” 功能。最后,我们还需要一个线性层用于输出评论为正向情感分析的倾向得分(经过 Sigmod() 函数后,大于 0.5 判断为 正情感倾向,小于等于 0.5 判断为 负情感倾向)。

  创建模型:

from torch import nn
from transformers import AutoModel

class ReviewAnalyzeModel(nn.Module):
    def __init__(self):
        super().__init__()

        file_path = os.path.join(HUGGING_FACE_DRIECTORY, "bert-base-chinese")
        self.bert = AutoModel.from_pretrained(file_path)

        # 输出层 [输入维度(模型输出维度), 输出维度(二分类)]
        self.liner = nn.Linear(self.bert.config.hidden_size, 1)

    def forward(self, input_ids, attention_mask, token_type_ids):
        # shape: [批量大小, 序列长度]
        output = self.bert(input_ids, attention_mask, token_type_ids)

        # last_hidden_state.shape: [批量大小, 序列长度, 隐藏层维度]
        last_hidden_state = output.last_hidden_state

        cls_hidden_state = last_hidden_state[:, 0, :]

        # Liner 层保留输入张量的所有维度(除最后一维外全部不变),仅将最后一维(特征维度)替换为 out_features
        # squeeze(dim) 删除张量(Tensor)中长度为 1 的指定维度(即移除大小为 1 的轴)
        # 如果该维度大小为 1:该维度被移除,张量总维度数减 1
        # 如果该维度大小不为 1:操作无效(PyTorch 会抛出 ValueError 或 RuntimeError)
        # 如果不指定 dim 参数:删除所有长度为 1 的维度
        # 如果 dim 为负索引,dim=-1 指向最后一个维度,dim=-2 指向倒数第二个维度,以此类推
        # output.shape: [批量大小]
        output = self.liner(cls_hidden_state).squeeze(-1)
        
        return output, cls_hidden_state

  模型单轮训练:

import torch

def model_train_one_epoch(model, device, data_loader, criterion, optimizer):
    model.train()                                                               # 训练模式
    total_loss = 0.0

    for data_batch_dict in data_loader:
        """
        {
            'labels': tensor([0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0]), 
            'input_ids': tensor([[ 101, 1355, 4385,  ...,    0,    0,    0],
                                 [ 101,  743, 2130,  ...,    0,    0,    0],
                                 [ 101, 3297, 1927,  ...,    0,    0,    0],
                                 ...,
                                 [ 101, 5885, 4281,  ...,    0,    0,    0],
                                 [ 101,  691, 6205,  ...,    0,    0,    0],
                                 [ 101, 6432, 1962,  ...,    0,    0,    0]]), 
            'attention_mask': tensor([[1, 1, 1,  ..., 0, 0, 0],
                                      [1, 1, 1,  ..., 0, 0, 0],
                                      [1, 1, 1,  ..., 0, 0, 0],
                                      ...,
                                      [1, 1, 1,  ..., 0, 0, 0],
                                      [1, 1, 1,  ..., 0, 0, 0],
                                      [1, 1, 1,  ..., 0, 0, 0]])
        }
        """
        inputs = {name: tensor.to(device) for name, tensor in data_batch_dict.items()}
        labels = inputs.pop("labels").to(dtype=torch.float)

        outputs, hn = model(**inputs)                                           # 模型前向传播
        
        # BCEWithLogitsLoss() 二分类损失函数
        # 如果目标值(y)的形状为 N,则预测值(output)的形状也为 [N]
        loss = criterion(outputs, labels)                                       # 计算损失
        optimizer.zero_grad()                                                   # 清空梯度
        loss.backward()                                                         # 反向传播
        optimizer.step()                                                        # 优化器更新参数

        # x.shape[0] 是当前批次的样本数量
        # 乘以 x.shape[0] 是为了消除批次大小不一致(尤其是最后一个不完整批次)带来的统计偏差
        total_loss += loss.item() * labels.shape[0]                             # 计算当前批次的损失
        
    # len(dataloader.dataset) 返回数据集的样本数量
    # len(dataloader) 返回数据加载器的批次数量
    return total_loss / len(data_loader.dataset)                                # 返回平均损失

  训练模型,情感分析为 二分类问题,所以损失函数采用 BCEWithLogitsLoss(),其结合了 softmax() 激活函数和二分类交叉熵计算。

BATCH_SIZE = 48

LEARNING_RATE = 1e-5
EPOCHS = 10
import os

import torch

from typing import cast

from torch.utils.data import Dataset as TorchDataset
from torch.utils.data import DataLoader

from datasets import load_from_disk

def model_train(model, device, data_loader, learning_rate, epochs):
    model.to(device)                                                            # 指定设备

    criterion = torch.nn.BCEWithLogitsLoss()                                    # 二分类损失函数
    optimizer = torch.optim.Adam(model.parameters(), learning_rate)             # 创建优化器

    # 开始训练
    for epoch in range(epochs):
        print(f"开始第 {epoch + 1} 次训练")
        loss = model_train_one_epoch(model, device, data_loader, criterion, optimizer)
        print(f"epoch: {epoch + 1:0>2},loss: {loss:.4f}")
        
    print("训练完成,开始保存模型")
    
    if not os.path.exists(MODEL_DRIECTORY):
        os.makedirs(MODEL_DRIECTORY)

    file_path = os.path.join(MODEL_DRIECTORY, "review_analyze_model_by_bert.pth")
    torch.save(model.state_dict(), file_path)                                   # 保存模型

    print("模型保存完成")

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

    file_path = os.path.join(DATASET_DRIECTORY, "online_shopping_dataset", "train")
    train_dataset = load_from_disk(file_path)                                   # 加载训练集
    train_dataset.set_format(type="torch")                                      # 设置数据集格式为 torch
    train_loader = DataLoader(cast(TorchDataset, train_dataset), BATCH_SIZE, True)  # 创建数据加载器
    print("加载训练集成功")

    model = ReviewAnalyzeModel()                                                # 模型
    print("创建模型成功")
    
    model_train(model, device, train_loader, LEARNING_RATE, EPOCHS)

  模型单批次预测:

import torch

def model_predict_one_batch(model, inputs):
    model.eval()                                                                # 评估模式
    
    with torch.no_grad():                                                       # 禁用梯度计算
        # output.shape: [批量大小)]
        outputs, hn = model(**inputs)                                           # 模型前向传播

    return torch.sigmoid(outputs).tolist()

  模型评估:

import os

import torch

from torch.utils.data import DataLoader

def model_evaluate(model, device, data_loader):
    total_count = 0
    correct_count = 0

    model.to(device)                                                            # 指定设备

    for data_batch_dict in data_loader:
        inputs = {name: tensor.to(device) for name, tensor in data_batch_dict.items()}
        labels = inputs.pop("labels").to(dtype=torch.float)
        
        targets = labels.tolist()
        results = model_predict_one_batch(model, inputs)

        # 统计准确率
        for probability, target in zip(results, targets):
            predict_label = 1 if probability > 0.5 else 0
            correct_count += (1 if predict_label == target else 0)
            total_count += 1

    accuracy = correct_count / total_count

    print(f"准确率:{accuracy:.4f}")

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

    file_path = os.path.join(DATASET_DRIECTORY, "online_shopping_dataset", "test")
    test_dataset = load_from_disk(file_path)                                    # 加载测试集
    test_dataset.set_format(type="torch")                                       # 设置数据集格式为 torch
    test_loader = DataLoader(cast(TorchDataset, test_dataset), BATCH_SIZE)      # 创建数据加载器
    print("加载测试集成功")
    
    model = ReviewAnalyzeModel().to(device)
    print("创建模型成功")
    
    file_path = os.path.join(MODEL_DRIECTORY, "review_analyze_model_by_bert.pth")
    model.load_state_dict(torch.load(file_path))                                # 加载模型参数
    print("加载模型参数成功")

    model_evaluate(model, device, test_loader)

  模型预测:

import os

from transformers import AutoTokenizer

def model_predict(model, device, text, tokenizer):
    model.to(device)                                                            # 指定设备

    inputs = tokenizer(text, padding="max_length", max_length=MAX_SQE_LEN, truncation=True, return_tensors="pt")
    inputs = {name: tensor.to(device) for name, tensor in inputs.items()}

    results = model_predict_one_batch(model, inputs)

    return results[0]

def model_run_predict(model, device, tokenizer):
    print("欢迎使用评论情感分析模型,输入q或quit退出")

    while True:
        text = input("> ")
        if text.strip() in ["q", "quit"]:
            break

        if text.strip() == "":
            continue

        result = model_predict(model, device, text, tokenizer)
        print(f"输入的评论:{text}")
        print(f"模型预测的情感:{'正面' if result > 0.5 else '负面'}(置信度:{result if result > 0.5 else 1 - result})")

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

    # 加载 BERT 模型的分词器
    tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-chinese", cache_dir=HUGGING_FACE_DRIECTORY)
    print("加载分词器成功")

    model = ReviewAnalyzeModel().to(device)
    print("创建模型成功")
    
    file_path = os.path.join(MODEL_DRIECTORY, "review_analyze_model_by_bert.pth")
    model.load_state_dict(torch.load(file_path))                                # 加载模型参数
    print("加载模型参数成功")
    
    model_run_predict(model, device, tokenizer)

  我们还可以使用 AutoModelForSequenceClassification 模型类优化代码,修改模型单轮训练的代码:

def model_train_one_epoch(model, device, data_loader, optimizer):
    model.train()                                                               # 训练模式
    total_loss = 0.0

    for data_batch_dict in data_loader:
        inputs = {name: tensor.to(device) for name, tensor in data_batch_dict.items()}

        outputs = model(**inputs)                                               # 模型前向传播
        
        loss = outputs.loss                                                     # 获取损失
        optimizer.zero_grad()                                                   # 清空梯度
        loss.backward()                                                         # 反向传播
        optimizer.step()                                                        # 优化器更新参数
        

        # x.shape[0] 是当前批次的样本数量
        # 乘以 x.shape[0] 是为了消除批次大小不一致(尤其是最后一个不完整批次)带来的统计偏差
        total_loss += loss.item() * inputs["labels"].shape[0]                   # 计算当前批次的损失
        
    # len(dataloader.dataset) 返回数据集的样本数量
    # len(dataloader) 返回数据加载器的批次数量
    return total_loss / len(data_loader.dataset)                                # 返回平均损失

  修改模型训练的代码:

import os

import torch

from typing import cast

from transformers import AutoModelForSequenceClassification
from torch.utils.data import Dataset as TorchDataset
from torch.utils.data import DataLoader

from datasets import load_from_disk

def model_train(model, device, data_loader, learning_rate, epochs):
    model.to(device)                                                            # 指定设备

    optimizer = torch.optim.Adam(model.parameters(), learning_rate)             # 创建优化器

    # 开始训练
    for epoch in range(epochs):
        print(f"开始第 {epoch + 1} 次训练")
        loss = model_train_one_epoch(model, device, data_loader, optimizer)
        print(f"epoch: {epoch + 1:0>2},loss: {loss:.4f}")
        
    print("训练完成,开始保存模型")
    
    if not os.path.exists(MODEL_DRIECTORY):
        os.makedirs(MODEL_DRIECTORY)
  
    file_path = os.path.join(MODEL_DRIECTORY, "review_analyze_model_by_bert_for_sequence_class")
    model.save_pretrained(file_path)                                            # 保存模型

    print("模型保存完成")

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

    file_path = os.path.join(DATASET_DRIECTORY, "online_shopping_dataset", "train")
    train_dataset = load_from_disk(file_path)                                   # 加载训练集
    train_dataset.set_format(type="torch")                                      # 设置数据集格式为 torch
    train_loader = DataLoader(cast(TorchDataset, train_dataset), BATCH_SIZE, True)  # 创建数据加载器
    print("加载训练集成功")

    # 模型
    model = AutoModelForSequenceClassification.from_pretrained(
        "google-bert/bert-base-chinese",
        cache_dir=HUGGING_FACE_DRIECTORY
    )
    print("创建模型成功")
    
    model_train(model, device, train_loader, LEARNING_RATE, EPOCHS)

  修改单批次预测的代码:

import torch

def model_predict_one_batch(model, inputs):
    model.eval()                                                                # 评估模式
    
    with torch.no_grad():                                                       # 禁用梯度计算
        output = model(**inputs)                                                # 模型前向传播
        # logists.shape: [批量大小, 标签数(2)]
        logists = output.logits

    return torch.argmax(logists, dim=1).tolist()

  修改模型评估的代码:

import os

import torch

from torch.utils.data import DataLoader
from transformers import AutoModelForSequenceClassification

def model_evaluate(model, device, data_loader):
    total_count = 0
    correct_count = 0

    model.to(device)                                                            # 指定设备

    for data_batch_dict in data_loader:
        inputs = {name: tensor.to(device) for name, tensor in data_batch_dict.items()}
        labels = inputs.pop("labels").to(dtype=torch.float)
        
        targets = labels.tolist()
        results = model_predict_one_batch(model, inputs)

        # 统计准确率
        for probability, target in zip(results, targets):
            correct_count += (1 if probability == target else 0)
            total_count += 1

    accuracy = correct_count / total_count

    print(f"准确率:{accuracy:.4f}")

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

    file_path = os.path.join(DATASET_DRIECTORY, "online_shopping_dataset", "test")
    test_dataset = load_from_disk(file_path)                                    # 加载测试集
    test_dataset.set_format(type="torch")                                       # 设置数据集格式为 torch
    test_loader = DataLoader(cast(TorchDataset, test_dataset), BATCH_SIZE)      # 创建数据加载器
    print("加载测试集成功")

    file_path = os.path.join(MODEL_DRIECTORY, "review_analyze_model_by_bert_for_sequence_class")
    model = AutoModelForSequenceClassification.from_pretrained(file_path).to(device)
    print("创建模型成功")

    model_evaluate(model, device, test_loader)

  修改模型预测的代码:

import os

from transformers import AutoTokenizer
from transformers import AutoModelForSequenceClassification

def model_predict(model, device, text, tokenizer):
    model.to(device)                                                            # 指定设备

    inputs = tokenizer(text, padding="max_length", max_length=MAX_SQE_LEN, truncation=True, return_tensors="pt")
    inputs = {name: tensor.to(device) for name, tensor in inputs.items()}

    results = model_predict_one_batch(model, inputs)

    return results[0]

def model_run_predict(model, device, tokenizer):
    print("欢迎使用评论情感分析模型,输入q或quit退出")

    while True:
        text = input("> ")
        if text.strip() in ["q", "quit"]:
            break

        if text.strip() == "":
            continue

        result = model_predict(model, device, text, tokenizer)
        print(f"输入的评论:{text}")
        print(f"模型预测的情感:{'正面' if result == 1 else '负面'}")

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

    # 加载 BERT 模型的分词器
    tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-chinese", cache_dir=HUGGING_FACE_DRIECTORY)
    print("加载分词器成功")

    file_path = os.path.join(MODEL_DRIECTORY, "review_analyze_model_by_bert_for_sequence_class")
    model = AutoModelForSequenceClassification.from_pretrained(file_path).to(device)
    print("创建模型成功")
    
    model_run_predict(model, device, tokenizer)
posted @ 2026-10-18 22:15  星光映梦  阅读(3)  评论(0)    收藏  举报