http://www.jianshu.com/p/d44ce1e3ec2f

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、建模

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

  • 数据准备
    为给模型准备数据,我们首先需要定义或获取n个样本:![][4]
    假如我们有一个句子“姚明 的 篮球 打得 很不错”。常规方式是首先由统计语言模型,由中间词预测周围词(SKIP-GRAM),或由周围词预测中间词(CBOW)等方式,然后以指定的窗口向前推进,以SKIP-GRAM方式为例,假设推进窗口为2,我们可以得到样本对:("篮球","的"),("篮球","姚明"),("篮球","打得"),("篮球","很不错"),X skip至"打得"时,得到样本对 :("打得","篮球"),("打得","的"),("打得","很不错"),以此类推...我们可以得到用于模型的训练样本。

  • 样本表示
    样本拆解出来了,接下来如何用数值来表达这些样本对呢?常用的办法是将所有的训练数据,即“word”对抽取出唯一不重复的单词来构建词典表(vocabulary),然后将样本数据中的“word”表达成one-hot编码,编码时只对有值的位置上为1其他位置均为0,以上面例子为例,“姚明 的 篮球 打得 很不错”。基于这个句子可以构建维度为5的词典表:{"姚明":0,"":1,"的":2,"篮球":3,"打得":4,"很不错":5},那么训练样本("篮球","姚明")即可表达为([0,0,1,0,0],0),看起来比较像常规的多分类数据了,这里为了好理解Y表示成了位置编号,后续在模型中仍以one-hot向量表达。

  • 各层条件分布
    神经网络基于这些训练样本将会输出一个概率分布,这个概率代表着我们的词典中的每个词是output word的可能性。更一般的,假设隐层有K个节点(即生成word对应vector向量的维度),对每个样本,我们需要做两件事情:

    • 给定隐层后预测output word的概率,即需要建个模型来估计![][5]
    • 将观测到的input word喂给隐层嵌入函数,得到隐层的概率分布,![][6]用连接函数表达即上面提到的(常见的一般会是K个关于x线性组合的方程组,后面会讲到为何不用该方式)![][3]

    接下来我们需要构建整体的似然函数进行优化:

  • 目标函数
    分别建立input层-隐层及隐层-output层的连接函数(RGLM),input层和隐层的函数上面已给出,如果假设p(y|w)为正态分布,则 log-likelihood loss便是(negative) L2 loss:![][7],如果假设p(y|w)为多项分布,则likelihood loss便是softmax loss:![][8]从训练样本可以看出,output层为多分类,即隐层-output可采用softmax loss.
    为了准确预测output word,该网络需要根据上述损失函数学习参数矩阵W和R(output层),实际上,对于我们来说,整个学习任务是为了学习隐层的W函数,即隐层节点参数。当然对于其他任务,比如神经网络推荐或Fasttext,网络构造过程类似,只是学习的任务是学习输出层的参数和结构。

  • 模型训练
    常规优化方法会采用梯度下降和反向传播,由上面的样本定义,我们的训练样本中input和output均以one-hot表示,向量极其稀疏(通常完整字典表会是几十万维,假设200000),仅有一个位置的数值为1,其余均为0,如果input到隐层的嵌入函数采用常见方式的话,假设节点数即嵌入向量维度为200,则隐层参数矩阵每个样本的迭代将会是1x200000的向量和200000x200矩阵的相乘,显然会带来巨大计算资源的消耗,其实每个样本的隐层参数仅需要根据one-hot向量中数值为1的索引对应的隐层参数参数矩阵的该索引行对应的向量取出即可:

     
    embedding.png

    经过抽象后我们可以得到上面定义的Embedding函数/参数矩阵:

     
    embedding-abstract.png

    这种方式其实联系上面提到的lookup table就容易理解了,即模型中的隐层权重矩阵便成了一个”查找表“(lookup table),进行矩阵计算时,只需要直接去查输入的one-hot向量中提取非零位置的索引,在隐层的对应行输出就是每个输入单词的“嵌入词向量”,该过程即完成了嵌入的动作。
    对于输出层:
    经过隐层的嵌入计算,input word会被映射为1x200的dense向量,再喂给输出层经过softmax的分类器的计算,对随机给定任意output word的嵌入向量计算其预测概率:![][8],这样基于同一input word,替换不同的beta(output word的嵌入向量)得到不同output word的预测概率。

    至此,数据的表示及目标损失函数的定义以及模型训练过程已拆解完毕。接下来,再看看训练性能提升和优化的方法。

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
  • batch数据生成器
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

建模角度理解word embedding及tensorflow实现的更多相关文章

  1. Word Embedding理解

    一直以来感觉好多地方都吧Word Embedding和word2vec混起来一起说,所以导致对这俩的区别不是很清楚. 其实简单说来就是word embedding包含了word2vec,word2ve ...

  2. DeepNLP的核心关键/NLP词的表示方法类型/NLP语言模型 /词的分布式表示/word embedding/word2vec

    DeepNLP的核心关键/NLP语言模型 /word embedding/word2vec Indexing: 〇.序 一.DeepNLP的核心关键:语言表示(Representation) 二.NL ...

  3. 从Word Embedding到Bert模型—自然语言处理中的预训练技术发展史(转载)

    转载 https://zhuanlan.zhihu.com/p/49271699 首发于深度学习前沿笔记 写文章   从Word Embedding到Bert模型—自然语言处理中的预训练技术发展史 张 ...

  4. 深度学习word embedding猜测性别初探

    根据用户的一些特征数据,如果能推测出用户的性别借此提高产品的服务质量.广告的精准性等都是极好的. 机器学习方法有很多,而且一般都可以达到不错的效果,比如svm或神经网络等. 本文使用的代码参考——&l ...

  5. GloVe:另一种Word Embedding方法

    若想深层地理解GloVe和本文,最好了解SVD, word2vec(skip-gram为主)的相关知识.若仅寻求一种新的word embedding方法,可以不必了解以上前置知识. 一言以蔽之,Glo ...

  6. zz从Word Embedding到Bert模型—自然语言处理中的预训练技术发展史

    从Word Embedding到Bert模型—自然语言处理中的预训练技术发展史 Bert最近很火,应该是最近最火爆的AI进展,网上的评价很高,那么Bert值得这么高的评价吗?我个人判断是值得.那为什么 ...

  7. 机器学习-NLP之Word embedding 原理及应用

    概述 自然语言是非常复杂多变的,计算机也不认识咱们的语言,那么咱们如何让咱们的计算机学习咱们的语言呢?首先肯定得对咱们的所有文字进行编码吧,那咱们很多小伙伴肯定立马就想出了这还不简单嘛,咱们的计算机不 ...

  8. 预训练语言模型的前世今生 - 从Word Embedding到BERT

    预训练语言模型的前世今生 - 从Word Embedding到BERT 本篇文章共 24619 个词,一个字一个字手码的不容易,转载请标明出处:预训练语言模型的前世今生 - 从Word Embeddi ...

  9. 转:如何学习SQL(第二部分:从关系角度理解SQL)

    转自:http://blog.163.com/mig3719@126/blog/static/285720652010950825538/ 6. 从关系角度理解SQL 6.1. 关系和表 众所周知,我 ...

随机推荐

  1. Delphi 的链式代码

    有了一系列的 Helper, Delphi 也可以使用链式代码了. //譬如要把 3.1415926 中的 141 提取为一个整数: var num: Integer; begin num := Pi ...

  2. Mui --- app与服务器之间的交互原理、mui ajax使用

    1.APP与服务器之间的交互原理 app端(客户端)与服务端的交互其实理解起来和容易,客户端想服务器端发送请求,服务器端进行数据运算后返回最终结果.结果可以是多种格式: 1.text 文本格式 2.x ...

  3. ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积01, 在控制台实现

    在电商产品模块中必经的一个环节是:当选择某一个产品类别,动态生成该类别下的所有属性和属性项,这些属性项有些是以DropDownList的形式存在,有些是以CheckBoxList的形式存在.接着,把C ...

  4. IIS7.5标识介绍

    应用程序池的标识是运行应用程序池的工作进程所使用的服务帐户名称.默认情况下,应用程序池以 Network Service 用户帐户运行,该帐户拥有低级别的用户权限.您可以将应用程序池配置为以 Wind ...

  5. Java Calendar,Date,DateFormat,TimeZone,Locale等时间相关内容的认知和使用(6) Locale

    本章介绍Locale. 1 Locale 介绍 Locale 表示地区.每一个Locale对象都代表了一个特定的地理.政治和文化地区. 在操作 Date, Calendar等表示日期/时间的对象时,经 ...

  6. 理解 block

    开始:Block 简介 Block 是 iOS 4.0 和 Mac OSX 10.6 引入的一个新特性. Block 可以极大的简化代码. 他们可以帮助你减少代码, 减少对代理的依赖, 并且写出更加简 ...

  7. C#判断字符串的是否是汉字

    //第一种方法:正则表达式 private bool IsChinese(string Text) { ; i < Text.Length; i++) { if (Regex.IsMatch(T ...

  8. Android按返回键退出程序但不销毁,程序后台运行,同QQ退出处理方式

    @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BA ...

  9. OLE、OCX和ActiveX控件之间的比较

      OLE(Object Linking and Embedding,对象连接与嵌入) 一.过去的OLE和今天的OLE 最初的OLE含义是指在程序之间链接和嵌入对象数据,它提供了建立混合文档的手段(资 ...

  10. fastjson 过滤不需要的字段或者只要某些字段

    /* * 第一种:在对象响应字段前加注解,这样生成的json也不包含该字段. * @JSONField(serialize=false)   * private String name;   */ / ...