注:本系列是基于参考文献中的内容,并对其进行整理,注释形成的一系列关于深度学习的基本理论与实践的材料,基本内容与参考文献保持一致,并对这个专题起名为“利用Theano理解深度学习”系列,若文中有任何问题欢迎咨询。本文提供PDF版本,欢迎索取。
“利用Theano理解深度学习”系列分为44个部分,其中第一部分主要包括:
卷积神经网络(Convolutional Neural Networks, CNN)是多层感知机MLP模型的一个变种,主要是受到生物学的启发。从Hubel和Wiesel早期的有关猫的视觉皮层的工作中我们知道猫的视觉皮层包含了一个复杂的细胞排列。这些细胞对小范围的视觉区域敏感,这样的小范围的视觉区域称为一个感受野(receptive field)。这些小的区域平铺开来可以盖满整个视觉区域。这些细胞就像是一个个的局部过滤器,可以对输入空间的信息进行过滤,并且非常适合在自然图像中发觉在空间上存在极强的局部相关性。
两种基本类型的细胞已经被确认:1、简单的细胞。这些细胞在他们的感受野中最大化的响应特定的类似边缘的模式;2、复杂的细胞。这些细胞拥有较大的感受野,而且对于确切的位置模式是局部不变的。
在现实世界中,动物的视觉皮层是最强大的视觉处理系统,似乎模仿其行为也变得很自然。因此,许多受神经系统启发的模型被发现,如NeoCognitron,HMAX和LeNet-5。
卷积神经网络CNN的特点主要有:1、稀疏连接(局部感受),2、共享权值,3、子采样。其中,稀疏连接主要是通过对数据中的局部区域进行建模以发现局部的一些特性;共享权值的目的为了简化计算的过程,使得需要优化的参数变少;子采样的目的是在解决图像中的平移不变性,即所要识别的内容与其在图像中的具体位置无关。
CNN通过在邻接层的神经元之间使用局部连接模式来发现输入特征在空间上存在的局部相关性。如下图所示,第mm层的隐层节点的输入是第m−1m-1层的神经单元的一个子集,这个子集中的神经单元在空间上是连续的,具有连续的感受野,如下图所示:
假设第m−1层是输入层。在上图中,第m层的神经单元具有在输入层上宽度为3的感受野,因此只连接输入层上的3个邻接神经元。在m+1层的神经单元与第m层的神经元之间具有相似的连接特性,即第m+1层的神经单元在第mm层上的感受野的宽度也是3,但是他们关于输入层m−1层的感受野却变大了,如上图中为5。每一个单元对于感受野范围之外的其他的神经单元是没有反应的。这样的设计架构就保证了学习到的“过滤器”能够对局部空间上的输入产生最强烈的反应。
然而,正如上面所示,堆叠如此多的层导致这些非线性的“过滤器”变得急剧的“全局化”,例如,对更大的像素空间区域产生响应。在m+1个隐含层单元上可以对宽度为55的非线性特征进行编码。
在CNN中,每一个过滤器hih_i在整个感受野中重复。这些重复的单元共享同样的参数(权重向量和偏置),然后形成一个特征映射。
在上图中有3个隐含层单元,这些隐含层单元都隶属于同样的特征映射。在上图中,对于同样颜色的特征映射的权重是共享的。梯度下降同样可以被应用于求解这些共享的参数,只是需要对原始的算法进行稍微的修改。共享的权重的梯度只是简单的将每一个共享的权重的梯度相加。
每一个单元重复这样的方法。此外,权值共享可以极大的缩减需要学习的参数的数量,这样就可以加速学习的速度。在模型上的这个约束可以使得CNN在视觉问题上可以获得更好的泛化能力。
在CNN中,另一个比较重要的概念是池化,在这里使用的是最大池化max-pooling,这是非线性下采样的一种形式。max-pooling将输入图像划分成为一系列的不重叠的正方形区域,然后对于每一个子区域,输出其中的最大值。
对于机器视觉而言,max-pooling的策略是非常有效的,主要有两个原因:
特征映射的过程是通过对整个图像的局部区域重复应用一个函数得到的,即对于一个输入图像进行卷积操作指的是利用一个线性过滤器,加上一个偏置项,之后再利用一个非线性函数。
稀疏连接,卷积操作和最大池化是LeNet型的卷积神经网络模型的关键,然而,对于模型的具体细节会差别很大。下图展示的是LeNet模型:
下层主要是由卷积层和最大池化层交替形成的。上层是全连接的,对应为传统的MLP(即隐含层+Logistic回归)。第一个全连接层的输入是所有特征平铺后组成的,这些特征有下层映射得到。
从实现的角度来看,这就意味着哎下层操作的是是44维的张量,为了使得这些张量能够被后续的MLP处理,故徐将其按照指定的大小平铺称为22维的矩阵形式。
CNN的训练是比较棘手的问题,因为相比较于MLP来说,CNN比标准的MLP有更多的超参数。一个经验的法则是:对于学习率和正则化参数通常选择常数,这在优化CNN的过程中要记住。
对于卷积层的过滤器的大小,一般取决于具体的数据集。对于数据集MNIST,其大小为28×28,在该数据集上的最好的结果的过滤器大小为:第一层是5×5,然而对于自然图像,这些图像的像素点的维度要高于数据集MNIST的图像的大小,一般更趋向于在第一层取更大的过滤器大小,如12×12或者15×15。
因此,对于卷积层的过滤器的大小的选择一般是根据给定的数据集中图像的大小而决定的。
在池化层,池化的目的是利用图像数据固有的特点,在计算的中间过程中降低数据的维度,一般比较常用的最大池化的大小为2×2或者不采用最大池化,即设置为0,对于特别大的输入图像,在低层,可采用4×4的池化大小,但是过大的池化大小会在计算过程中丢弃过多的信息,对模型的准确性会造成影响。
导入数据集的过程与Logistic回归中使用的一致。前面已经比较细致的描述 。代码如下:
def load_data(dataset):
'''导入数据
:type dataset: string
:param dataset: MNIST数据集
'''
#1、处理文件目录
data_dir, data_file = os.path.split(dataset)#把路径分割成dirname和basename,返回一个元组
if data_dir == "" and not os.path.isfile(dataset):
new_path = os.path.join(
os.path.split(__file__)[0],#__file__表示的是当前的路径
".",
"data",
dataset
)
if os.path.isfile(new_path) or data_file == 'mnist.pkl.gz':
dataset = new_path#文件所在的目录
if (not os.path.isfile(dataset)) and data_file == 'mnist.pkl.gz':
import urllib
origin = (
'http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz'
)
print 'Downloading data from %s' % origin
urllib.urlretrieve(origin, dataset)
print '... loading data'
#2、打开文件
f = gzip.open(dataset, 'rb')# 打开一个gzip已经压缩好的gzip格式的文件,并返回一个文件对象:file object.
train_set, valid_set, test_set = cPickle.load(f)#载入本地的文件
f.close()
'''训练集train_set,验证集valid_set和测试集test_set的格式:元组(input, target)
其中,input是一个矩阵(numpy.ndarray),每一行代表一个样本;target是一个向量(numpy.ndarray),大小与input的行数对应
'''
def shared_dataset(data_xy, borrow=True):
data_x, data_y = data_xy
shared_x = theano.shared(numpy.asarray(data_x, dtype=theano.config.floatX), borrow=borrow)
shared_y = theano.shared(numpy.asarray(data_y, dtype=theano.config.floatX), borrow=borrow)
return shared_x, T.cast(shared_y, 'int32')#将shared_y转换成整型
#3、将数据处理成需要的形式
test_set_x, test_set_y = shared_dataset(test_set)
valid_set_x, valid_set_y = shared_dataset(valid_set)
train_set_x, train_set_y = shared_dataset(train_set)
#4、返回数据集
rval = [(train_set_x, train_set_y), (valid_set_x, valid_set_y), (test_set_x, test_set_y)]
return rval
建立模型阶段主要是定义一个卷积+池化的过程,具体代码如下:
class LeNetConvPoolLayer(object):
def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)):
"""
:type rng: numpy.random.RandomState
:param rng:用于随机数生成权重
:type input: theano.tensor.dtensor4
:param input:卷积+池化层的输入,是一个4维的张量
:type filter_shape: tuple or list of length 4
:param filter_shape: 卷积层过滤器的大小(number of filters, num input feature maps,
filter height, filter width)
:type image_shape: tuple or list of length 4
:param image_shape: 图像的大小(batch size, num input feature maps,
image height, image width)
:type poolsize: tuple or list of length 2
:param poolsize: 池化的参数(#rows, #cols)
"""
assert image_shape[1] == filter_shape[1]
self.input = input
#fan_in和fan_out主要用于初始化权重参数
#卷积层的输入的大小 "num input feature maps * filter height * filter width"
fan_in = numpy.prod(filter_shape[1:])
#池化层的输出的大小 "num output feature maps * filter height * filter width" /pooling size
fan_out = (filter_shape[0] * numpy.prod(filter_shape[2:]) /
numpy.prod(poolsize))
#初始化权重
W_bound = numpy.sqrt(6. / (fan_in + fan_out))
self.W = theano.shared(
numpy.asarray(
rng.uniform(low=-W_bound, high=W_bound, size=filter_shape),
dtype=theano.config.floatX
),
borrow=True
)
#初始化偏置
b_values = numpy.zeros((filter_shape[0],), dtype=theano.config.floatX)
self.b = theano.shared(value=b_values, borrow=True)
#定义卷积层
conv_out = conv.conv2d(
input=input,#卷积层的输入
filters=self.W,#卷积层使用的权重
filter_shape=filter_shape,#每个过滤器的大小
image_shape=image_shape
)
#定义下采样层
pooled_out = downsample.max_pool_2d(
input=conv_out,#池化层的输出
ds=poolsize,#最大池化的大小
ignore_border=True
)
#加上偏置的过程,最终计算非线性映射
self.output = T.tanh(pooled_out + self.b.dimshuffle('x', 0, 'x', 'x'))
#模型中的参数,权重和偏置
self.params = [self.W, self.b]
# keep track of model input
self.input = input
在代码中,注意在卷积层的输出阶段只是将权重乘以输入,然后计算池化,在最终的结果上加上偏置,并进行非线性函数的映射,即利用tanh函数对其进行非线性的映射,这样的操作,主要是因为可以省去很多的计算。
在模型的训练阶段,主要是将数据通过两个卷积+池化层,最终用过一个MLP计算其分类,并通过梯度下降法对其模型中的参数进行调整,具体的代码如下:
# 三、训练模型
##############################################################################
print '... training'
# early-stopping参数
patience = 10000
patience_increase = 2
improvement_threshold = 0.995
validation_frequency = min(n_train_batches, patience / 2)
best_validation_loss = numpy.inf
best_iter = 0
test_score = 0.
start_time = timeit.default_timer()
epoch = 0
done_looping = False
# 循环训练
while (epoch < n_epochs) and (not done_looping):
epoch = epoch + 1
# 针对每一批样本
for minibatch_index in xrange(n_train_batches):
iter = (epoch - 1) * n_train_batches + minibatch_index
if iter % 100 == 0:
print 'training @ iter = ', iter
# 计算训练的误差
cost_ij = train_model(minibatch_index)
if (iter + 1) % validation_frequency == 0:
# 在验证集上计算误差
validation_losses = [validate_model(i) for i
in xrange(n_valid_batches)]
this_validation_loss = numpy.mean(validation_losses)
print('epoch %i, minibatch %i/%i, validation error %f %%' %
(epoch, minibatch_index + 1, n_train_batches,
this_validation_loss * 100.))
# 记录最优的模型
if this_validation_loss < best_validation_loss:
#improve patience if loss improvement is good enough
if this_validation_loss < best_validation_loss * \
improvement_threshold:
patience = max(patience, iter * patience_increase)
# save best validation score and iteration number
best_validation_loss = this_validation_loss
best_iter = iter
test_losses = [
test_model(i)
for i in xrange(n_test_batches)
]
test_score = numpy.mean(test_losses)
print((' epoch %i, minibatch %i/%i, test error of '
'best model %f %%') %
(epoch, minibatch_index + 1, n_train_batches,
test_score * 100.))
if patience <= iter:
done_looping = True
break
end_time = timeit.default_timer()
print('Optimization complete.')
print('Best validation score of %f %% obtained at iteration %i, '
'with test performance %f %%' %
(best_validation_loss * 100., best_iter + 1, test_score * 100.))
print >> sys.stderr, ('The code for file ' +
os.path.split(__file__)[1] +
' ran for %.2fm' % ((end_time - start_time) / 60.))
部分实验的结果如下:
若需要PDF版本,请关注我的新浪博客@赵_志_勇,私信你的邮箱地址给我。
Convolutional Neural Networks (LeNet)http://www.deeplearning.net/tutorial/lenet.html#tips-and-tricks