使用神经网络,构建神经网络模型;将数据输入到模型中,模型进行预测,并反复更新网络的权重
以下图为例,所谓的推理就是给定了单词的上下文,让模型去预测中间这个单词是什么;


要用神经网络处理单词,需要先将单词转化 为固定长度的向量
一种方式为one-hot独热编码,只有一个元素是 1,其他元素都是 0
用“You say goodbye and I say hello.”这个一句话的语料库来说明的话,这个语料库中, 一共有 7 个单词,因此独热编码向量的长度就可以固定为7,每个单词都是长度为7的向量,并将单词 ID 对应的元素设为 1,其他元素设为 0;
向量固定下来之后,神经网络的输入层的神经元个数就固定下来了

对于 one-hot 表示的某个单词,使用全连接层对其进行变换,如下图所示:

每个箭头上都有权重
权重和输入层神经元的加权和成为中间层的神经元
这里省略了偏置,因此没有偏置的全连接层相当于在计算矩阵乘积
对于某个单词的向量,其维度为[1,7],经过全连接层(即每个输入层神经元都会参与到所有箭头的计算中)得到结果的维度为[1,3],那么这里的箭头上的权重所构成的矩阵维度就是[7,3]
如何理解计算过程:输入层的七个神经元与一组七个箭头上的权重对应相乘再相加,得到中间层第一个神经元的结果;以此类推;如下图所示:

代码示例如下:
import numpy as np
c = np.array([[1, 0, 0, 0, 0, 0, 0]]) # [1,7]
W = np.random.randn(7, 3) # [7,3]
# If both a and b are 2-D arrays, it is matrix multiplication
h = np.dot(c, W) # [1,3]
print(h)
pass
本书中还提供了一个MatMul 层,我们详细看一下其中的内容(因为后面也会用到)
主要实现一个矩阵相乘的计算,同时提供了反向传播的计算函数
关于矩阵求梯度的理解可以看:【深度学习】7-矩阵乘法运算的反向传播求梯度_矩阵梯度公式-CSDN博客;
初始化:
w保存为参数params;grads是一个与权重w维度相同的矩阵;def __init__(self, W):
self.params = [W]
self.grads = [np.zeros_like(W)] # 放到列表中
self.x = None
前向计算:
x的维度为[4,2],w的维度为[2,3];得到计算结果维度为[4,3];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) # 求关于输入x的梯度
dW = np.dot(self.x.T, dout) # 求关于权重矩阵的梯度
self.grads[0][...] = dW # 权重的梯度保存下来
return dx
模型输入的构建
模型的网络结构

通过神经网络的不断训练,两个权重得到优化;输入层与中间层的这个权重矩阵其实就是单词的分布式表示,我的理解是:
注意点:
将之前讲述过的矩阵乘法层应用到这里:由于输入是两个单词,因此输入层使用两个MatMul 层(仍是共享权重),即两个矩阵乘法,两个结果相加再平均,得到中间层的结果;然后再使用一个MatMul 层得到输出
CBOW模型正向计算的代码实现:
import numpy as np
from utils.Matmul 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)
# 构建层
# 权重是待优化的项;这里两个输入层都使用w_in;因此是共享权重的方式
# 但是由于两个输入层都是单独构建了类的实例,因此共享权重会影响参数的更新
in_layer_0 = MatMul(w_in) # 输入层
in_layer_1 = MatMul(w_in) # 输入层
out_layer = MatMul(w_out) # 输出层
# 正向传播(前向计算)
h_0 = in_layer_0.forward(c0)
h_1 = in_layer_1.forward(c1)
h = 0.5 * (h_0 + h_1)
s = out_layer.forward(h)
print('前向计算结果:\n', s)
上述神经网络的输出结果称之为得分;对这个得分施加softmax函数,可以转换为概率分布,选取概率最大的作为对当前位置单词的预测;如果网络具有“良好的权重”,那么在最终得到的概率分布中,正确解将具有最高的概率值
当两个层共享权重时,每个层的梯度更新都会影响另一个层。这可能会导致一些问题,特别是当使用像 Adam 这样的优化器时,因为 Adam 依赖于每个参数的梯度的历史信息来调整学习率。
pytorch中如何解决这种问题:
backward() 方法时,PyTorch 会自动累积这些层的梯度,这样你就可以正确地更新共享的权重了。以下是一个简单的例子:
class SharedLayerNet(nn.Module):
def __init__(self):
super(SharedLayerNet, self).__init__()
self.shared_layer = nn.Linear(10, 20)
def forward(self, x1, x2):
out1 = self.shared_layer(x1)
out2 = self.shared_layer(x2)
return out1, out2
即训练上述神经网络,以优化权重,以便能够更准确的进行预测。
学习的结果是,权重
w_in(确切地说是w_in和w_out两者)学习到蕴含单词出现模式的向量;CBOW 模型只是学习语料库中单词的出现模式。如果语料库不一样, 学习到的单词的分布式表示也不一样。比如,只使用“体育”相关 的文章得到的单词的分布式表示,和只使用“音乐”相关的文章得 到的单词的分布式表示将有很大不同
- 但是与先前基于计数的方法相比,他的成本更低
如何学习?目标是什么?

下图为整个过程的维度变化:

w_in:每一行对应于各个单词的分布式表示;维度为[7,3];w_out:在列方向上保存了各个单词的分布式表示;维度为[3,7];w_in作为最终的单词的分布式表示w_in的每一列相乘,每一列又刚好是7(与单词个数对应);每一列都蕴含了一些信息;这里有3列;w_in的有 效性。另外,在与 word2vec 相似的 GloVe[27] 方法中,通过将两个 权重相加,也获得了良好的结果;因此可以看看论文中是如何描述的;