循环神经网络(RNN)是深度学习中处理序列数据的重要工具。本文将带了解RNN的基本概念,并使用PyTorch框架进行实现。请系好安全带,即将探索RNN的基本细节。
输入(Input):RNN的输入。
隐藏状态(Hidden):所有层在最后一个时间步的隐藏状态。
输出(Output):所有时间步的最后一层的隐藏状态,以便将隐藏状态传递给下一层。
在上述图中,有N个时间步(水平方向)和M层(垂直方向)。在t=0时输入,并最初将隐藏状态传递给RNN单元,然后输出隐藏状态再传递给下一个输入序列的RNN单元,以此类推。在PyTorch中实现时,如果对PyTorch不熟悉,想给一个非常有用的提示,这真的非常重要。如果遵循这个提示,保证会很快学会:关注形状。
假设有以下一维数组输入数据(行=7,列=1):
# 假设数据
现在需要将这个数据分成批次。假设取批次大小=2。
RNN应该有3个维度:(批次大小,序列长度和输入维度)。
批次大小(Batch Size):一次发送给模型的样本数量。在这个例子中,批次大小=2,但可以取4, 8, 16, 32, 64等,这取决于内存(基本上是2的幂)。
序列长度(Sequence Length):输入数据序列的长度(时间步:0,1,2…N),RNN学习数据集中的序列模式。这里的灰色部分是序列长度,所以序列长度=3。
假设有基于日频率(1天)的股票市场数据,希望网络学习30天的数据序列。那么序列长度将是30。
输入维度或输入大小(Input Dimension):在数据集中使用的特征或维度的数量。在这种情况下,是一(列/特征)。假设有以下特征的股票市场数据:最高价、最低价、开盘价和收盘价,想要预测收盘价。在这种情况下,有输入维度=4:最高价、最低价、开盘价和收盘价。将在后面更详细地看到输入维度。
输入类型1:序列长度,批次大小,输入维度
输入类型2:批次大小,序列长度,输入维度
如果选择输入类型1,形状将是=3, 2, 1。如果选择输入类型2,形状将是=2, 3, 1。
因为只向模型提供输入序列,然后将详细探讨这两种输入形状。首先,创建了一个自定义的Dataset类,它返回输入序列和标签。这里输入是一个浮点数组,seq_len是窗口大小或输入数据的序列长度。
class MyDataset(Dataset):
def __init__(self, input, seq_len):
self.input = input
self.seq_len = seq_len
def __getitem__(self, item):
return input[item:item + self.seq_len], input[item + self.seq_len]
def __len__(self):
return len(self.input) - self.seq_len
input = np.arange(1,8).reshape(-1,1)
input = torch.tensor(input, dtype=torch.float)
现在可以调用MyDataset类来获取数据:
ds = MyDataset(input, 3)
dl = DataLoader(ds, batch_size=2)
对于inp, label in dl:
for inp, label in dl:
print(inp.numpy())
如果仔细观察,第一个维度是2,这是批次大小,接下来是3,这是序列长度,最后一个是1,特征或输入维度。现在有形状为2, 3, 1(BS, SeqLen, Input Dim)。
那么输入数据将如下所示。唯一改变的是第三维变为2(2是特征的数量)。
如果想将这个转换为输入类型1,需要置换输入。要做到这一点,只需在输入数据中交换批次维度和序列维度,如下所示:
inp.permute(1,0,2) # 交换维度0和维度1
如果现在注意到,第一个维度是3,而不是2,它是序列长度。第二个维度是2,这是批次大小。第三个维度是2,这是输入维度/特征。输入形状是=(3, 2, 2),这是输入类型1。如果有点困惑,那么花点时间在这个图像上...
让实现小型循环神经网络类,继承基类nn.Module。HL_size = 隐藏大小可以定义为32, 64, 128(再次最好是2的幂)和输入大小是数据中的特征数量(输入维度)。这里输入大小对于数据类型2是2,对于数据类型1是1。
class RNNModel(torch.nn.Module):
def __init__(self, input_size, HL_size):
super(RNNModel, self).__init__()
self.rnn = torch.nn.RNN(input_size=input_size,
hidden_size=Hidden_Size(HS),
num_layers=number_of_stacked_RNN,
bidirectional=True/False,
batch_first=True default is False)
# 如果想使用输出作为下一层的输入
self.linear2 = torch.nn.Linear(#Direction * HS , Output_size)
# 如果想使用隐藏作为下一层的输入
self.linear2 = torch.nn.Linear(HS , Output_size)
RNN返回输出和隐藏状态。
如果使用batch_first=True,那么输出形状是(批次大小,序列长度,#方向 * 隐藏大小)。如果使用batch_first=False,那么输出形状是(序列长度,批次大小,方向数量 * 隐藏大小)。
(方向数量 * 层数,批次大小,隐藏大小),它保存有关最终隐藏状态的信息。所以大多数时候把隐藏状态作为输入传递给self.linear2。
如果正在进行回归或二元分类,那么线性变换中的output_size应该是1,如果正在进行多类分类,那么Output_size将是类别的数量。
def forward(self, input):
out, hidden_ = self.rnn(input)
#out: 选择想要传递给线性层的时间步数据
out = self.linear2(out)
out = self.linear2(hidden_)