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<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.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"
//<opencv2\opencv.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上面,感兴趣的可以去下载查看。欢迎提意见。

原文发布于微信公众号 - 人工智能LeadAI(atleadai)

原文发表时间:2018-01-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏人工智能

Net类的设计和神经网络的初始化

? NVIDIA 深度学习学院 带你快速进入火热的DL领域 正文共4898个字,2张图,预计阅读时间28分钟。 闲言少叙,直接开始 既然是要用C++来实现,那...

1676
来自专栏about云

Spark MLlib之 KMeans聚类算法详解

问题导读 1.什么是Spark MLlib ? 2.Spark MLlib 分为哪些类? 3.KMeans算法的基本思想是什么? 4.Spark Mllib ...

4486
来自专栏人工智能

OpenCV—Node.js教程系列:用Tensorflow和Caffe“做游戏”

AiTechYun 编辑:Yining 今天我们来看看OpenCV的深度神经网络模块。如果你想要释放神经网络的awesomeness来识别和分类图像中的物体,但...

27610
来自专栏机器之心

教程 | 维度、广播操作与可视化:如何高效使用TensorFlow

选自GitHub 机器之心编译 参与:Nurhachu Null、李泽南 本文从 Tensorflow 基础、理解静态维度和动态维度、广播操作(Broading...

3885
来自专栏决胜机器学习

机器学习(十) ——使用决策树进行预测(离散特征值)

机器学习(十)——使用决策树进行预测(离散特征值) (原创内容,转载请注明来源,谢谢) 一、绘制决策树 决策树的一大优点是直观,但是前提是其以图像形式展示。如...

3296
来自专栏人工智能LeadAI

TensorFlow应用实战-17-Qlearning实现迷宫小游戏

总共有12个状态,s1到s12.对于每一个状态会有四个动作。对于每个状态下的每个动作会有一个Q的值。

1141
来自专栏落影的专栏

Metal图像处理——直方图均衡化

首先,我们用直方图来表示一张图像:横坐标代表的是颜色值,纵坐标代表的是该颜色值在图像中出现次数。

2393
来自专栏技术墨客

TensorFlow入门 原

本文将初步向码农和程序媛们介绍如何使用TensorFlow进行编程。在阅读之前请先 安装TensorFlow,此外为了能够更好的理解本文的内容,阅读之前需要了解...

722
来自专栏ATYUN订阅号

利用统计方法,辨别和处理数据中的异常值

在建模时,清理数据样本非常重要,这样做可以确保观察结果充分代表问题。有时,数据集可能包含超出预期范围之外的极端值。这通常被称为异常值,通过理解甚至去除这些异常值...

923
来自专栏人工智能LeadAI

基于Spark /Tensorflow使用CNN处理NLP的尝试

01 前言 关于CNN如何和NLP结合,其实是被这篇文章(http://www.wildml.com/2015/11/understanding-convolu...

3376

扫描关注云+社区