Memory Networks
正如該方法名:memory,關於神經網絡的以及功能LSTM,GRU等方法能夠實現一部分記憶功能了,但大量的實驗和研究者們都證明了LSTM在更長時間內在處理數據的時間結構方面不夠有效,並不能達到記錄更多更長的記憶效果。所以對比起來,記憶網絡更像是想要嘗試通過一些記憶組件和方法來保存一些場景或者狀態的信息,以實現更爲長期記憶功能(實際上這種大量的先驗知識對推理是非常重要的,所以某種程度上來說,memory就是一個知識的外部存儲器)。
所以就外部存儲器的角度來說,memory需要完成兩個操作:1write 2read。
對於write, 書寫操作分爲擦除和添加兩部分,公式如下,w,e,a分別是權重,擦除和添加向量,t是當前時刻,j是寫入磁頭的位置:
- 首先需要把輸入向量按上式進行編碼
- 然後存入到記憶庫的slot中。分爲直接插入,忘記某一部分,或者記憶重新組織三種方法
對於read:
- 根據當前的輸入,選擇最合適的記憶進行抽取出來(計算輸入和記憶的匹配程度,用這個“程度”加權輸出向量),代表了一次推理過程。(如果是多次推理即是把每次的輸出當下一次的輸入用,類似深層神經網絡一樣)
- 通過這些權重值,就可以預測返回得到一個輸出
訓練的損失函數則是採用margin ranking loss,這個與支持向量機的損失函數類似,即是選出最合適中間結果,和得到最好的預測輸出。
Multi Memory Layers
如果要建立多層memory,即從如下圖的左側到右側,大致思路不變,需要注意的是層與層之間可以有多種聯繫方式,使用門控單元進行記憶選擇是很自然的結果:
- Scalar based Addition:
- Global Gated Addition:
- Location-wise Gated Addition:
- Recurrent Learning based Addition:
- Attention based Addition:
多層的End-To-End Memory Networks實現代碼:
#構建一個memory network
def build_memory(self):
self.global_step = tf.Variable(0, name="global_step")
#如圖中的A,B,C三個地方的embedding
self.A = tf.Variable(tf.random_normal([self.nwords, self.edim], stddev=self.init_std))
self.B = tf.Variable(tf.random_normal([self.nwords, self.edim], stddev=self.init_std))
self.C = tf.Variable(tf.random_normal([self.edim, self.edim], stddev=self.init_std))
# Temporal Encoding
self.T_A = tf.Variable(tf.random_normal([self.mem_size, self.edim], stddev=self.init_std))
self.T_B = tf.Variable(tf.random_normal([self.mem_size, self.edim], stddev=self.init_std))
# m_i = sum A_ij * x_ij + T_A_i
Ain_c = tf.nn.embedding_lookup(self.A, self.context)
Ain_t = tf.nn.embedding_lookup(self.T_A, self.time)
Ain = tf.add(Ain_c, Ain_t)
# c_i = sum B_ij * u + T_B_i
Bin_c = tf.nn.embedding_lookup(self.B, self.context)
Bin_t = tf.nn.embedding_lookup(self.T_B, self.time)
Bin = tf.add(Bin_c, Bin_t)
#number of hops
for h in xrange(self.nhop):
self.hid3dim = tf.reshape(self.hid[-1], [-1, 1, self.edim])
Aout = tf.matmul(self.hid3dim, Ain, adjoint_b=True)
Aout2dim = tf.reshape(Aout, [-1, self.mem_size])
#計算權重P
P = tf.nn.softmax(Aout2dim)
probs3dim = tf.reshape(P, [-1, 1, self.mem_size])
Bout = tf.matmul(probs3dim, Bin)
Bout2dim = tf.reshape(Bout, [-1, self.edim])
#層與層之間的處理
Cout = tf.matmul(self.hid[-1], self.C)
Dout = tf.add(Cout, Bout2dim)
self.share_list[0].append(Cout)
#首層,尾層和中間層
if self.lindim == self.edim:
self.hid.append(Dout)
elif self.lindim == 0:
self.hid.append(tf.nn.relu(Dout))
else:
F = tf.slice(Dout, [0, 0], [self.batch_size, self.lindim])
G = tf.slice(Dout, [0, self.lindim], [self.batch_size, self.edim-self.lindim])
K = tf.nn.relu(G)
self.hid.append(tf.concat(axis=1, values=[F, K]))
總之,記憶網絡是一個組件形式的模型,每個模型相互對立又相互影響。每個組件沒有固定的模型,可以是傳統的模型,也可以是神經網絡,所以獨立的每層可以搭積木的拼接起來完成長時間記憶的任務。
Gates
所謂的門控其實就是對於兩個或多個部分,都有一個權重矩陣W來適時分配重要程度,還可以再通過一個激活函數得到。
同樣的,在RNN變體系列中門控單元用的很多,主要起一些信息過濾的作用。門控的好處在於它是可以全自適應的學習方式,即每一時刻節點的權重都由當前的輸入決定(如LSTM等的“門”,往往就是當前數據和上一時間點隱藏單元的輸入共同決定),從而信息累積的時間尺度也可以隨着輸入序列而動態改變。通過memory記憶了長時間的信息後,使用門做處理是很自然的事情,所以在前面處理多層之間的融合方法中門控單元很適用。