概念
大家在学习深度学习时,都是按照“人工神经网络”、“卷积神经网络”的顺序来学习的。我们在学习的过程中可能会发现这样网络可能不适用一些带有“时间序列”的问题。
比如下面,要预测股票价格:
时间 | 特征1 | 特征2 | 特征3 | 特征4 | 价格 |
2024-11-14 | 0.56 | 0.34 | 0.78 | 0.91 | 456 |
2024-11-15 | 0.66 | 0.48 | 0.69 | 0.71 | 987 |
我们使用“人工神经网络”和“卷积神经网络”时,不能将“时间考虑在内”,但是尝试告诉我们,股票也会受到前面时间环境的影响,因此,我们需要构建一个新的模型,用来解决这个问题。
下面先看一下RNN的结构:
我们来解释一下这个结构,这里面x1、x2、x3...并不是指一个特征值,而是指一条记录。就如同上面的一条记录(2024-11-4,0.56,0.34,0.78,0.91)。这个结构的意思是,将前面的输出作为一个参数去帮助后面的一条记录的训练。
其数学推导式如下:
其中,g为响应的激活函数,w为响应的权重矩阵。
相应的:
任务
自动寻找句子中的人名:
1、The courses are taught by Flare Zhao and David Chen.
我们可以将每个单词作为一条记录,进行输入,然后每个单词也有一个对应的输出,比如:
RNN常见的结构
多输入,多输出
如:
输入:x1,x2,x3,...,xi
输出:y1,y2,y3,...,yi
多输入,单输出
如:
输入:x1,x2,x3,...,xi
输出:y
可以应用于情感识别
例子:i feel happy watching the movie.
判断为:positive
单个输入,多个输出
这样的结构可以应用于:序列数据生成,比如文本生成等。
多输入,多输出(结构不相等)
可以应用于,语言翻译等。
RNN的缺陷
普通的RNN有一个明显的缺陷,那就是前面的信息在传递给后面时,信息的“强度”会越来越小。
比如上面“蓝色”的圈,每次传递一层,其重要性就会减少一些。所以,有很多重要的信息可能会丢失。
例子
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np# 生成示例数据(模拟时间序列数据)
data = [[0.56, 0.34, 0.78, 0.91, 456],[0.66, 0.48, 0.69, 0.71, 987],[0.72, 0.55, 0.67, 0.65, 1001], # 添加更多数据以增强示例
]# 转换为numpy数组
data = np.array(data, dtype=np.float32)# 功能函数:创建序列数据的X和Y
def create_sequences(data, seq_length):xs = []ys = []for i in range(len(data) - seq_length):x = data[i:i + seq_length, :-1] # 使用前四列作为输入y = data[i + seq_length, -1] # 使用最后一列作为输出(价格)xs.append(x)ys.append(y)return np.array(xs), np.array(ys)seq_length = 2 # 序列长度(调整以获得不同的输入)
X, y = create_sequences(data, seq_length)# 转换为Tensor
X = torch.from_numpy(X)
y = torch.from_numpy(y).view(-1, 1)# 定义LSTM模型
class StockPricePredictor(nn.Module):def __init__(self, input_size, hidden_size, output_size, num_layers=1):super(StockPricePredictor, self).__init__()self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)self.fc = nn.Linear(hidden_size * seq_length, output_size)def forward(self, x):# 初始隐藏和单元状态h0 = torch.zeros(1, x.size(0), hidden_size) # 隐藏层c0 = torch.zeros(1, x.size(0), hidden_size) # 细胞状态out, _ = self.lstm(x, (h0, c0)) # LSTM输出out = out.reshape(out.shape[0], -1)out = self.fc(out)return out# 模型参数
input_size = 4 # 输入特征的数量
hidden_size = 50 # 隐藏层大小
output_size = 1 # 输出的特征数量(即价格)
num_epochs = 100 # 训练迭代次数
learning_rate = 0.001 # 学习率# 模型实例化
model = StockPricePredictor(input_size, hidden_size, output_size)# 损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)# 训练模型
for epoch in range(num_epochs):output = model(X)loss = criterion(output, y)optimizer.zero_grad()loss.backward()optimizer.step()if (epoch + 1) % 10 == 0:print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')# 测试模型
with torch.no_grad():test_seq = torch.tensor(data[:seq_length, :-1], dtype=torch.float32).view(1, seq_length, -1)predicted_price = model(test_seq)print(f'Predicted next day price: {predicted_price.item()}')
1. 创建序列数据的X和Y
在时间序列预测中,我们通常需要根据过去的数据来预测未来的值。为了做到这一点,我们需要将原始数据转换成一组输入-输出对,这样才能用于模型训练。
示例数据转换
假设我们有以下数据(每行表示一天):
时间 | 特征1 | 特征2 | 特征3 | 特征4 | 价格 |
---|---|---|---|---|---|
2024-11-14 | 0.56 | 0.34 | 0.78 | 0.91 | 456 |
2024-11-15 | 0.66 | 0.48 | 0.69 | 0.71 | 987 |
2024-11-16 | 0.72 | 0.55 | 0.67 | 0.65 | 1001 |
我们想根据前两天的特征预测第三天的价格。对于这个例子,let seq_length = 2(代表使用的过去两天数据来预测)。这里的目标是将这些数据转换成多个"输入-输出"对。
转换后的数据(示例):
- 输入 (X):
- [[0.56, 0.34, 0.78, 0.91], [0.66, 0.48, 0.69, 0.71]]
- 输出 (Y):
- [1001]
解释:
X
是一个二维数组,每个内部数组包含 seq_length 天的特征数据。Y
是目标值,就是我们要预测的第三天的价格。
这样做的目的是让模型学习到“给定过去的特征,输出未来的价格”。
2. LSTM的初始化
在代码中的这一行:
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
这行代码定义了一个LSTM层用于RNN模型。
参数解释
-
input_size: 输入数据的特征数量。在我们的例子中,每天有4个特征(例如:特征1到特征4),因此 input_size = 4。
-
hidden_size: 隐藏状态的维度。它决定了LSTM层的输出特征维度,并影响模型的记忆能力。通常,增加hidden_size能提高模型的复杂性和其潜在的预测能力,但也会增加计算复杂度。
-
num_layers: LSTM层的堆叠数量。这允许我们构造一个深度LSTM网络,单一层通常能处理简单问题,但对于复杂的时间依赖关系,堆叠多个LSTM层可能提高模型效果。
-
batch_first=True: 指定输入数据的维度顺序。在PyTorch中,默认的LSTM输入是
(seq_length, batch_size, input_size)
。当batch_first=True
时,输入的维度变为(batch_size, seq_length, input_size)
,这通常更符合直觉,因为批次维度是第一位的。这个参数的设置将影响输入数据的格式。