C++實現神經網路之一 | Net類的設計和神經網路的初始化
作者:冰不語
原文鏈接:eadbae4bddac275db48877a7e65d5c577fc1e68a24e4449f928c438102a28241060074700316查看更多的專業文章請移步至「人工智慧LeadAI」公眾號,查看更多的課程信息和產品信息,請移步至全新打造的官網:www.leadai.org.
正文共4898個字,2張圖,預計閱讀時間28分鐘。
閑言少敘,直接開始
既然是要用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 n#endif // NET_H #pragma once n#include <iostream> #include<opencv2corecore.hpp> n#include<opencv2highguihighgui.hpp> //n#include<iomanip> n#include"Function.h" namespace liu { nclass Net n{ public: nstd::vector<int> layer_neuron_num; nstd::vector<cv::Mat> layer; nstd::vector<cv::Mat> weights; nstd::vector<cv::Mat> bias; npublic: nNet() {}; n~Net() {}; n//Initialize nnet:genetate weights matrices、layer matrices and bias matrices n// bias default all zero nvoid initNet(std::vector<int> layer_neuron_num_); n//Initialise the weights matrices. nvoid initWeights(int type = 0, double a = 0., double b = 0.1); n//Initialise the bias matrices. nvoid initBias(cv::Scalar& bias); n//Forward nvoid farward(); n//Forward nvoid backward(); nprotected: n//initialise the weight matrix.if type =0,Gaussian.else uniform. nvoid initWeight(cv::Mat &dst, int type, double a, double b); n//Activation function ncv::Mat activationFunction(cv::Mat &x, std::string func_type); n//Compute delta error nvoid deltaError(); n//Update weights nvoid updateWeights(); n}; n}n
這不是完整的形態,只是對應於本文內容的一個簡化版,簡化之後看起來更加清晰明了。
成員變數與成員函數
現在Net類只有四個成員變數,分別是:
- 每一層神經元數目(layerneuronnum)
- 層(layer)
- 權值矩陣(weights)
- 偏置項(bias)
權值用矩陣表示就不用說了,需要說明的是,為了計算方便,這裡每一層和偏置項也用Mat表示,每一層和偏置都用一個單列矩陣來表示。
Net類的成員函數除了默認的構造函數和析構函數,還有:
- initNet():用來初始化神經網路
- initWeights():初始化權值矩陣,調用initWeight()函數
- initBias():初始化偏置項
- forward():執行前向運算,包括線性運算和非線性激活,同時計算誤差
- backward():執行反向傳播,調用updateWeights()函數更新權值。
這些函數已經是神經網路程序核心中的核心。剩下的內容就是慢慢實現了,實現的時候需要什麼添加什麼,逢山開路,遇河架橋。
神經網路初始化——initNet()函數
先說一下initNet()函數,這個函數只接受一個參數——每一層神經元數目,然後藉此初始化神經網路。這裡所謂初始化神經網路的含義是:生成每一層的矩陣、每一個權值矩陣和每一個偏置矩陣。聽起來很簡單,其實也很簡單。
實現代碼在Net.cpp中:
//Initialize netn void Net::initNet(std::vector<int> layer_neuron_num_)n {n layer_neuron_num = layer_neuron_num_;n //Generate every layer.n layer.resize(layer_neuron_num.size());n for (int i = 0; i < layer.size(); i++)n {n layer[i].create(layer_neuron_num[i], 1, CV_32FC1);n }n std::cout << "Generate layers, successfully!" << std::endl;n //Generate every weights matrix and biasn weights.resize(layer.size() - 1);n bias.resize(layer.size() - 1);n for (int i = 0; i < (layer.size() - 1); ++i)n {n weights[i].create(layer[i + 1].rows, layer[i].rows, CV_32FC1);n //bias[i].create(layer[i + 1].rows, 1, CV_32FC1);n bias[i] = cv::Mat::zeros(layer[i + 1].rows, 1, CV_32FC1);n }n std::cout << "Generate weights matrices and bias, successfully!" << std::endl;n std::cout << "Initialise Net, done!" << std::endl;nn }n
這裡生成各種矩陣沒啥難點,唯一需要留心的是權值矩陣的行數和列數的確定。值得一提的是這裡把權值默認全設為0。
權值初始化——initNet()函數
權值初始化函數initWeights()調用initWeight()函數,其實就是初始化一個和多個的區別。
//initialise the weights matrix.if type =0,Gaussian.else uniform. nvoid Net::initWeight(cv::Mat &dst, int type, double a, double b) n{ nif (type == 0) n{ nrandn(dst, a, b); n} nelse n{ nrandu(dst, a, b); n} n} n//initialise the weights matrix. nvoid Net::initWeights(int type, double a, double b) n{ n//Initialise weights cv::Matrices and bias nfor (int i = 0; i < weights.size(); ++i) n{ ninitWeight(weights[i], 0, 0., 0.1); n} n}n
偏置初始化是給所有的偏置賦相同的值。這裡用Scalar對象來給矩陣賦值。
//Initialise the bias matrices.n void Net::initBias(cv::Scalar& bias_)n {n for (int i = 0; i < bias.size(); i++)n {n bias[i] = bias_;n }nn }n
至此,神經網路需要初始化的部分已經全部初始化完成了。
初始化測試
我們可以用下面的代碼來初始化一個神經網路,雖然沒有什麼功能,但是至少可以測試下現在的代碼是否有BUG:
#include"../include/Net.h" n//<opencv2opencv.hpp> using namespace std; nusing namespace cv; using namespace liu; nint main(int argc, char *argv[]) { n//Set neuron number of every layer vector<int> layer_neuron_num = { 784,100,10 }; // Initialise Net and weights nNet net; nnet.initNet(layer_neuron_num); nnet.initWeights(0, 0., 0.01); nnet.initBias(Scalar(0.05)); ngetchar(); nreturn 0; }n
親測沒有問題。
本文先到這裡,前向傳播和反向傳播放在下一篇內容裡面。所有的代碼都已經託管在Github上面,感興趣的可以去下載查看。歡迎提意見。
推薦閱讀:
※Faster R-CNN
※簡單易懂的自動編碼器
※人工智慧vs人類智能小傳——再議阿爾法狗
※TensorFlow實現神經網路入門篇
※Deep Residual Network 深度殘差網路