卷积和循环神经网络中的操做都是一次处理一个局部邻域,在这篇文章中,做者提出了一个非局部的操做来做为捕获远程依赖的通用模块。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」!