程序猿成长史(二):Word2Vec的词嵌入技术(简单原理+代码实战)
Word2Vec的词嵌入技术
Word2Vec
word2vec技术是目前比较常用的一种词嵌入技术,同原有的独热码编码相比较,它能够刻画词语之间的关联关系,它主要是通过训练一个全联接的神经网络,再将神经网络的权重取出来替代原有的One-Hot独热码,以取得对于文本词汇向量的最优化表示的一种技术 .
其主要的方法有两种:
- Skip-grams (跳字模型)
- CBOW (连续词袋模型)
Skip-grams
我们首先以第一种Skip-grams方法为例:
其主要是通过设定一个长度固定的窗口window(此处假定window左右长度为2),用window的中心词的独热码作为全联接网络的输入x,将其周围的两个词语的独热码作为我们预测的label,从而在语料库中形成N个(中心词,周边词)的训练样例.
代码实现:
def OntHotEncode(mystr):
"""将语料库转换成对应的skip-grams的训练独热码语料"""
mywords = mystr.split(' ')
words_bag = set(mywords)
vocab_size = len(words_bag) # 词袋的大小
w2n_vocab = {
} # 从文字转换到数字
for i, item in enumerate(words_bag):
if item not in w2n_vocab.keys():
w2n_vocab[item] = i
for i, item in enumerate(mywords): # 用独热码OneHot代替原来的词语
vec = np.zeros(vocab_size)
vec[w2n_vocab[item]] = 1
mywords[i] = vec
window = [-2, -1, 1, 2] # 设定窗口的值,当前默认是2-grams
data = []
label = []
for i, item in enumerate(mywords): # 从当前的词语中提取出对应的skip-grams训练集合
for step in window: # 遍历窗口的元素
if 0 < i + step < len(mywords): # 对应添加元素
data.append(item)
label.append(mywords[i + step])
return data, label, vocab_size
搭建全联接网络并训练
在获取了训练样例以后,我们将这些对应的训练样例投入到神经网络中进行学习.训练处对应的权重即可.语料库越丰富,那么最后训练出来的向量就越能刻画词语之间的关联关系.
class Word2Vec:
def __init__(self, data, label, n_iters, vocab_size):
embed_size = 300 # 嵌入的维数是300
x = tf.placeholder(tf.float32, shape=(None, vocab_size))
y_label = tf.placeholder(tf.float32, shape=(None, vocab_size))
# 隐藏层部分
w = tf.Variable(tf.random_normal([vocab_size, embed_size])) # 最后的word2vec权重矩阵,每一行对应一个词语呢
b = tf.Variable(tf.random_normal([embed_size]))
hiddenlayers = tf.add(tf.matmul(x, w), b)
# 输出层部分
w2 = tf.Variable(tf.random_normal([embed_size, vocab_size]))
b2 = tf.Variable(tf.random_normal([vocab_size]))
y = tf.add(tf.matmul(hiddenlayers, w2), b2)
prediction = tf.nn.softmax(y) # 交叉熵计算的结果有可能log0得出nan要小心,采用。clip_by_value()的方法解决
loss = tf.reduce_mean(
-tf.reduce_sum(y_label * tf.log(tf.clip_by_value(prediction, 1e-8, 1.0)), reduction_indices=1))
trainstep = tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range(n_iters):
sess.run(trainstep, feed_dict={
x: data, y_label: label})
if i % 1 == 0:
theloss = sess.run(loss, feed_dict={
x: data, y_label: label})
print("loss:%s" % theloss)
#保存模型
Weight = sess.run(w, feed_dict={
x: data, y_label: label})
np.savetxt("w.txt", Weight, delimiter=',')
从代码可见,我们搭建了一个一层隐藏层,一层输出层,一层输入层的简单神经网络
损失函数方面采用的是交叉熵,主要原因是因为是一个多分类的任务嗷
简单的训练就可以得到对应的权重矩阵,保存起来即可.
嵌入向量代替原有独热码
这一步最后尚且还没有全部完成,其实就是读取对应的权重,但读取的会是一个(vocab_size, 1)的列表,这是由于是用的字符串读取的,需要用str.split()等函数还原成(None, vocab_size)的矩阵模式即可,工作量不多,也就留给大家啦.
用当初我们设定的独热码,点乘上矩阵,即是最后的嵌入向量啦.
#读取对应的嵌入权重(尚未还原哦)
weight = []
with open('w.txt') as f:
for i in f:
weight.append(i)
CBOW(连续词袋模型)
最后说一说关于CBOW模型,它同Skip-grams的不同仅仅是用周边词预测中心词,
所以训练数据集应该是N*(周边词,中心词),如果想深入了解可以自己尝试实现哦!
最后关于一些训练骚操作
在大型语料库时,训练往往比较困难因为样例很多,可以考虑欠采样的方法嗷
当然,如果没兴趣自己写也可以直接成为调库侠:
from gensim.models.word2vec import Word2Vec
model = Word2Vec(bag_character,
size=100, # 词向量维度
min_count=5, # 词频阈值
window=5, # 窗口大小
)
model.save('/home/myspace/word2vec.model')
return model
完整代码可自取:GitHub完整代码
参考文献:
word2vec笔记和实现
机器学习算法实现解析——word2vec源码解析
交叉熵代价函数(作用及公式推导)