语言模型
这两个 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
)
- Step 3: 数据集处理
- 关键代码:
DataCollatorForLanguageModeling(tokenizer, mlm=True, mlm_probability=0.15)
- 作用解读: 这是整个 MLM 训练的魔法核心。
DataCollator
是一个数据整理器,当mlm=True
时,它会在把数据喂给模型之前,自动帮你完成“完形填空”的出题工作。mlm_probability=0.15
的意思是,它会随机将数据中 15% 的词(Token)替换成一个特殊的[MASK]
标记。
- 关键代码:
- Step 4: 创建模型
- 关键代码:
AutoModelForMaskedLM.from_pretrained("hfl/chinese-macbert-base")
- 作用解读: 这里我们加载了一个专门用于 MLM 任务的模型结构。
hfl/chinese-macbert-base
是一个预训练好的 BERT 类模型。AutoModelForMaskedLM
会确保这个模型有一个“头部”(prediction head),这个头部的作用就是来预测那些被[MASK]
的位置应该是什么词。
- 关键代码:
- Step 7: 模型训练
- 关键代码:
trainer.train()
- 作用解读:
Trainer
会使用前面定义的DataCollator
不断地从wiki_cn_filtered
数据集中抽取文本,制作成“完形填空”题,然后喂给模型去学习。模型在成千上万次的练习中,不断提升自己联系上下文、理解语义的能力。
- 关键代码:
- 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
)
- Step 3: 数据集处理
- 关键代码:
DataCollatorForLanguageModeling(tokenizer, mlm=False)
- 作用解读: 这里的
mlm=False
是与 MLM 最核心的区别。它告诉数据整理器,不要做“完形填空”,而是为“文字接龙”准备数据。它会巧妙地将输入文本和标签文本进行移位,让模型在每个时间步都去预测下一个真实的词。 - 另一个细节:
contents = [e + tokenizer.eos_token for e in examples["completion"]]
。这里为每段文本末尾加上了一个特殊的结束符eos_token
,这能让模型学会什么时候应该“停笔”。
- 关键代码:
- Step 4: 创建模型
- 关键代码:
AutoModelForCausalLM.from_pretrained("Langboat/bloom-389m-zh")
- 作用解读: 这里加载的是一个专门用于 CLM 任务的模型结构。
Langboat/bloom-389m-zh
是一个中文的 BLOOM 模型,它天生就是用来做文本生成的。AutoModelForCausalLM
加载的正是这种“文字接龙”式的模型。
- 关键代码:
- Step 7: 模型训练
- 关键代码:
trainer.train()
- 作用解读: 在训练过程中,模型会不断地看到句子的开头,并努力预测下一个词。通过在海量
wiki_cn_filtered
文本上进行这种练习,模型逐渐掌握了语言的规律、事实知识以及生成流畅文本的能力。
- 关键代码:
- 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 |
文本摘要生成
这两个 Notebook 都旨在完成同一个任务:文本摘要(将一篇长文章,自动浓缩成一句或几句简短的摘要)。但它们采用了两种不同的、非常有代表性的模型架构:T5 和 GLM。理解它们的异同,对于掌握现代 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
)
- 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
(在这里标题被当作是摘要)。
- 关键代码:
- Step 4: 创建模型
- 关键代码:
model = AutoModelForSeq2SeqLM.from_pretrained("Langboat/mengzi-t5-base")
- 作用解读:
AutoModelForSeq2SeqLM
是 Hugging Face 中专门用来加载所有 Encoder-Decoder 架构模型的类。我们加载的mengzi-t5-base
就是一个经过中文数据预训练的 T5 模型。
- 关键代码:
- Step 5: 创建评估函数
- 关键代码:
from rouge_chinese import Rouge
- 作用解读: ROUGE 是评估摘要生成质量的标准指标。它的原理是比较模型生成的摘要和人工撰写的标准摘要之间,有多少重叠的字词或片段。分数越高,代表生成的摘要质量越好。
- 关键代码:
- 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
)
- 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 功能的核心。
- 关键代码:
- 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
函数)”。
- 关键代码:
- 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 架构的演进和创新。
生成式聊天机器人
这是一个非常热门且重要的应用,它展示了如何将一个通用的预训练语言模型(在这里是 bloom-389m-zh
),通过特定的数据集进行微调(Fine-tuning),把它变成一个能听懂指令、进行对话的聊天机器人。
这份 chatbot.ipynb
的核心技术是指令微调(Instruction Fine-tuning)。
核心思想:让模型学会“听话”
我们之前学习的因果语言模型(CLM),像 GPT 和 BLOOM,它们天生擅长“文字接龙”,但它们并不“听话”。你给它一句话,它会续写,但它不一定知道你是在向它提问,还是在下达指令。
指令微调就是解决这个问题的关键技术。
- 做法:我们准备一个特殊的数据集,里面包含成千上万条“指令-回答”对。然后,我们用这些数据去继续训练(微调)一个已经预训练好的语言模型。
- 学习目标:通过这种专门的“应答”练习,模型逐渐学会了识别出用户的指令(
Human: ...
),并按照指令的格式生成相应的回答(Assistant: ...
)。它从一个只会“续写”的模型,进化成了一个会“对话”和“执行指令”的模型。 - 代表模型:ChatGPT、Alpaca、ChatGLM 等所有现代的对话模型,都是基于这个原理打造的。
代码细节解读 (chatbot.ipynb
)
- Step 2: 加载数据集
- 关键代码:
ds = Dataset.load_from_disk("./alpaca_data_zh/")
- 数据集结构:
alpaca_data_zh
是一个专门用于指令微调的数据集。它的每一条数据都包含三个部分:instruction
: 用户的指令或问题(例如:“写一首关于春天的诗”)。input
: 为指令提供额外的上下文(可以为空,例如:“五言绝句”)。output
: 模型应该给出的标准回答(例如:“春眠不觉晓,处处闻啼鸟...”)。
- 关键代码:
- 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"] # ...
- 作用解读:
- 构造对话模板: 代码首先将数据构造成一个标准的对话格式:
Human: [用户的指令和输入]nnAssistant: [模型的回答]
。这个固定的格式就像一个“契约”,模型在微调中会学会遵循这个契约,在看到Assistant:
后就开始生成回答。 - “掩码”用户输入 (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:
后面的回答,而不需要它去学习如何生成用户的问题。模型只需要“听懂”问题,然后“回答”问题。这极大地提升了微调的效率和效果。
- 在 Hugging Face 的
- 构造对话模板: 代码首先将数据构造成一个标准的对话格式:
- Step 4: 创建模型
- 关键代码:
model = AutoModelForCausalLM.from_pretrained("Langboat/bloom-389m-zh")
- 作用解读: 我们加载的是一个因果语言模型 (Causal Language Model)。因为对话机器人的本质就是“文字接龙”,根据用户的输入(
Human: ...
)来续写(生成)回答。BLOOM 是一个非常适合做这类生成任务的开源模型。
- 关键代码:
- Step 6: 创建训练器
- 关键代码:
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True)
- 作用解读:
DataCollatorForSeq2Seq
是一个非常实用的数据整理器。虽然它的名字里有Seq2Seq
,但它同样适用于 CLM 的指令微调。它能正确地处理我们前面创建的input_ids
和labels
,并将一个批次(batch)内长短不一的句子填充(padding)到相同的长度。
- 关键代码:
- 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)的敲门砖。掌握了它,就理解了:
- 指令微调的核心思想:如何让一个预训练模型变得“听话”。
- 关键技术:Prompt Masking:理解为什么以及如何通过设置
labels
为-100
来让模型只学习回答部分。 - 模板的重要性:认识到在微调和推理时,保持输入格式(模板)的一致性是至关重要的。
基于这个项目,之后就可以尝试用自己的“指令-回答”数据来微调模型,打造一个专属于自己的、在特定领域表现出色的聊天机器人。
Comments NOTHING