深度學習學習筆記——RNN(LSTM、GRU、雙向RNN)

前置知識

  • 深度學習是什麼
    • 深度學習是機器學習的一個分支
    • 由全連接網絡、卷積神經網絡和循環神經網絡構成的結構
    • 多層全連接網絡:多層感知器
    • 多層卷積神經網絡
  • 卷積神經網絡基本結構
    • 數據:
      • 2D輸入數據形式:[批尺寸(batchsize),高度(H),寬度(W),通道數(特徵數)(channel)],[B, H, W, C]
      • 2D卷積核心格式:[(卷積核心大小1,卷積核心大小2),輸入通道數(特徵),輸出通道數(特徵)]
      • 1D輸入數據形式:[B, T, C]
      • 1D卷積核心格式:[K, C, C2]
      • 2D數據:圖像、雷達等
      • 1D數據:有順序的文本、信號
      • 有些人感覺鳶尾花數據是1D數據,其僅是一個向量點
    • 卷積:提取特徵,也就是濾波
    • 池化:降採樣(stride \neq 1)
    • LeNet(手寫數字識別):卷積+全連接
    • 卷積神經網絡中最重要的概念是什麼
      • 感受野(由卷積的kernel size決定)
      • 如何增加感受野
        • 增加層數
        • 降採樣
  • 深度學習中重要的觀念:
    • 向量:深度學習中所有的屬性、物體、特徵均是向量(大部分網絡均可由Numpy實現)

循環神經網絡(RNN)

RNN核心思想:可以考慮輸入數據前後文相關特徵

文本向量化

  • 常規文本向量化(不考慮順序)
    • 將整片文章轉換爲一個向量
    • 基於詞頻統計的
    • 詞袋子(Bag of words)模型
    • 此時丟失了順序的信息
    • eg:今 天 天 氣 不 好
      • 看到“好”字我們應該認爲是一個正面的情緒
      • 但是“好”前有個“不”
      • 有些人提取前後文特徵組成“詞”->N-gtam-range
      • 單個字或詞帶有信息少,需要結合前後文
  • 對順序文本進行向量化
    • 句子向量化:今 天 天 氣 不 好 。
    • 需要將字轉換爲ID:建立字典,爲每個字符賦予一個整形數字
    • 句子轉換爲:[96, 22, 22, 163, 3, 244, 1] -> [T], type:int
    • 對每個整形數字進行OneHot編碼:[T=字符數量,字符數量]
    • 可能有多個(BatchSize,B)句子,因此
    • 輸入:[B, T, 字符數量]->[B, T, 字符數量]->一維連續數據
      • 每個句子長度不同->補0->計算效率
    • 深度神經網絡僅能處理浮點型的向量,所以將每個字符均轉換爲向量
    • 但是字符數量長度太長,需要降維,乘以降維矩陣W[詞數量,降維長度]
    • 最終:[B, T , 降維長度]
    • 注意:
      • W初始取隨機值,隨後隨網絡一同訓練
      • X:[B, T, C] W[C, C2],如何相乘,張量點乘
    • 整個過程叫做Embedding
    • 將文字轉換爲向量的過程
    • 中文以“字”作爲基本單位,英文可以以“字母”或“詞”作爲基本單位

RNN 建模

  • 假設X是Embedding後的序列:[B, T, C]
  • 選取某個時間X[: , 0, :], 相當於取第1個詞[B, C]
  • 記錄xtx_t-> X[:,t,:]X[:, t, :], 所以X[:, 0, :]是x0x_0
  • 如果構建線性模型:yt=xtw+by_t = x_tw+b,實際上金相當於處理單個字符
  • 如果需要考慮前文內容,需要進行如下改動:
    • 輸入:(x0,x1,,xT)X(x_0,x_1,\dotsc,x_T)\in X
    • 狀態state:(h0,h1,,hT)H(h_0,h_1,\dotsc,h_T)\in H 其中h0h_0默認爲0
    • 輸出:(y0,y1,,yT)Y(y_0,y_1,\dotsc,y_T)\in Y
    • 因此有:
      yt=ht=tanh(concat[xt,ht1]W+b)y_t = h_t = \tanh(concat[x_t, h_{t-1}]\cdot W + b)
    • 其中xtx_t的形式爲[batchsize, features1],hth_t的形式爲[batchsize, features2]。多層rnn網絡可以在輸出的基礎上繼續加入RNN函數:

      htl=f(xt,ht1l)htl+1=f(xt,ht1l+1) h^l_t = f(x_t,h^l_{t-1})\\ h^{l+1}_t = f(x_t,h^{l+1}_{t-1})
      其中ll表示隱藏層
      Numpy實現(僅包含計算過程):
import numpy as np

# 讀取字典
word_map_file = open(r'model\wordmap','r',encoding='utf-8')

word2id_dict = eval(word_map_file.read())
word_map_file.close()

# print(word2id_dict)
# 文本向量化
B = 2 #文本數量(批次大小)
n_words = 5388
strs1 = '今天天氣不好。'
strs2 = '所以我不出門。'
strs_id1 = [word2id_dict.get(itr) for itr in strs1]
strs_id2 = [word2id_dict.get(itr) for itr in strs2]
print(f'文本1:“{strs1}”, 對應字典中的整數:{strs_id1}')
print(f'文本2:“{strs2}”, 對應字典中的整數:{strs_id2}')

# One hot編碼
T = max(len(strs1), len(strs2))               #字符串長度
C = len(word2id_dict)       #字典中的字符數量,總數
strs_vect = np.zeros([B, T, C])
for idx, ids in enumerate(strs_id1):
    strs_vect[0, idx, ids] = 1
for idx, ids in enumerate(strs_id2):
    strs_vect[1, idx, ids] = 1
# print(strs_vect)
print(f'降維前 Size:{strs_vect.shape}')

# 降維: 向量文本乘上一個矩陣[字符數量:5388,降維的維度:128(可訓練)]
enbedding_size = 128
W = np.random.normal(0, 0.1, [n_words, enbedding_size])
vect2d = np.reshape(strs_vect, [B*T, C])
out = vect2d @ W
vect = np.reshape(out, [B, T, enbedding_size])     #Embedding 過程(壓縮矩陣)
print(f'降維後 Size:{vect.shape}')

###############
##### RNN #####
###############
#初始化
hidden_size = 64
rnn_w = np.random.random([enbedding_size+hidden_size, hidden_size])
rnn_b = np.zeros([hidden_size])
state = np.zeros([B, hidden_size])

#正向計算傳播
outputs = []
for step in range(T):
    x_t = np.concatenate([vect[:, step, :], state], axis=1)
    state = np.tanh(x_t @ rnn_w + rnn_b)
    outputs.append(state)
last_output = outputs[-1] #包含前面全部信息

Tensorflow實現(僅框架,沒有傳入數據):

import tensorflow as tf

# 超參數
batch_size = 32         #  B = 32
seq_len = 100           # 文本長度, T=100
embedding_size = 128    # 降維後向量長度 C = 128
hidden_size = 128       # 隱藏層向量長度
epochs = 100            # 訓練次數

# 統計量
n_words = 5388          # 字符數量
n_class = 10            # 類別數量

# 原始數據
input_ID = tf.placeholder(tf.int32, [batch_size, seq_len])
label_ID = tf.placeholder(tf.int32, [batch_size])
# 降維數據
embedding_w = tf.get_variable('embedding_w', [n_words, embedding_size])
# 傳入模型數據
inputs = tf.nn.embedding_lookup(embedding_w, input_ID)

# 構建多層神經網絡單元
rnn_fn = tf.nn.rnn_cell.BasicRNNCell
rnn_cell = tf.nn.rnn_cell.MultiRNNCell([
    rnn_fn(hidden_size),
    rnn_fn(hidden_size)
])

# 將數據輸入循環神經網絡
outputs, last_state = tf.nn.dynamic_rnn(rnn_cell, inputs, dtype = tf.float32)

last_out = outputs[:, 0, :]
logits = tf.layers.dense(last_out, n_class, activation= None)
label_onehot = tf.one_hot(label_ID, n_class)

loss = tf.losses.softmax_cross_entropy(label_onehot, logits)
train_step = tf.train.AdamOptimizer().minimize(loss)

sess = tf.Session() 
sess.run(tf.global_variables_initializer())
for step in range(epochs):
    sess.run(train_step, feed_dict={label_ID:..., input_ID:...})

RNN 模型改進

  • RNN時間步較多時,梯度容易過大
  • RNN使用的激活函數是tanh
  • BasicRNN,容易出現“遺忘”
  • 改進的LSTM結構,長短時間記憶單元
    • 兩個向量用於保留記憶,h, c
    • 門控結構是一個加權的機制,在很多網絡中都有體現

LSTM(Long Short Term Memory)

在這裏插入圖片描述
其中:σ\sigma爲sigmod函數,tanh\tanh爲雙曲正切函數, 圖中左上角省略了輸入記憶單元Ct1C_{t-1},左上角省略了輸出記憶單元CtC_t,左下角省略了輸入狀態單元ht1h_{t-1}
公式如下:
ht=tanh{σ(concat[xt,ht1])×Ct1+σ(concat[xt,ht1])×tanh(concat[xt,ht1])}×σ(concat[xt,ht1])ct=σ(concat[xt,ht1])×Ct1+σ(concat[xt,ht1])×tanh(concat[xt,ht1]) h_t = \tanh \left \{ \sigma(concat[x_t, h_{t-1}]) \times C_{t-1} + \sigma(concat[x_t, h_{t-1}]) \times \tanh(concat[x_t, h_{t-1}]) \right \} \times \sigma(concat[x_t, h_{t-1}])\\ c_t = \sigma(concat[x_t, h_{t-1}]) \times C_{t-1} + \sigma(concat[x_t, h_{t-1}]) \times \tanh(concat[x_t, h_{t-1}])

其中:

  • 因爲σ[0,1]\sigma函數\in[0, 1],所以該部分的輸出皆作爲控制門信號
  • σ(concat[xt,ht1])\sigma(concat[x_t, h_{t-1}])爲遺忘門信號(圖中最左側的σ\sigma),判斷當前信息以及前文信息是否重要
  • 圖中中間的σ\sigma爲輸入門(σ(concat[xt,ht1])\sigma(concat[x_t, h_{t-1}])),判斷短時記憶是否重要,將短時記憶加入長時記憶中
  • 圖中中間的tanh\tanh爲Cell輸入信號(tanh(concat[xt,ht1])\tanh(concat[x_t, h_{t-1}])),其中包含短時記憶以及當前信息
  • 圖中右側的σ\sigma輸出門(σ(concat[xt,ht1])\sigma(concat[x_t, h_{t-1}])),判斷當前長時記憶與短時記憶是否重要,是否輸出
  • ct1c_{t-1}爲長時間記憶
  • ht1h_{t-1}爲短時間記憶

LSTM變形與數學表達式

在這裏插入圖片描述
其中:

  • ftf_t爲遺忘門,判斷長時記憶與短時記憶是否重要,是否需要遺忘
  • iti_t爲輸入門,判斷短時記憶是否重要,將短時記憶加入長時記憶中
  • oto_t爲輸出門,判斷當前長時記憶與短時記憶是否重要,是否輸出
    }$爲長時間記憶
  • ht1h_{t-1}爲短時間記憶

LSTM變形與數學表達式

[外鏈圖片轉存中…(img-94hEYY9w-1593923703227)]
其中:

  • ftf_t爲遺忘門,判斷長時記憶與短時記憶是否重要,是否需要遺忘
  • iti_t爲輸入門,判斷短時記憶是否重要,將短時記憶加入長時記憶中
  • oto_t爲輸出門,判斷當前長時記憶與短時記憶是否重要,是否輸出
  • CtC_t爲Cell輸入信號,其中包含短時記憶以及當前信息

門控循環單元GRU(Grated Recurrent Unit)

GRU爲簡化版本的LSTM,理論上速度會有顯著提升,具體算法:GRU

雙向RNN模型

  • 同時考慮“過去”和“未來”的信息
  • 構建兩個RNN/LSTM傳播的單元(一個正向,一個反向)
    模型構建代碼:
import tensorflow as tf  
# 超參數
batch_size = 16 
seq_len = 100 # 100個字 
emb_size = 128 # 字符向量長度 
n_layer = 2 
n_hidden = 128
# 統計量
n_words = 5388 # 多少字符
n_class = 10 # 多少類
# 定義輸入:長度爲100的字符ID序列,類型INT
# 定義標籤:每個時間步均需要做分類 
inputs = tf.placeholder(tf.int32, [batch_size, seq_len])
labels = tf.placeholder(tf.int32, [batch_size, seq_len]) 
mask = tf.placeholder(tf.float32, [batch_size, seq_len]) 
# Embedding
emb_w = tf.get_variable("emb_w", [n_words, emb_size]) #是可訓練的
inputs_emb = tf.nn.embedding_lookup(emb_w, inputs)
# inputs_emb是神經網絡輸入[batch_size(B), seq_len(T), emb_size(C)]
# 定義多層神經網絡 
# cell_fn = tf.nn.rnn_cell.BasicRNNCell  # 基本RNN 
cell_fn = tf.nn.rnn_cell.LSTMCell # LSTM
# 向前傳播的單元
cell_fw = tf.nn.rnn_cell.MultiRNNCell(
    [cell_fn(n_hidden) for itr in range(n_layer)]) 
# 反向傳播的單元
cell_bw = tf.nn.rnn_cell.MultiRNNCell(
    [cell_fn(n_hidden) for itr in range(n_layer)]) 
# 將Embedding後的向量輸入循環神經網絡中 
outputs, last_state = tf.nn.dynamic_rnn(cell_fw, inputs_emb, dtype=tf.float32)
intputs_bw = tf.reverse(inputs_emb, 1)
outputs_bw, last_state = tf.nn.dynamic_rnn(cell_bw, intputs_bw, dtype=tf.float32)
outputs_bw = tf.reverse(outputs_bw, 1)

# # 或者使用TF中tf.nn.bidirectional_dynamic_rnn()進行搭建,不許手動將輸入/輸出反向
# # 雙向RNN,seqlen用於不同長度序列解碼
# (fw_output, bw_output), state = tf.nn.bidirectional_dynamic_rnn(
#     cell_fw_cell,
#     cell_bw_cell,
#     emb_input,
#     seqlen, 
#     dtype=tf.float32
# )
# outputs = tf.concat([outputs, outputs_bw], 2)


# outputs相當於Y[batch_size, seq_len, n_hidden] 
# logits,每個時間步需要預測類別 
logits = tf.layers.dense(outputs, 4)
# 優化過程
loss = tf.contrib.seq2seq.sequence_loss(
            logits, # 網絡的輸出
            labels, # 標籤
            mask    # 掩碼 用於選取非補0區域數據,具體操作爲對補0區域乘上0權重
            )
step = tf.train.AdamOptimizer().minimize(loss) 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章