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源码解析
交叉熵代价函数(作用及公式推导)