词向量

本教程来自深度学习框架PaddlePaddle的入门引导。我没有修改前面的理论知识部分,是在后面加入了自己的应用实例便于理解。

词向量

本教程源代码目录在book/word2vec, 初次使用请参考PaddlePaddle安装教程,更多内容请参考本教程的视频课堂

背景介绍

本章我们介绍词的向量表征,也称为word embedding。词向量是自然语言处理中常见的一个操作,是搜索引擎、广告系统、推荐系统等互联网服务背后常见的基础技术。

在这些互联网服务里,我们经常要比较两个词或者两段文本之间的相关性。为了做这样的比较,我们往往先要把词表示成计算机适合处理的方式。最自然的方式恐怕莫过于向量空间模型(vector space model)。
在这种方式里,每个词被表示成一个实数向量(one-hot vector),其长度为字典大小,每个维度对应一个字典里的每个词,除了这个词对应维度上的值是1,其他元素都是0。

One-hot vector虽然自然,但是用处有限。比如,在互联网广告系统里,如果用户输入的query是“母亲节”,而有一个广告的关键词是“康乃馨”。虽然按照常理,我们知道这两个词之间是有联系的——母亲节通常应该送给母亲一束康乃馨;但是这两个词对应的one-hot vectors之间的距离度量,无论是欧氏距离还是余弦相似度(cosine similarity),由于其向量正交,都认为这两个词毫无相关性。 得出这种与我们相悖的结论的根本原因是:每个词本身的信息量都太小。所以,仅仅给定两个词,不足以让我们准确判别它们是否相关。要想精确计算相关性,我们还需要更多的信息——从大量数据里通过机器学习方法归纳出来的知识。

在机器学习领域里,各种“知识”被各种模型表示,词向量模型(word embedding model)就是其中的一类。通过词向量模型可将一个 one-hot vector映射到一个维度更低的实数向量(embedding vector),如 embedding()=[0.3,4.2,1.5,...],embedding()=[0.2,5.6,2.3,...] 。在这个映射到的实数向量表示中,希望两个语义(或用法)上相似的词对应的词向量“更像”,这样如“母亲节”和“康乃馨”的对应词向量的余弦相似度就不再为零了。

词向量模型可以是概率模型、共生矩阵(co-occurrence matrix)模型或神经元网络模型。在用神经网络求词向量之前,传统做法是统计一个词语的共生矩阵 X X是一个 |V|×|V| 大小的矩阵, Xij 表示在所有语料中,词汇表V(vocabulary)中第i个词和第j个词同时出现的词数, |V| 为词汇表的大小。对 X 做矩阵分解(如奇异值分解,Singular Value Decomposition [5]),得到的U即视为所有词的词向量:

X=USVT

但这样的传统做法有很多问题:

1) 由于很多词没有出现,导致矩阵极其稀疏,因此需要对词频做额外处理来达到好的矩阵分解效果;

2) 矩阵非常大,维度太高(通常达到 106106 的数量级);

3) 需要手动去掉停用词(如although, a,…),不然这些频繁出现的词也会影响矩阵分解的效果。

基于神经网络的模型不需要计算存储一个在全语料上统计的大表,而是通过学习语义信息得到词向量,因此能很好地解决以上问题。在本章里,我们将展示基于神经网络训练词向量的细节,以及如何用PaddlePaddle训练一个词向量模型。

效果展示

本章中,当词向量训练好后,我们可以用数据可视化算法t-SNE[4]画出词语特征在二维上的投影(如下图所示)。从图中可以看出,语义相关的词语(如a, the, these; big, huge)在投影上距离很近,语意无关的词(如say, business; decision, japan)在投影上的距离很远。


图1. 词向量的二维投影

另一方面,我们知道两个向量的余弦值在 [1,1] 的区间内:两个完全相同的向量余弦值为1, 两个相互垂直的向量之间余弦值为0,两个方向完全相反的向量余弦值为-1,即相关性和余弦值大小成正比。因此我们还可以计算两个词向量的余弦相似度:

similarity: 0.899180685161
please input two words: big huge

please input two words: from company
similarity: -0.0997506977351

以上结果可以通过运行calculate_dis.py, 加载字典里的单词和对应训练特征结果得到,我们将在应用模型中详细描述用法。

模型概览

在这里我们介绍三个训练词向量的模型:N-gram模型,CBOW模型和Skip-gram模型,它们的中心思想都是通过上下文得到一个词出现的概率。对于N-gram模型,我们会先介绍语言模型的概念,并在之后的训练模型中,带大家用PaddlePaddle实现它。而后两个模型,是近年来最有名的神经元词向量模型,由 Tomas Mikolov 在Google 研发[3],虽然它们很浅很简单,但训练效果很好。

语言模型

在介绍词向量模型之前,我们先来引入一个概念:语言模型。
语言模型旨在为语句的联合概率函数 P(w1,...,wT) 建模, 其中 wi 表示句子中的第i个词。语言模型的目标是,希望模型对有意义的句子赋予大概率,对没意义的句子赋予小概率。
这样的模型可以应用于很多领域,如机器翻译、语音识别、信息检索、词性标注、手写识别等,它们都希望能得到一个连续序列的概率。 以信息检索为例,当你在搜索“how long is a football bame”时(bame是一个医学名词),搜索引擎会提示你是否希望搜索”how long is a football game”, 这是因为根据语言模型计算出“how long is a football bame”的概率很低,而与bame近似的,可能引起错误的词中,game会使该句生成的概率最大。

对语言模型的目标概率 P(w1,...,wT) ,如果假设文本中每个词都是相互独立的,则整句话的联合概率可以表示为其中所有词语条件概率的乘积,即:

P(w1,...,wT)=t=1TP(wt)

然而我们知道语句中的每个词出现的概率都与其前面的词紧密相关, 所以实际上通常用条件概率表示语言模型:

P(w1,...,wT)=t=1TP(wt|w1,...,wt1)
### N-gram neural model 在计算语言学中,n-gram是一种重要的文本表示方法,表示一个文本中连续的n个项。基于具体的应用场景,每一项可以是一个字母、单词或者音节。 n-gram模型也是统计语言模型中的一种重要方法,用n-gram训练语言模型时,一般用每个n-gram的历史n-1个词语组成的内容来预测第n个词。 Yoshua Bengio等科学家就于2003年在著名论文 Neural Probabilistic Language Models
[1](#参考文献)
中介绍如何学习一个神经元网络表示的词向量模型。文中的神经概率语言模型(Neural Network Language Model,NNLM)通过一个线性映射和一个非线性隐层连接,同时学习了语言模型和词向量,即通过学习大量语料得到词语的向量表达,通过这些向量得到整个句子的概率。用这种方法学习语言模型可以克服维度灾难(curse of dimensionality),即训练和测试数据不同导致的模型不准。注意:由于“神经概率语言模型”说法较为泛泛,我们在这里不用其NNLM的本名,考虑到其具体做法,本文中称该模型为N-gram neural model。 我们在上文中已经讲到用条件概率建模语言模型,即一句话中第 t 个词的概率和该句话的前t1个词相关。可实际上越远的词语其实对该词的影响越小,那么如果考虑一个n-gram, 每个词都只受其前面`n-1`个词的影响,则有:
P(w1,...,wT)=t=nTP(wt|wt1,wt2,...,wtn+1)
给定一些真实语料,这些语料中都是有意义的句子,N-gram模型的优化目标则是最大化目标函数:

1Ttf(wt,wt1,...,wtn+1;θ)+R(θ)

其中 f(wt,wt1,...,wtn+1) 表示根据历史n-1个词得到当前词 wt 的条件概率, R(θ) 表示参数正则项。


图2. N-gram神经网络模型

图2展示了N-gram神经网络模型,从下往上看,该模型分为以下几个部分:
- 对于每个样本,模型输入 wtn+1,...wt1 , 输出句子第t个词为字典中|V|个词的概率。

每个输入词 wtn+1,...wt1 首先通过映射矩阵映射到词向量 C(wtn+1),...C(wt1)

  • 然后所有词语的词向量连接成一个大向量,并经过一个非线性映射得到历史词语的隐层表示:

    g=Utanh(θTx+b1)+Wx+b2

    其中, x 为所有词语的词向量连接成的大向量,表示文本历史特征;θ U b1 b2 W 分别为词向量层到隐层连接的参数。g表示未经归一化的所有输出单词概率, gi 表示未经归一化的字典中第 i 个单词的输出概率。

  • 根据softmax的定义,通过归一化gi, 生成目标词 wt 的概率为:

    P(wt|w1,...,wtn+1)=egwt|V|iegi

  • 整个网络的损失值(cost)为多类分类交叉熵,用公式表示为

    J(θ)=i=1Nc=1|V|yiklog(softmax(gik))

    其中 yik 表示第 i 个样本第k类的真实标签(0或1), softmax(gik) 表示第i个样本第k类softmax输出的概率。

Continuous Bag-of-Words model(CBOW)

CBOW模型通过一个词的上下文(各N个词)预测当前词。当N=2时,模型如下图所示:


图3. CBOW模型

具体来说,不考虑上下文的词语输入顺序,CBOW是用上下文词语的词向量的均值来预测当前词。即:

context=xt1+xt2+xt+1+xt+24

其中 xt 为第 t 个词的词向量,分类分数(score)向量 z=Ucontext,最终的分类 y 采用softmax,损失函数采用多类分类交叉熵。

Skip-gram model

CBOW的好处是对上下文词语的分布在词向量上进行了平滑,去掉了噪声,因此在小数据集上很有效。而Skip-gram的方法中,用一个词预测其上下文,得到了当前词上下文的很多样本,因此可用于更大的数据集。


图4. Skip-gram模型

如上图所示,Skip-gram模型的具体做法是,将一个词的词向量映射到2n个词的词向量( 2n 表示当前输入词的前后各 n 个词),然后分别通过softmax得到这2n个词的分类损失值之和。

总结

本章中,我们介绍了词向量、语言模型和词向量的关系、以及如何通过训练神经网络模型获得词向量。在信息检索中,我们可以根据向量间的余弦夹角,来判断query和文档关键词这二者间的相关性。在句法分析和语义分析中,训练好的词向量可以用来初始化模型,以得到更好的效果。在文档分类中,有了词向量之后,可以用聚类的方法将文档中同义词进行分组。希望大家在本章后能够自行运用词向量进行相关领域的研究。

参考文献

  1. Bengio Y, Ducharme R, Vincent P, et al. A neural probabilistic language model[J]. journal of machine learning research, 2003, 3(Feb): 1137-1155.
  2. Mikolov T, Kombrink S, Deoras A, et al. Rnnlm-recurrent neural network language modeling toolkit[C]//Proc. of the 2011 ASRU Workshop. 2011: 196-201.
  3. Mikolov T, Chen K, Corrado G, et al. Efficient estimation of word representations in vector space[J]. arXiv preprint arXiv:1301.3781, 2013.
  4. Maaten L, Hinton G. Visualizing data using t-SNE[J]. Journal of Machine Learning Research, 2008, 9(Nov): 2579-2605.
  5. https://en.wikipedia.org/wiki/Singular_value_decomposition

Paddle代码:

训练

# 训练
import math

import paddle.v2 as paddle

embsize = 32
hiddensize = 256
N = 5


def wordemb(inlayer):
    wordemb = paddle.layer.table_projection(
        input=inlayer,
        size=embsize,
        param_attr=paddle.attr.Param(
            name="_proj",
            initial_std=0.001,
            learning_rate=1,
            l2_rate=0, ))
    return wordemb


def main():
    paddle.init(use_gpu=False, trainer_count=1)
    word_dict = paddle.dataset.imikolov.build_dict()
    dict_size = len(word_dict)
    firstword = paddle.layer.data(
        name="firstw", type=paddle.data_type.integer_value(dict_size))
    secondword = paddle.layer.data(
        name="secondw", type=paddle.data_type.integer_value(dict_size))
    thirdword = paddle.layer.data(
        name="thirdw", type=paddle.data_type.integer_value(dict_size))
    fourthword = paddle.layer.data(
        name="fourthw", type=paddle.data_type.integer_value(dict_size))
    nextword = paddle.layer.data(
        name="fifthw", type=paddle.data_type.integer_value(dict_size))

    Efirst = wordemb(firstword)
    Esecond = wordemb(secondword)
    Ethird = wordemb(thirdword)
    Efourth = wordemb(fourthword)

    contextemb = paddle.layer.concat(input=[Efirst, Esecond, Ethird, Efourth])
    hidden1 = paddle.layer.fc(
        input=contextemb,
        size=hiddensize,
        act=paddle.activation.Sigmoid(),
        layer_attr=paddle.attr.Extra(drop_rate=0.5),
        bias_attr=paddle.attr.Param(learning_rate=2),
        param_attr=paddle.attr.Param(
            initial_std=1. / math.sqrt(embsize * 8), learning_rate=1))
    predictword = paddle.layer.fc(
        input=hidden1,
        size=dict_size,
        bias_attr=paddle.attr.Param(learning_rate=2),
        act=paddle.activation.Softmax())

    def event_handler(event):
        if isinstance(event, paddle.event.EndIteration):
            if event.batch_id % 100 == 0:
                result = trainer.test(
                    paddle.batch(
                        paddle.dataset.imikolov.test(word_dict, N), 32))
                print "Pass %d, Batch %d, Cost %f, %s, Testing metrics %s" % (
                    event.pass_id, event.batch_id, event.cost, event.metrics,
                    result.metrics)

    cost = paddle.layer.classification_cost(input=predictword, label=nextword)
    parameters = paddle.parameters.create(cost)
    adam_optimizer = paddle.optimizer.Adam(
        learning_rate=3e-3,
        regularization=paddle.optimizer.L2Regularization(8e-4))
    trainer = paddle.trainer.SGD(cost, parameters, adam_optimizer)
    trainer.train(
        paddle.batch(paddle.dataset.imikolov.train(word_dict, N), 32),
        num_passes=30,
        event_handler=event_handler)


if __name__ == '__main__':
    main()

应用

应用模型:
1.查看词向量

embeddings = parameters.get("_proj").reshape(len(word_dict), embsize)

print embeddings[word_dict['apple']]

2.计算词语之间的余弦距离
两个向量之间的距离可以用余弦值来表示,余弦值在 [−1,1]的区间内,向量间余弦值越大,其距离越近。

from scipy import spatial

emb_1 = embeddings[word_dict['world']]
emb_2 = embeddings[word_dict['would']]

print spatial.distance.cosine(emb_1, emb_2)

TensorFlow代码

可以在 tensorflow/g3doc/tutorials/word2vec/word2vec_basic.py(https://tensorflow.googlesource.com/tensorflow/+/master/tensorflow/g3doc/tutorials/word2vec/word2vec_basic.py) 查看到一个最简单的实现,这里也有一个同学的复制版https://github.com/Yinzo/Extra-comment-for-word2vec_basic.py-in-tensorflow-tutorial/blob/master/extra_comments_word2vec_basic.py 。这个基本的例子提供的代码可以完成下载一些数据,简单训练后展示结果。
一旦你觉得已经完全掌握了这个简单版本,你可以查看 tensorflow/models/embedding/word2vec.py(https://tensorflow.googlesource.com/tensorflow/+/master/tensorflow/models/embedding/word2vec.py),这里提供了一些更复杂的实现,同时也展示了TensorFlow的一些更进阶的特性,比如如何更高效地使用线程将数据送入文本模型,再比如如何在训练中设置检查点等等。

参考文章:https://github.com/PaddlePaddle/book/tree/develop/04.word2vec

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值