文本预处理
文本可以认为是一个时间序列,对于文本中的每个字或者词都可以认为是一个向量,这些向量之间有先后顺序关系。
对于一个文本序列,我们可以进行字符级语言建模或者词级语言建模:字级建模就是将每个字当成一个时间步的数据,比如“我要学习英语”,字符级语言建模会将其拆分为“我”、“要”、“学”、“习”、“英”、“语”;词级语言建模会将文本拆分成一个一个的词语,以此为最小单位作为每个时间步的数据,比如“我要学英语”,词级建模可能会将其拆分为“我”、“要”、“学习”、“英语”。观察这两个例子,如果是字符级建模,“英”其实可以与很多不同的字组合形成不同的意思,比如“英雄”、“英国”、“英语”、“英气”、“英年”等等,这给模型带来了更大的难度去理解不同时间步之间的意思;词级建模以单词为基本单位,天然携带完整的语义信息。因此,词级建模的效果要好于字符级建模。
词级建模的算力要求相对于字符级更低,因为同一个句子如果拆分成字符级会有更多的时间步。
拆分词元
对于中文而言,首先我们要将文本拆分成token
(词元),即一个个的时间步的数据,如果使用词级建模,可以使用jieba
库将文本拆分成词语:
1 |
|
jieba.cut()
可以将text
文本拆分成词语,返回的是一个generator
(生成器),可以用循环或list()
等将其按顺序读取,strip()
方法是去除字符串前面和后面的空字符(空格、制表符等)。
构建词汇表
将文本拆分成词元后,我们需要将这些词元进行统计,去除重复的词元,统计各个词元出现的次数,并给每个词元一个数字编号,这样我们就可以得到一个词元和其对应的数字编号:
1 |
|
词汇表构建示例:
1 |
|
文本转数字序列
词汇表构建完成后,我们需要将原本的文本序列变成数字序列,也就是将每个词元变成对应的数字编号:
1 |
|
词元向量化
在模型中,直接使用词元对应的数字编码来进行计算效果是不好的,整数编码仅将词映射为离散的整数值,但数值本身无实际意义,模型无法通过整数之间的差值或比例推导出词语的语义关联。
因此,我们需要将词元向量化处理,最容易想到的方法是One-Hot编码(独热编码),也就是将仅在向量的某个维度值为1,其余维度值为0,以此区分不同的词元。如果使用独热编码,有多少个词元,向量就有多少个维度,并且每个词元的向量均只有一个维度的值不为零,这样维度极高的稀疏向量会占用非常大的内存空间,计算效率也非常低,并且,独热编码不能区分两个词元之间是否具有一定的关联性(比如近义词等),因为对于两个词元,它们在欧式空间的距离均为\(\sqrt{2}\)
因此,我们需要将词元构建成一个低纬度稠密向量,常用的方法有TF-IDF
、Word2Vec
、BERT
动态向量等,此处我们使用最简单的nn.Embedding()
方法,原理是使用标准正态分布(均值为0,方差为1)随机初始化的方式生成词元向量:
nn.Embedding()
的参数:
num_embedding
:定义嵌入字典的大小,即词表的总词元数。该参数通常等于词汇表大小(如vocab_size
)embedding_dim
:定义每个词元嵌入向量的维度。维度越高,语义表达能力越强,但计算成本也更高。高维度适合复杂任务(如机器翻译),低维度适合轻量级任务(如文本分类)
可选参数:
padding_idx
:默认None
,max_norm
:浮点数,默认None
,对嵌入向量进行范数裁剪。若向量范数超过该值,则按norm_type
缩放至该值,防止梯度爆炸norm_type
:浮点数,默认2.0
,定义范数裁剪时使用的范数类型。例如,2.0
表示 L2 范数,1.0
表示 L1 范数scale_grad_by_freq
:布尔值,默认False
,若设为True
,梯度会根据词元在批次中的出现频率进行缩放。高频词元的梯度会被缩小,低频词元梯度放大,用于平衡数据分布sparse
:布尔值,默认False
,若设为True
,使用稀疏梯度更新以节省内存,但会牺牲计算速度。适用于词表极大(如百万级)的场景_weight
:张量,默认None
,自定义嵌入矩阵的初始化权重。需满足形状(num_embeddings, embedding_dim)
,常用于加载预训练词向量(如 Word2Vec、GloVe)
词元向量化可以放在神经网络的定义中实现:
1 |
|
这样,输入的x
只需要是文本对应的数字编号即可