• 机器学习---CNN(创建和训练一个卷积神经网络并评估其性能)上


    1. cnn_operations模块

    cnn_operations类

    1. @staticmethod
    2. def calc_loss(Y, tilde_Y):
    3. # 训练样本个数
    4. n_samples = Y.shape[0]
    5. # 网络代价
    6. loss = 0
    7. for i in range(n_samples):
    8. loss += np.sum((Y[i, :] - tilde_Y[i, :])**2)
    9. loss /= (2 * n_samples)
    10. return loss

    计算网络代价:
          
     
            Y: N * M array,各训练样本对应的类别标签,N为训练样本个数,M为类别数
            
            tilde_Y: N * M array,预测出的各训练样本的类别标签

            这个函数被定义为静态方法(使用了@staticmethod装饰器),所以它可以通过类名直接调

    用,而不需要创建类的实例。  

    1. @staticmethod
    2. def convolute_2d(feature_map, kernel, size_kernel, stride, padding):
    3. # 输入特征图尺寸
    4. size_feature_map = np.asarray(feature_map.shape)
    5. # 输出特征图尺寸
    6. size_output = [int( \
    7. (size_feature_map[0] + 2 * padding - size_kernel[0]) / stride + 1),
    8. int( \
    9. (size_feature_map[1] + 2 * padding - size_kernel[1]) / stride + 1)]
    10. if padding == 0:
    11. feature_map_padded = feature_map
    12. elif padding > 0:
    13. # 补零后的特征图
    14. feature_map_padded = np.zeros(size_feature_map + 2 * padding)
    15. feature_map_padded[padding: -padding, padding: -padding] += \
    16. feature_map
    17. output = np.empty(size_output)
    18. for i in range(0, feature_map_padded.shape[0], stride):
    19. for j in range(0, feature_map_padded.shape[1], stride):
    20. sub_mat = feature_map_padded[i: np.min([i + size_kernel[0],
    21. feature_map_padded.shape[0]]),
    22. j: np.min([j + size_kernel[1],
    23. feature_map_padded.shape[1]])]
    24. shape_sub_mat = sub_mat.shape
    25. if (shape_sub_mat[0] < size_kernel[0]) or \
    26. (shape_sub_mat[1] < size_kernel[1]):
    27. break
    28. output[int(i/stride), int(j/stride)] = \
    29. np.sum(sub_mat * kernel)
    30. return output

    二维卷积(卷积核不翻转):

            feature_map: 2-d array,输入特征图(也可以被看作是输入的图像或前一层输出的特征图)
            
            kernel: 2-d array,卷积核;
            
            size_kernel: array,卷积核尺寸;
            
            stride: 整数,卷积核步长;
            
            padding: 整数,边缘补零的宽度,常用于保持特征图的尺寸。

             output: 二维数组,表示进行卷积操作后输出的特征图。

    操作流程:

    ①计算输入特征图的尺寸size_feature_map。

    ②计算输出特征图的尺寸size_output。这个尺寸取决于输入特征图的尺寸,卷积核的尺寸,步长和

    补零的宽度。

    ③如果补零的宽度为0,则不进行任何操作;如果补零的宽度大于0,则在输入特征图的边缘添加相

    应宽度的零,得到feature_map_padded。

    ④初始化输出特征图output。

    ⑤对feature_map_padded进行卷积操作:在每个步长位置,取出与卷积核相同尺寸的子区域

    sub_mat,然后将sub_mat与卷积核逐元素相乘并求和,得到的结果存储在output的相应位置。

    1. @staticmethod
    2. def pool(feature_map, type_pooling, size_kernel, stride, padding):
    3. # 输入特征图尺寸
    4. size_feature_map = np.asarray(feature_map.shape)
    5. # 输出特征图尺寸
    6. size_output = [int( \
    7. (size_feature_map[0] + 2 * padding - size_kernel[0]) / stride + 1),
    8. int( \
    9. (size_feature_map[1] + 2 * padding - size_kernel[1]) / stride + 1)]
    10. if padding == 0:
    11. feature_map_padded = feature_map
    12. elif padding > 0:
    13. # 补零后的特征图
    14. feature_map_padded = np.zeros(size_feature_map + 2 * padding)
    15. feature_map_padded[padding: -padding, padding: -padding] += \
    16. feature_map
    17. output = np.empty(size_output)
    18. if type_pooling is "max":
    19. func = np.max
    20. elif type_pooling is "average":
    21. #池化层每个神经元有一个权值,此处只需求和,不需求均值
    22. func = np.sum
    23. for i in range(0, feature_map_padded.shape[0], stride):
    24. for j in range(0, feature_map_padded.shape[1], stride):
    25. sub_mat = feature_map_padded[i: np.min([i + size_kernel[0],
    26. feature_map_padded.shape[0]]),
    27. j: np.min([j + size_kernel[1],
    28. feature_map_padded.shape[1]])]
    29. shape_sub_mat = sub_mat.shape
    30. if (shape_sub_mat[0] < size_kernel[0]) or \
    31. (shape_sub_mat[1] < size_kernel[1]):
    32. break
    33. output[int(i/stride), int(j/stride)] = func(sub_mat)
    34. return output

    池化(用于降低特征图的维度,同时保持重要的特征):
            
            feature_map: 2-d array,输入特征图
            
            type_pooling: 池化核类型,{"max", "average"}
            
            size_kernel: array,池化核尺寸
            
            stride: 池化核步长,整数
            
            padding: 边缘补零的宽度,整数

            output: 二维数组,表示进行池化操作后输出的特征图。

    操作流程:

    ①计算输入特征图的尺寸size_feature_map。

    ②计算输出特征图尺寸size_output。取决于输入特征图尺寸,池化核尺寸,步长和补零的宽度。

    ③如果补零的宽度为0,则不进行任何操作;如果补零的宽度大于0,则在输入特征图的边缘添加相

    应宽度的零,得到feature_map_padded。

    ④初始化输出特征图output。

    ⑤根据池化类型选择相应的函数func。

    ⑥对feature_map_padded进行池化操作:在每个步长位置,取出与池化核相同尺寸的子区域

    sub_mat,然后将func应用于sub_mat,得到的结果存储在output的相应位置。

    1. @staticmethod
    2. def upsample_conv_2d(delta_next_layer, kernel, size_kernel, stride):
    3. # 下一层(卷积层)中一个神经元的灵敏度map的尺寸
    4. size_delta_next_layer = np.asarray(delta_next_layer.shape)
    5. # 边缘补零后当前层一中一个神经元灵敏度map的尺寸
    6. size_delta_padded = \
    7. [size_delta_next_layer[0] * stride + size_kernel[0] - stride,
    8. size_delta_next_layer[1] * stride + size_kernel[1] - stride]
    9. delta_padded = np.zeros(size_delta_padded)
    10. for i in range(delta_next_layer.shape[0]):
    11. for j in range(delta_next_layer.shape[1]):
    12. # 卷积核不翻转
    13. delta_padded[i * stride: i * stride + size_kernel[0],
    14. j * stride: j * stride + size_kernel[1]] += \
    15. delta_next_layer[i, j] * kernel
    16. return delta_padded

    当前层为池化层,下一层为卷积层时的上采样:
            
            delta_next_layer: 2-d array,下一层(卷积层)中一个神经元的灵敏度map
            
            kernel: 2-d array,(下一层中一个神经元的一个)卷积核
            
            size_kernel: array,(下一层中一个神经元的一个)卷积核的尺寸
            
            stride: (下一层中一个神经元的一个)卷积核的步长
        
            delta_padded: 2-d array,当前(池化)层中一个神经元的灵敏度map(边缘补零后)
         
    操作流程:

    ①计算下一层(卷积层)中一个神经元的灵敏度map的尺寸size_delta_next_layer。

    ②计算边缘补零后当前层中一个神经元的灵敏度map的尺寸size_delta_padded。这个尺寸取决于

    下一层灵敏度map的尺寸,卷积核的尺寸和步长。

    ③初始化当前层的灵敏度map:delta_padded。

    ④对下一层的灵敏度map进行遍历,在每个位置,将该位置的灵敏度值乘以卷积核,然后加到当前

    层的相应位置。

    1. @classmethod
    2. def upsample_pool(cls, delta_next_layer, type_pooling,
    3. size_kernel, stride):
    4. if type_pooling is "max":
    5. # TODO:
    6. pass
    7. elif type_pooling is "average":
    8. # 池化时实际进行了求和运算,故权值均为1
    9. kernel = np.ones(size_kernel)
    10. delta_padded = cls.upsample_conv_2d(delta_next_layer, kernel,
    11. size_kernel, stride)
    12. return delta_padded

    当前层为卷积层,下一层为池化层时的上采样:
            
            delta_next_layer: 2-d array,下一层(池化层)中一个神经元的灵敏度map
            
            type_pooling: (下一层中一个神经元的)池化核类型,{"max", "average"}
            
            size_kernel: array,(下一层中一个神经元的)池化核尺寸
            
            stride: (下一层中一个神经元的)池化核步长

            delta_padded: 2-d array,当前(卷积)层中一个神经元的灵敏度map(边缘补零后)
         
    操作流程:

    ①首先判断池化类型。如果是max,那么暂未实现(标记为TODO)。如果是average,则进行

    下一步操作。

    ②对于average池化,由于池化过程实际上是求和运算,每个元素的贡献相同,所以权值均为1,

    构造一个全为1的kernel。

    ③调用upsample_conv_2d方法,将下一层的灵敏度map扩展到当前层的尺寸。

    1. @staticmethod
    2. def inv_conv_2d(feature_map, size_kernel, stride, padding, delta):
    3. # 输入特征图尺寸
    4. size_feature_map = np.asarray(feature_map.shape)
    5. if padding == 0:
    6. feature_map_padded = feature_map
    7. elif padding > 0:
    8. # 补零后的特征图
    9. feature_map_padded = np.zeros(size_feature_map + 2 * padding)
    10. feature_map_padded[padding: -padding, padding: -padding] += \
    11. feature_map
    12. output = 0
    13. for i in range(0, feature_map_padded.shape[0], stride):
    14. for j in range(0, feature_map_padded.shape[1], stride):
    15. sub_mat = feature_map_padded[i: np.min([i + size_kernel[0],
    16. feature_map_padded.shape[0]]),
    17. j: np.min([j + size_kernel[1],
    18. feature_map_padded.shape[1]])]
    19. shape_sub_mat = sub_mat.shape
    20. if (shape_sub_mat[0] < size_kernel[0]) or \
    21. (shape_sub_mat[1] < size_kernel[1]):
    22. break
    23. output += delta[int(i/stride), int(j/stride)] * sub_mat
    24. return output

    计算网络代价对卷积层中一个神经元的一个卷积核的偏导数:
            
            feature_map: 2-d array,输入特征图
            
            size_kernel: array,卷积核尺寸
            
            stride: 卷积核的步长
            
            padding: 边缘补零的宽度
            
            delta: 2-d array,卷积层中一个神经元的灵敏度map
     
            output: 2-d array,尺寸与卷积核尺寸相同

    操作流程:

    ①首先获取输入特征图的尺寸,然后根据边缘补零的宽度padding,对输入特征图进行补零操作。

    ②初始化输出output为0。

    ③进行两层循环,以步长`stride`遍历补零后的特征图。

    ④在每次循环中,取出特征图中与卷积核尺寸相同的一个子区域sub_mat。

    ⑤检查子区域的尺寸,如果子区域的尺寸小于卷积核尺寸,那么跳过当前循环。

    ⑥计算子区域与对应的灵敏度值的乘积,并累加到输出值output中。

    1. @staticmethod
    2. def _relu(x):
    3. return np.max([0, x])
    4. @staticmethod
    5. def _sigmoid(x):
    6. return 1 / (1 + np.exp(-x))
    7. @classmethod
    8. def _tanh(cls, x):
    9. return 2 * cls._sigmoid(2 * x) - 1
    1. @classmethod
    2. def activate(cls, feature_map, type_activation):
    3. if type_activation is None:
    4. return feature_map
    5. elif type_activation is "relu":
    6. func = cls._relu
    7. elif type_activation is "sigmoid":
    8. func = cls._sigmoid
    9. elif type_activation is "tanh":
    10. func = cls._tanh
    11. size_feature_map = np.asarray(feature_map.shape)
    12. output = np.empty(size_feature_map)
    13. for i in range(size_feature_map[0]):
    14. for j in range(size_feature_map[1]):
    15. output[i, j] = func(feature_map[i, j])
    16. return output

    激活:

           feature_map: 2-d array,输入特征图
            
            type_activation: 激活函数类型,{"relu", "sigmoid", "tanh", None}
          
            output: 2-d array,激活后特征图,与输入特征图尺寸相同

            根据输入的类型_activation选择对应的激活函数,比如"relu"选择ReLU函数。通过双循环遍历

    输入特征图每个元素,将其输入到选择的激活函数中,得到输出特征图每个元素的值。实现了将输入

    特征图经过不同激活函数处理,输出经激活后的特征图。

    2. test模块

    配置和构建一个简单的卷积神经网络模型:

    1. def config_net():
    2. # 输入层
    3. size_input = np.array([8, 8])
    4. args_input = ("input", (size_input,))
    5. # C1卷积层
    6. connecting_matrix_C1 = np.ones([1, 2])
    7. size_conv_kernel_C1 = np.array([5, 5])
    8. stride_conv_kernel_C1 = 1
    9. padding_conv_C1 = 0
    10. type_activation_C1 = "sigmoid"
    11. args_C1 = ("convoluting", (connecting_matrix_C1, size_conv_kernel_C1,
    12. stride_conv_kernel_C1, padding_conv_C1,
    13. type_activation_C1))
    14. # S2池化层
    15. type_pooling_S2 = "average"
    16. size_pool_kernel_S2 = np.array([2, 2])
    17. stride_pool_kernel_S2 = 2
    18. padding_pool_S2 = 0
    19. type_activation_S2 = "sigmoid"
    20. args_S2 = ("pooling", (type_pooling_S2, size_pool_kernel_S2,
    21. stride_pool_kernel_S2, padding_pool_S2,
    22. type_activation_S2))
    23. # C3卷积层
    24. connecting_matrix_C3 = np.ones([2, 2])
    25. size_conv_kernel_C3 = np.array([2, 2])
    26. stride_conv_kernel_C3 = 1
    27. padding_conv_C3 = 0
    28. type_activation_C3 = "sigmoid"
    29. args_C3 = ("convoluting", (connecting_matrix_C3, size_conv_kernel_C3,
    30. stride_conv_kernel_C3, padding_conv_C3,
    31. type_activation_C3))
    32. # 输出层
    33. n_nodes_output = 2
    34. type_output = "softmax"
    35. args_output = ("output", (n_nodes_output, type_output))
    36. args = (args_input,
    37. args_C1,
    38. args_S2,
    39. args_C3,
    40. args_output)
    41. cnn_net = cnn()
    42. cnn_net.config(args)
    43. return cnn_net

    定义了网络各层的参数:

            输入层大小;

            每个卷积层的连接矩阵、卷积核大小、步长、边缘填充、激活函数;

            池化层类型、池化核大小、步长、边缘填充、激活函数;

             输出层节点数和激活函数。

    将这些参数打包成元组,形成每个层的参数元组,如args_C1定义C1层的参数元组。

    将所有层的参数元组组成一个总的参数元组args,初始化一个cnn网络对象cnn_net,调用cnn_net

    的config方法,传入参数元组args,完成网络结构的配置,返回配置好结构的cnn_net对象。
     

    1. n_train = 10000
    2. X_train = 0.2 * np.random.randn(8, 8, n_train)
    3. Y_train = np.random.randint(2, size=n_train)
    4. for i in range(Y_train.shape[0]):
    5. if Y_train[i] == 0:
    6. X_train[1, :, i] += np.ones(8)
    7. elif Y_train[i] == 1:
    8. X_train[:, 1, i] += np.ones(8)
    9. size_batch = 50
    10. n_epochs = 500
    11. cnn_net = config_net()
    12. cnn_net.fit(X_train, Y_train, size_batch=size_batch, n_epochs=n_epochs)
    13. n_test = 1000
    14. X_test = 0.2 * np.random.randn(8, 8, n_test)
    15. Y_test = np.random.randint(2, size=n_test)
    16. for i in range(Y_test.shape[0]):
    17. if Y_test[i] == 0:
    18. X_test[1, :, i] += np.ones(8)
    19. elif Y_test[i] == 1:
    20. X_test[:, 1, i] += np.ones(8)
    21. correct_rate = cnn_net.test(X_test, Y_test)
    22. plt.figure()
    23. for i in range(cnn_net.layers[1].n_nodes):
    24. plt.subplot(1, 2, i + 1)
    25. plt.imshow(cnn_net.layers[1].nodes[i].conv_kernels[0], cmap="gray")
    26. plt.show()

    生成训练数据和测试数据:

             X_train, X_test shape为(8,8,n);Y_train, Y_test为0或1;

             根据Y标记,在X的不同位置增加一个单位向量 signal

            配置网络结构cnn_net,使用训练数据训练网络cnn_net.fit(),使用测试数据评估网络准确率

    cnn_net.test(),可视化第一个卷积层学习得到的卷积核参数。

    主要步骤包括:数据生成、网络配置、训练、测试、参数可视化

    完整的分类任务流程:生成带有标记的训练和测试数据、搭建并训练卷积神经网络、测试网络效

    果、查看卷积层学习到的特征
     

     


      


            

     

  • 相关阅读:
    python制作小游戏之二2048第二部分
    力扣刷题-数组-双指针法总结-移除元素
    阿里云ossutil使用
    Java读取文件内容写入新文件
    八股文之git
    【算法教程】排列与组合的实现
    冒泡排序、插入排序、选择排序和快速排序的原理
    C++ 共享内存ShellCode跨进程传输
    深度学习入门(5) - RNN
    C#:轮询调度算法​(附完整源码)
  • 原文地址:https://blog.csdn.net/weixin_43961909/article/details/133986745