#引入torch
import torch
#引入time计时
import time
#引入math数学函数
import math
#引入numpy
import numpy as np
#引入plt
import matplotlib.pyplot as plt
#从torch的工具的数据引入数据集,数据加载器
from torch.utils.data import Dataset,DataLoader
#从torch的神经网络的数据的rnn中引入包装填充好的序列。作用是将填充的pad去掉,然后根据序列的长短进行排序
from torch.nn.utils.rnn import pack_padded_sequence
#引入gzip 压缩文件
import gzip
#引入csv模块
import csv
#隐层数是100
HIDDEN_SIZE = 100
#batch的大小时256
BATCH_SIZE = 256
#应用2层的GRU
N_LAYER = 2
#循环100
N_EPOCHS = 100
#字符数量时128
N_CHARS = 128
#不使用GPU
USE_GPU =False
#定义名字数据集的类,继承自数据集
class NameDataset(Dataset):
#自身初始化,是训练集为真
def __init__(self,is_train_set=True):
#文件名是训练集。如果训练为真,否则是测试集
filename = 'names_train.csv.gz' if is_train_set else 'names_test.csv.gz'
#用gzip打开文件名,操作text文本的时候使用'rt',作为f
with gzip.open(filename,'rt') as f:
#阅读器是用csv的阅读器阅读文件
reader = csv.reader(f)
#将文件设成一个列表
rows = list(reader)
#自身名字是文件的第一列都是名字,提取第一列,对于r在rs中时
self.names = [row[0] for row in rows]
#长度是名字的长度
self.len = len(self.names)
#国家是第二列
self.countries = [row[1] for row in rows]
#将国家变成集合,去除重复的元素,然后进行排序,然后接着再变回列表
self.country_list = list(sorted(set(self.countries)))
#得到国家的词典,将列表转化成词典(有索引)
self.country_dict = self.getCountryDict()
#长度是国家的长度
self.country_num = len(self.country_list)
#定义 获得项目类,提供索引访问,自身,索引
def __getitem__(self,index):
#返回 带索引的名字,带索引的国家,代入,得到带国家的词典
return self.names[index], self.country_dict[self.countries[index]]
#定义长度
def __len__(self):
#返回长度
return self.len
#定义获得国家词典
def getCountryDict(self):
#现设一个空字典
country_dict = dict()
#idx表示进行多少次的迭代,country_name是国家名,用列举的方法将国家列表的数据提取出来,从0开始
for idx,country_name in enumerate(self.country_list,0):
#构造键值对,将国家名代入国家列表中等于1,2,3.
country_dict[country_name] = idx
#返回国家列表
return country_dict
#定义 索引返回国家字符串,自身索引
def idx2country(self,index):
#返回 自身,将索引代入国家列表得到字符串
return self.country_list[index]
#获得国家数量
def getCountriesNum(self):
#返回自身国家数量
return self.country_num
#将训练集为真,代入名字数据集模型中得到训练集
trainset = NameDataset(is_train_set=True)
#将训练集,batch的大小等于batch的大小,shuffle为真将数据打乱。代入到数据加载器中。得到训练加载器
trainloader = DataLoader(trainset,batch_size=BATCH_SIZE,shuffle=True)
#将训练集为假,代入名字数据集模型中得到测试集
testset = NameDataset(is_train_set=False)
#将测试集,batch的大小等于batch的大小,shuffle为假不把数据打乱。代入到数据加载器中。得到测试加载器
testloader = DataLoader(testset,batch_size=BATCH_SIZE,shuffle=False)
#训练集的获得国家数量得到国家数量
N_COUNTRY = trainset.getCountriesNum()
#创建tensor
def create_tensor(tensor):
#如果使用GPU
if USE_GPU:
#使用第一个GPU代入到设置,得到设置
device = torch.device("cuda:0")
#让张量在设置里面跑
tensor = tensor.to(device)
#返回张量
return tensor
#将RNN分类器分成一个类,继承自Module模块
class RNNClassifier(torch.nn.Module):
#定义自身初始化,输入的大小,隐层的大小,输出的大小,层数是1,bidirectional为真设成双向的。
def __init__(self,input_size,hidden_size,output_size,n_layers=1,bidirectional=True):
#父类初始化
super(RNNClassifier,self).__init__()
#自身隐层等于隐层
self.hidden_size = hidden_size
#自身层数等于层数
self.n_layers = n_layers
#自身方向数量是如果bidirectional为真则是2,否则是1
self.n_directions = 2 if bidirectional else 1
#将输入的大小和隐层的大小代入嵌入层得到自身嵌入层
self.embedding = torch.nn.Embedding(input_size,hidden_size)
#隐层的大小是输入,隐层的大小是输出,层数,双向代入GRU模型中,得到gru
self.gru = torch.nn.GRU(hidden_size,hidden_size,n_layers,bidirectional=bidirectional)
#因为是双向的,所以隐层×双向,输出的大小代入线性模型,得到,激活函数。
self.fc = torch.nn.Linear(hidden_size * self.n_directions,output_size)
#初始化h0,自身batch的大小
def _init_hidden(self,batch_size):
#将层数×方向数,batch的大小,隐层的大小归零,得到h0
hidden = torch.zeros(self.n_layers * self.n_directions,batch_size,self.hidden_size)
#返回 创建张量的隐层
return create_tensor(hidden)
#定义前馈计算,自身,输入,序列的长度
def forward(self,input,seq_lengths):
#将输入进行转置,B*S--S*B
input = input.t()
#输入的第二列是batch的大小
batch_size = input.size(1)
#将batch的大小代入到自身初始隐层中,得到隐层的大小
hidden = self._init_hidden(batch_size)
#将输入的大小代入到自身嵌入层得到嵌入层
embedding = self.embedding(input)
#将嵌入层和序列的长度代入pack_padded_sequence中,先将嵌入层多余的零去掉,然后排序,打包出来,得到GRU的输入。
gru_input = pack_padded_sequence(embedding,seq_lengths)
#将输入和隐层代入gru,得到输出和隐层
output,hidden = self.gru(gru_input,hidden)
#如果是双向的
if self.n_directions == 2:
#将隐层的最后一个和隐层的最后第二个拼接起来,按照维度为1的方向拼接起来。得到隐层
hidden_cat = torch.cat([hidden[-1],hidden[-2]],dim=1)
#否则
else:
#隐层就只有最后一个
hidden_cat = hidden[-1]
#将隐层代入激活函数得到输出
fc_output = self.fc(hidden_cat)
#返回输出
return fc_output
#定义名字到列表
def name2list(name):
#对于c在名字里,将c转变为ASC11值
arr = [ord(c) for c in name]
#返回arr和长度
return arr, len(arr)
#定义制作张量 名字 国家
def make_tensors(names, countries):
#将名字代入到模型中得到ASC11值,对于名字在名字中,得到序列和长度
sequences_and_lengths = [name2list(name) for name in names]
#将第一列取出来得到名字序列
name_sequences = [sl[0] for sl in sequences_and_lengths]
#将第二列转换成长tensor得到序列的长度
seq_lengths = torch.LongTensor([sl[1] for sl in sequences_and_lengths])
#将国家变为长整型数据
countries = countries.long()
#将名字序列的长度,序列长度的最大值的长整型归零。得到序列的张量
seq_tensor = torch.zeros(len(name_sequences), seq_lengths.max()).long()
#对于索引,序列和序列长度 在名字序列和名字长度中遍历,从零开始
for idx, (seq, seq_len) in enumerate(zip(name_sequences, seq_lengths), 0):
#将序列变成长张量,等于序列张量,idx是索引,第1,2,3.。。。,
#:seq_len是按照从小到大排序的序列长度,这样就将序列复制到空序列中了。
seq_tensor[idx, :seq_len] = torch.LongTensor(seq)
#将序列长度按照维度为0,进行排序,下降是真,得到序列长度和索引
seq_lengths, perm_idx = seq_lengths.sort(dim=0, descending=True)
#将索引赋值给序列张量
seq_tensor = seq_tensor[perm_idx]
#将索引赋值给国家
countries = countries[perm_idx]
#返回序列张量,序列长度,国家。创建tensor
return create_tensor(seq_tensor),\
create_tensor(seq_lengths),\
create_tensor(countries)
#定义time_since模块
def time_since(since):
#现在的时间减去开始的时间的到时间差
s = time.time() - since
#Math.floor() 返回小于或等于一个给定数字的最大整数。计算分钟数
m = math.floor(s / 60)
#减去分钟数乘以60就是剩下的秒数
s -= m * 60
#返回分秒
return '%dm %ds' % (m, s)
#定义训练模型
def trainModel():
#损失设为0
total_loss = 0
#对于i,名字和国家在训练加载器中遍历,从1开始
for i , (names,countries)in enumerate(trainloader,1):
#将名字和国家代入到make_tensors模型中得到输入,序列长度,目标
inputs,seq_lengths,target = make_tensors(names,countries)
#将输入和序列长度代入到分类器中得到输出
output = classifier(inputs,seq_lengths)
#将输出和目标代入到损失标准器中得到损失
loss = criterion(output,target)
#梯度归零
optimizer.zero_grad()
#反向传播
loss.backward()
#更新
optimizer.step()
#损失标量相加得到总的损失
total_loss +=loss.item()
#如果i能被10整除
if i % 10 == 0:
#以f开头表示在字符串内支持大括号内的python 表达式。将开始的时间代入time_since中得到分秒,循环次数,end是不换行加空格
print(f'[{time_since(start)}]) Epoch {epoch}',end='')
#f,i×输入的长度除以训练集的长度
print(f'[{i * len(inputs)}/{len(trainset)}]',end='')
#总损失除以i×输入的长度,得到损失
print(f'loss={total_loss / (i * len(inputs))}')
#返回总损失
return total_loss
#定义测试模型
def testModel():
#初始正确的为0
correct = 0
#总长是测试集的长度
total = len(testset)
#打印,,,
print("evaluating trained model ...")
#不用梯度
with torch.no_grad():
#对于i,名字和国家在测试加载器中遍历,从1开始
for i,(name,countries)in enumerate(testloader,1):
#将名字和国家代入到make_tensors模型中得到输入,序列长度,目标
inputs,seq_lengths,target = make_tensors(name,countries)
#将输入和序列长度代入到分类器中得到输出
output = classifier(inputs,seq_lengths)
#按照维度为1的方向,保持输出的维度为真,取输出的最大值的第二个结果,得到预测值
pred = output.max(dim=1,keepdim=True)[1]
#view_as将target的张量变成和pred同样形状的张量,eq是等于,预测和目标相等。标量求和
correct += pred.eq(target.view_as(pred)).sum().item()
#100×正确除以错误,小数点后保留两位,得到百分比
percent = '%.2f' % (100 * correct / total)
#测试集正确率
print(f'Test set: Accuracy {correct}/{total} {percent}%')
#返回正确除以总数
return correct / total
#封装到if语句里面
if __name__ == '__main__':
#实例化分类器,字符的长度,隐层的大小,国家的数量,层数
classifier = RNNClassifier(N_CHARS,HIDDEN_SIZE,N_COUNTRY,N_LAYER)
#如果使用GPU
if USE_GPU:
#设置使用第一个GPU
device = torch.device("coda:0")
#让分类器进到设置里面跑
classifier.to(device)
#标准器是交叉熵损失
criterion = torch.nn.CrossEntropyLoss()
#优化器是Adam。分类器的大部分参数,学习率是0.001
optimizer = torch.optim.Adam(classifier.parameters(),lr=0.001)
#开始时时间的时间
start = time.time()
#打印循环次数
print("Training for %d epochs..."% N_EPOCHS)
#空列表
acc_list = []
#对于循环在1到循环次数中。
for epoch in range(1,N_EPOCHS + 1):
#训练模型
trainModel()
#测试模型
acc = testModel()
#将测试结果加到列表中
acc_list.append(acc)
#循环,起始是1,列表长度+1是终点。步长是1
epoch = np.arange(1, len(acc_list) + 1, 1)
#将数据变成一个矩阵
acc_list = np.array(acc_list)
#循环,列表
plt.plot(epoch, acc_list)
#x标签
plt.xlabel('Epoch')
#y标签
plt.ylabel('Accuracy')
#绿色
plt.grid()
#展示
plt.show()