TensorFlow实现word2vec(Skip-Gram、CBOW)代码记录

最近学习word2vec,发现一些文章写的有点,略。。(>﹏<),而且有些代码有错误,这里记录一些学习代码过程中的问题,这里构建的方式是Skip-Gram,代码不全部写出,只写一些觉得重要的地方。
首先,如果想要了解详细的数学原理,可以移步word2vec中的数学原理,文档中写的非常非常详细,推荐度max。
另外,使用TensorFlow实现这两种,代码很大程度是一样的,这里主要介绍Skip-Gram方式的实现,CBOW只是简要注明不同之处。

Skip-Gram

1.构建数据集

这里主要是对词语列表进行更进一步的操作,例如生成词典并编号等等:

def generate_dataset(data):
    num_words = len(data)
    count = [['UNK', -1]]
    count.extend(collections.Counter(data).most_common(vocabulary_size-1))

    dic = {}
    for val, count in count:
        dic[val] = len(dic)

    reverse_dic = dict(zip(dic.values(), dic.keys()))

    num_data = []
    for item in data:
        if item in dic:
            num_data.append(dic[item])
        else:
            num_data.append(0)

    return dic, reverse_dic, num_data, num_words

说明:

  • dic是生成的词典,按照词频从大到小选择了vocabulary_size个词语,注意特殊的 UNK表示其他未收录的词语,所以真正使用Counter计数的时候添加的应该是vocabulary_size-1个词语,词典的组织形式是(词语–序号)
  • reverse_dic是反转的词典,也就是将词典的组织形式转化为(序号–词典)形式
  • num_data与data的长度一样,只是将原本data中每个元素由具体单词(string)转化为序号(int)
  • num_words是词典长度
  • count的类型是collections.Counter
  • count中有一个UNK,主要是对于我们丢弃了一部分词语,例如之前去掉的高频停用词,或者规定很少出现的词语不纳入词典,将这些词语记为 UNK,可以看到在之后将词语列表转为序号列表时,将这些词标序号记为0
  • 关于生成dictionary,生成的唯一编号使用的len函数,毕竟每次增加一个词语,那么词典的长度就会增加一,所以就生成了唯一的编号了(这个地方很简单,但是防止有的时候转不过弯来还是提一下)
2.生成batch数据

由全部数据生成一个batch的数据:

index_data = 0
def generate_batch(batch_size, n_skips, window_skip):
    global index_data
    assert batch_size % n_skips == 0
    assert n_skips <= 2*window_skip

    batch_x = np.ndarray(shape=(batch_size), dtype=np.int32)
    batch_y = np.ndarray(shape=(batch_size, 1), dtype=np.int32)

    span = 2 * window_skip + 1
    buffer = collections.deque(maxlen=span)
    for _ in range(span):
        buffer.append(num_data[index_data])
        index_data = (index_data + 1)%num_words

    for i in range(batch_size // n_skips):
        target = window_skip
        avoid_target = [window_skip]

        for j in range(n_skips):
            while target in avoid_target:
                target = random.randint(0, span-1)
            avoid_target.append(target)
            batch_x[i*n_skips+j] = buffer[window_skip]
            batch_y[i*n_skips+j] = buffer[target]
        buffer.append(num_data[index_data])
        index_data = (index_data+1) % num_words

    return batch_x, batch_y

说明:

  • 对于传入参数的理解:n_skip表示我们为每个中心词所生成的样本(或者说是训练数据)数目,window_skip表示半径,举个例子:I will go to school by bus. 假设到了某一步,中心词是 to ,window_skip是2,也就是使用 to 来预测 will go school by四个词语,但是我们不一定要生成所有的(to --> will)这样的数据,而是在中间随即选出 n_skip条数据,也就是在2window_skip条数据中选出n_skip条数据作为训练数据,所以才会要求 n_skip<=2window_skip,即函数开头的 assert 所判定的。至于另一个判定,主要是保证每个中心词所生成的数据条数都一样(对于每个中心词,都生成n_skip条数据,所以一个batch数量,应该是n_skip的整数倍)
  • 关于batch_x, batch_y的维数问题,由于训练数据集其实就是一个词预测另一个词,所以它们的维数应该是一样的。但是可以看到batch_y被处理成了一个二维数组,这主要是因为下面使用的损失函数 nce_loss 的要求,其实(n, 1)的二维数组跟(n)维的列向量是一样的。
  • 注意deque的滑动,这是一个队列,使用一个中心词生成一组数据后,就会向后滑动一个单词,在队首“挤”出一个单词,在队尾添加一个单词。
  • 具体实现如何在 2*window_skip个词中随机选出n_skip个,这里使用了一个avoid_target数组,实现的很巧妙。
3.负采样计算

借助TensorFlow的函数,Skip_Gram网络的结构非常简单:

valid_exampled = np.random.choice(valid_window, valid_size, replace=False)

input_x = tf.placeholder(tf.int32, shape=[batch_size])
input_y = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_data = tf.constant(valid_exampled)

embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size]))
embed = tf.nn.embedding_lookup(embeddings, input_x)

nce_weight = tf.Variable(tf.truncated_normal([vocabulary_size, embedding_size],
                                              stddev=1.0 / np.sqrt(embedding_size)))
nce_bias = tf.Variable(tf.zeros([vocabulary_size]))

loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weight,
                      biases=nce_bias,
                      labels=input_y,
                      inputs=embed,
                      num_classes=vocabulary_size,
                      num_sampled=num_sample))
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate).minimize(loss)

说明:

  • 带有valid部分的代码都是用来生成验证数据的,主要是训练完成之后需要一个评价,可以随便生成一些验证数据,然后从词向量中按照相似度选出最相似的向量,看看以我们直观的感觉是否这些词是否类似。
  • embeddings 就是词向量矩阵(从维数上就可以看出来),关于这个embed,使用了tf.nn.embedding_lookup()函数,这个函数的作用原理其实非常简单,如下面的公式所示,如果如果左边的大矩阵是词向量矩阵,也就是embeddings,那么提供一个索引矩阵,然后按照索引,这里是0,2,3,把第一个矩阵的中第0,2,3行拿出来组成一个矩阵。所以这个函数的两个参数分别是embeddings 和 input_x,也就是拿出这一个batch的训练数据所对应的行向量。

[ 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.2 0.1 0.3 0.5 0.4 0.6 ]

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值