一、语言模型
在统计自然语言处理中,语言模型指的是计算一个句子的概率模型。
传统的语言模型
- 词的表示是原始的、面向字符串的。
- 向量角度:更高维、更稀疏的向量。若词汇表大小为 N,每个字符串形式的词语字典序为 i,则其被表示为一个 N 维向量,该向量的第 i 维为 1,其他维都为 0。==》维度灾难
神经概率语言模型
- 词的表示是向量形式、面向语义的。
- 向量角度:低维(可以*控制维度,一般是100左右)
- 可以计算两个词的相似度
二、word2vec
是一种 Distributed Representation
方法, 其存在大量的非零分量,Distributed Representation
思想是:通过训练将某种语言中的每一个词映射成一个固定长度的短向量(相对于one-hot而言)。其本身其实是神经概率模型的副产品,是为了通过神经网络学习某个语言模型而产生的中间结果。
- 某个语言模型:CBOW 和 Skip-gram。
- 学习过程中降低复杂度近似方法:Hierarchical Softmax或Negative Sampling。
==》两个模型乘以两种方法,一共有四种实现。
1、模型
word2vec的两种模型:CBOW模型和Skip-gram模型。
两个模型都包含三层:输入层、投影层和输出层。
- CBOW:已知当前词 wt 的上下文 wt−2,wt−1,wt+1,wt+2的前提下预测当前词 wt.
- Skip-gram:已知当前词 wt 的前提下预测其上下文 wt−2,wt−1,wt+1,wt+2。
2、基于 Hierarchical Softmax 的 CBOW 模型
CBOW(Continuous Bag-of-Words Model),是一种根据上下文的词语预测当前词语的出现概率的语言模型。
其学习目标是最大化对数似然函数:
L=w∈C∑logp(w∣Context(w))
其中,w 表示语料库 C 中任意一个词。
对于 CBOW 模型:
- 输入层:上下文词语的词向量(训练模型,词向量是模型中的参数,会不断更新)
- 投影层:对输入层进行求和,即向量加法。
- 输出层:输出最可能的 w。可看为多分类问题(语料库 ∣C∣ 个词向量)
对于神经网络模型多分类,常使用 Softmax 回归:
hθ(x(i))=⎣⎢⎢⎡p(y(i)=1∣x(i);θ)p(y(i)=2∣x(i);θ)...p(y(i)=k∣x(i);θ)⎦⎥⎥⎤=∑j=1keθjTx(i)1⎣⎢⎢⎢⎡eθ1Tx(i)eθ2Tx(i)...eθkTx(i)⎦⎥⎥⎥⎤
==》缺点:需要对语料库中每个词语(类)都计算一遍输出概率并进行归一化。
Hierarchical Softmax,是输出层的树形结构。它是利用二叉树结构对Softmax进行优化。
- 非叶子节点(黄色节点)相当于神经元,进行二分类决策并输出1或0,分别代表向下左转或向下右转;
- 叶子节点代表语料库中的一个词语,于是每个词语都可以被 01 唯一地编码,并且其编码序列对应一个事件序列.
==》则可计算条件概率 p(w∣Context(w))
符号约定:
- pw:从根节点出发到达 w 对应叶子结点的路径;
- lw:路径中包含结点的个数;
- p1w,p2w,...,plww:路径 pw 中各个节点;
- d1w,d2w,...,dlww∈(0,1):词 w 的编码,djw 表示路径 pw 第 j 个节点对应的编码(根节点无编码)
- θ1w,θ2w,...,θlww∈Rm:路径 pw 中非叶节点对应的参数向量
则 w 的条件概率:
p(w∣Context(w))=j=2∏lwp(djw∣xw,θj−1w)
表示根节点到叶子节点 xw,经过 lw−1 个节点,编码从下标2开始(根节点无编码),对应的参数向量下标从1开始(根节点为1)。
其中,每一项是一个 Logistic Regression:
p(djw∣xw,θj−1w)=⎩⎨⎧σ(xwTθj−1w),1−σ(xwTθj−1w),djw=0djw=1
考虑到 d 只有0和1两种取值,则用指数形式表示为:
p(djw∣xw,θj−1w)=[σ(xwTθj−1w)]1−djw.[1−σ(xwTθj−1w)]djw
对目标函数对对数似然并将 p(w∣Context(w)) 带入目标函数:
L=w∈C∑logp(w∣Context(w))=w∈C∑logj=2∏lw{[σ(xwTθj−1w)]1−djw.[1−σ(xwTθj−1w)]djw}=w∈C∑j=2∑lw(1−djw).log[σ(xwTθj−1w)]+djw.log[1−σ(xwTθj−1w)]
每一项不妨简写为:
L(w,j)=(1−djw).log[σ(xwTθj−1w)]+djw.log[1−σ(xwTθj−1w)]
如何最大化对数似然函数:最大化每一项即可(近似)。
如何最大化每一项:随机梯度上升法。首先求函数对每个变量的偏导数;然后,对于每个样本,带入偏导数表达式可得函数在该维度的梯度,进行参数更新。
对函数 L(w,j) 有两个参数:每个节点的参数向量 θj−1w 和输出层的输入 xw,分别求偏导:
∂θj−1w∂L(w,j)=∂θj−1w∂{(1−djw).log[σ(xwTθj−1w)]+djw.log[1−σ(xwTθj−1w)]}
∵sigmoid 函数的导数为:σ′(x)=σ(x)[1−σ(x)]
于是代入上式得到:
∂θj−1w∂L(w,j)=(1−djw)[1−σ(xwTθj−1w)]xw−djwσ(xwTθj−1w)xw=[1−djw−σ(xwTθj−1w)]xw
于是 θj−1w 的更新表达式如下:
θj−1w:=θj−1w+η[1−djw−σ(xwTθj−1w)]xw
其中,η是学习率,学习率越大训练速度越快,但目标函数容易在局部区域来回抖动。
对 xw 求偏导,注意到在 L(w,j)=(1−djw).log[σ(xwTθj−1w)]+djw.log[1−σ(xwTθj−1w)] 中 xw 和 θj−1w 是对称的,所以可以将 θj−1w 换成 xw,得到关于 xw 的偏导数:
∂xw∂L(w,j)==[1−djw−σ(xwTθj−1w)]θj−1w
xw 是上下文的词向量的和,而非上下文单个词的词向量。如何将这个更新量应用到单个单词的词向量上?
==》直接将 xw 的更新量整个应用到每个单词的词向量上:
v(w~):=v(w~)+ηj=2∑lw∂xw∂L(w,j),w~∈Context(w)
其中,v(w~) 表示上下文某一个单词的词向量。
想法:可以取平均之后更新到每个词向量上,
伪代码:
e=0
xw=∑u∈Context(w)v(u)
For j=2:lw DO
{
3.1 q=σ(xwTθj−1w)
3.2 g=η(1−djw−q)
3.3 e:=e+gθj−1w
3.4 θj−1w:=θj−1w+gxw }FOR u∈Context(w) DO
{
v(u):=v(u)+e
}
3、基于Hierarchical Softmax的Skip-gram模型
Skip-gram只是逆转了CBOW的因果关系而已,即已知当前词语,预测上下文。其网络结构如图所示:
与CBOW模型的区别:
- 输入层不再是多个词向量,而是一个词向量;
- 投影层直接将输入层的词向量传递给输出层。
记:u:表示 w 的上下文中的一个词语。
语言模型的概率函数可以写作:
p(Context(w)∣w)=w∈Context(w)∏p(u∣w)
这里是一个词袋模型,所以 u 是无序的(相互独立的)
在Hierarchical Softmax思想下,每个 u 都可以编码为一条01路径:
p(u∣w)=j=2∏lup(dju∣v(w),θj−1u)
类似的,每项可以简写如下:
p(dju∣v(w),θj−1u)=[σ(v(w)Tθj−1u)]1−dju⋅[1−σ(v(w)Tθj−1u)]dju
将它们写到一块,得到目标函数:
L=w∈C∑logu∈Context(w)∏j=2∏lu{[σ(v(w)Tθj−1u)]1−dju⋅[1−σ(v(w)Tθj−1u)]dju}=w∈C∑u∈Context(w)∑j=2∑lu{(1−dju)⋅log[σ(v(w)Tθj−1u)]+dju⋅log[1−σ(v(w)Tθj−1u)]}
虽然上式对比CBOW多了一个u,但给定训练实例(一个词w和它的上下文{u}),u也是固定的。所以上式其实依然只有两个变量 xw 和 θj−1w,对其求偏导数:
∂xw∂L(w,u,j)=[1−dju−σ(v(w)Tθj−1u)]v(w)
省略求导过程,可以得到 θj−1w 的更新表达式:
θj−1u:=θj−1u+η[1−dju−σ(v(w)Tθj−1u)]v(w)
利用对称性可得 xw 的偏导数:
∂v(w)∂L(w,u,j)=[1−dju−σ(v(w)Tθj−1u)]θj−1u
于是得到 xw 的更新表达式:
v(w):=v(w)+ηu∈Context(w)∑j=2∑lu∂v(w)∂L(w,u,j)
4、Negative Sampling
对于Negative Sampling,负例是随机挑选出来的。据说Negative Sampling能提高速度、改进模型质量。
三、用gensim实现word2vec
1、API参数
在gensim中,word2vec
相关的API都在包gensim.models.word2vec
中。
和算法有关的参数都在类gensim.models.word2vec.Word2Vec
中。
2、参数
-
sentences
: 我们要分析的语料,可以是一个列表,或者从文件中遍历读出。后面我们会有从文件读出的例子。 -
size
: 词向量的维度,默认值是100。这个维度的取值一般与我们的语料的大小相关,如果是不大的语料,比如小于100M的文本语料,则使用默认值一般就可以了。如果是超大的语料,建议增大维度。 -
window
:即词向量上下文最大距离,这个参数在我们的算法原理篇中标记为c,window越大,则和某一词较远的词也会产生上下文关系。默认值为5。在实际使用中,可以根据实际的需求来动态调整这个window的大小。如果是小语料则这个值可以设的更小。对于一般的语料这个值推荐在[5,10]之间。 -
sg
: 即我们的word2vec两个模型的选择了。如果是0, 则是CBOW模型,是1则是Skip-Gram模型,默认是0即CBOW模型。 -
hs
: 即我们的word2vec两个解法的选择了,如果是0, 则是Negative Sampling,是1的话并且负采样个数negative大于0, 则是Hierarchical Softmax。默认是0即Negative Sampling。 -
negative
:即使用Negative Sampling时负采样的个数,默认是5。推荐在[3,10]之间。这个参数在我们的算法原理篇中标记为neg。 -
cbow_mean
: 仅用于CBOW在做投影的时候,为0,则算法中的xw为上下文的词向量之和,为1则为上下文的词向量的平均值。在我们的原理篇中,是按照词向量的平均值来描述的。个人比较喜欢用平均值来表示xw,默认值也是1,不推荐修改默认值。 -
min_count
:需要计算词向量的最小词频。这个值可以去掉一些很生僻的低频词,默认是5。如果是小语料,可以调低这个值。 -
iter
: 随机梯度下降法中迭代的最大次数,默认是5。对于大语料,可以增大这个值。 -
alpha
: 在随机梯度下降法中迭代的初始步长。算法原理篇中标记为η,默认是0.025。 -
min_alpha
: 由于算法支持在迭代的过程中逐渐减小步长,min_alpha
给出了最小的迭代步长值。随机梯度下降中每轮的迭代步长可以由iter
,alpha
,min_alpha
一起得出。这部分由于不是word2vec
算法的核心内容,因此在原理篇我们没有提到。对于大语料,需要对alpha, min_alpha,iter一起调参,来选择合适的三个值。
import pandas as pd
import gensim
import time
import pickle
import numpy as np
import csv,sys
vector_size = 100
maxInt = sys.maxsize
decrement = True
while decrement:
# decrease the maxInt value by factor 10
# as long as the OverflowError occurs.
decrement = False
try:
csv.field_size_limit(maxInt)
except OverflowError:
maxInt = int(maxInt/10)
decrement = True
#=======================================================================================================================
# 0 辅助函数
#=======================================================================================================================
def sentence2list(sentence):
return sentence.strip().split()
start_time = time.time()
#=======================================================================================================================
# 1 准备训练数据
#=======================================================================================================================
print("准备数据................ ")
df_train = pd.read_csv('train_set.csv',nrows=5000,engine='python')
df_test = pd.read_csv('test_set.csv',nrows=5000,engine='python')
sentences_train = list(df_train.loc[:, 'word_seg'].apply(sentence2list))
sentences_test = list(df_test.loc[:, 'word_seg'].apply(sentence2list))
sentences = sentences_train + sentences_test
print("准备数据完成! ")
#=======================================================================================================================
# 2 训练
#=======================================================================================================================
print("开始训练................ ")
model = gensim.models.Word2Vec(sentences=sentences, size=vector_size, window=5, min_count=5, workers=8, sg=0, iter=5)
print("训练完成! ")
#=======================================================================================================================
# 3 提取词汇表及vectors,并保存
#=======================================================================================================================
print(" 保存训练结果........... ")
wv = model.wv
vocab_list = wv.index2word
word_idx_dict = {}
for idx, word in enumerate(vocab_list):
word_idx_dict[word] = idx
vectors_arr = wv.vectors
vectors_arr = np.concatenate((np.zeros(vector_size)[np.newaxis, :], vectors_arr), axis=0)#第0位置的vector为'unk'的vector
f_wordidx = open('word_seg_word_idx_dict.pkl', 'wb')
f_vectors = open('word_seg_vectors_arr.pkl', 'wb')
pickle.dump(word_idx_dict, f_wordidx)
pickle.dump(vectors_arr, f_vectors)
f_wordidx.close()
f_vectors.close()
print("训练结果已保存到该目录下! ")
end_time = time.time()
print("耗时:{}s ".format(end_time - start_time))
参考:
- word2vec 中的数学原理详解
- https://github.com/Heitao5200/DGB/blob/master/feature/feature_code/train_word2vec.py
- 用gensim学习word2vec