本文目录:
- 一、案例介绍
- (一)关于人名分类
- (二)人名分类数据预览
- 二、案例步骤
- (一)导入工具包
- (二)数据预处理
- 1. 获取常用的字符数量
- 2. 国家名种类数和个数
- 3.读数据到内存
- 4.构建数据源NameClassDataset
- 5.构建迭代器遍历数据
- 三、构建RNN模型(选择LSTM模型)
- (一)构建模型
- (二)模型测试
- 四、构建训练函数并进行训练
- (一)构建RNN训练函数
- (二)LSTM模型训练
- (三)模型训练日志数据制图
- (四)模型训练结果分析
- 1.损失对比曲线分析
- 2 .训练耗时分析
- 3. 训练准确率分析
- 4.结论
- 五、模型预测
- 最后,附赠代码完整版(包括传统RNN、LSTM和GRU建模及预测)
前言:前面介绍了传统RNN、LSTM、GRU,本篇文章分享综合案例。
一、案例介绍
(一)关于人名分类
以一个人名为输入, 使用模型帮助我们判断它最有可能是来自哪一个国家的人名, 这在某些国际化公司的业务中具有重要意义, 在用户注册过程中, 会根据用户填写的名字直接给他分配可能的国家或地区选项, 以及该国家或地区的国旗, 限制手机号码位数等等。
(二)人名分类数据预览
数据存放路径:$(home)/data/name_classfication.txt
数据格式说明 每一行第一个单词为人名,第二个单词为国家名。中间用制表符tab分割。
Huffmann German
Hummel German
Hummel German
Hutmacher German
Ingersleben German
Jaeger German
Jager German
Deng Chinese
Ding Chinese
Dong Chinese
Dou Chinese
Duan Chinese
Eng Chinese
Fan Chinese
Fei Chinese
Abaimov Russian
Abakeliya Russian
Abakovsky Russian
Abakshin Russian
Abakumoff Russian
Abakumov Russian
Abakumtsev Russian
Abakushin Russian
Abalakin Russian
二、案例步骤
整个案例的实现可分为以下五个步骤:
第一步导入必备的工具包
第二步对data文件中的数据进行处理,满足训练要求
第三步构建RNN模型(选择 LSTM)
第四步构建训练函数并进行训练
第五步构建预测函数并进行预测
(一)导入工具包
# 导入torch工具
import torch
# 导入nn准备构建模型
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
# 导入torch的数据源 数据迭代器工具包
from torch.utils.data import Dataset, DataLoader
# 用于获得常见字母及字符规范化
import string
# 导入时间工具包
import time
# 引入制图工具包
import matplotlib.pyplot as plt
# 从io中导入文件打开方法
from io import open
(二)数据预处理
这里需要对data文件中的数据进行处理,满足训练要求。
1. 获取常用的字符数量
# 获取所有常用字符包括字母和常用标点
all_letters = string.ascii_letters + " .,;'"# 获取常用字符数量
n_letters = len(all_letters)print("n_letter:", n_letters)
运行结果:
n_letter: 57
2. 国家名种类数和个数
# 国家名 种类数
categorys = ['Italian', 'English', 'Arabic', 'Spanish', 'Scottish', 'Irish', 'Chinese', 'Vietnamese', 'Japanese','French', 'Greek', 'Dutch', 'Korean', 'Polish', 'Portuguese', 'Russian', 'Czech', 'German']
# 国家名 个数
categorynum = len(categorys)
print('categorys--->', categorys)
运行结果:
categorys---> ['Italian', 'English', 'Arabic', 'Spanish', 'Scottish', 'Irish', 'Chinese', 'Vietnamese', 'Japanese', 'French', 'Greek', 'Dutch', 'Korean', 'Polish', 'Portuguese', 'Russian', 'Czech', 'German']
categorynum---> 18
3.读数据到内存
# 思路分析
# 1 打开数据文件 open(filename, mode='r', encoding='utf-8')
# 2 按行读文件、提取样本x 样本y line.strip().split('\t')
# 3 返回样本x的列表、样本y的列表 my_list_x, my_list_y
def read_data(filename):my_list_x, my_list_y= [], []# 打开文件with open(filename, mode='r', encoding='utf-8') as f:# 按照行读数据for line in f.readlines():if len(line) <= 5:continue# 按照行提取样本x 样本y(x, y) = line.strip().split('\t')my_list_x.append(x)my_list_y.append(y)# 打印样本的数量print('my_list_x->', len(my_list_x))print('my_list_y->', len(my_list_y))# 返回样本x的列表、样本y的列表return my_list_x, my_list_y
4.构建数据源NameClassDataset
# 原始数据 -> 数据源NameClassDataset --> 数据迭代器DataLoader
# 构造数据源 NameClassDataset,把语料转换成x y
# 1 init函数 设置样本x和y self.my_list_x self.my_list_y 条目数self.sample_len
# 2 __len__(self)函数 获取样本条数
# 3 __getitem__(self, index)函数 获取第几条样本数据
# 按索引 获取数据样本 x y
# 样本x one-hot张量化 tensor_x[li][all_letters.find(letter)] = 1
# 样本y 张量化 torch.tensor(categorys.index(y), dtype=torch.long)
# 返回tensor_x, tensor_y
class NameClassDataset(Dataset):def __init__(self, my_list_x, my_list_y):# 样本xself.my_list_x = my_list_x# 样本yself.my_list_y = my_list_y# 样本条目数self.sample_len = len(my_list_x)# 获取样本条数def __len__(self):return self.sample_len# 获取第几条 样本数据def __getitem__(self, index):# 对index异常值进行修正 [0, self.sample_len-1]index = min(max(index, 0), self.sample_len-1)# 按索引获取 数据样本 x yx = self.my_list_x[index]y = self.my_list_y[index]# 样本x one-hot张量化tensor_x = torch.zeros(len(x), n_letters)# 遍历人名 的 每个字母 做成one-hot编码for li, letter in enumerate(x):# letter2indx 使用all_letters.find(letter)查找字母在all_letters表中的位置 给one-hot赋值tensor_x[li][all_letters.find(letter)] = 1# 样本y 张量化tensor_y = torch.tensor(categorys.index(y), dtype=torch.long)# 返回结果return tensor_x, tensor_y
分析
文本张量化,这里也就是人名张量化是通过one-hot编码来完成。
# 将字符串(单词粒度)转化为张量表示,如:"ab" --->
# tensor([[[1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
# 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
# 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
# 0., 0., 0., 0., 0., 0.]],# [[0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
# 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
# 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
# 0., 0., 0., 0., 0., 0.]]])
5.构建迭代器遍历数据
def dm_test_NameClassDataset():# 1 获取数据myfilename = './data/name_classfication.txt'my_list_x, my_list_y = read_data(myfilename)print('my_list_x length', len(my_list_x))print('my_list_y length', len(my_list_y))# 2 实例化dataset对象nameclassdataset = NameClassDataset(my_list_x, my_list_y)# 3 实例化dataloadermydataloader = DataLoader(dataset=nameclassdataset, batch_size=1, shuffle=True)for i, (x, y) in enumerate (mydataloader):print('x.shape', x.shape, x)print('y.shape', y.shape, y)break
运行结果:
my_list_x length 20074
my_list_y length 20074
x.shape torch.Size([1, 5, 57]) tensor([[[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0.],[0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0.],[1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0.],[1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0.]]])
y.shape torch.Size([1]) tensor([15])
三、构建RNN模型(选择LSTM模型)
(一)构建模型
# LSTM类 实现思路分析:
# 1 init函数 准备三个层 self.rnn self.linear self.softmax=nn.LogSoftmax(dim=-1)
# def __init__(self, input_size, hidden_size, output_size, num_layers=1)# 2 forward(input, hidden)函数
# 让数据经过三个层 返回softmax结果和hn
# 形状变化 [seqlen,1,57],[1,1,128]) -> [seqlen,1,128],[1,1,128]# 3 初始化隐藏层输入数据 inithidden()
# 形状[self.num_layers, 1, self.hidden_size]
class LSTM(nn.Module):def __init__(self, input_size, hidden_size, output_size, num_layers=1):super(LSTM, self).__init__()# 1 init函数 准备三个层 self.rnn self.linear self.softmax=nn.LogSoftmax(dim=-1)self.input_size = input_sizeself.hidden_size = hidden_sizeself.output_size = output_sizeself.num_layers = num_layers# 定义rnn层self.rnn = nn.LSTM(self.input_size, self.hidden_size, self.num_layers)# 定义linear层self.linear = nn.Linear(self.hidden_size, self.output_size)# 定义softmax层self.softmax = nn.LogSoftmax(dim=-1)def forward(self, input, hidden, c):# 让数据经过三个层 返回softmax结果和 hn c# 数据形状 [6,57] -> [6,1,52]input = input.unsqueeze(1)# 把数据送给模型 提取事物特征# 数据形状 [seqlen,1,57],[1,1,128], [1,1,128]) -> [seqlen,1,18],[1,1,128],[1,1,128]rr, (hn, cn) = self.rnn(input, (hidden, c))# 数据形状 [seqlen,1,128] - [1, 128]tmprr = rr[-1]tmprr = self.linear(tmprr)return self.softmax(tmprr), hn, cndef inithidden(self):# 初始化隐藏层输入数据 inithidden()hidden = c = torch.zeros(self.num_layers, 1, self.hidden_size)return hidden, c
(二)模型测试
def dm_test_rnn_lstm_gru():# one-hot编码特征57(n_letters),也是RNN的输入尺寸input_size = 57# 定义隐层的最后一维尺寸大小n_hidden = 128# 输出尺寸为语言类别总数n_categories # 1个字符预测成18个类别output_size = 18# 1 获取数据myfilename = './data/name_classfication.txt'my_list_x, my_list_y = read_data(myfilename)print('categorys--->', categorys)# 2 实例化dataset对象nameclassdataset = NameClassDataset(my_list_x, my_list_y)# 3 实例化dataloadermydataloader = DataLoader(dataset=nameclassdataset, batch_size=1, shuffle=True)my_lstm = LSTM(n_letters, n_hidden, categorynum)print('lstm 模型', my_lstm)for i, (x, y) in enumerate (mydataloader):# print('x.shape', x.shape, x)# print('y.shape', y.shape, y)hidden, c = my_lstm.inithidden()output, hidden, c = my_lstm(x[0], hidden, c)print("lstm output.shape--->:", output.shape, output)if (i == 0):break
运行结果:
lstm 模型 LSTM((rnn): LSTM(57, 128)(linear): Linear(in_features=128, out_features=18, bias=True)(softmax): LogSoftmax(dim=-1)
)
lstm output.shape--->: torch.Size([1, 18]) tensor([[-2.9283, -3.0017, -2.8902, -2.8179, -2.8484, -2.8152, -2.9654, -2.8846,-2.8642, -2.8602, -2.8860, -2.9505, -2.8806, -2.9436, -2.8388, -2.9312,-2.9241, -2.8211]], grad_fn=<LogSoftmaxBackward0>)
四、构建训练函数并进行训练
(一)构建RNN训练函数
# 思路分析
# 从文件获取数据、实例化数据源对象nameclassdataset 数据迭代器对象mydataloader
# 实例化模型对象my_rnn 损失函数对象mycrossentropyloss=nn.NLLLoss() 优化器对象myadam
# 定义模型训练的参数
# starttime total_iter_num total_loss total_loss_list total_acc_num total_acc_list
# 外层for循环 控制轮数 for epoch_idx in range(epochs)
# 内层for循环 控制迭代次数 for i, (x, y) in enumerate(mydataloader)# 给模型喂数据 # 计算损失 # 梯度清零 # 反向传播 # 梯度更新# 计算辅助信息 # 累加总损失和准确数 每100次训练计算一个总体平均损失 总体平均准确率 每2000次训练 打印日志# 其他 # 预测对错 i_predit_tag = (1 if torch.argmax(output).item() == y.item() else 0)# 模型保存# torch.save(my_rnn.state_dict(), './my_rnn_model_%d.bin' % (epoch_idx + 1))
# 返回 平均损失列表total_loss_list, 时间total_time, 平均准确total_acc_list
(二)LSTM模型训练
def my_train_lstm():# 获取数据myfilename = './data/name_classfication.txt'my_list_x, my_list_y = read_data(myfilename)# 实例化dataset对象nameclassdataset = NameClassDataset(my_list_x, my_list_y)# 实例化 模型input_size = 57n_hidden = 128output_size = 18my_lstm = LSTM(input_size, n_hidden, output_size)print('my_lstm模型--->', my_lstm)# 实例化 损失函数 adam优化器mycrossentropyloss = nn.NLLLoss()myadam = optim.Adam(my_lstm.parameters(), lr=mylr)# 定义模型训练参数starttime = time.time()total_iter_num = 0 # 已训练的样本数total_loss = 0.0 # 已训练的损失和total_loss_list = [] # 每100个样本求一次平均损失 形成损失列表total_acc_num = 0 # 已训练样本预测准确总数total_acc_list = [] # 每100个样本求一次平均准确率 形成平均准确率列表# 外层for循环 控制轮数for epoch_idx in range(epochs):# 实例化dataloadermydataloader = DataLoader(dataset=nameclassdataset, batch_size=1, shuffle=True)# 内层for循环 控制迭代次数for i, (x, y) in enumerate(mydataloader):# 给模型喂数据hidden, c = my_lstm.inithidden()output, hidden, c = my_lstm(x[0], hidden, c)# 计算损失myloss = mycrossentropyloss(output, y)# 梯度清零myadam.zero_grad()# 反向传播myloss.backward()# 梯度更新myadam.step()# 计算总损失total_iter_num = total_iter_num + 1total_loss = total_loss + myloss.item()# 计算总准确率i_predit_tag = (1 if torch.argmax(output).item() == y.item() else 0)total_acc_num = total_acc_num + i_predit_tag# 每100次训练 求一次平均损失 平均准确率if (total_iter_num % 100 == 0):tmploss = total_loss/total_iter_numtotal_loss_list.append(tmploss)tmpacc = total_acc_num/total_iter_numtotal_acc_list.append(tmpacc)# 每2000次训练 打印日志if (total_iter_num % 2000 == 0):tmploss = total_loss / total_iter_numprint('轮次:%d, 损失:%.6f, 时间:%d,准确率:%.3f' %(epoch_idx+1, tmploss, time.time() - starttime, tmpacc))# 每个轮次保存模型torch.save(my_lstm.state_dict(), './my_lstm_model_%d.bin' % (epoch_idx + 1))# 计算总时间total_time = int(time.time() - starttime)return total_loss_list, total_time, total_acc_list
(三)模型训练日志数据制图
def dm_test_train_rnn_lstm_gru():total_loss_list_rnn, total_time_rnn, total_acc_list_rnn = my_train_rnn()total_loss_list_lstm, total_time_lstm, total_acc_list_lstm = my_train_lstm()total_loss_list_gru, total_time_gru, total_acc_list_gru = my_train_gru()# 绘制损失对比曲线# 创建画布0plt.figure(0)# # 绘制损失对比曲线plt.plot(total_loss_list_rnn, label="RNN")plt.plot(total_loss_list_lstm, color="red", label="LSTM")plt.plot(total_loss_list_gru, color="orange", label="GRU")plt.legend(loc='upper left')plt.savefig('./img/RNN_LSTM_GRU_loss2.png')plt.show()# 绘制柱状图# 创建画布1plt.figure(1)x_data = ["RNN", "LSTM", "GRU"]y_data = [total_time_rnn, total_time_lstm, total_time_gru]# 绘制训练耗时对比柱状图plt.bar(range(len(x_data)), y_data, tick_label=x_data)plt.savefig('./img/RNN_LSTM_GRU_period2.png')plt.show()# 绘制准确率对比曲线plt.figure(2)plt.plot(total_acc_list_rnn, label="RNN")plt.plot(total_acc_list_lstm, color="red", label="LSTM")plt.plot(total_acc_list_gru, color="orange", label="GRU")plt.legend(loc='upper left')plt.savefig('./img/RNN_LSTM_GRU_acc2.png')plt.show()
模型训练日志输出:
轮次:3, 损失:0.805885, 时间:118,准确率:0.759
轮次:3, 损失:0.794148, 时间:123,准确率:0.762
轮次:3, 损失:0.783356, 时间:128,准确率:0.765
轮次:3, 损失:0.774931, 时间:133,准确率:0.767
轮次:3, 损失:0.765427, 时间:137,准确率:0.769
轮次:3, 损失:0.757254, 时间:142,准确率:0.771
轮次:3, 损失:0.750375, 时间:147,准确率:0.773
轮次:3, 损失:0.743092, 时间:152,准确率:0.775
轮次:4, 损失:0.732983, 时间:157,准确率:0.778
轮次:4, 损失:0.723816, 时间:162,准确率:0.780
轮次:4, 损失:0.716507, 时间:167,准确率:0.782
轮次:4, 损失:0.708377, 时间:172,准确率:0.785
轮次:4, 损失:0.700820, 时间:177,准确率:0.787
轮次:4, 损失:0.694714, 时间:182,准确率:0.788
轮次:4, 损失:0.688386, 时间:187,准确率:0.790
轮次:4, 损失:0.683056, 时间:191,准确率:0.791
轮次:4, 损失:0.677051, 时间:196,准确率:0.793
轮次:4, 损失:0.671668, 时间:201,准确率:0.794
(四)模型训练结果分析
1.损失对比曲线分析
左图:1个轮次损失对比曲线,右图4个轮次损失对比曲线
模型训练的损失降低快慢代表模型收敛程度。由图可知, 传统RNN的模型第一个轮次开始收敛情况最好,然后是GRU, 最后是LSTM, 这是因为RNN模型简单参数少,见效快。随着训练数据的增加,GRU效果最好、LSTM效果次之、RNN效果排最后。
所以在以后的模型选用时, 要通过对任务的分析以及实验对比, 选择最适合的模型。
2 .训练耗时分析
训练耗时对比图:
模型训练的耗时长短代表模型的计算复杂度,由图可知, 也正如我们之前的理论分析,传统RNN复杂度最低, 耗时几乎只是后两者的一半, 然后是GRU,最后是复杂度最高的LSTM。
3. 训练准确率分析
训练准确率对比图:
由图可知, GRU效果最好、LSTM效果次之、RNN效果排最后。
4.结论
模型选用一般应通过实验对比,并非越复杂或越先进的模型表现越好,而是需要结合自己的特定任务,从对数据的分析和实验结果中获得最佳答案。
五、模型预测
def my_predict_lstm(x):n_letters = 57n_hidden = 128n_categories = 18# 输入文本, 张量化one-hotx_tensor = lineToTensor(x)# 实例化模型 加载已训练模型参数my_lstm = LSTM(n_letters, n_hidden, n_categories)my_lstm.load_state_dict(torch.load(my_path_lstm))with torch.no_grad():# 模型预测hidden, c = my_lstm.inithidden()output, hidden, c = my_lstm(x_tensor, hidden, c)# 从预测结果中取出前3名# 3表示取前3名, 1表示要排序的维度, True表示是否返回最大或是最下的元素topv, topi = output.topk(3, 1, True)print('rnn =>', x)for i in range(3):value = topv[0][i]category_idx = topi[0][i]category = categorys[category_idx]print('\t value:%d category:%s' % (value, category))print('\t value:%d category:%s' % (value, category))
运行结果:
rnn => zhangvalue:0 category:Chinesevalue:-1 category:Russianvalue:-1 category:German
本案例也可以构建传统rnn模型或者gru模型进行目标完成,具体大家可自行尝试。
最后,附赠代码完整版(包括传统RNN、LSTM和GRU建模及预测)
# -*-coding:utf-8-*-
# 导入torch工具
import torch
# 导入nn准备构建模型
import torch.nn as nn
#导入优化器optim
import torch.optim as optim
# 导入torch的数据源 数据迭代器工具包
from torch.utils.data import Dataset, DataLoader
# 用于获得常见字母及字符规范化
import string
# 导入时间工具包
import time
# 引入制图工具包
import matplotlib.pyplot as plt
from tqdm import tqdm
import json# 1.todo: 获取常用的字符数量
# 此次将人名变成向量的过程:将人名中的每个字母(字符)进行one-hot张量表示,然后拼接代表整个人名的向量表示
# 因为人名的组成大部分都是由大小写英文字母以及某些特殊的字符组成,这里一个展示是57个,其实就是one-hot编码的维度all_letters = string.ascii_letters+" .,;'"
print(f'all_letters--》{all_letters}')
print(f'{all_letters.find("A")}')
n_letters = len(all_letters)
print('字符的总个数', n_letters)# 2 todo: 获取国家的类别个数
# 国家名 种类数
categorys = ['Italian', 'English', 'Arabic', 'Spanish', 'Scottish', 'Irish', 'Chinese', 'Vietnamese', 'Japanese','French', 'Greek', 'Dutch', 'Korean', 'Polish', 'Portuguese', 'Russian', 'Czech', 'German']
# 国家名 个数
categorynum = len(categorys)
print('categorys--->', categorynum)# 3 todo. 读取数据到内存中
def read_data(filename):# 定义两个空列表,分别存储人名和国家名my_list_x, my_list_y = [], []# 读取数据with open(filename, 'r', encoding='utf-8') as fr:for line in fr.readlines():# 数据清洗if len(line) <= 5:continuex, y = line.strip().split('\t')my_list_x.append(x)my_list_y.append(y)return my_list_x, my_list_y# 4 todo. 构建dataset类
class NameClassDataset(Dataset):def __init__(self, my_list_x, my_list_y):# 如果继承的父类没有__init__方法,那么此时可以省略掉super().__init__(),当然要是写上也不会报错# super().__init__()# 获取样本xself.my_list_x = my_list_x# 获取样本x对应的标签yself.my_list_y = my_list_y# 获取样本的长度self.sample_len = len(my_list_x)def __len__(self):return self.sample_lendef __getitem__(self, item):# item代表就是索引index = min(max(item, 0), self.sample_len-1)# 根据索引取出对应的x和yx = self.my_list_x[index]# print(f'x---->{x}')y = self.my_list_y[index]# print(f'y---》{y}')# 初始化全零的一个张量tensor_x = torch.zeros(len(x), n_letters)# 遍历人名的每个字母变成one-hot编码for idx, letter in enumerate(x):tensor_x[idx][all_letters.find(letter)] = 1# print(f'tensor_x--》{tensor_x}')tensor_y = torch.tensor(categorys.index(y), dtype=torch.long)return tensor_x, tensor_y# 5 todo. 实例化Dataloader
def get_dataloader():my_list_x, my_list_y = read_data(filename='./data/name_classfication.txt')nameClass_dataset = NameClassDataset(my_list_x, my_list_y)# 实例化Dataloadertrain_dataloader = DataLoader(dataset=nameClass_dataset,batch_size=1,shuffle=True)# for tensor_x, tensor_y in train_dataloader:# print(f'tensor_x--》{tensor_x.shape}')# print(f'tensor_y--》{tensor_y.shape}')# breakreturn train_dataloader# 6 todo. 定义RNN模型
class NameRNN(nn.Module):def __init__(self, input_size, hidden_size, output_size, num_layers=1):super().__init__()# input_size-->输入x单词的词嵌入维度self.input_size = input_size# hidden_size-->RNN输出的隐藏层维度self.hidden_size = hidden_size# output_size-->国家类别的总个数self.output_size = output_size# num_layers:几层隐藏层self.num_layers = num_layers# 实例化RNN对象# batch_first=False,默认self.rnn = nn.RNN(input_size, hidden_size, num_layers)# 定义输出层self.out = nn.Linear(hidden_size, output_size)# 定义logsoftmax层self.softmax = nn.LogSoftmax(dim=-1)def forward(self, input_x, hidden):# input_x按照讲义,输入的时候,是个二维的【seq_len, input_size】-->[6, 57]# hidden:初始化隐藏层的值:[1, 1, 128]# 升维度,在dim=1的维度进行升维:input_x-->[6, 1, 57]input_x = input_x.unsqueeze(dim=1)# 将input_x和hidden送入rnn:rnn_output-->shape--[6,1,128];hn-->[1, 1, 128]rnn_output, hn = self.rnn(input_x, hidden)# 将上述rnn的结果经过输出层;rnn_output[-1]获取最后一个单词的词向量代表整个句子的语意# temp_vec-->[1, 128]temp_vec = rnn_output[-1]# 将temp_vec送入输出层:result-->[1, 18]result = self.out(temp_vec)return self.softmax(result), hndef inithidden(self):return torch.zeros(self.num_layers, 1, self.hidden_size)# 7 todo. 定义LSTM模型
class NameLSTM(nn.Module):def __init__(self, input_size, hidden_size, output_size, num_layers=1):super().__init__()# input_size-->输入x单词的词嵌入维度self.input_size = input_size# hidden_size-->RNN输出的隐藏层维度self.hidden_size = hidden_size# output_size-->国家类别的总个数self.output_size = output_size# num_layers:几层隐藏层self.num_layers = num_layers# 实例化LSTM对象self.lstm = nn.LSTM(input_size, hidden_size, num_layers)# 定义输出层self.out = nn.Linear(hidden_size, output_size)# 定义logsoftmax层self.softmax = nn.LogSoftmax(dim=-1)def forward(self, input_x, h0, c0):# input_x-shape-->[seq_len, embed_dim]-->[6, 57]# h0,c0-->shape-->[num_layers, batch_size, hidden_size]-->[1, 1, 128]# 1.先对input_x升维度;shape-->[6, 1, 57]input_x = input_x.unsqueeze(dim=1)# 2.将input_x, h0,c0送入lstm模型# lstm_output-->shape=-->[6, 1, 128]lstm_output, (hn, cn) = self.lstm(input_x, (h0, c0))# 3.取出lstm输出结果中最后一个单词对应的隐藏层输出结果送入输出层# temp_vec-->shape-->[1, 128]temp_vec = lstm_output[-1]# 送入输出层result->shape-->[1, 18]result = self.out(temp_vec)return self.softmax(result), hn, cndef inithidden(self):h0 = torch.zeros(self.num_layers, 1, self.hidden_size)c0 = torch.zeros(self.num_layers, 1, self.hidden_size)return h0, c0# 8 todo. 定义GRU模型
class NameGRU(nn.Module):def __init__(self, input_size, hidden_size, output_size, num_layers=1):super().__init__()# input_size-->输入x单词的词嵌入维度self.input_size = input_size# hidden_size-->RNN输出的隐藏层维度self.hidden_size = hidden_size# output_size-->国家类别的总个数self.output_size = output_size# num_layers:几层隐藏层self.num_layers = num_layers# 实例化GRU对象# batch_first=False,默认self.gru = nn.GRU(input_size, hidden_size, num_layers)# 定义输出层self.out = nn.Linear(hidden_size, output_size)# 定义logsoftmax层self.softmax = nn.LogSoftmax(dim=-1)def forward(self, input_x, hidden):# input_x按照讲义,输入的时候,是个二维的【seq_len, input_size】-->[6, 57]# hidden:初始化隐藏层的值:[1, 1, 128]# 升维度,在dim=1的维度进行升维:input_x-->[6, 1, 57]input_x = input_x.unsqueeze(dim=1)# 将input_x和hidden送入rnn:rnn_output-->shape--[6,1,128];hn-->[1, 1, 128]rnn_output, hn = self.gru(input_x, hidden)# 将上述gru的结果经过输出层;rnn_output[-1]获取最后一个单词的词向量代表整个句子的语意# temp_vec-->[1, 128]temp_vec = rnn_output[-1]# 将temp_vec送入输出层:result-->[1, 18]result = self.out(temp_vec)return self.softmax(result), hndef inithidden(self):return torch.zeros(self.num_layers, 1, self.hidden_size)my_lr = 1e-3
epochs = 1# 9 todo: 定义RNN模型的训练函数
def train_rnn():# 1.读取txt文档数据my_list_x, my_list_y = read_data(filename='./data/name_classfication.txt')# 2.实例化Dataset对象name_dataset = NameClassDataset(my_list_x, my_list_y)# 3.实例化自定义的RNN模型对象input_size = n_letters # 57hidden_size = 128output_size = categorynum # 18name_rnn = NameRNN(input_size, hidden_size, output_size)# 4.实例化损失函数对象以及优化器对象rnn_nllloss = nn.NLLLoss()rnn_adam = optim.Adam(name_rnn.parameters(), lr=my_lr)# 5.定义训练日志的参数start_time = time.time()total_iter_num = 0 # 已经训练的样本的总数total_loss = 0.0 # 已经训练的样本的总损失total_loss_list = [] # 每隔100个样本我们计算一下平均损失,并保存total_acc_num = 0 # 已经训练的样本中预测正确的个数total_acc_list = []# 每隔100个样本我们计算一下平均准确率,并保存# 6. 开始外部迭代for epoch_idx in range(epochs):# 6.1 实例化Dataloader的对象train_dataloader = DataLoader(dataset=name_dataset, batch_size=1, shuffle=True)# 6.2 开始遍历迭代器,内部迭代for idx, (tensor_x, tensor_y) in enumerate(tqdm(train_dataloader)):# 6.3 准备模型需要的数据tensor_x0 = tensor_x[0] # [seq_length, input_size]h0 = name_rnn.inithidden()# print(f'tensor_y--》{tensor_y}')# 6.4 将数据送入模型得到预测结果output-->shape-->[1, 18]output, hn = name_rnn(tensor_x0, h0)# print(f'output--》{output}')# 6.5 计算损失my_loss = rnn_nllloss(output, tensor_y)# print(f'my_loss--》{my_loss}')# 6.6 梯度清零rnn_adam.zero_grad()# 6.7 反向传播my_loss.backward()# 6.8 梯度更新rnn_adam.step()# 6.9 统计已经训练的样本的总个数total_iter_num = total_iter_num + 1# 6.10 统计已经训练的样本总损失total_loss = total_loss + my_loss.item()# 6.11 统计已经训练的样本中预测正确的样本总个数# torch.argmax(output)取出预测结果中最大概率值对应的索引predict_id = 1 if torch.argmax(output).item() == tensor_y.item() else 0total_acc_num = total_acc_num + predict_id# 6.12 每间隔100步,保存平均损失已经平均准确率if (total_iter_num % 100) == 0:# 计算平均损失并保存avg_loss = total_loss / total_iter_numtotal_loss_list.append(avg_loss)# 计算平均准确率并保存avg_acc = total_acc_num / total_iter_numtotal_acc_list.append(avg_acc)# 6.13 每间隔2000步,打印日志if (total_iter_num % 2000) == 0:temp_avg_loss = total_loss / total_iter_numtemp_avg_acc = total_acc_num / total_iter_numprint('轮次:%d, 损失:%.6f, 时间:%d,准确率:%.3f' %(epoch_idx+1, temp_avg_loss, time.time() - start_time, temp_avg_acc))# 7保存模型torch.save(name_rnn.state_dict(), './save_model/szAI_%d.bin'%(epoch_idx+1))total_time = time.time() - start_timernn_dict = {"total_loss_list": total_loss_list,"total_time": total_time,"total_acc_list": total_acc_list}with open('name_classify_rnn.json', 'w', encoding='utf-8') as fw:fw.write(json.dumps(rnn_dict))
def train_lstm():my_list_x, my_list_y = read_data(filename='./data/name_classfication.txt')# 2.实例化Dataset对象name_dataset = NameClassDataset(my_list_x, my_list_y)# 3.实例化自定义的RNN模型对象input_size = n_letters # 57hidden_size = 128output_size = categorynum # 18name_lstm = NameLSTM(input_size, hidden_size, output_size)# 4.实例化损失函数对象以及优化器对象rnn_nllloss = nn.NLLLoss()rnn_adam = optim.Adam(name_lstm.parameters(), lr=my_lr)# 5.定义训练日志的参数start_time = time.time()total_iter_num = 0 # 已经训练的样本的总数total_loss = 0.0 # 已经训练的样本的总损失total_loss_list = [] # 每隔100个样本我们计算一下平均损失,并保存total_acc_num = 0 # 已经训练的样本中预测正确的个数total_acc_list = [] # 每隔100个样本我们计算一下平均准确率,并保存# 6. 开始外部迭代for epoch_idx in range(epochs):# 6.1 实例化Dataloader的对象train_dataloader = DataLoader(dataset=name_dataset, batch_size=1, shuffle=True)# 6.2 开始遍历迭代器,内部迭代for idx, (tensor_x, tensor_y) in enumerate(tqdm(train_dataloader)):# 6.3 准备模型需要的数据tensor_x0 = tensor_x[0] # [seq_length, input_size]h0,c = name_lstm.inithidden()# print(f'tensor_y--》{tensor_y}')# 6.4 将数据送入模型得到预测结果output-->shape-->[1, 18]output, hn,cn = name_lstm(tensor_x0, h0,c)# print(f'output--》{output}')# 6.5 计算损失my_loss = rnn_nllloss(output, tensor_y)# print(f'my_loss--》{my_loss}')# 6.6 梯度清零rnn_adam.zero_grad()# 6.7 反向传播my_loss.backward()# 6.8 梯度更新rnn_adam.step()# 6.9 统计已经训练的样本的总个数total_iter_num = total_iter_num + 1# 6.10 统计已经训练的样本总损失total_loss = total_loss + my_loss.item()# 6.11 统计已经训练的样本中预测正确的样本总个数# torch.argmax(output)取出预测结果中最大概率值对应的索引predict_id = 1 if torch.argmax(output).item() == tensor_y.item() else 0total_acc_num = total_acc_num + predict_id# 6.12 每间隔100步,保存平均损失已经平均准确率if (total_iter_num % 100) == 0:# 计算平均损失并保存avg_loss = total_loss / total_iter_numtotal_loss_list.append(avg_loss)# 计算平均准确率并保存avg_acc = total_acc_num / total_iter_numtotal_acc_list.append(avg_acc)# 6.13 每间隔2000步,打印日志if (total_iter_num % 2000) == 0:temp_avg_loss = total_loss / total_iter_numtemp_avg_acc = total_acc_num / total_iter_numprint('轮次:%d, 损失:%.6f, 时间:%d,准确率:%.3f' % (epoch_idx + 1, temp_avg_loss, time.time() - start_time, temp_avg_acc))# 7保存模型torch.save(name_lstm.state_dict(), './save_model/_%d.bin' % (epoch_idx + 1))total_time=time.time()-start_time# 9. 将损失列表和准确率列表以及时间保存到字典并存储到文件里lstm_dict = {"total_loss_list": total_loss_list,"total_time": total_time,"total_acc_list": total_acc_list}with open('name_classify_lstm.json', 'w', encoding='utf-8') as fw:fw.write(json.dumps(lstm_dict))
def train_gru():# 1.读取txt文档数据my_list_x, my_list_y = read_data(filename='./data/name_classfication.txt')# 2.实例化Dataset对象name_dataset = NameClassDataset(my_list_x, my_list_y)# 3.实例化自定义的RNN模型对象input_size = n_letters # 57hidden_size = 128output_size = categorynum # 18name_gru = NameGRU(input_size, hidden_size, output_size)# 4.实例化损失函数对象以及优化器对象rnn_nllloss = nn.NLLLoss()rnn_adam = optim.Adam(name_gru.parameters(), lr=my_lr)# 5.定义训练日志的参数start_time = time.time()total_iter_num = 0 # 已经训练的样本的总数total_loss = 0.0 # 已经训练的样本的总损失total_loss_list = [] # 每隔100个样本我们计算一下平均损失,并保存total_acc_num = 0 # 已经训练的样本中预测正确的个数total_acc_list = []# 每隔100个样本我们计算一下平均准确率,并保存# 6. 开始外部迭代for epoch_idx in range(epochs):# 6.1 实例化Dataloader的对象train_dataloader = DataLoader(dataset=name_dataset, batch_size=1, shuffle=True)# 6.2 开始遍历迭代器,内部迭代for idx, (tensor_x, tensor_y) in enumerate(tqdm(train_dataloader)):# 6.3 准备模型需要的数据tensor_x0 = tensor_x[0] # [seq_length, input_size]h0 = name_gru.inithidden()# print(f'tensor_y--》{tensor_y}')# 6.4 将数据送入模型得到预测结果output-->shape-->[1, 18]output, hn = name_gru(tensor_x0, h0)# print(f'output--》{output}')# 6.5 计算损失my_loss = rnn_nllloss(output, tensor_y)# print(f'my_loss--》{my_loss}')# 6.6 梯度清零rnn_adam.zero_grad()# 6.7 反向传播my_loss.backward()# 6.8 梯度更新rnn_adam.step()# 6.9 统计已经训练的样本的总个数total_iter_num = total_iter_num + 1# 6.10 统计已经训练的样本总损失total_loss = total_loss + my_loss.item()# 6.11 统计已经训练的样本中预测正确的样本总个数# torch.argmax(output)取出预测结果中最大概率值对应的索引predict_id = 1 if torch.argmax(output).item() == tensor_y.item() else 0total_acc_num = total_acc_num + predict_id# 6.12 每间隔100步,保存平均损失已经平均准确率if (total_iter_num % 100) == 0:# 计算平均损失并保存avg_loss = total_loss / total_iter_numtotal_loss_list.append(avg_loss)# 计算平均准确率并保存avg_acc = total_acc_num / total_iter_numtotal_acc_list.append(avg_acc)# 6.13 每间隔2000步,打印日志if (total_iter_num % 2000) == 0:temp_avg_loss = total_loss / total_iter_numtemp_avg_acc = total_acc_num / total_iter_numprint('轮次:%d, 损失:%.6f, 时间:%d,准确率:%.3f' %(epoch_idx+1, temp_avg_loss, time.time() - start_time, temp_avg_acc))# 7保存模型torch.save(name_gru.state_dict(), './save_model/gru_%d.bin'%(epoch_idx+1))total_time = time.time() - start_timegru_dict = {"total_loss_list": total_loss_list,"total_time": total_time,"total_acc_list": total_acc_list}with open('name_classify_gru.json', 'w', encoding='utf-8') as fw:fw.write(json.dumps(gru_dict))
def draw_picture():with (open('name_classify_gru.json','r') as fg,open('name_classify_rnn.json','r') as fr,open('name_classify_lstm.json','r') as fl):fr=json.loads(fr.read())fg=json.loads(fg.read())fl=json.loads(fl.read())plt.figure(0)plt.plot(fr['total_loss_list'],label='rnn')plt.plot(fl['total_loss_list'],label='lstm')plt.plot(fg['total_loss_list'],label='gru')plt.legend(loc='upper left')plt.show()plt.figure(1)x_list=["RNN", "LSTM", "GRU"]plt.bar(range(len(x_list)),y=[fr['total_time'],fl['total_time'],fg['total_time']],tick_label=x_list)plt.show()plt.figure(2)plt.plot(fr['total_acc_list'], label='rnn')plt.plot(fl['total_acc_list'], label='lstm')plt.plot(fg['total_acc_list'], label='gru')plt.legend(loc='upper left')plt.show()
def str_to_vec(x):len_letter=len(x)tensor_x = torch.zeros(len_letter, n_letters)for idx,letter in enumerate(x):tensor_x[idx][all_letters.find(letter)]=1return tensor_x
#模型预测
def my_predict_rnn(x):n_letters = 57n_hidden = 128n_categories = 18# 输入文本, 张量化one-hotx_tensor = str_to_vec(x)# 实例化模型 加载已训练模型参数my_rnn = NameRNN(n_letters, n_hidden, n_categories)my_rnn.load_state_dict(torch.load( './save_model/szAI_1.bin'))with torch.no_grad():# 模型预测output, hidden = my_rnn(x_tensor, my_rnn.inithidden())# 从预测结果中取出前3名# 3表示取前3名, 1表示要排序的维度, True表示是否返回最大或是最下的元素topv, topi = output.topk(3, 1, True)print('rnn =>', x)for i in range(3):value = topv[0][i]category_idx = topi[0][i]category = categorys[category_idx]print('\t value:%d category:%s' %(value, category))
def my_predict_lstm(x):n_letters = 57n_hidden = 128n_categories = 18# 输入文本, 张量化one-hotx_tensor = str_to_vec(x)# 实例化模型 加载已训练模型参数my_rnn = NameLSTM(n_letters, n_hidden, n_categories)my_rnn.load_state_dict(torch.load( './save_model/_1.bin'))with torch.no_grad():# 模型预测hidden, c=my_rnn.inithidden()output, hidden ,c= my_rnn(x_tensor,hidden,c )# 从预测结果中取出前3名# 3表示取前3名, 1表示要排序的维度, True表示是否返回最大或是最下的元素topv, topi = output.topk(3, 1, True)print(topi,topv) #一个是值,一个是索引print('lstm =>', x)for i in range(3):value = topv[0][i]category_idx = topi[0][i]category = categorys[category_idx]print('\t value:%d category:%s' %(value, category))
def my_predict_gru(x):n_letters = 57n_hidden = 128n_categories = 18# 输入文本, 张量化one-hotx_tensor = str_to_vec(x)# 实例化模型 加载已训练模型参数my_rnn = NameGRU(n_letters, n_hidden, n_categories)my_rnn.load_state_dict(torch.load( './save_model/gru_1.bin'))with torch.no_grad():# 模型预测hidden=my_rnn.inithidden()output, hidden= my_rnn(x_tensor,hidden)# 从预测结果中取出前3名# 3表示取前3名, 1表示要排序的维度, True表示是否返回最大或是最下的元素topv, topi = output.topk(3, 1, True)print(topi,topv) #一个是值,一个是索引print('lstm =>', x)for i in range(3):value = topv[0][i]category_idx = topi[0][i]category = categorys[category_idx]print('\t value:%d category:%s' %(value, category))if __name__ == '__main__':# train_dataloader = get_dataloader()# # name_lstm= NameLSTM(input_size=n_letters, hidden_size=128, output_size=categorynum)# name_gru= NameGRU(input_size=n_letters, hidden_size=128, output_size=categorynum)# print(name_gru)# for tensor_x, tensor_y in train_dataloader:# h0 = name_gru.inithidden()# x0 = tensor_x[0]# output, hn= name_gru(x0, h0)# print(f'output--》{output.shape}')# print(f'output--》{output}')# print(f'hn--》{hn.shape}')# break# train_rnn()# train_lstm()# train_gru()# draw_picture()# tensor_x=str_to_vec('zhang')# print(tensor_x)my_predict_rnn('zhang')my_predict_lstm('zhang')my_predict_gru('zhang')my_predict_rnn('Benesch')my_predict_lstm('Benesch')my_predict_gru('Benesch')
今日分享结束。