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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1. cnn_operations模块

cnn_operations类

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

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

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

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

    @staticmethod
    def convolute_2d(feature_map, kernel, size_kernel, stride, padding):
        
        # 输入特征图尺寸
        size_feature_map = np.asarray(feature_map.shape)
        # 输出特征图尺寸
        size_output = [int( \
            (size_feature_map[0] + 2 * padding - size_kernel[0]) / stride + 1),
            int( \
            (size_feature_map[1] + 2 * padding - size_kernel[1]) / stride + 1)]
        
        if padding == 0:
            feature_map_padded = feature_map
        elif padding > 0:
            # 补零后的特征图
            feature_map_padded = np.zeros(size_feature_map + 2 * padding)
            feature_map_padded[padding: -padding, padding: -padding] += \
                    feature_map
                    
        output = np.empty(size_output)
        
        for i in range(0, feature_map_padded.shape[0], stride):
            for j in range(0, feature_map_padded.shape[1], stride):
                sub_mat = feature_map_padded[i: np.min([i + size_kernel[0], 
                        feature_map_padded.shape[0]]), 
                        j: np.min([j + size_kernel[1], 
                        feature_map_padded.shape[1]])]
                shape_sub_mat = sub_mat.shape
                if (shape_sub_mat[0] < size_kernel[0]) or \
                        (shape_sub_mat[1] < size_kernel[1]):
                    break
                
                output[int(i/stride), int(j/stride)] = \
                        np.sum(sub_mat * kernel)
        
        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的相应位置。

    @staticmethod
    def pool(feature_map, type_pooling, size_kernel, stride, padding):
      
        # 输入特征图尺寸
        size_feature_map = np.asarray(feature_map.shape)
        # 输出特征图尺寸
        size_output = [int( \
            (size_feature_map[0] + 2 * padding - size_kernel[0]) / stride + 1),
            int( \
            (size_feature_map[1] + 2 * padding - size_kernel[1]) / stride + 1)]
        
        if padding == 0:
            feature_map_padded = feature_map
        elif padding > 0:
            # 补零后的特征图
            feature_map_padded = np.zeros(size_feature_map + 2 * padding)
            feature_map_padded[padding: -padding, padding: -padding] += \
                    feature_map
                    
        output = np.empty(size_output)
        
        if type_pooling is "max":
            func = np.max
        elif type_pooling is "average":
            #池化层每个神经元有一个权值此处只需求和不需求均值
            func = np.sum
        
        for i in range(0, feature_map_padded.shape[0], stride):
            for j in range(0, feature_map_padded.shape[1], stride):
                sub_mat = feature_map_padded[i: np.min([i + size_kernel[0], 
                        feature_map_padded.shape[0]]), 
                        j: np.min([j + size_kernel[1], 
                        feature_map_padded.shape[1]])]
                shape_sub_mat = sub_mat.shape
                if (shape_sub_mat[0] < size_kernel[0]) or \
                        (shape_sub_mat[1] < size_kernel[1]):
                    break
                
                output[int(i/stride), int(j/stride)] = func(sub_mat)
                
        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的相应位置。

    @staticmethod
    def upsample_conv_2d(delta_next_layer, kernel, size_kernel, stride):
       
        # 下一层卷积层中一个神经元的灵敏度map的尺寸
        size_delta_next_layer = np.asarray(delta_next_layer.shape)
        # 边缘补零后当前层一中一个神经元灵敏度map的尺寸
        size_delta_padded = \
                [size_delta_next_layer[0] * stride + size_kernel[0] - stride, 
                 size_delta_next_layer[1] * stride + size_kernel[1] - stride]
        delta_padded = np.zeros(size_delta_padded)
        for i in range(delta_next_layer.shape[0]):
            for j in range(delta_next_layer.shape[1]):
                # 卷积核不翻转
                delta_padded[i * stride: i * stride + size_kernel[0], 
                             j * stride: j * stride + size_kernel[1]] += \
                                     delta_next_layer[i, j] * kernel
        
        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的尺寸卷积核的尺寸和步长。

③初始化当前层的灵敏度mapdelta_padded。

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

层的相应位置。

    @classmethod
    def upsample_pool(cls, delta_next_layer, type_pooling, 
                      size_kernel, stride):
       
        if type_pooling is "max":
            # TODO: 
            pass
        
        elif type_pooling is "average":
            # 池化时实际进行了求和运算故权值均为1
            kernel = np.ones(size_kernel)
            delta_padded = cls.upsample_conv_2d(delta_next_layer, kernel, 
                                                size_kernel, stride)
            
        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扩展到当前层的尺寸。

    @staticmethod
    def inv_conv_2d(feature_map, size_kernel, stride, padding, delta):
       
        # 输入特征图尺寸
        size_feature_map = np.asarray(feature_map.shape)
        
        if padding == 0:
            feature_map_padded = feature_map
        elif padding > 0:
            # 补零后的特征图
            feature_map_padded = np.zeros(size_feature_map + 2 * padding)
            feature_map_padded[padding: -padding, padding: -padding] += \
                    feature_map
                    
        output = 0
        for i in range(0, feature_map_padded.shape[0], stride):
            for j in range(0, feature_map_padded.shape[1], stride):
                sub_mat = feature_map_padded[i: np.min([i + size_kernel[0], 
                        feature_map_padded.shape[0]]), 
                        j: np.min([j + size_kernel[1], 
                        feature_map_padded.shape[1]])]
                shape_sub_mat = sub_mat.shape
                
                if (shape_sub_mat[0] < size_kernel[0]) or \
                        (shape_sub_mat[1] < size_kernel[1]):
                    break
                
                output += delta[int(i/stride), int(j/stride)] * sub_mat
                
        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中。

    @staticmethod
    def _relu(x):
        
        return np.max([0, x])
    
    
    @staticmethod
    def _sigmoid(x):
        return 1 / (1 + np.exp(-x))
    
    
    @classmethod
    def _tanh(cls, x):
        return 2 * cls._sigmoid(2 * x) - 1
    
    @classmethod
    def activate(cls, feature_map, type_activation):
      
        if type_activation is None:
            return feature_map
        
        elif type_activation is "relu":
            func = cls._relu
        elif type_activation is "sigmoid":
            func = cls._sigmoid
        elif type_activation is "tanh":
            func = cls._tanh
            
        size_feature_map = np.asarray(feature_map.shape)
        
        output = np.empty(size_feature_map)
        for i in range(size_feature_map[0]):
            for j in range(size_feature_map[1]):
                output[i, j] = func(feature_map[i, j])
                
        return output
    

激活

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

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

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

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

2. test模块

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

def config_net():
   
    # 输入层
    size_input = np.array([8, 8])
    args_input = ("input", (size_input,))
    
    # C1卷积层
    connecting_matrix_C1 = np.ones([1, 2])
    size_conv_kernel_C1 = np.array([5, 5])
    stride_conv_kernel_C1 = 1
    padding_conv_C1 = 0
    type_activation_C1 = "sigmoid"
    args_C1 = ("convoluting", (connecting_matrix_C1, size_conv_kernel_C1, 
                               stride_conv_kernel_C1, padding_conv_C1, 
                               type_activation_C1))
    
    # S2池化层
    type_pooling_S2 = "average"
    size_pool_kernel_S2 = np.array([2, 2])
    stride_pool_kernel_S2 = 2
    padding_pool_S2 = 0
    type_activation_S2 = "sigmoid"
    args_S2 = ("pooling", (type_pooling_S2, size_pool_kernel_S2, 
                           stride_pool_kernel_S2, padding_pool_S2, 
                           type_activation_S2))
    
    # C3卷积层
    connecting_matrix_C3 = np.ones([2, 2])
    size_conv_kernel_C3 = np.array([2, 2])
    stride_conv_kernel_C3 = 1
    padding_conv_C3 = 0
    type_activation_C3 = "sigmoid"
    args_C3 = ("convoluting", (connecting_matrix_C3, size_conv_kernel_C3, 
                               stride_conv_kernel_C3, padding_conv_C3, 
                               type_activation_C3))
    
    # 输出层
    n_nodes_output = 2
    type_output = "softmax"
    args_output = ("output", (n_nodes_output, type_output))
    
    args = (args_input,
            args_C1,
            args_S2,
            args_C3,
            args_output)
    cnn_net = cnn()
    cnn_net.config(args)
    
    return cnn_net

定义了网络各层的参数:

        输入层大小

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

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

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

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

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

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

n_train = 10000
X_train = 0.2 * np.random.randn(8, 8, n_train)
Y_train = np.random.randint(2, size=n_train)
for i in range(Y_train.shape[0]):
    if Y_train[i] == 0:
        X_train[1, :, i] += np.ones(8)
    elif Y_train[i] == 1:
        X_train[:, 1, i] += np.ones(8)

size_batch = 50
n_epochs = 500
cnn_net = config_net()
cnn_net.fit(X_train, Y_train, size_batch=size_batch, n_epochs=n_epochs)

n_test = 1000
X_test = 0.2 * np.random.randn(8, 8, n_test)
Y_test = np.random.randint(2, size=n_test)
for i in range(Y_test.shape[0]):
    if Y_test[i] == 0:
        X_test[1, :, i] += np.ones(8)
    elif Y_test[i] == 1:
        X_test[:, 1, i] += np.ones(8)
        
correct_rate = cnn_net.test(X_test, Y_test)

plt.figure()
for i in range(cnn_net.layers[1].n_nodes):
    plt.subplot(1, 2, i + 1)
    plt.imshow(cnn_net.layers[1].nodes[i].conv_kernels[0], cmap="gray")
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()可视化第一个卷积层学习得到的卷积核参数。

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

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

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

 


  


        

 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: 机器学习