深度学习进阶:自然语言处理入门:第3章 word2vec
作者:互联网
深度学习进阶:自然语言处理入门
第3章 word2vec
本章的主题仍是单词的分布式表示。在上一章中,我们使 用基于计数的方法得到了单词的分布式表示。本章我们将讨论该方法的替代 方法,即基于推理的方法。
顾名思义,基于推理的方法使用了推理机制。当然,这里的推理机制用 的是神经网络。本章,著名的 word2vec 将会登场。
本章的目标是实现一个简单的 word2vec。这个简单的 word2vec 会优 先考虑易理解性,从而牺牲一定的处理效率。因此,我们不会用它来处理大 规模数据集,但用它处理小数据集毫无问题。
3.1 基于推理的方法和神经网络
用向量表示单词的研究最近正在如火如荼地展开,其中比较成功的方 法大致可以分为两种:一种是基于计数的方法;另一种是基于推理的方法。
3.1.1 基于计数的方法的问题
基于计数的方法使用整个语料库的统计数据(共现矩阵和 PPMI 等), 通过一次处理(SVD 等)获得单词的分布式表示。而基于推理的方法使用 神经网络,通常在 mini-batch 数据上进行学习。这意味着神经网络一次只 需要看一部分学习数据(mini-batch),并反复更新权重。
基于计数的方法一次性处理全部学习数据;反之,基于推理的方法使用部分学习数据逐步学习。
3.1.2 基于推理的方法的概要
基于推理的方法的主要操作是**“推理”。**
3.1.3 神经网络中单词的处理方法
如图 3-5 所示,输入层由 7 个神经元表示,分别对应于 7 个单词(第 1 个神经元对应于 you,第 2 个神经元对应于 say)
现在事情变得很简单了。因为只要将单词表示为向量,这些向量就可以由构成神经网络的各种“层”来处理。比如,对于one-hot表示的某个单词, 使用全连接层对其进行变换的情况如图 3-6 所示。(省略了偏置)
我们看一下代码。这里的全连接层变换可以写成如下
import numpy as np
c = np.array([[1, 0, 0, 0, 0, 0, 0]]) # 输入
W = np.random.randn(7, 3) # 权重
h = np.dot(c, W) # 中间节点
print(W)
print(h)
#输出
[[-1.30982294 0.19485001 -0.49979452]
[-1.77466539 -2.67810488 2.7992046 ]
[-0.20747764 -0.68246166 0.7149981 ]
[ 0.18558413 -0.61176428 2.25844791]
[-0.70263837 0.63946127 -0.33276184]
[-0.31945603 0.07161013 1.18615179]
[ 2.01949978 -0.5961003 -1.01233551]]
[[-1.30982294 0.19485001 -0.49979452]]
class MatMul:
def __init__(self, W):
self.params = [W]
self.grads = [np.zeros_like(W)]
self.x = None
def forward(self, x):
W, = self.params
out = np.dot(x, W)
self.x = x
return out
def backward(self, dout):
W, = self.params
dx = np.dot(dout, W.T)
dW = np.dot(self.x.T, dout)
self.grads[0][...] = dW
return dx
这里,仅为了提取权重的行向量而进行矩阵乘积计算好像不是很有效 率。关于这一点,我们将在 4.1 节进行改进。另外,上述代码的功能也可以 使用第 1 章中实现的 MatMul 层完成,
c = np.array([[1, 0, 0, 0, 0, 0, 0]])
W = np.random.randn(7, 3)
layer = MatMul(W)
h = layer.forward(c)
print(h)
# [[-0.70012195 0.25204755 -0.79774592]]
3.2 简单的 word2vec
word2vec 一词最初用来指程序或者工具,但是随着该词的流行, 在某些语境下,也指神经网络的模型。正确地说,CBOW 模型 和 skip-gram 模型是 word2vec 中使用的两个神经网络。
3.2.1 CBOW模型的推理
CBOW 模型是根据上下文预测目标词的神经网络(“目标词”是指中间 的单词,它周围的单词是“上下文”)。通过训练这个 CBOW 模型,使其能 尽可能地进行正确的预测,我们可以获得单词的分布式表示。
CBOW 模型的输入是上下文。这个上下文用 [‘you’, ‘goodbye’] 这样 的单词列表表示。我们将其转换为 one-hot 表示,以便 CBOW 模型可以进 行处理。在此基础上,CBOW 模型的网络可以画成图 3-9 这样。
图3-9 是 CBOW 模型的网络。它有两个输入层,经过中间层到达输出 层。这里,从输入层到中间层的变换由相同的全连接层(权重为Win)完成, 从中间层到输出层神经元的变换由另一个全连接层(权重为 Wout)完成。
有时将得分经过 Softmax 层之后的神经元称为输出层。这里,我 们将输出得分的节点称为输出层。
不使用偏置的全连接层的处理由 MatMul 层的正向传播代理。这 个层在内部计算矩阵乘积。
实现 CBOW 模型的推理,具 体实现如下所示
# coding: utf-8
import sys
sys.path.append('..')
import numpy as np
from common.layers import MatMul
# 样本的上下文数据
c0 = np.array([[1, 0, 0, 0, 0, 0, 0]])
c1 = np.array([[0, 0, 1, 0, 0, 0, 0]])
# 初始化权重
W_in = np.random.randn(7, 3)
W_out = np.random.randn(3, 7)
# 生成层
in_layer0 = MatMul(W_in)
in_layer1 = MatMul(W_in)
out_layer = MatMul(W_out)
# 正向传播
h0 = in_layer0.forward(c0)
h1 = in_layer1.forward(c1)
h = 0.5 * (h0 + h1)
s = out_layer.forward(h)
print(s)
# [[-0.33304998 3.19700011 1.75226542 1.36880744 1.68725368 2.38521564 0.81187955]]
以上就是 CBOW 模型的推理过程。这里我们见到的 CBOW 模型是没 有使用激活函数的简单的网络结构。除了多个输入层共享权重外,并没有什 么难点。
3.2.2 CBOW模型的学习
CBOW 模型的学习就是调整权重,以使预测准确。其结果是,权重 Win(确切地说是 Win 和 Wout 两者)学习到蕴含单词出现模式的向量。根据过去的实验,CBOW 模型(和 skip-gram 模型)得到的单词的分布式表示,特别是使用维基百科等大规模语料库学习到的单词的分布式表示,在单词的含义和语法上符合我们直觉的案例有很多.
CBOW模型只是学习语料库中单词的出现模式。如果语料库不一样, 学习到的单词的分布式表示也不一样。比如,只使用“体育”相关 的文章得到的单词的分布式表示,和只使用“音乐”相关的文章得 到的单词的分布式表示将有很大不同。
3.2.3 word2vec的权重和分布式表示
就 word2vec(特别是 skip-gram 模型)而言,最受欢迎的是方案 A。遵循这一思路,我们也使用 Win 作为单词的分布式表示
3.3 学习数据的准备
、在开始 word2vec 的学习之前,我们先来准备学习用的数据。这里我们 仍以“You say goodbye and I say hello.”这个只有一句话的语料库为例进 行说明。
3.3.1 上下文和目标词
word2vec 中使用的神经网络的输入是上下文,它的正确解标签是被这些上下文包围在中间的单词,即目标词。
def preprocess(text):
text = text.lower()
text = text.replace('.', ' .')
words = text.split(' ')
word_to_id = {}
id_to_word = {}
for word in words:
if word not in word_to_id:
new_id = len(word_to_id)
word_to_id[word] = new_id
id_to_word[new_id] = word
corpus = np.array([word_to_id[w] for w in words])
return corpus, word_to_id, id_to_word
我们来实现从语料库生成上下文和目标词的函数。在此之前,我 们先复习一下上一章的内容。首先,将语料库的文本转化成单词 ID。
import sys
sys.path.append('..')
from common.util import preprocess
text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
print(corpus)
# [0 1 2 3 4 1 5 6]
print(id_to_word)
# {0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
生成上下文和目标词的函数 create_contexts_target(corpus, window_size)
def create_contexts_target(corpus, window_size=1):
'''生成上下文和目标词
:param corpus: 语料库(单词ID列表)
:param window_size: 窗口大小(当窗口大小为1时,左右各1个单词为上下文)
:return:
'''
target = corpus[window_size:-window_size]
contexts = []
for idx in range(window_size, len(corpus)-window_size):
cs = []
for t in range(-window_size, window_size + 1):
if t == 0:
continue
cs.append(corpus[idx + t])
contexts.append(cs)
return np.array(contexts), np.array(target)
接着刚才的实现, 代码如下所示。
contexts, target = create_contexts_target(corpus, window_size=1)
print(contexts)
# [[0 2]
# [1 3]
# [2 4]
# [3 1]
# [4 5]
# [1 6]]
print(target)
# [1 2 3 4 1 5]
3.3.2 转化为one-hot表示
def convert_one_hot(corpus, vocab_size):
'''转换为one-hot表示
:param corpus: 单词ID列表(一维或二维的NumPy数组)
:param vocab_size: 词汇个数
:return: one-hot表示(二维或三维的NumPy数组)
'''
N = corpus.shape[0]
if corpus.ndim == 1:
one_hot = np.zeros((N, vocab_size), dtype=np.int32)
for idx, word_id in enumerate(corpus):
one_hot[idx, word_id] = 1
elif corpus.ndim == 2:
C = corpus.shape[1]
one_hot = np.zeros((N, C, vocab_size), dtype=np.int32)
for idx_0, word_ids in enumerate(corpus):
for idx_1, word_id in enumerate(word_ids):
one_hot[idx_0, idx_1, word_id] = 1
return one_hot
实现代码:学习数据的准备就完成了
text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
contexts, target = create_contexts_target(corpus, window_size=1)
vocab_size = len(word_to_id)
target = convert_one_hot(target, vocab_size)
contexts = convert_one_hot(contexts, vocab_size)
contexts
#输出contexts
array([[[1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0]],
[[0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0]],
[[0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0]],
[[0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0]],
[[0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 0]],
[[0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1]]])
3.4 CBOW模型的实现
class SimpleCBOW:
def __init__(self, vocab_size, hidden_size):
V, H = vocab_size, hidden_size #词汇个数 vocab_size,中间层的神经元个数 hidden_size
# 初始化权重,并用一些小的随机值初始化这两个权重,astype('f')初始化将使用 32 位的浮点数。
W_in = 0.01 * np.random.randn(V, H).astype('f')
W_out = 0.01 * np.random.randn(H, V).astype('f')
# 生成层
#生成两个输入侧的 MatMul 层、一个输出侧的 MatMul 层,以及一个 Softmax with Loss 层。
self.in_layer0 = MatMul(W_in)
self.in_layer1 = MatMul(W_in)
self.out_layer = MatMul(W_out)
self.loss_layer = SoftmaxWithLoss()
# 将所有的权重和梯度整理到列表中
layers = [self.in_layer0, self.in_layer1, self.out_layer]
self.params, self.grads = [], []
for layer in layers:
self.params += layer.params
self.grads += layer.grads
# 将单词的分布式表示设置为成员变量
self.word_vecs = W_in
#现神经网络的正向传播 forward() 函数
def forward(self, contexts, target):
h0 = self.in_layer0.forward(contexts[:, 0])
h1 = self.in_layer1.forward(contexts[:, 1])
h = (h0 + h1) * 0.5
score = self.out_layer.forward(h)
loss = self.loss_layer.forward(score, target)
return loss
def backward(self, dout=1):
ds = self.loss_layer.backward(dout)
da = self.out_layer.backward(ds)
da *= 0.5
self.in_layer1.backward(da)
self.in_layer0.backward(da)
return None
这里,用来处理输入侧上下文的 MatMul 层的数量与上下文的单词数量相同(本例中是两个)。 另外,我们使用相同的权重来初始化 MatMul 层。
最后,将该神经网络中使用的权重参数和梯度分别保存在列表类型的成 员变量 params 和 grads 中。
这里,多个层共享相同的权重。因此,params列表中存在多个相 同的权重。但是,在 params列表中存在多个相同的权重的情况 下,Adam、Momentum 等优化器的运行会变得不符合预期(至 少就我们的代码而言)。为此,在 Trainer类的内部,在更新参数 时会进行简单的去重操作。关于这一点,这里省略说明,感兴趣 的读者可以参考 common/trainer.py的 remove_duplicate(params, grads)
这里,我们假定参数 contexts 是一个三维 NumPy 数组,即上一节图 3-18 的例子中 (6,2,7)的形状,其中第 0 维的元素个数是 mini-batch 的数量, 第 1 维的元素个数是上下文的窗口大小,第 2 维表示 one-hot 向量。此外, target 是 (6,7) 这样的二维形状。
至此,反向传播的实现就结束了。我们已经将各个权重参数的梯度保存在了成员变量 grads 中。因此,通过先调用 forward() 函数,再调 用 backward() 函数,grads 列表中的梯度被更新。下面,我们继续看一下 SimpleCBOW 类的学习
学习的实现
CBOW 模型的学习和一般的神经网络的学习完全相同。首先,给神 经网络准备好学习数据。然后,求梯度,并逐步更新权重参数。这里,我 们使用第 1 章介绍的 Trainer 类来执行学习过程,
import sys
sys.path.append('..') # 为了引入父目录的文件而进行的设定
from common.trainer import Trainer
from common.optimizer import Adam
from simple_cbow import SimpleCBOW
from common.util import preprocess, create_contexts_target, convert_one_hot
window_size = 1
hidden_size = 5
batch_size = 3
max_epoch = 1000
text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
contexts, target = create_contexts_target(corpus, window_size)
target = convert_one_hot(target, vocab_size)
contexts = convert_one_hot(contexts, vocab_size)
model = SimpleCBOW(vocab_size, hidden_size)
optimizer = Adam()
trainer = Trainer(model, optimizer)
trainer.fit(contexts, target, max_epoch, batch_size)
trainer.plot()
word_vecs = model.word_vecs #为输入侧的 MatMul 层的权重已经赋值给了成员变量 word_vecs
for word_id, word in id_to_word.items():
print(word, word_vecs[word_id])
you [ 1.1550112 1.2552509 -1.1116056 1.1482503 -1.2046812]
say [-1.2141827 -1.2367412 1.2163384 -1.2366292 0.67279905]
goodbye [ 0.7116186 0.55987084 -0.8319744 0.749239 -0.8436555 ]
and [-0.94652086 -0.8535852 0.55927175 -0.6934891 2.0411916 ]
i [ 0.7177702 0.5699475 -0.8368816 0.7513028 -0.8419596]
hello [ 1.1411363 1.2600429 -1.105042 1.1401148 -1.2044929]
. [-1.1948084 -1.2921802 1.4119368 -1.345656 -1.5299033]
不过,遗憾的是,这里使用的小型语料库并没有给出很好的结果。当 然,主要原因是语料库太小了。如果换成更大、更实用的语料库,相信会获 得更好的结果。但是,这样在处理速度方面又会出现新的问题,这是因为当 前这个 CBOW 模型的实现在处理效率方面存在几个问题。下一章我们将改 进这个简单的 CBOW 模型,实现一个“真正的”CBOW 模型。
3.5 word2vec的补充说明
3.5.1 CBOW模型和概率
本书中将概率记为 P(·),比如事 件 A 发生的概率记为 P(A)。联合概率记为** P(A, B),表示事件 A 和事件 B 同时发生的概率。 后验概率记为 P(A|B)**,字面意思是“事件发生后的概率”。从另一个 角度来看,也可以解释为“在给定事件 B(的信息)时事件 A 发生的概率”。
3.5.2 skip-gram模型
import sys
sys.path.append('..')
import numpy as np
from common.layers import MatMul, SoftmaxWithLoss
class SimpleSkipGram:
def __init__(self, vocab_size, hidden_size):
V, H = vocab_size, hidden_size
# 初始化权重
W_in = 0.01 * np.random.randn(V, H).astype('f')
W_out = 0.01 * np.random.randn(H, V).astype('f')
# 生成层
self.in_layer = MatMul(W_in)
self.out_layer = MatMul(W_out)
self.loss_layer1 = SoftmaxWithLoss()
self.loss_layer2 = SoftmaxWithLoss()
# 将所有的权重和梯度整理到列表中
layers = [self.in_layer, self.out_layer]
self.params, self.grads = [], []
for layer in layers:
self.params += layer.params
self.grads += layer.grads
# 将单词的分布式表示设置为成员变量
self.word_vecs = W_in
def forward(self, contexts, target):
h = self.in_layer.forward(target)
s = self.out_layer.forward(h)
l1 = self.loss_layer1.forward(s, contexts[:, 0])
l2 = self.loss_layer2.forward(s, contexts[:, 1])
loss = l1 + l2
return loss
def backward(self, dout=1):
dl1 = self.loss_layer1.backward(dout)
dl2 = self.loss_layer2.backward(dout)
ds = dl1 + dl2
dh = self.out_layer.backward(ds)
self.in_layer.backward(dh)
return None
class MatMul:
def __init__(self, W):
self.params = [W]
self.grads = [np.zeros_like(W)]
self.x = None
def forward(self, x):
W, = self.params
out = np.dot(x, W)
self.x = x
return out
def backward(self, dout):
W, = self.params
dx = np.dot(dout, W.T)
dW = np.dot(self.x.T, dout)
self.grads[0][...] = dW
return dx
class SoftmaxWithLoss:
def __init__(self):
self.params, self.grads = [], []
self.y = None # softmax的输出
self.t = None # 监督标签
def forward(self, x, t):
self.t = t
self.y = softmax(x)
# 在监督标签为one-hot向量的情况下,转换为正确解标签的索引
if self.t.size == self.y.size:
self.t = self.t.argmax(axis=1)
loss = cross_entropy_error(self.y, self.t)
return loss
def backward(self, dout=1):
batch_size = self.t.shape[0]
dx = self.y.copy()
dx[np.arange(batch_size), self.t] -= 1
dx *= dout
dx = dx / batch_size
return dx
3.5.3 基于计数与基于推理
此外,在 word2vec 之后,有研究人员提出了 GloVe 方法 [27]。GloVe 方法融合了基于推理的方法和基于计数的方法。该方法的思想是,将整个语 料库的统计数据的信息纳入损失函数,进行 mini-batch 学习(具体请参考 论文 [27])。据此,这两个方法论成功地被融合在了一起。
3.6 小结
本章我们详细解释了 word2vec 的 CBOW 模型,并对其进行了实 现。CBOW 模型基本上是一个 2 层的神经网络,结构非常简单。我们使用 MatMul 层和 Softmax with Loss 层构建了 CBOW 模型,并用一个小规模 语料库确认了它的学习过程。遗憾的是,现阶段的 CBOW 模型在处理效率 上还存在一些问题。不过,在理解了本章的 CBOW 模型之后,离真正的 word2vec 也就一步之遥了。下一章,我们将改进 CBOW 模型。
本章所学的内容
- 基于推理的方法以预测为目标,同时获得了作为副产物的单词的分 布式表示
- word2vec 是基于推理的方法,由简单的 2 层神经网络构成
- word2vec 有 skip-gram 模型和 CBOW 模型
- CBOW 模型从多个单词(上下文)预测 1 个单词(目标词)
- skip-gram 模型反过来从 1 个单词(目标词)预测多个单词(上下文)
- 由于 word2vec 可以进行权重的增量学习,所以能够高效地更新或添 加单词的分布式表示
标签:word,进阶,contexts,self,CBOW,np,word2vec,自然语言,size 来源: https://blog.csdn.net/weixin_44953928/article/details/121844426