RNN人名分类器案例

RNN人名分类器案例 RNN人名分类器案例1 任务目的目的: 给定一个人名来判定这个人名属于哪个国家 典型的文本分类任务: 18分类---多分类任务2 数据格式注意两列数据第一列是人名第二列是国家类别中间用制表符号\t隔开Ang Chinese AuYong Chinese Yuasa Japanese Yuhara Japanese Yunokawa Japanese3 任务实现流程1. 获取数据:案例中是直接给定的 2. 数据预处理: 脏数据清洗、数据格式转换、数据源Dataset的构造、数据迭代器Dataloader的构造 3. 模型搭建: RNN、LSTM、GRU一系列模型 4. 模型训练和评估测试 5. 模型上线---API接口4 数据预处理4.1读取txt文档数据目的将文档里面的数据读取到内存中实际上我们做了一个操作: 将人名存放到一个列表中国家类别存放到一个列表中代码实现defread_data(filename):# 1. 初始化两个空列表my_list_x,my_list_y[],[]# 2. 读取文件内容withopen(filename,r,encodingutf-8)asfr:forlineinfr.readlines():iflen(line)5:continue# 过滤掉脏数据# strip()方法默认将字符串首尾两端的空白去掉x,yline.strip().split(\t)my_list_x.append(x)my_list_y.append(y)returnmy_list_x,my_list_y4.2 构建自己的数据源DataSet目的使用Pytorch框架一般遵从一个规矩使用DataSet方法构造数据源来让模型进行使用 构造数据源的过程中:必须继承torch.utils.data.Dataset类必须构造两个魔法方法__len__(), __getitem__() __len__(): 一般返回的是样本的总个数我们可以直接len(dataset对象)直接就可以获得结果 __getitem__(): 可以根据某个索引取出样本值我们可以直接用dataset对象[index]来直接获得结果代码实现classNameClassDataset(Dataset):def__init__(self,mylist_x,mylist_y):self.mylist_xmylist_x self.mylist_ymylist_y self.sample_lenlen(mylist_x)# 定义魔法方法lendef__len__(self):returnself.sample_len# 定义魔法方法getitemdef__getitem__(self,index):# 1.index异常值处理indexmin(max(index,0),self.sample_len-1)# 2. 根据index取出人名和国家名xself.mylist_x[index]# print(fx---{x})yself.mylist_y[index]# print(fy---{y})# 3.需要对人名进行one-hot编码表示这里的思路是针对每个人名组成的单词进行one-hot然后再拼接tensor_xtorch.zeros(len(x),n_letter)# print(ftensor_x--{tensor_x})forli,letterinenumerate(x):tensor_x[li][all_letters.find(letter)]1# 4.获取标签# print(fdataset内部的tensor_x--》{tensor_x.shape})tensor_ytorch.tensor(categorys.index(y),dtypetorch.long)# print(fdataset内部的tensor_y--{tensor_y})returntensor_x,tensor_y4.3 构建数据源Dataloader目的为了将Dataset我们上一步构建的数据源进行再次封装变成一个迭代器可以进行for循环而且可以自动为我们dataset里面的数据进行增维bath_size,也可以随机打乱我们的取值顺序代码实现filename./data/name_classfication.txtmy_list_x,my_list_yread_data(filename)mydatasetNameClassDataset(mylist_xmy_list_x,mylist_ymy_list_y)my_dataloaderDataLoader(datasetmydataset,batch_size1,shuffleTrue)5 模型搭建5.1 搭建RNN模型注意事项RNN模型在实例化的时候默认batch_firstFalse因此需要小心输入数据的形状 因为: dataloader返回的结果x---》shape--〉[batch_size, seq_len, input_size], 我们的代码是batch_firstTrue这样做的目的可以直接承接x的输入。代码实现classMyRNN(nn.Module):def__init__(self,input_size,hidden_size,ouput_size,num_layers1):super().__init__()# input_size 代表词嵌入维度self.input_sizeinput_size# hidden_size代表RNN隐藏层维度self.hidden_sizehidden_size# output_size代表国家种类个数self.ouput_sizeouput_size self.num_layersnum_layers# 定义RNN网络层# 设定了batch_firstTrue,意味着rnn接受的input第一个参数是batch_sizeself.rnnnn.RNN(self.input_size,self.hidden_size,num_layersself.num_layers,batch_firstTrue)# 定义输出网络层self.linearnn.Linear(self.hidden_size,self.ouput_size)# 定义softmax层self.softmaxnn.LogSoftmax(dim-1)defforward(self,input,hidden):# input的shape---》[batch_size, seq_len, input_size] [1, 9, 57]# hidden的shape---》[num_layers, batch_size, hidden_size] [1,1,128]# 将input和hidden送入RNN模型得到结果rnn_output【1,9,128】,rnn_hn[1,1,128]rnn_output,rnn_hnself.rnn(input,hidden)# print(frnn_output--》{rnn_output.shape})# temp:[1, 128]tmeprnn_output[0][-1].unsqueeze(0)# print(ftmep--》{tmep.shape})# 将临时tmep代表当前样本最后一词的隐藏层输出结果[1, 18]outputself.linear(tmep)# print(foutput--》{output.shape})# 经过softmaxreturnself.softmax(output),rnn_hndefinithidden(self):returntorch.zeros(self.num_layers,1,self.hidden_size)RNN模型测试deftest_RNN():# 1.得到数据my_dataloaderget_dataloader()# 2.实例化模型input_sizen_letter# 57hidden_size128# 自定设定RNN模型输出结果维度output_sizelen(categorys)# 18my_rnnMyRNN(input_size,hidden_size,output_size)h0my_rnn.inithidden()# 3.将数据送入模型fori,(x,y)inenumerate(my_dataloader):print(fx---{x.shape})output,hnmy_rnn(inputx,hiddenh0)print(foutput模型输出结果--{output.shape})print(fhn--{hn.shape})break5.2 搭建LSTM模型注意事项LSTM模型在实例化的时候默认batch_firstFalse因此需要小心输入数据的形状 因为: dataloader返回的结果x---》shape--〉[batch_size, seq_len, input_size], 我们的代码是batch_firstTrue这样做的目的可以直接承接x的输入。代码实现classMyLSTM(nn.Module):def__init__(self,input_size,hidden_size,ouput_size,num_layers1):super().__init__()# input_size 代表词嵌入维度self.input_sizeinput_size# hidden_size代表RNN隐藏层维度self.hidden_sizehidden_size# output_size代表国家种类个数self.ouput_sizeouput_size self.num_layersnum_layers# 定义LSTM网络层# 设定了batch_firstTrue,意味着rnn接受的input第一个参数是batch_sizeself.lstmnn.LSTM(self.input_size,self.hidden_size,num_layersself.num_layers,batch_firstTrue)# 定义输出网络层self.linearnn.Linear(self.hidden_size,self.ouput_size)# 定义softmax层self.softmaxnn.LogSoftmax(dim-1)defforward(self,input,hidden,c0):# input的shape---》[batch_size, seq_len, input_size] [1, 9, 57]# hidden的shape---》[num_layers, batch_size, hidden_size] [1,1,128]# 将input和hidden送入RNN模型得到结果rnn_output【1,9,128】,rnn_hn[1,1,128]lstm_output,(lstm_hn,lstm_cn)self.lstm(input,(hidden,c0))# print(frnn_output--》{rnn_output.shape})# temp:[1, 128]tmeplstm_output[0][-1].unsqueeze(0)# print(ftmep--》{tmep.shape})# 将临时tmep代表当前样本最后一词的隐藏层输出结果[1, 18]outputself.linear(tmep)# print(foutput--》{output.shape})# 经过softmaxreturnself.softmax(output),lstm_hn,lstm_cndefinithidden(self):h0torch.zeros(self.num_layers,1,self.hidden_size)c0torch.zeros(self.num_layers,1,self.hidden_size)returnh0,c0LSTM测试deftest_LSTM():# 1.得到数据my_dataloaderget_dataloader()# 2.实例化模型input_sizen_letter# 57hidden_size128# 自定设定LSTM模型输出结果维度output_sizelen(categorys)# 18my_lstmMyLSTM(input_size,hidden_size,output_size)h0,c0my_lstm.inithidden()# 3.将数据送入模型fori,(x,y)inenumerate(my_dataloader):print(fx---{x.shape})output,hn,cnmy_lstm(inputx,hiddenh0,c0c0)print(foutput模型输出结果--{output.shape})print(fhn--{hn.shape})print(fcn--{cn.shape})break5.3 搭建GRU模型注意事项GRU模型在实例化的时候默认batch_firstFalse因此需要小心输入数据的形状 因为: dataloader返回的结果x---》shape--〉[batch_size, seq_len, input_size], 所以课堂上代码和讲义稍微有点不同我们的代码是batch_firstTrue这样做的目的可以直接承接x的输入。代码实现classMyGRU(nn.Module):def__init__(self,input_size,hidden_size,ouput_size,num_layers1):super().__init__()# input_size 代表词嵌入维度self.input_sizeinput_size# hidden_size代表RNN隐藏层维度self.hidden_sizehidden_size# output_size代表国家种类个数self.ouput_sizeouput_size self.num_layersnum_layers# 定义GRU网络层# 设定了batch_firstTrue,意味着rnn接受的input第一个参数是batch_sizeself.grunn.GRU(self.input_size,self.hidden_size,num_layersself.num_layers,batch_firstTrue)# 定义输出网络层self.linearnn.Linear(self.hidden_size,self.ouput_size)# 定义softmax层self.softmaxnn.LogSoftmax(dim-1)defforward(self,input,hidden):# input的shape---》[batch_size, seq_len, input_size] [1, 9, 57]# hidden的shape---》[num_layers, batch_size, hidden_size] [1,1,128]# 将input和hidden送入RNN模型得到结果rnn_output【1,9,128】,rnn_hn[1,1,128]gru_output,gru_hnself.gru(input,hidden)# print(frnn_output--》{rnn_output.shape})# temp:[1, 128]tmepgru_output[0][-1].unsqueeze(0)# print(ftmep--》{tmep.shape})# 将临时tmep代表当前样本最后一词的隐藏层输出结果[1, 18]outputself.linear(tmep)# print(foutput--》{output.shape})# 经过softmaxreturnself.softmax(output),gru_hndefinithidden(self):returntorch.zeros(self.num_layers,1,self.hidden_size)GRU测试deftest_GRU():# 1.得到数据my_dataloaderget_dataloader()# 2.实例化模型input_sizen_letter# 57hidden_size128# 自定设定RNN模型输出结果维度output_sizelen(categorys)# 18my_gruMyGRU(input_size,hidden_size,output_size)# 2.1 初始化参数h0my_gru.inithidden()# 3.将数据送入模型fori,(x,y)inenumerate(my_dataloader):print(fx---{x.shape})output,hnmy_gru(inputx,hiddenh0)print(foutput模型输出结果--{output.shape})print(fhn--{hn.shape})break6 模型训练基本过程1.获取数据 2.构建数据源Dataset 3.构建数据迭代器Dataloader 4.加载自定义的模型 5.实例化损失函数对象 6.实例化优化器对象 7.定义打印日志参数 8.开始训练 8.1 实现外层大循环epoch (可以在这构建数据迭代器Dataloader) 8.2 内部遍历数据迭代球dataloader 8.3 将数据送入模型得到输出结果 8.4 计算损失 8.5 梯度清零: optimizer.zero_grad() 8.6 反向传播: loss.backward() 8.7 参数更新梯度更新: optimizer.step() 8.8 打印训练日志 9. 保存模型: torch.save(model.state_dict(), model_path)6.1 RNN模型训练代码实现my_lr1e-3epochs1# 训练rnn模型deftrain_rnn():# 读取数据my_list_x,my_list_yread_data(filepath./data/name_classfication.txt)# 实例化dataset数据源对象my_datasetNameClassDataset(my_list_x,my_list_y)# 实例化模型# n_letters57, hidden_size128,类别总数output_size18my_rnnMy_RNN(input_size57,hidden_size128,output_size18)# 实例化损失函数对象my_nll_lossnn.NLLLoss()# 实例化优化器对象my_optimoptim.Adam(my_rnn.parameters(),lrmy_lr)# 定义打印日志的参数start_timetime.time()total_iter_num0# 当前已经训练的样本总数total_loss0# 已经训练的损失值total_loss_list[]# 每隔n个样本保存平均损失值total_acc_num0# 预测正确的样本个数total_acc_list[]# 每隔n个样本保存平均准确率# 开始训练forepoch_idxinrange(epochs):# 实例化dataloadermy_dataloaderDataLoader(datasetmy_dataset,batch_size1,shuffleTrue)# 开始内部迭代数据送入模型fori,(x,y)inenumerate(tqdm(my_dataloader)):# print(fx--》{x.shape})# print(fy--》{y})output,hnmy_rnn(inputx[0],hiddenmy_rnn.inithidden())# print(foutput--》{output}) # [1, 18]# 计算损失my_lossmy_nll_loss(output,y)# print(fmy_loss--》{my_loss})# print(fmy_loss--》{type(my_loss)})# 梯度清零my_optim.zero_grad()# 反向传播my_loss.backward()# 梯度更新my_optim.step()# 统计一下已经训练样本的总个数total_iter_numtotal_iter_num1# 统计一下已经训练样本的总损失total_losstotal_lossmy_loss.item()# 统计已经训练的样本中预测正确的个数i_predict_num1iftorch.argmax(output).item()y.item()else0total_acc_numtotal_acc_numi_predict_num# 每隔100次训练保存一下平均损失和准确率iftotal_iter_num%1000:avg_losstotal_loss/total_iter_num total_loss_list.append(avg_loss)avg_acctotal_acc_num/total_iter_num total_acc_list.append(avg_acc)# 每隔2000次训练打印一下日志iftotal_iter_num%20000:temp_losstotal_loss/total_iter_num temp_acctotal_acc_num/total_iter_num temp_timetime.time()-start_timeprint(轮次:%d, 损失:%.6f, 时间:%d准确率:%.3f%(epoch_idx1,temp_loss,temp_time,temp_acc))torch.save(my_rnn.state_dict(),./save_model/ai20_rnn_%d.bin%(epoch_idx1))# 计算总时间total_timeint(time.time()-start_time)print(训练总耗时,total_time)# 将结果保存到文件中dict1{avg_loss:total_loss_list,all_time:total_time,avg_acc:total_acc_list}withopen(./save_results/ai_rnn.json,w)asfw:fw.write(json.dumps(dict1))returntotal_loss_list,total_time,total_acc_list6.2 LSTM模型训练代码实现基本原理同上6.3 GRU模型训练代码基本原理同上7 模型预测基本过程1.获取数据 2.数据预处理将数据转化one-hot编码 3.实例化模型 4.加载模型训练好的参数: model.load_state_dict(torch.load(model_path)) 5.with torch.no_grad(): 6.将数据送入模型进行预测注意:张量的形状变换RNN模型预测代码defline2tensor(x):# x--baitensor_xtorch.zeros(len(x),n_letters)# one-hot表示forli,letterinenumerate(x):tensor_x[li][letters.find(letter)]1returntensor_x# 构造rnn预测函数defrnn_predict(x):# 将数据x进行张量的转换tensor_xline2tensor(x)# 加载训练好的模型my_rnnMy_RNN(input_size57,hidden_size128,output_size18)my_rnn.load_state_dict(torch.load(./save_model/ai20_rnn_3.bin))# 实现模型的预测withtorch.no_grad():# 将数据送入模型output,hnmy_rnn(tensor_x,my_rnn.inithidden())print(foutput--》{output})# 获取output最大的前3个值# output.topk(3, 1, True)values,indexestorch.topk(output,k3,dim-1,largestTrue)print(fvalues--{values})print(findexes--{indexes})foriinrange(3):valuevalues[0][i]indexindexes[0][i]categorycategorys[index]print(f当前预测的值是{value}, 国家类别是{category})