使用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 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 forward();
        //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类的完整形态,只是对应于本文内容的一个简化版,简化之后看起来会更加清晰明了。

成员变量与成员函数

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

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

  • 层(layer)

  • 权值矩阵(weights)

  • 偏置项(bias)

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

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

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

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

  • initBias():初始化偏置项

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

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

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

神经网络初始化

initNet()函数

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

实现代码在Net.cpp中。

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

    //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;
    }

权值初始化

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 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;
}

二、前向传播与反向传播

在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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数字信号处理】傅里叶变换的离散性与周期性

傅里叶变换的离散性与周期性 2023年11月21日 #elecEngeneer 文章目录 傅里叶变换的离散性与周期性1. 符号说明2. 具体分析3. 序列的序号表示的DFT下链 1. 符号说明 t : 连续时间(时域)变量 ω : 频域变量&#xff0c;aka角频率 g : 时域函数 G : 频域函数 n : 时域采样序列序号…

『RabbitMQ』入门指南(安装,配置,应用)

前言 RabbitMQ 是在 AMQP&#xff08;Advanced Message Queuing Protocol&#xff09; 协议标准基础上完整的&#xff0c;可复用的企业消息系统。它遵循 Mozilla Public License 开源协议&#xff0c;采用 Erlang 实现的工业级的消息队列(MQ)服务器&#xff0c;建立在 Erlang …

C语言——文件操作

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 我辈皆凡人&#xff0c;用一生铺就的…

【管理运筹学】背诵手册(五)| 动态规划

五、动态规划 基本概念 阶段&#xff08;Stage&#xff09;&#xff1a;将所给问题的过程&#xff0c;按时间或空间特征分解成若干相互联系的阶段&#xff0c;以便按次序去求解每阶段的解&#xff0c;常用字母 k k k 表示。 状态&#xff08;State&#xff09;&#xff1a;…

Ubuntu20.04 install pnpm

npm install -g pnpm referrence link: Installation | pnpmPrerequisiteshttps://pnpm.io/installation

【libGDX】使用Mesh绘制立方体

1 前言 本文主要介绍使用 Mesh 绘制立方体&#xff0c;读者如果对 Mesh 不太熟悉&#xff0c;请回顾以下内容&#xff1a; 使用Mesh绘制三角形使用Mesh绘制矩形使用Mesh绘制圆形 在绘制立方体的过程中&#xff0c;主要用到了 MVP &#xff08;Model View Projection&#xff0…

Javaweb之前后台分离开发介绍的详细解析

2.1 前后台分离开发介绍 在之前的课程中&#xff0c;我们介绍过&#xff0c;前端开发有2种方式&#xff1a;前后台混合开发和前后台分离开发。 前后台混合开发&#xff0c;顾名思义就是前台后台代码混在一起开发&#xff0c;如下图所示&#xff1a; 这种开发模式有如下缺点&a…

C++11『lambda表达式 ‖ 线程库 ‖ 包装器』

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2022 版本 17.6.5 文章目录 &#x1f307;前言&#x1f3d9;️正文1.lambda表达式1.1.仿函数的使用1.2.lambda表达式的语法1.3.lambda表达式的使用…

C/C++ 开发SCM服务管理组件

SCM&#xff08;Service Control Manager&#xff09;服务管理器是 Windows 操作系统中的一个关键组件&#xff0c;负责管理系统服务的启动、停止和配置。服务是一种在后台运行的应用程序&#xff0c;可以在系统启动时自动启动&#xff0c;也可以由用户或其他应用程序手动启动。…

win10戴尔电脑安装操作系统遇到的问题MBR分区表只能安装GPT磁盘

首先按F2启动boot管理界面 调整启动盘的启动顺序&#xff0c;这里启动U盘为第一顺序。 第一步 选择安装程序的磁盘 第二步 转换磁盘为GPT磁盘 一般出现 磁盘0和1&#xff0c;说明存在两个盘 &#xff0c;这里两个盘不是说的是C盘和D盘的问题&#xff0c;而是在物理上实际存在…

2024年度投资策略:AI大模型和半导体国产化加速

今天分享的是AI系列深度研究报告&#xff1a;《2024年度投资策略&#xff1a;AI大模型和半导体国产化加速》。 &#xff08;报告出品方&#xff1a;东方证券&#xff09; 报告共计&#xff1a;48页 前言: 行情回顾与未来展望 电子板块涨幅转正&#xff0c;信心逐渐回归。截至…

Ps:参考线

参考线 Guides用于帮助精确地定位图像或元素&#xff0c;显示为浮动在图像上的非打印线&#xff0c;可以移动或移除&#xff0c;还可以临时锁定。 Ps 中的参考线可分为三大类&#xff1a;画布参考线、画板参考线和智能参考线。 可在“首选项/参考线、网格和切片”中设置参考线的…

【Java线程通信】一窥究竟

今天我们要聊一聊Java线程通信。如果你是一个Java开发者&#xff0c;那么你肯定知道线程是Java中的一个重要概念。线程是程序执行的最小单位&#xff0c;它可以独立运行&#xff0c;也可以与其他线程共享资源。那么&#xff0c;线程之间如何进行通信呢&#xff1f;这就是我们今…

【uniapp】部分图标点击事件无反应

比如&#xff1a;点击这个图标在h5都正常&#xff0c;在小程序上无反应 css&#xff1a;也设置z-index&#xff0c;padding 页面上也试过click.native.stop.prevent"changePassword()" 时而可以时而不行&#xff0c; 最后发现是手机里输入键盘的原因&#xff0c;输…

文心一言 VS 讯飞星火 VS chatgpt (141)-- 算法导论12.1 1题

一、用go语言&#xff0c;对于关键字集合(1&#xff0c;4&#xff0c;5&#xff0c;10&#xff0c;16&#xff0c;17&#xff0c;21)&#xff0c;分别画出高度为 2、3、4、5和6的二又搜索树。 文心一言&#xff1a; 以下是用Go语言为给定的关键字集合(1, 4, 5, 10, 16, 17, 2…

马养殖场建设VR模拟实训教学平台具有灵活性和复用性

为保障养殖场生物安全&#xff0c;避免疫病传播&#xff0c;学生出入养殖场受时间和地域的限制&#xff0c; 生产实习多以参观为主&#xff0c;通过畜牧企业技术人员的讲解&#xff0c;学生被动了解生产过程。为了解决畜牧养殖实训难的问题&#xff0c;借助VR技术开展畜牧养殖虚…

CUDA与GPU编程

文章目录 CUDA与GPU编程1. 并行处理与GPU体系架构1.1 并行处理简介1.1.1 串行处理与并行处理的区别1.1.2 并行处理的概念1.1.3 常见的并行处理 1.2 GPU并行处理1.2.1 GPU与CPU并行处理的异同1.2.2 CPU的优化方式1.2.3 GPU的特点 1.3 环境搭建 CUDA与GPU编程 1. 并行处理与GPU体…

关于easy-es的聚合问题

es实体类&#xff1a; public class ChemicalES {IndexId(type IdType.CUSTOMIZE)private Long id;HighLightIndexField(fieldType FieldType.TEXT, analyzer "ik_max_word")private String name;IndexField(fieldType FieldType.KEYWORD)private List<Stri…

某60区块链安全之未初始化的存储指针实战一学习记录

区块链安全 文章目录 区块链安全未初始化的存储指针实战一实验目的实验环境实验工具实验原理实验过程 未初始化的存储指针实战一 实验目的 学会使用python3的web3模块 学会分析以太坊智能合约未初始化的存储指针漏洞 找到合约漏洞进行分析并形成利用 实验环境 Ubuntu18.04操…

Vue3 封装组件库并发布到npm仓库

一、创建 Vue3 TS Vite 项目 输入项目名称&#xff0c;并依次选择需要安装的依赖项 npm create vuelatest 项目目录结构截图如下&#xff1a; 二、编写组件代码、配置项和本地打包测试组件 在项目根目录新建 package 文件夹用于存放组件 &#xff08;以customVideo为例&a…