词嵌入向量WordEmbedding的原理和生成方法

 
WordEmbedding

词嵌入向量(WordEmbedding)是NLP里面一个重要的概念,我们可以利用WordEmbedding将一个单词转换成固定长度的向量表示,从而便于进行数学处理。本文将介绍WordEmbedding的使用方式,并讲解如何通过神经网络生成WordEmbedding。

WordEmbedding的使用

使用数学模型处理文本语料的第一步就是把文本转换成数学表示,有两种方法,第一种方法可以通过one-hot矩阵表示一个单词,one-hot矩阵是指每一行有且只有一个元素为1,其他元素都是0的矩阵。针对字典中的每个单词,我们分配一个编号,对某句话进行编码时,将里面的每个单词转换成字典里面这个单词编号对应的位置为1的one-hot矩阵就可以了。比如我们要表达“the cat sat on the mat”,可以使用如下的矩阵表示。

 
one-hot矩阵表示法

one-hot表示方式很直观,但是有两个缺点,第一,矩阵的每一维长度都是字典的长度,比如字典包含10000个单词,那么每个单词对应的one-hot向量就是1X10000的向量,而这个向量只有一个位置为1,其余都是0,浪费空间,不利于计算。第二,one-hot矩阵相当于简单的给每个单词编了个号,但是单词和单词之间的关系则完全体现不出来。比如“cat”和“mouse”的关联性要高于“cat”和“cellphone”,这种关系在one-hot表示法中就没有体现出来。

WordEmbedding解决了这两个问题。WordEmbedding矩阵给每个单词分配一个固定长度的向量表示,这个长度可以自行设定,比如300,实际上会远远小于字典长度(比如10000)。而且两个单词向量之间的夹角值可以作为他们之间关系的一个衡量。如下表示:

 
WordEmbedding表示法

通过简单的余弦函数,我们就可以计算两个单词之间的相关性,简单高效:

 
两个向量相关性计算

因为WordEmbedding节省空间和便于计算的特点,使得它广泛应用于NLP领域。接下来我们讲解如何通过神经网络生成WordEmbedding。

WordEmbedding的生成

WordEmbedding的生成我们使用tensorflow,通过构造一个包含了一个隐藏层的神经网络实现。

下面是下载数据和加载数据的代码,一看就懂。训练数据我们使用的是http://mattmahoney.net/dc/enwik8.zip数据,里面是维基百科的数据。

def maybe_download(filename, url):
"""Download a file if not present, and make sure it's the right size."""
if not os.path.exists(filename):
filename, _ = urllib.urlretrieve(url + filename, filename)
return filename # Read the data into a list of strings.
def read_data(filename):
"""Extract the first file enclosed in a zip file as a list of words."""
with zipfile.ZipFile(filename) as f:
data = tf.compat.as_str(f.read(f.namelist()[0])).split()
return data def collect_data(vocabulary_size=10000):
url = 'http://mattmahoney.net/dc/'
filename = maybe_download('enwik8.zip', url)
vocabulary = read_data(filename)
print(vocabulary[:7])
data, count, dictionary, reverse_dictionary = build_dataset(vocabulary, vocabulary_size)
del vocabulary # Hint to reduce memory.
return data, count, dictionary, reverse_dictionary

接下来是如何构建训练数据。构建训练数据主要包括统计词频,生成字典文件,并且根据字典文件给训练源数据中的单词进行编号等工作。我们生成的字典不可能包含所有的单词,一般我们按照单词频率由高到低排序,选择覆盖率大于95%的单词加入词典就可以了,因为词典越大,覆盖的场景越大,同时计算开销越大,这是一个均衡。下面的代码展示了这个过程,首先统计所有输入语料的词频,选出频率最高的10000个单词加入字典。同时在字典第一个位置插入一项“UNK"代表不能识别的单词,也就是未出现在字典的单词统一用UNK表示。然后给字典里每个词编号,并把源句子里每个词表示成在字典中的编号。我们可以根据每个词的编号查找WordEmbedding中的向量表示。

def build_dataset(words, n_words):
"""Process raw inputs into a dataset."""
count = [['UNK', -1]]
# [['UNK', -1], ['i', 500], ['the', 498], ['man', 312], ...]
count.extend(collections.Counter(words).most_common(n_words - 1))
# dictionary {'UNK':0, 'i':1, 'the': 2, 'man':3, ...}
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 += 1
data.append(index)
count[0][1] = unk_count
reversed_dictionary = dict(zip(dictionary.values(), dictionary.keys())) # data: "I like cat" -> [1, 21, 124]
# count: [['UNK', 349], ['i', 500], ['the', 498], ['man', 312], ...]
# dictionary {'UNK':0, 'i':1, 'the': 2, 'man':3, ...}
# reversed_dictionary: {0:'UNK', 1:'i', 2:'the', 3:'man', ...}
return data, count, dictionary, reversed_dictionary

接下来我们看一下如何将源句子转换成训练过程的输入和输出,这一步是比较关键的。有两种业界常用的WordEmbedding生成方式,Continuous Bag Of Words (CBOW)方法和n-gram方法,我们采用n-gram方法。训练的目的是获得能够反映任意两个单词之间关系的单词向量表示,所以我们的输入到输出的映射也要翻译两个单词之间的关联。n-gram的思路是将所有的源句子按固定长度(比如128个单词)分割成很多batch。对于每个batch,从前往后每次选取长度为skip_window的窗口(我们设定skip_window=5)。对于窗口中的5个单词,我们生成两个source-target数据对,这两个source-target对的source都是窗口中间的单词,也就是第三个单词,然后从另外四个单词中随机选取两个作为两个target单词。然后窗口向后移动一个单词,每次向后移动一个位置获取下5个单词,一共循环64次,获取到64X2=128个source-target对,作为一个batch的训练数据。总的思路就是把某个单词和附近的单词组对,作为输入和输出。这里同一个source单词,会被映射到不同的target单词,这样理论上可以获取任意两个单词之间的关系。

比如对于句子"cat and dog play balls on the floor",第一个窗口就是“cat and dog play balls",生成的两个source-target对可能是下面中的任意两个:
dog -> cat
dog -> and
dog -> balls
dog -> play

第二个窗口是"and dog play balls on",生成的两个source-target对可能是下面中的任意两个:
play -> and
play -> balls
play -> dog
play -> on

下面是代码实现:

def generate_batch(data, batch_size, num_skips, skip_window):
global data_index
assert batch_size % num_skips == 0
assert num_skips <= 2 * skip_window
batch = np.ndarray(shape=(batch_size), dtype=np.int32)
context = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
span = 2 * skip_window + 1 # span含义 -> [ skip_window input_word skip_window ] # 初始化最大长度为span的双端队列,超过最大长度后再添加数据,会从另一端删除容不下的数据
# buffer: 1, 21, 124, 438, 11
buffer = collections.deque(maxlen=span)
for _ in range(span):
buffer.append(data[data_index])
data_index = (data_index + 1) % len(data)
for i in range(batch_size // num_skips): # 128 / 2
# target: 2
target = skip_window # input word at the center of the buffer
# targets_to_avoid: [2]
targets_to_avoid = [skip_window] # 需要忽略的词在当前span的位置 # 更新源单词为当前5个单词的中间单词
source_word = buffer[skip_window] # 随机选择的5个span单词中除了源单词之外的4个单词中的两个
for j in range(num_skips):
while target in targets_to_avoid: # 随机重新从5个词中选择一个尚未选择过的词
target = random.randint(0, span - 1)
targets_to_avoid.append(target) # batch添加源单词
batch[i * num_skips + j] = source_word
# context添加目标单词,单词来自随机选择的5个span单词中除了源单词之外的4个单词中的两个
context[i * num_skips + j, 0] = buffer[target] # 往双端队列中添加下一个单词,双端队列会自动将容不下的数据从另一端删除
buffer.append(data[data_index])
data_index = (data_index + 1) % len(data)
# Backtrack a little bit to avoid skipping words in the end of a batch
data_index = (data_index + len(data) - span) % len(data)
return batch, context

接下来是构建神经网络的过程,我们构建了一个包含一个隐藏层的神经网络,该隐藏层包含300个节点,这个数量和我们要构造的WordEmbedding维度一致。

with graph.as_default():

  # 定义输入输出
train_sources = tf.placeholder(tf.int32, shape=[batch_size])
train_targets = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32) # 初始化embeddings矩阵,这个就是经过多步训练后最终我们需要的embedding
embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0)) # 将输入序列转换成embedding表示, [batch_size, embedding_size]
embed = tf.nn.embedding_lookup(embeddings, train_sources) # 初始化权重
weights = tf.Variable(tf.truncated_normal([embedding_size, vocabulary_size], stddev=1.0 / math.sqrt(embedding_size)))
biases = tf.Variable(tf.zeros([vocabulary_size])) # 隐藏层输出结果的计算, [batch_size, vocabulary_size]
hidden_out = tf.transpose(tf.matmul(tf.transpose(weights), tf.transpose(embed))) + biases # 将label结果转换成one-hot表示, [batch_size, 1] -> [batch_size, vocabulary_size]
train_one_hot = tf.one_hot(train_targets, vocabulary_size) # 根据隐藏层输出结果和标记结果,计算交叉熵
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=hidden_out, labels=train_one_hot)) # 随机梯度下降进行一步反向传递
optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(cross_entropy) # 计算验证数据集中的单词和字典表里所有单词的相似度,并在validate过程输出相似度最高的几个单词
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))
normalized_embeddings = embeddings / norm
valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)
similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b=True) # 参数初始化赋值
init = tf.global_variables_initializer()

我们首先随机初始化embeddings矩阵,通过tf.nn.embedding_lookup函数将输入序列转换成WordEmbedding表示作为隐藏层的输入。初始化weights和biases,计算隐藏层的输出。然后计算输出和target结果的交叉熵,使用GradientDescentOptimizer完成一次反向传递,更新可训练的参数,包括embeddings变量。在Validate过程中,对测试数据集中的单词,利用embeddings矩阵计算测试单词和所有其他单词的相似度,输出相似度最高的几个单词,看看它们相关性如何,作为一种验证方式。

通过这个神经网络,就可以完成WordEmbedding的训练,继而应用于其他NLP的任务。

完整代码可以参考Git Demo Code

参考:
https://www.tensorflow.org/tutorials/word2vec
http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
http://adventuresinmachinelearning.com/word2vec-tutorial-tensorflow/

词嵌入向量WordEmbedding的更多相关文章

  1. [DeeplearningAI笔记]序列模型2.10词嵌入除偏

    5.2自然语言处理 觉得有用的话,欢迎一起讨论相互学习~Follow Me 2.10词嵌入除偏 Debiasing word embeddings Bolukbasi T, Chang K W, Zo ...

  2. 词向量表示:word2vec与词嵌入

    在NLP任务中,训练数据一般是一句话(中文或英文),输入序列数据的每一步是一个字母.我们需要对数据进行的预处理是:先对这些字母使用独热编码再把它输入到RNN中,如字母a表示为(1, 0, 0, 0, ...

  3. 词嵌入之FastText

    什么是FastText FastText是Facebook于2016年开源的一个词向量计算和文本分类工具,它提出了子词嵌入的方法,试图在词嵌入向量中引入构词信息.一般情况下,使用fastText进行文 ...

  4. 词向量 词嵌入 word embedding

    词嵌入 word embedding embedding 嵌入 embedding: 嵌入, 在数学上表示一个映射f:x->y, 是将x所在的空间映射到y所在空间上去,并且在x空间中每一个x有y ...

  5. [ DLPytorch ] word2vec&词嵌入

    word2vec WordEmbedding 对词汇进行多维度的描述,形成一个密集的矩阵.这样每两个词之间的相似性可以通过进行内积的大小体现出来.越大说明距离越远,则越不相似. Analogies(类 ...

  6. cips2016+学习笔记︱简述常见的语言表示模型(词嵌入、句表示、篇章表示)

    在cips2016出来之前,笔者也总结过种类繁多,类似词向量的内容,自然语言处理︱简述四大类文本分析中的"词向量"(文本词特征提取)事实证明,笔者当时所写的基本跟CIPS2016一 ...

  7. DeepLearning.ai学习笔记(五)序列模型 -- week2 自然语言处理与词嵌入

    一.词汇表征 首先回顾一下之前介绍的单词表示方法,即one hot表示法. 如下图示,"Man"这个单词可以用 \(O_{5391}\) 表示,其中O表示One_hot.其他单词同 ...

  8. DLNg序列模型第二周NLP与词嵌入

    1.使用词嵌入 给了一个命名实体识别的例子,如果两句分别是“orange farmer”和“apple farmer”,由于两种都是比较常见的,那么可以判断主语为人名. 但是如果是榴莲种植员可能就无法 ...

  9. NLP领域的ImageNet时代到来:词嵌入「已死」,语言模型当立

    http://3g.163.com/all/article/DM995J240511AQHO.html 选自the Gradient 作者:Sebastian Ruder 机器之心编译 计算机视觉领域 ...

随机推荐

  1. canvas背景

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. String的非空判断、Integer的非空判断、list的大小判断,对象的非空判断

    1.String的非空判断. StringUtils.isNotEmpty(String str); 2.Integer的非空判断. null != Integer ; 3.list的大小判断. li ...

  3. oracle 之分析函数 over (partition by ...order by ...)

    一:分析函数overOracle从8.1.6开始提供分析函数,分析函数用于计算基于组的某种聚合值,它和聚合函数的不同之处是对于每个组返回多行,而聚合函数对于每个组只返回一行. 1.分析函数和聚合函数的 ...

  4. 【oracle笔记1】基础知识大集锦:增删改,数据类型,用户操作,持续更新中···

    什么是数据库?数据库就是用来存储和管理数据的仓库.首先我来简单介绍一下各数据库的背景,常见的数据库如下,oracle:甲骨文公司(市场占用率最高),oracle也是一个公司名,翻译过来就是甲骨文的意思 ...

  5. Eclipse新导入的项目中ctrl+点击指定方法名或类名没有反应,不能跳转问题

    项目没有转成java项目 解决方法:右击项目名---选择properties----点击Project Facets,这样就可以实现ctrl+左键点击跳转了. 转成java项目后会报错 解决办法:选中 ...

  6. 原生js实现简单的随机点名系统

    <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8 ...

  7. C++笔记011:C++对C的扩展——变量检测增强

    原创笔记,转载请注明出处! 点击[关注],关注也是一种美德~ 在C语言中重复定义多个同名的变量是合法的,多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上. 在C++中,不允许定义多个同名的 ...

  8. 竞赛题解 - Ikki's Story IV-Panda's Trick

    Ikki's Story IV-Panda's Trick - 竞赛题解 也算是2-sat学习的一个节点吧 终于能够自己解决一道2-sat的题了 ·题目 一个圆上有n个点按顺时针编号为 0~n-1 , ...

  9. lower_case_table_name

    linux上是区分表名大小写的,但是可以通过 my.cnf文件中设置不区分! 1.找到my.cnf文件的所在地. find / -name my.cnf 找到这个文件的位置.我服务器上的位置是 /us ...

  10. 02.将python3作为centos7的默认python命令

    博客为日常工作学习积累总结: 由于个人兴趣爱好对python有了解: 1.安装Python3: 参考博客:https://zhuanlan.zhihu.com/p/47868341 安装依赖包: yu ...