目录
- 原理部分
- 代码
- 代码注意点
原理部分
- 为了通过前面的词预测后一个词。对于一个结构固定的模型来说,要求每个batch的输入数据的长度要一致
- 将索引表示的词,转化为向量表示,作为输入层,将前面词的向量拼接才一起作为输入向量,经过一个权值矩阵后,使用tanh作为激活函数,得到隐藏层中前面词的向量表示。
- 将隐藏层作为输入,同时也将输入层作为输入(注意点,也就是图上的绿色虚线),分别经过两个权值矩阵后相加得到输出层
代码
import torch
import torch.nn as nn
import torch.optim as optimizer
import torch.utils.data as Data
dtype = torch.FloatTensor
sentences = ['i like cat', 'i love coffee', 'i hate milk']
sentences_list = " ".join(sentences).split() # ['i', 'like', 'cat', 'i', 'love'. 'coffee',...]
vocab = list(set(sentences_list)) # 还得转list,不然那没有索引
word2idx = {w:i for i, w in enumerate(vocab)}
idx2word = {i:w for i, w in enumerate(vocab)}
V = len(vocab) # 词表大小
def make_data(sentences):
input_data = []
target_data = []
for sen in sentences:
sen = sen.split() # ['i', 'like', 'cat']
input_tmp = [word2idx[w] for w in sen[:-1]] # 输入不包含最后一个单词转为索引
target_tmp = word2idx[sen[-1]] # 最后一个单词的索引作为目标
input_data.append(input_tmp)
target_data.append(target_tmp)
return input_data, target_data
input_data, target_data = make_data(sentences) # 还是list类型的
input_data, target_data = torch.LongTensor(input_data), torch.LongTensor(target_data) # 转为long张量
dataset = Data.TensorDataset(input_data, target_data) # 包装数据和目标张量的数据集
# 数据加载器,组合数据集和采样器,在数据集上提供单进程或多进程迭代器
'''
batch_size:每个batch的样本数
shuffle:True表示每个epoch重新打乱数据
'''
loader = Data.DataLoader(dataset, 2, True)
# parameters
m = 2 # 向量表示的维度
n_step = 2 # 输入向量的个数
n_hidden = 10
class NNLM(nn.Module):
def __init__(self):
super(NNLM, self).__init__()
self.C = nn.Embedding(V, m)
'''
1. 将单词的索引转化为词向量表示后,前面所有词的词向量拼接在一起,作为输入向量,
2. 然后经过一个(n_step * m, n_hidden)的加权矩阵 H ,转化为隐藏层的向量的表示
3. 输出层以隐藏层作为输入,又以输入层作为输入
'''
self.H = nn.Parameter(torch.randn(n_step * m, n_hidden).type(dtype))
self.d = nn.Parameter(torch.randn(n_hidden).type(dtype)) # 隐藏层生成的偏置
self.b = nn.Parameter(torch.randn(V).type(dtype))
self.W = nn.Parameter(torch.randn(n_step * m, V).type(dtype))
self.U = nn.Parameter(torch.randn(n_hidden, V).type(dtype))
def forward(self, X):
'''
X : [batch_size, n_step]
'''
X = self.C(X) # [batch_size, n_step, m] # 每个词转化为m维了
X = X.view(-1, n_step * m) # [batch_szie, n_step * m]
hidden_out = torch.tanh(self.d + torch.mm(X, self.H)) # [batch_size, n_hidden]
output = self.b + torch.mm(X, self.W) + torch.mm(hidden_out, self.U)
return output
model = NNLM()
optim = optimizer.Adam(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss() # 损失函数为交叉熵损失
'''
训练的标准流程
'''
for epoch in range(5000):
for batch_x, batch_y in loader:
pred = model(batch_x) # 喂数据
loss = criterion(pred, batch_y) # 计算损失
if (epoch + 1) % 1000 == 0:
print(epoch + 1, loss.item())
optim.zero_grad() # 清空上一步的参与更新参数值
loss.backward() # 误差反向传播,计算参数更新值
optim.step() # 将参数更新值,加入到参数上
# Pred
pred = model(input_data).max(1, keepdim=True)[1]
print([idx2word[idx.item()] for idx in pred.squeeze()])
其中pytorch的训练代码是比较通用的代码
'''
训练的标准流程
'''
for epoch in range(5000):
for batch_x, batch_y in loader:
pred = model(batch_x) # 喂数据
loss = criterion(pred, batch_y) # 计算损失
if (epoch + 1) % 1000 == 0:
print(epoch + 1, loss.item())
optim.zero_grad() # 清空上一步的参与更新参数值
loss.backward() # 误差反向传播,计算参数更新值
optim.step() # 将参数更新值,加入到参数上
代码注意点
- 工作量主要集中在网络结构的设计上,注意输出层的输入是隐藏层和输入层
- 注意每次进行加权求和还有一个偏置矩阵要加
- 就是pytorch中的一些数据转换的函数view,squeeze等