概述
之前关于Pytorch的学习一直以CNN为主,今天学习的是一个RNN模型——Character-Level-RNN,即为字符级别的RNN。
char-RNN原理
model
Ng的RNN课程中提到过,当RNN应用在语言模型上的时候,用字典表示单词的方法有两种,一种是单词级的描述,即在字典中存放的都是单词,因此在表示单词的时候直接利用单词在字典中的位置生成独热码即可;另外一种即为字符级的表示,意思是在字典中只存放最基本的单词,当表示单词的时,需要将单词拆成字母来表示。(当然也有非字典的表示方法,即用特征表示的“词嵌入”方法)课中所说当预测的是句子的单词的时候,用单词级的描述更方便。但是在本处应用的时候,输入为一个名字,即为一个单词,如果采用单词级的描述则无法体现RNN的特征,所以采用的是使用字符级的字典描述。
对于char-RNN,karpathy的博客中有所提及。这篇博客中char-RNN的举例是根据不完整的单词来预测完成的单词,比如给出”hell”,其期望RNN可以预测出下一个字母为”o”,这样方式的实现结构如图(摘自其博客):
本文中char-RNN的目的是为了对名字进行分类,所以我们对于仅需要一个118输出即可,即为一个one-to-many的模型。基本的传递过程与上同:
其网络模型是先利一个字符母与hidden-layer层生成output与新的hidden-layer,然后将新的hidden-layer传入下一个train中,与下一个字符一起训练出新的hidden-layer,直到最后一个字符的训练完成的时候,输出训练得到的output作为RNN的输出,得到118的矩阵,以此作为分类的标准。传递过程中各层计算关系如图所示:
训练
训练的过程中,先对于hidden-layer进行一次的初始化,然后每次循环的步骤如下:
- 建立新的tensors类型的input和target
- 初始化隐层参数
- 逐次输入单词中的每个字母并前向计算
- 计算得到网络的输出
- 反向传播并更新参数
- 得到output和loss
以”Yan”作为输入进行举例,网络训练过程如下:
加深理解
为了更好的理解char-RNN的构造,我们将已用代码实现的model输出如下:1
2
3
4
5RNN (
(i2h): Linear (185 -> 128)
(i2o): Linear (185 -> 18)
(softmax): LogSoftmax ()
)
因为input的大小为157,hidden的大小为1128,由图2所示,”i2h”和”i20”输入均由input和hidden组成,所以输出为$57+128=185$,输出output即为1*18的判别向量。
再观察下每个单词的训练过程1
2
3
4
5
6
7
8rnn.zero_grad()
for i in range(line_tensor.size()[0]):
output, hidden = rnn(line_tensor[i], hidden)
loss = criterion(output, category_tensor)
loss.backward()
# Add parameters' gradients to their values, multiplied by learning rate
for p in rnn.parameters():
p.data.add_(-learning_rate, p.grad.data)
可以发现训练过程为先把单词分为字符,再将每个字符逐此训,并向下一个字符传递训练好的hidden,只到最后一个字符输出output为止。
实现
char-RNN代码实现参考Pytorch的官方tutorials。
源代码
此处只展示RNN的Model定义代码以及训练代码。
Model代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# create the Network
class RNN(nn.Module):
def __init__(self,input_size,hidden_size,output_size):
super(RNN,self).__init__()
self.hidden_size = hidden_size
self.i2h = nn.Linear(input_size+hidden_size,hidden_size)
self.i2o = nn.Linear(input_size+hidden_size,output_size)
self.softmax = nn.LogSoftmax()
def forward(self,input,hidden):
combined = torch.cat((input,hidden),1)
hidden = self.i2h(combined)
output = self.i2o(combined)
output = self.softmax(output)
return output,hidden
def initHidden(self):
return Variable(torch.zeros(1,self.hidden_size))
train函数定义如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# train the Network
criterion = nn.NLLLoss()
learning_rate = 0.005
def train(category_tensor, line_tensor):
hidden = rnn.initHidden()
rnn.zero_grad()
for i in range(line_tensor.size()[0]):
output, hidden = rnn(line_tensor[i], hidden)
loss = criterion(output, category_tensor)
loss.backward()
# Add parameters' gradients to their values, multiplied by learning rate
for p in rnn.parameters():
p.data.add_(-learning_rate, p.grad.data)
return output, loss.data[0]
实际训练过程如下,其中“ print_every”为每次输出结果的间隔数,“ plot_every”为每次loss图上点的打印间隔:1
2
3
4
5
6
7
8
9
10
11
12
13for iter in range(1,n_iters+1):
category,line,category_tensor,line_tensor = randomTrainExample()
output,loss = train(category_tensor,line_tensor)
current_loss += loss
if iter % print_every == 0:
guess,guess_i = categoryFromOutput(output)
correct = 'Right' if guess == category else 'Wrong(%s)' % category
print('%d %d%% (%s) %.4f %s / %s %s' % (iter, iter / n_iters * 100, timeSince(start), loss, line, guess, correct))
if iter % plot_every == 0:
all_losses.append(current_loss / plot_every)
current_loss = 0
测试输出
部分输出结果如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1990500 90% (2m 57s) 1.9990 Rosales / Greek Wrong(Spanish)
91000 91% (2m 58s) 0.1811 Millar / Scottish Right
91500 91% (2m 59s) 6.5660 Zaloumi / Italian Wrong(Greek)
92000 92% (3m 0s) 2.9399 Skinner / German Wrong(English)
92500 92% (3m 1s) 0.6929 Doan / Vietnamese Right
93000 93% (3m 1s) 0.9913 Kanaan / Arabic Right
93500 93% (3m 2s) 0.1651 Nahas / Arabic Right
94000 94% (3m 3s) 2.1802 Handal / Irish Wrong(Arabic)
94500 94% (3m 4s) 1.4668 Zabala / Spanish Right
95000 95% (3m 5s) 1.6680 Chen / Korean Wrong(Chinese)
95500 95% (3m 6s) 1.0585 Niadh / Irish Right
96000 96% (3m 7s) 1.1397 Flann / Irish Right
96500 96% (3m 7s) 0.5109 Gorecki / Polish Right
97000 97% (3m 8s) 1.2091 Verney / English Right
97500 97% (3m 9s) 0.4440 Tomika / Japanese Right
98000 98% (3m 10s) 2.0474 Murchadh / German Wrong(Irish)
98500 98% (3m 11s) 0.4104 Whyte / Scottish Right
99000 99% (3m 12s) 1.4593 Segers / Portuguese Wrong(Dutch)
99500 99% (3m 13s) 3.6759 Paulis / Greek Wrong(Dutch)
训练中的损失值记录如图:
每种语言名称的评价准确度如图:
小结
本次试验在写代码的时候由于不仔细,在model中将一个“output”错写为“ouput”,所依错生成了一个没有使用的变量,导致输出错误,调试了很长时间才找到错误所在,之后在写代码的时候应该吸取这次的教训。
参考
[1] Pytorch官方文档
[2] karpathy的博客
[3] SicongYan的博客