IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    如何理解RNN中的循环

    chenxia发表于 2023-09-06 12:31:53
    love 0

    相比较MLP和CNN,RNN结构仿佛尤其的神秘。相比较更加直观的可视化,实现端到端的输出,包含隐藏状态(Hidden state)的时序建模方式对于RNN代码的理解真是尤其的困难。虽然Torch为我们提供封装好函数,但是我更加希望去探索其封装借口背后的结构,来提高自己的理解能力。

    0x01 缘起

    相比较MLP和CNN,RNN结构仿佛尤其的神秘。相比较更加直观的可视化,实现端到端的输出,包含隐藏状态(Hidden state)的时序建模方式对于RNN代码的理解真是尤其的困难。虽然Torch为我们提供封装好函数,但是我更加希望去探索其封装借口背后的结构,来提高自己的理解能力。

    0x02 数学公式

    对于神经网络的计算方式可以考虑层层递进,包括单个神经元的前向传播、单个神经元的反向梯度下降、批量梯度下降。由此来理解它是如何使用的、如何学习的以及如何更好的工程化学习。

    2.1 基本思想

    RNN、LSTM、GRU等等这类循环神经网络更多的是参考如何应用在序列的数据中,参考沐神的解释,这一类网络结构的特点优势包括:

    1. 跨时间权重共享
    2. 时间平移不对称
    3. 适用不同长度

    这里有点太晦涩了。当我们以时间序列预测(Time series forecasting)的方式来理解一个问题,我们如何从t的状态来预测t+1,经过分析我们会知道t+1状态并不完全取决于t。比如同一支股票在相同的价格均会存在下跌和上涨的状态,因此我们会希望另外一个隐藏值H来表示之前所有序列的状态,但是这个隐藏状态可能不能太长,导致隐含信息被平滑,也不能太短到无法提供信息。因此我们需要选择合适的WINDOW-SIZE。由此我们的估计值应该包括

    yt=f(xt,ht−1)y_t = f(x_t,h_{t-1})y​t​​=f(x​t​​,h​t−1​​)

    所以存在部分量一直在被网络输入和输出,一直在循环的迭代。如果我们再抽离整个网络结构,我们会发现我们似乎在对每个步骤重复一些值的计算,只是我们并没有将其作为输出。这一类的基本思想为

    我们坚信,在相当长的序列中存在不同类别的形式。这样的参数共享可以使得模型在不同输入样本下得到更好的泛化。

    2.2 前向传播

    这个时候我们先定义我们的数据集,是长度为N的维度为D的序列组成X=(x1,x2,...,xt,..)RN∗DX=(x_1,x_2,...,x_t,..)R^{N*D}X=(x​1​​,x​2​​,...,x​t​​,..)R​N∗D​​,由此我们根据当前状态和上一状态的隐藏值更新隐藏状态,同时也根据隐藏状态和当前输入得到预测输出。

    Ht=ϕ(xtWxh+Ht−1Whh+bh)H^t = \phi(x^tW_{xh}+H^{t-1}W_{hh}+b_h)H​t​​=ϕ(x​t​​W​xh​​+H​t−1​​W​hh​​+b​h​​)

    Yt=ϕ(HtWoh+bo)Y^t=\phi(H^tW_{oh}+b_o)Y​t​​=ϕ(H​t​​W​oh​​+b​o​​)

    Untitled

    2.3 反向梯度下降

    这个网络结构看上去可以实现,看上去也非常的诱人。但是如何构造一种学习的方式来对其参数更新并且有效呢?梯度下降(Gradient Decent)已经被证明在MLP和CNN中有效,由此我们可以尝试对RNN的梯度进行分析,来判断我们可以如何来接近真值(Ground-truth)。

    \begin{split}\begin{aligned}\frac{\partial L}{\partial w_{hh}} & = \frac{1}{T}\sum_{t=1}^T \frac{\partial l(y_t, o_t)}{\partial w_{hh}} \\& = \frac{1}{T}\sum_{t=1}^T \frac{\partial l(y_t, o_t)}{\partial o_t} \frac{\partial g(h_t, \theta_o)}{\partial h_t} \frac{\partial h_t}{\partial w_{hh}}.\end{aligned}\end{split}

    但是要知道隐藏状态是会循环的,我们发现在计算梯度的时候,似乎它并不会消失,而是会一直计算下去,它可能会迭代消失,但是更多情况下是会迭代爆炸。因为这种无限可能性的存在,导致我们很容易焦虑因此在循环神经网络中往往会有的技巧(Trick)是对其梯度进行裁剪,或者是增加新的控制单元来解决正则化。具体的实现方式并不在这里赘述。

    2.4 批量梯度下降

    如果说上述是理论分析部分,下面希望是实践的角度来说明,如何确定网络构建的参数。首先定义两个基本的原则:

    1. 要明白Tensor的维度规则,是自外向内的。同时可以通过折叠维度的方式来升维和降维
    2. 矩阵乘法的过程中,MN和NQ的结果是M*Q
    3. 利用矩阵乘法广播和利用批量乘法是不一样的,因为有的三维数组并不能广播。BMN和BNQ想乘得到BMQ

    下文中变量的维度D为输入维度 input_size,其中隐藏状态维度为hidden_size,输出维度D(与输入维度相同)为output_size

    Untitled

    在使用批量梯度下降的过程中,因为同一个batch中的数据是不同的序列,因此隐藏状态并不是1h的广播,而必须是batchsizeh。因此这里并不像MLP可以使用广播。同时这里的重点也不是想得到多么好的隐藏状态h,而是具有一种可以反应正常状态的规则,也就是权重,这才是真正希望学习的参数

    Untitled

    疑问:但是在训练过程中隐藏状态都是随机产生的,虽然可能在一定时间预热(Warm up )之后得到一个比较好的效果,但是依旧很难反应他是真实有效的反应隐藏状态的?这里的梯度下降似乎没有理论依据

    注意这里还会有batch是否设置为first,这样对最终的矩阵的形状还会有不同的影响。

    0x03 时间序列预测任务

    降雨量数据预测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import torch 
    import torch.nn as nn
    import pandas as pd
    import matplotlib.pyplot as plt
    import numpy as np
    df = pd.read_csv('../input/Rainfall_data.csv')
    df.info()

    plt.subplot(4,1,1)
    plt.plot(df['Specific Humidity'])
    plt.title('Specific Humidity')
    plt.subplot(4,1,2)
    plt.plot(df['Relative Humidity'])
    plt.title('Relative Humidity')
    plt.subplot(4,1,3)
    plt.plot(df['Temperature'])
    plt.title('Temperature')
    plt.subplot(4,1,4)
    plt.plot(df['Precipitation'])
    plt.title('Precipitation')
    plt.tight_layout()
    plt.show()

    Untitled

    使用自回归进行预测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    from statsmodels.tsa.ar_model import AutoReg

    # Train the model and gets the params
    x = df['Precipitation'].values
    train, test = x[:len(x)-50], x[len(x)-50:]
    p = 12
    model = AutoReg(train, lags = p)
    model_fit = model.fit()
    print('Coefficients: %s' % model_fit.params)
    params = model_fit.params

    # Walk forward over time steps in test
    history = train[len(train)-p:]
    history = [history[i] for i in range(len(history))]
    predictions = list()
    for t in range(len(test)):
    length = len(history)
    lag = [history[i] for i in range(length-p,length)]
    yhat = params[0]
    for d in range(p):
    yhat += params[d+1] * lag[p-d-1]
    obs = test[t]
    predictions.append(yhat)
    history.append(obs)
    print('predicted=%f, expected=%f' % (yhat, obs))

    # Evaluate forecasts
    from sklearn.metrics import mean_squared_error
    error = mean_squared_error(test, predictions)
    print('Test MSE: %.3f' % error)
    plt.plot(test)
    plt.plot(predictions, color='red')
    plt.show()

    Untitled

    利用RNN进行序列预测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    print('The length of the dataset is:',len(train))
    print('The length of the test set is:',len(test))
    window_size = 12
    def input_data(seq,ws):
    output = []
    for i in range(len(seq)-ws):
    output.append(seq[i:i+ws])
    return np.array(output)

    train_set = input_data(train,window_size)
    test_set = input_data(test,window_size)
    print(train_set)

    input_tensor = torch.Tensor(train_set)
    test_set = torch.Tensor(test_set)

    class MyDataset(torch.utils.data.Dataset):
    def __init__(self, train_set):
    self.train_set = train_set

    def __getitem__(self, index):
    return self.train_set[index,:11], self.train_set[index,-1]

    def __len__(self):
    return len(self.train_set)

    train_dataset = MyDataset(input_tensor)
    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, shuffle=False)

    for x,y in train_loader:
    print(x.shape,y.shape)
    break

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    class MyRnn(nn.Module):
    def __init__(self, input_size,hidden_size,num_layers,output_size):
    '''
    input_size: number of features
    hidden_size: number of hidden units
    num_layers: number of layers
    output_size: number of output
    '''
    super().__init__()
    self.hidden_size = hidden_size
    self.num_layers = num_layers
    self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
    self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
    h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
    out, _ = self.rnn(x, h0)
    out = self.fc(out[:, -1, :])
    return out

    input_size = 1
    hidden_size = 128
    num_layers = 2
    output_size = 1

    model = MyRnn(input_size, hidden_size, num_layers, output_size).to(device)
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

    num_epochs = 5000
    for epoch in range(num_epochs):
    for x,y in train_loader:
    x.unsqueeze_(-1)
    x = x.to(device)
    y = y.to(device)
    output = model(x)
    loss = criterion(output, y.unsqueeze(1))
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    if epoch % 100 == 0:
    print('Epoch: {}, Loss: {:.5f}'.format(epoch, loss.item()/len(x)))

    Untitled

    利用RNN进行多变量时序预测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    dataset = df.iloc[:,3:7].values
    train_set = dataset[:len(dataset)-50]
    test_set = dataset[len(dataset)-50:]

    WINDOWS_SIZE = 12
    def input_data(seq,ws):
    output = []
    for i in range(len(seq)-ws):
    output.append(seq[i:i+ws])
    return np.array(output)

    train_set = input_data(train_set,WINDOWS_SIZE)
    test_set = input_data(test_set,WINDOWS_SIZE)

    input_tensor = torch.Tensor(train_set)
    test_set = torch.Tensor(test_set)

    class MyDataset(torch.utils.data.Dataset):
    def __init__(self, train_set):
    self.train_set = train_set

    def __getitem__(self, index):
    return self.train_set[index,:11], self.train_set[index,-1]

    def __len__(self):
    return len(self.train_set)

    train_dataset = MyDataset(input_tensor)
    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, shuffle=False)

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    test_dataset = MyDataset(test_set)
    test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=1, shuffle=False)

    input_size = 4
    model = MyRnn(input_size, hidden_size, num_layers, output_size).to(device)
    epochs = 5000
    for epoch in range(epochs):
    for x,y in train_loader:
    x = x.to(device)
    y = y.to(device)
    output = model(x)
    loss = criterion(output, y.unsqueeze(1))
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()


沪ICP备19023445号-2号
友情链接