• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • CNN網絡中卷積層的正向傳播與反向傳播理解

    1. 基礎理論

    1.1 網絡結構梳理

    在CNN網絡模型是建立在傳統神經網絡結構上的,對于一個傳統的神經網絡其結構是這樣的:
    這里寫圖片描述
    從上面可以看出,其模型是全連接的。若是使用一幅512*512大小的圖像作為輸入,隱層1中含有10000個神經元,那么在不算偏置項的情況下,權值參數的個數就是512*512*10000個,如此巨大的參數量不計較內存其優化也是比較困難的。
    在此基礎上學者推出了CNN網絡模型,其較為經典的運用便是LeNet,下面是其結構展示:
    這里寫圖片描述
    可以從上圖中看出,其網絡結構中主要存在兩個之前沒有的成分:卷積層(ConvNet)、池化層(PoolNet)。對于上圖中的卷積層C1可以看到它包含的是6個卷積核,每個卷積核的大小是5*5,因而其參數量是6*(5*5+1)。相比之前的傳統神經網絡在參數量上就小了很多。說道卷積其具體的含義就是對圖像使用掩膜進行運算,與傳統圖像處理中的中值濾波、均值濾波有類似性,可以使用下圖進行解釋:
    這里寫圖片描述
    在上圖中可以看到黃色的滑動窗口在圖像矩陣中進行滑動,從而得到了最后的卷積結果。對于卷積之后圖像的尺寸是有一個計算公式的,當下假設輸入圖像的尺寸W?H,卷積核的尺寸為Wk?Hk,填充用的padding=p,卷積的步長為stride=s。則生成的卷積結果尺寸Wc?Hc的計算公式為:

    Wc=(W+2?p?Wk)/s+1

    Hc=(H+2?p?Hk)/s+1

    那么為什么需要卷積層呢?全連接層通過相應參數的變換也能獲得類似的效果,著這樣做處于以下兩點:
    (1)在使用了卷積層之后,網絡中的參數會少很多,這一點在外文中有敘述
    (2)卷積層在運算的過程中使用了圖像的局部相關性。這是因為一個像素通常是和其周圍的像素的相關性較大,從而組成諸如角點、邊緣之類的特征。但是,相對較遠的像素其相關性就不是那么大了,因而在卷積計算的過程中關注了像素附近的像素,忽略較遠的像素。其實在卷積之后一般會跟上一層Pooling層,它是從附近的卷積結果中提取更具有價值的信息,進一步丟棄掉冗余信息。

    1.2 傳統模型的推導

    對于傳統的神經網絡中正向傳播與反向傳播不是很了解的朋友,可以看看下面的內容:
    1. 神經網絡
    2. 反向傳導算法

    2. 卷積層正向傳播

    2.1 模型參數

    一般來講定義的CNN模型參數是:
    (1)一般我們的卷積核不止一個,比如有K個,那么我們輸入層的輸出,或者說第二層卷積層的對應的輸入就K個。
    (2)卷積核中每個子矩陣的的大小,一般我們都用子矩陣為方陣的卷積核,比如FxF的子矩陣。
    (3)填充padding,我們卷積的時候,為了可以更好的識別邊緣,一般都會在輸入矩陣在周圍加上若干圈的0再進行卷積,加多少圈則P為多少。
    (4)步幅stride(以下簡稱S),即在卷積過程中每次移動的像素距離大小。

    2.2 Caffe中的正向傳播

    通過前面1.1節的講解已經能夠很清楚的知道CNN網絡卷積層正向傳播的過程就是卷積的過程,我們可以很方便地使用4重for循環就可以搞定,但是在Caffe中的卷積計算卻并不是這么回事。那么其計算時怎么回事呢?首先來看Caffe中的前向傳播函數:

    template <typename Dtype>
    void ConvolutionLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
          const vector<Blob<Dtype>*>& top) {
      const Dtype* weight = this->blobs_[0]->cpu_data();
      for (int i = 0; i < bottom.size(); ++i) {
        const Dtype* bottom_data = bottom[i]->cpu_data();
        Dtype* top_data = top[i]->mutable_cpu_data();
        for (int n = 0; n < this->num_; ++n) {
          this->forward_cpu_gemm(bottom_data + n * this->bottom_dim_, weight,
              top_data + n * this->top_dim_);
          if (this->bias_term_) {
            const Dtype* bias = this->blobs_[1]->cpu_data();
            this->forward_cpu_bias(top_data + n * this->top_dim_, bias);
          }
        }
      }
    }

    在其函數中使用了this->forward_cpu_gemm(bottom_data + n * this->bottom_dim_, weight,...這個地方就是調用進行卷積運算了。再來看看這個函數里面發生的了什么

    template <typename Dtype>
    void BaseConvolutionLayer<Dtype>::forward_cpu_gemm(const Dtype* input,
        const Dtype* weights, Dtype* output, bool skip_im2col) {
      const Dtype* col_buff = input;
      if (!is_1x1_) {
        if (!skip_im2col) {
          conv_im2col_cpu(input, col_buffer_.mutable_cpu_data()); //圖像轉換為“列向量”
        }
        col_buff = col_buffer_.cpu_data();
      }
      for (int g = 0; g < group_; ++g) {
        caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, conv_out_channels_ /
            group_, conv_out_spatial_dim_, kernel_dim_,
            (Dtype)1., weights + weight_offset_ * g, col_buff + col_offset_ * g,
            (Dtype)0., output + output_offset_ * g);
      }
    }

    從上面的代碼中可以看到,這里首先對輸入的數據使用conv_im2col_cpu函數進行處理,這個函數到最后其實就是調用了下面這個函數

    //將圖像轉換為“列向量”
    template <typename Dtype>
    void im2col_cpu(const Dtype* data_im, const int channels,
        const int height, const int width, const int kernel_h, const int kernel_w,
        const int pad_h, const int pad_w,
        const int stride_h, const int stride_w,
        const int dilation_h, const int dilation_w,
        Dtype* data_col) {
      const int output_h = (height + 2 * pad_h -
        (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
      const int output_w = (width + 2 * pad_w -
        (dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;
      const int channel_size = height * width;
      for (int channel = channels; channel--; data_im += channel_size) {
        for (int kernel_row = 0; kernel_row < kernel_h; kernel_row++) {
          for (int kernel_col = 0; kernel_col < kernel_w; kernel_col++) {
            int input_row = -pad_h + kernel_row * dilation_h;
            for (int output_rows = output_h; output_rows; output_rows--) {
              if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
                for (int output_cols = output_w; output_cols; output_cols--) {
                  *(data_col++) = 0;
                }
              } else {
                int input_col = -pad_w + kernel_col * dilation_w;
                for (int output_col = output_w; output_col; output_col--) {
                  if (is_a_ge_zero_and_a_lt_b(input_col, width)) {
                    *(data_col++) = data_im[input_row * width + input_col];
                  } else {
                    *(data_col++) = 0;
                  }
                  input_col += stride_w;
                }
              }
              input_row += stride_h;
            }
          }
        }
      }
    }

    這個函數就是將數據轉換成為了“列向量”,其輸出的維度是(Wk?Wk)?(Wo?Ho),在進行卷積運算的時候上面轉換得到的“列向量”是在右邊,卷積核在運算的左邊。上面輸出矩陣的每一行存儲的是和卷積核某一個參數相乘的所有圖像數據,每一列存儲的是一個卷積子操作所需要的數據。

    3. 卷積層反向傳播

    上面說到了在進行卷積操作之前需要將圖像轉換成為“列向量”,這是為什么呢?我的一個直觀的理解便是方便進行反向傳播,也可以看做是對卷積層反向傳播的更加直觀的理解,相當于就是之前將的傳統神經網絡的相連關系,這樣帶來的效果便是更加直觀,像Caffe這樣的庫也是使用這樣的方式進行的。但是這樣的矩陣轉換與運算會消耗內存也耗時,因而Caffe中使用了快速的CuDNN庫來實現。
    在反向傳播中使用的原理與傳統神經網絡的反向傳播原理類似。這里先寫到這里,后面再來補充。

    4. 參考資料

    1.UFLDL教程
    2. 卷積神經網絡(CNN)前向傳播算法
    3. 卷積神經網絡(CNN)反向傳播算法

    版權聲明:本文為m_buddy原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/m_buddy/article/details/80412499

    智能推薦

    MatConvNet的CNN卷積網絡目標函數定義,優化和反向傳播及其Matlab代碼實現

    在CNN卷積網絡中,從輸入前向傳播到誤差反向傳播,如下圖所示: 其中,在反向傳播過程中,還需要把網絡的輸出經過一個目標函數(Objective function),通常也稱為損失函數(Loss function),把網絡的輸出映射為一個實數,反向傳播就是去優化這個損失函數,具體如下圖所示: 在上圖中,f 表示網絡的計算模塊,y 為網絡輸出,g 表示損失函數,網絡的輸出 y 經 g 映射為一個實數 ...

    如何理解神經網絡里的反向傳播

    反向傳播的底層原理是數學求導里的鏈式法則,有空再補充 一般講反向傳播都離不開神經網絡,然后就得有公式和結構圖。OK。 公式占坑。 結構圖: 簡單的問題容易被復雜的公式和結構圖搞的很復雜,作為行動主義者的程序員,上代碼。 代碼輸出: 代碼說明: 代碼實際是中間包含1個隱層的神經網絡,**函數是relu。 反向表現在最終誤差的反向傳播,最直接的誤差能用來更新w2,反向發力后,w1得到了更新,最終整個結...

    CNN | 04卷積反向傳播代碼實現

    4 卷積反向傳播代碼實現 4.1 方法1 完全按照17.3中的講解來實現反向傳播,但是由于有17.2中關于numba幫助,我們在實現代碼時,可以考慮把一些模塊化的計算放到獨立的函數中,用numba在運行時編譯加速。 為了節省篇幅,上面的代碼中做了一些省略,只保留了基本的實現思路,并給出了詳盡的注釋,相信讀者在充分理解17.3的內容的基礎上,可以看懂。 其中,兩個計算量大的函數,一個是計算權重矩陣的...

    【Python實現卷積神經網絡】:從softmax層開始反向傳播+python實現代碼

    1.從Loss函數開始   卷積神經網絡中的Loss函數的數學原理以及python實現請看我之前的文章:【交叉熵】:神經網絡的Loss函數編寫:Softmax+Cross_Entropy,交叉熵與二次代價函數的區別與聯系請訪問:交叉熵代價函數(作用及公式推導),該作者將為什么使用交叉熵而不用二次代價函數講的很清楚。 1.1 softmax函數及其求導1   softmax的函數公式如下:   其中...

    CNN中一些特殊環節的反向傳播(池化層+relu)

    轉載自:https://blog.csdn.net/qq_21190081/article/details/72871704 在深度學習筆記(2)——卷積神經網絡(Convolutional Neural Network)中我們介紹了CNN網絡的前向傳播,這一篇我們介紹CNN的反向傳播,講到反向傳播的時候實質就是一大堆求梯度的數學公式,這些公式其實已經在深度學習筆記(1)&...

    猜你喜歡

    數組刪除其中某個對象的方法

    數組刪除其中的對象或元素,在前端是比較常見的需求。 我現在比較常用的方法如下: 這種方法只適合刪除具有唯一標識的對象。 有沒有想要脫單的小伙伴,加入我們的脫單星球,認識更多優秀的小哥哥小姐姐 特此聲明,星球是免費的,但是創建星球的時候說是必須輸入金額,所以只能先私聊,我再加你免費加入!...

    圖床搭建以及圖床工具的使用

    為什么要用圖床和圖床工具? 比較下面三種md中的圖片url地址(均免費),你會使用哪一種? 選1?由于是本地路徑,文檔分享后給其他人打開后很可能顯示圖片加載失敗。 選2?雖然分享后可以顯示圖片,但能保證加載速度? 選3?我肯定選這種,即兼容2的瀏覽器訪問,又能保證訪問速度。 這樣就可以回答上面的問題了!保證瀏覽器訪問要用圖床,保證加載速度要用圖床工具,又不花錢想想就開心。 除此之外本篇博客還會講解...

    并發編程理論篇

    一、必備知識回顧 計算機又叫電腦,即通電的大腦,發明計算機是為了讓他通電之后能夠像人一樣去工作,并且它比人的工作效率更高,因為可以24小時不間斷 計算機五大組成部分 控制器 運算器 存儲器 輸入設備 輸出設備 計算機的核心真正干活的是CPU(控制器+運算器=中央處理器) 程序要想被計算機運行,它的代碼必須要先由硬盤讀到內存,之后cpu取指再執行 并發 看起來像同時運行的就可以稱之為并發 并行 真正...

    Java LinkedHashMap

    Java LinkedHashMap 前言 Map是我們在實際使用過程中常用的集合,HashMap在Java的實際開發中出鏡率很高,它通過hash算法實現了高效的非線程安全的集合,它有一個缺點就是,用戶插入集合的數據時無序,在我們需要一些有序的map的時候,我們就需要引入另外一個集合:LinkedHashMap。 LinkedHashMap是一個有序的非線程安全的集合,它是HashMap的子類,基...

    Spark Streaming處理文件(本地文件以及hdfs上面的文件)

    標題介紹文件流之前先介紹一下Dstream 下面是來自官網一段的說明,Discretized Streams或DStream是Spark Streaming提供的基本抽象。它表示連續的數據流,可以是從源接收的輸入數據流,也可以是通過轉換輸入流生成的已處理數據流。在內部,DStream由一系列連續的RDD表示,這是Spark對不可變的分布式數據集的抽象(有關更多詳細信息,請參見Spark編程指南)。...

    精品国产乱码久久久久久蜜桃不卡