目录
1. 基于推理的方法和神经网络
神经网络中单词的处理方法
2. 简单的word2vec
CBOW模型的推理与学习
3. 学习数据的准备
上下文和目标词
转化为one-hot表示
数据预处理总结
4. CBOW模型的实现
5. word2vec的补充说明
CBOW模型和概率
skip-gram模型
两个模型的区别分析
基于计数与基于推理总结
上章中,我们使用基于计数的方法得到单词的分布式表示,本章我们将讨论基于推理的方法,推理机制用的是神经网络,实现一个简单的word2vec。
# 输入数据c的维数是2,考虑了mini-batch处理
# 将各个数据保存在了第1维(0维度),也就是行方向
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)
CBOW模型是根据上下文预测目标词的神经网络。通过训练该模型,使其能尽可能地进行正确的预测,我们可以获得单词的分布式表示。模型的输入是上下文,用[’you’, ‘goodbye’]这样的单词列表表示,再将其转换为hot-one表示
分析:
实现:
# 样本的上下文数据
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.6819715 -0.08929536 -1.68627624 0.8961376
# -1.03090686 -0.65943828 0.80935793]]
我们仍以“You say goodbye and I say hello.”作为语料库
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)
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.'
# 1.生成单词ID列表以及字典
corpus, word_to_id, id_to_word = preprocess(text)
# 2.生成上下文和目标词
contexts, target = create_contexts_target(corpus, window_size=1)
# 3.转化为one-hot表示
vocab_size = len(word_to_id)
target = convert_one_hot(target, vocab_size)
contexts = convert_one_hot(contexts, vocab_size)
class SimpleCBOW:
def __init__(self, vocab_size, hidden_size):
V, H = 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')
# 生成层
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
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
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
for word_id, word in id_to_word.items():
print(word, word_vecs[word_id])
""" 结果如下
you [ 0.9532905 -1.3833157 0.9600834 -1.333352 0.8822044]
say [-1.1779003 -0.48005727 -1.1771895 0.18510178 -1.1754793 ]
goodbye [ 0.91159654 -0.50769854 0.9059445 -0.55956 0.9156929 ]
and [-0.8856774 -1.7152286 -0.8718644 1.4566529 -0.884314 ]
i [ 0.9017562 -0.49907902 0.911299 -0.55395776 0.9301149 ]
hello [ 0.94429344 -1.4025779 0.95916694 -1.3244895 0.89125985]
. [-1.1089147 1.4758974 -1.1068727 -1.4950483 -1.1257972]
"""
skip-gram模型是反转了CBOW模型处理的上下文和目标词的模型,从中间的单词(目标词)预测周围的多个单词(上下文)
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
基于计数 | 基于推理 | |
---|---|---|
学习机制 | 通过对整个语料库的统计数据进行一次学习来获得单词的分布式表示 | 反复观察语料库的一部分数据进行学习 |
添加新词并更新 | 从头重新计算,重新生成共现矩阵、进行SVD等操作 | 允许参数的增量学习,就是可以将之前学习到的权重作为下一次学习的初始值 |
分布式表示的性质 | 编码单词的相似性 | 除了单词的相似性外,还能理解更复杂的单词之间的模式 |
分布式表示的准确度 | 就单词的相似性而言,两种方法难分上下 |