深度學習面試題35:RNN梯度消失問題(vanishing gradient)


目錄

  梯度消失原因之一:激活函數

  梯度消失原因之二:初始化權重

  不同損失函數下RNN的梯度消失程度對比

  實踐中遇到梯度消失怎么辦?

  參考資料


在實踐過程中,RNN的一個缺點是在訓練的過程中容易梯度消失。

梯度消失原因之一:激活函數

 

sigmod的導函數峰值為0.25,由於反向傳播的距離越長,連乘的小數越多,所以sigmod一定會產生梯度消失,並且很嚴重。但是因為tanh的導函數峰值為1,所以tanh造成的梯度消失的程度比sigmod更小。這一段的結論應該是比較簡單易懂的。

那么Relu類的激活函數呢?

先給出我的理解,Relu不是解決梯度消失的問題充分條件。分析如下:

雖然Relu的導函數在x>0的區域等於1,好像再怎么連乘都不會讓累計梯度變小。但是這里忽略了在x<=0的區域導函數是等於0的情況,那就意味着和他連接的淺層邊上的梯度直接就歸0了,那就是梯度消失了。

我們畫圖來分析上面的問題,假設有如下網絡

這里假設要計算w1的梯度值,其導函數為

由鏈式法則可知損失函數對淺層網絡參數的導數應該是多個梯度值的累和,由Relu的導函數的性質可知,z3,z4,z1如果小於等於0,都會影響到w1的梯度值,且z1的影響程度最大,一旦z1小於等於0,那么w1的梯度就直接歸0,這就會造成梯度消失。

 返回目錄

 

梯度消失原因之二:初始化權重

 考略下圖,如果初始化的模型參數比較大,不要說梯度消失了,可能梯度都爆炸了;但是如果權重參數太小,那也會引起梯度消失。

 返回目錄

 

不同損失函數下RNN的梯度消失程度對比

從下圖可以看到,在RNN中,隨着網絡層數的變淺參數梯度越來越小,即梯度很難傳到淺層的位置

對應代碼

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

torch.manual_seed(5)
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用來正常顯示中文標簽
plt.rcParams['axes.unicode_minus'] = False  # 用來正常顯示負號


class RNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, active_function):
        super(RNN, self).__init__()

        self.hidden_size = hidden_size
        self.active_function = active_function
        self.i2h0 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2h1 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2h2 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2h3 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2h4 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2h5 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2h6 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2h7 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2h8 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2h9 = nn.Linear(input_size + hidden_size, hidden_size, bias=False)
        self.i2o = nn.Linear(hidden_size, output_size, bias=False)

    def forward(self, input, hidden):
        combined = torch.cat((hidden, input[0]), 1)

        hidden = self.active_function(self.i2h0(combined))
        combined = torch.cat((hidden, input[1]), 1)
        hidden = self.active_function(self.i2h1(combined))
        combined = torch.cat((hidden, input[2]), 1)
        hidden = self.active_function(self.i2h2(combined))
        combined = torch.cat((hidden, input[3]), 1)
        hidden = self.active_function(self.i2h3(combined))
        combined = torch.cat((hidden, input[4]), 1)
        hidden = self.active_function(self.i2h4(combined))
        combined = torch.cat((hidden, input[5]), 1)
        hidden = self.active_function(self.i2h5(combined))
        combined = torch.cat((hidden, input[6]), 1)
        hidden = self.active_function(self.i2h6(combined))
        combined = torch.cat((hidden, input[7]), 1)
        hidden = self.active_function(self.i2h7(combined))
        combined = torch.cat((hidden, input[8]), 1)
        hidden = self.active_function(self.i2h8(combined))
        combined = torch.cat((hidden, input[9]), 1)
        hidden = self.active_function(self.i2h9(combined))
        output = self.active_function(self.i2o(hidden))

        return output, hidden

    def initHidden(self):
        # return torch.zeros(1, self.hidden_size)
        w = torch.empty(1, self.hidden_size)
        nn.init.kaiming_uniform_(w, mode='fan_in', nonlinearity='relu')
        return w


def train(category_tensor, input_tensor):
    hidden = rnn.initHidden()
    rnn.zero_grad()

    output, hidden = rnn(input_tensor, hidden)

    loss = criterion(output, category_tensor)
    loss.backward()

    # Add parameters' gradients to their values, multiplied by learning rate
    lst_params = list(rnn.parameters())[:10]  # 只獲取i2h的參數
    lst_x = []
    lst_y = []
    for i, p in enumerate(lst_params):
        # print("梯度值", p.grad.data)
        grad_abs = np.abs(np.array(p.grad.data))
        # np_greater_than_0 = grad_abs.reshape(-1)
        np_greater_than_0 = grad_abs
        # np_greater_than_0 = np_greater_than_0[np_greater_than_0 > 0]
        print(np.max(np_greater_than_0))
        grad_abs_mean_log = np.log10(np_greater_than_0.mean())
        grad_abs_var_log = np.log10(np_greater_than_0.var())
        # print("倒數第{}層的梯度張量絕對值的均值取對數為{},方差取對數為{}".format(i + 1, grad_abs_mean_log, grad_abs_var_log))
        lst_x.append(i + 1)
        lst_y.append(grad_abs_mean_log)
        p.data.add_(p.grad.data, alpha=-learning_rate)

    return output, loss.item(), lst_x, lst_y


if __name__ == '__main__':
    input_tensor = torch.randn(10, 1, 100)
    input_size = input_tensor.shape[-1]
    hidden_size = 200
    output_size = 2
    # rnn0 = RNN(input_size, hidden_size, output_size, torch.relu)
    # init_weight = rnn0.i2h0._parameters["weight"].data
    init_weight = torch.randn(200, 300)/15

    active_function = torch.relu
    active_function = torch.tanh
    active_function = torch.sigmoid
    rnn = RNN(input_size, hidden_size, output_size, active_function)
    # init_weight = rnn.i2h0._parameters["weight"].data
    rnn.i2h1._parameters["weight"].data = init_weight
    rnn.i2h2._parameters["weight"].data = init_weight
    rnn.i2h3._parameters["weight"].data = init_weight
    rnn.i2h4._parameters["weight"].data = init_weight
    rnn.i2h5._parameters["weight"].data = init_weight
    rnn.i2h6._parameters["weight"].data = init_weight
    rnn.i2h7._parameters["weight"].data = init_weight
    rnn.i2h8._parameters["weight"].data = init_weight
    rnn.i2h9._parameters["weight"].data = init_weight

    criterion = nn.CrossEntropyLoss()
    learning_rate = 1
    n_iters = 1
    all_losses = []
    lst_x = []
    lst_y = []
    for iter in range(1, n_iters + 1):
        category_tensor = torch.tensor([0])  # 第0類,啞編碼:[1, 0]
        output, loss, lst_x, lst_y = train(category_tensor, input_tensor)
        print("迭代次數", iter, output, loss)

    print(lst_y)
    lst_sigmod = [-9.315002, -8.481065, -7.7988734, -7.133273, -6.412653, -5.703941, -5.020198, -4.441074, -3.7632055, -3.1263535]
    lst_tanh = [-3.5717661, -3.4407198, -3.1482387, -2.968598, -2.7806234, -2.58508, -2.4179213, -2.3331132, -2.164275, -2.0336704]
    lst_relu = [-4.169364, -4.0102725, -3.6641762, -3.505077, -3.2865758, -3.089403, -2.8985455, -2.762998, -2.503199, -2.368149]

    plt.plot(lst_x, lst_sigmod, label="sigmod")
    plt.plot(lst_x, lst_tanh, label="tanh")
    plt.plot(lst_x, lst_relu, label="relu")
    plt.xlabel("第i個時間步")
    plt.ylabel("梯度張量絕對值的均值取對數")
    plt.title("調研:不同激活函數下梯度消失的程度")
    plt.legend(loc="lower left")
    plt.show()
View Code

 返回目錄

 

實踐中遇到梯度消失怎么辦?

注意:梯度消失不一定就代表網絡就不能學習!如果不是太深的網絡(針對RNN來講就是時間步較少),即使存在梯度消失的問題,還是可以訓練的,只不過時間會久一些。

在實踐過程中我們可以選用Relu系列的激活函數(畢竟導函數有等於1的區域,就意味着每次迭代都能讓一些較淺層的參數得到較大的梯度值,可以這樣理解,他的通透性比較強,但是每一輪更新只有一些位置都能通過去,循環起來效果還是可以的),並且合理的初始化模型參數(不能太小,但也不能達到讓梯度爆炸的程度)

在訓練深層網絡的時候,直接訓練肯定沒戲,淺層梯度基本都等於0了,CNN可以使用帶有跳躍連接的模塊,比如ResNet;RNN可以使用LSTM的結構,這個在以后的內容中再細談。

 返回目錄

 

參考資料

https://zhuanlan.zhihu.com/p/33006526?utm_source=wechat_session&utm_medium=social&utm_oi=829090756730970112&utm_content=first

https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial

 返回目錄

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM