1. 程式人生 > 實用技巧 >深度學習面試題35:RNN梯度消失問題(vanishing gradient)

深度學習面試題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[::-1]): # 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.sigmoid active_function = torch.tanh active_function = torch.relu 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 = [-3.1263535, -3.7632055, -4.441074, -5.020198, -5.703941, -6.412653, -7.133273, -7.7988734, -8.481065, -9.315002] lst_tanh = [-2.0336704, -2.164275, -2.3331132, -2.4179213, -2.58508, -2.7806234, -2.968598, -3.1482387, -3.4407198, -3.5717661] lst_relu = [-2.368149, -2.503199, -2.762998, -2.8985455, -3.089403, -3.2865758, -3.505077, -3.6641762, -4.0102725, -4.169364] 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

返回目錄