NLP(六):word embedding(词嵌入)

参考:

https://www.zhihu.com/question/45027109/answer/129387065

https://blog.csdn.net/longxinchen_ml/article/details/89077048

一、引入

在我们完成基本的句子分词后,我们要把它们输入计算机中,最好要转化成数字形式,即我们可以创建一个数组(矩阵),数组由多个向量组成,每个向量中有一个数字为1,其余数字为0,1在向量所在的位置代表的是单词出现的位置,这个叫做one-hot向量。接下来研究一下如何把语句进行分词并且转为onehot向量:
import numpy as np
sentence_example = "I miss you, but i must study deep learning."
token_seq = str.split(sentence_example)    # 使用str.plit()函数分割句子
token_seq

这里用简单的英文为例,并且采用split分词,因为它是最基本的方法。它通过指定的分隔符分割给定的字符串后返回字符串列表。默认情况下,split()是以一个或多个空格作为分隔符。

现在我们要把它转化为onehot编码,像字典一样,一个词应当只有一个位置,所以在这个只由一句话构成的语料库里,我们可以先看看里面一共出现了几种词,来得到词汇表的大小V,也就是onehot向量的长度。

vocab = sorted(set(token_seq))             # set函数找出句子中所有独特唯一的单词,sorted函数则按照数字字母(先大写后小写)方式排序
','.join(vocab)                            # 按照分割的单词组成新的语句
num_tokens = len(token_seq)                # 让我们知道句子是由多少个标识符组成
print("The total tokens are: ", num_tokens)
vocab_size = len(vocab)                    # 句子中包含多少个单词(这里统计的是非重复的,区别于标识符)
print("the vocabulary size is: ", vocab_size)
one_hot_vector = np.zeros((num_tokens, vocab_size), int)   # 创建一个空矩阵(句子词数*词汇表长度)来初始化one-hot-vectors
for i, word in enumerate(token_seq):
    one_hot_vector[i, vocab.index(word)] = 1#原分词结果中第i个词word,先去找它在词汇表中的索引位置,假设为7,则这个词的onohot编码就是在第7个位置上为1,其余位置为0的长度为9的向量。整个句子的向量构成了一个01矩阵
print(one_hot_vector)

可以看见,原来的分词结果变成了一个01矩阵的形式,而且这个矩阵,每一行表示一个分词。

比起one-hot编码表示一个词,我们更加希望用分布式表示(distributed representation)来表示一个词。因为one-hot编码的稀疏性决定了它很难把握词语之间的相似性,包括词性,语义信息等等,比如car和bus或多或少具有相似性,one-hot编码却难以衡量。在One hot representation的基础上考虑到词与词之间的联系,例如词义、词性等信息。每一个维度元素不再是0或1,而是连续的实数,表示不同的程度,就有了distributed representation。
所以我们拿到一个文本,对其中的词进行了一个one-hot编码后,我们还可以把它喂进一个Embedding layer,它的作用主要在于学习词语的分布式表达并将极其稀疏的one-hot编码的词语进行降维。
 

二、如何训练词的分布式表示

遵循的一个原则是:词要放在上下文中才有意义。Word2vec是一种从文本中学习词嵌入的预测模型。词嵌入的学习使得在语料库中共享公共上下文的单词在向量空间中紧密地结合在一起。就产生了两种训练词的distributed representation的方法:CBOW模型和Skip-gram模型,这两种模型的最终输出层都不是我们想要获得的‘稠密’的词向量,有种醉翁之意不在酒的感觉。二者区别如下:

CBOW:根据上下文来预测当前词语的概率,而且上下文中所有的词对当前词出现的概率的影响权重是一样的。CBOW模型的训练输入是某一个特征词的上下文相关的词对应的词向量,而输出层有词汇表大小个神经元。

Skip-gram:根据一个特征词来预测上下文出现词语的概率。训练输入是某一个特征词的词向量,而输出层有词汇表大小个神经元。 

似乎这么做输出的是词的概率,而不是想要的分布式分布的词向量
假如在‘i want to eat apple’这么一句话中,使用CBOW模型,挖去特征词eat,把其余词作为一个输入,输入网络中进行训练,我们的目标就是使得在输出层中大小为词汇表的这些节点中,eat对应的节点的概率最大,如果达成了这个任务,则我们认定此时的模型对词的处理很好。
 
如果是输入层-隐藏层-输出层这样的网络模型,显然我们是把onehot向量输入,经过权重矩阵W的某种计算,隐藏层输送了这个运算结果给输出层,输出层再经过另一个矩阵计算,softmax一下,得到最终的概率。既然这个模型整体能够预测出我们挖去的特征词eat,说明这个模型能够很好的处理语义,也就说明了我们训练出来的这个权重矩阵W,它和onehot向量组成的矩阵相乘得到的结果,可以当做一个稠密的词向量,拿去做其他的任务。
也就是说,看似我们的目标是获取特征词,或者获取上下文的单词,其实达成了这个目标之后,我们要的只是其中的权重矩阵而已。
因为最开始的词是一个稀疏向量,所以和W相乘后,得到的是一个W中的某行或某列值。W的大小如果是n×隐藏层数目,那么一个词以稀疏向量形式输入后得到的稠密向量的长度就应该是隐藏层长度,而不是最开始的词汇表长度,这就是降维
上述两个模型属于Word2vec工具包中,训练一个这样的模型也比较简单:
from gensim.models import Word2Vec
sentences='你的语料库' word2vec_model
= Word2Vec(sentences, size=100, window=5, min_count=5, workers=3, sg=0) #size: 你想要的词向量大小,就是你想把词汇表大小的原向量降到多低,默认是100 #window: 被预测的词和当前词的最大距离,默认为5。例如若为2,则在CBOW中根据当前词的前2个和后2个词来预测输出当前词的概率。 #min_count: 忽略出现频次低于它的词,默认为5 #workers: number of worker threads used to train the model (defaults to 3) #sg: training algorithm; 1 for skip-gram and 0 for CBOW (defaults to 0)

然后训练好这个模型后,我们就可以用这个模型做一些事情了,比如:

word2vec_model.wv["apple"]#预测apple的分布式词向量
word2vec_model.wv.most_similar("民生银行")  # 找最相似的词

注意sentences,需要是一个已经分好分词的结果:

#小型语料库
sentences = [["cat", "say", "meow"], ["dog", "say", "woof"]]

#大型语料库
sentences = gensim.models.word2vec.LineSentence('a.txt')   #  文本格式是 单词空格分开,一行为一个文档

sentences = gensim.models.word2vec.Text8Corpus('a.txt')   #  文本格式是 单词空格分开,一行为一个文

 训练的过程耗时很长,所以如果训练好了可以先保存起来以后用,免得下次还要训练很久

# 将训练的词向量结果保存至data/vectors.bin文件,一般将文件保存为二进制文件,方便以后做研究用。
model.save('data/vectors.bin')
# 为了方便查看训练的词向量结果,也可以将训练的结果保存至data/vectors.txt文本文件。
model.wv.save_word2vec_format('data/vectors.txt', binary=False)

,


原文地址:https://www.cnblogs.com/liuxiangyan/p/12526392.html