仅使用NumPy完成卷积神经网络CNN的搭建(附Python代码)

摘要: 现有的Caffe、TensorFlow等工具箱已经很好地实现CNN模型,但这些工具箱需要的硬件资源比较多,不利于初学者实践和理解。因此,本文教大家如何仅使用NumPy来构建卷积神经网络(Convolutional Neural Network , CNN)模型,具体实现了卷积层、ReLU激活函数层以及最大池化层(max pooling),代码简单,讲解详细。

       目前网络上存在很多编译好的机器学习、深度学习工具箱,在某些情况下,直接调用已经搭好的模型可能是非常方便且有效的,比如Caffe、TensorFlow工具箱,但这些工具箱需要的硬件资源比较多,不利于初学者实践和理解。因此,为了更好的理解并掌握相关知识,最好是能够自己编程实践下。本文将展示如何使用NumPy来构建卷积神经网络(Convolutional Neural Network , CNN)。
       CNN是较早提出的一种神经网络,直到近年来才变得火热,可以说是计算机视觉领域中应用最多的网络。一些工具箱中已经很好地实现CNN模型,相关的库函数已经完全编译好,开发人员只需调用现有的模块即可完成模型的搭建,避免了实现的复杂性。但实际上,这样会使得开发人员不知道其中具体的实现细节。有些时候,数据科学家必须通过一些细节来提升模型的性能,但这些细节是工具箱不具备的。在这种情况下,唯一的解决方案就是自己编程实现一个类似的模型,这样你对实现的模型会有最高级别的控制权,同时也能更好地理解模型每步的处理过程。
       本文将仅使用NumPy实现CNN网络,创建三个层模块,分别为卷积层(Conv)、ReLu激活函数和最大池化(max pooling)。

1.读取输入图像

       以下代码将从skimage Python库中读取已经存在的图像,并将其转换为灰度图:

1.  import skimage.data  
2.  # Reading the image  
3.  img = skimage.data.chelsea()  
4.  # Converting the image into gray.  
5.  img = skimage.color.rgb2gray(img)js

       读取图像是第一步,下一步的操作取决于输入图像的大小。将图像转换为灰度图如下所示:


2.准备滤波器

       以下代码为第一个卷积层Conv准备滤波器组(Layer 1,缩写为l1,下同):

1.  l1_filter = numpy.zeros((2,3,3))

       根据滤波器的数目和每个滤波器的大小来创建零数组。上述代码创建了2个3x3大小的滤波器,(2,3,3)中的元素数字分别表示2:滤波器的数目(num_filters)、3:表示滤波器的列数、3:表示滤波器的行数。由于输入图像是灰度图,读取后变成2维图像矩阵,因此滤波器的尺寸选择为2维阵列,舍去了深度。如果图像是彩色图(具有3个通道,分别为RGB),则滤波器的大小必须为(3,3,3),最后一个3表示深度,上述代码也要更改,变成(2,3,3,3)。
       滤波器组的大小由自己指定,但没有给定滤波器中具体的数值,一般采用随机初始化。下列一组值可以用来检查垂直和水平边缘:

1.  l1_filter[0, :, :] = numpy.array([[[-1, 0, 1],   
2.                                     [-1, 0, 1],   
3.                                     [-1, 0, 1]]])  
4.  l1_filter[1, :, :] = numpy.array([[[1,   1,  1],   
5.                                     [0,   0,  0],   
6.                                     [-1, -1, -1]]]) 

3.卷积层(Conv Layer)

       构建好滤波器后,接下来就是与输入图像进行卷积操作。下面代码使用conv函数将输入图像与滤波器组进行卷积:

1.  l1_feature_map = conv(img, l1_filter) 

       conv函数只接受两个参数,分别为输入图像、滤波器组:

1.  def conv(img, conv_filter):  
2.      if len(img.shape) > 2 or len(conv_filter.shape) > 3: # Check if number of image channels matches the filter depth.  
3.          if img.shape[-1] != conv_filter.shape[-1]:  
4.              print("Error: Number of channels in both image and filter must match.")  
5.              sys.exit()  
6.      if conv_filter.shape[1] != conv_filter.shape[2]: # Check if filter dimensions are equal.  
7.          print('Error: Filter must be a square matrix. I.e. number of rows and columns must match.')  
8.          sys.exit()  
9.      if conv_filter.shape[1]%2==0: # Check if filter diemnsions are odd.  
10.         print('Error: Filter must have an odd size. I.e. number of rows and columns must be odd.')  
11.         sys.exit()  
12.   
13.     # An empty feature map to hold the output of convolving the filter(s) with the image.  
14.     feature_maps = numpy.zeros((img.shape[0]-conv_filter.shape[1]+1,   
15.                                 img.shape[1]-conv_filter.shape[1]+1,   
16.                                 conv_filter.shape[0]))  
17.   
18.     # Convolving the image by the filter(s).  
19.     for filter_num in range(conv_filter.shape[0]):  
20.         print("Filter ", filter_num + 1)  
21.         curr_filter = conv_filter[filter_num, :] # getting a filter from the bank.  
22.         """  
23.         Checking if there are mutliple channels for the single filter. 
24.         If so, then each channel will convolve the image. 
25.         The result of all convolutions are summed to return a single feature map. 
26.         """  
27.         if len(curr_filter.shape) > 2:  
28.             conv_map = conv_(img[:, :, 0], curr_filter[:, :, 0]) # Array holding the sum of all feature maps.  
29.             for ch_num in range(1, curr_filter.shape[-1]): # Convolving each channel with the image and summing the results.  
30.                 conv_map = conv_map + conv_(img[:, :, ch_num],   
31.                                   curr_filter[:, :, ch_num])  
32.         else: # There is just a single channel in the filter.  
33.             conv_map = conv_(img, curr_filter)  
34.         feature_maps[:, :, filter_num] = conv_map # Holding feature map with the current filter.
35.      return feature_maps # Returning all feature maps. 

       该函数首先确保每个滤波器的深度等于图像通道的数目,代码如下。if语句首先检查图像与滤波器是否有一个深度通道,若存在,则检查其通道数是否相等,如果匹配不成功,则报错。

1.  if len(img.shape) > 2 or len(conv_filter.shape) > 3: # Check if number of image channels matches the filter depth.  
2.          if img.shape[-1] != conv_filter.shape[-1]:  
3.              print("Error: Number of channels in both image and filter must match.")  

       此外,滤波器的大小应该是奇数,且每个滤波器的大小是相等的。这是根据下面两个if条件语块来检查的。如果条件不满足,则程序报错并退出。

1.  if conv_filter.shape[1] != conv_filter.shape[2]: # Check if filter dimensions are equal.  
2.      print('Error: Filter must be a square matrix. I.e. number of rows and columns must match.')  
3.      sys.exit()  
4.  if conv_filter.shape[1]%2==0: # Check if filter diemnsions are odd.  
5.      print('Error: Filter must have an odd size. I.e. number of rows and columns must be odd.')  
6.      sys.exit()  

       上述条件都满足后,通过初始化一个数组来作为滤波器的值,通过下面代码来指定滤波器的值:

1.  # An empty feature map to hold the output of convolving the filter(s) with the image.  
2.  feature_maps = numpy.zeros((img.shape[0]-conv_filter.shape[1]+1,   
3.                              img.shape[1]-conv_filter.shape[1]+1,   
4.                              conv_filter.shape[0])) 

       由于没有设置步幅(stride)或填充(padding),默认为步幅设置为1,无填充。那么卷积操作后得到的特征图大小为(img_rows-filter_rows+1, image_columns-filter_columns+1, num_filters),即输入图像的尺寸减去滤波器的尺寸后再加1。注意到,每个滤波器都会输出一个特征图。

1.   # Convolving the image by the filter(s).  
2.      for filter_num in range(conv_filter.shape[0]):  
3.          print("Filter ", filter_num + 1)  
4.          curr_filter = conv_filter[filter_num, :] # getting a filter from the bank.  
5.          """  
6.          Checking if there are mutliple channels for the single filter. 
7.          If so, then each channel will convolve the image. 
8.          The result of all convolutions are summed to return a single feature map. 
9.          """  
10.         if len(curr_filter.shape) > 2:  
11.             conv_map = conv_(img[:, :, 0], curr_filter[:, :, 0]) # Array holding the sum of all feature maps.  
12.             for ch_num in range(1, curr_filter.shape[-1]): # Convolving each channel with the image and summing the results.  
13.                 conv_map = conv_map + conv_(img[:, :, ch_num],   
14.                                   curr_filter[:, :, ch_num])  
15.         else: # There is just a single channel in the filter.  
16.             conv_map = conv_(img, curr_filter)  
17.         feature_maps[:, :, filter_num] = conv_map # Holding feature map with the current filter.  

循环遍历滤波器组中的每个滤波器后,通过下面代码更新滤波器的状态:

1.  curr_filter = conv_filter[filter_num, :] # getting a filter from the bank.  

       如果输入图像不止一个通道,则滤波器必须具有同样的通道数目。只有这样,卷积过程才能正常进行。最后将每个滤波器的输出求和作为输出特征图。下面的代码检测输入图像的通道数,如果图像只有一个通道,那么一次卷积即可完成整个过程:

1.  if len(curr_filter.shape) > 2:  
2.       conv_map = conv_(img[:, :, 0], curr_filter[:, :, 0]) # Array holding the sum of all feature map 
3.       for ch_num in range(1, curr_filter.shape[-1]): # Convolving each channel with the image and summing the results.  
4.          conv_map = conv_map + conv_(img[:, :, ch_num],   
5.                                    curr_filter[:, :, ch_num])  
6.  else: # There is just a single channel in the filter.  
7.      conv_map = conv_(img, curr_filter) 

       上述代码中conv_函数与之前的conv函数不同,函数conv只接受输入图像和滤波器组这两个参数,本身并不进行卷积操作,它只是设置用于conv_函数执行卷积操作的每一组输入滤波器。下面是conv_函数的实现代码:

1.  def conv_(img, conv_filter):  
2.      filter_size = conv_filter.shape[0]  
3.      result = numpy.zeros((img.shape))  
4.      #Looping through the image to apply the convolution operation.  
5.      for r in numpy.uint16(numpy.arange(filter_size/2,   
6.                            img.shape[0]-filter_size/2-2)):  
7.          for c in numpy.uint16(numpy.arange(filter_size/2, img.shape[1]-filter_size/2-2)):  
8.              #Getting the current region to get multiplied with the filter.  
9.              curr_region = img[r:r+filter_size, c:c+filter_size]  
10.             #Element-wise multipliplication between the current region and the filter.  
11.             curr_result = curr_region * conv_filter  
12.             conv_sum = numpy.sum(curr_result) #Summing the result of multiplication.  
13.             result[r, c] = conv_sum #Saving the summation in the convolution layer feature map.  
14.               
15.     #Clipping the outliers of the result matrix.  
16.     final_result = result[numpy.uint16(filter_size/2):result.shape[0]-numpy.uint16(filter_size/2),   
17.                           numpy.uint16(filter_size/2):result.shape[1]-numpy.uint16(filter_size/2)]  
18.     return final_result  

每个滤波器在图像上迭代卷积的尺寸相同,通过以下代码实现:

1.  curr_region = img[r:r+filter_size, c:c+filter_size]  

之后,在图像区域矩阵和滤波器之间对位相乘,并将结果求和以得到单值输出:

1.  #Element-wise multipliplication between the current region and the filter.  
2.  curr_result = curr_region * conv_filter  
3.  conv_sum = numpy.sum(curr_result) #Summing the result of multiplication.  
4.  result[r, c] = conv_sum #Saving the summation in the convolution layer feature map.  

       输入图像与每个滤波器卷积后,通过conv函数返回特征图。下图显示conv层返回的特征图(由于l1卷积层的滤波器参数为(2,3,3),即2个3x3大小的卷积核,最终输出2个特征图):


卷积后图像


卷积层的后面一般跟着激活函数层,本文采用ReLU激活函数。

4.ReLU激活函数层

       ReLU层将ReLU激活函数应用于conv层输出的每个特征图上,根据以下代码行调用ReLU激活函数:

l1_feature_map_relu = relu(l1_feature_map)

ReLU激活函数(ReLU)的具体实现代码如下:

1.  def relu(feature_map):  
2.      #Preparing the output of the ReLU activation function.  
3.      relu_out = numpy.zeros(feature_map.shape)  
4.      for map_num in range(feature_map.shape[-1]):  
5.          for r in numpy.arange(0,feature_map.shape[0]):  
6.              for c in numpy.arange(0, feature_map.shape[1]):  
7.                  relu_out[r, c, map_num] = numpy.max(feature_map[r, c, map_num], 0)  

       ReLU思想很简单,只是将特征图中的每个元素与0进行比较,若大于0,则保留原始值。否则将其设置为0。ReLU层的输出如下图所示:


ReLU层输出图像


激活函数层后面一般紧跟池化层,本文采用最大池化(max pooling)。

5.最大池化层

       ReLU层的输出作为最大池化层的输入,根据下面的代码行调用最大池化操作:

1.  l1_feature_map_relu_pool = pooling(l1_feature_map_relu, 2, 2)  

最大池化函数(max pooling)的具体实现代码如下:

1.  def pooling(feature_map, size=2, stride=2):  
2.      #Preparing the output of the pooling operation.  
3.      pool_out = numpy.zeros((numpy.uint16((feature_map.shape[0]-size+1)/stride),  
4.                              numpy.uint16((feature_map.shape[1]-size+1)/stride),  
5.                              feature_map.shape[-1]))  
6.      for map_num in range(feature_map.shape[-1]):  
7.          r2 = 0  
8.          for r in numpy.arange(0,feature_map.shape[0]-size-1, stride):  
9.              c2 = 0  
10.             for c in numpy.arange(0, feature_map.shape[1]-size-1, stride):  
11.                 pool_out[r2, c2, map_num] = numpy.max(feature_map[r:r+size,  c:c+size])  
12.                 c2 = c2 + 1  
13.             r2 = r2 +1  

       该函数接受3个参数,分别为ReLU层的输出,池化掩膜的大小和步幅。首先也是创建一个空数组,用来保存该函数的输出。数组大小根据输入特征图的尺寸、掩膜大小以及步幅来确定。

1.  pool_out = numpy.zeros((numpy.uint16((feature_map.shape[0]-size+1)/stride),  
2.                          numpy.uint16((feature_map.shape[1]-size+1)/stride),  
3.                          feature_map.shape[-1]))  

       对每个输入特征图通道都进行最大池化操作,返回该区域中最大的值,代码如下:

pool_out[r2, c2, map_num] = numpy.max(feature_map[r:r+size,  c:c+size])

       池化层的输出如下图所示,这里为了显示让其图像大小看起来一样,其实池化操作后图像尺寸远远小于其输入图像。


池化层输出图像

6.层堆叠

       以上内容已经实现CNN结构的基本层——conv、ReLU以及max pooling,现在将其进行堆叠使用,代码如下:

1.  # Second conv layer  
2.  l2_filter = numpy.random.rand(3, 5, 5, l1_feature_map_relu_pool.shape[-1])  
3.  print("\n**Working with conv layer 2**")  
4.  l2_feature_map = conv(l1_feature_map_relu_pool, l2_filter)  
5.  print("\n**ReLU**")  
6.  l2_feature_map_relu = relu(l2_feature_map)  
7.  print("\n**Pooling**")  
8.  l2_feature_map_relu_pool = pooling(l2_feature_map_relu, 2, 2)  
9.  print("**End of conv layer 2**\n") 

       从代码中可以看到,l2表示第二个卷积层,该卷积层使用的卷积核为(3,5,5),即3个5x5大小的卷积核(滤波器)与第一层的输出进行卷积操作,得到3个特征图。后续接着进行ReLU激活函数以及最大池化操作。将每个操作的结果可视化,如下图所示:


l2层处理过程可视化图像

1.  # Third conv layer  
2.  l3_filter = numpy.random.rand(1, 7, 7, l2_feature_map_relu_pool.shape[-1])  
3.  print("\n**Working with conv layer 3**")  
4.  l3_feature_map = conv(l2_feature_map_relu_pool, l3_filter)  
5.  print("\n**ReLU**")  
6.  l3_feature_map_relu = relu(l3_feature_map)  
7.  print("\n**Pooling**")  
8.  l3_feature_map_relu_pool = pooling(l3_feature_map_relu, 2, 2)  
9.  print("**End of conv layer 3**\n"

       从代码中可以看到,l3表示第三个卷积层,该卷积层使用的卷积核为(1,7,7),即1个7x7大小的卷积核(滤波器)与第二层的输出进行卷积操作,得到1个特征图。后续接着进行ReLU激活函数以及最大池化操作。将每个操作的结果可视化,如下图所示:


l3层处理过程可视化图像


       神经网络的基本结构是前一层的输出作为下一层的输入,比如l2层接收l1层的输出,l3层接收来l2层的输出,代码如下:
1.  l2_feature_map = conv(l1_feature_map_relu_pool, l2_filter)  
2.  l3_feature_map = conv(l2_feature_map_relu_pool, l3_filter)

7.完整代码

       全部代码已经上传至Github上,每层的可视化是使用Matplotlib库实现。


本文由阿里云云栖社区组织翻译。
文章原标题《Building Convolutional Neural Network using NumPy from Scratch》

原文链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/521840.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot 使用LibreOffice 在线预览 doc,doxc,xls,xlsx,ppt,pptx 文件

接上一篇:linux环境源码安装unoconv Linux环境_源码安装Unoconv实现文件在线预览doc,doxc,xls,xlsx,ppt,pptx 文件 https://gblfy.blog.csdn.net/article/details/103540694 接上一篇:linux环境…

裸辞面11家python公司,拿到4个offer!网友:买了简历置顶会员吗?

我一直认为,敢裸辞的只有2种人:1、能力足够强,即使裸辞也不会被财务问题所困,更不愁找工作。2、年轻气盛,遇到傻X领导和傻X同事,绝对0容忍!但是最近我在刷某乎的时候,发现了第三种人…

万用表怎么测电池内阻_数字万用表和指针万用表,两者怎么选?

​万用表又称为复用表、多用表、三用表、繁用表等,一般以测量电压、电流和电阻为主要目的。是一种多功能、多量程的测量仪表,可测量直流电流、直流电压、交流电流、交流电压、电阻和音频电平等,有的还可以测交流电流、电容量、电感量及半导体…

在Linux系统上安装Git

Git是目前流行的非常好用的版本控制工具,这里介绍两种安装方式,1、yum安装,2、从github上下载最新的源码编译后安装 文章目录一、yum安装二、从GitHub上下载最新的源码编译后安装1、下载tar包2、上传tar包3、解压压缩包4、进入到解压后的文件…

Michael Jordan:当下的AI其实都是伪“AI”

摘要: Michael Jordan认真的解读当下的AI到底是什么?人工智能(AI)是未来的趋势,这句话是技术人员、学者、记者和风险投资家一致赞同的。如同从技术学术领域跨越到普遍行业的许多词语一样,在使用这些词语的时…

如何提升员工体验 助力企业业务增长?这个棘手的问题终于被解决了!

戳蓝字“CSDN云计算”关注我们哦!相信大部分人都经历过入职第一天的手忙脚乱与无所适从,一份新的工作本就是一项全新的挑战,各种设备的安装与链接、安装适应任何新任务的应用流程,以及令人头疼的各种密码,凡此种种都为…

vb 打印机 任意尺寸_创新小黑管“省心省力”惠普Laser NS 1020 智能闪充激光打印机...

对于小型公司、个人工作室或者有打印需求的家庭用户来说,一款优秀的轻巧、便携的打印机必不可少。前不久,惠普推出了全新一代惠普Laser NS 1020打印机,采用突破性的智能闪充碳粉技术,可实现15秒充粉,简便省时&#xff…

隐私与机器学习,二者可以兼得吗?

摘要: 隐私数据与机器学习看似矛盾,其实不然。如何有效保护机器学习训练中的隐私数据?谷歌专家给出了答案——PATE框架,就算你不太懂隐私保护的知识,也可以通过PATE框架来保护机器学习里的训练数据。最近关于互联网隐私…

SpringBoot 深度调优,让你的项目飞起来!

文章目录项目调优一、修改配置文件项目调优 作为一名工程师,项目调优这事,是必须得熟练掌握的事情。 在SpringBoot项目中,调优主要通过配置文件和配置JVM的参数的方式进行。 一、修改配置文件 关于修改配置文件application.properties。 …

青年PM倪超:想让程序员多一点点幸福感

摘要: 本文主人公倪超,在内部,同事都习惯叫他花名银时,是阿里云产品经理。前不久,他所负责的企业级分布式应用服务EDAS,荣获了公司2018财年年度最佳产品奖。在五四青年节来临之际,我们有幸近距离…

C语言--if...else语句【语法讲解】

一.if...else语句的介绍 if…else 语句是编程中常用的一种分支语句,用于根据条件执行不同的操作。 它的基本语法如下: if (条件表达式) {// 当条件表达式为真时执行的代码块 } else {// 当条件表达式为假时执行的代码块 } 当条件表达式为真时&#xff…

黑苹果找不到触控板_猫和老鼠手游:好友位不足却找不到人开黑?玩家给好友系统支招...

猫追鼠,鼠躲猫,欢乐互动乐逍遥,欢迎你来到猫和老鼠游戏学院第126期。随着开学季的到来,我们在游戏中会发现一个有趣的现象:任何时候打开自己的好友界面,里面都是灰色一片,不可否认这确实和玩家群…

Serverless 的喧哗与骚动

戳蓝字“CSDN云计算”关注我们哦!导读:从 2016 年 AWS 发布 Lambda 以来,全世界的开发者和云厂商对 Serverless 的热情在不断高涨。假设不想在开发应用程序并将其部署在服务器上的过程细节上花费精力,是否有一种简单的架构模型能够…

2018年最佳深度学习书单

摘要: AI人才缺口巨大?如果你想成为AI革命中的一员,那么你就必须要学习深度学习,看看这12本书,它们将成为你的利器!我相信你应该知道人工智能,尤其是深度学习在过去5年左右取得了不错的进步。 深…

双绞线传输距离_视频监控系统中布线常用传输线缆分类及制作方法

同轴电缆:同轴电缆同轴电缆可分为两种基本类型,基带同轴电缆和宽带同轴电缆。目前基带是常用的电缆,其屏蔽线是用铜做成的网状的,特征阻抗为75(如RG-8、RG-58等);宽带同轴电缆常用的电缆的屏蔽层通常是用铝冲压成的&am…

国庆出游,这个银行卡大小的充电宝一定要带

说到充电宝,不知道有没有人跟我有同样的烦恼:5000毫安容量小,不够用。10000毫安容量够用,但又大又笨重,拿在手里充电超累的,还占地方!尤其是旅行出差的时候,这种体验更加明显&#x…

HBase运维基础——元数据逆向修复原理

摘要: 鉴于上次一篇文章——“云HBase小组成功抢救某公司自建HBase集群,挽救30T数据”的读者反馈,对HBase的逆向工程比较感兴趣,并咨询如何使用相应工具进行运维等等。总的来说,就是想更深层理解HBase运维原理&#xf…

50多种适合机器学习和预测应用的API,你的选择是?(2018年版本)

摘要: 本文盘点了2018年以来人脸和图像识别、文本分析、自然语言处理、情感分析、语言翻译、 机器学习和预测这几个领域常用的API,读者可以根据自己需求选择合适的API完成相应的任务。对于做工程项目和搞科研的人来说,有现成的模块或工具使用…

谷歌10月15日发布 Pixel 4;高通以31亿美元收购与TDK公司权益;甲骨文、VMware就云技术及支持达成协议……...

关注并标星星CSDN云计算极客头条:速递、最新、绝对有料。这里有企业新动、这里有业界要闻,打起十二分精神,紧跟fashion你可以的!每周三次,打卡即read更快、更全了解泛云圈精彩newsgo go go 荣耀 Play 3(图片…

天线巴伦制作和原理_10米段的春天 | 用自制环型天线+改装SDR接收器27MHz采访实录...

HF部分包括9个不同的波段,范围从1.8MHz到29.7MHz,它们通常也称…