文本预处理
文本可以认为是一个时间序列,对于文本中的每个字或者词都可以认为是一个向量,这些向量之间有先后顺序关系。
对于一个文本序列,我们可以进行字符级语言建模或者词级语言建模:字级建模就是将每个字当成一个时间步的数据,比如“我要学习英语”,字符级语言建模会将其拆分为“我”、“要”、“学”、“习”、“英”、“语”;词级语言建模会将文本拆分成一个一个的词语,以此为最小单位作为每个时间步的数据,比如“我要学英语”,词级建模可能会将其拆分为“我”、“要”、“学习”、“英语”。观察这两个例子,如果是字符级建模,“英”其实可以与很多不同的字组合形成不同的意思,比如“英雄”、“英国”、“英语”、“英气”、“英年”等等,这给模型带来了更大的难度去理解不同时间步之间的意思;词级建模以单词为基本单位,天然携带完整的语义信息。因此,词级建模的效果要好于字符级建模。
词级建模的算力要求相对于字符级更低,因为同一个句子如果拆分成字符级会有更多的时间步。
拆分词元
对于中文而言,首先我们要将文本拆分成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只需要是文本对应的数字编号即可