编写C语言版本的卷积神经网络CNN之二:CNN网络的总体结构

原创文章
转载请注册来源 http://blog.csdn.net/tostq

      上一节我们总体介绍项目并说明Minst手写数字数据库的使用,这一节我们将重点介绍CNN网络总体结构。

      上图我们已经非常熟悉,其为Yann在1998年介绍的LeNet-5网络的结构,其刚被提出,就在学术和工业领域上得到广泛应用,而本文的CNN卷积网络却是如下图所示(博主自己画的,画这个图还是挺麻烦的:L,不清晰请原谅),和LeNet-5相比主要有以下三点不同:
      (1)LeNet-5给输入图像增加了一圈黑边,使输入图像大小变成了32x32,这样的目的是为了在下层卷积过程中保留更多原图的信息。
      (2)LeNet-5的卷积层C3只有16个模板,得到16个输出,而本文的卷积层C3由于是全连接,所以有6*12个模板,得到12个输出图像
      (3)LeNet-5多了两种,分别是C5到F6的全连接神经网络层,和F6到OUTPUT高斯连接网络层。而本文的直接由采样层S4直接经过一层全连接神经网络层到OUTPUT。
      下面我们将重点介绍各层的结构及数据的前向传播。


一、各层的解释
(1) 卷积层C1
      输入为28x28的灰度图像,灰度图像分别同6个5x5的模板进行卷积操作,分别得到了6个24x24的卷积图像,图像里的每个像素加上一个权重,并经过一个激活函数,得到该层的输出。
      所以该层的相关参数为:6个5x5的模板参数w,6个模板对应的权重参数b,共6x5x5+6个参数
Tips:
      关于激活函数:激活函数我们在学习神经网络时就已经接触过了,其主要有两个目的,第一是将数据钳制在一定范围内(如Sigmoid函数将数据压缩在-1到1之间),不太高也不太低,第二是用来加入非线性因素的,因为线性模型的表达能力不够。传统神经网络中最常用的两个激活函数Sigmoid系和Tanh系,而Sigmoid系(Logistic-Sigmoid、Tanh-Sigmoid)被视为神经网络的核心所在。本文的例子就是Sigmoid系。

      近年来,在深度学习领域中效果最好,应用更为广泛的是ReLu激活函数,其相较于Sigmoid系,主要变化有三点:①单侧抑制 ②相对宽阔的兴奋边界 ③稀疏激活性。特别是在神经科学方面,除了新的激活频率函数之外,神经科学家还发现了的稀疏激活性广泛存在于大脑的神经元, 神经元编码工作方式具有稀疏性和分布性。 大脑同时被激活的神经元只有1~4%。  从信号方面来看,即神经元同时只对输入信号的少部分选择性响应,大量信号被刻意的屏蔽了,这样可以提高学习的精度,更好更快地提取稀疏特征。而 在经验规则的初始化W之后,传统的Sigmoid系函数同时近乎有一半的神经元被激活,这不符合神经科学的研究,而且会给深度网络训练带来巨大问题。 Softplus照顾到了新模型的前两点,却没有稀疏激活性。因而,校正函数max(0,x)即ReLu函数成了最大赢家。

(2)采样层S2及S4(Pooling层)
      采样层S又名Pooling层,Pooling主要是为了减少数据处理的维度,常见的pooling方法有max pooling和average pooling等。
       max pooling 就是选择当前块内最大像素值来表示当前局部块
       average pooling 就是选择当前块的像素值平均值来代替
      本文的选择Pooling方法是average pooling,而使用广泛效果较好的方法却是max pooling。(看到这里,你可能会吐槽,为什么不用效果好,因为平均计算相比而言,有那么一丢丢简单!)
(3) 卷积层C3
      这里的卷积层是一个全连接的卷积层。输出的卷积公式如下,这里I表示图像,W表示卷积模板,b表示偏重,φ表示激活函数,i表示输入图像序号(i=1~6),j表示该层输出图像序号(j=1~12)

      由此可以看到在卷积层C3中输入为6个12x12的图像,输出为12个8x8的图像
      所需要训练的参数有6x12个5x5的卷积模板w和12个偏重b(每个模板对应的偏重都是相同的)
      而实际上由于神经网络的稀疏结构和减少训练时间的需要,该卷积层一般不是利用全连接的,就比如前面介绍LeNet-5网络,只需要利用16个卷积模板就可以了,而不是全连接的6x12个,其连接方法如下,其最终得到16个输出图像。

      这里X表示选择卷积,比如第0张输出图像是由第0、1、2张输入图像分别同第0个卷积模板卷积相加,再加上偏重,经过激活函数得到的。而第15张图像是由第0、1、2、3、4、5张输入图像分别同第15个卷积模板卷积相加得到的。
(4)输出层O5:
      采样层S4后,我们将得到12张4*4的图像,将所有图像展开成一维,就得到了12*4*4=192位的向量。
输出层是由输入192位,输出10位的全连接单层神经网络,共有10个神经元构成,每个神经元都同192位输入相连,即都有192位的输入和1位输出,其处理公式如下,这里j表示输出神经元的序号,i表示输入的序号。

      所以该层参数共有192*10个权重w,和10个偏重b

二、卷积神经网络的相关数据结构
      这个卷积网络主要有五层网络,主要结构是卷积层、采样层(Pooling)、卷积层、采样层(Pooling)和全连接的单层神经网络层(输出层),所以我们建立了三个基本层的结构及一个总的卷积网络结构。
      这里结构内除了必要的权重参数,而需要记录该层输入输出数据y,及需要传递到下一层的局部梯度d。
(1)卷积层
// 卷积层
typedef struct convolutional_layer{
    int inputWidth;   //输入图像的宽
    int inputHeight;  //输入图像的长
    int mapSize;      //特征模板的大小,模板一般都是正方形

    int inChannels;   //输入图像的数目
    int outChannels;  //输出图像的数目

    // 关于特征模板的权重分布,这里是一个四维数组
    // 其大小为inChannels*outChannels*mapSize*mapSize大小
    // 这里用四维数组,主要是为了表现全连接的形式,实际上卷积层并没有用到全连接的形式
    // 这里的例子是DeapLearningToolboox里的CNN例子,其用到就是全连接
    float**** mapData;     //存放特征模块的数据
    float**** dmapData;    //存放特征模块的数据的局部梯度

    float* basicData;   //偏置,偏置的大小,为outChannels
    bool isFullConnect; //是否为全连接
    bool* connectModel; //连接模式(默认为全连接)

    // 下面三者的大小同输出的维度相同
    float*** v; // 进入激活函数的输入值
    float*** y; // 激活函数后神经元的输出

    // 输出像素的局部梯度
    float*** d; // 网络的局部梯度,δ值  
}CovLayer;

(2)采样层
// 采样层 pooling
typedef struct pooling_layer{
    int inputWidth;   //输入图像的宽
    int inputHeight;  //输入图像的长
    int mapSize;      //特征模板的大小

    int inChannels;   //输入图像的数目
    int outChannels;  //输出图像的数目

    int poolType;     //Pooling的方法
    float* basicData;   //偏置

    float*** y; // 采样函数后神经元的输出,无激活函数
    float*** d; // 网络的局部梯度,δ值
}PoolLayer;

(3)全连接的单层神经网络
// 输出层 全连接的神经网络
typedef struct nn_layer{
    int inputNum;   //输入数据的数目
    int outputNum;  //输出数据的数目

    float** wData; // 权重数据,为一个inputNum*outputNum大小
    float* basicData;   //偏置,大小为outputNum大小

    // 下面三者的大小同输出的维度相同
    float* v; // 进入激活函数的输入值
    float* y; // 激活函数后神经元的输出
    float* d; // 网络的局部梯度,δ值

    bool isFullConnect; //是否为全连接
}OutLayer;

(4)各层共同组成一个完整的卷积网络
typedef struct cnn_network{
    int layerNum;
    CovLayer* C1;
    PoolLayer* S2;
    CovLayer* C3;
    PoolLayer* S4;
    OutLayer* O5;

    float* e; // 训练误差
    float* L; // 瞬时误差能量
}CNN;

(5) 另外还有一个用于存放训练参量的结构
typedef struct train_opts{
    int numepochs; // 训练的迭代次数
    float alpha; // 学习速率
}CNNOpts;

三、卷积神经网络的初始化
       卷积神经网络的初始化主要包含了各数据的 空间 初始化及权重的随机赋值,没有什么复杂,按照结构分配空间就可以了,这里不再详细赘述了,可以直接参考代码内cnnsetup()函数

四、卷积神经网络的前向传播过程
       前向传播过程实际上就是指输入图像数据,得到输出结果的过程,而后向传播过程就是将输出结果的误差由后向前传递给各层,各层依次调整权重的过程。所以前向传播过程相比而是比较直观,而且简单的。
前向传播过程在项目中主要是由cnnff函数完成,下面我们将按层介绍其过程
(1)卷积层C1
       卷积层C1共有6个卷积模板,每个模板同输入图像卷积将会得到一个输出,即共6个输出,以下是图像的卷积公式:

       C1层的相关代码,这里cov函数是卷积函数,在mat.cpp是具体的定义,activation_Sigma是激活函数
    int outSizeW=cnn->S2->inputWidth;
    int outSizeH=cnn->S2->inputHeight;
    // 第一层的传播
    int i,j,r,c;
    // 第一层输出数据
    nSize mapSize={cnn->C1->mapSize,cnn->C1->mapSize};
    nSize inSize={cnn->C1->inputWidth,cnn->C1->inputHeight};
    nSize outSize={cnn->S2->inputWidth,cnn->S2->inputHeight};
    for(i=0;i<(cnn->C1->outChannels);i++){
        for(j=0;j<(cnn->C1->inChannels);j++){
            float** mapout=cov(cnn->C1->mapData[j][i],mapSize,inputData,inSize,valid);
            addmat(cnn->C1->v[i],cnn->C1->v[i],outSize,mapout,outSize);
            for(r=0;r<outSize.r;r++)
                free(mapout[r]);
            free(mapout);
        }
        for(r=0;r<outSize.r;r++)
            for(c=0;c<outSize.c;c++)
                cnn->C1->y[i][r][c]=activation_Sigma(cnn->C1->v[i][r][c],cnn->C1->basicData[i]);
    }
(2)采样层S2,avgPooling是平均Pooling函数
   
 // 第二层的输出传播S2,采样层
    outSize.c=cnn->C3->inputWidth;
    outSize.r=cnn->C3->inputHeight;
    inSize.c=cnn->S2->inputWidth;
    inSize.r=cnn->S2->inputHeight;
    for(i=0;i<(cnn->S2->outChannels);i++){
        if(cnn->S2->poolType==AvePool)
            avgPooling(cnn->S2->y[i],outSize,cnn->C1->y[i],inSize,cnn->S2->mapSize);
    }

(3)卷积层C3,同C1很类似    
// 第三层输出传播,这里是全连接
    outSize.c=cnn->S4->inputWidth;
    outSize.r=cnn->S4->inputHeight;
    inSize.c=cnn->C3->inputWidth;
    inSize.r=cnn->C3->inputHeight;
    mapSize.c=cnn->C3->mapSize;
    mapSize.r=cnn->C3->mapSize;
    for(i=0;i<(cnn->C3->outChannels);i++){
        for(j=0;j<(cnn->C3->inChannels);j++){
            float** mapout=cov(cnn->C3->mapData[j][i],mapSize,cnn->S2->y[j],inSize,valid);
            addmat(cnn->C3->v[i],cnn->C3->v[i],outSize,mapout,outSize);
            for(r=0;r<outSize.r;r++)
                free(mapout[r]);
            free(mapout);
        }
        for(r=0;r<outSize.r;r++)
            for(c=0;c<outSize.c;c++)
                cnn->C3->y[i][r][c]=activation_Sigma(cnn->C3->v[i][r][c],cnn->C3->basicData[i]);
    }
(4)采样层S4,同S2很类似  
// 第四层的输出传播
    inSize.c=cnn->S4->inputWidth;
    inSize.r=cnn->S4->inputHeight;
    outSize.c=inSize.c/cnn->S4->mapSize;
    outSize.r=inSize.r/cnn->S4->mapSize;
    for(i=0;i<(cnn->S4->outChannels);i++){
        if(cnn->S4->poolType==AvePool)
            avgPooling(cnn->S4->y[i],outSize,cnn->C3->y[i],inSize,cnn->S4->mapSize);
    }
(5)输出层O5
    // 输出层O5的处理
    // 首先需要将前面的多维输出展开成一维向量
    float* O5inData=(float*)malloc((cnn->O5->inputNum)*sizeof(float)); 
    for(i=0;i<(cnn->S4->outChannels);i++)
        for(r=0;r<outSize.r;r++)
            for(c=0;c<outSize.c;c++)
                O5inData[i*outSize.r*outSize.c+r*outSize.c+c]=cnn->S4->y[i][r][c];

    nSize nnSize={cnn->O5->inputNum,cnn->O5->outputNum};
    nnff(cnn->O5->v,O5inData,cnn->O5->wData,cnn->O5->basicData,nnSize);
    for(i=0;i<cnn->O5->outputNum;i++)
        cnn->O5->y[i]=activation_Sigma(cnn->O5->v[i],cnn->O5->basicData[i]);  // 这里多加了一个bias, 感谢wydbyxr的指出!
    free(O5inData);
}

 项目代码地址: https://github.com/tostq/DeepLearningC/tree/master/CNN

  • 15
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 30
    评论
### 回答1: 我不是很熟悉C语言,但是我可以给你参考一个卷积神经网络算法的示例:// 导入库 #include "stdio.h" #include "math.h"// 定义结构体 struct CNN { int input_width; int input_height; int input_depth; int filter_width; int filter_height; int filter_depth; int stride; int padding; int output_width; int output_height; int output_depth; };// 初始化结构体 void init_CNN(struct CNN *cnn) { cnn->input_width = 8; cnn->input_height = 8; cnn->input_depth = 3; cnn->filter_width = 3; cnn->filter_height = 3; cnn->filter_depth = 3; cnn->stride = 1; cnn->padding = 0; cnn->output_width = 6; cnn->output_height = 6; cnn->output_depth = 3; }// 定义卷积函数 float conv2d(float input[][], float filter[][], int stride, int padding) { int input_width = input_width; int input_height = input_height; int filter_width = filter_width; int filter_height = filter_height; int output_width = (input_width - filter_width + 2 * padding) / stride + 1; int output_height = (input_height - filter_height + 2 * padding) / stride + 1; float output[output_width][output_height]; // 使用双重循环实现卷积 for (int i = 0; i < output_width; i++) { for (int j = 0; j < output_height; j++) { float sum = 0.0; // 卷积核的滑动窗口 for (int k = 0; k < filter_width; k++) { for (int l = 0; l < filter_height; l++) { int x = i * stride + k - padding; int y = j * stride + l - padding; // 判断是否越界 if (x >= 0 && x < input_width && y >= 0 && y < input_height) { // 计算卷积 sum += input[x][y] * filter[k][l]; } } } output[i][j] = sum; } } return output; }int main(int argc, char *argv[]) { struct CNN cnn; init_CNN(&cnn); float input[cnn.input_width][cnn.input_height]; float filter[cnn.filter_width][cnn.filter_height]; float output = conv2d(input, filter, cnn.stride, cnn.padding); return 0; } ### 回答2: 卷积神经网络(Convolutional Neural Network,CNN)是一种深度学习算法,主要用于图像识别和计算机视觉任务。以下是一个用C语言实现CNN算法的基本框架: 1. 导入所需的头文件和库: ``` #include <stdio.h> #include <stdlib.h> #include <math.h> #include <time.h> ``` 2. 定义网络的超参数: ``` #define INPUT_SIZE 28 #define CONV_SIZE 3 #define NUM_FILTERS 16 #define POOL_SIZE 2 #define OUTPUT_SIZE 10 ``` 3. 定义卷积层的函数: ``` float convolution(float input[INPUT_SIZE][INPUT_SIZE], float filter[CONV_SIZE][CONV_SIZE]) { float result = 0.0; for (int i = 0; i < CONV_SIZE; i++) { for (int j = 0; j < CONV_SIZE; j++) { result += input[i][j] * filter[i][j]; } } return result; } ``` 4. 定义池化层的函数: ``` float pooling(float input[INPUT_SIZE][INPUT_SIZE], int startX, int startY) { float max = 0.0; for (int i = startX; i < startX + POOL_SIZE; i++) { for (int j = startY; j < startY + POOL_SIZE; j++) { if (input[i][j] > max) { max = input[i][j]; } } } return max; } ``` 5. 定义前向传播的函数: ``` void forward(const float input[INPUT_SIZE][INPUT_SIZE], float output[OUTPUT_SIZE]) { float convOutput[NUM_FILTERS][INPUT_SIZE-CONV_SIZE+1][INPUT_SIZE-CONV_SIZE+1]; float poolOutput[NUM_FILTERS][INPUT_SIZE/POOL_SIZE][INPUT_SIZE/POOL_SIZE]; // 卷积层 for (int k = 0; k < NUM_FILTERS; k++) { for (int i = 0; i < INPUT_SIZE-CONV_SIZE+1; i++) { for (int j = 0; j < INPUT_SIZE-CONV_SIZE+1; j++) { float convResult = convolution(input, filters[k]); convOutput[k][i][j] = convResult; } } } // 池化层 for (int k = 0; k < NUM_FILTERS; k++) { for (int i = 0; i < INPUT_SIZE/POOL_SIZE; i++) { for (int j = 0; j < INPUT_SIZE/POOL_SIZE; j++) { int startX = i * POOL_SIZE; int startY = j * POOL_SIZE; float max = pooling(convOutput[k], startX, startY); poolOutput[k][i][j] = max; } } } // 全连接层 for (int i = 0; i < OUTPUT_SIZE; i++) { float sum = 0.0; for (int k = 0; k < NUM_FILTERS; k++) { for (int j = 0; j < INPUT_SIZE/POOL_SIZE; j++) { for (int l = 0; l < INPUT_SIZE/POOL_SIZE; l++) { sum += poolOutput[k][j][l] * weights[i][k][j][l]; } } } output[i] = sigmoid(sum); } } ``` 以上是一个简单的C语言实现的卷积神经网络算法。这只是一个基础的框架,实际应用中可能需要进一步优化和完善。 ### 回答3: 卷积神经网络CNN)是一种常用于图像分类和对象识别等任务的深度学习算法。下面是一个用C语言编写的简单卷积神经网络算法的示例代码: ```c #include <stdio.h> #include <stdlib.h> #define IMAGE_SIZE 28 #define NUM_CLASSES 10 #define CONV_SIZE 3 #define POOL_SIZE 2 typedef struct { float input_image[IMAGE_SIZE][IMAGE_SIZE]; float weights[NUM_CLASSES][CONV_SIZE][CONV_SIZE]; float bias[NUM_CLASSES]; float output[NUM_CLASSES]; } CNN; void convolution(CNN* cnn) { int i, j, m, n, k, l; int stride = 1; int padding = 1; float sum; for (k = 0; k < NUM_CLASSES; k++) { for (i = 0; i < IMAGE_SIZE; i += stride) { for (j = 0; j < IMAGE_SIZE; j += stride) { sum = 0.0; for (m = 0; m < CONV_SIZE; m++) { for (n = 0; n < CONV_SIZE; n++) { int row = i - padding + m; int col = j - padding + n; if (row >= 0 && row < IMAGE_SIZE && col >= 0 && col < IMAGE_SIZE) { sum += cnn->input_image[row][col] * cnn->weights[k][m][n]; } } } cnn->output[k] = sum + cnn->bias[k]; } } } } void pooling(CNN* cnn) { int i, j, m, n; int stride = 2; float max; for (m = 0; m < NUM_CLASSES; m++) { for (i = 0; i < IMAGE_SIZE; i += stride) { for (j = 0; j < IMAGE_SIZE; j += stride) { max = 0.0; for (n = 0; n < POOL_SIZE; n++) { for (n = 0; n < POOL_SIZE; n++) { int row = i + m; int col = j + n; if (row < IMAGE_SIZE && col < IMAGE_SIZE) { if (cnn->output[m] > max) { max = cnn->output[m]; } } } } cnn->output[m] = max; } } } } int main() { // Create a CNN instance CNN cnn; // Initialize the input image, weights, and bias values // ... // Perform convolution and pooling operations convolution(&cnn); pooling(&cnn); // Print the final output for (int i = 0; i < NUM_CLASSES; i++) { printf("Output for class %d: %f\n", i, cnn.output[i]); } return 0; } ``` 这个示例代码中定义了一个CNN结构体,包含了输入图像、权重、偏置和输出等属性。通过调用convolution和pooling函数,可以分别实现卷积和池化操作。在main函数中通过创建一个CNN实例,初始化输入和参数,最后打印出最终的输出结果。请注意,此示例代码仅用于展示基本的卷积神经网络的实现,并不包括完整的训练过程和其他优化技术。
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值