1. 程式人生 > 其它 >C++實現神經網路之一 | Net類的設計和神經網路的初始化

C++實現神經網路之一 | Net類的設計和神經網路的初始化

閒言少敘,直接開始

既然是要用C++來實現,那麼我們自然而然的想到設計一個神經網路類來表示神經網路,這裡我稱之為Net類。由於這個類名太過普遍,很有可能跟其他人寫的程式衝突,所以我的所有程式都包含在namespace liu中,由此不難想到我姓劉。在之前的部落格反向傳播演算法資源整理中,我列舉了幾個比較不錯的資源。對於理論不熟悉而且學習精神的同學可以出門左轉去看看這篇文章的資源。這裡假設讀者對於神經網路的基本理論有一定的瞭解。

神經網路要素

在真正開始coding之前還是有必要交代一下神經網路基礎,其實也就是設計類和寫程式的思路。簡而言之,神經網路的包含幾大要素:

  • 神經元節點
  • 層(layer)
  • 權值(weights)
  • 偏置項(bias)

神經網路的兩大計算過程分別是前向傳播和反向傳播過程。每層的前向傳播分別包含加權求和(卷積?)的線性運算和啟用函式的非線性運算。反向傳播主要是用BP演算法更新權值。 雖然裡面還有很多細節,但是對於作為第一篇的本文來說,以上內容足夠了。

Net——基於mat

神經網路中的計算幾乎都可以用矩陣計算的形式表示,這也是我用OpenCV的Mat類的原因之一,它

提供了非常完善的、充分優化過的各種矩陣運算方法;另一個原因是我最熟悉的庫就是OpenCV......有很多比較好的庫和框架在實現神經網路的時候會用很多類來表示不同的部分。比如Blob類表示資料,Layer類表示各種層,Optimizer類來表示各種優化演算法。但是這裡沒那麼複雜,主要還是能力有限,只用一個Net類表示神經網路。

還是直接讓程式說話,Net類包含在Net.h中,大致如下:

#ifndef NET_H
#define NET_H
#endif // NET_H
#pragma once
#include <iostream>
#include<opencv2corecore.hpp>
#include<opencv2highguihighgui.hpp>
//#include<iomanip>
#include"Function.h"
namespace liu
{
   class Net
   {
   public:
       std::vector<int> layer_neuron_num;
       std::vector<cv::Mat> layer;
       std::vector<cv::Mat> weights;
       std::vector<cv::Mat> bias;
   public:
       Net() {};
       ~Net() {};
       //Initialize net:genetate weights matrices、layer matrices and bias matrices
       // bias default all zero
       void initNet(std::vector<int> layer_neuron_num_);
       //Initialise the weights matrices.
       void initWeights(int type = 0, double a = 0., double b = 0.1);
       //Initialise the bias matrices.
       void initBias(cv::Scalar& bias);
       //Forward
       void farward();
       //Forward
       void backward();
   protected:
       //initialise the weight matrix.if type =0,Gaussian.else uniform.
       void initWeight(cv::Mat &dst, int type, double a, double b);
       //Activation function
       cv::Mat activationFunction(cv::Mat &x, std::string func_type);
       //Compute delta error
       void deltaError();
       //Update weights
       void updateWeights();
   };
}

這不是完整的形態,只是對應於本文內容的一個簡化版,簡化之後看起來更加清晰明瞭。

成員變數與成員函式

現在Net類只有四個成員變數,分別是:

  • 每一層神經元數目(layerneuronnum)
  • 層(layer)
  • 權值矩陣(weights)
  • 偏置項(bias)

權值用矩陣表示就不用說了,需要說明的是,為了計算方便,這裡每一層和偏置項也用Mat表示,每一層和偏置都用一個單列矩陣來表示。

Net類的成員函式除了預設的建構函式和解構函式,還有:

  • initNet():用來初始化神經網路
  • initWeights():初始化權值矩陣,呼叫initWeight()函式
  • initBias():初始化偏置項
  • forward():執行前向運算,包括線性運算和非線性啟用,同時計算誤差
  • backward():執行反向傳播,呼叫updateWeights()函式更新權值。

這些函式已經是神經網路程式核心中的核心。剩下的內容就是慢慢實現了,實現的時候需要什麼新增什麼,逢山開路,遇河架橋。

神經網路初始化——initNet()函式

先說一下initNet()函式,這個函式只接受一個引數——每一層神經元數目,然後藉此初始化神經網路。這裡所謂初始化神經網路的含義是:生成每一層的矩陣、每一個權值矩陣和每一個偏置矩陣。聽起來很簡單,其實也很簡單。

實現程式碼在Net.cpp中:

   //Initialize net
   void Net::initNet(std::vector<int> layer_neuron_num_)
   {
       layer_neuron_num = layer_neuron_num_;
       //Generate every layer.
       layer.resize(layer_neuron_num.size());
       for (int i = 0; i < layer.size(); i++)
       {
           layer[i].create(layer_neuron_num[i], 1, CV_32FC1);
       }
       std::cout << "Generate layers, successfully!" << std::endl;
       //Generate every weights matrix and bias
       weights.resize(layer.size() - 1);
       bias.resize(layer.size() - 1);
       for (int i = 0; i < (layer.size() - 1); ++i)
       {
           weights[i].create(layer[i + 1].rows, layer[i].rows, CV_32FC1);
           //bias[i].create(layer[i + 1].rows, 1, CV_32FC1);
           bias[i] = cv::Mat::zeros(layer[i + 1].rows, 1, CV_32FC1);
       }
       std::cout << "Generate weights matrices and bias, successfully!" << std::endl;
       std::cout << "Initialise Net, done!" << std::endl;

}

這裡生成各種矩陣沒啥難點,唯一需要留心的是權值矩陣的行數和列數的確定。值得一提的是這裡把權值預設全設為0。

權值初始化——initNet()函式

權值初始化函式initWeights()呼叫initWeight()函式,其實就是初始化一個和多個的區別。

   //initialise the weights matrix.if type =0,Gaussian.else uniform.
   void Net::initWeight(cv::Mat &dst, int type, double a, double b)
   {
       if (type == 0)
       {
           randn(dst, a, b);
       }
       else
       {
           randu(dst, a, b);
       }
   }
   //initialise the weights matrix.
   void Net::initWeights(int type, double a, double b)
   {
       //Initialise weights cv::Matrices and bias
       for (int i = 0; i < weights.size(); ++i)
       {
           initWeight(weights[i], 0, 0., 0.1);
       }
   }

偏置初始化是給所有的偏置賦相同的值。這裡用Scalar物件來給矩陣賦值。

   //Initialise the bias matrices.
   void Net::initBias(cv::Scalar& bias_)
   {
       for (int i = 0; i < bias.size(); i++)
       {
           bias[i] = bias_;
       }

    }

至此,神經網路需要初始化的部分已經全部初始化完成了。

初始化測試

我們可以用下面的程式碼來初始化一個神經網路,雖然沒有什麼功能,但是至少可以測試下現在的程式碼是否有BUG:

#include"../include/Net.h"
//<opencv2opencv.hpp>
using namespace std;
using namespace cv;
using namespace liu;
int main(int argc, char *argv[])
{
 //Set neuron number of every layer
 vector<int> layer_neuron_num = { 784,100,10 };
 // Initialise Net and weights
   Net net;
   net.initNet(layer_neuron_num);
   net.initWeights(0, 0., 0.01);
   net.initBias(Scalar(0.05));
   getchar();
 return 0;
}

親測沒有問題。

本文先到這裡,前向傳播和反向傳播放在下一篇內容裡面。所有的程式碼都已經託管在Github上面,感興趣的可以去下載檢視。歡迎提意見。