this is a test post

译者注:
This post is a Chinese translation of Tim Dettmers‘s original post — “Understanding Convolution in Deep Learning“.
这篇文章是发布于Tim Dettmers博客上的文章”Understanding Convolution in Deep Learning“的中文翻译,因为原文章较长,我将分两次来翻译,下面是第一部分。


理解深度学习中的卷积(一)(Understanding Convolution in Deep Learning, Part 1)

卷积是深度学习中最为重要的概念。卷积和卷积网络将深度学习推向了几乎所有机器学习任务的前沿阵地,是什么令卷积具有如此强大的能力呢?它又是如何工作的呢?本篇博客将解释卷积以及其他帮助读者全面理解卷积的概念。

目前网上有很多关于卷积的博客文章,但是我发现其中涉及过多的数学公式,并且这些数学的东西并没有帮助读者理解卷积,然而增加的困惑。本篇博客也会有一些必要的数学公式,但是我会从概念的角度出发来讲解这些公式,并且提供易于理解的图表来帮助读者理解。本文的第一部分适用于任何希望了解深度学习中卷积以及卷积网络的读者。文章的第二部分包含了对卷积的更加深入理解,希望对深度学习研究者和专家有所帮助。

什么是卷积

本文就是为了回答这个问题,但是我们不妨先来管中窥豹的了解一下卷积。

我们可以把卷积想象为信息的糅合:比如将两个装满了信息的“桶”,我们它们按照一定的规则倒入到一个桶当中。每一“桶”信息中都有其独特“配方”来描述自己的信息如何与其他“桶”中的信息糅合。因此,我们可以将卷积看作两个信息源进行有序交织的过程。

卷积可以从数学角度来描述,事实上,它就像加、乘、除一样是一种数学运算,只是其形式更加复杂而已。在物理以及工程领域,卷积在简化一些复杂公式的方面,显得尤为有用。在本文的第二部分,我们首先回顾卷积的数学发展,并且将结合科学和深度学习领域来展开对卷积的更加深入的理解。但是,首先还是让我们来看一个实际的例子吧。

如何对图像进行卷积

当我们对图像进行卷积的时候,也就是在图像的宽度和高度两个维度进行卷积。我们将两个“桶”里的信息进行混合:第一个“桶”是输入图像,它由三个矩阵组成,分别代表了红、蓝、绿三个颜色通道,每个颜色通道中的任意一个像素是一个0-255的整数。第二个“桶”是我们的卷积核,一个由浮点数组成的矩阵,这个矩阵中的每一个元素,以及这个矩阵的大小都可以认为是其与图像信息进行交织时使用的“配方”。卷积核的输出就是被卷积以后的图像,在深度学习中该图像常常被称作特征图。每个颜色通道都会生成一个特征图。

边缘检测

下面,我们实际的将图像信息和卷积核通过卷积操作进行信息交织。一种进行图像卷积的方式是在图像中选取一个和卷积核一样大小的图像块,比如我们图像大小为100$\times$100,卷积核为3$\times$3,那么取3$\times$3的图像块,然后将图像块和卷积核中对应元素相乘,所有乘积之和就产生了特征图中的一个像素值。计算得到特征图中一个像素值以后,我们取相邻的下一个图像块,并进行下一次计算。当输入图像中所有图像块都进行了卷积运算,也就是特征图中的所有像素值都得到时,输入图像和卷积核的卷积计算就完成。下面所示的gif图中,我们可以看到一个图像块和卷积核进行卷积运算的过程。

卷积过程

我们可以从上图中看到,卷积过程中,输出值根据卷积核的大小进行了归一化。该过程保证了输入图像灰度值之和与特征图的灰度值之和一样。

图像卷积在深度学习中的作用

图像信息中有很多的多余信息, 对我们待提取的关键信息造成了干扰。我和Jannek Thomas在Burda Bootcamp所做的一个项目就是一个很好的例子。Burda Bootcamp是一个具有黑客马拉松氛围的快速原型搭建实验室。与我另外九个同事一道,我们在两个月创造了11个产品。我的其中一个项目就是利用深度自编码来构建时尚产品搜索:用户上传一个流行产品的图形,我们就用自编码的方式来找到相似的产品。

现在想象一下,如果你想区别两种不同款式的衣服,那么衣服的颜色似乎就不是那么重要的信息;同样的衣服品牌标记也不是那么重要。相比而言,衣服的形状应该是更加重要的信息。比如,衬衫和T恤、夹克、裤子的形状就有很大的差异。那么,如果能够从图像中滤除掉这些不必要的信息,使我们的算法不会被诸如颜色、品牌标记等这些信息干扰的话,将是极好滴!事实上,利用卷积方法,我们可以很好的实现这一目标。

我的同事,Jannek Thomas同志利用Sobel边缘检测算子对图像进行了预处理(与上图中卷积核类似)来滤除除了衣服形状以外的信息——这就是为什么卷积也被称作滤波器(后文将更加详细介绍滤波器的定义)。经过滤波得到的特征图对我们区别不同类型的衣服非常的有用,因为其他不必要的信息都被滤掉了。
2015/12/19 16:51:50

我们可以更进一步的思考上面的问题,如果有很多的卷积核,就可以生成不同的特征图。比如,对图像进行锐化(得到更多的细节),或者对图像进行模糊(得到更少的细节),不同的特征图就可能帮助我们的算法更好的工作(比如识别你的夹克衫是三个扣子还是两个扣子)

上面我们所描述的过程——输入,对输入进行变换,得到变换结果并将其交给后续算法继续处理——叫做“特征提取”。实现高效的特征提取的并不容易,目前也没有很多文献资料可以让我们提高这方面的水平。因此,很少有人能够针对不同的算法任务设计出好的特征提取方法。特征提取更多涉及到实际经验,它的难点就在于,对于不同类型的数据、不同的问题,需要不同的特征:图像特征提取中使用的方式方法在序列数据中就很可能毫无用处;就算两个很相似的图像问题,如果图像中的物体不一致的话,在一个问题中好用的特征在另一个问题中并不一定好用。总之,提取游泳的特征并不容易,很多时候需要经验。

因此,特征提取并不是容易的事情,针对每个不同的问题,我们都几乎需要从零开始来设计好的特征提取方法。那么我们不禁要想,能不能自动的找到好的特征提取方式呢?在图像问题中,这个问题就是自动找到“对”的卷积核。

卷积网络

卷积网络就能自动提取特征!我们首先将卷积核中的各个元素参数化并通过训练的方法得到其具体的值,随着我们不断的训练,这些卷积核提取我们需要特征的能力就会变得越来越强大。这个过程是自动完成的,我们称之为“特征学习”。特征学习的过程对每个不同的问题是一般化的:为了找到针对新问题的滤波器,我们只需要重新训练我们的网络就可以。这就是为何卷积网络如何强大——我们不在需要进行手工设计特征提取的方法。

一般的,在卷积网络中,我们同时学习的是层次化的多个卷积核,而不是简单一个卷积核。比如,对一个256$\times$256大小的图像使用32$\times$16$\times$16卷积核,将会产生32个241$\times$241大小的特征图(这是标准大小,特征图的大小根据卷积核的大小不同而不同,特征图大小 = 图像大小 - 卷积核大小 + 1)。因此,通过训练,我们可以自动得道32个新的卷积核,其中每个特征卷积核都能为我们所要解决的问题提供相关信息。这些特征图,将作为输入被下一级别的卷积核滤波,并继续产生相应的特征图。只要我们将所有层次的卷积核都通过学习得到,我们就可以以此组建一个全连结的神经网络,并利用该神经网络来对图像进行分类了。以上就是关于卷积网络的基础概念了(池化也很重要,但是可能需要新开一篇文章来写咯)。

CIFAR-10数据库由60000个32$\times$32的彩色图像组成,一般50000个用作训练,10000个用作测试。这60000个图片共包含了10类。CIFAR-10的库作者已经将数据打包成了一定的标准格式方便各位筒子的使用。在Python下,这个数据库被划分为了6个batch,每个batch包含一万张图像,并以numpy array的形式存储。这个10000$\times$3072大小的array,每一行的数据代表了一张图像,其中,这一行中3072个数据的前1024个数据为该32$\times$32图像的red通道,而随后的1024个数据为green通道,最后的1024个数据为blue通道。

在Python中读入这些数据的方式如下:

1
2
3
4
5
6
def unpickle(file):
import cPickle
fo = open(file, 'rb')
dict = cPickle.load(fo)
fo.close()
return dict

CS231n这个课程中提供的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import cPickle as pickle
import numpy as np
import os

def load_CIFAR_batch(filename):
""" load single batch of cifar """
with open(filename, 'rb') as f:
datadict = pickle.load(f)
X = datadict['data']
Y = datadict['labels']
X = X.reshape(10000, 3, 32, 32).transpose(0,2,3,1).astype("float")
Y = np.array(Y)
return X, Y

利用Cpickle读入数据后,对数据进行了转换:

1
X = X.reshape(10000, 3, 32, 32).transpose(0,2,3,1).astype("float")

reshape用于对数组尺寸进行改变,上面reshape(10000, 3, 32, 32)首先将这个10000$\times$3072数组均分成10000份,也就是1行1份,然后每份在继续分成3份,也就是R、G、B一个通道一份,然后再将这一份分为32$\times$32表示。我们可以用下图来理解这一步骤,图中,蓝色圆圈内数字为相应的某维度/轴。

Python Reshape and Transpose.jpg-47.7kB

而transpose(0,2,3,1)函数的意义则是生成矩阵进行转置,新矩阵的0,1,2,3维度/轴分别为原矩阵的0,2,3,1维度/轴,从下面的图中我们就可以清楚看到矩阵维度/轴的变换。

Python Reshape and Transpose.png-47.5kB

经过这样变换,我们可以很方便的获取想要的图像和图像中对应像素的值。

我们首先来了解一下,什么是SSH KEY?

SSH 密钥对总是成双出现的,一把公钥,一把私钥。公钥可以自由的放在您所需要连接的 SSH 服务器上,而私钥必须稳妥的保管好。

所谓”公钥登录”,原理很简单,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。这样子,我们即可保证了整个登录过程的安全,也不会受到中间人攻击。

当我们在本地需要用不同的账户对Github上不同的项目进行提交,那么就涉及到在本地使用多个SSH KEY了,生成一个SSH Key的方法如下:

ssh-keygen -t rsa -C "mailname1@xxx.com"  

上面这个email地址就是你在github上注册该账号时使用的email,系统会提示你,是否将key保存在 ~/.ssh/id_rsa 这个文件中,如果这是你第一次生成SSH KEY,并且也你也只有一个账号,那么就可以存储到这个默认的位置,如果有多个账号的话,建议保存为~/.ssh/id_rsa_emailname1,方便以后识别。

如果有多个Github的账号,那么继续生成一个SSK Key,并保存在~/.ssh/id_rsa_emailname2这个文件下。

ssh-keygen -t rsa -C "mailname2@xxx.com"  

当生成了N个账号的SSH KEY并保存到相应的文件中时,在~/.ssh文件夹下建立一个config文件,该文件用来指明,当使用某个账号时对应使用哪个SSH KEY

1
2
3
4
5
6
7
8
9
Host github.com_mailname1
Hostname github.com
User git
IdentityFile ~/.ssh/id_rsa_mailname1

Host github.com_mailname2
Hostname github.com
User git
IdentityFile ~/.ssh/id_rsa_mailname2

为了在版本库提交时,能够通过相应的Host找到与之对应的SSH KEY,我们还需要对版本库中的配置文件.git/config进行修改。比如,某个版本库使用的账号是mailname1这个邮箱地址注册的,那么我们就要将其配置文件中原来的

url = git@github.com:xxxxxxxxx

修改为

url = git@github.com_mailname1:xxxxxxx

这样,当我们提交这个版本库时,版本库会根据github.com_mailname1对应到~/.ssh/config文件中的相应HOST,并使用相应的SSH KEY。