使用C++从0到1实现人工智能神经网络及实战案例

引言

既然是要用C++来实现,那么我们自然而然的想到设计一个神经网络类来表示神经网络,这里我称之为Net类。由于这个类名太过普遍,很有可能跟其他人写的程序冲突,所以我的所有程序都包含在namespace liu中,由此不难想到我姓刘。在之前的博客反向传播算法资源整理中,我列举了几个比较不错的资源。对于理论不熟悉而且学习精神的同学可以出门左转去看看这篇文章的资源。这里假设读者对于神经网络的基本理论有一定的了解。

一、Net类的设计与神经网络初始化

神经网络的要素

在真正开始coding之前还是有必要交代一下神经网络基础,其实也就是设计类和写程序的思路。简而言之,神经网络的包含几大要素:

  • 神经元节点

  • 层(layer)

  • 权值(weights)

  • 偏置项(bias)

神经网络的两大计算过程分别是前向传播和反向传播过程。每层的前向传播分别包含加权求和(卷积?)的线性运算和激活函数的非线性运算。反向传播主要是用BP算法更新权值。 虽然里面还有很多细节,但是对于作为第一篇的本文来说,以上内容足够了。

Net类的设计

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 zerovoid 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);//Forwardvoid forward();//Forwardvoid backward();protected://initialise the weight matrix.if type =0,Gaussian.else uniform.void initWeight(cv::Mat &dst, int type, double a, double b);//Activation functioncv::Mat activationFunction(cv::Mat &x, std::string func_type);//Compute delta errorvoid deltaError();//Update weightsvoid updateWeights();};
}

说明

以上不是Net类的完整形态,只是对应于本文内容的一个简化版,简化之后看起来会更加清晰明了。

成员变量与成员函数

现在Net类只有四个成员变量,分别是:

  • 每一层神经元数目(layer_neuron_num)

  • 层(layer)

  • 权值矩阵(weights)

  • 偏置项(bias)

权值用矩阵表示就不用说了,需要说明的是,为了计算方便,这里每一层和偏置项也用Mat表示,每一层和偏置都用一个单列矩阵来表示。

Net类的成员函数除了默认的构造函数和析构函数,还有:

  • initNet():用来初始化神经网络

  • initWeights():初始化权值矩阵,调用initWeight()函数

  • initBias():初始化偏置项

  • forward():执行前向运算,包括线性运算和非线性激活,同时计算误差

  • backward():执行反向传播,调用updateWeights()函数更新权值。

这些函数已经是神经网络程序核心中的核心。剩下的内容就是慢慢实现了,实现的时候需要什么添加什么,逢山开路,遇河架桥。

神经网络初始化

initNet()函数

先说一下initNet()函数,这个函数只接受一个参数——每一层神经元数目,然后借此初始化神经网络。这里所谓初始化神经网络的含义是:生成每一层的矩阵、每一个权值矩阵和每一个偏置矩阵。听起来很简单,其实也很简单。

实现代码在Net.cpp中。

这里生成各种矩阵没啥难点,唯一需要留心的是权值矩阵的行数和列数的确定。值得一提的是这里把权值默认全设为0。

    //Initialize netvoid 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 biasweights.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;}

权值初始化

initWeight()函数

权值初始化函数initWeights()调用initWeight()函数,其实就是初始化一个和多个的区别。

偏置初始化是给所有的偏置赋相同的值。这里用Scalar对象来给矩阵赋值。

    //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 biasfor (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 layervector<int> layer_neuron_num = { 784,100,10 };// Initialise Net and weightsNet net;net.initNet(layer_neuron_num);net.initWeights(0, 0., 0.01);net.initBias(Scalar(0.05));getchar();return 0;
}

二、前向传播与反向传播

在Net类的设计和神经网络的初始化中,大部分还是比较简单的。因为最重要事情就是生成各种矩阵并初始化。神经网络中的重点和核心就是本文的内容——前向和反向传播两大计算过程。每层的前向传播分别包含加权求和(卷积?)的线性运算和激活函数的非线性运算。反向传播主要是用BP算法更新权值。

前向过程

如前所述,前向过程分为线性运算和非线性运算两部分。相对来说比较简单。

线型运算可以用Y=WX+b 来表示,其中X是输入样本,这里即是第N层的单列矩阵,W是权值矩阵,Y是加权求和之后的结果矩阵,大小与N+1层的单列矩阵相同。b是偏置,默认初始化全部为0。不难推知鬼知道我推了多久!,W的大小是 (N+1).rows * N.rows。正前面中生成weights矩阵的代码实现一样:

weights[i].create(layer[i + 1].rows, layer[i].rows, CV_32FC1); 

非线性运算可以用O

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/212836.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ Boost 实现异步端口扫描器

端口扫描是一种用于识别目标系统上哪些网络端口处于开放、关闭或监听状态的网络活动。在计算机网络中&#xff0c;端口是一个虚拟的通信端点&#xff0c;用于在计算机之间传输数据。每个端口都关联着特定类型的网络服务或应用程序。端口扫描通常是网络管理员、安全专业人员用来…

Go iota简介

当声明枚举类型或定义一组相关常量时&#xff0c;Go语言中的iota关键字可以帮助我们简化代码并自动生成递增的值。本文档将详细介绍iota的用法和行为。 iota关键字 iota是Go语言中的一个预定义标识符&#xff0c;它用于创建自增的无类型整数常量。iota的行为类似于一个计数器…

沃趣班11月月考题目解析

沃趣班11月月考题目解析 1.在oracle中创建用户时&#xff0c;若未设置default tablespace关键字&#xff0c;则oracle将哪个表空间分配给用户作为默认表空间 答案&#xff1a;D.user SQL> create user mytest identified by 123456; SQL> grant connect to mytest; SQL…

中国毫米波雷达产业分析2——毫米波雷达产业链分析

一、产业链构成 毫米波雷达产业链分为三部分&#xff1a;上游主要包括射频前端组件&#xff08;MMIC&#xff09;、数字信号处理器&#xff08;DSP/FPGA&#xff09;、高频PCB板、微控制器&#xff08;MCU&#xff09;、天线及控制电路等硬件供应商&#xff1b;中游主体是毫米波…

An issue was found when checking AAR metadata

一、报错信息 An issue was found when checking AAR metadata:1. Dependency androidx.activity:activity:1.8.0 requires libraries and applications that depend on it to compile against version 34 or later of the Android APIs.:app is currently compiled against …

由红黑树引出的HashMap扩容机制的思考

红黑树是什么&#xff1f; 三大特点&#xff1a; 根节点是黑色&#xff0c;叶节点是不存储数据的黑色空节点 任何相邻的两个节点不能同时为红色 任意节点到其可到达的节点间包含相同数量的黑色节点 联想&#xff1a;Java HashMap底层红黑树原理 HashMap基于哈希表Map接口实…

【C++干货铺】适配器 | stack | queue

个人主页点击直达&#xff1a;小白不是程序媛 C系列学习专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 stack的介绍和使用 stack的介绍 stack的使用 queue的介绍和使用 queue的介绍 queue的使用 容器适配器 什么是适配器 STL中stack和queue的底层结构 d…

前端工程、静态代码、Html页面 打包成nginx 的 docker镜像

1. 创建一个 mynginx的目录 2. 将前端代码文件夹&#xff08;比如叫 front &#xff09;复制到 mynginx 目录下 3. 在mynginx 目录下创建一个名为Dockerfile 的文件&#xff08;文件名不要改&#xff09;&#xff0c;文件内容如下&#xff1a; # 使用官方的 Nginx 镜像作为基…

蓝桥杯物联网竞赛_STM32L071_3_Oled显示

地位&#xff1a; 对于任何一门编程语言的学习&#xff0c;print函数毫无疑问是一种最好的调试手段&#xff0c;调试者不仅能通过它获取程序变量的运行状态而且通过对其合理使用获取程序的运行流程&#xff0c;更能通过关键变量的输出帮你验证推理的正确与否&#xff0c;朴素的…

使用Git bash切换Gitee、GitHub多个Git账号

Git是分布式代码管理工具&#xff0c;使用命令行的方式提交commit、revert回滚代码。这里介绍使用Git bash软件来切换Gitee、GitHub账号。     假设在gitee.com上的邮箱是alicefoxmail.com 、用户名为alice&#xff1b;在github上的邮箱是bobfoxmail.com、用户名为bob。 账号…

vue3自定义拖拽指令

<template><div v-move class"box"></div> </template><script setup lang"ts"> import { Directive } from vue const vMove:Directive (el:HTMLElement) >{const mousedown (e:MouseEvent) >{// 鼠标按下const s…

java项目之品牌银饰售卖平台(ssm+vue)

项目简介 主要功能包括首页、个人中心、用户管理、促销活动管理、饰品管理、我的收藏管理、系统管理、订单管理等。管理员模块: 管理员可以查询、编辑、管理每个用户的信息和系统管理员自己的信息&#xff0c;同时还可以编辑、修改、查询用户账户和密码&#xff0c;以及对系统…