程序员充电站机器学习与数据挖掘

建模角度理解word embedding及tensorflow实

2017-06-30  本文已影响3542人  leepand

1、 前言

本篇主要介绍关键词的向量表示,也就是大家熟悉的word embedding。自Google 2013 年开源word2vec算法程序以后,它的简单、高效、实用,很快引起业界众人的关注和应用,为搜索引擎、[广告系统-谷歌的wide & deep learning][2]、[推荐系统][1]等互联网服务提供新的基础技术和思路。

何为Embedding?

开篇之前首先需要明白一个概念何为Embedding?Embedding可以看作是数学上的一个空间映射(Mapping):map( lambda y: f(x) ),该映射的特点是:单射(在数学里,单射函数为一函数,其将不同的引数连接至不同的值上。更精确地说,函数f被称为是单射时,对每一值域内的y,存在至多一个定义域内的x使得f(x) = y。)、映射前后结构不变,对应到word embedding概念中可以理解为寻找一个函数或映射,生成新的空间上的表达,把单词one-hot所表达的X空间信息映射到Y的多维空间向量。
接下来,将以模型的角度分解embedding映射函数及新空间内表达的建模过程:

非监督的“监督学习”

从应用角度,新空间内映射函数的学习方法不需要大量的人工标记样本就可以得到质量还不错的embedding向量,没有具体的应用任务导向,从这个角度可以看作非监督的学习过程,而从建模角度,向量提取的建模过程是 个分类模型,又可以看做是监督学习,只是这个监督没有实际的监督意义,当然后来有的应该将word2vec的前段表达方式喂给标注的过文本,形成真正意义上的监督学习,如Facebook的FastText

2、一层隐层神经网络

带有一个隐层的神经网络有以下普遍特性:理论上给定足够该隐层节点数,这个隐层可以近似拟合任意函数,本质上,隐层是上一层的嵌入(Embedding)函数的近似表示,而且可以被用做lookup表(后面会介绍),word2vec也是基于该层去找到输入word的嵌入向量表示,然后再建立下一层和当前层的连接(connections),来控制目标函数的误差。【进一步抽象,如果从统计的角度,其实不同层之间的统计关系是一种递归的广义线性关系(递归广义线性模型),每一层通过线性组合对前一层进行变换,然后以一些非线性连接函数(不同函数对应output label不同的统计分布,比如softmax对应多项目分布,sigmoid对应二项分布等)得到非线性结果喂给下一层,参见图rglm】


model_net.png rglm.png

3、Embedding函数

从前面的定义,我们期望在隐层中找到一个/组嵌入函数W(这里采用lookup table的方式),使得![][3]具体的,假设指定固定的向量维度,W("篮球")=(0.2, -0.4, 0.7, ...),W("苹果")=(0.0, 0.6, -0.1, ...),W初始化时可以赋值给每个维度一个随机数,并通过与output层连接建立学习模型/任务后得到有意义的向量。

4、建模

接下来来看看如何建立和训练模型。

5、抽样

基于上面的拆解,我们会发现其实训练过程涉及的参数数量会非常庞大,以上面的200000个单词的字典表为例,隐层嵌入200维的词向量,那么每次迭代的输入-隐层权重矩阵和隐层-输出层的权重矩阵都会有 200000 x 200 = 4000万个权重,在如此庞大的神经网络中进行梯度下降是相当慢的,而且需要大量的训练数据来调整这些权重并且避免过拟合。所以对性能的要求仍然很高,虽然上面已经采用lookup table的方式简化了一些计算,针对这个问题,Word2Vec的作者在论文提出了有效的方法,叫“negative sampling”,每个训练样本的训练只会更新一小部分的模型权重,从而降低计算负担,甚至是词向量的质量。基于对假设是,我们的数据中存在大量冗余和噪音,举例:对于“的”这种常用高频单词,我们会发现一些问题:当我们得到成对的单词训练样本时,**("的", "篮球") *这样的训练样本并不会给我们提供关于“篮球”更多的语义信息,因为“的”这样的噪音词在大部分单词的上下文中几乎都会出现。由于在语料中“的”这样的常用词出现概率很大,因此我们将会有大量的(”的“,...)这样的训练样本,而这些样本数量远远超过了我们学习“的”这个词向量所需的训练样本数。所以在设计抽样方法的时候可以对这样的样本直接排除在训练样本之外,对于其他样本对随机抽取少量的负样本进行参数的更新,而不是对one-hot向量中所有200000个位置对样本都进行计算,从而大大提高训练效率。
上面叙述的有点繁杂,总结起来就是在对给定input word计算softmax时,不去更新所有词表中word的输出概率,而是从该样本的output word之外随机抽样有限个(比如只抽样5个word)作为负样本计算其概率,进一步进行梯度和参数的更新。也就是说通过负样本抽样对于每次训练只更新(5+1)个beta向量对应的参数,也就是200
6=1200个参数,这样与4000万个相比,需要更新的参数占比仅为0.003%,效率提升可想而知。

6、基于tensorflow的实现

import os
def load_w2c_textcn_dataset(path='./data/'):
    """
    Returns
    --------
    word_list_all : a list
        a list of string (word).\n
     要求:中文语料需要先分词  
    """

    print("Load or Download chinese text corpus Dataset> {}".format(path))

    filename = 'wiki_cn.cut'
    word_list_all=[]
    with open(os.path.join(path, filename)) as f:
        for line in f:
            word_list=line.strip().split()
            for idx, word in enumerate(word_list):
                word_list[idx] = word_list[idx].decode('utf-8') 
                #print word_list[idx]
                word_list_all.append(word_list[idx])
    return word_list_all
words=load_w2c_textcn_dataset(path='./data/')
print len(words)
import collections
vocabulary_size = 200000
count = [['UNK', -1]]
count.extend(collections.Counter(words).most_common(vocabulary_size - 1))
dictionary = dict()

for word, _ in count:
    dictionary[word] = len(dictionary)
data = list()
unk_count = 0
for word in words:
    if word in dictionary:
        index = dictionary[word]
    else:
        index = 0  # dictionary['UNK']
        unk_count = unk_count + 1
    data.append(index)

count[0][1] = unk_count
reverse_dictionary = dict(zip(dictionary.values(), dictionary.keys())) 
del words
data_index = 0

def generate_batch(batch_size, num_skips, skip_window):
    global data_index
    batch = np.ndarray(shape=(batch_size), dtype=np.int32)
    labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
    span = 2 * skip_window + 1  # [ skip_window target skip_window ]
    buf = collections.deque(maxlen=span)
    for _ in xrange(span):
        buf.append(data[data_index])
        data_index = (data_index + 1) % len(data)
    for i in xrange(batch_size // num_skips):
        target = skip_window  # target label at the center of the buffer
        targets_to_avoid = [ skip_window ]
        for j in xrange(num_skips):
            while target in targets_to_avoid:
                target = random.randint(0, span - 1)
            targets_to_avoid.append(target)
            batch[i * num_skips + j] = buf[skip_window]
            labels[i * num_skips + j, 0] = buf[target]
        buf.append(data[data_index])
        data_index = (data_index + 1) % len(data)
    return batch, labels
import tensorflow as tf
import collections
import numpy as np
batch_size = 128
embedding_size = 128  # 生成向量维度.
skip_window = 2       # 左右窗口.
num_skips = 2        # 同一个keyword产生label的次数.
num_sampled = 64      # 负样本抽样数.

graph = tf.Graph()

with graph.as_default(), tf.device('/cpu:0'):
    train_dataset = tf.placeholder(tf.int32, shape=[batch_size])
    train_labels  = tf.placeholder(tf.int32, shape=[batch_size, 1])
  
    embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
    softmax_weights = tf.Variable(
        tf.truncated_normal([vocabulary_size, embedding_size], stddev=1.0/np.sqrt(embedding_size)))
    softmax_biases = tf.Variable(tf.zeros([vocabulary_size]))
  
    embed = tf.nn.embedding_lookup(embeddings, train_dataset)
    loss = tf.reduce_mean(
        tf.nn.sampled_softmax_loss(weights=softmax_weights, biases=softmax_biases, inputs=embed,
                                   labels=train_labels, num_sampled=num_sampled, num_classes=vocabulary_size))

    optimizer = tf.train.AdagradOptimizer(1.0).minimize(loss)

    norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))
    normalized_embeddings = embeddings / norm
num_steps = 500001
import random
with tf.Session(graph=graph) as session:
    tf.global_variables_initializer().run()
    average_loss = 0
    for step in range(num_steps):
        batch_data, batch_labels = generate_batch(batch_size, num_skips, skip_window)
        feed_dict = {train_dataset : batch_data, train_labels : batch_labels}
        _, l = session.run([optimizer, loss], feed_dict=feed_dict)
        average_loss += l
        if step % 100000 == 0 and step > 0:
            print('Average loss at step %d: %f' % (step, average_loss / 100000))
            average_loss = 0
    word2vec = normalized_embeddings.eval()
distances = -word2vec[dictionary[u'数据']].reshape((1, -1)).dot(word2vec.T)
inds = np.argsort(distances.ravel())[1:6]
print(' '.join([reverse_dictionary[i] for i in inds]))
----------------------------------------------
资料 统计 显示 信息 证据

[1] Peter McCullagh, John A Nelder, Generalized linear models., , 1989
[2] The seminal paper, A Neural Probabilistic Language Model (Bengio, et al. 2003) has a great deal of insight about why word embeddings are powerful.
[3]:https://erikbern.com/2014/06/28/recurrent-neural-networks-for-collaborative-filtering.html
[4]:https://research.googleblog.com/2016/06/wide-deep-learning-better-together-with.html?utm_source=tuicool&utm_medium=referral

上一篇 下一篇

猜你喜欢

热点阅读