本文将不使用任何人工智能框架,只用简单的 dotnet 的类,自己搭建一个人工智能网络。本文适合小伙伴跟着一步步写
特别感谢老马的程序人生的帮助,本文有大量代码都是从如何利用 C# 抽象神经网络模型抄的
在人工智能模型有不同的问题可以选用不同的模型,本文主要写一个 BP 网络用于做分类,也就是写出一个简单的多分类人工智能和一个模拟二进制 与 计算和 或 计算。请不要认为本文会告诉大家如何写一个会和你聊QQ的人工智能,这里的人工智能其实也就是一个工具,和想象的智能差距有点大。本文的人工智能只能做对数值输入进行分类或实现模拟二进制计算
在一个人工智能模型,可以将人工智能模型作为网络模型建立,一个人工智能模型是一个网络模型,一个网络里面会有很多层,每一层有很多元。本文将从小到大进行定义,先从元定义,然后再从层定义,最后定义网络
在人工智能模型里面,最小单元都是神经元。一个神经元可以收到多个输入,而只有一个输出。在代码里面,将输入和输出的值都定义为double值。而本文写的神经元是固定输入数量,也就是在神经元对象创建的时候需要告诉这个神经元可以收到多少个数量的输入
在神经元里面将会对每个输入添加一个权值,在神经网络的每个元可以收到多个输入,而对每个输入需要使用不同的权值计算
最简单的神经元就是将每个输入的值乘以一个权值然后加起来然后输出。当然稍微复杂一点的是加起来之后需要加一个阈值然后调用激活函数计算输出
先忽略元的计算,定义元的数据结构
上面的模型感谢老马的程序人生大佬提供,只是定义了基础数据结构,而计算方法作为抽象方法。在 Compute 方法接受多个输入,然后有一个 double 输出
在 Randomize 方法给了权值数组一些随机值,其实有一句话是人工智能和随机猜是差不多的。本文下面将写一个随机给权值的训练方法
上面的 DoubleRange 是自己定义的,用来创建范围内的随机数,只需要看代码就知道是如何做的
接下来定义一个 ActivationNeuron 继承 Neuron 这里定义了阈值和激活函数
计算的方法是将多个输入的每个输入乘以权值加起来,然后加上阈值,接下来放入激活函数计算输出。为什么需要激活函数?原因是计算的 sum 的值需要输出到下一层需要将 sum 的值处理,如本文需要让每一层的输入的值都是 1
和 0
而因为每一层的输出会作为下一层的输入,所以需要将 sum 值计算为 1
和 0
也就是通过 阈值函数 将一个值按照是否大于等于 0 分为 1
和 0
两个值
那么 阈值函数 的定义是什么,阈值就是临界值,函数的目的是大于这个临界值会怎么样,小于这个临界值会怎么样。本文只是让大于等于 0 输出为 1
否则输出 0 这个值
激活函数的定义是
和 阈值函数 相应的激活函数还有符号函数等,符号函数就是按照一定的范围,将输入转换为 1
或 -1
因为负数叫符号,这也就是符号函数的名字。详细定义请看 符号函数
也就是在接受多个输入,对多个输入乘以权值加起来,再加上阈值,放入激活函数,计算后输出。这就是最简单的元的定义
定义完成了元,接下来就是定义层的概念,每一层可以有多个元,每一层可以收到上一层的数据。而第一层叫输入层,输入层将会接受用户的输入。最后一层叫输出层,输出层的值将会作为输出。简单的网络只需要一个元,一个元作为一层,而这个网络也只有一层。这一层只有一个元,是输入层也是输出层。这是最简单的模型,也就是本文接下来告诉大家的模型。也就是元是核心,只有一个元也能做出人工智能
定义的层需要知道输入的数量,而层里每个元都会被定义相同数量的输入。此时的连接和全连接差不多,也就是每个元都接受到相同的输入。其实这样的定义对于某个元只需要特定的几个输入也是可以实现的,因为每个元会对每个输入一个权值,如果设置某个输入的权值为 0 那么相当于放弃这个输入。这样就可以做到某个元只接受特定的几个输入而不是收到所有的输入。而为什么一些高级的模型不会让同一层的所有元收到的输入相同?刚才不是说可以让元自己控制放弃哪些输入,原因是虽然可以让元自己控制放弃一些输入,但是这样做的效率比较低,高级的模型需要提升效率,本文这里无视所以可以使用每一层的所有元收到相同的输入
本文这里使用的模型是每一层的元数量不可变,在定义层时就知道这一层有多少元。这样的模型功能会比较差,但是作为入门的博客,这样的定义差不多可以使用了
每一个元只有一个输入,所以每一层的输出数量和每一层的元数量相同
接下来定义 ActivationLayer 作为实际的神经网络层,其实从代码可以看到连个类可以合并在一起,只是老马的程序人生大佬作为两篇博客,我这里也就跟着他写了
一个网络可以包含多个层
请看一下代码里面的注释
实现一个网络
上面代码的实现有些诡异,原因是我的参数没有写好。在 ActivationNetwork 的最后一个参数是一个数组,指定神经网络每层中的神经元数量。也就是输入 [1,2]
表示有两层,其中第一层有 1 个神经元,第二层有两个。输入 [1,1,5]
表示有三层
而根据人工智能的教程,第一层是输入层,也就是 i == 0
设置这一层的输入为用户输入数量。从第二层开始,每一层的输入数量为上一层的输出数量
定义完成了人工智能模型,一个模型不会自动运行,还需要定义一个训练方法。作为可以自己学习的人工智能,学习方法可以分为监督学习和无监督学习,在代码里面我用老马的程序人生的说法非监督学习。这两个方法的不同在于监督学习是我知道输入内容和结果,我将输入放入模型,对比模型输出的值和我知道的结果,按照模型输出的值和我知道的结果的误差反馈给模型,让模型修改参数,如修改权值参数。而无监督学习是我也不知道结果,这个比较难理解,详细请看监督学习和无监督学习 或 小白都看得懂的监督学习与无监督学习 本文用到的是监督学习
这里的输入分为多样本训练和单样本训练也就是我给一堆数据就是多样本训练。从方法参数可以看到输入的都是二维数组,当然这里说二维数组是不对的,应该是数组的数组。从单样本训练方法可以看到每个数据都是输入是一个 double 数组,而输出也是一个 double 数组,那么多个输入和多个输出就是数组的数组
刚才也有说到,人工智能和随机猜是一样的,在人工智能的训练很重要的是反馈,也就是我告诉人工智能说算错了,他应该如何修改参数?这部分看起来有点难,假设这个人工智能我告诉他算错了,他就随机修改他的参数,这就是本文的 Slow 训练方法,这个方法只是慢,和其他训练方法差不多
用随机修改参数方法要求模型很简单,本文要求的模型只是一层,也就是输入层和输出层是相同的一层。在 单个训练样本 方法将会使用模型计算出一个值,通过 double[] networkOutput = _network.Compute(input);
然后对比误差 double e = output[j] - networkOutput[j];
如果存在误差,那么用 perceptron.Weights[i] = RandRange.GetRan();
更新每个元的每个参数的值
现在大概写完了代码,本文的代码放在 github 下载用 VisualStudio 打开 Bp.sln 文件,然后按下 F5 就可以运行
尝试用这个模型和训练方法做出一个模拟二进制 与 的计算,也就是输入有两个,输出是件这两个输入进行 与 运算
这个人工智能网络使用输入层有两个,只有一层网络,一层网络里面只有一个神经元
创建训练方法
尝试运行代码,可以看到我没有告诉人工智能如何做 与 运算,但是人工智能模拟了方法
尝试训练人工智能模型模拟二进制或计算
我没有告诉人工智能或计算的方法,但是人工智能可以训练如何计算
这样的太简单了,其实上面的模型可以做出多分类,多分类就是将一些输入分为几类。如按照二维几何距离将数据分为几类,然后让人工智能分类
这个数据是如何利用 C# 实现神经网络的感知器模型用到的数据
上面的数据的输入是两个数,而输出是三个数。在输出用 0 和 1 表示属于哪个类型
本文定义一层网络,在这一层网络的输出需要三个数也就是需要三个元
这就是一个简单的人工智能模型,所有代码都没有用到现有人工智能框架,都是使用 dotnet 基础的代码。用 C# 实现人工智能模型最成熟的是 ML.NET 但是这个库没有基础很难知道是做什么
本文的代码放在github 欢迎小伙伴访问
其实人工智能的一个核心是训练算法,本文告诉大家的是 Slow 算法,这个算法就是在人工智能模型输出的值和我知道的值不同时,让模型随机更新参数。这个做法虽然能完成,但是效率很低,特别在元的数量多的时候。此时就需要用到比较高级的训练方法,如如何利用 C# 实现神经网络的感知器模型
特别感谢老马的程序人生提供的模型