当前位置:   article > 正文

LSTM从入门到精通(形象的图解,详细的代码和注释,完美的数学推导过程)_lstm模型

lstm模型

先附上这篇文章的一个思维导图


  1. 什么是RNN

按照八股文来说:RNN实际上就是一个带有 记忆的时间序列的预测模型

RNN的细胞结构图如下:

softmax激活函数只是我举的一个例子,实际上得到y<t>也可以通过其他的激活函数得到

其中a<t-1>代表t-1时刻隐藏状态,a<t>代表经过X<t>这一t时刻的输入之后,得到的新的隐藏状态。公式主要是a<t> = tanh(Waa * a<t-1> + Wax * X<t> + b1) ;大白话解释一下就是,X<t>是今天的吊针,a<t-1>是昨天的发烧度数39,经过今天这一针之后,a<t>变成38度。这里的记忆体现在今天的38度是在前一天的基础上,通过打吊针来达到第二天的降温状态。


1.1 RNN的应用

由于RNN的记忆性,我们最容易想到的就是RNN在自然语言处理方面的应用,譬如下面这张图,提前预测出下一个字。

除此之外,RNN的应用还包括下面的方向:

  1. 语言模型:RNN被广泛应用于语言模型的建模中,例如自然语言处理、机器翻译、语音识别等领域。

  1. 时间序列预测:RNN可以用于时间序列预测,例如股票价格预测、气象预测、心电图信号预测等。

  1. 生成模型:RNN可以用于生成模型,例如文本生成、音乐生成、艺术创作等。

  1. 强化学习:RNN可以用于强化学习中,例如在游戏、机器人控制和决策制定等领域。


1.2 RNN的缺陷

想必大家一定听说过LSTM,没错,就是由于RNN的尿性,所以才出现LSTM这一更精妙的时间序列预测模型的设计。但是我们知己知彼才能百战百胜,因此我还是决定详细分析一下RNN的缺陷,看过一些资料,但是只是肤浅的提到了梯度消失和梯度爆炸,没有实际的数学推导,这可不是一个求学之人应该有的态度!

主要的缺陷是两个:

  1. 长期依赖问题导致的梯度消失:众所周知RNN模型是一个具有记忆的模型,每一次的预测都和当前输入以及之前的状态有关,但是我们试想,如果我们的句子很长,他在第1000个记忆细胞还能记住并很好的利用第1个细胞的记忆状态吗?答案显然是否定的

  1. 梯度爆炸


1.2.1 梯度消失和梯度爆炸的详细公式推导

敲黑板(手写公式推导,大家最迷糊的地方):

根据下面图示的例子,我手写并反复检查了自己的过程(下图),请各位看官务必认真看看,理解起来并不难,对于别的文章随口一提的梯度消失和梯度爆炸实在是透彻太多啦!!!

我们假设损失函数 ,Y是实际值,O是预测值;首先,我们假设只有 三层,然后通过三层我们就能以此类推找出规律。反向传播我们需要对Wo,Wx,Ws,b四个变量都求偏导,在这里我们主要对Wx求偏导,其他三个以此类推,就很简单了。为了表示更清晰,笔者使用紫色的x表示乘法。

根据推导的公式我们得到一个指数函数,我们在高中时候就学到过指数函数的变化系数是极大的,因此在t趋于比较大的时候(也就是我们的句子比较长的时候),如果比1小不少,那么模型的一部分梯度会趋于0,因此优化会几乎停止;同理,如果比1大一些,那么模型的部分梯度会极大,导致模型和的变化无法控制,优化毫无意义。


  1. 什么是LSTM

八股文解释:LSTM(长短时记忆网络)是一种常用于处理序列数据的深度学习模型,与传统的 RNN(循环神经网络)相比,LSTM引入了三个门( 输入门、遗忘门、输出门,如下图所示)和一个 细胞状态(cell state),这些机制使得LSTM能够更好地处理序列中的长期依赖关系。注意:小蝌蚪形状表示的是sigmoid激活函数
Ct是细胞状态(记忆状态), 是输入的信息, 是隐藏状态(基于 得到的)

用最朴素的语言解释一下三个门,并且用两门考试来形象的解释一下LSTM:

  1. 遗忘门:通过x和ht的操作,并经过sigmoid函数,得到0,1的向量,0对应的就代表之前的记忆某一部分要忘记,1对应的就代表之前的记忆需要留下的部分 ===>代表复习上一门线性代数所包含的记忆,通过遗忘门,忘记掉和下一门高等数学无关的内容(比如矩阵的秩)

  1. 输入门:通过将之前的需要留下的信息和现在需要记住的信息相加,也就是得到了新的记忆状态。===>代表复习下一门科目高等数学的时候输入的一些记忆(比如洛必达法则等等),那么已经线性代数残余且和高数相关的部分(比如数学运算)+高数的知识=新的记忆状态

  1. 输出门:整合,得到一个输出===>代表高数所需要的记忆,但是在实际的考试不一定全都发挥出来考到100分。因此,则代表实际的考试分数

为了便于大家理解,附上几张非常好的图供大家理解完整的数据处理的流程:

遗忘门:

输入门:

细胞状态:

输出门:


2.1 LSTM的模型结构

这里有两张别的博主的很好的图,我在初学的时候也是恍然大悟:

图的出处

解释一下pytorch训练lstm所使用的参数:

  1. 这是利用pytorch调用LSTM所使用的参数

output,(h_n,c_n) = lstm (x, [ht_1, ct_1]),一般直接放入x就好,后面中括号的不用管
  1. 这是作为x(输入)喂给LSTM的参数

x:[seq_length, batch_size, input_size],这里有点反人类,batch_size一般都是放在开始的位置
  1. 这是pytorch简历LSTM是所需参数

lstm = LSTM(input_size,hidden_size,num_layers)

2.2 LSTM相比RNN的优势

LSTM的反向传播的数学推导很繁琐,因为涉及到的变量很多,但是LSTM确实是可以在一定程度上解决梯度消失和梯度爆炸的问题。我简单说一下,RNN的连乘主要是W的连乘,而W是一样的,因此就是一个指数函数(在梯度中出现指数函数并不是一件友好的事情);相反,LSTM的连乘是的偏导的不断累乘,如果前后的记忆差别不大,那偏导的值就是1,那就是多个1相乘。当然,也可能出现某一一些偏导的值很大,但是一定不会很多(换句话说,一句话的前后没有逻辑,那完全没有训练的必要)。


2.3 pytorch实现LSTM对股票的预测(实战)

需要安装一下tushare的金融方面的数据集,代码的注解我已经写的很清楚了

  1. #!/usr/bin/python3
  2. # -*- encoding: utf-8 -*-
  3. import matplotlib.pyplot as plt
  4. import numpy as np
  5. import tushare as ts
  6. import pandas as pd
  7. import torch
  8. from torch import nn
  9. import datetime
  10. import time
  11. DAYS_FOR_TRAIN = 10
  12. class LSTM_Regression(nn.Module):
  13. """
  14. 使用LSTM进行回归
  15. 参数:
  16. - input_size: feature size
  17. - hidden_size: number of hidden units
  18. - output_size: number of output
  19. - num_layers: layers of LSTM to stack
  20. """
  21. def __init__(self, input_size, hidden_size, output_size=1, num_layers=2):
  22. super().__init__()
  23. self.lstm = nn.LSTM(input_size, hidden_size, num_layers)
  24. self.fc = nn.Linear(hidden_size, output_size)
  25. def forward(self, _x):
  26. x, _ = self.lstm(_x) # _x is input, size (seq_len, batch, input_size)
  27. s, b, h = x.shape
  28. x = x.view(s * b, h)
  29. x = self.fc(x)
  30. x = x.view(s, b, -1) # 把形状改回来
  31. return x
  32. def create_dataset(data, days_for_train=5) -> (np.array, np.array):
  33. """
  34. 根据给定的序列data,生成数据集
  35. 数据集分为输入和输出,每一个输入的长度为days_for_train,每一个输出的长度为1。
  36. 也就是说用days_for_train天的数据,对应下一天的数据。
  37. 若给定序列的长度为d,将输出长度为(d-days_for_train+1)个输入/输出对
  38. """
  39. dataset_x, dataset_y = [], []
  40. for i in range(len(data) - days_for_train):
  41. _x = data[i:(i + days_for_train)]
  42. dataset_x.append(_x)
  43. dataset_y.append(data[i + days_for_train])
  44. return (np.array(dataset_x), np.array(dataset_y))
  45. if __name__ == '__main__':
  46. t0 = time.time()
  47. data_close = ts.get_k_data('000001', start='2019-01-01', index=True)['close'] # 取上证指数的收盘价
  48. data_close.to_csv('000001.csv', index=False) #将下载的数据转存为.csv格式保存
  49. data_close = pd.read_csv('000001.csv') # 读取文件
  50. df_sh = ts.get_k_data('sh', start='2019-01-01', end=datetime.datetime.now().strftime('%Y-%m-%d'))
  51. print(df_sh.shape)
  52. data_close = data_close.astype('float32').values # 转换数据类型
  53. plt.plot(data_close)
  54. plt.savefig('data.png', format='png', dpi=200)
  55. plt.close()
  56. # 将价格标准化到0~1
  57. max_value = np.max(data_close)
  58. min_value = np.min(data_close)
  59. data_close = (data_close - min_value) / (max_value - min_value)
  60. # dataset_x
  61. # 是形状为(样本数, 时间窗口大小)
  62. # 的二维数组,用于训练模型的输入
  63. # dataset_y
  64. # 是形状为(样本数, )
  65. # 的一维数组,用于训练模型的输出。
  66. dataset_x, dataset_y = create_dataset(data_close, DAYS_FOR_TRAIN) # 分别是(1007,10,1)(1007,1)
  67. # 划分训练集和测试集,70%作为训练集
  68. train_size = int(len(dataset_x) * 0.7)
  69. train_x = dataset_x[:train_size]
  70. train_y = dataset_y[:train_size]
  71. # 将数据改变形状,RNN 读入的数据维度是 (seq_size, batch_size, feature_size)
  72. train_x = train_x.reshape(-1, 1, DAYS_FOR_TRAIN)
  73. train_y = train_y.reshape(-1, 1, 1)
  74. # 转为pytorch的tensor对象
  75. train_x = torch.from_numpy(train_x)
  76. train_y = torch.from_numpy(train_y)
  77. model = LSTM_Regression(DAYS_FOR_TRAIN, 8, output_size=1, num_layers=2) # 导入模型并设置模型的参数输入输出层、隐藏层等
  78. model_total = sum([param.nelement() for param in model.parameters()]) # 计算模型参数
  79. print("Number of model_total parameter: %.8fM" % (model_total / 1e6))
  80. train_loss = []
  81. loss_function = nn.MSELoss()
  82. optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)
  83. for i in range(200):
  84. out = model(train_x)
  85. loss = loss_function(out, train_y)
  86. loss.backward()
  87. optimizer.step()
  88. optimizer.zero_grad()
  89. train_loss.append(loss.item())
  90. # 将训练过程的损失值写入文档保存,并在终端打印出来
  91. with open('log.txt', 'a+') as f:
  92. f.write('{} - {}\n'.format(i + 1, loss.item()))
  93. if (i + 1) % 1 == 0:
  94. print('Epoch: {}, Loss:{:.5f}'.format(i + 1, loss.item()))
  95. # 画loss曲线
  96. plt.figure()
  97. plt.plot(train_loss, 'b', label='loss')
  98. plt.title("Train_Loss_Curve")
  99. plt.ylabel('train_loss')
  100. plt.xlabel('epoch_num')
  101. plt.savefig('loss.png', format='png', dpi=200)
  102. plt.close()
  103. # torch.save(model.state_dict(), 'model_params.pkl') # 可以保存模型的参数供未来使用
  104. t1 = time.time()
  105. T = t1 - t0
  106. print('The training time took %.2f' % (T / 60) + ' mins.')
  107. tt0 = time.asctime(time.localtime(t0))
  108. tt1 = time.asctime(time.localtime(t1))
  109. print('The starting time was ', tt0)
  110. print('The finishing time was ', tt1)
  111. # for test
  112. model = model.eval() # 转换成评估模式
  113. # model.load_state_dict(torch.load('model_params.pkl')) # 读取参数
  114. # 注意这里用的是全集 模型的输出长度会比原数据少DAYS_FOR_TRAIN 填充使长度相等再作图
  115. dataset_x = dataset_x.reshape(-1, 1, DAYS_FOR_TRAIN) # (seq_size, batch_size, feature_size)
  116. dataset_x = torch.from_numpy(dataset_x)
  117. pred_test = model(dataset_x) # 全量训练集
  118. # 的模型输出 (seq_size, batch_size, output_size)
  119. pred_test = pred_test.view(-1).data.numpy()
  120. pred_test = np.concatenate((np.zeros(DAYS_FOR_TRAIN), pred_test)) # 填充0 使长度相同
  121. assert len(pred_test) == len(data_close)
  122. plt.plot(pred_test, 'r', label='prediction')
  123. plt.plot(data_close, 'b', label='real')
  124. plt.plot((train_size, train_size), (0, 1), 'g--') # 分割线 左边是训练数据 右边是测试数据的输出
  125. plt.legend(loc='best')
  126. plt.savefig('result.png', format='png', dpi=200)
  127. plt.close()

2.4 小问题:为什么采用tanh函数,不能都用sigmoid函数吗

先放上两个函数的图形:

  1. Sigmoid函数比Tanh函数收敛饱和速度慢

  1. Sigmoid函数比Tanh函数值域范围更窄

  1. tanh的均值是0,Sigmoid均值在0.5左右,均值在0的数据显然更便于数据处理

  1. tanh的函数变化敏感区间更大

  1. 对两者求导,发现tanh对计算的压力更小,直接是1-原函数的平方,不需要指数操作

使用该问的图请标明出处,创作不易,希望收获你的赞赞

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/591909
推荐阅读
相关标签
  

闽ICP备14008679号