理论篇
1.关于分词
目前对于大模型而言,最基础的就是词元,而对于词元而言,我们通常要通过使用分词器来得到,每一个句子中所包含的词元。分词是后续所有任务的基础。处理方式通常是将分词作为一个独立且"硬性"的预处理步骤,这就导致一个微小的分词错误就可能造成语义信息的丢失。这种错误会在后续的处理链条中被不断放大,产生"差之毫厘,谬以千里"的级联效应(Cascading Effect)。
2.通过使用传统的jieba分词器来进行对分词这一任务的理解:
python
import jieba
text = "我在梦里收到清华大学录取通知书"
seg_list = jieba.lcut(text, cut_all=False) # cut_all=False 表示精确模式
print(seg_list)
通常得到的结果是:
python
['我', '在', '梦里', '收到', '清华大学', '录取', '通知书']
为什么当前的jieba对这句话进行分词效果不错?
是因为"清华大学"、"通知书"等词在词典中已有较稳定的统计信息。反过来,精确模式的核心仍然是"基于词典找最优路径"。一旦遇到词典里没有收录的新词(例如"奔波儿灞"),就容易被拆成更小的片段,出现不符合预期的切分。这时,基于词典的分词方法的优势就体现出来了。我们可以进行人工干预,通过自定义词典把新词加入词表,"教会"jieba认识它。接下来创建一个自定义词典user_dict.txt:
python
# 未加载词典前的错误分词
text = "九头虫让奔波儿灞把唐僧师徒除掉"
print(f"精确模式: {jieba.lcut(text, cut_all=False)}")
# 加载自定义词典
jieba.load_userdict("./user_dict.txt")
print(f"加载词典后: {jieba.lcut(text, cut_all=False)}")
输出结果如下:
python
精确模式: ['九头', '虫', '让', '奔波', '儿', '灞', '把', '唐僧', '师徒', '除掉']
加载词典后: ['九头虫', '让', '奔波儿灞', '把', '唐僧', '师徒', '除掉']
这是因为在user_dict.txt中我们加入了九头虫 奔波儿灞这两个词语。
3.进行词的嵌入
进行词元分析之后,我们得到了最基本的词元,但是我们之后需要将这些传入给模型,让模型理解这些数据之后进行训练,所以说我们要将词元转换为计算机能读懂的语言。
3.1离散表示
One-Hot独热编码
独热编码操作:就是首先是构建词典,也就是从整个语料库中收集所有出现过的唯一词语,构成一个词典;接着分配索引,为词典中的每个词语分配一个从 0 开始的唯一整数索引;最后创建向量,用一个长度等于词典大小的向量来表示每个词,向量中该词对应索引的位置为 1,其余所有位置均为 0。基本上最后得到词元的向量就是其中一数为1,其余为0,从而最后反应出来的就是稀疏高维的词元向量,并且每一个词元之间是正交的,导致最后词元向量之间没有关联。
词袋模型
原本单个词根据One_Hot独热编码之后,只能表示一个词,为了表示一句话或者一个文档,就是将独热编码的向量相加最终得到一个向量。对于这些向量如何计算相似就是使用余弦相似度计算两个向量之间的相似度,来作为这两个向量是否存在联系。
TF-IDF
TF-IDF 主要由两部分组成,第一部分是词频(Term Frequency, TF) ,用于衡量一个词在当前文档中出现的频繁程度。它的计算方式主要有两种,一是直接使用原始频数 TF(t,d)=ft,dTF(t, d) = f_{t,d}TF(t,d)=ft,d (其中 ft,df_{t,d}ft,d 表示词 ttt 在文档 ddd 中出现的次数);二是采用归一化频率 TF(t,d)=ft,d∑t′∈dft′,dTF(t, d) = \frac{f_{t,d}}{\sum_{t' \in d} f_{t',d}}TF(t,d)=∑t′∈dft′,dft,d,通过除以文档总词数来消除长文档带来的偏差。第二部分是逆文档频率(Inverse Document Frequency, IDF),用于衡量一个词的"稀有"程度或"信息量"。
TF-IDF 在 NLP 领域应用广泛,最常见的是进行关键词提取,就是计算文章中每个词的 TF-IDF 值并降序排列,排在最前面的便是关键词。除此之外,它也常用于文本相似度计算,通过构建两篇文档的 TF-IDF 向量并计算其余弦相似度来判断内容是否相近。
N-gram模型
词袋模型和 TF-IDF 虽然在统计上很有效,但它们最大的局限在于彻底丢失了词序信息。而 N-gram(N元语法) 通过统计连续词组的方式弥补了这一短板。
与词袋模型只关心"有什么词"不同,N-gram(1-gram 是特例)关心的是"词的顺序"。它的核心基于马尔可夫假设,也就是认为一个词出现的概率只取决于它前面 N-1 个词。这种简化大大降低了建模的复杂度,并且由于它基于前文预测后续内容,常被视为生成式 AI 的雏形。根据依赖的前文长度不同,N-gram 模型可分为多种类型,其中 Unigram(1-gram) 和词袋模型一样,假设每个词独立且不依赖前文;Bigram(2-gram) 只依赖前 1 个词,例如看到"喜欢"预测"玩"的概率;而 Trigram(3-gram) 则依赖前 2 个词,例如根据"喜欢 玩"来预测"GTA6"的概率。
4.大模型的Tokenizer
之前介绍的离散表示,就是现在大模型使用的Tokenizer Pipeline的前身。而对于现有大模型而言,参数的大幅度上涨,以及加入一些特殊的词元,这些就可以作为当前大模型的输入数据,这种通常被称为参数化。
4.1参数化
对于大模型的参数化而言,存在和之前的离散表示一样的阶段,构建词典、ID映射,同时还存在和之前有差异的环节,就是在构建此段和ID映射之间存在一个阶段是增加特殊的词元;如:在词典中加入一些有特殊功能的 Token,至少包括 [PAD](Padding)和 [UNK](Unknown)。[PAD] 的 ID 通常为 0,用于将短句子填充 至同一批次内的最长长度,以满足批处理需求;[UNK] 的 ID 通常为 1,用于表示所有词典中未出现过 的词。根据任务需求,还可能加入 [CLS](分类)、[SEP](分隔)等其他特殊词元。
4.2参数化实例
假设我们现在有三个句子需要处理,分别是"我挣一个亿"、"比方说我"和"我先挣钱"。同时现在有一个精简词典内容如下:
text
{'[PAD]': 0, '[UNK]': 1, '比': 2, '方': 3, '说': 4, '我': 5, '先': 6, '挣': 7, '它': 8, '一': 9, '个': 10, '亿': 11}
首先,对这三个句子进行分词 ,并根据词典查找每个词元对应的 ID 。由于"钱"字不在词典中,将其映射为 [UNK] 的 ID 1:
text
句子1 (我挣一个亿): 我 (5), 挣 (7), 一 (9), 个 (10), 亿 (11) -> [5, 7, 9, 10, 11]
句子2 (比方说我): 比 (2), 方 (3), 说 (4), 我 (5) -> [2, 3, 4, 5]
句子3 (我先挣钱): 我 (5), 先 (6), 挣 (7), 钱 (不在词典中) -> [5, 6, 7, 1]
为了将这三个长短不一的序列组成一个矩阵 ,我们需要以最长的序列(句子1,长度为5)为基准,使用 [PAD] 的 ID 0 对其他短序列进行填充 (Padding):
text
序列1 (长度5): [5, 7, 9, 10, 11]
序列2 (长度4→5): [2, 3, 4, 5, 0]
序列3 (长度4→5): [5, 6, 7, 1, 0]
最终,我们得到一个 3x5 的整数矩阵,这个矩阵就是喂给深度学习模型的最终输入。
bash
# 最终输入模型的张量(Tensor)
[[5, 7, 9, 10, 11],
[2, 3, 4, 5, 0],
[5, 6, 7, 1, 0]]
当前我们得到的这个矩阵中的数据,不能通过数字大小来进行重要性等的判断,这些数据主要是为了之后的词嵌入做铺垫。