编写C语言版本的卷积神经网络CNN之三:CNN的误差反向传播过程

原创文章
转载请注册来源 http://blog.csdn.net/tostq
      
       上一节我们介绍了卷积神经网络的前向传播过程,这一节我们重点介绍反向传播过程,反向传播过程反映神经网络的学习训练过程。
误差反向传播方法是神经网络学习的基础,网络上已经有许多相关的内容了,不过关于卷积网络的误差反向传递的公式推导却比较少,而且也不是很清晰,本文将会详细推导这个过程,虽然内容很复杂,但却值得学习.
       首先我们需要知道的是 误差反向传播的 学习方法,实际是梯度下降法求最小误差的权重过程。当然 我们的目的是求误差能量关于参数(权重)的导数.
       梯度下降法更新权重公式如下所示:
       
       这里W表示权重,E表示误差能量,n表示第n轮更新迭代,η表示学习参数,Y表示输出,δ表示局域梯度。
       而另一方面误差能量关于参数(权重)的导数同当前层输入是相关的,所以我们需要一个更好地将当前层误差传递给下一层的量,因为这个 δ同当前层的输出无关,其只是反映了当前层的固定结构,所以我们可以将这个固有性质δ反向传递给下一层,其定义为:
       
       接下来我们分层分析整个网络的反向传播过程。 在本文的卷积神经网络中主要有以下四种情况:

一、输出层(单层神经网络层)
       (1)误差能量定义为实际输出与理想输出的误差
       这里的d是理想预期输出,y指实际输出,i指输出位,本文的网络输出为10位,所以N=10.
       
       (2) 误差能量关于参数(权重)的导数。
       这一层是比较简单的
       
       由于本文是采用Sigmoid系数的激活函数,所以其导数可以求出为:
       
       局域梯度 δ表示为:
       

二、后接输出层的采样层S4
       后接输出层的采样层向多层感知器的隐藏神经元的反向传播是类似的。
       由于这一层没有权重,所以不需要进行权重更新,但是我们也需要将误差能量传递给下一层,所以需要计算 局域梯度δ,其定义如下,这里j指输出图像中的像素序号,S4层共有12*4*4=192个输出像素,所以j=1~192。
       
       另外输出层O5的局域梯度δ也已经计算过了:
       
       由于采样层没有激活函数,所以φ的导数为1,则最终可以得到
       
       通过上式,我们就可以算出由输出层O5传递到S4层的局域梯度δ值。 可以看出传递到采样层各输出像素j的 局域梯度 δ值,实际是相当于与其相连的下层输出的局域梯度δ值乘上相连权重的总和。

三、后接采样层的卷积层C1、C3
       前面为了方便计算,S4层和O5层 的输出都被展开成了一维,所以像素都是以i和j作为标号的,到了C3层往前,我们以像素的坐标m(x,y)来标号,m(x,y)表示第m张输出模板的(x,y)位置的像素。局域梯度δ值定义为:
       
       传递到该像素的误差能量等于所有与其相连的像素误差能量和,这里的i指的m(x,y)采样邻域Θ内的所有像素
       
       因为本文采用的是平均Pooling方法, S4的输出就是该像素邻域内的所有像素的平均值,这里的S指邻域Θ内的所有像素的总数,本文采用的是2*2的采样块,所以S=4。
       
(1) 因此 由S4传递到C3层的 局域梯度 δ值为:
       
       接下来我们依据局域梯度δ值,来计算C3层的权重更新值。
(2)C3层的权重更新值。
       C3层共有6*12个5*5的模板,我们首先定义n=1~6,m=1~12表示模板的标号,s,t表示模板中参数的位置
       
(3)C1层的权重更新公式和局域梯度δ值
       同理,我们也可以得到C1层的权重更新公式,这里的M=6,N=1,而y是指输入图像
       

四、后接卷积层的采样层S2
       这里的n为当前S2层的输出图像序号(n=1~6),n为当前C3层的输出图像序号(m=1~12)。
       
       因此第n块图像的局域梯度δ值为
       

五、误差反向传播过程的代码展示
void cnnbp(CNN* cnn,float* outputData) // 网络的后向传播
{
    int i,j,c,r; // 将误差保存到网络中
    for(i=0;i<cnn->O5->outputNum;i++)
        cnn->e[i]=cnn->O5->y[i]-outputData[i];

    /*从后向前反向计算*/
    // 输出层O5
    for(i=0;i<cnn->O5->outputNum;i++)
        cnn->O5->d[i]=cnn->e[i]*sigma_derivation(cnn->O5->y[i]);

    // S4层,传递到S4层的误差
    // 这里没有激活函数
    nSize outSize={cnn->S4->inputWidth/cnn->S4->mapSize,cnn->S4->inputHeight/cnn->S4->mapSize};
    for(i=0;i<cnn->S4->outChannels;i++)
        for(r=0;r<outSize.r;r++)
            for(c=0;c<outSize.c;c++)
                for(j=0;j<cnn->O5->outputNum;j++){
                    int wInt=i*outSize.c*outSize.r+r*outSize.c+c;
                    cnn->S4->d[i][r][c]=cnn->S4->d[i][r][c]+cnn->O5->d[j]*cnn->O5->wData[j][wInt];
                }

    // C3层
    // 由S4层传递的各反向误差,这里只是在S4的梯度上扩充一倍
    int mapdata=cnn->S4->mapSize;
    nSize S4dSize={cnn->S4->inputWidth/cnn->S4->mapSize,cnn->S4->inputHeight/cnn->S4->mapSize};
    // 这里的Pooling是求平均,所以反向传递到下一神经元的误差梯度没有变化
    for(i=0;i<cnn->C3->outChannels;i++){
        float** C3e=UpSample(cnn->S4->d[i],S4dSize,cnn->S4->mapSize,cnn->S4->mapSize);
        for(r=0;r<cnn->S4->inputHeight;r++)
            for(c=0;c<cnn->S4->inputWidth;c++)
                cnn->C3->d[i][r][c]=C3e[r][c]*sigma_derivation(cnn->C3->y[i][r][c])/(float)(cnn->S4->mapSize*cnn->S4->mapSize);
        for(r=0;r<cnn->S4->inputHeight;r++)
            free(C3e[r]);
        free(C3e);
    }

    // S2层,S2层没有激活函数,这里只有卷积层有激活函数部分
    // 由卷积层传递给采样层的误差梯度,这里卷积层共有6*12个卷积模板
    outSize.c=cnn->C3->inputWidth;
    outSize.r=cnn->C3->inputHeight;
    nSize inSize={cnn->S4->inputWidth,cnn->S4->inputHeight};
    nSize mapSize={cnn->C3->mapSize,cnn->C3->mapSize};
    for(i=0;i<cnn->S2->outChannels;i++){
        for(j=0;j<cnn->C3->outChannels;j++){
            float** corr=correlation(cnn->C3->mapData[i][j],mapSize,cnn->C3->d[j],inSize,full);
            addmat(cnn->S2->d[i],cnn->S2->d[i],outSize,corr,outSize);
            for(r=0;r<outSize.r;r++)
                free(corr[r]);
            free(corr);
        }
        /*
        for(r=0;r<cnn->C3->inputHeight;r++)
            for(c=0;c<cnn->C3->inputWidth;c++)
                // 这里本来用于采样的激活
        */
    }

    // C1层,卷积层
    mapdata=cnn->S2->mapSize;
    nSize S2dSize={cnn->S2->inputWidth/cnn->S2->mapSize,cnn->S2->inputHeight/cnn->S2->mapSize};
    // 这里的Pooling是求平均,所以反向传递到下一神经元的误差梯度没有变化
    for(i=0;i<cnn->C1->outChannels;i++){
        float** C1e=UpSample(cnn->S2->d[i],S2dSize,cnn->S2->mapSize,cnn->S2->mapSize);
        for(r=0;r<cnn->S2->inputHeight;r++)
            for(c=0;c<cnn->S2->inputWidth;c++)
                cnn->C1->d[i][r][c]=C1e[r][c]*sigma_derivation(cnn->C1->y[i][r][c])/(float)(cnn->S2->mapSize*cnn->S2->mapSize);
        for(r=0;r<cnn->S2->inputHeight;r++)
            free(C1e[r]);
        free(C1e);
    }    
}


  • 8
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
### 回答1: 搭建卷神经网络需要以下步骤: 1. 定义输入数据:卷神经网络的输入通常是图像数据。 2. 初始化权重:权重是网络的核心部分,需要随机初始化。 3. 卷层:卷层通过卷核对输入数据进行处理,生成卷层的特征图。 4. 池化层:池化层通过池化操作对卷层特征图进行下采样。 5. 全连接层:全连接层把池化层的输出压缩为一个向量,并对其进行处理。 6. 输出层:输出层通过softmax函数对全连接层的结果进行分类。 7. 定义损失函数:损失函数用于评估网络的性能,常用的损失函数包括交叉熵损失。 8. 优化器:优化器用于更新网络的参数,常用的优化器包括SGD和Adam。 9. 训练网络:通过不断地训练网络,可以使得网络在训练数据上的性能越来越好。 这些步骤可以使用C语言实现。如果您还不熟悉C语言,建议先学习一些C语言的基础知识,然 ### 回答2: 要用C语言来搭建卷神经网络,需要遵循一些步骤和原则。 首先,我们需要定义卷神经网络的结构和层。在C语言中,可以使用结构体来定义一个层和网络的结构。每个层通常包含输入、权重、偏置、激活函数等组成部分。 接下来,我们需要定义函数来执行卷操作和池化操作。卷操作需要在输入数据和权重之间进行计算,并使用激活函数对计算结果进行处理。池化操作则是在卷后对输出进行降采样。这些函数需要按照卷和池化的步骤来进行编码。 然后,我们需要实现前向传播反向传播算法来训练卷神经网络。前向传播算法用于计算预测结果,并将其与真实标签进行比较来计算损失。反向传播算法用于根据损失来更新权重和偏置,以优化网络的性能。 此外,我们还需要实现一些辅助函数,如初始化权重和偏置、导入和导出数据、计算预测精度等。这些辅助函数将帮助我们更好地搭建和测试卷神经网络。 最后,我们需要使用训练数据来训练网络,并使用测试数据来评估网络的性能。通过多次迭代训练和优化,可以提高网络的准确率和泛化能力。 总之,建立一个使用C语言实现的卷神经网络需要定义网络结构、编写和池化函数、实现前向和反向传播算法、编写辅助函数,并使用训练和测试数据进行训练和评估。 ### 回答3: 卷神经网络(Convolutional Neural Network,CNN)是一种经典的神经网络结构,特别适用于图像和语音处理任务。下面我将介绍使用C语言搭建卷神经网络的基本步骤: 1. 定义网络结构:首先,我们需要定义卷神经网络的结构,包括网络的层数、每层的神经元数量、卷核的大小等。可以使用结构体或数组来表示网络结构。 2. 初始化权重:使用随机数或者预训练好的权重来初始化网络中的权重参数。可以使用数组或矩阵来表示网络中的权重。 3. 前向传播:对于给定的输入数据,通过卷计算、池化等操作依次进行前向传播,得到网络的输出。卷操作可以使用嵌套循环实现,池化操作可以使用最大值或平均值等方式。 4. 激活函数:将前向传播得到的输出通过激活函数进行非线性变换。常用的激活函数包括ReLU、sigmoid和tanh等。 5. 损失函数:根据网络的输出与真实标签之间的差异计算损失函数,常用的损失函数包括均方误差和交叉熵等。 6. 反向传播:通过计算损失函数对网络中的权重参数求导,然后根据梯度下降算法更新网络中的权重参数。可以使用链式法则计算梯度,通过嵌套循环实现权重的更新。 7. 训练网络:使用训练数据对网络进行训练,通过反复进行前向传播反向传播来逐渐优化网络的权重参数,使得网络输出与真实标签更加接近。 8. 测试网络:使用测试数据对训练好的网络进行测试,计算网络的准确率或其他性能指标。 总之,通过定义网络结构、初始化权重、前向传播、激活函数、损失函数、反向传播、训练网络和测试网络等步骤,可以用C语言搭建卷神经网络,并应用于图像和语音处理等任务中。需要注意的是,C语言相比其他高级语言(如Python)的代码实现会复杂一些,但通过良好的设计和代码编写,同样可以实现高效的卷神经网络
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值