感谢阅读
- RNN简介
-
- 传统RNN
-
- 演示内部结构过程
- 内部计算公式
- RNN输出
- 激活函数tanh
- Pytorch构建传统RNN
- 梯度计算
- LSTM介绍
-
- 遗忘门结构分析:
- 输入门结构分析:
- 细胞状态更新分析:
- 输出门结构分析:
- 结构图
- 梯度公式
- 加强对现实生活的理解
- 代码示例
- GRU介绍
-
- 结构图
- 个人对GRU的理解
- LSTM两个地方难以比拟
- RNN示例(人名分类问题)
-
- 案例介绍
- 下载和解释数据集
- 导包
- 检查常用字符的数量
- 建立国家名称,获得国家数量
- 读数据到内存
- 构建数据源并迭代
-
- 改进异常索引的处理
- 构建三种RNN模型
-
- 构建传统RNN
- 构建LSTM
- 构建GRU
- 测试和训练三个模型
-
- 测试
- 训练
-
- 传统RNN
- LSTM
- GRU
- 进行预测
-
- 构建预测函数
-
- 传统RNN
- LSTM
- GRU
- 调用
- 注意力机制
-
- 注意机制简介
-
- 注意力概念
- 注意力计算规则
- 作用
- 帮助理解生活场景
- bmm运算简介
- 代码实现
- RNN案例 seq2seq英译法
-
- seq2seq介绍
-
- seq2seq模型架构
- 模型解释
- 数据集下载
- 清理导包和文件
- 思路分析
- 数据预处理
- 构建数据源对象并进行测试
- 编码器和解码器
-
- 构建基于GRU的编码器
-
- 思路分析
- 代码实现
- 基于GRU和Attention的解码器
-
- 结构图
- 代码实现
- 训练模型
-
- teacher_forcing
- 内部迭代训练函数
-
- 设置参数
- 代码实现
- 训练
- 模型评估与测试
-
- 书写评估函数
- 评估
- 关于服务器调试python的说明
-
- 操作前需要知道的知识点
-
- nohup
- tail
- 首先进入python文件所在目录并编译
- 转后台进程
- 启动一个SSH连接看训练输出
- 执行结果
RNN简介
RNN(Recurrent Neural Network), 中文称循环神经网络, 它通常以序列数据为输入, 通过网络内部的结构设计,有效地捕捉序列之间的关系特征, 一般以序列的形式输出
传统RNN
演示内部结构过程
两个黑点一起到达蓝色区域(并在之前形成整体)
内部计算公式
RNN输出
激活函数tanh
帮助调整流经网络的值, tanh函数将值压缩在-1和1之间
Pytorch构建传统RNN
def dm_run_for_hiddennum(): ''' 第一个参数:input_size(输入张量x的维度) 第二个参数:hidden_size(隐藏层的维度, 隐藏层的神经元数) 第三个参数:num_layer(隐藏层的数量) ''' rnn = nn.RNN(5, 6, 2) # A 隐藏层数从1-->2 需要修改以下程序的地方? ''' 第一个参数:sequencelength(输入序列的长度) 第二个参数:batch_size(批次的样本数量) 第三个参数:input_size(输入张量的维度) '''
input = torch.randn(1, 3, 5) # B
''' 第一个参数:num_layer * num_directions(层数*网络方向) 第二个参数:batch_size(批次的样本数) 第三个参数:hidden_size(隐藏层的维度, 隐藏层神经元的个数) '''
h0 = torch.randn(2, 3, 6) # C
output, hn = rnn(input, h0) #
print('output-->', output.shape, output)
print('hn-->', hn.shape, hn)
print('rnn模型--->', rnn) # nn模型---> RNN(5, 6, num_layers=11)
# 结论:若只有一个隐藏次 output输出结果等于hn
# 结论:如果有2个隐藏层,output的输出结果有2个,hn等于最后一个隐藏层
梯度计算
LSTM介绍
优点:LSTM的门结构能够有效减缓长序列问题中可能出现的梯度消失或爆炸, 虽然并不能杜绝这种现象, 但在更长的序列问题上表现优于传统RNN. 缺点:由于内部结构相对较复杂, 因此训练效率在同等算力下较传统RNN低很多
遗忘门结构分析:
与传统RNN的内部结构计算非常相似, 首先将当前时间步输入x(t)与上一个时间步隐含状态h(t-1)拼接, 得到[x(t), h(t-1)], 然后通过一个全连接层做变换, 最后通过sigmoid函数进行激活得到f(t), 我们可以将f(t)看作是门值, 好比一扇门开合的大小程度, 门值都将作用在通过该扇门的张量, 遗忘门门值将作用的上一层的细胞状态上, 代表遗忘过去的多少信息, 又因为遗忘门门值是由x(t), h(t-1)计算得来的, 因此整个公式意味着根据当前时间步输入和上一个时间步隐含状态h(t-1)来决定遗忘多少上一层的细胞状态所携带的过往信息.
输入门结构分析:
我们看到输入门的计算公式有两个, 第一个就是产生输入门门值的公式, 它和遗忘门公式几乎相同, 区别只是在于它们之后要作用的目标上. 这个公式意味着输入信息有多少需要进行过滤. 输入门的第二个公式是与传统RNN的内部结构计算相同. 对于LSTM来讲, 它得到的是当前的细胞状态, 而不是像经典RNN一样得到的是隐含状态.
细胞状态更新分析:
细胞更新的结构与计算公式非常容易理解, 这里没有全连接层, 只是将刚刚得到的遗忘门门值与上一个时间步得到的C(t-1)相乘, 再加上输入门门值与当前时间步得到的未更新C(t)相乘的结果. 最终得到更新后的C(t)作为下一个时间步输入的一部分. 整个细胞状态更新过程就是对遗忘门和输入门的应用.
输出门结构分析:
输出门部分的公式也是两个, 第一个即是计算输出门的门值, 它和遗忘门,输入门计算方式相同. 第二个即是使用这个门值产生隐含状态h(t), 他将作用在更新后的细胞状态C(t)上, 并做tanh激活, 最终得到h(t)作为下一时间步输入的一部分. 整个输出门的过程, 就是为了产生隐含状态h(t).
结构图
C表示某时刻的记忆细胞 h表示某时刻的状态 f遗忘门 i更新门 o输出门 ht流向下一个时刻以及直接输出
梯度公式
现实生活列子加强理解
我们以考试为例子:我们马上要期末考试了,第一门是高数,第二门线代。这个图的Xt就是考线代,h(t-1)就是考高数的状态,c(t-1)就是考高数的记忆,ht包含了考线代结束的状态以及分数,分数那一部分作为输出从上面输出,状态传给下一门考试(比如英语)。为什么遗忘?因为并不是所有东西都是线代需要关心的,这就是遗忘门的作用。为什么要保留上一门知识,比如高数用到的运算能力是我们需要保留的。 同样的,传统RNN就是要记住所有的东西,效率就会变低。
代码示例
# 定义LSTM的参数含义: (input_size, hidden_size, num_layers)
# 定义输入张量的参数含义: (sequence_length, batch_size, input_size)
# 定义隐藏层初始张量和细胞初始状态张量的参数含义:
# (num_layers * num_directions, batch_size, hidden_size)
>>> import torch.nn as nn
>>> import torch
>>> rnn = nn.LSTM(5, 6, 2)
>>> input = torch.randn(1, 3, 5)
>>> h0 = torch.randn(2, 3, 6)
>>> c0 = torch.randn(2, 3, 6)
>>> output, (hn, cn) = rnn(input, (h0, c0))
>>> output
tensor([[[ 0.0447, -0.0335, 0.1454, 0.0438, 0.0865, 0.0416],
[ 0.0105, 0.1923, 0.5507, -0.1742, 0.1569, -0.0548],
[-0.1186, 0.1835, -0.0022, -0.1388, -0.0877, -0.4007]]],
grad_fn=<StackBackward>)
>>> hn
tensor([[[ 0.4647, -0.2364, 0.0645, -0.3996, -0.0500, -0.0152],
[ 0.3852, 0.0704, 0.2103, -0.2524, 0.0243, 0.0477],
[ 0.2571, 0.0608, 0.2322, 0.1815, -0.0513, -0.0291]],
[[ 0.0447, -0.0335, 0.1454, 0.0438, 0.0865, 0.0416],
[ 0.0105, 0.1923, 0.5507, -0.1742, 0.1569, -0.0548],
[-0.1186, 0.1835, -0.0022, -0.1388, -0.0877, -0.4007]]],
grad_fn=<StackBackward>)
>>> cn
tensor([[[ 0.8083, -0.5500, 0.1009, -0.5806, -0.0668, -0.1161],
[ 0.7438, 0.0957, 0.5509, -0.7725, 0.0824, 0.0626],
[ 0.3131, 0.0920, 0.8359, 0.9187, -0.4826, -0.0717]],
[[ 0.1240, -0.0526, 0.3035, 0.1099, 0.5915, 0.0828],
[ 0.0203, 0.8367, 0.9832, -0.4454, 0.3917, -0.1983],
[-0.2976, 0.7764, -0.0074, -0.1965, -0.1343, -0.6683]]],
grad_fn=<StackBackward>)
GRU介绍
GRU(Gated Recurrent Unit)也称门控循环单元结构, 它也是传统RNN的变体, 同LSTM一样能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象.
结构图
个人对GRU的理解
鄙人不才,如有理解错误之处,还望海涵斧正,先行谢过。 其实,个人感觉GRU是LSTM的改进版,把记忆、h二者功能进行了合并,其核心是上图最后一个式子,这就决定了记忆和遗忘的强度。 我还是以上学为例子。h(t-1)相当于我们在学校期间所学的所有东西,现在我们要搞毕设(以软件工程为例子,因为不才是软件工程专业出身,其他专业不是很了解)。Xt就是我们要搞毕设.h(t)就是我们学到的东西可以提供给做毕设的,比如python语言、软件体系结构、软件工程导论等。rt是什么呢?就是各学科可以提供给做毕设的东西的比列,比如python语言提供70%,软件体系提供10%。当然也有可能有0,比如选修课中的外国历史。h(上面带波浪线)的就相当于需要新学习的东西,比如我们做毕设的时候,想搞个反向代理服务器,学校没有教我们,我们是不是要自学?
LSTM难以比拟的两个地方
1.由于参数的减少,模型训练速度将会提升,同时可解释性也略微提升了一些。 2.由于运算的改良,降低了过拟合的风险。
RNN示例(人名分类问题)
案例介绍
以一个人名为输入, 使用模型帮助我们判断它最有可能是来自哪一个国家的人名, 这在某些国际化公司的业务中具有重要意义, 在用户注册过程中, 会根据用户填写的名字直接给他分配可能的国家或地区选项, 以及该国家或地区的国旗, 限制手机号码位数等等。
数据集下载与解释
在github上的name_decalre 点我下载 数据格式说明 每一行第一个单词为人名,第二个单词为国家名。中间用制表符tab分割
导包
# 导入torch工具
import torch
# 导入nn准备构建模型
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
# 导入torch的数据源 数据迭代器工具包
from torch.utils.data import Dataset, DataLoader
# 用于获得常见字母及字符规范化
import string
# 导入时间工具包
import time
# 引入制图工具包
import matplotlib.pyplot as plt
# 从io中导入文件打开方法
from io import open
查看常用字符数量
def data_process():
# 获取所有常用字符包括字母和常用标点
all_letters = string.ascii_letters + " .,;'"
# 获取常用字符数量
n_letters = len(all_letters)
return n_letters
def main():
print(data_process())
return 0
if __name__ == '__main__':
main()
构建国家名字,并获取国家数量
def get_country():
# 国家名 种类数
categorys = ['Italian', 'English', 'Arabic', 'Spanish', 'Scottish', 'Irish', 'Chinese', 'Vietnamese', 'Japanese',
'French', 'Greek', 'Dutch', 'Korean', 'Polish', 'Portuguese', 'Russian', 'Czech', 'German']
# 国家名 个数
categorynum = len(categorys)
return categorys,categorynum
def main():
# print(data_process())
print(get_country())
return 0
读数据到内存
def read_data(filename):
""" :param filename: :return: # 思路分析 1 打开数据文件 open(filename, mode='r', encoding='utf-8') 2 按行读文件、提取样本x 样本y line.strip().split('\t') 3 返回样本x的列表、样本y的列表 my_list_x, my_list_y """
my_list_x, my_list_y= [], []
# 打开文件
with open(filename, mode='r', encoding='utf-8') as f:
# 按照行读数据
for line in f.readlines():
if len(line) <= 5:
continue
# 按照行提取样本x 样本y
(x, y) = line.strip().split('\t')
my_list_x.append(x)
my_list_y.append(y)
# 返回样本x的列表、样本y的列表
return my_list_x, my_list_y
构建数据源并进行迭代
def read_data(filename):
""" :param filename: :return: # 思路分析 1 打开数据文件 open(filename, mode='r', encoding='utf-8') 2 按行读文件、提取样本x 样本y line.strip().split('\t') 3 返回样本x的列表、样本y的列表 my_list_x, my_list_y """
my_list_x, my_list_y= [], []
# 打开文件
with open(filename, mode='r', encoding='utf-8') as f:
# 按照行读数据
for line in f.readlines():
if len(line) <= 5:
continue
# 按照行提取样本x 样本y
(x, y) = line.strip().split('\t')
my_list_x.append(x)
my_list_y.append(y)
# 返回样本x的列表、样本y的列表
return my_list_x, my_list_y
class NameClassDataset(Dataset):
def __init__(self, my_list_x, my_list_y):
# 样本x
self.my_list_x = my_list_x
# 样本y
self.my_list_y = my_list_y
# 样本条目数
self.sample_len = len(my_list_x)
# 获取样本条数
def __len__(self):
return self.sample_len
# 获取第几条 样本数据
def __getitem__(self, index):
# 对index异常值进行修正 [0, self.sample_len-1]
index = min(max(index, 0), self.sample_len-1)
# 按索引获取 数据样本 x y
x = self.my_list_x[index]
y = self.my_list_y[index]
# print(x, y)
# 样本x one-hot张量化
tensor_x = torch.zeros(len(x), n_letters)
# 遍历人名 的 每个字母 做成one-hot编码
for li, letter in enumerate(x):
# letter2indx 使用all_letters.find(letter)查找字母在all_letters表中的位置
# 给one-hot赋值
tensor_x[li][all_letters.find(letter)] = 1
# 样本y 张量化
tensor_y = torch.tensor(categorys.index(y), dtype=torch.long)
# 返回结果
return tensor_x, tensor_y
def dm_test_NameClassDataset():
# 1 获取数据
myfilename = '../data/name_classfication.txt'
my_list_x, my_list_y = read_data(myfilename)
# 2 实例化dataset对象
nameclassdataset = NameClassDataset(my_list_x, my_list_y)
# 3 实例化dataloader
mydataloader = DataLoader(dataset=nameclassdataset, batch_size=1, shuffle=True)
for i, (x, y) in enumerate (mydataloader):
print('x.shape', x.shape, x)
print('y.shape', y.shape, y)
break
对异常索引的处理的改良
python语言支持我们的索引,所以我们的索引也应该如此,但是程序也会复杂一些,这个代码想替换的可以把NameClassDataset的__getitem__替换为下面的代码:
def __getitem__(self, index):
# 对index异常值进行修正 [0, self.sample_len-1]
if index < 0:
index = max(-self.sample_len, index)
else:
index = min(self.sample_len-1, index)
# 按索引获取 数据样本 x y
x = self.my_list_x[index]
y = self.my_list_y[index]
# print(x, y)
# 样本x one-hot张量化
tensor_x = torch.zeros(len(x), n_letters)
# 遍历人名 的 每个字母 做成one-hot编码
for li, letter in enumerate(x):
# letter2indx 使用all_letters.find(letter)查找字母在all_letters表中的位置
# 给one-hot赋值
tensor_x[li][all_letters.find(letter)] = 1
# 样本y 张量化
tensor_y = torch.tensor(categorys.index(y), dtype=torch.long)
# 返回结果
return tensor_x, tensor_y
构建三种RNN模型
构建传统RNN
class RNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size, num_layers=1):
super(RNN, self).__init__()
# 1 init函数 准备三个层 self.rnn self.linear self.softmax=nn.LogSoftmax(dim=-1)
self.input_size = input_size
self.hidden_size = hidden_size
self.output_size = output_size
self.num_layers = num_layers
# 定义rnn层
self.rnn = nn.RNN(self.input_size, self.hidden_size, self.num_layers)
# 定义linear层(全连接线性层)
self.linear = nn.Linear(self.hidden_size, self.output_size)
# 定义softmax层
self.softmax = nn.LogSoftmax(dim=-1)
def forward(self, input, hidden):
# 让数据经过三个层 返回softmax结果和hn
# 数据形状 [6,57] -> [6,1,57]
input = input.unsqueeze(1)
# 把数据送给模型 提取事物特征
# 数据形状 [seqlen,1,57],[1,1,128]) -> [seqlen,1,18],[1,1,128]
rr, hn = self.rnn(input, hidden)
# 数据形状 [seqlen,1,128] - [1, 128]
tmprr = rr[-1]
tmprr = self.linear(tmprr)
return self.softmax(tmprr), hn
def inithidden(self):
# 初始化隐藏层输入数据 inithidden()
return torch.zeros(self.num_layers, 1,self.hidden_size)
构建LSTM
class LSTM(nn.Module):
def __init__(self, input_size, hidden_size, output_size, num_layers=1):
super(LSTM, self).__init__()
# 1 init函数 准备三个层 self.rnn self.linear self.softmax=nn.LogSoftmax(dim=-1)
self.input_size = input_size
self.hidden_size = hidden_size
self.output_size = output_size
self.num_layers = num_layers
# 定义rnn层
self.rnn = nn.LSTM(self.input_size, self.hidden_size, self.num_layers)
# 定义linear层
self.linear = nn.Linear(self.hidden_size, self.output_size)
# 定义softmax层
self.softmax = nn.LogSoftmax(dim=-1)
def forward(self, input, hidden, c):
# 让数据经过三