前言: 之前的博文部分地讲解了RNN的标准结构,也用pytorch的RNNCell类和RNN类实现了前向传播的计算,这里我们再举一个例子,做一个特别简单特别简单特别简单特别简单的翻译器,目标如下: 将英文hello and thank you翻译成汉语拼音ni hao qie duo xie ni
篇幅有限,我们拿这五个数据练手吧。 如下两个例子也是为了让我们学习一下RNN相关维度的概念。
一:使用RNNCell类实现简单的字符串翻译 这里我们需要用到one-hot vector技术,也就是我们建立一个字符表,包含空格字符和26个小写英文字符,也就是一共有27个字符的字典表,空格符号标号是0,az编号是126,我们定义有22个序列的输入(也就是最多一次能接受22个字符的输入),每个输入的x 也是27维度的向量,输出层也是27维度的,也就是多个分类的问题,用RNN实现。代码如下:
import torch import numpy as np from torch.autograd import Variable batch_size = 1 # 批量数据的大小,数据的批次,比如这里有5个需要测试的单词。 seq_len = 22 # 一共是多少个时间序列,是指时间的维度,在这里的意思就是一次性最大能接受20个字符的输入。 input_size = 27 # 某一时刻下,输入的X的维度向量,比如这里的4指的是维度是[4, 1] hidden_size = 27 # 每一层的激活后 h 的维度,比如这里的2指的是维度是[2, 1] # num_layers = 1 # a~z标号是1~26, 空格是1,这个字典表,每个字符有个对应的编号,也就是下标就是编号 idx2char = [' ', # 0 'a', 'b', 'c', 'd', 'e', 'f', 'g', # 1-7 'h', 'i', 'j', 'k', 'l', 'm', 'n', # 8-14 'o', 'p', 'q', 'r', 's', 't', # 15-20 'u', 'v', 'w', 'x', 'y', 'z'] # 21-26 idx2char = np.array(idx2char) # 每个编号有个对应的向量,也就是下标对应的向量 one_hot_look = np.array(np.eye(idx2char.shape[0])).astype(int) print('idx2char.shape[0]=', idx2char.shape[0]) x_data = np.zeros((batch_size, seq_len)).astype(int) y_data = np.zeros((batch_size, seq_len)).astype(int) # 待翻译的句子 x_data_char = ['hello and thank you'] y_data_char = ['ni hao qie duo xie you'] # 填充每个字符的下标 i=0 for item in x_data_char: # fill each data j=0 for letter in item: x_data[i][j] = np.where(idx2char == letter)[0][0] j = j + 1 i = i + 1 i=0 for item in y_data_char: # fill each data j=0 for letter in item: y_data[i][j] = np.where(idx2char == letter)[0][0] j = j + 1 i = i + 1 #print(x_data) #print(y_data) # Step 2:============================定义一个RNNCell的模型=================== class RNNCellModel(torch.nn.Module): def __init__(self, input_size, hidden_size): super(RNNCellModel, self).__init__() self.input_size = input_size self.hidden_size = hidden_size self.rnncell = torch.nn.RNNCell(input_size=self.input_size, hidden_size=self.hidden_size) def forward(self, input, hidden): hidden = self.rnncell(input, hidden) return hidden model = RNNCellModel(input_size, hidden_size) # Step 3:============================定义损失函数和优化器=================== # 定义 loss 函数,这里用的是交叉熵损失函数(Cross Entropy),这种损失函数之前博文也讲过的。 criterion = torch.nn.CrossEntropyLoss() # 我们优先使用Adam下降,lr是学习率: 0.1 optimizer = torch.optim.Adam(model.parameters(), 1e-1) # Step 4:============================开始训练=================== for e in range(200): loss = 0 optimizer.zero_grad() hidden = torch.zeros(batch_size, hidden_size) # 随机初始化的h0,且必须是Tensor类型的 i = 0 print('e============================') print('predicted str: ', end='') for cur_seq in zip(*x_data): # for each seq cur_seq = np.array(cur_seq) #input 必须是Tensor类型的 input = torch.Tensor([one_hot_look[x] for x in cur_seq]) # print(input.shape) # y_label 必须是长整数型,且必须是Tensor类型的 y_label = torch.LongTensor(np.array(y_data[:, i])) #print('real y_label: ', idx2char[y_label.item()], end='') # 前向传播 hidden = model(input, hidden) _, idx = hidden.max(dim=1) print(idx2char[idx.item()], end='') # print(hidden) # 累加损失 loss += criterion(hidden, y_label) i = i+1 loss.backward() optimizer.step() print(',epoch [%d/200] loss=%.4f' % (e+1, loss.item()), end='')输出结果如下:训练的字符串是hello and thank you
这个模型基本就差不多了训练出来是 ni hao qie duo xie ni
二:使用RNN类实现简单的字符串翻译 或者还可以用RNN模型来做,这样省去了自己写循环迭代序列的过程 ,这里需要注意模型的建立需要传入层数,以及需要传递batch_size,因为hidden节点需要这个参数。
import torch import numpy as np from torch.autograd import Variable batch_size = 1 # 批量数据的大小,数据的批次,比如这里有5个需要测试的单词。 seq_len = 22 # 一共是多少个时间序列,是指时间的维度,在这里的意思就是一次性最大能接受22个字符的输入。 input_size = 27 # 某一时刻下,输入的X的维度向量,比如这里的4指的是维度是[4, 1] hidden_size = 27 # 每一层的激活后 h 的维度,比如这里的2指的是维度是[2, 1] num_layers = 3 # 共有三层RNN # a~z标号是1~26, 空格是1,这个字典表,每个字符有个对应的编号,也就是下标就是编号 idx2char = [' ', # 0 'a', 'b', 'c', 'd', 'e', 'f', 'g', # 1-7 'h', 'i', 'j', 'k', 'l', 'm', 'n', # 8-14 'o', 'p', 'q', 'r', 's', 't', # 15-20 'u', 'v', 'w', 'x', 'y', 'z'] # 21-26 idx2char = np.array(idx2char) # 每个编号有个对应的向量,也就是下标对应的向量 one_hot_look = np.array(np.eye(idx2char.shape[0])).astype(int) print('idx2char.shape[0]=', idx2char.shape[0]) x_data = np.zeros((batch_size, seq_len)).astype(int) y_data = np.zeros((batch_size, seq_len)).astype(int) # 待翻译的句子 x_data_char = ['hello and thank you'] y_data_char = ['ni hao qie duo xie you'] # 填充每个字符的下标 i=0 for item in x_data_char: # fill each data j=0 for letter in item: x_data[i][j] = np.where(idx2char == letter)[0][0] j = j + 1 i = i + 1 i=0 for item in y_data_char: # fill each data j=0 for letter in item: y_data[i][j] = np.where(idx2char == letter)[0][0] j = j + 1 i = i + 1 #print(x_data) #print(y_data) x_one_hot = [one_hot_look[x] for x in x_data] inputs = torch.Tensor(x_one_hot).view(seq_len, batch_size, input_size) lables = torch.LongTensor(y_data).view(-1) # Step 2:============================定义一个RNNCell的模型=================== class RNNModel(torch.nn.Module): def __init__(self, input_size, hidden_size, batch_size, num_layers): super(RNNModel, self).__init__() self.input_size = input_size self.hidden_size = hidden_size self.batch_size = batch_size self.num_layers = num_layers self.rnn = torch.nn.RNN(input_size=self.input_size, hidden_size=self.hidden_size, num_layers=num_layers) def forward(self, input): # 由于不需要自己写序列的迭代,因此直接将随机初始化h0写道这里 hidden = torch.zeros(num_layers, batch_size, hidden_size) # 随机初始化的h0,且必须是Tensor类型的 # 我们只关心output: [seq_len, batch_size, hidden_size] # input是: [seq_len, batch_size, input_size] output, _ = self.rnn(input, hidden) # 将out reshape到[seq_len * batch_size, hidden_size] return output.view(-1, self.hidden_size) model = RNNModel(input_size, hidden_size, batch_size, num_layers) # Step 3:============================定义损失函数和优化器=================== # 定义 loss 函数,这里用的是交叉熵损失函数(Cross Entropy),这种损失函数之前博文也讲过的。 criterion = torch.nn.CrossEntropyLoss() # 我们优先使用Adam下降,lr是学习率: 0.1 optimizer = torch.optim.Adam(model.parameters(), lr=0.05) # Step 4:============================开始训练=================== for e in range(200): loss = 0 optimizer.zero_grad() # 前向传播 hidden = model(inputs) # 计算损失 loss = criterion(hidden, lables) # 反向传播,更新参数 loss.backward() optimizer.step() _, idx = hidden.max(dim=1) idx = idx.data.numpy() print('e============================') print('predicted str: ', end='') print(''.join([idx2char[x] for x in idx]), end='') print(',epoch [%d/200] loss=%.4f' % (e+1, loss.item()), end='')效果如下:很明显,损失好像更低了些。