Pipeline 的内部
使用 tokenizer 进行预处理
Transformer 无法直接处理原始文本, 需要将文本输入, 通过 tokenizer 转换为模型能够理解的数字:
- 将输入拆分为单词、子单词或符号(如标点符号),称为 token(标记)
- 将每个标记(token)映射到一个数字,称为 input ID(inputs ID)
- 添加模型需要的其他输入,例如特殊标记(如
[CLS]
和[SEP]
)- 位置编码:指示每个标记在句子中的位置
- 段落标记:区分不同段落的文本
- 特殊标记:例如 [CLS] 和 [SEP] 标记,用于标识句子的开头和结尾
实战
|
|
- 下载
distilbert
作为 checkpoint, 调用它的 tokenizer - 然后使用
AutoTokenizer
类的from_pretrained
这个方法, 进行实例化
|
|
- 这里传入一些句子
- 指定 tokenizer 实例的一些参数
以下是返回结果:
|
|
包含两个值:input_ids
, 以及 attention_mask
探索模型
|
|
这里使用 Automodel
类载入一个模型
AutoModel
默认只加载 模型的主体(通常是 encoder 或 transformer 本体),不会附带分类头等任务相关结构
高维向量
Transformer 的矢量输出很大, 通常包含三个维度:
- Batch size(批次大小):一次处理的序列数(在我们的示例中为 2)
- Sequence length(序列长度):表示序列(句子)的长度(在我们的示例中为 16)
- Hidden size(隐藏层大小):每个模型输入的向量维度
|
|
- 对
input
解包, 用model
加载, 得到output
- 查看
output
的隐状态维度
|
|
输出是这样的, 表示:
- Batch size 为 2, 输入了两条文本数据
- Sequence length 为 16, 是文本被分词后的序列长度
- Hidden size 为 768, 每个 token 被编码为 768 维的向量
模型头
- Transformer 模型的输出会直接发给模型头进行处理
- 模型头一般由几个线性层组成
- 模型头围绕处理特定任务进行设计
以下是一些常见的模型头结构:
*Model
(隐状态检索)*ForCausalLM
*ForMaskedLM
*ForMultipleChoice
*ForQuestionAnswering
*ForSequenceClassification
*ForTokenClassification
示例
|
|
这里使用序列分类头的模型, 因此可以将原先的输出分类为不同的类别
|
|
这是最后的输出形状, 模型头将高维向量按照两个标签的进行分类
由于是两个术语, 因此最终输出形状是 2x2
对输出进行后序处理
对于上面得到的 output.logits
, 我们进一步了解和处理
|
|
这里我们得到的并不是最终的概率, 而是经过处理后得到的 logits
经过 softmax 处理后即可转换成概率分布
|
|
这里就得到了每一句对应的两个标签的预测概率分布
至于具体哪个标签对应哪个, 需要我们进一步查看模型信息
|
|
模型
创建 Transformer 模型
|
|
这里我们初始化 Bert 模型
先加载 Config 对象, 并从 Config 类初始化模型
|
|
这是 config 实例的详细信息
不同的加载方式
从默认配置创建
上面使用默认配置创建模型, 会使用随机值对其进行初始化
可以运行, 但是会胡言乱语, 因为未经过训练
从已有模型加载
这里使用 from_pretrained()
方法
|
|
这里我们直接加载了经过预训练的 Bert 模型的参数
权重已经经过训练, 而非随机初始化
这里的 checkpoint 需要和 BertModel 相匹配
保存模型
使用 save_pretrained()
方法
|
|
这里是保存后的两个文件
config.json
会列出模型所需的属性, 以及一些元数据
pytorch_model.bin
被称为状态字典, 包含了模型的所有权重, 也就是模型参数
使用 Transformer 模型进行推理
model
接受 tensor 类型作为 input IDs
的输入
input IDs
是必需的参数
|
|
这里 model_inputs
是 tensor 类型的张量
Tokenizers
- tokenizer 将文本转换为模型可以处理的数据, 具体来说是数字
- tokenizer 的目标是找到对于模型来说, 最有效, 为简洁的文本-数字表达方式
常见的一些 tokenization
基于单词(Word-based)的 tokenization
|
|
- 可以得到非常大的词汇表, 大小由语料中的 token 数确定
- 为每个单词分配 ID, 模型通过 ID 来识别每个词
- 这种方法会导致 ID 数量及其庞大
- 同时, 无法捕获词语间的关系
- 自定义 token
- 用自定义 token 表示不再词汇表中的单词
- 通常表示为 [UNK] 或 < unk >
基于字符(Character-based)的 tokenization
- 把文本拆分为字符, 而非单词
- unknown tokens 要少得多,因为每个单词都可以由字符构建
- 有人认为字符无法表示含义
- 模型需要处理大量的 token
基于子词(subword)的 tokenization
- 基于一个原则 : 常用词不应被分解为更小的子词,但罕见词应被分解为有意义的子词
- 大概就是把那种 复合词, 相对复杂的词 拆分成 词根+词缀
- 可以在保持空间效率的同时具有语义含义
加载和保存
- 和模型一样, 基于两种方法 :
from_pretrained()
和save_pretrained()
|
|
- 这种是指定一种 tokenizer, 然后输出对应的 checkpoint
|
|
- 这种是使用
AutoTokenizer
, 可以根据输入的 checkpoint 自动获取正确的 tokenizer 类
|
|
这是分词的做法与结果
编码
- 编码是指将文本转换成数字
- 编码的步骤:
- 分词
- 转换为
inputs ID
其实就是把 tokenizer 干的拆成了两步
分词
|
|
- 调用 tokenizer 的
tokenize()
方法实现 - 输出字符串列表, 也就是 tokens
转换为 inputs ID
|
|
转换为 tensor 后, 就可以作为模型输入了
解码
- 与编码正好相反, 从 inputs ID 转换成字符串
|
|
- 调用 tokenizer 下的
decode()
方法 - 不仅将索引转换为 token, 还把相同单词的 token 组合, 生成可读的句子
处理多个序列
将一批输入传给模型
错误示例
|
|
这里的错误原因是: 传了一个句子, 而模型默认情况下需要一个句子列表
这里 input_ids 少了 batch 这个维度
正确方式是:
|
|
这样 id 会变成一个列表, batch size 为 1
填充输入 Padding
|
|
这样的列表不能直接转换为张量
解决方法是, 通过填充使得张量成为标准的矩形
|
|
注意力掩码层 Attention Mask
|
|
- 注意力掩码是和 inputs ID 张量形状完全相同的张量
- 其用 0 和 1 填充
- 1 表示应该关注的 token
- 0 表示因该被忽略的 token
综合应用
填充
|
|
截断
|
|
指定张量类型
|
|