目录
引言
神经网络语言模型(Neural Network Language Model, NNLM)是利用神经网络计算词向量的方法,根据(w{t-n+1}...w{t-1})来预测(w{t})是什么词,即用前(n-1)个单词来预测第(n)个单词。
一、原理图
二、代码
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
from tqdm import tqdm
import numpy as np
import re
sentences = ["我爱你", "喜羊羊", "灰太狼"] # sentences = ['我爱你', '喜羊羊', '灰太狼']
# 将输入的句子进行中文分字处理。它首先使用正则表达式找到句子中的中文字符,并将句子按照中文字符进行分割。然后,去除分割后的结果中的空白字符,最后返回分割后的中文字符列表。
def seg_char(sent):
pattern = re.compile(r'([\u4e00-\u9fa5])')
chars = pattern.split(sent)
chars = [w for w in chars if len(w.strip()) > 0]
return chars
# 对给定的句子列表进行中文分字处理,得到一个包含所有句子中汉字的二维数组。
chars = np.array([seg_char(i) for i in sentences]) # chars = [['我' '爱' '你'], ['喜' '羊' '羊'], ['灰' '太' '狼']]
# 将二维数组展平为一个一维数组。
chars = chars.reshape(1, -1) # chars = [['我' '爱' '你' '喜' '羊' '羊' '灰' '太' '狼']]
# 通过去除数组中的空白字符和重复项,得到汉字的列表。
word_list = np.squeeze(chars) # word_list = ['我' '爱' '你' '喜' '羊' '羊' '灰' '太' '狼']
word_list = list(set(word_list)) # word_list = ['灰', '太', '狼', '喜', '羊', '爱', '你', '我']
# 建立汉字与索引的映射关系,生成词典。 i=0—>灰;i=1—>太;i=2—>狼...i=7—>我
word_dict = {w: i for i, w in enumerate(word_list)} # word_dict = {'灰': 0, '太': 1, '狼': 2, '喜': 3, '羊': 4, '爱': 5, '你': 6, '我': 7}
# 创建索引与汉字的反向映射关系,生成反向词典。
number_dict = {i: w for i, w in enumerate(word_list)} # number_dict = {0: '灰', 1: '太', 2: '狼', 3: '喜', 4: '羊', 5: '爱', 6: '你', 7: '我'}
# 确定词汇表的大小。
n_class = len(word_dict) # 词汇表的大小 n_class = 8
# NNLM 参数
n_step = 2 # 步数
# 将句子列表转换为神经网络模型训练所需的输入批次和目标批次。输入输出 one-hot 编码
def make_batch(sentences): # sentences = ['我爱你', '喜羊羊', '灰太狼']
input_batch = []
target_batch = []
# 遍历句子列表,对每个句子进行中文分字处理,得到汉字列表。
for sen in sentences: # sen = '灰太狼'
# 对于每个句子,将汉字列表中的前n-1个字符作为输入,最后一个字符作为目标。
word = seg_char(sen) # word = ['灰', '太', '狼']
input = [word_dict[n] for n in word[:-1]] # 使用词典将汉字转换为对应的索引。input = [0, 1]
target = word_dict[word[-1]] # target = 2
# 对输入和目标进行 one-hot 编码,生成输入批次和目标批次。
# [tensor([[0., 0., 0., 0., 0., 0., 0., 1.], [0., 0., 0., 0., 0., 1., 0., 0.]]),
# tensor([[0., 0., 0., 1., 0., 0., 0., 0.], [0., 0., 0., 0., 1., 0., 0., 0.]]),
# tensor([[1., 0., 0., 0., 0., 0., 0., 0.], [0., 1., 0., 0., 0., 0., 0., 0.]])]
input_batch.append(torch.eye(n_class)[input])
# [tensor([0., 0., 0., 0., 0., 0., 1., 0.]), tensor([0., 0., 0., 0., 1., 0., 0., 0.]), tensor([0., 0., 1., 0., 0., 0., 0., 0.])]
target_batch.append(torch.eye(n_class)[target])
return input_batch, target_batch
# 将所有输入批次和目标批次合并为一个张量,并整理成模型需要的形状。
input_batch, target_batch = make_batch(sentences)
# tensor:(3, 16)
# tensor([[0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 0., 0.],
# [0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
# [1., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.]])
input_batch = torch.cat(input_batch).view(-1, n_step * n_class)
# tensor:(3, 8)
# tensor([[0., 0., 0., 0., 0., 0., 1., 0.],
# [0., 0., 0., 0., 1., 0., 0., 0.],
# [0., 0., 1., 0., 0., 0., 0., 0.]])
target_batch = torch.cat(target_batch).view(-1, n_class)
# 定义模型
class NNLM(nn.Module):
# 初始化函数: 定义了模型的各个层和激活函数
def __init__(self):
super(NNLM, self).__init__()
self.linear1 = nn.Linear(n_step * n_class, 2) # 全连接层, 输入大小为 n_step * n_class,输出大小为 2
self.tanh = nn.Tanh() # tanh 激活函数
self.linear2 = nn.Linear(2, n_class) # 另一个全连接层,输入大小为 2,输出大小为 n_class
self.softmax = nn.Softmax(dim=1) # softmax 激活函数,用于输出层
# 前向传播函数: 定义了数据流的传递方式
def forward(self, x):
x = self.linear1(x) # 输入 x 经过第一个全连接层 self.linear1 得到中间表示
x = self.tanh(x) # 中间表示经过 tanh 激活函数
x = self.linear2(x) # 经过第二个全连接层 self.linear2 得到输出
x = self.softmax(x) # 输出经过 softmax 激活函数,得到最终的预测结果
return x
# 准备训练数据
train_dataset = TensorDataset(input_batch, target_batch)
train_loader = DataLoader(train_dataset, batch_size=1)
# NNLM(
# (linear1): Linear(in_features=16, out_features=2, bias=True)
# (tanh): Tanh()
# (linear2): Linear(in_features=2, out_features=8, bias=True)
# (softmax): Softmax(dim=1)
# )
model = NNLM()
# 定义损失函数和优化器
# 损失函数使用交叉熵损失函数 nn.CrossEntropyLoss(),用于计算模型预测结果与真实标签之间的差异
criterion = nn.CrossEntropyLoss() # criterion = CrossEntropyLoss()
# 优化器使用 Adam 优化器 optim.Adam,用于更新模型的参数以最小化损失函数
# Adam (
# Parameter Group 0
# amsgrad: False
# betas: (0.9, 0.999)
# capturable: False
# differentiable: False
# eps: 1e-08
# foreach: None
# fused: None
# lr: 0.001
# maximize: False
# weight_decay: 0
# )
optimizer = optim.Adam(model.parameters())
# 训练模型: 使用了一个循环来迭代执行多个 epoch(训练轮数)
epochs = 5000
for epoch in tqdm(range(epochs), desc='训练进度'): # epoch = 0,..., epoch = 4
total_loss = 0.0
correct = 0
total_samples = 0
for inputs, targets in train_loader:
optimizer.zero_grad() # 首先,使用优化器的 zero_grad() 方法将模型参数的梯度归零,以准备计算新一轮的梯度。
# 然后,通过模型前向传播计算得到模型的输出 outputs。
# epoch = 0时:
# outputs = tensor([[0.1091, 0.0567, 0.0587, 0.0967, 0.1380, 0.1759, 0.1786, 0.1863],
# [0.1626, 0.0645, 0.0532, 0.1406, 0.0927, 0.1947, 0.1429, 0.1487],
# [0.1081, 0.0481, 0.0837, 0.1257, 0.1359, 0.1466, 0.1929, 0.1590]],
# grad_fn=<SoftmaxBackward0>)
# epoch = 4时:
# outputs = tensor([[0.1272, 0.1309, 0.1559, 0.0674, 0.1287, 0.0707, 0.1293, 0.1898],
# [0.1423, 0.1347, 0.1282, 0.0570, 0.1251, 0.0887, 0.1523, 0.1716],
# [0.1320, 0.1184, 0.1521, 0.0699, 0.1312, 0.0632, 0.1283, 0.2050]],
# rad_fn=<SoftmaxBackward0>)
outputs = model(input_batch)
# 接着,计算模型的预测结果与真实标签之间的交叉熵损失,即模型在当前轮次的损失值 loss。
# epoch = 0时:
# loss = tensor(2.0923, grad_fn=<NllLossBackward0>)
# epoch = 4时:
# loss = tensor(2.0697, grad_fn=<NllLossBackward0>)
loss = criterion(outputs, torch.max(target_batch, 1)[1])
# 使用损失函数的 backward() 方法计算损失关于模型参数的梯度。
loss.backward()
# 最后,使用优化器的 step() 方法更新模型的参数,以最小化损失函数。
optimizer.step()
total_loss += loss.item() * inputs.size(0)
_, predicted = torch.max(outputs, 1)
correct += (predicted == torch.max(targets, 1)[1]).sum().item()
total_samples += inputs.size(0)
accuracy = correct / total_samples
avg_loss = total_loss / total_samples
# 每隔 100 个 epoch 打印一次当前轮次的损失值。
if (epoch+1) % 1000 == 0:
print(f'Epoch {epoch + 1}/{epochs}')
print(f'Loss: {avg_loss:.4f} - Accuracy: {accuracy:.4f}\n')
# print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch+1, epochs, loss.item()))
# 预测测试
predict = model(input_batch) # 使用训练好的模型对测试数据进行预测; 将测试数据输入模型,得到模型的预测结果
_, predict = torch.max(predict, 1) # 通过 torch.max() 函数找到每个预测结果中概率最大的类别索引
# 将预测结果转换为汉字,并打印出原始输入数据和模型预测得到的结果。
print('输入的是:', [seg_char(sen)[:2] for sen in sentences])
print('预测得到:', [number_dict[n.item()] for n in predict])