卷積和循環神經網絡中的操做都是一次處理一個局部鄰域,在這篇文章中,做者提出了一個非局部的操做來做爲捕獲遠程依賴的通用模塊。python
受計算機視覺中經典的非局部均值方法啓發,咱們的非局部操做計算某一位置的響應爲全部位置特徵的加權和。並且,這個模塊能夠插入到許多計算機視覺網絡架構中去。網絡
在深度神經網絡中,捕獲遠程依賴很是重要。卷積神經網絡依靠大的感知野來對遠程依賴建模,這是經過重複疊加捲積塊來實現的。但同時,它也有一些限制。首先,它在計算上效率低下。其次,它會致使須要仔細解決的優化難題。最後,這些挑戰使得多跳依賴性建模很是困難,例如,當須要在遠程位置之間來回傳遞消息時。架構
假設有一個 3×3 的卷積核,那咱們的每個激活值就只與這 9 個點的值有關,因此也就是局部(local)的。所謂非局部(non-local),也就是說,每個激活值可能不至與它相鄰近的點有關,還可能與離它比較遠的點也有關。好比下面第一幀的球,就與後兩幀的球相關。框架
所以,做者提出的非局部操做計算某一位置的響應爲全部位置特徵的加權和,這些位置能夠是空間的、時間的或者時空的。函數
使用非局部操做有幾個優勢:(a)與循環和卷積操做的漸進行爲相反,非本地操做經過計算任意兩個位置之間的相互關係直接捕獲遠程依賴性,而無論它們的位置距離如何;(b)非局部操做很是有效,即便只有幾層(例如5)也能達到最佳效果;(c)最後,非本地操做保持可變的輸入大小,而且能夠很容易地與其餘操做組合(例如卷積)。學習
咱們要計算輸出位置 i 的響應,j 枚舉了全部的位置,函數 f 則計算 i 和全部 j 的關係,最後再進行一個歸一化。由於 j 考慮了全部可能的位置,因此這個操做是非局部的。而卷積操做只考慮某一個區域,循環操做只考慮當前和最近的時間序列,它們都是局部的。至於全鏈接,它的權重是學習到的,不是 i 和 j 的一個函數關係。此外,上面的公式能夠接受不一樣的輸入大小,而且能保持輸出大小和輸入一致,而全鏈接則必須固定輸入和輸出大小。優化
固然了,f 和 g 能夠有不一樣的選擇。可是實驗發現,模型對 f 和 g 的選擇並不敏感,也就是說通用的非局部操做纔是改進的關鍵。code
爲了簡化,g 只考慮線性形式:blog
W 是要學習的權重,這也就能夠經過一個 1×1 的卷積來實現。get
函數 f 則有多種選擇:
高斯函數是最多見的,歐氏距離也一樣適用,不過點積在深度學習平臺實現則更友好。歸一化則採起:
另外一種則是在嵌入空間使用高斯函數:
採起兩個映射:
歸一化則與上面保持同樣。給定 i,上面的公式實際上也就是沿着 j 方向的一個 Softmax:
爲了說明 Softmax 並非必須的,做者還提出了另外兩個選擇,它們的歸一化用位置 j 的數量 N 來實現。
做者進而將上面的方程包裝到一個非局部塊,從而能夠方便地插入到現有的框架中去,一個局部塊定義以下:
’+‘ 是引入了殘差鏈接,這樣將非局部塊插入到現有訓練好的模型中後,若是權重係數初始化爲零,就能夠維持原狀態不變。
整個流程如上圖所示,爲了減少計算,引入了瓶頸結構,先將通道數減爲一半。除此以外,還能夠對 x 進行空間下采樣,也就是在上圖中的 φ 和 g 後面加入最大池化,這樣能夠進一步提升效率。其一個 TensorFlow 實現以下:
def NonLocalBlock(input, subsample=True): """ @Non-local Neural Networks Non-local Block """ _, height, width, channel = input.get_shape().as_list() # (B, H, W, C) theta = tf.layers.conv2d(input, channel // 2, 1) # (B, H, W, C // 2) theta = tf.reshape(theta, [-1, height*width, channel // 2]) # (B, H*W, C // 2) phi = tf.layers.conv2d(input, channel // 2, 1) # (B, H, W, C // 2) if subsample: phi = tf.layers.max_pooling2d(phi, 2, 2) # (B, H / 2, W / 2, C // 2) phi = tf.reshape(phi, [-1, height * width // 4, channel // 2]) # (B, H * W / 4, C // 2) else: phi = tf.reshape(phi, [-1, height * width, channel // 2]) # (B, H*W, C // 2) phi = tf.transpose(phi, [0, 2, 1]) # (B, C // 2, H*W) f = tf.matmul(theta, phi) # (B, H*W, H*W) f = tf.nn.softmax(f) # (B, H*W, H*W) g = tf.layers.conv2d(input, channel // 2, 1) # (B, H, W, C // 2) if subsample: g = tf.layers.max_pooling2d(g, 2, 2) # (B, H / 2, W / 2, C // 2) g = tf.reshape(g, [-1, height * width // 4, channel // 2]) # (B, H*W, C // 2) else: g = tf.reshape(g, [-1, height * width, channel // 2]) # (B, H*W, C // 2) y = tf.matmul(f, g) # (B, H*W, C // 2) y = tf.reshape(y, [-1, height, width, channel // 2]) # (B, H, W, C // 2) y = tf.layers.conv2d(y, channel, 1) # (B, H, W, C) y = tf.add(input, y) # (B, W, H, C) return y
對比了幾種 f 的選擇,發現它們對模型的表現並無起到決定性的做用,最根本的仍是非局部的思想對實驗結果有提高。
在第二三四個殘差塊以後加入非局部塊效果類似,都有比較明顯的提高;但在第五個殘差塊以後添加的話,效果沒有以前那樣明顯。做者猜想多是最後一個殘差塊的空間分辨率比較小隻有 7×7,不足夠提供精確的空間信息。
添加更多的非局部塊,模型的表現相對來講會更好。
獲取更多精彩,請關注「seniusen」!