深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx

上传人:b****7 文档编号:10594859 上传时间:2023-02-21 格式:DOCX 页数:14 大小:22.65KB
下载 相关 举报
深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx_第1页
第1页 / 共14页
深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx_第2页
第2页 / 共14页
深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx_第3页
第3页 / 共14页
深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx_第4页
第4页 / 共14页
深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx

《深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx》由会员分享,可在线阅读,更多相关《深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx(14页珍藏版)》请在冰豆网上搜索。

深度学习系列卷积神经网络详解二自己手写一个卷积神经网络.docx

深度学习系列卷积神经网络详解二自己手写一个卷积神经网络

【深度学习系列】卷积神经网络详解

(二)——自己手写一个卷积神经网络

作者:

Charlotte77数学系的数据挖掘民工

博客专栏:

个人公众号:

Charlotte数据挖掘(ID:

CharlotteDataMining)

精彩回顾:

【深度学习系列】卷积神经网络CNN原理详解

(一)——基本原理【深度学习系列】PaddlePaddle之数据预处理?

?

 上篇文章中我们讲解了卷积神经网络的基本原理,包括几个基本层的定义、运算规则等。

本文主要写卷积神经网络如何进行一次完整的训练,包括前向传播和反向传播,并自己手写一个卷积神经网络。

如果不了解基本原理的,可以先看看上篇文章:

?

?

?

?

【深度学习系列】卷积神经网络CNN原理详解

(一)——基本原理卷积神经网络的前向传播  首先我们来看一个最简单的卷积神经网络:

1.输入层---->卷积层  以上一节的例子为例,输入是一个4*4的image,经过两个2*2的卷积核进行卷积运算后,变成两个3*3的feature_map以卷积核filter1为例(stride=1):

计算第一个卷积层神经元o11的输入:

 

 神经元o11的输出:

(此处使用Relu激活函数)

 其他神经元计算方式相同 2.卷积层---->池化层 计算池化层m11的输入(取窗口为2*2),池化层没有激活函数  3.池化层---->全连接层  池化层的输出到flatten层把所有元素“拍平”,然后到全连接层。

  4.全连接层---->输出层  全连接层到输出层就是正常的神经元与神经元之间的邻接相连,通过softmax函数计算后输出到output,得到不同类别的概率值,输出概率值最大的即为该图片的类别。

卷积神经网络的反向传播  传统的神经网络是全连接形式的,如果进行反向传播,只需要由下一层对前一层不断的求偏导,即求链式偏导就可以求出每一层的误差敏感项,然后求出权重和偏置项的梯度,即可更新权重。

而卷积神经网络有两个特殊的层:

卷积层和池化层。

池化层输出时不需要经过激活函数,是一个滑动窗口的最大值,一个常数,那么它的偏导是1。

池化层相当于对上层图片做了一个压缩,这个反向求误差敏感项时与传统的反向传播方式不同。

从卷积后的feature_map反向传播到前一层时,由于前向传播时是通过卷积核做卷积运算得到的feature_map,所以反向传播与传统的也不一样,需要更新卷积核的参数。

下面我们介绍一下池化层和卷积层是如何做反向传播的。

  在介绍之前,首先回顾一下传统的反向传播方法:

  1.通过前向传播计算每一层的输入值neti,jneti,j(如卷积后的feature_map的第一个神经元的输入:

neti11neti11)  2.反向传播计算每个神经元的误差项δi,jδi,j,δi,j=?

E?

neti,jδi,j=?

E?

neti,j,其中E为损失函数计算得到的总体误差,可以用平方差,交叉熵等表示。

  3.计算每个神经元权重wi,jwi,j的梯度,ηi,j=?

E?

neti,j?

?

neti,j?

wi,j=δi,j?

outi,jηi,j=?

E?

neti,j?

?

neti,j?

wi,j=δi,j?

outi,j

  4.更新权重wi,j=wi,j?

λ?

ηi,jwi,j=wi,j?

λ?

ηi,j(其中λλ为学习率)  卷积层的反向传播  由前向传播可得:

 所以上一层的输出也就是这一层的输入,即:

outi11=activators(neti11)=i11outi11=activators(neti11)=i11  首先计算输入层的误差项δ11δ11:

(注意这里是neti11neti11,代表的是上一层的输入,不是neto11neto11)  先计算?

E?

i11?

E?

i11  此处我们并不清楚?

E?

i11?

E?

i11怎么算,那可以先把input层通过卷积核做完卷积运算后的输出feature_map写出来:

 然后依次对输入元素ii,jii,j求偏导  i11i11的偏导:

 观察一下上面几个式子的规律,归纳一下,可以得到如下表达式:

 图中的卷积核进行了180°翻转,与这一层的误差敏感项矩阵deltai,j)deltai,j)周围补零后的矩阵做卷积运算后,就可以得到?

E?

i11?

E?

i11,即?

E?

ii,j=∑m?

∑nhm,nδi+m,j+n?

E?

ii,j=∑m?

∑nhm,nδi+m,j+n  第一项求完后,我们来求第二项?

i11?

neti11 此时我们的误差敏感矩阵就求完了,得到误差敏感矩阵后,即可求权重的梯度。

  由于上面已经写出了卷积层的输入neto11neto11与权重hi,jhi,j之间的表达式,所以可以直接求出:

推论出权重的梯度:

偏置项的梯度:

  可以看出,偏置项的偏导等于这一层所有误差敏感项之和。

得到了权重和偏置项的梯度后,就可以根据梯度下降法更新权重和梯度了。

     池化层的反向传播 池化层的反向传播就比较好求了,看着下面的图,左边是上一层的输出,也就是卷积层的输出feature_map,右边是池化层的输入,还是先根据前向传播,把式子都写出来,方便计算:

 假设上一层这个滑动窗口的最大值是outo11

 这样就求出了池化层的误差敏感项矩阵。

同理可以求出每个神经元的梯度并更新权重。

手写一个卷积神经网络  1.定义一个卷积层  首先我们通过ConvLayer来实现一个卷积层,定义卷积层的超参数classConvLayer(object):

'''参数含义:

input_width:

输入图片尺寸——宽度input_height:

输入图片尺寸——长度channel_number:

通道数,彩色为3,灰色为1filter_width:

卷积核的宽filter_height:

卷积核的长filter_number:

卷积核数量zero_padding:

补零长度stride:

步长activator:

激活函数learning_rate:

学习率'''def__init__(self,input_width,input_height,channel_number,filter_width,filter_height,filter_number,zero_padding,stride,activator,learning_rate):

self.input_width=input_widthself.input_height=input_heightself.channel_number=channel_numberself.filter_width=filter_widthself.filter_height=filter_heightself.filter_number=filter_numberself.zero_padding=zero_paddingself.stride=strideself.output_width=\ConvLayer.calculate_output_size(self.input_width,filter_width,zero_padding,stride)self.output_height=\ConvLayer.calculate_output_size(self.input_height,filter_height,zero_padding,stride)self.output_array=np.zeros((self.filter_number,self.output_height,self.output_width))self.filters=[]foriinrange(filter_number):

self.filters.append(Filter(filter_width,filter_height,self.channel_number))self.activator=activatorself.learning_rate=learning_rate

 其中calculate_output_size用来计算通过卷积运算后输出的feature_map大小

@staticmethod

defcalculate_output_size(input_size,

filter_size,zero_padding,stride):

return(input_size-filter_size+5

2*zero_padding)/stride+1 2.构造一个激活函数  此处用的是RELU激活函数,因此我们在activators.py里定义,forward是前向计算,backforward是计算公式的导数:

classReluActivator(object):

defforward(self,weighted_input):

#returnweighted_inputreturnmax(0,weighted_input)

defbackward(self,output):

return1ifoutput>0else0

 其他常见的激活函数我们也可以放到activators里,如sigmoid函数,我们可以做如下定义:

classSigmoidActivator(object):

defforward(self,weighted_input):

return1.0/(1.0+np.exp(-weighted_input))

#thepartialofsigmoid

defbackward(self,output):

returnoutput*(1-output) 如果我们需要自动以其他的激活函数,都可以在activator.py定义一个类即可。

  3.定义一个类,保存卷积层的参数和梯度classFilter(object):

def__init__(self,width,height,depth):

#初始权重self.weights=np.random.uniform(-1e-4,1e-4,(depth,height,width))#初始偏置self.bias=0self.weights_grad=np.zeros(self.weights.shape)self.bias_grad=0

def__repr__(self):

return'filterweights:

\n%s\nbias:

\n%s'%(repr(self.weights),repr(self.bias))

defget_weights(self):

returnself.weights

defget_bias(self):

returnself.bias

defupdate(self,learning_rate):

self.weights-=learning_rate*self.weights_gradself.bias-=learning_rate*self.bias_grad 4.卷积层的前向传播  1).获取卷积区域#获取卷积区域defget_patch(input_array,i,j,filter_width,filter_height,stride):

'''从输入数组中获取本次卷积的区域,自动适配输入为2D和3D的情况'''start_i=i*stridestart_j=j*strideifinput_array.ndim==2:

input_array_conv=input_array[start_i:

start_i+filter_height,start_j:

start_j+filter_width]print'input_array_conv:

',input_array_convreturninput_array_conv

elifinput_array.ndim==3:

input_array_conv=input_array[:

start_i:

start_i+filter_height,start_j:

start_j+filter_width]print'input_array_conv:

',input_array_convreturninput_array_conv 2).进行卷积运算

defconv(input_array,kernel_array,output_array,stride,bias):

'''计算卷积,自动适配输入为2D和3D的情况'''channel_number=input_array.ndimoutput_width=output_array.shape[1]output_height=output_array.shape[0]kernel_width=kernel_array.shape[-1]kernel_height=kernel_array.shape[-2]foriinrange(output_height):

forjinrange(output_width):

output_array[i][j]=(get_patch(input_array,i,j,kernel_width,kernel_height,stride)*kernel_array).sum()+bias

 3).增加zero_padding

#增加Zeropaddingdefpadding(input_array,zp):

'''为数组增加Zeropadding,自动适配输入为2D和3D的情况'''ifzp==0:

returninput_arrayelse:

ifinput_array.ndim==3:

input_width=input_array.shape[2]input_height=input_array.shape[1]input_depth=input_array.shape[0]padded_array=np.zeros((input_depth,input_height+2*zp,input_width+2*zp))padded_array[:

zp:

zp+input_height,zp:

zp+input_width]=input_arrayreturnpadded_arrayelifinput_array.ndim==2:

input_width=input_array.shape[1]input_height=input_array.shape[0]padded_array=np.zeros((input_height+2*zp,input_width+2*zp))padded_array[zp:

zp+input_height,zp:

zp+input_width]=input_arrayreturnpadded_array

 4).进行前向传播

defforward(self,input_array):

'''计算卷积层的输出输出结果保存在self.output_array'''self.input_array=input_arrayself.padded_input_array=padding(input_array,self.zero_padding)forfinrange(self.filter_number):

filter=self.filters[f]conv(self.padded_input_array,filter.get_weights(),self.output_array[f],self.stride,filter.get_bias())element_wise_op(self.output_array,self.activator.forward) 其中element_wise_op函数是将每个组的元素对应相乘#对numpy数组进行elementwise操作,将矩阵中的每个元素对应相

defelement_wise_op(array,op)

foriinnp.nditer(array

op_flags=['readwrite']):

i[...]=op(i)  5.卷积层的反向传播  1).将误差传递到上一层defbp_sensitivity_map(self,sensitivity_array,activator):

'''计算传递到上一层的sensitivitymapsensitivity_array:

本层的sensitivitymapactivator:

上一层的激活函数'''#处理卷积步长,对原始sensitivitymap进行扩展expanded_array=self.expand_sensitivity_map(sensitivity_array)#full卷积,对sensitivitiymap进行zeropadding#虽然原始输入的zeropadding单元也会获得残差#但这个残差不需要继续向上传递,因此就不计算了expanded_width=expanded_array.shape[2]zp=(self.input_width+self.filter_width-1-expanded_width)/2padded_array=padding(expanded_array,zp)#初始化delta_array,用于保存传递到上一层的#sensitivitymapself.delta_array=self.create_delta_array()#对于具有多个filter的卷积层来说,最终传递到上一层的#sensitivitymap相当于所有的filter的#sensitivitymap之和forfinrange(self.filter_number):

filter=self.filters[f]#将filter权重翻转180度flipped_weights=np.array(map(lambdai:

np.rot90(i,2),filter.get_weights()))#计算与一个filter对应的delta_arraydelta_array=self.create_delta_array()fordinrange(delta_array.shape[0]):

conv(padded_array[f],flipped_weights[d],delta_array[d],1,0)self.delta_array+=delta_array#将计算结果与激活函数的偏导数做element-wise乘法操作derivative_array=np.array(self.input_array)element_wise_op(derivative_array,activator.backward)self.delta_array*=derivative_array

 2).保存传递到上一层的sensitivitymap的数组defcreate_delta_array(self):

returnnp.zeros((self.channel_number,

self.input_height,self.input_width))    3).计算代码梯度

defbp_gradient(self,sensitivity_array):

#处理卷积步长,对原始sensitivitymap进行扩展expanded_array=self.expand_sensitivity_map(sensitivity_array)forfinrange(self.filter_number):

#计算每个权重的梯度filter=self.filters[f]fordinrange(filter.weights.shape[0]):

conv(self.padded_input_array[d],expanded_array[f],filter.weights_grad[d],1,0)#计算偏置项的梯度filter.bias_grad=expanded_array[f].sum()

 4).按照梯度下降法更新参数defupdate(self):

'''

按照梯度下降,更新权重

'''

forfilterinself.filters:

filter.update(self.learning_rate)  6.MaxPooling层的训练  1).定义MaxPooling类classMaxPoolingLayer(object):

def__init__(self,input_width,input_height,channel_number,filter_width,filter_height,stride):

self.input_width=input_widthself.input_height=input_heightself.channel_number=channel_numberself.filter_width=filter_widthself.filter_height=filter_heightself.stride=strideself.output_width=(input_width-filter_width)/self.stride+1self.output_height=(input_height-filter_height)/self.stride+1self.output_array=np.zeros((self.channel_number,self.output_height,self.output_width)) 2).前向传播计算

#前向传播defforward(self,input_array):

fordinrange(self.channel_number):

foriinrange(self.output_height):

forjinrange(self.output_width):

self.output_array[d,i,j]=(get_patch(input_array[d],i,j,self.filter_width,self.filter_height,self.stride).max())

  3).反向传播计算

#反向传播defbackward(self,input_array,sensitivity_array):

self.delta

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 机械仪表

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1