通过 Numpy 手动搭建 CNN

1. 背景知识

  主要参考如下链接:

  https://github.com/mbadry1/DeepLearning.ai-Summary
  http://neuralnetworksanddeeplearning.com/chap6.html
  http://www.deeplearningbook.org/contents/convnets.html

  1.1 CNN原理
    CNN 是Neural Network 中的一个子类, *深度学习* 中的主要方法之一。主要用于解决图像形式的 NN 识别及分类。当进行图形的神经网络处理时,对于全链接网络如按每个像素进行处理的化,相应的特征点值与多层网络的结合,将会使计算量难以接受,这也是引入“卷积”的背景。
    其核心内涵表达式为:

    $$ feature_{map} = input cdot kernel $$

  1.2 CNN 结构
    按 Andrew Ng 的说法,CNN 的主要结构包括:
        1. 输入层

     2. 卷积层-i(多个)

        - 池化层(一般用Max.Pooling)

       3. 全链接层-j(FullyConnected) (也可能有多个)

       4. 最后一层进行转换函数运算(Softmax:Sigmoid/Tanh/ReLU...)

     每一层的结构及计算基本是按图像的像素维度为输入对象,架构成的矩阵运算,其中卷积的计算——即通过 filter 在输入图像上按既定参数进行“遍历”,并通过矩阵运算的方式获得结果——的这一过程。

    而按 Nielsen 参考文献的说法,CNN 的主要组成要素共有三个:

     1. Local receptive fields

           类似前面所描述的 filter ,在运算时所对应的原输入图像中的相应区域。

        2. shared weights

            每个filter 的卷积乘子(即hidden layer 上的每个节点)权重值,都被设置成了一样的,这些结果称为 share weights,相应的偏差称为 share bias。

        3. pooling

            一般紧接在卷积层之后使用,用于 “简化” 从卷积层获得的信息。

          也因此,诸如在 Ng 的教材中,一般池化的方法采用“最大池化”即简单直接的取卷积结果2x2矩阵中的最大值。

     在多个filter存在的条件下,每个filter输出的内容上(即相应的hidden layer输出量)均可进行池化,而在文中的说明中,池化(尤其最大池化)的内涵即筛选出图像中的最大特征点(值),而相应的忽略其精确的位置信息,认为相对于图像的特征值,其位置信息是相对次要的。

2. 方法实现

  参考如下链接:
  https://www.kdnuggets.com/2018/04/building-convolutional-neural-network-numpy-scratch.html

  尤其相应代码:https://github.com/ahmedfgad/NumPyCNN (基本没修改,为学习方便通过注释进行了解读)

  2.1 类和方法的定义

    主要是 NumPyCNN 代码中的 NumPyCNN.py 文件内容。

    原文及注释尽量保留:

    - 首先导入相关库:

 1 import numpy
 2 import sys
 3 
 4 """
 5 Convolutional neural network implementation using NumPy.
 6 An article describing this project is titled "Building Convolutional Neural Network using NumPy from Scratch". It is available in these links: https://www.linkedin.com/pulse/building-convolutional-neural-network-using-numpy-from-ahmed-gad/
 7 https://www.kdnuggets.com/2018/04/building-convolutional-neural-network-numpy-scratch.html
 8 It is also translated into Chinese: http://m.aliyun.com/yunqi/articles/585741
 9 
10 The project is tested using Python 3.5.2 installed inside Anaconda 4.2.0 (64-bit)
11 NumPy version used is 1.14.0
12 
13 For more info., contact me:
14     Ahmed Fawzy Gad
15     KDnuggets: https://www.kdnuggets.com/author/ahmed-gad
16     LinkedIn: https://www.linkedin.com/in/ahmedfgad
17     Facebook: https://www.facebook.com/ahmed.f.gadd
18     ahmed.f.gad@gmail.com
19     ahmed.fawzy@ci.menofia.edu.eg
20 """

    - 定义进行卷积计算的函数 “conv_” :

 1 ### 定义函数 conv_ 即卷积操作中,具体的矩阵运算方式:
 2 ### 总的来说,conv_ 函数的实现内容就是用filter在img上进行遍历,并在每一步进行卷积(矩阵内积),并获得相应结果值的过程
 3 def conv_(img, conv_filter):
 4     filter_size = conv_filter.shape[1] 
 5     result = numpy.zeros((img.shape)) # 按 img 的尺寸构建结果
 6     #Looping through the image to apply the convolution operation.
 7     
 8    
 9     ### 注意后面定义的 conv 函数,几个判断要求保证了输入的 filter 是正矩阵
10     ### 实际上,就是以filter(矩阵)的中心为基点(r,c),在原图 img 上进行遍历的过程,该基点能够达到的位置
11 
12     for r in numpy.uint16(numpy.arange(filter_size/2.0, 
13                           img.shape[0]-filter_size/2.0+1)): 
14         for c in numpy.uint16(numpy.arange(filter_size/2.0, 
15                                            img.shape[1]-filter_size/2.0+1)):
16             
17             """
18             Getting the current region to get multiplied with the filter.
19             How to loop through the image and get the region based on 
20             the image and filer sizes is the most tricky part of convolution.
21             """
22             
23             ### 即取出与filter尺寸相同的 img 内容。'
24           
25             curr_region = img[r-numpy.uint16(numpy.floor(filter_size/2.0)):r+numpy.uint16(numpy.ceil(filter_size/2.0)), 
26                               c-numpy.uint16(numpy.floor(filter_size/2.0)):c+numpy.uint16(numpy.ceil(filter_size/2.0))]
27            
28             #Element-wise multipliplication between the current region and the filter.
29             ### 将对应的内容(filter 与 img 对应filter尺寸的部分)进行矩阵相乘——即卷积操作
30             curr_result = curr_region * conv_filter
31             conv_sum = numpy.sum(curr_result) #Summing the result of multiplication.
32             result[r, c] = conv_sum #Saving the summation in the convolution layer feature map.
33             ### 进一步将所有求和的值,存储在 result 中,result是一个矩阵,尺寸即为filter 中心‘走’过的座标位置,
34                        
35     #Clipping the outliers of the result matrix.
36     ### 这一步将只保留 result 中有用的部分(即存储 numpy.sum 值的部分),
37    
38     final_result = result[numpy.uint16(filter_size/2.0):result.shape[0]-numpy.uint16(filter_size/2.0), 
39                           numpy.uint16(filter_size/2.0):result.shape[1]-numpy.uint16(filter_size/2.0)]
40     return final_result

    - 再定义进行各层卷积方式的函数 “conv”,其中调用了“conv_” 进行了具体卷积计算 :

 1 def conv(img, conv_filter):  ### 这个函数是调整并设置卷积计算的各个要素的维度和尺寸,并通过引入 conv_ 进行计算
 2       ###-------------------------------------------------------------------------------
 3     ### 该函数相对 conv_ 而言,是对多重或多维(多通道)的 img 或 filter 制定相应的操作规则,
 4     ### 而 conv_ 是按照这个规则,进行一对一的卷积操作运算
 5     ###-------------------------------------------------------------------------------
 6     
 7     if len(img.shape) > 2 or len(conv_filter.shape) > 3: # Check if number of image channels matches the filter depth.
 8         if img.shape[-1] != conv_filter.shape[-1]:
 9             print("Error: Number of channels in both image and filter must match.")
10             sys.exit()
11     
12    
13     if conv_filter.shape[1] != conv_filter.shape[2]: # Check if filter dimensions are equal.
14         print('Error: Filter must be a square matrix. I.e. number of rows and columns must match.')
15         sys.exit()
16             
17   
18     if conv_filter.shape[1]%2==0: # Check if filter diemnsions are odd.
19         print('Error: Filter must have an odd size. I.e. number of rows and columns must be odd.')
20         sys.exit()
21 
22     # An empty feature map to hold the output of convolving the filter(s) with the image.
23     ### 但要注意,通过filter所得结果的结构形式构造,与传统理解上做了个小变化,将filter的通道数目列在了最后(filter.shape[0]),
24     ### 这将在 numpy.zeros 的构造中建立以后两个数为矩阵维度、第一个数为组数的三维矩阵,
25     ### 后面的操作将第三个维度作为标识,用前两个维度构成的结构存储了 conv_map,即卷积后的结果,
26    
27     feature_maps = numpy.zeros((img.shape[0]-conv_filter.shape[1]+1, 
28                                 img.shape[1]-conv_filter.shape[1]+1, 
29                                 conv_filter.shape[0]))
30 
31     # Convolving the image by the filter(s).
32     for filter_num in range(conv_filter.shape[0]): ### 对于每个通道上的 filter 
33         print("Filter ", filter_num + 1)
34         
35      
36         ### 定下的第一个值作为通道后,使用第二维以后的内容作为filter矩阵进行操作
37         curr_filter = conv_filter[filter_num, :] # getting a filter from the bank.
38         """ 
39         Checking if there are mutliple channels for the single filter.
40         If so, then each channel will convolve the image.
41         The result of all convolutions are summed to return a single feature map.
42         """
43         if len(curr_filter.shape) > 2: ### 如果所选择的当前通道filter上的维度数目大于2
44             ### 则采用当前通道上,各组矩阵的第一列转置为行后,构成的矩阵(维度是 [:,:] ),
45             ### 并使用该矩阵与相应的图形(注意按判断1, 维度相同)进行具体的卷积操作 “conv_”
46             conv_map = conv_(img[:, :, 0], curr_filter[:, :, 0]) # Array holding the sum of all feature maps.
47             
48                 ### 对于各个子通道的filter
49             for ch_num in range(1, curr_filter.shape[-1]): # Convolving each channel with the image and summing the results.
50               
51                 conv_map = conv_map + conv_(img[:, :, ch_num], 
52                                   curr_filter[:, :, ch_num])
53         else: # There is just a single channel in the filter.
54             ### 否则,如果只是单一个通道,直接 conv_ 操作就可以了
55             conv_map = conv_(img, curr_filter) 
56         
57         ### 这里用到了上面的 feature_maps 的情况:每个通道单独给一个 conv_map 卷积结果数值
58        
59         feature_maps[:, :, filter_num] = conv_map # Holding feature map with the current filter.
60     ###返回 feature_maps
61     return feature_maps # Returning all feature maps.

    - 再定义最大池化函数:

 1 def pooling(feature_map, size=2, stride=2):
 2     #Preparing the output of the pooling operation.
 3     ### 最大池化,直接制定了默认池化尺寸是 2x2 ,
 4 
 5     pool_out = numpy.zeros((numpy.uint16((feature_map.shape[0]-size+1)/stride),
 6                             numpy.uint16((feature_map.shape[1]-size+1)/stride),
 7                             feature_map.shape[-1]))
 8     for map_num in range(feature_map.shape[-1]): (feature_map.shape[-1])
 9         r2 = 0 
10         for r in numpy.arange(0,feature_map.shape[0]-size-1, stride):
11             c2 = 0
12             for c in numpy.arange(0, feature_map.shape[1]-size-1, stride):
13                 ### 在通道 map_num 上,池化矩阵第(r2,c2)的位置上,做最大池化的操作,即选择该池化尺寸对应的f_map内容中的最大值,
14 
15                 pool_out[r2, c2, map_num] = numpy.max([feature_map[r:r+size,  c:c+size]])
16                 c2 = c2 + 1
17             r2 = r2 +1
18     return pool_out

    - 再定义ReLU激励函数:

1 def relu(feature_map):
2     #Preparing the output of the ReLU activation function.
3     ### ReLU是激励函数中取 max(0,value) 的实现方法
4     relu_out = numpy.zeros(feature_map.shape)
5     for map_num in range(feature_map.shape[-1]):
6         for r in numpy.arange(0,feature_map.shape[0]):
7             for c in numpy.arange(0, feature_map.shape[1]):
8                 relu_out[r, c, map_num] = numpy.max([feature_map[r, c, map_num], 0])
9     return relu_out

  2.2 应用实现过程

    主要参考:NumPyCNN 中的 example.py 文件。

    - 导入相关库:

 1 import skimage.data
 2 import matplotlib
 3 #import numpy
 4 #导入到notebook中,原.py文件导入不再需要:import numpycnn, 以及 import numpy
 5 
 6 """
 7 Convolutional neural network implementation using NumPy.
 8 An article describing this project is titled "Building Convolutional Neural Network using NumPy from Scratch". It is available in these links: https://www.linkedin.com/pulse/building-convolutional-neural-network-using-numpy-from-ahmed-gad/
 9 https://www.kdnuggets.com/2018/04/building-convolutional-neural-network-numpy-scratch.html
10 It is also translated into Chinese: http://m.aliyun.com/yunqi/articles/585741
11 
12 The project is tested using Python 3.5.2 installed inside Anaconda 4.2.0 (64-bit)
13 NumPy version used is 1.14.0
14 
15 For more info., contact me:
16     Ahmed Fawzy Gad
17     KDnuggets: https://www.kdnuggets.com/author/ahmed-gad
18     LinkedIn: https://www.linkedin.com/in/ahmedfgad
19     Facebook: https://www.facebook.com/ahmed.f.gadd
20     ahmed.f.gad@gmail.com
21     ahmed.fawzy@ci.menofia.edu.eg
22 """

    - 导入图像:

1 img = skimage.data.chelsea() 
2 # Converting the image into gray.
3 ### 转化成灰度图
4 img = skimage.color.rgb2gray(img) 

    - 进行第一层卷积(实际是 conv/ReLU/pooling 三个操作步骤):

 1 # First conv layer
 2 #l1_filter = numpy.random.rand(2,7,7)*20 # Preparing the filters randomly.
 3 ### 定义第一层 filter 的结构形式,为2层(通道)的 3x3矩阵
 4 l1_filter = numpy.zeros((2,3,3)) 
 5 ### 量化第一个通道内容
 6 l1_filter[0, :, :] = numpy.array([[[-1, 0, 1], 
 7                                    [-1, 0, 1], 
 8                                    [-1, 0, 1]]]) 
 9 ### 量化第二个通道内容
10 l1_filter[1, :, :] = numpy.array([[[1,   1,  1], 
11                                    [0,   0,  0], 
12                                    [-1, -1, -1]]]) 
13 
14 print("
**Working with conv layer 1**")
15 
16 ### 使用 conv 进行第一层的卷积计算
17 l1_feature_map = conv(img, l1_filter) 
18 print("
**ReLU**")
19 #l1_feature_map_relu = numpycnn.relu(l1_feature_map)
20 ### 对第一层卷积层计算结果应用 ReLU 函数
21 l1_feature_map_relu = relu(l1_feature_map) 
22 print("
**Pooling**")
23 #l1_feature_map_relu_pool = numpycnn.pooling(l1_feature_map_relu, 2, 2)
24 ### 对第一层卷积层计算结果进行 Pooling 处理
25 l1_feature_map_relu_pool = pooling(l1_feature_map_relu, 2, 2)
26 print("**End of conv layer 1**
")
27 
28 ### 至此,由于filter矩阵维度是(2,3,3),即2通道3x3矩阵,因此经过卷积操作(conv)的矩阵维度是 2通道,每个通道(n-(3-1))x(m-(3-1)),即,298x449
29 ### 激励函数 ReLU 只是对卷积层结果的每个值进行计算变换,因此维度不变,依然是(2x298x449)
30 ### 池化层采用 2x2结构,对两个通道的每个结果进行处理,因此仍为2组,每组为 (n'/2)x(m'/2)=(148x224)

    此时,输出各图的维度是:

    l1-conv:(298, 449, 2), l1-relu:(298, 449, 2), l1-pooling:(148, 224, 2)

    

    - 进行第二层卷积(实际是 conv/ReLU/pooling 三个操作步骤):

 1 # Second conv layer
 2 ### 构造第二层的filter,结构上使用上一层(l1)的最终维度
 3 ### 按照 numpy.random.rand 的方法实现,这个是一个5x2矩阵,分了3大组,每组5个矩阵
 4 l2_filter = numpy.random.rand(3, 5, 5, l1_feature_map_relu_pool.shape[-1])
 5 print("
**Working with conv layer 2**")
 6 #l2_feature_map = numpycnn.conv(l1_feature_map_relu_pool, l2_filter)
 7 ### 
 8 l2_feature_map = conv(l1_feature_map_relu_pool, l2_filter)
 9 print("
**ReLU**")
10 #l2_feature_map_relu = numpycnn.relu(l2_feature_map)
11 l2_feature_map_relu = relu(l2_feature_map)
12 print("
**Pooling**")
13 #l2_feature_map_relu_pool = numpycnn.pooling(l2_feature_map_relu, 2, 2)
14 l2_feature_map_relu_pool = pooling(l2_feature_map_relu, 2, 2)
15 print("**End of conv layer 2**
")

    此时,输出各图的维度是:

    l2-conv:(144, 220, 3), l2-relu:(144, 220, 3), l2-pooling:(71, 109, 3)

   可以简单算一下,与第一层卷积后得结果经过第二层filter 的卷积结果,是一致的。

    - 进行第三层卷积(依然是 conv/ReLU/pooling 三个操作步骤):

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

    这一层的结果是该 CNN 最后一层的结果:

    l3-conv:(65, 103, 1), l3-relu:(65, 103, 1), l3-pooling:(32, 51, 1)

    与第二层卷积后得结果经过第三层filter 的卷积结果是一致的,同时由于该层仅有一个filter,因此图形回到单一 feature_map 的情况。

    

    至此,卷积的工作已经做完,继续进行图像的存储处理:

1 # Graphing results
2 ### 使用 matplotlib 进行绘图,本cell先绘制原图 in_img.png,具体细节见 matplotlib mannal,这里不细述
3 fig0, ax0 = matplotlib.pyplot.subplots(nrows=1, ncols=1)
4 ax0.imshow(img).set_cmap("gray")
5 ax0.set_title("Input Image")
6 ax0.get_xaxis().set_ticks([])
7 ax0.get_yaxis().set_ticks([])
8 matplotlib.pyplot.savefig("in_img.png", bbox_inches="tight")
9 matplotlib.pyplot.close(fig0)

    L1 层:

 1 # Layer 1
 2 ### 绘制第一层卷积、ReLU处理、池化后的图,因该层 filter 是两通道,因此每个(conv/ReLU/pooling)结果是两个图
 3 fig1, ax1 = matplotlib.pyplot.subplots(nrows=3, ncols=2)
 4 ax1[0, 0].imshow(l1_feature_map[:, :, 0]).set_cmap("gray")
 5 ax1[0, 0].get_xaxis().set_ticks([])
 6 ax1[0, 0].get_yaxis().set_ticks([])
 7 ax1[0, 0].set_title("L1-Map1")
 8 
 9 ax1[0, 1].imshow(l1_feature_map[:, :, 1]).set_cmap("gray")
10 ax1[0, 1].get_xaxis().set_ticks([])
11 ax1[0, 1].get_yaxis().set_ticks([])
12 ax1[0, 1].set_title("L1-Map2")
13 
14 ax1[1, 0].imshow(l1_feature_map_relu[:, :, 0]).set_cmap("gray")
15 ax1[1, 0].get_xaxis().set_ticks([])
16 ax1[1, 0].get_yaxis().set_ticks([])
17 ax1[1, 0].set_title("L1-Map1ReLU")
18 
19 ax1[1, 1].imshow(l1_feature_map_relu[:, :, 1]).set_cmap("gray")
20 ax1[1, 1].get_xaxis().set_ticks([])
21 ax1[1, 1].get_yaxis().set_ticks([])
22 ax1[1, 1].set_title("L1-Map2ReLU")
23 
24 ax1[2, 0].imshow(l1_feature_map_relu_pool[:, :, 0]).set_cmap("gray")
25 ax1[2, 0].get_xaxis().set_ticks([])
26 ax1[2, 0].get_yaxis().set_ticks([])
27 ax1[2, 0].set_title("L1-Map1ReLUPool")
28 
29 ax1[2, 1].imshow(l1_feature_map_relu_pool[:, :, 1]).set_cmap("gray")
30 ax1[2, 0].get_xaxis().set_ticks([])
31 ax1[2, 0].get_yaxis().set_ticks([])
32 ax1[2, 1].set_title("L1-Map2ReLUPool")
33 
34 matplotlib.pyplot.savefig("L1.png", bbox_inches="tight")
35 matplotlib.pyplot.close(fig1)

    L2层:

 1 # Layer 2
 2 ### 绘制第二层卷积、ReLU处理、池化后的图,因该层 filter 是三通道,因此每个(conv/ReLU/pooling)结果是三个图
 3 fig2, ax2 = matplotlib.pyplot.subplots(nrows=3, ncols=3)
 4 ax2[0, 0].imshow(l2_feature_map[:, :, 0]).set_cmap("gray")
 5 ax2[0, 0].get_xaxis().set_ticks([])
 6 ax2[0, 0].get_yaxis().set_ticks([])
 7 ax2[0, 0].set_title("L2-Map1")
 8 
 9 ax2[0, 1].imshow(l2_feature_map[:, :, 1]).set_cmap("gray")
10 ax2[0, 1].get_xaxis().set_ticks([])
11 ax2[0, 1].get_yaxis().set_ticks([])
12 ax2[0, 1].set_title("L2-Map2")
13 
14 ax2[0, 2].imshow(l2_feature_map[:, :, 2]).set_cmap("gray")
15 ax2[0, 2].get_xaxis().set_ticks([])
16 ax2[0, 2].get_yaxis().set_ticks([])
17 ax2[0, 2].set_title("L2-Map3")
18 
19 ax2[1, 0].imshow(l2_feature_map_relu[:, :, 0]).set_cmap("gray")
20 ax2[1, 0].get_xaxis().set_ticks([])
21 ax2[1, 0].get_yaxis().set_ticks([])
22 ax2[1, 0].set_title("L2-Map1ReLU")
23 
24 ax2[1, 1].imshow(l2_feature_map_relu[:, :, 1]).set_cmap("gray")
25 ax2[1, 1].get_xaxis().set_ticks([])
26 ax2[1, 1].get_yaxis().set_ticks([])
27 ax2[1, 1].set_title("L2-Map2ReLU")
28 
29 ax2[1, 2].imshow(l2_feature_map_relu[:, :, 2]).set_cmap("gray")
30 ax2[1, 2].get_xaxis().set_ticks([])
31 ax2[1, 2].get_yaxis().set_ticks([])
32 ax2[1, 2].set_title("L2-Map3ReLU")
33 
34 ax2[2, 0].imshow(l2_feature_map_relu_pool[:, :, 0]).set_cmap("gray")
35 ax2[2, 0].get_xaxis().set_ticks([])
36 ax2[2, 0].get_yaxis().set_ticks([])
37 ax2[2, 0].set_title("L2-Map1ReLUPool")
38 
39 ax2[2, 1].imshow(l2_feature_map_relu_pool[:, :, 1]).set_cmap("gray")
40 ax2[2, 1].get_xaxis().set_ticks([])
41 ax2[2, 1].get_yaxis().set_ticks([])
42 ax2[2, 1].set_title("L2-Map2ReLUPool")
43 
44 ax2[2, 2].imshow(l2_feature_map_relu_pool[:, :, 2]).set_cmap("gray")
45 ax2[2, 2].get_xaxis().set_ticks([])
46 ax2[2, 2].get_yaxis().set_ticks([])
47 ax2[2, 2].set_title("L2-Map3ReLUPool")
48 
49 matplotlib.pyplot.savefig("L2.png", bbox_inches="tight")
50 matplotlib.pyplot.close(fig2)

    L3层:

 1 # Layer 3
 2 ### 绘制第三层(最后一层)的卷积、ReLU处理、池化后的图,因该层 filter 是一通道,因此每个(conv/ReLU/pooling)结果是一个图
 3 fig3, ax3 = matplotlib.pyplot.subplots(nrows=1, ncols=3)
 4 ax3[0].imshow(l3_feature_map[:, :, 0]).set_cmap("gray")
 5 ax3[0].get_xaxis().set_ticks([])
 6 ax3[0].get_yaxis().set_ticks([])
 7 ax3[0].set_title("L3-Map1")
 8 
 9 ax3[1].imshow(l3_feature_map_relu[:, :, 0]).set_cmap("gray")
10 ax3[1].get_xaxis().set_ticks([])
11 ax3[1].get_yaxis().set_ticks([])
12 ax3[1].set_title("L3-Map1ReLU")
13 
14 ax3[2].imshow(l3_feature_map_relu_pool[:, :, 0]).set_cmap("gray")
15 ax3[2].get_xaxis().set_ticks([])
16 ax3[2].get_yaxis().set_ticks([])
17 ax3[2].set_title("L3-Map1ReLUPool")
18 
19 matplotlib.pyplot.savefig("L3.png", bbox_inches="tight")
20 matplotlib.pyplot.close(fig3)

    到此,所有三层输出已全部完成。可以根据图像的内容判断与结果的差异、以及分类的效果。

    另一可参考到的内容为:https://coveralls.io/github/wkcn/mobula?branch=master

    代码是全的,可深入参考。

原文地址:https://www.cnblogs.com/ruich/p/9271572.html