在过去十年左右的时间里,主要源于深度学习的进展,计算机视觉取得了巨大的进步。不仅是因为我们有了更新和更好的算法,数码相机和互联网为我们提供了近乎无限的训练数据。更重要的是,原本为计算机游戏所开发的显卡被发现在训练深度神经网络方面具有超强的计算能力。
对于想要尝试深度学习和图像识别的人来说,这些都是好消息。如今开发一个猫与狗的分类器只需要Keras库和一个有100行代码的Python notebook文件。但只这样做并不能告诉我们计算机看到了什么。
如果我们想要了解人类是如何看的,我们可以打开头骨,然后试图弄清楚信息如何从眼睛的感光细胞通过视觉皮层流向大脑的其他部分,不过这是相当困难的。更简单的方法是用电极刺激受试者的大脑直到他们能看到蓝色。那么,我们如何用电极刺激神经网络呢?
现代的神经网络通常由堆叠在彼此之上的很多神经层组成。要识别的图像被送入最低层,当信息通过网络传播时,图像的表征变得越来越抽象,直到传播到末端,这时结果标签出现。神经网络说,我看到了一只猫!
用电极探测神经网络可以被归结为反向运行上述的过程。我们不是给神经网络显示图片并询问它看到了什么,而是要给神经网络一些噪音并要求神经网络改变以便让特定的神经元具有最大激活输出。具有这样表征的图像代表了这个特定的神经元能看到的东西,就如同我们刺激那个神经元,人类会看到什么。
让我们用Keras导入一个预先训练好的图像识别神经网络:
model = vgg16.VGG16(weights=’imagenet’, include_top=False)
model.summary()
第二条语句会显示这个神经网络的架构。
我们定义一个优化给定神经元输出的损失函数,然后创建一个迭代Keras函数,通过改变输入图像来优化它。然后我们从充满噪声的图像开始迭代运行16次。(本文中提到的所有代码在GitHub上都可以找到,既有可独立运行的脚本,也有Python notebook文件。请参阅本文末尾的参考资料。)
loss = K.mean(layer_output[:, :, :, neuron_idx])
grads = K.gradients(loss, input_img)[0]
iterate = K.function([input_img], [loss, grads])
img_data = np.random.uniform(size=(1, 256, 256, 3)) + 128.
for i in range(16):
loss_value, grads_value = iterate([img_data])
img_data += grads_value * step
我们可以在一些随机挑选的神经元上尝试这个,我们看到一些看起来似乎关联到这些神经元的人造模式:
很漂亮!看起来并不像我想象的脑部手术引起的幻觉,但它是一个开始!这些神经元是随机地从层中挑选出来的。请记住,当我们浏览这些堆叠起来的层时,随着层级的增加,抽象级别应该会上升。我们现在可以做的是从不同层的神经元抽样,并按层排序。这样我们就可以了解每个层发现的特征了:
这与我们的直觉完美地吻合,即当我们遍历层时抽象程度会增加。最底层会识别颜色和简单的图案。层级越高,模式会变得越复杂。
不幸的是,这个技巧有很多的局限。如果你看代表猫咪的最高层中的神经元,尽管你可以优化你想要的所有,但并没有明显的猫的样子会出现。
但是,我们可以通过在运行优化函数时放大图像来获得更多的细节。这里的想法是,如果你为神经元激活进行图像优化,它往往会陷入局部最小值,因为没有好的方法来影响图像的整体结构。因此,我们不是从大的完整的图像开始,而是使用我们优化的小图像(64×64)开始。然后,我们将图像放大一点并再次进行优化。这样做20次能给我们一个很好的结果,它具有一定的可塑性。
这个放大的效果自身在某种程度上是挺好的,就像某种有机展开一样。
图片达到一定的尺寸后,我们就需要开始做中心裁剪,让图像始终保持相同的大小。这个视频有一个很好的分形般的迷人效果。但为什么要停在这里?我们可以在缩放时循环观察一组随机神经元,这样能制作出相当刺激的电影:
在这篇博文中,我们已经看到了一些可视化神经网络能看到什么的技巧。可视化的结果很有趣,并且自身在某种程度上也是吸引人的。但它们也帮助我们了解了计算机视觉的工作原理。当信息流过神经网络时,抽象程度会随着层级的升高而增加,我们可以在某种程度上展示这些抽象。
Notebook版本的代码可以在这个GitHub里找到。脚本化的版本可以在GitHub里找到。