一、什么是文本张量表示
将一段文本使用张量进行表示,其中一般将词汇表示为向量,称作词向量,再由各个词向量按循序组成矩阵形成文本的张量表示。
例如:我们通过分词方法将一句话“人生该如何起头”划分为四个分词,然后再使用一些分词转换为向量的方法将每个分词转换为对应的向量,最后将分词向量按分词在原句子中的顺序进行拼接,就会生成该句子的张量表示。
["人生","该","如何","起头"]
=========================
# 每个词对应矩阵中的一个向量
[[1.32, 4.32, 5.23, 1.23], #人生
[1.2, 5.2, 1.35, 0.45], #该
[3.21, 5.32, 2, 4.32], #如何
[2.54, 2.34, 1.65, 0.68]] #起头
文本张量表示的作用:
将文本表示成张量(矩阵)形式,能够使语言文本可以作为计算机处理程序的输入,进行接下来一系列的解析工作。
文本张量表示的方法:
- one-hot 编码
- Word2Vec
- Word Embedding
二、one-hot的词向量表示
one-hot编码又称为独热编码,将每个词表示为具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数。one-hot编码所有的1都在对角线上。
例如,下面就是一个one-hot编码的示例:
["人生","该","如何","起头"]
=========================
# 每个词对应矩阵中的一个向量
[[1, 0, 0, 0], #人生
[0, 1, 0, 0], #该
[0, 0, 1, 0], #如何
[0, 0, 0, 1]] #起头
使用pytorch实现one-hot编码
import torch
# 初始化一个词汇表
vocab = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "鹿晗"}
vocab_id = [i for i in range(len(vocab))] #分词ID化
vocab_id_tensor = torch.tensor(vocab_id) #ID张量化
# 使用one_hot函数进行编码
one_hot_encoded_labels = torch.nn.functional.one_hot(
vocab_id_tensor, len(vocab))#给定函数ID的张量和ID的长度
# 输出是一个形状为(batch_size, num_classes)的张量,其中batch_size为vocab的长度
print(one_hot_encoded_labels)
tensor([[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1]])
one-hot编码的优劣势
- 优势:操作简单,容易理解
- 劣势:完全割裂了词与词之间的联系,而且在大量语料集的情况下,每个向量长度过大,占据大量内存。相近的词和相远的词之间无法通过距离表达。
三、Word2Vec
word2Vec是一种流行的将词汇表示为向量的无监督训练方法,该过程将构建网络模型,将网络参数作为词汇的向量表示,它包含CBOW和skipgarm两种训练模式。
(一)CBOW模式
CBOW(Continuous bag of words),连续词袋模型:给定一段用于训练的文本语料,再选定某段长度(窗口)作为研究对象,使用上下文词汇预测目标词汇。
上图中,窗口大小为9,使用前后4个词汇对目标词汇“Jumps”进行预测。
1、CBOW模式下的word2vec计算过程
假设我们给定的训练语料中只有一句话:Hope can set you free(愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope can set ,因为是CBOW模式,所以将使用Hope和set作为输入,can作为输出,在训练模型时,Hope,can,set等词汇都使用它们的one-hot编码。如图所示:每个one-hot编码的单词与各自的变换矩阵(即参数矩阵3×5,这里的3是指最后得到的词向量维度)相乘之后再相加,得到上下文表示矩阵(3×1)。
接着,将上下文表示矩阵与变换矩阵(参数矩阵5×3,所有的变换矩阵共享参数)相乘,得到5×1的结果矩阵,它将与我们真正的目标矩阵即can的one-hot编码矩阵(5×1)进行损失的计算,然后更新网络参数完成一次模型迭代。
最后窗口按顺序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵(3×5),这个变换矩阵与每个词汇的one-hot编码(5×1)相乘,得到3×1的矩阵就是该词汇的word2vec张量表示。
(二)SkipGram模式
给定一段用于训练的文本语料,再选定某段长度(窗口)作为研究对象,使用目标词汇j预测上下文词汇。
如上图,图中窗口大小为9,使用目标词汇Jumps对前后各四个词汇进行预测。
1、skipgram模式下的word2vec计算过程
假设我们给定相同的训练语料,窗口大小为3,因此模型的第一个训练样本来自Hope can set,因为是skipgram模式,所以将使用can作为输入,Hope和set作为输出,在模型训练时,Hope,can,set等词汇都使用他们的one-hot编码,如图所示,将can的one-hot编码与变换矩阵(即参数矩阵3×5,这里的3是指最后得到的词向量维度)相乘,得到目标词汇表示矩阵(3×1)。
接着,将目标词汇表示矩阵与多个变换矩阵(参数矩阵5×3)相乘,得到多个5×3的结果矩阵,它将与我们Hope和set对应的one-hot编码矩阵(5×1)进行损失的计算,然后更新网络参数完成一次模型迭代。
最后窗口按顺序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵即参数矩阵(3×5),这个变换矩阵与每个词汇的one-hot编码(5×1)相乘,得到的3×1的矩阵就是该词汇的word2vec的向量表示。
(三)使用fasttext实现word2vec
1、获取训练数据
这里是下载了《三体:黑暗森林》的txt文件作为预训练语料,打开文件后,可以看到,其中存在大量的空格和换行,且没有进行分词。fasttext对输入的文本有两点要求,一是必须是空格分隔的分词(英文的话不用在意这一点,但是中文必须分词);二是输入的文本必须是UTF-8编码。
我们需要对上述的文本进行一下预处理,剔除不必要的字符和使用上节学到的jieba对文本进行分词。
import jieba
with open(r'./Datasets/三体III:死神永生.txt', 'r') as f:
content = f.read()#读取文本内容
token = jieba.lcut(content)#使用jieba进行分词
s = ''
for t in token:
if (t == '\n' or t == ' '):#剔除换行符和所有的空格
continue
s = s+t+' '
# 保存分词和清洗完成的数据,保存格式为utf-8编码格式
with open(r'./Datasets/santi_token.txt', 'w', encoding='utf-8') as fs:
fs.write(s)
清洗完成后的数据,就变得十分规整,符合fasttext的输入要求了,如下图:
2、训练词向量
import fasttext as ft
# 使用fasttext的train_unsupervised(无监督训练方式)训练词向量模型
model = ft.train_unsupervised(r"./Datasets/santi_token.txt")
# 查看单词对应的词向量
print(model.get_word_vector('三体'))
[ 0.37971157 0.06909833 0.03543181 -0.04888123 -0.0216679 -0.34706125 -0.3062346 -0.26646283 -0.07326028 0.35442632 0.1931618 -0.4089342 -0.03335481 0.08014391 0.37675148 0.14254698 -0.1458862 -0.2052374 -0.21407518 -0.06824867 0.19573641 -0.12700017 -0.12727436 0.24899586 -0.28292418 -0.07073592 -0.36006644 -0.05082047 -0.06891748 0.2928134 0.0507793 0.24470061 0.05686486 -0.21991748 -0.31881845 -0.33984822 0.14262599 0.07354249 -0.28883344 0.13031189 -0.01082141 0.08320222 -0.26397425 0.37692732 -0.11790431 -0.7893586 0.2898587 0.02643363 -0.14984205 0.04117543 0.06116182 0.21580848 0.27619147 0.2231 -0.1335352 -0.363623 -0.38489527 -0.40508583 0.02364224 -0.29590675 0.29189956 0.10454249 -0.6501397 -0.14524996 0.10176794 -0.268071 0.11645795 -0.29426482 -0.49784827 -0.45061135 0.27009434 -0.02474914 -0.07712444 -0.10420072 -0.21345241 -0.06190152 0.48487225 0.17569439 0.14259018 -0.03715926 -0.12320035 0.15963304 0.28912544 0.550086 0.49838996 -0.10521852 -0.16692662 0.3040915 -0.02100541 0.31654721 -0.22503647 0.28449228 0.20581862 -0.18517417 -0.06572792 -0.08873171 -0.06199432 0.29534584 0.10030682 -0.01322567]
#查看和单词相近的词语
print(model.get_nearest_neighbors('黑暗'))
[(0.9705036282539368, '森林'), (0.8523691892623901, '打击'), (0.8293827176094055, '到来'), (0.8209201693534851, '历史'), (0.810953676700592, '新'), (0.8058372139930725, '降临'), (0.8012556433677673, '节选'), (0.7987314462661743, '成为'), (0.7978155612945557, 'II'), (0.7951033115386963, '往事')]
# 使用save_model保存训练好的模型
model.save_model('san_ti.bin')
# 加载训练好的模型
model = ft.load_model('san_ti.bin')
3、fasttext.train_unsupervised中的超参数设置
在训练词向量的过程中,我们可以设定很多常用超参数来调节我们的模型,如下:
参数名称 | 参数作用 |
训练模式model | skipgram或cbow,默认为skipgram,实践中skipgram效果更好 |
词嵌入维度dim | 默认为100,但随着语料库的增大,词嵌入的维度往往也要更大 |
数据迭代次数epoch | 默认为5,当数据集足够大时,可能不需要那么多次,数据集小的话,要多次 |
学习率lr | 默认为0.05,一般是在[0.01,1]范围内 |
使用的线程数thread | 默认为12个线程,一般建议和电脑的cpu核数相同 |
四、Word embedding词嵌入
- word Embedding是通过一定的方式将词汇映射到指定维度(一般指更高的维度)的空间。
- 广义的word embedding包括所有密集词汇向量的表示方法,如上述的word2vec可以被认为是word embedding的一种。one-hot这种表示会产生稀疏矩阵,就是非密集词汇的表示方法。
- 狭义的word embedding是指在神经网络中加入的embedding层,对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数),这个embedding矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵。