模型训练实现:
import json
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import string
import time
import matplotlib.pyplot as plt
from io import open
from tqdm import tqdm
all_letters = string.ascii_letters + " .,;'"
n_letter = len(all_letters)
print(f’all_letters长度–>{len(all_letters)}‘)
print(’*'*80)
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)
print(‘*’*80)
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)
print(‘*’*80)
def read_data(filename):
# 1. 初始化两个空列表
my_list_x, my_list_y = [], []
# 2. 读取文件内容
with open(filename,‘r’, encoding=‘utf-8’) as fr:
for line in fr.readlines():
if len(line) <= 5:
continue
# strip()方法默认将字符串首尾两端的空白去掉
x, y = line.strip().split(‘\t’)
my_list_x.append(x)
my_list_y.append(y)
return my_list_x, my_list_y
class NameClassDataset(Dataset):
def init(self, mylist_x, mylist_y):
self.mylist_x = mylist_x
self.mylist_y = mylist_y
self.sample_len = len(mylist_x)
def __len__(self):
return self.sample_len
def __getitem__(self, index):
# todo 异常值处理 max(x,y) 取x,y较大的值,若小于0则取0
# todo min(x,y) 取最小的值 保证索引不超出界限
index = min(max(index, 0), self.sample_len - 1)
x = self.mylist_x[index]
y = self.mylist_y[index]
# todo one-hot处理 0,1 创建全0张量 特征置为一 就实现了one-hot编码
tensor_x=torch.zeros(len(x),n_letter)
# li 索引 letter 值
for li,letter in enumerate(x):
# todo 将得到第li行数第find列置为1
tensor_x[li][all_letters.find(letter)]=1
# 返回值的索引 置为张量
tensor_y=torch.tensor(categorys.index(y),dtype=torch.long)
return tensor_x,tensor_y
def get_dataloader():
# todo 核心思想: 处理脏数据 封装数据处理方法 将数据打乱分批次
filename = ‘…/data/name_classfication.txt’
my_list_x, my_list_y = read_data(filename)
mydataset = NameClassDataset(mylist_x=my_list_x, mylist_y=my_list_y)
my_dataloader = DataLoader(dataset=mydataset, batch_size=1, shuffle=True)
return my_dataloader
class MyRNN(nn.Module):
def init(self, input_size, hidden_size, ouput_size, num_layers=1):
# 调用nn方法
super().init()
# todo 输入 长度不一致的数据:文本 时间序列数据 长度一致:语音 图像
# todo 再这里输入前需要对数据处理保证长度一致 方法:填充 截断
self.input_size = input_size
# todo 长度固定 8 62 128 256
self.hidden_size = hidden_size
# todo 长度等于要预测的种类数量
self.ouput_size = ouput_size
self.num_layers = num_layers
# rnn网络层 todo 昨天区别参数顺序不一致 因为batch_first=true
self.rnn = nn.RNN(self.input_size, self.hidden_size,
num_layers=self.num_layers, batch_first=True)
# 输出层
self.linear = nn.Linear(self.hidden_size, self.ouput_size)
# softmax层
# todo Softmax 函数会将输入转换为概率分布,然后再通过 CrossEntropyLoss 计算交叉熵损失。
# todo LogSoftmax 先将输入转换为对数概率分布,然后与 NLLLoss 结合计算损失。
# todo 选型 回归:mse mae 分类 Softmax LogSoftmax
# todo 选型: LogSoftmax + NLLLoss 计算快多分类
# todo 选型: Softmax + CrossEntropyLoss 默认多分类 不稳定
# todo dim=-1 最后一个维度
self.softmax = nn.LogSoftmax(dim=-1)
def forward(self,input,hidden):
rnn_output, rnn_hn = self.rnn(input, hidden)
# todo 保持维度一致 如果这里维度不统一后续计算会出错
tmep = rnn_output[0][-1].unsqueeze(0)
output = self.linear(tmep)
return self.softmax(output), rnn_hn
# 初始化隐藏值 第一个时间步需要
def inithidden(self):
return torch.zeros(self.num_layers, 1, self.hidden_size)
def test_RNN():
# todo 核心思想加载数据 置为需要的张量形状 调用模型 查看模型输出结果
# 1.得到数据
my_dataloader = get_dataloader()
# 2.实例化模型
input_size = n_letter # 57
hidden_size = 128 # 自定设定RNN模型输出结果维度
output_size = len(categorys) # 18
my_rnn = MyRNN(input_size, hidden_size, output_size)
h0 = my_rnn.inithidden()
for i, (x, y) in enumerate(my_dataloader):
print(f’x—>{x.shape}‘)
output, hn = my_rnn(input=x, hidden=h0)
print(f’output模型输出结果–>{output.shape}’)
print(f’hn–>{hn.shape}')
break
epochs = 1
my_lr = 1e-3
def train_RNN():
# todo 核心思想: 加载数据集 置换维度形状 调用模型
my_list_x, my_list_y = read_data(filename=‘…/data/name_classfication.txt’)
# 实例化自己定义的Dataset
myDataset = NameClassDataset(mylist_x=my_list_x, mylist_y=my_list_y)
# 实例化自己的Dataloader
my_dataloader = DataLoader(dataset=myDataset, batch_size=1, shuffle=True)
input_size = 57
hidden_size = 128
ouput_size = 18
my_rnn = MyRNN(input_size, hidden_size, ouput_size)
# todo forward定义了损失函数的话这里为什么还需要定义? 因为这是整个训练过程 函数内是单个的不是全局
#todo --------- 损失函数和优化器可以互换置
my_crossentropy=nn.NLLLoss()
my_optimizer=optim.Adam(my_rnn.parameters(),lr=my_lr)
#todo ---------
start_time = time.time() # 开始的时间
total_iter_num = 0 # 已经训练好的样本数
total_loss = 0 # 已经训练的总损失
total_loss_list = [] # 每隔100步存储一下平均损失
total_acc_num = 0 # 已经训练的样本数预测正确的样本
total_acc_list = [] # 每隔100步存储一下平均准确率
#训练
#批次
for epoch_idx in range(epochs):
# 批次样本数量
for i, (x, y) in enumerate(tqdm(my_dataloader)):
# todo 这里为什么要再次初始化???模型内部已经实现了
# todo 答: 在 PyTorch 中,每个批次数据的计算图(Computational Graph)会被动态构建。
# 在每次向后传播(backward)之前,计算图会被清空以释放显存,并且隐藏状态等变量也会被重置
h0=my_rnn.inithidden()
output,hn=my_rnn(input=x,hidden=h0)
# todo 注意: 以下顺序不能变 否则精度不准确
# 预测值和真实值做计算
my_loss = my_crossentropy(output, y)
# 优化器梯度清零
my_optimizer.zero_grad()
# 损失函数反向传播
my_loss.backward()
# 优化器更新参数
my_optimizer.step()
total_iter_num += 1 # 计数
total_loss += my_loss.item() # 累计损失值
# todo 模型预测的类别与真实值对比 对为1
item1 = 1 if torch.argmax(output, dim=-1).item() == y.item() else 0
total_acc_num += item1 # 累计正确样本的个数
# 每隔100步存储一下平均损失和准确率
if total_iter_num % 100 == 0:
# 保存平均损失
loss_avg = total_loss / total_iter_num
total_loss_list.append(loss_avg)
# 保存平均准确率
acc_avg = total_acc_num / total_iter_num
total_acc_list.append(acc_avg)
if total_iter_num % 2000 == 0:
loss_avg = total_loss / total_iter_num
acc_avg = total_acc_num / total_iter_num
end_time = time.time()
use_time = end_time-start_time
print("当前训练的批次: %d,平均损失: %.5f, 训练时间: %.3f, 准确率: %.2f"%(epoch_idx+1,
loss_avg,
use_time,
acc_avg))
# todo 模型保存作用: 复用和部署
#todo 模型结构: 输入层 隐藏层 输出层
# todo 模型参数: 权重偏执
#todo my_rnn.state_dict() 只保存模型参数 不保存模型结构
# 加载方法:
# model = MyRNN(input_size=10, hidden_size=128, output_size=5)
# model.load_state_dict(torch.load('./my_rnn_state_dict.pth'))
#todo torch.save() 保存结构和参数
# todo model = torch.load('../model/my_rnn_model.pth')
torch.save(my_rnn.state_dict(),'../model/my_rnn.bin')
all_time = time.time() - start_time
return total_loss_list, all_time, total_acc_list
def save_rnn_results():
# 1. 训练模型得到需要的结果
total_loss_list, all_time, total_acc_list = train_RNN() # 假设train_my_rnn是另一个函数,用于训练RNN模型并返回损失列表、总时间消耗和准确率列表
# 2. 定义一个字典
dict1 = {"loss": total_loss_list, # 字典中包含损失列表
"time": all_time, # 字典中包含总时间消耗
"acc": total_acc_list} # 字典中包含准确率列表
# 3. 保存到一个json格式的文件
with open("../rnn_result.json", "w") as fw: # 打开一个名为"rnn_result.json"的文件,用于写入
fw.write(json.dumps(dict1)) # 使用json.dumps将字典dict1转换为JSON格式的字符串,并写入文件
def read_json():
with open(“…/rnn_result.json”, “r”) as fr: # 打开一个名为"rnn_result.json"的文件,用于读取
results = json.load(fr) # 使用json.load将JSON格式的字符串转换回字典
return results # 返回读取到的字典
if name == ‘main’:
test_RNN()
train_RNN()
save_rnn_results()
read_json()
模型预测:
import json
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import string
import time
import matplotlib.pyplot as plt
from io import open
from tqdm import tqdm
from nlp_self.day03.rnn.example_rnn_train import read_json, MyRNN
import os
os.environ[‘KMP_DUPLICATE_LIB_OK’]=‘True’
all_letters = string.ascii_letters + " .,;'"
n_letter = len(all_letters)
print(f’all_letters长度–>{len(all_letters)}‘)
print(’*'*80)
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)
def plt_RNN():
#加载模型参数
rnn_results=read_json()
# todo 根据key获取
total_loss_list_rnn, all_time_rnn, total_acc_list_rnn =rnn_results[‘loss’],rnn_results[‘time’],rnn_results[‘acc’]
# 损失对比
plt.figure(0)
plt.plot(total_loss_list_rnn,label=‘RNN’)
plt.legend(loc=“upper left”)
# todo 保存图片
plt.savefig(‘…/pictures/loss.png’)
plt.show()
def line2tensor(x):
#todo 核心思想: 模型不能识别文字需要转码 常用词嵌入 onehot
# todo 选型: 字符就用one-hot 单词就用词嵌入
tensor_x=torch.zeros(len(x),n_letter)
for li,letter in enumerate(x):
# todo 找到的张量置为1 实现one-hot编码
tensor_x[li][all_letters.find(letter)]=1
return tensor_x
def rnn_predict(x):
# todo 模型预测和训练区别: 数据不一致:训练要标签数据 预测不需要 训练要反向传播 预测不需要
# todo 方法: 单步 多步 递归预测(模型使用已有的预测结果作为下一个时间步的输入,依次递归预测整个序列)
# todo : 核心思想: 传数据 调用模型
# todo 调用模型属于推理过程 要使用上下文管理器 (它可以临时地关闭 PyTorch 中的梯度计算。在这个上下文中的所有操作,都不会被记录在计算图中,也不会影响梯度的计算)
# todo 开始预测过程: 1.上下文管理器 2参数初始化(因为每次调用pytorch会清空隐藏状态) 3. 取出预测结果
tensor_x=line2tensor(x)
my_rnn = MyRNN(input_size=57, hidden_size=128, ouput_size=18)
my_rnn.load_state_dict(torch.load(‘…/model/my_rnn.bin’))
# # todo 上下文管理器 关闭梯度计算 推理过程关闭
with torch.no_grad():
# todo 升维 保持现状一致
input0=tensor_x.unsqueeze(0)
h0=my_rnn.inithidden()
output, hn = my_rnn(input0, h0)
# todo 取出预测最大值
topv,topi=output.topk(3,1,True)
for i in range(3):
value = topv[0][i]
index = topi[0][i]
category = categorys[index]
print('RNN模型预测的结果:%.2f, 国家类别是%s'% (value, category))
if name == ‘main’:
#plt_RNN()
rnn_predict(‘Wang’)