原文地址:(40条消息) 深度学习(四)卷积神经网络入门学习(1)_hjimce的专栏-CSDN博客_卷积神经网络学习
如果对卷积层,池化层,全连接(FC)层等概念不熟悉的新手,可先去看:写文章-CSDN博客。
顺便说一下,原文博主真的一级棒,强推优秀博主。原文作者hjimce。
这里只是简单的进行重复,加一些自己的领悟进去。
卷积神经网络是很久以前的算法,不过近年来因为深度学习相关算法为多层网络的训练提供了新方法,然后电脑计算能力又大幅提高,训练数据很多,所以神经网络相关算法就又火了起来。
需要明确的是,现在网上的相关教程一般都指的是神经网络的前向传导过程,反向传播都是用梯度下降法进行训练,大部分深度学习库都已经封装好了,想要深入学习就得自己慢慢学了。
卷积神经网络的经典模型是:Lenet-5实现,只要理解这个前向传导过程,其他的OK,后面讲Lenet-5的实现。
一、理论知识
此处略去权值共享,局部感受野等概念,直接从网络讲起。卷积神经网络的整个过程,只有S2到C3是最难的,接下来开始阐述。
1. 卷积
卷积就是数学概念,是描述两个信号间的相互作用,其目的是求某一输入信号经线性时不变系统作用后的输出信号,数学概念可等到下节描述。对于给定的一幅图像来说,给定一个卷积核,卷积就是根据卷积窗口,进行像素的加权求和。

卷积神经网络与我们之前学到的图像的卷积的区别:我们之前学的图像处理遇到卷积,一般来说这个卷积核是已知的,比如各种边缘检测算子,高斯模糊等这些,这都是已知卷积核,再与图像进行卷积运算。而深度学习中的卷积神经网络的卷积核是未知的,我们训练一个网络,就是要训练得到这些卷积核,而这些卷积核就相当于我们学单层感知器的那些参数W,因为可以把待学习的卷积核看成是神网络的训练参数W。
2. 池化
所谓池化,就是图片向下采样。每一层通过卷积,然后卷积后进行下采样。因为卷积后得到的特征图可能很大,所以池化对数据进行降维,使图形变小,用更高层次的特征表示图像。
池化层以降低特征图的参数量来降低信息冗余,使得模型更关注全局特征而非局部出现的位置。这种降维的过程中可以保留一些重要的特征信息,提升容错能力,且在一定程度上起到防止过拟合的作用。
池化的详细解释可见另一篇博客:写文章-CSDN博客。
3. feature maps(特征图)
特征图其实就是CNN中的每张图片,都可以称之为特征图张。在CNN中,我们要训练的卷积核并不仅仅只有一个,这些卷积核用于提取特征,卷积核个数越多,提取的特征越多,理论上来说精度也会更高,然而卷积核一堆,意味着我们要训练的参数的个数越多。在LeNet-5经典结构中,第一层卷积核选择了6个,而在AlexNet中,第一层卷积核就选择了96个,具体多少个合适,还有待学习。
回到特征图概念,CNN的每一个卷积层我们都要人为的选取合适的卷积核个数,及卷积核大小。每个卷积核与图片进行卷积,就可以得到一张特征图了,比如LeNet-5经典结构中,第一层卷积核选择了6个,我们可以得到6个特征图,这些特征图也就是下一层网络的输入了。我们也可以把输入图片看成一张特征图,作为第一层网络的输入。
4. CNN的经典结构
对于刚入门学CNN的人来说,首先需要学习的是现在的一些经典结构:
(1)LeNet-5。这个是n多年前就有的一个CNN的经典结构,主要是用于手写字体的识别,也是刚入门需要学习熟悉的一个网络,这篇博文主要就是要讲这个网络。

(2)AlexNet。在imagenet上的图像分类challenge上大神Alex提出的alexnet网络结构模型赢得了2012届的冠军,振奋人心,利用CNN实现了图片分类,别人用传统的机器学习算法调参调到半死也就那样,Alex利用CNN精度远超传统的网络。

其它的还有什么Network In Network,GoogLeNet、Deconvolution Network,在以后的学习中我们会遇到。比如利用Deconvolution Network反卷积网络实现图片的去模糊,牛逼哄哄。
二、LeNet-5网络讲解
理论阶段就啰嗦到这里就好了,接着就讲解 LeNet-5, LeNet-5是用于手写字体的识别的一个经典CNN:

输入:32*32的手写字体图片,这些手写字体包含0~9数字,也就是相当于10个类别的图片。
输出:分类结果,0~9之间的一个数。
因此我们可以知道,这是一个多分类问题,总共有十个类,因此神经网络的最后输出层必然是SoftMax问题,然后神经元的个数是10个。LeNet-5结构:
输入层:32*32的图片,也就是相当于1024个神经元。
C1层:paper作者选择6个特征卷积核,然后卷积核大小选择5*5,这样我们可以得到6个特征图,然后每个特征图的大小为32-5+1=28,也就是神经元的个数为6*28*28=784。
S2层:这就是下采样层,也就是使用最大池化进行下采样,池化的size,选择(2,2),也就是相当于对C1层28*28的图片,进行分块,每个块的大小为2*2,这样我们可以得到14*14个块,然后我们统计每个块中,最大的值作为下采样的新像素,因此我们可以得到S1结果为:14*14大小的图片,共有6个这样的图片。
C3层:卷积层,这一层我们选择卷积核的大小依旧为5*5,据此我们可以得到新的图片大小为14-5+1=10,然后我们希望可以得到16张特征图。那么问题来了?这一层是最难理解的,我们知道S2包含:6张14*14大小的图片,我们希望这一层得到的结果是:16张10*10的图片。这16张图片的每一张,是通过S2的6张图片进行加权组合得到的,具体是怎么组合的呢?问题如下图所示:

为了解释这个问题,我们先从简单的开始,我现在假设输入6特征图的大小是5*5的,分别用6个5*5的卷积核进行卷积,得到6个卷积结果图片大小为1*1,如下图所示:

为了简便起见,我这里先做一些标号的定义:我们假设输入第i个特征图的各个像素值为x1i,x2i……x25i,因为每个特征图有25个像素。因此第I个特征图经过5*5的图片卷积后,得到的卷积结果图片的像素值Pi可以表示成:
![]()
这个是卷积公式,不解释。因此对于上面的P1~P6的计算方法,这个就是直接根据公式。然后我们把P1~P6相加起来,也就是:
P=P1+P2+……P6
把上面的Pi的计算公式,代入上式,那么我们可以得到:
P=WX
其中X就是输入的那6张5*5特征图片的各个像素点值,而W就是我们需要学习的参数,也就相当于6个5*5的卷积核,当然它包含着6*(5*5)个参数。因此我们的输出特征图就是:
Out=f(P+b)
这个就是从S2到C3的计算方法,其中b表示偏置项,f为激活函数。
我们回归到原来的问题:有6张输入14*14的特征图片,我们希望用5*5的卷积核,然后最后我们希望得到一张10*10的输出特征图片?
根据上面的过程,也就是其实我们用5*5的卷积核去卷积每一张输入的特征图,当然每张特征图的卷积核参数是不一样的,也就是不共享,因此我们就相当于需要6*(5*5)个参数。对每一张输入特征图进行卷积后,我们得到6张10*10,新图片,这个时候,我们把这6张图片相加在一起,然后加一个偏置项b,然后用激活函数进行映射,就可以得到一张10*10的输出特征图了。
那么一张10*10的特征图需要6个5*5的卷积核,16张肯定就需要乘个16了。等价于这里需要16*6个不同的卷积核,每个卷积核有5*5个参数。
我们希望得到16张10*10的输出特征图,因此我们就需要卷积参数个数为16*(6*(5*5))=16*6*(5*5)个参数。总之,C3层每个图片是通过S2图片进行卷积后,然后相加,并且加上偏置b,最后在进行激活函数映射得到的结果。
S4层:下采样层,比较简单,也是知己对C3的16张10*10的图片进行最大池化,池化块的大小为2*2。因此最后S4层为16张大小为5*5的图片。至此我们的神经元个数已经减少为:16*5*5=400。
C5层:我们继续用5*5的卷积核进行卷积,然后我们希望得到120个特征图。这样C5层图片的大小为5-5+1=1,也就是相当于1个神经元,120个特征图,因此最后只剩下120个神经元了。怎么得到这120个神经元,同上,激活函数加偏置。这个时候,神经元的个数已经够少的了,后面我们就可以直接利用全连接神经网络,进行这120个神经元的后续处理,后面具体要怎么搞,只要懂多层感知器的都懂了,不解释。
上面的结构,只是一种参考,在现实使用中,每一层特征图需要多少个,卷积核大小选择,还有池化的时候采样率要多少,等这些都是变化的,这就是所谓的CNN调参,我们需要学会灵活多变。
比如我们可以把上面的结构改为:C1层卷积核大小为7*7,然后把C3层卷积核大小改为3*3等,然后特征图的个数也是自己选,说不定得到手写字体识别的精度比上面那个还高,这也是有可能的,总之一句话:需要学会灵活多变,需要学会CNN的调参。
总结一下LeNet-5的结构:
(1)输入32*32的手写照片;
(2)C1卷积层:6个5*5的卷积核,得到6个28*28的特征图;
(3)S2下采样层:也为池化层,(2,2)进行最大池化,得到6个14*14的特征图;
(4)C3卷积层:利用5*5的卷积核进行卷积,每张特征图需要6个卷积核,将得到的6个10*10的特征图加起来,经过激活函数和偏置得到一张新的特征图,经过16次这样的操作得到16张特征图,此处用了16*6个5*5的卷积核;
(5)S4下采样层:也为池化层,(2,2)进行最大池化,得到16个5*5的特征图;
(6)C5卷积层:用5*5卷积核进行卷积,16张特征图每张大小变为1,进行激活函数和偏置操作,最后得到120个1*1的特征图,此时只有120个神经元;
(7)全连接层进行分类。
三、实战阶段
学习CNN的源码实现网站:http://deeplearning.net/tutorial/lenet.html#lenet
在theano学习库中有手写字体的库,可以从网上下载到,名为:mnist.pkl.gz的手写字体库,里面包含了三个部分的数据,训练数据集train_set:50000个训练样本,验证集valid_set,我们可以用如下的代码读取这些数据,然后用plot显示其中的一张图片:
<span style="font-size:18px;">import cPickle
import gzip
import numpy as np
import matplotlib.pyplot as plt
f = gzip.open('mnist.pkl.gz', 'rb')
train_set, valid_set, test_set = cPickle.load(f)
f.close()
tx,ty=train_set;
#查看训练样本
print np.shape(tx)#可以看到tx大小为(50000,28*28)的二维矩阵
print np.shape(ty)#可以看到ty大小为(50000,1)的矩阵
#图片显示
A=tx[8].reshape(28,28)#第八个训练样本
Y=ty[8]
print Y
plt.imshow(A,cmap='gray')#显示手写字体图片</span>
在上面的代码中我显示的是第8张图片,可以看到如下结果:

LeNet-5实现
首先你要知道mnist.pkl.gz这个库给我们的图片的大小是28*28的,因此我们可以第一步选择5*5的卷积核进行卷积得到24*24,同时我们希望C1层得到20张特征图,等等,具体的代码实现如下:
import os
import sys
import timeit
import numpy
import theano
import theano.tensor as T
from theano.tensor.signal import downsample
from theano.tensor.nnet import conv
from logistic_sgd import LogisticRegression, load_data
from mlp import HiddenLayer
#卷积神经网络的一层,包含:卷积+下采样两个步骤
#算法的过程是:卷积-》下采样-》激活函数
class LeNetConvPoolLayer(object):
#image_shape是输入数据的相关参数设置 filter_shape本层的相关参数设置
def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)):
"""
:type rng: numpy.random.RandomState
:param rng: a random number generator used to initialize weights
3、input: 输入特征图数据,也就是n幅特征图片
4、参数 filter_shape: (number of filters, num input feature maps,
filter height, filter width)
num of filters:是卷积核的个数,有多少个卷积核,那么本层的out feature maps的个数
也将生成多少个。num input feature maps:输入特征图的个数。
然后接着filter height, filter width是卷积核的宽高,比如5*5,9*9……
filter_shape是列表,因此我们可以用filter_shape[0]获取卷积核个数
5、参数 image_shape: (batch size, num input feature maps,
image height, image width),
batch size:批量训练样本个数 ,num input feature maps:输入特征图的个数
image height, image width分别是输入的feature map图片的大小。
image_shape是一个列表类型,所以可以直接用索引,访问上面的4个参数,索引下标从
0~3。比如image_shape[2]=image_heigth image_shape[3]=num input feature maps
6、参数 poolsize: 池化下采样的的块大小,一般为(2,2)
"""
assert image_shape[1] == filter_shape[1]#判断输入特征图的个数是否一致,如果不一致是错误的
self.input = input
# fan_in=num input feature maps *filter height*filter width
#numpy.prod(x)函数为计算x各个元素的乘积
#也就是说fan_in就相当于每个即将输出的feature map所需要链接参数权值的个数
fan_in = numpy.prod(filter_shape[1:])
# fan_out=num output feature maps * filter height * filter width
fan_out = (filter_shape[0] * numpy.prod(filter_shape[2:]) /
numpy.prod(poolsize))
# 把参数初始化到[-a,a]之间的数,其中a=sqrt(6./(fan_in + fan_out)),然后参数采用均匀采样
#权值需要多少个?卷积核个数*输入特征图个数*卷积核宽*卷积核高?这样没有包含采样层的链接权值个数
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为偏置,是一维的向量。每个输出特征图i对应一个偏置参数b[i]
#,因此下面初始化b的个数就是特征图的个数filter_shape[0]
b_values = numpy.zeros((filter_shape[0],), dtype=theano.config.floatX)
self.b = theano.shared(value=b_values, borrow=True)
# 卷积层操作,函数conv.conv2d的第一个参数为输入的特征图,第二个参数为随机出事化的卷积核参数
#第三个参数为卷积核的相关属性,输入特征图的相关属性
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
)
#激励函数,也就是说是先经过卷积核再池化后,然后在进行非线性映射
# add the bias term. Since the bias is a vector (1D array), we first
# reshape it to a tensor of shape (1, n_filters, 1, 1). Each bias will
# thus be broadcasted across mini-batches and feature map
# width & height
self.output = T.tanh(pooled_out + self.b.dimshuffle('x', 0, 'x', 'x'))
# 保存参数
self.params = [self.W, self.b]
self.input = input
#测试函数
def evaluate_lenet5(learning_rate=0.1, n_epochs=200,
dataset='mnist.pkl.gz',
nkerns=[20, 50], batch_size=500):
""" Demonstrates lenet on MNIST dataset
:learning_rate: 梯度下降法的学习率
:n_epochs: 最大迭代次数
:type dataset: string
:param dataset: path to the dataset used for training /testing (MNIST here)
:nkerns: 每个卷积层的卷积核个数,第一层卷积核个数为 nkerns[0]=20,第二层卷积核个数
为50个
"""
rng = numpy.random.RandomState(23455)
datasets = load_data(dataset)#加载训练数据,训练数据包含三个部分
train_set_x, train_set_y = datasets[0]#训练数据
valid_set_x, valid_set_y = datasets[1]#验证数据
test_set_x, test_set_y = datasets[2]#测试数据
# 计算批量训练可以分多少批数据进行训练,这个只要是知道批量训练的人都知道
n_train_batches = train_set_x.get_value(borrow=True).shape[0]#训练数据个数
n_valid_batches = valid_set_x.get_value(borrow=True).shape[0]
n_test_batches = test_set_x.get_value(borrow=True).shape[0]
n_train_batches /= batch_size#批数
n_valid_batches /= batch_size
n_test_batches /= batch_size
# allocate symbolic variables for the data
index = T.lscalar() # index to a [mini]batch
# start-snippet-1
x = T.matrix('x') # the data is presented as rasterized images
y = T.ivector('y') # the labels are presented as 1D vector of
# [int] labels
# Reshape matrix of rasterized images of shape (batch_size, 28 * 28)
# to a 4D tensor, compatible with our LeNetConvPoolLayer
# (28, 28) is the size of MNIST images.
layer0_input = x.reshape((batch_size, 1, 28, 28))
'''构建第一层网络:
image_shape:输入大小为28*28的特征图,batch_size个训练数据,每个训练数据有1个特征图
filter_shape:卷积核个数为nkernes[0]=20,因此本层每个训练样本即将生成20个特征图
经过卷积操作,图片大小变为(28-5+1 , 28-5+1) = (24, 24)
经过池化操作,图片大小变为 (24/2, 24/2) = (12, 12)
最后生成的本层image_shape为(batch_size, nkerns[0], 12, 12)'''
layer0 = LeNetConvPoolLayer(
rng,
input=layer0_input,
image_shape=(batch_size, 1, 28, 28),
filter_shape=(nkerns[0], 1, 5, 5),
poolsize=(2, 2)
)
'''构建第二层网络:输入batch_size个训练图片,经过第一层的卷积后,每个训练图片有nkernes[0]个特征图,每个特征图
大小为12*12
经过卷积后,图片大小变为(12-5+1, 12-5+1) = (8, 8)
经过池化后,图片大小变为(8/2, 8/2) = (4, 4)
最后生成的本层的image_shape为(batch_size, nkerns[1], 4, 4)'''
layer1 = LeNetConvPoolLayer(
rng,
input=layer0.output,
image_shape=(batch_size, nkerns[0], 12, 12),
filter_shape=(nkerns[1], nkerns[0], 5, 5),
poolsize=(2, 2)
)
# the HiddenLayer being fully-connected, it operates on 2D matrices of
# shape (batch_size, num_pixels) (i.e matrix of rasterized images).
# This will generate a matrix of shape (batch_size, nkerns[1] * 4 * 4),
# or (500, 50 * 4 * 4) = (500, 800) with the default values.
layer2_input = layer1.output.flatten(2)
'''全链接:输入layer2_input是一个二维的矩阵,第一维表示样本,第二维表示上面经过卷积下采样后
每个样本所得到的神经元,也就是每个样本的特征,HiddenLayer类是一个单层网络结构
下面的layer2把神经元个数由800个压缩映射为500个'''
layer2 = HiddenLayer(
rng,
input=layer2_input,
n_in=nkerns[1] * 4 * 4,
n_out=500,
activation=T.tanh
)
# 最后一层:逻辑回归层分类判别,把500个神经元,压缩映射成10个神经元,分别对应于手写字体的0~9
layer3 = LogisticRegression(input=layer2.output, n_in=500, n_out=10)
# the cost we minimize during training is the NLL of the model
cost = layer3.negative_log_likelihood(y)
# create a function to compute the mistakes that are made by the model
test_model = theano.function(
[index],
layer3.errors(y),
givens={
x: test_set_x[index * batch_size: (index + 1) * batch_size],
y: test_set_y[index * batch_size: (index + 1) * batch_size]
}
)
validate_model = theano.function(
[index],
layer3.errors(y),
givens={
x: valid_set_x[index * batch_size: (index + 1) * batch_size],
y: valid_set_y[index * batch_size: (index + 1) * batch_size]
}
)
#把所有的参数放在同一个列表里,可直接使用列表相加
params = layer3.params + layer2.params + layer1.params + layer0.params
#梯度求导
grads = T.grad(cost, params)
# train_model is a function that updates the model parameters by
# SGD Since this model has many parameters, it would be tedious to
# manually create an update rule for each model parameter. We thus
# create the updates list by automatically looping over all
# (params[i], grads[i]) pairs.
updates = [
(param_i, param_i - learning_rate * grad_i)
for param_i, grad_i in zip(params, grads)
]
train_model = theano.function(
[index],
cost,
updates=updates,
givens={
x: train_set_x[index * batch_size: (index + 1) * batch_size],
y: train_set_y[index * batch_size: (index + 1) * batch_size]
}
)
# end-snippet-1
###############
# TRAIN MODEL #
###############
print '... training'
# early-stopping parameters
patience = 10000 # look as this many examples regardless
patience_increase = 2 # wait this much longer when a new best is
# found
improvement_threshold = 0.995 # a relative improvement of this much is
# considered significant
validation_frequency = min(n_train_batches, patience / 2)
# go through this many
# minibatche before checking the network
# on the validation set; in this case we
# check every epoch
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):#每一批训练数据
cost_ij = train_model(minibatch_index)
iter = (epoch - 1) * n_train_batches + minibatch_index
if (iter + 1) % validation_frequency == 0:
# compute zero-one loss on validation set
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 we got the best validation score until now
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 it on the test set
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.))
if __name__ == '__main__':
evaluate_lenet5()
def experiment(state, channel):
evaluate_lenet5(state.learning_rate, dataset=state.dataset)
训练结果:

1万+

被折叠的 条评论
为什么被折叠?



