一、“嵌入”(Embeding)
自然语言处理少不了这个过程,尤其是神经网络模型,需要进行嵌入,那么什么是嵌入?我们先看看词典怎么解释
Embed这个词,英文的释义为, fix (an object) firmly and deeply in a surrounding mass, 也就是“嵌入”之意。例如:One of the bullets passed through Andrea's chest before embedding itself in a wall.
另外,这个词(的分词形式)在数学上也是一个专有名词,Embedding,它广泛存在于包括代数、拓扑与几何等诸多数学领域。它主要表征某个数学结构中的一个实例被包含在另外一个实例中,例如一个group它同时又是一个subgroup。
二、词嵌入(Word Embedding)
前面我们之所以要讨论Embedding在数学上的意思,就是因为这名称其实特别具有一种误导性,尤其是在翻译成中文的时候会令汉语思维下的我们更加难于把握住它的本质。
Word Embedding并不是要把单词像贴瓷砖那样镶嵌进什么地方。更重要的是,我们在把单词嵌入进另外一个空间时,要做到单射和structure-preserving,或者说我们更专注的是映射关系,而最终得到每个单词在另外一个空间中的表达也仅仅是之前设计好的映射关系的很自然的表达。
Word embedding 是NLP中一组语言模型(language modeling)和特征学习技术(feature learning techniques)的总称,这些技术会把词汇表中的单词或者短语(words or phrases)映射成由实数构成的向量上。
最简单的一种Word Embedding方法,就是基于词袋(BOW)的One-Hot表示。这种方法,把词汇表中的词排成一列,对于某个单词 A,如果它出现在上述词汇序列中的位置为 k,那么它的向量表示就是“第 k 位为1,其他位置都为0 ”的一个向量。
例如,有语料库如下:
John likes to watch movies. Mary likes movies too.
John also likes to watch football games.
把上述语料中的词汇表整理出来并排序(具体的排序原则可以有很多,例如可以根据字母表顺序,也可以根据出现在语料库中的先后顺序)
假设我们的词汇表排序结果如下:
{"John": 1, "likes": 2, "to": 3, "watch": 4, "movies": 5, "also":6, "football": 7, "games": 8, "Mary": 9, "too": 10}
那么则有如下word的向量表示:
John: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
likes: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
……
此时,你也可以进一步地把文档也表示成向量。方法就是直接将各词的词向量表示加和,于是则有原来的两句话的向量表示如下:
[1, 2, 1, 1, 2, 0, 0, 0, 1, 1]
[1, 1, 1, 1, 0, 1, 1, 1, 0, 0]
One-hot方法很简单,但是它的问题也很明显:
- 它没有考虑单词之间相对位置的关系;
- 词向量可能非常非常长!
针对第一个问题,你可能会想到n-gram方法,这确实是一个策略,但是它可能会导致计算量的急剧增长。因为n-gram已经在之前的文章中解释过了,下面我们来看另外一个方法:共现矩阵 (Cocurrence matrix)。
一个非常重要的思想是,我们认为某个词的意思跟它临近的单词是紧密相关的。这是我们可以设定一个窗口(大小一般是5~10),如下窗口大小是2,那么在这个窗口内,与rests 共同出现的单词就有life、he、in、peace。然后我们就利用这种共现关系来生成词向量。
例如,现在我们的语料库包括下面三份文档资料:
I like deep learning.
I like NLP.
I enjoy flying.
作为示例,我们设定的窗口大小为1,也就是只看某个单词周围紧邻着的那个单词。此时,将得到一个对称矩阵——共现矩阵。因为在我们的语料库中,I 和 like做为邻居同时出现在窗口中的次数是2,所以下表中I 和like相交的位置其值就是2。这样我们也实现了将word变成向量的设想,在共现矩阵每一行(或每一列)都是对应单词的一个向量表示。
虽然Cocurrence matrix一定程度上解决了单词间相对位置也应予以重视这个问题。但是它仍然面对维度灾难。也即是说一个word的向量表示长度太长了。这时,很自然地会想到SVD或者PCA等一些常用的降维方法。当然,这也会带来其他的一些问题,例如,我们的词汇表中有新词加入,那么就很难为他分配一个新的向量。但这并非本文要讨论的重点,我们不再赘述。
三、Word2Vec
我们已经见识了两种词嵌入的方式。而现在最常用、最流行的方法,就是Word2Vec。这是Tomas Mikolov在谷歌工作时发明的一类方法,也是由谷歌开源的一个工具包的名称。具体来说,Word2Vec中涉及到了两种算法,一个是CBOW一个是Skip-Gram。这也是因为深度学习流行起来之后,基于神经网络来完成的Word Embedding方法。
Word2Vec之所以现在这么流行,不同于之前的一些Word Embedding方法,
它能够自动实现:
- 单词语义相似性的度量;
- 词汇的语义的类比。
此处,语义的类比,反应的是类似下面这种关系:
- “国王” – “王后” ≈ “男” – “女”
- “英国” – “伦敦” ≈ “法国” – “巴黎” ≈ “首都”
SKip-Gram
对于Skip-Gram模型来说,它是要Generates each word in context given centre word。如下图所示:
所以总概率定义为:
其中下标denotes position in running text. 对于每个单词而言,则有
CBOW
对于CBOW模型来说,Condition on context, and generate centre word。如下图所示:
下面我们将在Python中实际使用一下Word2Vec,可以直接使用gensim [1],其实现了Word2Vec。语料库来自NLTK中的Brown语料库。实际中要获得更高质量的模型,往往意味着需要更大的语料库,当然这也意味着更多的训练时间。
Word2Vec比较占用时间,一般来说使用文件缓存机制,加速运行
import gensim, logging, os
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
import nltk
corpus = nltk.corpus.brown.sents()
fname = 'brown_skipgram.model'
if os.path.exists(fname):
# load the file if it has already been trained, to save repeating the slow training step below
model = gensim.models.Word2Vec.load(fname)
else:
# can take a few minutes, grab a cuppa
model = gensim.models.Word2Vec(corpus, size=100, min_count=5, workers=2, iter=50)
model.save(fname)
现在已经得到模型了,接下来我们来评估一下这个模型的质量。我们要评估一下下面这几个词之间的相近(或伴随)程度。
words = "woman women man girl boy green blue did".split()
for w1 in words:
for w2 in words:
print(w1, w2, model.similarity(w1, w2))
输出
woman woman 1.0
woman women 0.3451595268
woman man 0.607956254336
woman girl 0.761190251497
woman boy 0.558522930154
woman green 0.24118403927
woman blue 0.178044251325
woman did 0.0751838683173
women woman 0.3451595268
women women 1.0
women man 0.126646555737
women girl 0.292825346454
women boy 0.298552943639
women green 0.104096393379
women blue 0.0930137564485
women did 0.152766770859
图形化的结果显示:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
M = np.zeros((len(words), len(words)))
for i, w1 in enumerate(words):
for j, w2 in enumerate(words):
M[i,j] = model.similarity(w1, w2)
plt.imshow(M, interpolation='nearest')
plt.colorbar()
ax = plt.gca()
ax.set_xticklabels([''] + words, rotation=45)
ax.set_yticklabels([''] + words)
你也可以从词汇表中提取出跟某个单词(例如woman)最相关的k个词:
print(model.most_similar(positive=['woman'], topn=10))