基础的理论知识参考:https://www.zybuluo.com/hanbingtao/note/485480

下面的代码也是基于上面文章的实现:
整个算法分为三个步骤:

  1. 前向计算每个神经元的输出值ajaja_j(jjj表示网络的第j" role="presentation" style="position: relative;">jjj个神经元,以下同);
  2. 反向计算每个神经元的误差项δjδj\delta _j,δjδj\delta _j在有的文献中也叫做敏感度(sensitivity)。它实际上是EdEdE_d网络的损失函数对神经元netjnetjnet_j加权输入的偏导数,即δj=∂Ed∂netjδj=∂Ed∂netj\delta_j =\frac{\partial E_d}{\partial net_j};
  3. 计算每个神经元连接权重wjiwjiw_{ji}的梯度(wjiwjiw_{ji}表示从神经元连接到神经元jjj的权重),公式为∂Ed∂wji=aiδj" role="presentation" style="position: relative;">∂Ed∂wji=aiδj∂Ed∂wji=aiδj\frac{\partial E_d}{\partial w_{ji}}=a_i\delta _j,其中aiaia_i,表示神经元ii<script type="math/tex" id="MathJax-Element-14">i</script>的输出。

最后,根据梯度下降法则更新每个权重即可。

具体的细节参考上面的连接文章,这里只贴出代码实现:

  • 首先是activators.py文件:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-import numpy as npclass ReluActivator(object):def forward(self, weighted_input):#return weighted_inputreturn max(0, weighted_input)def backward(self, output):return 1 if output > 0 else 0class IdentityActivator(object):def forward(self, weighted_input):return weighted_inputdef backward(self, output):return 1class SigmoidActivator(object):def forward(self, weighted_input):return 1.0 / (1.0 + np.exp(-weighted_input))def backward(self, output):return output * (1 - output)class TanhActivator(object):def forward(self, weighted_input):return 2.0 / (1.0 + np.exp(-2 * weighted_input)) - 1.0def backward(self, output):return 1 - output * output

是一些基本的激活函数的实现

  • 下面的CNN.py文件实现cnn网络主要的组件
#!/usr/bin/env python
# -*- coding: UTF-8 -*-import numpy as np
from activators import ReluActivator, IdentityActivator# 获取卷积区域
def get_patch(input_array, i, j, filter_width,filter_height, stride):'''从输入数组中获取本次卷积的区域,自动适配输入为2D和3D的情况'''start_i = i * stridestart_j = j * strideif input_array.ndim == 2:return input_array[ start_i : start_i + filter_height, start_j : start_j + filter_width]elif input_array.ndim == 3:return input_array[:, start_i : start_i + filter_height, start_j : start_j + filter_width]# 获取一个2D区域的最大值所在的索引
def get_max_index(array):max_i = 0max_j = 0max_value = array[0,0]for i in range(array.shape[0]):for j in range(array.shape[1]):if array[i,j] > max_value:max_value = array[i,j]max_i, max_j = i, jreturn max_i, max_j# 计算卷积:conv函数实现了2维和3维数组的卷积
def conv(input_array,kernel_array,output_array,stride, bias):'''计算卷积,自动适配输入为2D和3D的情况,是在get_patch函数中判断的'''#print 'shape 1:',np.shape(input_array)#print 'shape 2:',np.shape(kernel_array)#print 'shape 3:',np.shape(output_array)channel_number = input_array.ndim output_width = output_array.shape[1]output_height = output_array.shape[0]kernel_width = kernel_array.shape[-1]kernel_height = kernel_array.shape[-2]for i in range(output_height):for j in range(output_width):# 这里的*是np.array*np.array的对应元素相乘#print 'get_patch:\n',get_patch(input_array, i, j, kernel_width,kernel_height, stride)#print 'kernel_array:\n',kernel_arrayoutput_array[i][j] = (get_patch(input_array, i, j, kernel_width, kernel_height, stride) * kernel_array).sum() + bias# padding函数实现了zero padding操作
def padding(input_array, zp): '''为数组增加Zero padding,自动适配输入为2D和3D的情况'''if zp == 0:return input_arrayelse:# 输入为3D时if input_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_arrayreturn padded_array# # 输入为2D时elif input_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_array return padded_array# 对numpy数组进行element wise操作
# element_wise_op函数实现了对numpy数组进行按元素操作,并将返回值写回到数组中
def element_wise_op(array, op):for i in np.nditer(array,op_flags=['readwrite']):i[...] = op(i)# Filter类保存了卷积层的参数以及梯度,并且实现了用梯度下降算法来更新参数
class Filter(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 = 0def __repr__(self):return 'filter weights:\n%s\nbias:\n%s' % (repr(self.weights), repr(self.bias))def get_weights(self):return self.weightsdef get_bias(self):return self.biasdef update(self, learning_rate):self.weights -= learning_rate * self.weights_gradself.bias -= learning_rate * self.bias_grad# 用ConvLayer类来实现一个卷积层
class ConvLayer(object):# 初始化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.activator = activatorself.learning_rate = learning_rate# 卷积后的Feature Map的高度和宽度self.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)# 把输出的feature map用列表存起来self.output_array = np.zeros((self.filter_number, self.output_height, self.output_width))# filters的每个元素是过滤器对象self.filters = []for i in range(filter_number):self.filters.append(Filter(filter_width,filter_height, self.channel_number))# 用来确定卷积层输出的大小@staticmethoddef calculate_output_size(input_size,filter_size, zero_padding, stride):return (input_size - filter_size + 2 * zero_padding) / stride + 1# forward方法实现了卷积层的前向计算                     def forward(self, input_array):'''计算卷积层的输出输出结果保存在self.output_array'''self.input_array = input_array# 为数组增加Zero paddingself.padded_input_array = padding(input_array,self.zero_padding)for f in range(self.filter_number):filter = self.filters[f]#print 'shape of filter:',np.shape(filter.get_weights())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)def backward(self, input_array, sensitivity_array, activator):'''计算传递给前一层的误差项,以及计算每个权重的梯度前一层的误差项保存在:self.delta_array梯度保存在:Filter对象的weights_grad'''self.forward(input_array)self.bp_sensitivity_map(sensitivity_array,activator)self.bp_gradient(sensitivity_array)def update(self):'''按照梯度下降,更新权重'''for filter in self.filters:filter.update(self.learning_rate)def bp_sensitivity_map(self, sensitivity_array,activator):'''计算传递到上一层的sensitivity mapsensitivity_array: 本层的sensitivity mapactivator: 上一层的激活函数'''# 处理卷积步长,对原始sensitivity map进行扩展expanded_array = self.expand_sensitivity_map(sensitivity_array)# full卷积,对sensitivitiy map进行zero padding# 虽然原始输入的zero padding单元也会获得残差# 但这个残差不需要继续向上传递,因此就不计算了expanded_width = expanded_array.shape[2]zp = (self.input_width + self.filter_width - 1 - expanded_width) / 2#print 'zp:',zp# 对误差图进行扩展后再进行0填充padded_array = padding(expanded_array, zp)print 'padded_array:',np.shape(padded_array)# 初始化delta_array,用于保存传递到上一层的sensitivity mapself.delta_array = self.create_delta_array()# 对于具有多个filter的卷积层来说,最终传递到上一层的# sensitivity map相当于所有的filter的sensitivity map之和for f in range(self.filter_number):filter = self.filters[f]# 将filter权重翻转180度flipped_weights = np.array(map(lambda i: np.rot90(i, 2),filter.get_weights()))print 'flipped_weights:',np.shape(flipped_weights)# 计算与一个filter对应的delta_arraydelta_array = self.create_delta_array()for d in range(delta_array.shape[0]):# input_array,kernel_array,output_array,stride, biasconv(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_arraydef bp_gradient(self, sensitivity_array):# 处理卷积步长,对原始sensitivity map进行扩展expanded_array = self.expand_sensitivity_map(sensitivity_array)for f in range(self.filter_number):# 计算每个权重的梯度filter = self.filters[f]for d in range(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()def expand_sensitivity_map(self, sensitivity_array):print 'sensitivity_array:\n',sensitivity_arraydepth = sensitivity_array.shape[0]# 确定扩展后sensitivity map的大小,计算stride为1时sensitivity map的大小expanded_width = (self.input_width - self.filter_width + 2 * self.zero_padding + 1)expanded_height = (self.input_height - self.filter_height + 2 * self.zero_padding + 1)# 构建新的sensitivity_mapexpand_array = np.zeros((depth, expanded_height, expanded_width))# 从原始sensitivity map拷贝误差值for i in range(self.output_height):for j in range(self.output_width):i_pos = i * self.stridej_pos = j * self.strideexpand_array[:,i_pos,j_pos] = sensitivity_array[:,i,j]print 'expand_array:\n',expand_arrayreturn expand_arraydef create_delta_array(self):return np.zeros((self.channel_number,self.input_height, self.input_width))# 池化层
class MaxPoolingLayer(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))def forward(self, input_array):for d in range(self.channel_number):for i in range(self.output_height):for j in range(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())def backward(self, input_array, sensitivity_array):self.delta_array = np.zeros(input_array.shape)for d in range(self.channel_number):for i in range(self.output_height):for j in range(self.output_width):patch_array = get_patch(input_array[d], i, j,self.filter_width, self.filter_height, self.stride)k, l = get_max_index(patch_array)self.delta_array[d, i * self.stride + k, j * self.stride + l] = \sensitivity_array[d,i,j]##.............................卷积层的一些测试.......................................
# 卷积层前向传播数据初始化
def init_test():a = np.array([[[0,1,1,0,2],[2,2,2,2,1],[1,0,0,2,0],[0,1,1,0,0],[1,2,0,0,2]],[[1,0,2,2,0],[0,0,0,2,0],[1,2,1,2,1],[1,0,0,0,0],[1,2,1,1,1]],[[2,1,2,0,0],[1,0,0,1,0],[0,2,1,0,1],[0,1,2,2,2],[2,1,0,0,1]]])# 假设误差项矩阵已经算好                    b = np.array([[[0,1,1],[2,2,2],[1,0,0]],[[1,0,2],[0,0,0],[1,2,1]]])# input_width, input_height, channel_number, filter_width, filter_height, # filter_number, zero_padding, stride, activator,learning_rate                    cl = ConvLayer(5,5,3,3,3,  2,1,2,IdentityActivator(),0.001)cl.filters[0].weights = np.array([[[-1,1,0],[0,1,0],[0,1,1]],[[-1,-1,0],[0,0,0],[0,-1,0]],[[0,0,-1],[0,1,0],[1,-1,-1]]], dtype=np.float64)cl.filters[0].bias=1cl.filters[1].weights = np.array([[[1,1,-1],[-1,-1,1],[0,-1,1]],[[0,1,0],[-1,0,-1],[-1,1,0]],[[-1,0,0],[-1,0,1],[-1,0,0]]], dtype=np.float64)cl.filters[1].bias=1            return a, b, cl# 卷积层前向传播测试
def test():a, b, cl = init_test()cl.forward(a)print 'cl.output_array:\n',cl.output_array# 卷积层的反向传播测试
def test_bp():a, b, cl = init_test()cl.backward(a, b, IdentityActivator())cl.update()print 'cl.filters[0]:\n',cl.filters[0]print 'cl.filters[1]:\n',cl.filters[1]#.............................池化层的一些测试.......................................
# 池化层测试数据初始化
def init_pool_test():a = np.array([[[1,1,2,4],[5,6,7,8],[3,2,1,0],[1,2,3,4]],[[0,1,2,3],[4,5,6,7],[8,9,0,1],[3,4,5,6]]], dtype=np.float64)b = np.array([[[1,2],[2,4]],[[3,5],[8,2]]], dtype=np.float64)# input_width, input_height, channel_number, filter_width, filter_height, stride                    mpl = MaxPoolingLayer(4,4,2,2,2,2)return a, b, mpl# 池化层测试
def test_pool():a, b, mpl = init_pool_test()mpl.forward(a)print 'input array:\n%s\noutput array:\n%s' % (a,mpl.output_array)def test_pool_bp():a, b, mpl = init_pool_test()mpl.backward(a, b)print 'input array:\n%s\nsensitivity array:\n%s\ndelta array:\n%s' % (a, b, mpl.delta_array)if __name__ == '__main__':test()test_pool()test_bp()print '................................................'test_pool_bp()# 测试np.nditer'''a = np.arange(6).reshape(2, 3)  print a  for x in np.nditer(a, op_flags = ['readwrite']):  x[...] = 2*x   print a  '''

一些基本得运行结果:

cl.output_array:
[[[ 6.  7.  5.][ 3. -1. -1.][ 2. -1.  4.]][[ 3. -4. -7.][ 2. -3. -3.][ 1. -4. -4.]]]
input array:
[[[ 1.  1.  2.  4.][ 5.  6.  7.  8.][ 3.  2.  1.  0.][ 1.  2.  3.  4.]][[ 0.  1.  2.  3.][ 4.  5.  6.  7.][ 8.  9.  0.  1.][ 3.  4.  5.  6.]]]
output array:
[[[ 6.  8.][ 3.  4.]][[ 5.  7.][ 9.  6.]]]
sensitivity_array:
[[[0 1 1][2 2 2][1 0 0]][[1 0 2][0 0 0][1 2 1]]]
expand_array:
[[[ 0.  0.  1.  0.  1.][ 0.  0.  0.  0.  0.][ 2.  0.  2.  0.  2.][ 0.  0.  0.  0.  0.][ 1.  0.  0.  0.  0.]][[ 1.  0.  0.  0.  2.][ 0.  0.  0.  0.  0.][ 0.  0.  0.  0.  0.][ 0.  0.  0.  0.  0.][ 1.  0.  2.  0.  1.]]]
padded_array: (2L, 7L, 7L)
flipped_weights: (3L, 3L, 3L)
flipped_weights: (3L, 3L, 3L)
sensitivity_array:
[[[0 1 1][2 2 2][1 0 0]][[1 0 2][0 0 0][1 2 1]]]
expand_array:
[[[ 0.  0.  1.  0.  1.][ 0.  0.  0.  0.  0.][ 2.  0.  2.  0.  2.][ 0.  0.  0.  0.  0.][ 1.  0.  0.  0.  0.]][[ 1.  0.  0.  0.  2.][ 0.  0.  0.  0.  0.][ 0.  0.  0.  0.  0.][ 0.  0.  0.  0.  0.][ 1.  0.  2.  0.  1.]]]
cl.filters[0]:
filter weights:
array([[[-1.008,  0.99 , -0.009],[-0.005,  0.994, -0.006],[-0.006,  0.995,  0.996]],[[-1.004, -1.001, -0.004],[-0.01 , -0.009, -0.012],[-0.002, -1.002, -0.002]],[[-0.002, -0.002, -1.003],[-0.005,  0.992, -0.005],[ 0.993, -1.008, -1.007]]])
bias:
0.99099999999999999
cl.filters[1]:
filter weights:
array([[[  9.98000000e-01,   9.98000000e-01,  -1.00100000e+00],[ -1.00400000e+00,  -1.00700000e+00,   9.97000000e-01],[ -4.00000000e-03,  -1.00400000e+00,   9.98000000e-01]],[[  0.00000000e+00,   9.99000000e-01,   0.00000000e+00],[ -1.00900000e+00,  -5.00000000e-03,  -1.00400000e+00],[ -1.00400000e+00,   1.00000000e+00,   0.00000000e+00]],[[ -1.00400000e+00,  -6.00000000e-03,  -5.00000000e-03],[ -1.00200000e+00,  -5.00000000e-03,   9.98000000e-01],[ -1.00200000e+00,  -1.00000000e-03,   0.00000000e+00]]])
bias:
0.99299999999999999
................................................
input array:
[[[ 1.  1.  2.  4.][ 5.  6.  7.  8.][ 3.  2.  1.  0.][ 1.  2.  3.  4.]][[ 0.  1.  2.  3.][ 4.  5.  6.  7.][ 8.  9.  0.  1.][ 3.  4.  5.  6.]]]
sensitivity array:
[[[ 1.  2.][ 2.  4.]][[ 3.  5.][ 8.  2.]]]
delta array:
[[[ 0.  0.  0.  0.][ 0.  1.  0.  2.][ 2.  0.  0.  0.][ 0.  0.  0.  4.]][[ 0.  0.  0.  0.][ 0.  3.  0.  5.][ 0.  8.  0.  0.][ 0.  0.  0.  2.]]]

全连接层的实现和上一篇文章类似,在此就不再赘述了。至此,你已经拥有了实现了一个简单的卷积神经网络所需要的基本组件,并没有完全实现一个CNN网络。
对于卷积神经网络,现在有很多优秀的开源实现,因此我们并不需要真的自己去实现一个。这里贴出这些代码能让我们更深的理解卷积神经网络的原理,仅供参考学习。

python 面向对象实现CNN(四)相关推荐

  1. python面向对象继承_四. python面向对象(继承)

    一. 继承 class A:pass #父类 基类 超类 class B:pass #父类 基类 超类 class A_son(A,B): pass #子类 派生类 class AB_son(A):p ...

  2. 十四、理解nn.module方法——学习python面向对象编程(一)

    起因 在(十二)中说到pytorch中创建神经网络的两种方法:一个是Sequential类(这也是一种继承父类属性和方法并可对其方法重构的子类),另一个是自己编写代码,继承nn.module类,对其内 ...

  3. python基础学习十四:面向对象三大特性之继承

    一.继承的基本概念 Python面向对象的继承指的是多个类之间的所属关系,即子类默认继承父类的所有属性和方法,具体如下: # ⽗类A class A(object):def __init__(self ...

  4. python对象编程例子-这是我见过最详细的Python面向对象编程!建议收藏!

    原标题:这是我见过最详细的Python面向对象编程!建议收藏! 面向对象编程和函数式编程(面向过程编程)都是程序设计的方法,不过稍有区别. 面向过程编程: 1. 导入各种外部库 2. 设计各种全局变量 ...

  5. python面向对象编程的三大特性_Python面向对象总结及类与正则表达式详解

    Python3 面向对象 -------------------------------------------------------------------------------- 一丶面向对象 ...

  6. 【Python进阶】实战Python面向对象基本编程

    欢迎来到专栏<Python进阶>.在这个专栏中,我们会讲述Python的各种进阶操作,包括Python对文件.数据的处理,Python各种好用的库如NumPy.Scipy.Matplotl ...

  7. Python面向对象中反射和双下的正确用法

    一.反射 反射:程序可以访问,检测和修改它本身状态或行为的一种能力(自省) python面向对象中的反射:通过字符串的形式操作对象相关的属性 python中的一切事物都是对象(都可以使用反射) 四个可 ...

  8. Python面向对象反射,双下方法

    一. 反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究.它首先被程序 ...

  9. python getattr_来一点Python面向对象第一级进阶的东西

    isinstance和issubclass # isinstance(obj,cls) 检查obj是否是类cls的对象class Foo: def __init__(self,name): self. ...

最新文章

  1. 梅花桩上练真功,腾讯公布机器人移动技术探索新突破
  2. 这个AI能帮你快速搜监控:文字定位关键画面,24小时录像10分钟处理完
  3. 面试:URI中的 “//” 有什么用?
  4. CentOS镜像下载地址
  5. 程序怎么启动vasp_构建可扩展的GPU加速应用程序(NVIDIA HPC)
  6. HYSBZ 1588 营业额统计 平衡二叉树模板
  7. 日结问题解决思路和方式
  8. java spring orm jbpm_spring整合jbpm4
  9. IT也要健康:帮助你保持健康的几个重要因素
  10. 3.Chrome开发者工具不完全指南(二、进阶篇)
  11. 拓端tecdat|matlab脉冲响应图的时域特征
  12. Myeclipse10如何进行代码提示和自动补全
  13. 2017计算机办公自动化试题,2017年计算机考试办公自动化试题及答案
  14. Java开发手册归纳知识点
  15. 控制系统--系统结构图
  16. windows系统自带cmd命令下载文件(类似linux的wget下载文件)
  17. 核心单词Word List 3
  18. java 工具类_16 个有用的的Java工具类(小结)
  19. 为什么博士后的年薪远远高于刚入职的大学老师?
  20. 基于Domoticz智能家居系统(十七)DIY一款基于MySensors的ESP8266+NRF24L01的MQTT(WIFI)和RF无线网关(三)一个简单MySensors客户端的验证实验

热门文章

  1. OpenCV相位校正phase corr的实例(附完整代码)
  2. QDoc包括代码内联includecodeinline
  3. QT的QSignalTransition类的使用
  4. QT的QMediaPlayer类的使用
  5. 经典C语言程序100例之五
  6. 国内经济学硕士 申国外计算机硕士,一个经济硕士留学美国的视角
  7. php 从第几开始截取,php如何实现截取前几个字符
  8. 「Ubuntu」Ubuntu中的python终端配置(修改终端默认python配置,软连接,不同版本python环境配置)
  9. Nginx缓存引发的跨域惨案(转:https://www.baidu.com/home/news/data/newspage?nid=9966642810298490574n_type=0p_f)
  10. 1、MyEclipse插件配置以及通过MyEclipse生成表对应的JPA代码