大模型开发之huggingface Transformers库应用

发布于 3 天前 38 次阅读 预计阅读时间: 24 分钟


语言模型

掩码语言模型.ipynb

因果语言模型.ipynb

这两个 Notebook 分别介绍了当今自然语言处理(NLP)领域最基础、最重要的两种模型:掩码语言模型(Masked Language Model, MLM)因果语言模型(Causal Language Model, CLM)。理解了它们,就理解了 BERT 和 GPT 这两大模型家族的根本原理。


第一部分:掩码语言模型 (Masked Language Model, MLM)

这是 masked_lm.ipynb 文件所展示的技术。

核心思想 (The Big Picture)

可以将 MLM 理解为给模型做 “完形填空” 练习。

  • 做法:从一句话中,随机地“挖掉”(Mask)一些词,然后让模型去猜测被挖掉的词应该是什么。
  • 学习目标:为了能准确地猜出被挖掉的词,模型必须深刻理解这个词的上下文,也就是它左边和右边的词是什么,以及它们组合起来的整体含义。
  • 特点:这种训练方式让模型能够双向理解文本(Bidirectional)。比如在“今天天气[MASK]不错”中,模型既能看到左边的“天气”,也能看到右边的“不错”,从而推断出中间是“真”。
  • 代表模型:大名鼎鼎的 BERT 就是用这种方式训练的。因此,MLM 模型特别擅长做理解类的任务,比如文本分类、情感分析、句子关系判断等。

具体例子

  • 原始句子: 西安交通大学博物馆是一座位于西安交通大学的博物馆
  • “完形填空”练习: 西安交通[MASK][MASK]博物馆是一座位于西安交通大学的博物馆
  • 模型的目标: 预测第一个 [MASK] 是 “大”,第二个 [MASK] 是 “学”。

代码细节解读 (masked_lm.ipynb)

  1. Step 3: 数据集处理
    • 关键代码: DataCollatorForLanguageModeling(tokenizer, mlm=True, mlm_probability=0.15)
    • 作用解读: 这是整个 MLM 训练的魔法核心。DataCollator 是一个数据整理器,当 mlm=True 时,它会在把数据喂给模型之前,自动帮你完成“完形填空”的出题工作。mlm_probability=0.15 的意思是,它会随机将数据中 15% 的词(Token)替换成一个特殊的 [MASK] 标记。
  2. Step 4: 创建模型
    • 关键代码: AutoModelForMaskedLM.from_pretrained("hfl/chinese-macbert-base")
    • 作用解读: 这里我们加载了一个专门用于 MLM 任务的模型结构。hfl/chinese-macbert-base 是一个预训练好的 BERT 类模型。AutoModelForMaskedLM 会确保这个模型有一个“头部”(prediction head),这个头部的作用就是来预测那些被 [MASK] 的位置应该是什么词。
  3. Step 7: 模型训练
    • 关键代码: trainer.train()
    • 作用解读: Trainer 会使用前面定义的 DataCollator 不断地从 wiki_cn_filtered 数据集中抽取文本,制作成“完形填空”题,然后喂给模型去学习。模型在成千上万次的练习中,不断提升自己联系上下文、理解语义的能力。
  4. Step 8: 模型推理
    • 关键代码: pipe = pipeline("fill-mask", ...)
    • 作用解读: fill-mask 是专门为 MLM 模型设计的推理任务。当你给它一个带有 [MASK] 的句子时,它就会利用自己学到的知识,帮你把“空”填上。
    • 输入输出示例:
      • 输入: pipe("西安交通[MASK][MASK]博物馆...")
      • 可能的输出: 模型会给出概率最高的几个填充结果,最好的那个很可能就是 西安交通大学博物馆

第二部分:因果语言模型 (Causal Language Model, CLM)

这是 causal_lm.ipynb 文件所展示的技术。

核心思想 (The Big Picture)

可以将 CLM 理解为让模型玩 “文字接龙” 游戏。

  • 做法:给模型一句话的前半部分,让它预测下一个词应该是什么。
  • 学习目标:为了能准确地预测下一个词,模型必须理解前面所有句子的含义,并根据这个含义生成符合逻辑和语法的后续内容。
  • 特点:这种训练方式是单向的(Unidirectional)。在预测第 N 个词时,模型只能看到第 1 到 N-1 个词,它看不到后面的内容(因为还没有生成)。
  • 代表模型GPT 系列(包括 ChatGPT)和 BLOOM 就是用这种方式训练的。因此,CLM 模型特别擅长做生成类的任务,比如写文章、写代码、聊天对话等。

具体例子

  • 输入: 今天天气真
  • 模型的目标: 预测下一个最可能的词是 不错
  • 继续接龙: 当模型预测出 不错 后,新的输入就变成了 今天天气真不错,模型再根据这个新的句子,预测再下一个词(可能是一个句号 或者 )。

代码细节解读 (causal_lm.ipynb)

  1. Step 3: 数据集处理
    • 关键代码: DataCollatorForLanguageModeling(tokenizer, mlm=False)
    • 作用解读: 这里的 mlm=False 是与 MLM 最核心的区别。它告诉数据整理器,不要做“完形填空”,而是为“文字接龙”准备数据。它会巧妙地将输入文本和标签文本进行移位,让模型在每个时间步都去预测下一个真实的词。
    • 另一个细节: contents = [e + tokenizer.eos_token for e in examples["completion"]]。这里为每段文本末尾加上了一个特殊的结束符 eos_token,这能让模型学会什么时候应该“停笔”。
  2. Step 4: 创建模型
    • 关键代码: AutoModelForCausalLM.from_pretrained("Langboat/bloom-389m-zh")
    • 作用解读: 这里加载的是一个专门用于 CLM 任务的模型结构。Langboat/bloom-389m-zh 是一个中文的 BLOOM 模型,它天生就是用来做文本生成的。AutoModelForCausalLM 加载的正是这种“文字接龙”式的模型。
  3. Step 7: 模型训练
    • 关键代码: trainer.train()
    • 作用解读: 在训练过程中,模型会不断地看到句子的开头,并努力预测下一个词。通过在海量 wiki_cn_filtered 文本上进行这种练习,模型逐渐掌握了语言的规律、事实知识以及生成流畅文本的能力。
  4. Step 8: 模型推理
    • 关键代码: pipe = pipeline("text-generation", ...)
    • 作用解读: text-generation 是专门为 CLM 模型设计的推理任务。你给它一个开头(prompt),它就会像玩“文字接龙”一样,帮你把后面的内容续写出来。
    • 输入输出示例:
      • 输入: pipe("西安交通大学博物馆是一座位于西安", ...)
      • 可能的输出: 模型可能会续写出 交通大学兴庆校区的一所综合类博物馆,馆藏涵盖历史、艺术、科技等多个领域。 这样一段通顺的话。

总结与对比

特性 掩码语言模型 (MLM) 因果语言模型 (CLM)
核心任务 完形填空 (Fill-in-the-Blanks) 文字接龙 (Text Generation)
上下文 双向 (能看到左右两边的词) 单向 (只能看到左边的词)
典型模型 BERT, RoBERTa, MacBERT GPT, BLOOM, LLaMA
擅长领域 理解类任务:分类、情感分析、句子匹配 生成类任务:文章写作、对话机器人、代码生成
推理接口 fill-mask text-generation

文本摘要生成

文本摘要生成T5.ipynb

文本摘要生成GLM.ipynb

这两个 Notebook 都旨在完成同一个任务:文本摘要(将一篇长文章,自动浓缩成一句或几句简短的摘要)。但它们采用了两种不同的、非常有代表性的模型架构:T5GLM。理解它们的异同,对于掌握现代 NLP 的生成任务非常有帮助。

核心思想:万物皆可 "Seq2Seq"

在深入代码之前,我们需要理解文本摘要任务的本质。它是一个典型的 序列到序列(Sequence-to-Sequence, Seq2Seq) 任务。

  • 什么是 Seq2Seq? 就是模型接收一个序列(比如长篇文章的文字)作为输入,然后生成另一个序列(比如摘要的文字)作为输出。
  • 模型结构:这类模型通常采用 编码器-解码器(Encoder-Decoder) 架构。
    • 编码器 (Encoder):负责“阅读”并理解输入的长篇文章,将其压缩成一个包含文章核心信息的数学表示(向量)。
    • 解码器 (Decoder):负责根据这个数学表示,“写”出简短的摘要。

T5 和 GLM 都是能够完成 Seq2Seq 任务的强大模型,但它们实现的方式和“哲学”有所不同。


第一部分:基于 T5 的文本摘要

这是 summarization.ipynb 文件所展示的技术。

T5 的核心思想

T5 (Text-to-Text Transfer Transformer) 的设计哲学非常简洁:将所有 NLP 任务都统一为“文本到文本”的格式

  • 做法:通过在输入文本前加上不同的 任务前缀(Prompt),来告诉模型需要执行什么任务。例如:
    • 翻译任务:"translate English to German: ..."
    • 摘要任务:"summarize: ..." (在本例中,为了更适配中文,使用了 "摘要生成: n")
  • 特点:T5 是一个非常经典和标准的 Encoder-Decoder 模型,其训练和使用流程是 Hugging Face 生态中的“标杆”,非常规范和易于理解。

代码细节解读 (summarization.ipynb)

  1. Step 3: 数据处理
    • 关键代码:
      def process_func(exmaples):
          contents = ["摘要生成: n" + e for e in exmaples["content"]]
          inputs = tokenizer(contents, ...)
          labels = tokenizer(text_target=exmaples["title"], ...)
          inputs["labels"] = labels["input_ids"]
          return inputs
    • 作用解读: 这是 T5 模式的核心体现。
      • contents = ["摘要生成: n" + e for e in exmaples["content"]]:我们为每一篇要摘要的文章 content 都加上了任务前缀 "摘要生成: n"。这明确地告诉 T5 模型:“接下来请你做摘要任务!”
      • inputs: 指的是编码器(Encoder)要处理的输入,即“前缀+文章”。
      • labels: 指的是解码器(Decoder)要学习生成的目标,即文章的标题 title(在这里标题被当作是摘要)。
  2. Step 4: 创建模型
    • 关键代码: model = AutoModelForSeq2SeqLM.from_pretrained("Langboat/mengzi-t5-base")
    • 作用解读: AutoModelForSeq2SeqLM 是 Hugging Face 中专门用来加载所有 Encoder-Decoder 架构模型的类。我们加载的 mengzi-t5-base 就是一个经过中文数据预训练的 T5 模型。
  3. Step 5: 创建评估函数
    • 关键代码: from rouge_chinese import Rouge
    • 作用解读: ROUGE 是评估摘要生成质量的标准指标。它的原理是比较模型生成的摘要和人工撰写的标准摘要之间,有多少重叠的字词或片段。分数越高,代表生成的摘要质量越好。
  4. Step 9: 模型推理
    • 关键代码: pipe = pipeline("text2text-generation", ...)
    • 作用解读: text2text-generation 是为 T5 这类模型量身定做的推理管道。
    • 输入输出示例:
      • 输入:
        # 假设 ds["test"][-1]["content"] 是一篇关于...的新闻
        # “苹果公司今天发布了最新的iPhone 18,配备了A20芯片,摄像头性能提升了50%...”
        pipe("摘要生成:n 苹果公司今天发布了最新的iPhone 18...", max_length=64)
      • 可能的输出 (一个字典):
        [{'generated_text': '苹果发布新款iPhone 18 性能大幅提升'}]

        这个输出就是模型自动生成的摘要。


第二部分:基于 GLM 的文本摘要

这是 summarization_glm.ipynb 文件所展示的技术。

GLM 的核心思想

GLM (General Language Model) 是一种更具创新性的架构,它试图统一不同的预训练范式(比如 BERT 的双向理解和 GPT 的单向生成)。

  • 做法:在处理 Seq2Seq 任务时,GLM 采用了一种巧妙的“填空”方式。它将输入和输出拼接在一起,并用特殊的 [MASK] 标记来分隔,然后让模型来“填补” [MASK] 之后的内容。
  • 特点:GLM 的数据处理方式比较特殊,需要模型作者提供的特定代码(trust_remote_code=True)来辅助,但这也展示了其架构的灵活性。

代码细节解读 (summarization_glm.ipynb)

  1. Step 3: 数据处理
    • 关键代码:
      def process_func(exmaples):
          contents = ["摘要生成: n" + e + tokenizer.mask_token for e in exmaples["content"]]
          inputs = tokenizer(...)
          inputs = tokenizer.build_inputs_for_generation(inputs, targets=exmaples['title'], ...)
          return inputs
    • 作用解读: 这是 GLM 与 T5 最大的不同之处。
      • contents = [... + tokenizer.mask_token]:GLM 的输入格式是在文章 content 的末尾加上一个 [MASK] 标记。这个 [MASK] 就像一个信号,告诉模型:“文章我已经读完了,现在请你开始生成摘要”。
      • tokenizer.build_inputs_for_generation(...):这是一个 GLM 特有的函数。它会将“文章”和“摘要”两部分巧妙地整合到同一个输入序列中,并使用一种叫做 position_ids 的机制来区分哪部分是原文(需要阅读),哪部分是摘要(需要生成)。这是 GLM 实现 Seq2Seq 功能的核心。
  2. Step 4 & 7: 创建模型和训练器
    • 关键代码: AutoTokenizer.from_pretrained(..., trust_remote_code=True), AutoModelForSeq2SeqLM.from_pretrained(..., trust_remote_code=True)
    • 作用解读: trust_remote_code=True 参数是必需的。因为它告诉 Hugging Face 库:“这个模型比较特殊,请允许加载并执行模型作者在 Hub 上提供的自定义 Python 代码(比如上面提到的 build_inputs_for_generation 函数)”。
  3. Step 9: 模型推理
    • 关键代码:
      inputs = tokenizer("摘要生成: n" + input_text + tokenizer.mask_token, ...)
      inputs = tokenizer.build_inputs_for_generation(inputs, ...)
      output = model.generate(**inputs, ...)
    • 作用解读: 推理过程严格遵循了训练时的数据处理方式。必须先手动拼接好 "前缀 + 文章 + [MASK]",然后调用特殊的 build_inputs_for_generation 来构造最终输入,最后再送入 model.generate
    • 输入输出示例:
      • 输入 (构造好的格式):
        "摘要生成: n 苹果公司今天发布了最新的iPhone 18... [MASK]"
      • 可能的输出 (一长串 token IDs,解码后):
        '摘要生成: n 苹果公司今天发布了最新的iPhone 18... [MASK] <|startofpiece|> 苹果发布新款iPhone 18 性能大幅提升 <|endofpiece|>'

        需要从这个字符串中提取出 <|startofpiece|><|endofpiece|> 之间的部分,才是真正的摘要。

总结与对比

特性 基于 T5 的方法 基于 GLM 的方法
核心范式 经典的 Encoder-Decoder,通过任务前缀区分任务。 统一的自回归填空,通过 [MASK]position_ids 实现 Seq2Seq 功能。
数据处理 简单直观,输入是原文,标签是摘要。 相对复杂,需要拼接 [MASK] 并调用特定函数 build_inputs_for_generation
代码依赖 使用Hugging Face标准组件即可。 需要 trust_remote_code=True 来加载模型的自定义代码。
易用性 对初学者更友好,流程更标准化。 概念和实现更进阶,但展示了模型架构的灵活性。

对于初学者来说,T5 的方法是一个绝佳的起点,因为它完美地契合了 Hugging Face 的标准工具链。当熟悉了 T5 的流程后,再去研究 GLM 的巧妙设计,就能更深刻地理解不同 Transformer 架构的演进和创新。

生成式聊天机器人

生成式对话机器人.ipynb

这是一个非常热门且重要的应用,它展示了如何将一个通用的预训练语言模型(在这里是 bloom-389m-zh),通过特定的数据集进行微调(Fine-tuning),把它变成一个能听懂指令、进行对话的聊天机器人。

这份 chatbot.ipynb 的核心技术是指令微调(Instruction Fine-tuning)

核心思想:让模型学会“听话”

我们之前学习的因果语言模型(CLM),像 GPT 和 BLOOM,它们天生擅长“文字接龙”,但它们并不“听话”。你给它一句话,它会续写,但它不一定知道你是在向它提问,还是在下达指令。

指令微调就是解决这个问题的关键技术。

  • 做法:我们准备一个特殊的数据集,里面包含成千上万条“指令-回答”对。然后,我们用这些数据去继续训练(微调)一个已经预训练好的语言模型。
  • 学习目标:通过这种专门的“应答”练习,模型逐渐学会了识别出用户的指令(Human: ...),并按照指令的格式生成相应的回答(Assistant: ...)。它从一个只会“续写”的模型,进化成了一个会“对话”和“执行指令”的模型。
  • 代表模型:ChatGPT、Alpaca、ChatGLM 等所有现代的对话模型,都是基于这个原理打造的。

代码细节解读 (chatbot.ipynb)

  1. Step 2: 加载数据集
    • 关键代码: ds = Dataset.load_from_disk("./alpaca_data_zh/")
    • 数据集结构: alpaca_data_zh 是一个专门用于指令微调的数据集。它的每一条数据都包含三个部分:
      • instruction: 用户的指令或问题(例如:“写一首关于春天的诗”)。
      • input: 为指令提供额外的上下文(可以为空,例如:“五言绝句”)。
      • output: 模型应该给出的标准回答(例如:“春眠不觉晓,处处闻啼鸟...”)。
  2. Step 3: 数据集预处理
    • 这是整个项目中最核心、最需要理解的一步
    • 关键代码:
      def process_func(example):
          # ...
          instruction = tokenizer("n".join(["Human: " + example["instruction"], example["input"]]).strip() + "nnAssistant: ")
          response = tokenizer(example["output"] + tokenizer.eos_token)
          # ...
          labels = [-100] * len(instruction["input_ids"]) + response["input_ids"]
          # ...
    • 作用解读:
      1. 构造对话模板: 代码首先将数据构造成一个标准的对话格式:Human: [用户的指令和输入]nnAssistant: [模型的回答]。这个固定的格式就像一个“契约”,模型在微调中会学会遵循这个契约,在看到 Assistant: 后就开始生成回答。
      2. “掩码”用户输入 (Masking the Prompt): labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] 这行代码是指令微调的精髓。
        • 在 Hugging Face 的 Trainer 中,labels 是用来计算损失(loss)的。模型会比较自己的预测和 labels 的差异,然后调整自己。
        • 我们将 instruction(即 Human: ... 部分)对应的 labels 全部设置成 -100-100 是一个特殊的标记,它告诉 Trainer:“请忽略这部分,不要计算它的损失”。
        • 这意味着什么? 这意味着我们只要求模型去学习如何生成 Assistant: 后面的回答,而不需要它去学习如何生成用户的问题。模型只需要“听懂”问题,然后“回答”问题。这极大地提升了微调的效率和效果。
  3. Step 4: 创建模型
    • 关键代码: model = AutoModelForCausalLM.from_pretrained("Langboat/bloom-389m-zh")
    • 作用解读: 我们加载的是一个因果语言模型 (Causal Language Model)。因为对话机器人的本质就是“文字接龙”,根据用户的输入(Human: ...)来续写(生成)回答。BLOOM 是一个非常适合做这类生成任务的开源模型。
  4. Step 6: 创建训练器
    • 关键代码: data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True)
    • 作用解读: DataCollatorForSeq2Seq 是一个非常实用的数据整理器。虽然它的名字里有 Seq2Seq,但它同样适用于 CLM 的指令微调。它能正确地处理我们前面创建的 input_idslabels,并将一个批次(batch)内长短不一的句子填充(padding)到相同的长度。
  5. Step 8: 模型推理
    • 关键代码:
      ipt = "Human: {}\n{}".format("考试有哪些技巧?", "").strip() + "nnAssistant: "
      pipe(ipt, max_length=256, do_sample=True)
    • 作用解读: 进行推理时,我们必须严格遵循训练时使用的对话模板
      • 我们将用户的问题(“考试有哪些技巧?”)放入 Human: ... 的模板中,并以 nnAssistant: 结尾。
      • 模型在看到这个结尾后,就会“知道”轮到它发言了,然后就会开始生成(续写)回答。
    • 输入输出示例:
      • 输入 (一个严格遵循模板的字符串): "Human: 考试有哪些技巧?nnAssistant: "
      • 可能的输出 (一个包含生成文本的字典):
        [{'generated_text': 'Human: 考试有哪些技巧?nnAssistant: 考试技巧有很多,比如:1. 提前做好复习计划,不要临时抱佛脚。2. 答题时先易后难,合理分配时间。3. 仔细审题,避免答非所问。'}]

        模型成功地在 Assistant: 后面生成了符合逻辑的、有帮助的回答。

总结

这份代码是通往现代大语言模型应用(如 ChatGPT)的敲门砖。掌握了它,就理解了:

  1. 指令微调的核心思想:如何让一个预训练模型变得“听话”。
  2. 关键技术:Prompt Masking:理解为什么以及如何通过设置 labels-100 来让模型只学习回答部分。
  3. 模板的重要性:认识到在微调和推理时,保持输入格式(模板)的一致性是至关重要的。

基于这个项目,之后就可以尝试用自己的“指令-回答”数据来微调模型,打造一个专属于自己的、在特定领域表现出色的聊天机器人。