肥猫、忆臻作者

手把手教你用飞桨做词向量模型 SkipGram

在做 NLP 的任务时,一个非常 basic 的操作就是如何编码自然语言中的符号,例如词、短语,甚至词缀。目前流行的方法有大约三种:

•  特征工程:这类方法依赖于手工特征,例如tf-idf 同时考虑词频和词的稀缺度;

统计方法:统计上常常通过矩阵分解(如SVD、EVD)来建模大规模文档集合;

•  神经网络:目前非常流行通过神经网络端到端的建模语言模型,得到词向量副产品;


今天要讲解的就是SkipGram 模型就属于第三种方法,它的主要思想是利用的词义的分布式表示。除了让您彻底弄懂什么是语言模型以及 SkipGram 的基本原理。我们还会详细的说明如何一步步的用飞桨(PaddlePaddle)实现它。


1.什么是词向量


首先我们需要了解什么是词向量。NLP和图像不太一样,图像的输入本身就是一个有数值特征的矩阵,而 NLP 的输入通常只是一堆自然语言的符号,不方便计算机直接计算。因此,在计算语言学中,我们通常会希望用数值向量来表示这些符号。例如现在我们希望比较词汇“米饭”与“猪肉”和“家具”之间词义的相似性,可以考虑用下面这样的 two-stage 范式。


• 将词汇用向量来表示,例如这里以及

• 考虑某种数值距离去度量,例如内积。这里有。类似的,同理我们也可以得到;从数值大小可以判断,与“家具”相比,“米饭”和“猪肉”更相似。


从上面这个例子可以看出,使用向量数值表示法最关键的地方在于如何获取词汇的向量的表示,而 SkipGram 就是一个良方。


2.什么是语言模型


词向量一般不是直接获取的,而是某些任务的副产品。它们通常是随机初始化的,然后通过不断的数值优化过程中获得语义信息,例如上述的相似性。因此,训练词向量的办法可以有很多,但是如何高效的获得高质量的词向量很重要,另外任务也应该有一定的可拓展性,例如语料充足,不需要额外标注。

 语言模型是一个非常好的选择。因为它语料充足,只要有文章,有帖子,那就有数据;同时由于其任务的特殊性,不需要人工进行额外的数据标注(网上有很多称这是无监督,但我觉得不是特别合适,不需要数据标注和无监督概念有所差异)。那么什么是语言模型呢?


语言模型就是用来衡量一句话出现的概率的。例如

•  

•  

显然,第二句话一般不会出自于一个神智正常的人之口,因此他的概率很低。而一句话 的概率可以用条件概率分解如下:


这里 表示词的上下文,是对的近似,否则计算复杂度是指数的。其中一种近似就是 n-gram,即


如上图所示的例子,当n=5时, 表示已知前 4 个词,预测下一个词的概率。由于让模型提高 P(s) 的概率等价于让模型提高每个 P(wi|ci)的概率,因此语言模型又可以被理解为已知上下文时中心词不确定性的度量。


3.什么是 SkipGram


经过前两节的解释,相信您对词向量有了很深的认识了。这一小节中我将会介绍 SkipGram,一种有效训练语言模型的方法。


说到 SkipGram,一定有同学会想到 CBOW。实际上 CBOW 更符合常人的思考逻辑,它建模词语上下文的方法很简单,如下图所示:

它从若干文档的文段中随机抽取出5 个连续的词, 然后类似做完形填空,希望模型能够根据上下文 预测。而 SkipGram 则恰恰相反,如下图所示,它是拿用中心词去预测上下文:

虽然看起来 CBOW 更合理,但很多文献指出,用 SkipGram 训出来的词向量效果更好。笔者分析可能存在下面一些原因:


•   SkipGram 用一个中心词去预测上下文,这样相当于对这个中心词的表示要求更高,这就好像一个学生(中心词)同时受到了多个老师(上下文)的教导(这个学习的过程可以被理解为中间的梯度传播),效果肯定比一个老师教导多个学生(因此梯度是均分的,没有区分性,而且由于梯度均分,容易破坏一个窗口中词向量的异构性)效果要好得多;

•   其次,SkipGram 这种强调中心词的结构对某些具有较低频率的生僻词比较友好,因此低频词也可以学到质量较高的向量表示;


但可能是因为 CBOW 的结构相对简单些,经验显示,CBOW 的训练速度要比 SkipGram 快的多,因此两者其实各有优势。


拿上面提到的例子 “Can you please come here ?” 说明 SkipGram 的流程。假设滑动窗口的长度为 5,那么现在窗口 cover 住了片段 “can you please come here”。此时以中心词 please 为输入并度量与上下文 can, you,come, here 的相似度,优化时希望这个值尽量高。

在工程上,实现词向量模型有很多trick,例如概率平滑化,高频词抽样等。但如果做个 demo 不需要考虑太多这些细节。不过无论是 CBOW 还是 SkipGram 都无法规避一个问题,就是过高的词典容量。正常情况下,英语词典的容量在 3000 ~ 4000 上下,因此当训练语料很大时会造成巨大的计算负担。为了权衡质量和效率,目前最常用的方法就是负采样。通俗的来说,就是我不再把整个词典当成负样本了,而是随机抽取若干词作为负样本。实现时,这个随机抽取的数量是一个超参数,大概是 20 ~ 30 之间,这样很明显大大提高了计算效率。另外,也有人指出,用一些重要性采样的技术可以进一步改善效果。


4.用飞桨实现


现在你已经基本了解了什么是SkipGram,下面我们就用强大的飞桨一步一步实现它。


现在你已经基本了解了什么是SkipGram,而实现它需要借助现有的深度学习框架。飞桨是百度自主研发的深度学习框架,功能非常强大,同时支持稠密参数、稀疏参数并行训练;静态网络、动态网络等。而且有非常丰富的中英文文档,非常方便您使用。下面我们就用强大的飞桨一步一步实现它.


首先,我们需要导入一些必要的计算库。

# PaddlePaddle 计算引擎.importpaddlefrompaddle import fluid
# 一些常用的科学计算库.importnumpy as npimportmatplotlib.pyplot as plt


然后设置定义一些超参数,用于控制网络结构和训练逻辑。

EMBEDDING_DIM =64  # 词向量维度.WINDOW_SIZE =5     #滑动窗口大小.BATCH_SIZE =200    #迭代 batch 大小.EPOCH_NUM =10      #训练的轮数.RANDOM_STATE =0    #设置伪随机数种子.


然后就是文本数据,这里使用飞桨自带(会自动下载)的 PTB 数据集,导入如下:

from paddle.dataset import imikolov
word_vocab =imikolov.build_dict()vocab_size =len(word_vocab)
# 打印 PTB 数据字典的容量大小.print("imikolov 字典大小为 "+str(vocab_size))
# 类似 Pytorch 的 DataLoader, 用于在训练时做 batch, 很方便.data_loader =paddle.batch(imikolov.test(word_vocab, WINDOW_SIZE), BATCH_SIZE)


imikolov字典大小为 2074


下面我们需要搭建SkipGram 的网络结构,我们用一个函数打包如下:

def build_neural_network():    assertWINDOW_SIZE %2==1   medium_num = WINDOW_SIZE //2
    # 定义输入变量, 是从文本中截取的连续的文本段.   var_name_list = [str(i) +"-word"for i inrange(0, WINDOW_SIZE)]   word_list = [fluid.layers.data(name=n, shape=[1], dtype="int64")for n in var_name_list]
    # 取中心词作为输入, 而周围上下文作为输出.   input_word = word_list[medium_num]   output_context = word_list[:medium_num] + word_list[medium_num +1:]
    # 将输入输出都做词向量表示, 并且将输出拼起来.   embed_input = fluid.layers.embedding(        input=input_word, size=[vocab_size, EMBEDDING_DIM],       dtype="float32",is_sparse=True,param_attr="input_embedding")   embed_output_list = [fluid.layers.embedding(        input=w, size=[vocab_size, EMBEDDING_DIM], dtype="float32",       is_sparse=True,param_attr="output_embedding")for w in output_context]   concat_output = fluid.layers.concat(input=embed_output_list,axis=1)
    # 用 -log(sigmoid(score)) 作为度量损失函数.    var_score =fluid.layers.matmul(embed_input, concat_output, transpose_x=True)   avg_loss =0-fluid.layers.mean(fluid.layers.log(fluid.layers.sigmoid(var_score)))
    # 使用 Adam 优化算法, 并注意需要返回变量定义名.   fluid.optimizer.AdamOptimizer().minimize(avg_loss)    returnavg_loss, var_name_list


类似于 Tensorflow,运行 PaddlePaddle 计算引擎前需要一些热身代码。

# 确定执行的环境, 如果支持 CUDA 可以调用CUDAPlace 函数.device_place =fluid.CPUPlace()executor = fluid.Executor(device_place)
main_program =fluid.default_main_program()star_program =fluid.default_startup_program()
# 固定伪随机数种子, 一般用于保证论文效果可复现.main_program.random_seed =RANDOM_STATEstar_program.random_seed =RANDOM_STATE
# 定义模型的架构 (之前定义函数输出) 以及模型的输入.train_loss, tag_list =build_neural_network()feed_var_list =[main_program.global_block().var(n) for n in tag_list]data_feeder =fluid.DataFeeder(feed_list=feed_var_list, place=device_place)


下面开始训练模型的流程,将迭代器产生的 batch 不断喂入网络中:

executor.run(star_program)forepoch_idx inrange(EPOCH_NUM):   total_loss, loss_list =0.0, []
    forbatch_data in data_loader():       total_loss +=float(executor.run(           main_program, feed=data_feeder.feed(batch_data),           fetch_list=[train_loss])[0])   loss_list.append(total_loss)    print("[迭代轮数{:4d}], 在训练集的损失为{:.6f}".format(epoch_idx, total_loss))


[迭代轮数    0], 在训练集的损失为 75.395110

[迭代轮数 1], 在训练集的损失为 2.346059

[迭代轮数 2], 在训练集的损失为 0.797208

[迭代轮数 3], 在训练集的损失为 0.413886

[迭代轮数 4], 在训练集的损失为 0.254423

[迭代轮数 5], 在训练集的损失为 0.171255

[迭代轮数 6], 在训练集的损失为 0.121907

[迭代轮数 7], 在训练集的损失为 0.090095

[迭代轮数 8], 在训练集的损失为 0.068378

[迭代轮数    9], 在训练集的损失为 0.052923


我们可以将刚才训练过程中的损失用 matplotlib 的库函数画出来。

plt.plot(np.array(range(0, len(loss_list))),loss_list)


好啦,以上就是本次所要分享。总的来说,本节我们主要讲述了什么是词向量,什么是语言模型,SkipGram 算法的内容以及其特性,相对 CBOW 来说它对低频词更友好,而且词向量质量更佳,最后我们还细致的教您一步一步用飞桨实现一个简单的 SkipGram 模型。希望您多多支持,咱们下期再会。

 

想与更多的深度学习开发者交流,请加入飞桨官方QQ群:796771754


如果您想详细了解更多飞桨的相关内容,请参阅以下文档。


官网地址:https://www.paddlepaddle.org.cn


Skip-Gram相关内容参考项目地址:

https://github.com/PaddlePaddle/models/tree/v1.5.1/PaddleRec/word2vec

PaddlePaddle
PaddlePaddle

PaddlePaddle是百度独立研发的深度学习平台,易用,高效,灵活可伸缩,可支持海量图像识别分类、机器翻译和自动驾驶等多个领域业务需求,现已全面开源。

工程词向量语言模型PaddlePaddle
1
暂无评论
暂无评论~