前言
按照进度, 学习theano
中的卷积操作
国际惯例, 来一波参考网址
Convolutional Neural Networks (LeNet)
卷积小知识
三大特性:局部感知(稀疏连接), 权值共享, 池化
上图很重要, 描述的是前一个隐层m-1
具有四个特征图, 第m
个隐层具有两个特征图, 四个权重框代表的是第m-1
层的所有特征图与第m
层的第一个特征图的连接,注意前一层的所有特征图与后一层第二个特征图的权重并没有画出来, 所以说, 由第m-1
层映射到第m
层, 总共需要4*2=8个不同的权重矩阵, 而不是四个.
代码实现
前面折腾过各种函数,其中就有conv2d
, 然后我们就用它来尝试一下2D卷积,需要注意的是定义的输入、输出、卷积核矩阵它们每个维度都代表什么,详细可以戳我前面的【theano-windows】学习笔记十一——theano中与神经网络相关函数, 这里也写一下:
对于输入:是一个4D张量,分别代表 [批大小,输入特征数(即通道数),图像高度,图像宽度]
对于卷积核: 是一个4D张量,分别代表(第
无反向传播的卷积网络
其实这个代码与第一篇关于theano
的博客一样,戳这里
写一遍便于理解, 顺序就是
-
定义输入张量
import theano
import theano.tensor as T
import numpy as np
#随机数种子初始化
rng=np.random.RandomState(23455)
input=T.tensor4(name='input')#定义输入张量 -
初始化权重张量
#初始化权重张量
w_shp=(2,3,9,9)#下一层两个特征图,上一层三个特征图,卷积核大小9*9
w_bound=np.sqrt(3*9*9)
W=theano.shared(np.asarray(rng.uniform(low=-1.0/w_bound,high=1.0/w_bound,size=w_shp),dtype=input.dtype),
name='W') -
初始化偏置张量
#初始化偏置,如果需要训练,一般设置为0,此处模仿训练好的偏置
b_shp=(2,)
b=theano.shared(np.asarray(rng.uniform(low=-.5,high=.5,size=b_shp),dtype=input.dtype),
name='b') -
卷积并用sigmoid激活
#卷积操作
conv_out=T.nnet.conv2d(input,W)
#用sigmoid激活
output=T.nnet.sigmoid(conv_out+b.dimshuffle('x',0,'x','x')) -
丢入到
function
*后续使用
#整个操作的函数
f=theano.function([input],output) -
读取并处理图片:需要注意的是图片必须改成(批大小,通道,高,宽)
############################注意事项###################################
# 图片的原始数据是一个3D数据【高,宽,通道数量】,
# 经过数据置换(transpose(2,0,1))之后,变成了【通道数量,高,宽】,
# 因为f中传入参数需要4D,因此需要将图片数据reshape成为一个【1, 通道数量, 高, 宽】这样的4D张量,
# reshape的参数一定要注意,1就是最外的那一维度,3就是通道数量,然后是【高】和【宽】,
# 这样结果的 img_.shape =【1, 3, 宽, 高】
#
# 为什么reshape为这样的size呢?因为调用f时需要传入一个input,而这个input就是4D,最终的这个input是传入到
# conv2d中的第一个参数,而那个参数的格式是什么呢?[mini-batch size,特征图的数量,图像高度,图像宽度]
# 这样就串起来了吧,第一个参数是batch size,据我所知应该是指卷积核的数量吧,但是不知道为什么这里是1?
# 第二个参数代表输入层的特征图数量,这个地方是3,其实就是把一张彩色图片按照3个通道作为3个特征图进行输入;
# 最后两个是图像的高度和宽度,正好一一对应。
#使用此函数对一张图片进行操作
import pylab
from PIL import Image
img = Image.open('F:\\Photo\\2.jpg')#读取图片
img_w,img_h=img.size#图像的宽和高
img=np.asarray(img,dtype=input.dtype)/256.
img_=img.transpose(2,0,1).reshape(1,3,img_h,img_w)
filtered_img=f(img_)可视化结果
pylab.subplot(1,3,1);pylab.axis('off');pylab.imshow(img)
pylab.gray();
pylab.subplot(1,3,2);pylab.axis('off');pylab.imshow(filtered_img[0,0,:,:])
pylab.subplot(1,3,3);pylab.axis('off');pylab.imshow(filtered_img[0,1,:,:])
pylab.show()
最大值池化
是卷积中非常重要的非线性下采样方法,它将图像分成不重叠的块,对每一块选择最大值输出
两个理由告诉你为什么最大值池化重要:
- 通过消除非极大值,可以降低高层的计算
- 提供了平移不变性, 图像中的每个像素就有8个平移*度, 如果将最大值池化级联到卷积层,假设是2*2的池化区域, 8种平移可能有3个相同结果, 但是如果用
3×3 的池化区域,就可能有5种相同结果(这个原因还在探索中)
函数调用格式是:
theano.tensor.signal.pool.pool_2d(input, ws=None, ignore_border=None, stride=None, pad=(0, 0), mode='max', ds=None, st=None, padding=None)
参数说明:
-
input
: N维的图片输入张量 -
ws
:长度为2的元组, 缩减因子,在每个维度上将图像减半 -
ignore_border
: 若为true
, 则(5,5)的输入经过ws=(2,2)
得到(2,2)的输出, 否则是(3,3)的输出 -
stride
:步长, 池化操作在行/列上移动的长度,如果是None
,那么与ws
相同 -
pad
:两个int
型元组,或者两个int
型的theano
向量,大小为2, 用于填充图像的四个边界,第一个是上下填充大小, 第二个是左右填充大小 -
mode
是每个池化窗口的操作方法,包括max
,’sum’,’average_inc_pad’,’average_exc_pad’ -
ds,st,padding
丢弃使用了,分别用ws
,stride
,pad
代替
此处我们就不用官方的例子了,直接把上面的图片进行池化操作:
import theano
import theano.tensor as T
import numpy as np
import pylab
from PIL import Image
#随机数种子初始化
rng=np.random.RandomState(23455)
input=T.dtensor4(name='input')#定义输入张量
img = Image.open('F:\\Photo\\2.jpg')#读取图片
img_w,img_h=img.size#图像的宽和高
img=np.asarray(img,dtype=input.dtype)/256.
img_=img.transpose(2,0,1).reshape(1,3,img_h,img_w)
maxpool_shape=(2,2)
pool_out=T.signal.pool.pool_2d(input,maxpool_shape,ignore_border=False)
f_pool=theano.function([input],pool_out)
pool_img=f_pool(img_)
img_pool=pool_img[0,:,:,:].transpose(1,2,0)
print img.shape
print img_pool.shape
pylab.subplot(1,2,1);pylab.axis('off');pylab.imshow(img)
pylab.subplot(1,2,2);pylab.axis('off');pylab.imshow(img_pool)
pylab.show()
输出
上述程序一定要注意定义的容器input
的数据类型,我刚开始写的程序如下:
#错误写法
import theano
import theano.tensor as T
import numpy as np
import pylab
from PIL import Image
#随机数种子初始化
rng=np.random.RandomState(23455)
input=T.tensor4(name='input')#定义输入张量
img = Image.open('F:\\Photo\\2.jpg')#读取图片
img_w,img_h=img.size#图像的宽和高
img=np.asarray(img,dtype=input.dtype)/256.
img_=img.transpose(2,0,1).reshape(1,3,img_h,img_w)
maxpool_shape=(2,2)
pool_out=T.signal.pool.pool_2d(input,maxpool_shape,ignore_border=False)
f_pool=theano.function([input],pool_out)
print input.dtype#float32
print img_.dtype#float32
pool_img=f_pool(img_)
img_pool=pool_img[0,:,:,:].transpose(1,2,0)
print img.shape
print img_pool.shape
pylab.subplot(1,2,1);pylab.axis('off');pylab.imshow(img)
pylab.subplot(1,2,2);pylab.axis('off');pylab.imshow(img_pool)
pylab.show()
虽然我们的input容器和img输入都是float32
,但是不知道为什么一直出现下列错误:
ValueError: GpuDownsampleFactorMax: last dimention size of 600 is bigger then 512. This case is not implemented.
Apply node that caused the error: GpuDownsampleFactorMax{(2, 2),False}(GpuFromHost.0)
Toposort index: 1
Inputs types: [CudaNdarrayType(float32, 4D)]
Inputs shapes: [(1, 3, 800, 1200)]
Inputs strides: [(0, 960000, 1200, 1)]
Inputs values: ['not shown']
Outputs clients: [[HostFromGpu(GpuDownsampleFactorMax{(2, 2),False}.0)]]
HINT: Re-running with most Theano optimization disabled could give you a back-trace of when this node was created. This can be done with by setting the Theano flag 'optimizer=fast_compile'. If that does not work, Theano optimizations can be disabled with 'optimizer=None'.
HINT: Use the Theano flag 'exception_verbosity=high' for a debugprint and storage map footprint of this apply node.
网上的解释戳这里,它的意思是池化以后的矩阵的最后一个维度(第四个维度)不支持大于512的维度,建议翻转一下后两个维度. 但是如果我们的图片长宽都大于1024,那么翻转就没用了,这时候只要把input
容器变成dtensor
,而非tensor
就可以运行了,很神奇,卡了我好几个小时, 原理在于dtensor
是在CPU上运行, 而CPU的内存交换没有GPU那么小,tensor
是运行在GPU上的.
另一种可以处理任意维度的数据的方法是使用theano.sandbox.cuda.dnn.dnn_pool
,详细介绍戳这里, 代码如下
#错误写法
import theano
import theano.tensor as T
import numpy as np
import pylab
from PIL import Image
#random seeds
rng=np.random.RandomState(23455)
input=T.tensor4(name='input',dtype=theano.config.floatX)#input tensor
# img = Image.open('F:\\Photo\\2.jpg')#read image
# img_w,img_h=img.size# with and height of the image
# img=np.asarray(img,dtype=theano.config.floatX)/256. #normalize
# img_=img.transpose(2,0,1).reshape(1,3,img_h,img_w) #reshape for input
img=np.random.RandomState(1).rand(5000, 5000,3)
img_=np.asarray(img,dtype=theano.config.floatX)
img_=img.transpose(2,0,1).reshape(1,3,img_.shape[0],img_.shape[1])
img_=np.asarray(img_,dtype=theano.config.floatX)
print img_.shape
maxpool_shape=(2,2)
# pool_out=T.signal.pool.pool_2d(input,maxpool_shape,ignore_border=False)#limited by 512
pool_out=theano.sandbox.cuda.dnn.dnn_pool(input,maxpool_shape)# non-limited
f_pool=theano.function([input],pool_out)
print input.dtype#float32
print img_.dtype#float32
pool_img=f_pool(img_)
pool_img=np.asarray(pool_img,dtype=theano.config.floatX)
img_pool=pool_img[0,:,:,:].transpose(1,2,0)
print img.shape
print img_pool.shape
pylab.subplot(1,2,1);pylab.axis('off');pylab.imshow(img)
pylab.subplot(1,2,2);pylab.axis('off');pylab.imshow(img_pool)
pylab.show()
Lenet分类模型
模型结构如下:
如此就可以按照顺序依次搭建: 卷积+池化, 全连接+中间隐层, softmax
读数据
这个没什么好说的,跟前面博客一样, 重点是记得把数据放入到共享区域
#引入相关库
import theano
import theano.tensor as T
import numpy as np
import os
import cPickle,gzip
import timeit
#定义读数据的函数,把数据丢入到共享区域
def load_data(dataset):
data_dir,data_file=os.path.split(dataset)
if os.path.isfile(dataset):
with gzip.open(dataset,'rb') as f:
train_set,valid_set,test_set=cPickle.load(f)
#共享数据集
def shared_dataset(data_xy,borrow=True):
data_x,data_y=data_xy
shared_x=theano.shared(np.asarray(data_x,dtype=theano.config.floatX),borrow=borrow)
shared_y=theano.shared(np.asarray(data_y,dtype=theano.config.floatX),borrow=borrow)
return shared_x,T.cast(shared_y,'int32')
#定义三个元组分别存储训练集,验证集,测试集
train_set_x,train_set_y=shared_dataset(train_set)
valid_set_x,valid_set_y=shared_dataset(valid_set)
test_set_x,test_set_y=shared_dataset(test_set)
rval=[(train_set_x,train_set_y),(valid_set_x,valid_set_y),(test_set_x,test_set_y)]
return rval
定义卷积池化层
使用的fan_in,fan_out
权重初始化准则, 具体公式戳这里, 前提是需要知道对于每个隐单元有
实现此层过程就是, 先定义并初始化权重和偏置, 随后进行卷积操作, 池化, 激活, (貌似正常情况下是卷积->激活->池化).
#定义卷积和最大池化
class ConvPool(object):
def __init__(self,rng,input,filter_shape,img_shape,pool_shape):
#input是容器,img_shape是(批大小,输入特征图数,高,宽)
self.input=input
#初始化参数按照fan_in,fan_out准则
#对于每个隐单元,有(输入特征图*高*宽)个输入神经元
fan_in=np.prod(filter_shape[1:])
#对于低层隐单元接收(输出特征图*滤波器高*滤波器宽)/池化大小的梯度
fan_out=(filter_shape[0]*np.prod(filter_shape[2:])//np.prod(pool_shape))
#随机初始化权重
W_bound=np.sqrt(6./(fan_in+fan_out))
self.W=theano.shared(np.asarray(rng.uniform(low=-W_bound,high=W_bound,size=filter_shape),
dtype=theano.config.floatX),
borrow=True)
#初始化偏置全零
b_values=np.zeros((filter_shape[0],),dtype=theano.config.floatX)
self.b=theano.shared(value=b_values,borrow=True)
#定义卷积操作
conv_out=T.nnet.conv2d(input=input,
filters=self.W,
filter_shape=filter_shape,
input_shape=img_shape)
#定义池化操作
pool_out=T.signal.pool.pool_2d(input=conv_out,
ws=pool_shape,
ignore_border=True)
#激活函数
self.output=T.tanh(pool_out+self.b.dimshuffle('x',0,'x','x'))
#存储参数
self.params=[self.W,self.b]
#更新输出
self.input=input
定义隐层
这一个可以戳前面的博客, 大概过程也是: 定义并初始化权重和偏置, 乘积激活
#定义隐层
class HiddenLayer(object):
def __init__(self,rng,input,n_in,n_out,W=None,b=None,activitation=T.tanh):
self.input=input #输入
#根据fan_in,fan_out初始化权重
if W is None:
W_values=np.asarray(rng.uniform(low=- np.sqrt(6./(n_in+n_out)),
high=np.sqrt(6./(n_in+n_out)),
size=(n_in,n_out)),
dtype=theano.config.floatX)
if activitation==T.nnet.sigmoid:
W_values*=4
if b is None:
b_values=np.zeros((n_out,),dtype=theano.config.floatX)
#将权重和偏置放入到共享变量中
W=theano.shared(value=W_values,name='W',borrow=True)
b=theano.shared(value=b_values,name='b',borrow=True)
self.W=W
self.b=b
self.params=[self.W,self.b]
#激活
lin_ouput=T.dot(input,self.W)+self.b
self.output=(lin_ouput if activitation is None else activitation(lin_ouput))
定义softmax层
过程是: 定义并初始化权重, 定义损失函数, 定义测试误差函数
#定义输出层,softmax
class SoftMax(object):
def __init__(self,rng,input,n_in,n_out):
#定义权重
W_values=np.asarray(rng.uniform(low=-np.sqrt(6./(n_in+n_out)),
high=np.sqrt(6./(n_in+n_out)),
size=(n_in,n_out)),
dtype=theano.config.floatX)
#定义偏置
b_values=np.zeros((n_out,),dtype=theano.config.floatX)
#共享变量
self.W=theano.shared(value=W_values,borrow=True,name='W')
self.b=theano.shared(value=b_values,borrow=True,name='b')
#softmax函数值
self.p_y_given_x=T.nnet.softmax(T.dot(input,self.W)+self.b)
#预测值
self.y_pred=T.argmax(self.p_y_given_x,axis=1)
self.params=[self.W,self.b]
self.input=input
def negative_log_likelihood(self,y):
#定义对数似然
return - T.mean(T.log(self.p_y_given_x)[T.arange(y.shape[0]),y])
def errors(self,y):
#定义误差
if y.ndim!=self.y_pred.ndim:
raise TypeError('y should have the same shape as self.y_pred',
('y',y.type,'y_pred',self.y_pred.type))
if y.dtype.startswith('int'):
return T.mean(T.neq(self.y_pred,y))
else:
raise NotImplementedError()
搭建网络
所需要的层已经定义完毕, 然后按照上面的图中所示的网络结构搭建.
需要注意的问题有:
- 原始图片存储方法为每一行代表一张图片,所以在丢入到卷积层之前需要重新组织成(批大小, 通道数, 图片高, 图片宽), 因为是灰度图,所以通道为1,图片大小是标准的mnist手写数字大小(28\times28)
- 最好事先推导一下每层卷积的特征图大小, 全连接层单元数, 因为定义的网络需要输入特征图大小, 我刚开始想的是取出第一层卷积过后得到的
ouput
, 然后用shape
自动得到第二层所需要的分别得到下一个卷积所需要的特征图大小, 但是死活取不出来这个值, 尴了个尬. - 存储模型的方法还是沿用上一篇博客介绍的方法,存储为pkl格式
#定义整个训练和测试过程
def test_ConvPool(learning_rate=0.1,n_epoches=200,dataset='mnist.pkl.gz',nkerns=[20,50],n_hidden=100,batch_size=500):
datasets=load_data(dataset=dataset)
train_set_x,train_set_y=datasets[0]
valide_set_x,valide_set_y=datasets[1]
test_set_x,test_set_y=datasets[2]
#计算小批数据的批数
n_train_batches=train_set_x.get_value(borrow=True).shape[0]//batch_size
n_valid_batches=valide_set_x.get_value(borrow=True).shape[0]//batch_size
n_test_batches=test_set_x.get_value(borrow=True).shape[0]//batch_size
#小批数据的索引
print '建立模型...'
index=T.iscalar()#批索引
x=T.matrix('x')
y=T.ivector('y')
rng=np.random.RandomState(123455)
layer0_input=x.reshape((batch_size,1,28,28))
Classifier=Lenet(rng,batch_size=batch_size,input=layer0_input,n_hidden=n_hidden,nkerns=nkerns,n_out=10)
#损失函数
cost=Classifier.negative_log_likelihood(y)
#梯度计算
grads=T.grad(cost,Classifier.params)
#梯度更新
updates=[(params_i,params_i-learning_rate*grad_i)
for params_i,grad_i in zip(Classifier.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]})
#验证模型
valid_model=theano.function([index],Classifier.layer3.errors(y),
givens={
x:valide_set_x[index*batch_size:(index+1)*batch_size],
y:valide_set_y[index*batch_size:(index+1)*batch_size]
})
#测试模型
test_model=theano.function([index],Classifier.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]
})
#使用提前停止方法训练模型
print('训练模型...')
patiences=10000
patiences_increase=2
improvement_threshold=0.995
validation_frequency=min(n_train_batches,patiences//2)
best_validation_loss=np.inf
best_iter=0
test_score=0
start_time=timeit.default_timer()
epoch=0
done_loop=False
while(epoch<n_epoches) and (not done_loop):
epoch=epoch+1
for minibatch_index in range(n_train_batches):
minibatch_avg_cost=train_model(minibatch_index)
iter=(epoch-1)*n_train_batches+minibatch_index
if (iter+1)%validation_frequency==0:
validation_loss=[valid_model(i) for i in range(n_valid_batches)]
this_validateion_loss=np.mean(validation_loss)
print ('epoch %i,minibatch %i/%i,validation error %f %%'%(epoch,
minibatch_index+1,
n_train_batches,
this_validateion_loss*100.))
if this_validateion_loss<best_validation_loss:
if this_validateion_loss<best_validation_loss*improvement_threshold:
patiences=max(patiences,iter*patiences_increase)
best_validation_loss=this_validateion_loss
best_iter=iter
test_losses=[test_model(i) for i in range(n_test_batches)]
test_score=np.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.))
#保存最优模型
save_file=open('best_model_Conv.pkl','wb')
model=[Classifier.layer0,Classifier.layer1,Classifier.layer2,Classifier.layer3]
cPickle.dump( model,save_file)
if patiences<=iter:
done_loop=True
break
endtime=timeit.default_timer()
print('优化结束')
print('Best validation score of %f %% obtained at iteration %i, '
'with test performance %f %%' %
(best_validation_loss * 100., best_iter + 1, test_score * 100.))
其实如果你用pycharm之类的工具调试以后,可以发现这个best_model_Conv.pkl
存储的就是四个层的模型参数
测试网络
首先是载入模型
mnist_class=cPickle.load(open('best_model_Conv.pkl'))
然后构建一个前向网络
x=T.matrix('x')
single_input=x.reshape((1,1,28,28))
hidden_num=mnist_class[2].b.container.data.shape[0];
kerns_num=[mnist_class[1].W.container.data.shape[0],mnist_class[1].W.container.data.shape[1]]
classifier_test=Lenet(rng=np.random.RandomState(1234),
batch_size=1,
input=single_input,
n_hidden=hidden_num,
nkerns=kerns_num,
n_out=10)
给网络参数赋值
#逐层赋值
#第一层卷积
classifier_test.layer0.W.set_value(mnist_class[0].W.get_value())
classifier_test.layer0.b.set_value(mnist_class[0].b.get_value())
#第二层卷积
classifier_test.layer1.W.set_value(mnist_class[1].W.get_value())
classifier_test.layer1.b.set_value(mnist_class[1].b.get_value())
#第三层全连接->隐层
classifier_test.layer2.W.set_value(mnist_class[2].W.get_value())
classifier_test.layer2.b.set_value(mnist_class[2].b.get_value())
#第四层softmax
classifier_test.layer3.W.set_value(mnist_class[3].W.get_value())
classifier_test.layer3.b.set_value(mnist_class[3].b.get_value())
定义前向计算的操作
#前向计算
forward_compute=theano.function([single_input],classifier_test.layer3.y_pred)
测试网络,这里我们使用前面学习caffe时候手工制作的mnist数据集,戳这里下载, 密码是bead.当然也可以按照MLP中的方法采用mnist原始数据集.
from PIL import Image
import pylab
img=Image.open('E:\\code_test\\theano\\binarybmp\\9.bmp')
img_w,img_h=img.size#图像的宽和高
img=np.asarray(img,dtype='float32')
pylab.imshow(img)
pylab.show()
#原始图片是28*28,要增加两个维度
img=img.reshape((1,1,28,28))
识别结果输出
label_pre=forward_compute(img)
print label_pre
#9
目前就是0识别成6, 6识别成5,剩下的几个全对
博客code打包:链接: https://pan.baidu.com/s/1i5xipiH 密码: rvit
后记
通过池化那部分的研究我们发现关于卷积的实现在theano.tensor.nnet戳这里以及theano.sandbox.cuda.dnn戳这里中也有对应实现。这里要注意, 我们以后使用卷积是否要研究后者的使用而非前者。