PyTorch实现字符生成

Pytorch学习笔记

PyTorch是一个基于科学计算的python包,目标于两类受众:

  1. 在GPU上取代Numpy
  2. 一个提供最大的灵活性和加速的深度学习平台

字符生成任务目标:训练一些字符串,然后给定一个字符,输出一串字符串。

输入

字符生成和文本生成是一个问题,这里输入序列和目标序列是错位的
输入原始序列为一个字符串,需要将这个字符串映射为一个one-hot矩阵,构建一个字符词表n_letters,如下所示:

1
2
3
4
5
6
7
def inputTensor(line):
#第一维是line的长度,第二维度是1,第3维度是字符词表大小
tensor = torch.zeros(len(line), 1, n_letters)
for li in range(len(line)):
letter = line[li]
tensor[li][0][all_letters.find(letter)] = 1#找到的字符返回下标
return tensor

先是构建一个零矩阵,然后进行添加。
目标序列是错位的,最后一个字符用EOS添加,如下:

1
2
3
4
def targetTensor(line):
letter_indexes = [all_letters.find(line[li]) for li in range(1, len(line))]
letter_indexes.append(n_letters - 1) # EOS
return torch.LongTensor(letter_indexes)#这个可以看成一个转化,直接将python中的形式转为torch中的形式

可以看到输出序列并不是一个one-hot序列

模型构建

这里自己实现RNN如下图:
dl01301
按照上图一步步写,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class RNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
"""
:param input_size: 输入大小,就是词表大小,位one-hot编码
:param hidden_size: 隐藏层大小 128
:param output_size: 输出层大小 softmax对应的大小也是词表大小
"""
super(RNN, self).__init__()
self.hidden_size = hidden_size
#定义
self.i2h = nn.Linear(n_categories + input_size + hidden_size, hidden_size)#w矩阵
self.i2o = nn.Linear(n_categories + input_size + hidden_size, output_size)
self.o2o = nn.Linear(hidden_size + output_size, output_size)
self.dropout = nn.Dropout(0.1)
self.softmax = nn.LogSoftmax(dim=1)#和softmax的区别在于使用了log
def forward(self, category, input, hidden):
#赋值
input_combined = torch.cat((category, input, hidden), 1)
hidden = self.i2h(input_combined)
output = self.i2o(input_combined)
output_combined = torch.cat((hidden, output), 1)
output = self.o2o(output_combined)#这一步和RNN不同额
output = self.dropout(output)
output = self.softmax(output)
return output, hidden
#初始化
def initHidden(self):
return torch.zeros(1, self.hidden_size)

nn.Linear表示的是一个线性矩阵变换,输入的两个参数代表矩阵W的维度,也就是进行y=Wx+b
使用的时候如forward函数,直接将输入作为参数[这是pytorch和一般的python的区别,第一步初始化参数,第二步传入执行函数参数,RNN也是一样,第一次传参进入到init中,第二次传参进入到forward中]
nn.LogSoftmax表示对softmax进行log,参数dim表示的是对哪一维度进行计算,我这里举个例子:
如下代码:

1
2
3
4
5
m = nn.Softmax(dim=1)
input = torch.randn(2, 3)
print(input)
output = m(input)
print(output)

input和output分别如下:

1
2
3
4
tensor([[ 0.4836, -0.8574, -0.2286],
[-0.2327, -0.3745, 0.0708]])
tensor([[ 0.5707, 0.1493, 0.2800],
[ 0.3103, 0.2693, 0.4204]])

对第1维进行归一的结果[下标]

模型训练

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def train(input_line_tensor, target_line_tensor):
target_line_tensor.unsqueeze_(-1)#对最后一维再添加一个维度
hidden = rnn.initHidden()
rnn.zero_grad()#设置模型参数的梯度为0
loss = 0
criterion=nn.NLLLoss()#加上logsoftmax后等价于交叉熵
for i in range(input_line_tensor.size(0)):
output, hidden = rnn(input_line_tensor[i], hidden)#第二次传入参数
l = criterion(output, target_line_tensor[i])
loss += l
loss.backward()
for p in rnn.parameters():
p.data.add_(-learning_rate, p.grad.data)
return output, loss.item() / input_line_tensor.size(0)

这里对代码中的两点进行解释:
第一点是交叉熵函数 nn.NLLoss
表示负对数似然损失,用于训练C个类别的分类问题,可选参数weight是一个1维的Tensor,用来设置每个类别的权重,当训练集不平衡时该参数十分有用。
输入必须形如(minibatch,C)的2维Tensor,目标值是一个类别list。
需要在神经网络的最后一层添加LogSoftmax来得到对数概率,如果不希望在神经网络中添加额外一层,可以使用CrossEntropyLoss来代替,实质就是交叉熵。
具体计算如下:

1
2
loss(x, class) = -x[class]
loss(x, class) = -weight[class] * x[class]

也就是对应的值求和取平均,我这里举个例子:

1
2
3
4
5
6
7
8
m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.tensor([1, 0, 4])
mm=m(input)
output = loss(m(input), target)
print(output)
print(-(mm[0][1]+mm[1][0]+mm[2][4])/3)

第二点是input_line_tensor, target_line_tensor的维度:
input_line_tensor的维度是[len(line),1,nletters],target_line_tensor的维度是[len(line)],然后经过`unsequeeze`进行扩维,变为[len(line),1],这样每次传入到rnn中的就是[1,n_letters]和[1,1]

迭代

代码如下:

1
2
3
4
5
6
7
8
9
10
rnn = RNN(n_letters, 128, n_letters)#第一次传入参数
start = time.time()
for iter in range(1, n_iters + 1):
output, loss = train(*randomTrainingExample())#这里每次只是训练一组
total_loss += loss
if iter % print_every == 0:
print('%s (%d %d%%) %.4f' % (timeSince(start), iter, iter / n_iters * 100, loss))
if iter % plot_every == 0:
all_losses.append(total_loss / plot_every)
total_loss = 0

如果觉得有帮助,给我打赏吧!