非线性神经网络语言模型可以解决一些传统语言模型中的问题:增加上下文的同时参数仅呈线性增长,缓解了人工设计的需要,支持不同上下文泛化性能。
- 输入:k元语句(代码实现熵是 k 个词向量的拼接)
- 输出:下个词的概率分布P
我们的目标是希望神经网络发现如下的规律,于是有了网络结构图:
P
(
C
(
w
i
)
∣
C
(
w
i
−
4
)
,
C
(
w
i
−
3
)
,
C
(
w
i
−
2
)
,
C
(
w
i
−
1
)
)
(2.2.1)
P(C(w_i)|C(w_{i-4}),C(w_{i-3}),C(w_{i-2}),C(w_{i-1}))\tag{2.2.1}
P(C(wi)∣C(wi−4),C(wi−3),C(wi−2),C(wi−1))(2.2.1)
2.2.1 one-hot 表示
先用最简单的方式表示每个词,one-hot 表示为:
- d o g = ( 0 , 0 , 0 , 0 , 1 , 0 , 0 , . . . ) dog = (0, 0, 0, 0, 1, 0, 0, ...) dog=(0,0,0,0,1,0,0,...)
- c a t = ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , . . . ) cat = (0, 0, 0, 0 ,0 ,0, 0, 0 ,1, 0, ...) cat=(0,0,0,0,0,0,0,0,1,0,...)
- e a t = ( 0 , 1 , 0 , 0 , . . . ) eat = (0, 1, 0, 0, ...) eat=(0,1,0,0,...)
可是 one-hot 表示法有诸多缺陷,还是稠密的向量表示更好一些,那么如何转换呢?只需要加一个隐藏层映射一下就好了。映射之后的向量层如果单独拿出来看,还有办法找到原来的词是什么吗?
one-hot 表示法这时候就作为一个索引字典了,可以通过映射矩阵对应到具体的词向量。
这样,这个神经网络的每个环节都没问题了,可以开始训练了。
独热编码:让计算机认识单词
词典 V V V:新华词典里面把所有的词集合成一个集合 V V V。
假设词典里面有 8 个单词,计算机不认识单词,但是我们要计算机认识单词。
独热编码:给出一个 8 × 8 8 \times 8 8×8 的矩阵。
- “time”:10000000
- “fruit”:01000000
- ……
- “banana”:00000001
余弦相似度 去计算两者的相似度,发现独热编码 计算得到的余弦相似度都为0,即这些单词都没有关联度(独热编码缺陷),于是就有了词向量的概念。
数学公式总结:
输入向量
x
x
x,输出结果
y
y
y 的概率分布,两者的表达式如下所示,其中
w
w
w 代表词向量,
v
v
v 代表词到词嵌入的映射,
L
M
LM
LM (Lamguage Model)是一个多层感知机:
100
L
M
(
w
1
:
k
)
=
s
o
f
t
m
a
x
(
h
W
2
+
b
2
)
x
=
[
v
(
w
1
)
;
v
(
w
2
)
;
.
.
.
;
v
(
w
k
)
]
h
=
g
(
x
W
1
+
b
1
)
(2.2.2)
\begin{align}{100} LM(w_{1:k})&=softmax(hW^2+b^2)\\ &x = [v(w_1);v(w_2);...;v(w_k)]\\ &h=g(xW^1+b^1) \end{align}\tag{2.2.2}
100LM(w1:k)=softmax(hW2+b2)x=[v(w1);v(w2);...;v(wk)]h=g(xW1+b1)(2.2.2)
- v v v 就是映射矩阵
- w w w 是输入的词向量
- x x x 是将前 k 个词通过映射矩阵处理后的一个拼接
- h h h 是隐藏层
- W 1 W^1 W1 和 W 2 W^2 W2 表示两个矩阵
2.3.2 神经网络语言模型(NNLM)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c3aGJCNx-1687508673257)(https://bucket-zly.oss-cn-beijing.aliyuncs.com/img/Typora/202306181936991.png)]
假设有4个单词: w 1 , w 2 , w 3 , w 4 w_1,w_2,w_3,w_4 w1,w2,w3,w4(4个单词的独热编码)。
-
w
1
×
Q
=
c
1
,
w
2
×
Q
=
c
2
,
w
3
×
Q
=
c
3
,
w
4
×
Q
=
c
4
w_1 \times Q = c_1,\space \space w_2 \times Q = c_2,\space\space w_3 \times Q = c_3,\space\space w_4 \times Q = c_4
w1×Q=c1, w2×Q=c2, w3×Q=c3, w4×Q=c4。
- Q Q Q 就是一个随机矩阵(可学习),是一个参数
- C = [ c 1 , c 2 , c 3 , c 4 ] C = [c_1,c_2,c_3,c_4] C=[c1,c2,c3,c4]。
- s o f t m a x ( U [ t a n h ( W C + b 1 ) ] + b 2 ) = = [ 0.1 , 0.2 , 0.5 , 0.2 ] ∈ [ 1 , V L ] softmax(U[tanh(WC+b_1)]+b_2) == [ 0.1,\space0.2,\space0.5,\space0.2 ]\in[1,V_L] softmax(U[tanh(WC+b1)]+b2)==[0.1, 0.2, 0.5, 0.2]∈[1,VL]。最后生成一个一维矩阵,其中矩阵的长度为词典的长度 V L V_L VL
2.3.3 词向量(神经网络语言模型的副产品 Q Q Q)
给我任何一个词(“判断”),会给出相应的独热编码 [ 0 , 0 , 1 , 0 , . . . , 0 ] [0,0,1,0,...,0] [0,0,1,0,...,0]。
- W 1 × Q = c 1 W_1 \times Q = c_1 W1×Q=c1, c 1 c_1 c1 就是 “判断” 这个词的词向量。
词向量:就是用一个向量来表示一个单词。独热编码也属于词向量,只是独热编码存储空间太大,且两者的余弦相似度都为0。
经过 Q Q Q 处理,可以控制词向量的维度(大小),也解决了相似度的问题。
通过神经网络语言模型,找到一个合适的 Q Q Q 矩阵,得到一个合适的词向量,这个词向量能够更加准确的表示这个词
2.3.4 案例实现
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from tqdm import tqdm
from torch.autograd import Variable
dtype = torch.FloatTensor
sentences = ["i like dog", "i love coffee", "i hate milk", "i do nlp"]
word_list = ' '.join(sentences).split()
word_list = list(set(word_list))
word_dict = {w: i for i, w in enumerate(word_list)}
number_dict = {i: w for i, w in enumerate(word_list)}
# print(word_dict)
n_class = len(word_dict)
m = 2
n_step = 2
n_hidden = 2
def make_batch(sentence):
input_batch = []
target_batch = []
for sen in sentence:
word = sen.split()
input = [word_dict[n] for n in word[:-1]]
target = word_dict[word[-1]]
input_batch.append(input)
target_batch.append(target)
return input_batch, target_batch
class NNLM(nn.Module):
def __init__(self):
super(NNLM, self).__init__()
self.embed = nn.Embedding(n_class, m)
self.W = nn.Parameter(torch.randn(n_step * m, n_hidden).type(dtype))
self.d = nn.Parameter(torch.randn(n_hidden).type(dtype))
self.U = nn.Parameter(torch.randn(n_hidden, n_class).type(dtype))
self.b = nn.Parameter(torch.randn(n_class).type(dtype))
def forward(self, x):
x = self.embed(x) # 4 x 2 x 2
x = x.view(-1, n_step * m)
tanh = torch.tanh(self.d + torch.mm(x, self.W)) # 4 x 2
output = self.b + torch.mm(tanh, self.U)
return output
model = NNLM()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
input_batch, target_batch = make_batch(sentences)
input_batch = Variable(torch.LongTensor(input_batch))
target_batch = Variable(torch.LongTensor(target_batch))
for epoch in range(5000):
optimizer.zero_grad()
output = model(input_batch) # input: 4 x 2
loss = criterion(output, target_batch)
if (epoch + 1) % 1000 == 0:
print('epoch:', '%04d' % (epoch + 1), 'cost = {:.6f}'.format(loss.item()))
loss.backward()
optimizer.step()
predict = model(input_batch).data.max(1, keepdim=True)[1]
print([sen.split()[:2] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])