【神经网络-C#】蓝莓、草莓神经网络分类器demo源码学习-理解神经网络


读代码学编程、读代码学理论、读代码学技术、读代码学专业英语


样本数据

3d6fef02a170d2abfaa1f1444dec2618.png

蓝莓

4b2e08eece34acabdbb507bd6576b31e.png

草莓(好想吃啊)

训练神经网络-四步

InitializeData();//初始化训练集和测试集
            InitializeLayers();//初始化神经网络
            RunNetwork(1250, false);//运行神经网络
            TestNetwork(1000);//测试神经网络

初始化训练集和测试集

//蓝莓图片目录
            string BlueberryDirectory = @"D:\test/Image Data/Blueberries";
            //草莓图片目录
            string StrawberryDirectory = @"D:\test/Image Data/Strawberries";
            List<Data> STImageData = new List<Data>();//草莓图片映射的数据列表
            List<Data> BLImageData = new List<Data>();//蓝莓图片映射的数据列表
            List<Data> NewTrainingImageData = new List<Data>();//新训练图像数据列表
            List<Data> NewTestingImageData = new List<Data>();//新测试图像数据列表
            foreach (string BLimage in Directory.GetFiles(BlueberryDirectory))//遍历所有蓝莓图像
            {
                List<float> PixelValues = new List<float>(); //归一化的像素的R通道值列表
                //从指定的现有图像初始化Bitmap,缩放到指定的大小。
                Bitmap image = new Bitmap(Image.FromFile(BLimage), newSize: new Size(ImageResolution, ImageResolution));
                Data newData = new Data();//创建图像映射数据
                //获取所有
                for (int i = 0; i < ImageResolution; i++)
                {
                    for (int j = 0; j < ImageResolution; j++)
                    {
                        float ColorValue = image.GetPixel(i, j).R;//读取像素的R通道值  红色
                        PixelValues.Add(Remap(ColorValue, 0, 255, 0, 1));//将R值映射到【0,1】区间
                    }
                }
                newData.IsBlueBerry = 1;//蓝莓设置为1
                newData.Values = PixelValues.ToArray();//归一化的R通道值数组
                BLImageData.Add(newData);//添加到  图像映射数据 列表
            }
            foreach (string STimage in Directory.GetFiles(StrawberryDirectory))//遍历所有草莓图像
            {
                List<float> PixelValues = new List<float>();//创建像素值列表


                //  Bitmap image = new Bitmap(STimage);//读取草莓图像 225x225像素。这里应该需要缩放到统一大小  //分辨率16x16 :  网络精度 92.8%  90.6%    分辨率225x225 :
                Bitmap image = new Bitmap(Image.FromFile(STimage), newSize: new Size(ImageResolution, ImageResolution));//78.7%  73.9%
                Data newData = new Data();
                for (int i = 0; i < ImageResolution; i++)
                {
                    for (int j = 0; j < ImageResolution; j++)
                    {
                        float ColorValue = image.GetPixel(i, j).R;
                        PixelValues.Add(Remap(ColorValue, 0, 255, 0, 1));//获取草莓 归一化的R通道像素值列表
                    }
                }
                newData.IsBlueBerry = 0;//草莓设置为0
                newData.Values = PixelValues.ToArray();//草莓图像归一化的R通道像素值数组
                STImageData.Add(newData);//添加到 草莓图像映射数据列表
            }
            int BLSeparationNumber = Convert.ToInt32((float)BLImageData.Count * 0.26f);//分割蓝莓样本数据:训练集  测试集
            int STSeparationNumber = Convert.ToInt32((float)STImageData.Count * 0.26f);//分割草莓样本数据:26%测试集 
            for (int i = 0; i < STImageData.Count; i++)
            {
                if (i < STSeparationNumber)
                {
                    NewTestingImageData.Add(STImageData[i]);//草莓测试集添加到总的测试集
                }
                else
                {
                    NewTrainingImageData.Add(STImageData[i]);//添加其余的到训练集
                }
            }
            for (int i = 0; i < BLImageData.Count; i++)
            {
                if (i < BLSeparationNumber)
                {
                    NewTestingImageData.Add(BLImageData[i]);//蓝莓测试集添加到总的测试集
                }
                else
                {
                    NewTrainingImageData.Add(BLImageData[i]);// 其余添加到训练集
                }
            }
            TrainingData = NewTrainingImageData.ToArray();//所有训练集
            TestingData = NewTestingImageData.ToArray();//所有测试集
            RandomizeTrainingData();//打乱顺序
            RandomizeTestingData();

初始化神经网络

//将每一层附加到前一层,然后随机化这些层
        static void InitializeLayers()
        {
            Console.WriteLine("\n\nInitializing Layers...");//初始化所有层:偏置,权重数组
            if (Layers.Length >= 3)//4层
            {
                for (int i = 1; i < Layers.Length; i++)
                {
                    Layers[i].AppendToLayer(Layers[i - 1]);//初始化所有神经元权重数组
                }
                for (int i = 0; i < Layers.Length; i++)
                {
                    Layers[i].Randomize();//将所有神经元偏置 和 权重数组里的值 设置为随机浮点数
                }
            }
        }

前馈神经网络算法

//将数据输入网络以获得输出   Feed Data into the Network to get an output
        static void FeedForward()//前馈
        {
            Inputs.SetValues(data.Values);//输入层:映射数据。测试时需要给全局变量data赋值
            for (int i = 1; i < Layers.Length; i++)//遍历非输入层的所有层
            {
                Layer layer = Layers[i]; // 第i层,下一层
                int index = 0;
                foreach (Neuron neuron in layer.Neurons)//遍历下一层的神经元,计算神经元的输出值
                {
                    float newValue = 0;//
                    foreach (Neuron prevNeuron in Layers[i - 1].Neurons)//遍历前一层神经元
                    {
                        newValue += prevNeuron.Value * prevNeuron.Weights[index];//前一层神经元值的加权和
                        if (Double.IsNaN(Sigmoid((newValue + neuron.Bias))))//判断加权和的非线性变换   是否不为数字
                        {
                            throw new Exception("NaN value was found in network.");// LearnRate 0.5, BiasLeranRate 0.1 = 1:20s : NaN; LearnRate 0.005, BiasLearningRate 0.001 = 4:31s : 61.8% no ch;
                        }
                    }
                    newValue += neuron.Bias;//加权和+偏置
                    neuron.Value = Sigmoid(newValue);//非线性变换后得到神经元的输出值
                    index++; //计算下一神经元的输出
                }
            }
        }

反向传播算法

//调整偏置和权重--反向传播   Adjust the biases and weights
        static void BackPropagate()
        {
            float LearningRate = 0.005f;//学习率
            float BiasLearningRate = 0.001f;//偏置学习率
            float[] expectedAnswer;//期望输出数组
            if (data.IsBlueBerry == 1)
                expectedAnswer = new float[] { 1, 0 };//蓝莓
            else
                expectedAnswer = new float[] { 0, 1 };//草莓




            //Begin with changing the Weights connected to the output layer
            //首先更改连接到输出层的权重
            //foreach Neuron in Outputs  遍历输出层的神经元,计算输出层神经元的gamma,调整上一层神经元的权重和偏置。
            for (int NeuronNum = 0; NeuronNum < Outputs.Length; NeuronNum++) //遍历输出层的神经元
            {
                Neuron neuron = Outputs[NeuronNum];//取输出层的一个神经元
                //   Gamma= (实际输出-期望输出)*激活函数的导数     可以把导数理解为该神经元对残差的贡献比例 。也可理解为连续激活的概率。
                float Gamma = (neuron.Value - expectedAnswer[NeuronNum]) * (1 - (neuron.Value) * (neuron.Value));//残差*该神经元sigmoid导数
                neuron.Gamma = Gamma;//分到输出层神经元的锅,
                //遍历倒数第二层神经元   foreach Neuron in Previous Layer
                for (int PrevLayerNum = 0; PrevLayerNum < Layers[Layers.Length - 2].Length; PrevLayerNum++)
                {
                    //计算要减去的新值 Calculate a New Value to Subtract
                    float Delta = Gamma * Layers[Layers.Length - 2][PrevLayerNum].Value;//为降低代价函数 计算每一神经元调整量
                    //应用新值来调整指向神经元的权重   Apply the New Value to Adjust the Weight thats pointing to the neuron
                    Layers[Layers.Length - 2][PrevLayerNum].Weights[NeuronNum] -= Delta * LearningRate;//调整权重     减去所需调整量*学习率
                    //应用新值来调整先前权重的偏差 Apply the New Value to Adjust the Bias of the Previous Weight
                    Layers[Layers.Length - 2][PrevLayerNum].Bias -= Delta * BiasLearningRate;//调整偏置   
                }
            }


            for (int LayerNum = Layers.Length - 2; LayerNum > 0; LayerNum--)//反向遍历所有层  ,从倒数第二层开始计算。
            {
                Layer layer = Layers[LayerNum];//取一层神经元,第一次执行为倒数第二层


                for (int NeuronNum = 0; NeuronNum < layer.Length; NeuronNum++)//遍历该层所有神经元
                {
                    Neuron neuron = layer[NeuronNum];//取一个神经元
                    float Gamma = 0;//初始化神经元改分的锅   第一次执行为倒数第二层的神经元
                    for (int NeuronNum2 = 0; NeuronNum2 < Layers[LayerNum + 1].Length; NeuronNum2++)//遍历后一层神经元-  相对的输出层。第一次执行是最后一层即输出层。
                    {
                        Gamma += Layers[LayerNum + 1][NeuronNum2].Gamma * Layers[LayerNum][NeuronNum].Weights[NeuronNum2];//后一层的NeuronNum2神经元的gamma * 该神经元的NeuronNum2权重 再求和。加权残差 
                    }
                    Gamma *= (1 - neuron.Value * neuron.Value);//加权残差 * sigmoid导数
                    neuron.Gamma = Gamma;//神经元分到的锅。第一次执行为倒数第二层
                    //遍历前一层神经元   计算其新的权重和偏置      foreach Neuron in Previous Layer
                    for (int PrevLayerNum = 0; PrevLayerNum < Layers[LayerNum - 1].Length; PrevLayerNum++)
                    {
                        //计算要减去的新值
                        float Delta = Gamma * Layers[LayerNum - 1][PrevLayerNum].Value;
                        //应用新值来调整指向神经元的权重
                        Layers[LayerNum - 1][PrevLayerNum].Weights[NeuronNum] -= Delta * LearningRate;
                        //应用新值来调整先前权重的偏置
                        Layers[LayerNum - 1][PrevLayerNum].Bias -= Delta * BiasLearningRate;
                    }
                }
            }
        }

运行神经网络

static void RunNetwork(int Iterations, bool log)
        {
            Console.WriteLine("\n\nRunning Network...\n\n");
            for (int i = 0; i < Iterations; i++)
            {
                print($"Iteration {i + 1} / {Iterations} Completed\n");
                GetNextTrainingData();//获取打乱顺序的训练数据集
                FeedForward();//前馈
                BackPropagate();//反向传播
            }


            void print(string str)
            {
                if (log)
                {
                    Console.WriteLine(str);//控制台输出  准确率百分比
                }
            }
        }

测试神经网络

static void TestNetwork(int Iterations)
        {
            int CorrectAnswers = 0;//正确答案数
            for (int i = 0; i < Iterations; i++)//反复测试不同图像  Iterations
            {
                GetNextTestingData();//获取下一 测试数据,修改全局变量data
                FeedForward();//前馈:输入图像映射数据 计算神经网络输出
                Console.WriteLine("\n\nImage: " + data.IsBlueBerry);//标签
                Console.WriteLine("0: " + Outputs[0].Value);// 是蓝莓的答案。1是,0否
                Console.WriteLine("1: " + Outputs[1].Value);//是草莓的答案。1是,0否
                //判断神经网络输出是否与标签一致
                if ((data.IsBlueBerry == 1 && Outputs[0].Value > Outputs[1].Value) || (data.IsBlueBerry == 0 && Outputs[1].Value > Outputs[0].Value))
                {
                    Console.WriteLine("\nCorrect");//输出与标签一致,打印正确 
                    CorrectAnswers++;//正确数量
                }
                else
                {
                    Console.WriteLine("\nIncorrect");
                }
            }
            Console.WriteLine($"\n\nNetwork Accuracy: {(float)CorrectAnswers / (float)Iterations * 100f}%");//打印正确率  92.8
        }

该BP神经网络仅把输入图像缩放后的像素R通道归一化后的值作为特征向量(图像映射数据),随着缩放尺寸不同,训练后的神经网络准确度也不同,缩放尺寸越大,训练时间越长,识别精度越差。加上样本数量有限,试验了几次识别率最高不到93% 。图像目标识别还得用卷积神经网络。

95bb054918ecdb1ed092a0dae190d59b.png

b9b98d3f57a2038f7592fb19dbe207a7.png

运行效果


笔记:

Sigmoid函数

e4dbba7390e81733b559858256f59617.png

f7366fbd79f85cf1da7cd61b0edfe37d.png

Sigmoid函数导数最大值为0.25,因链式法则需要连乘,故进行反向传播时容易导致梯度消失。前边神经元的权重可能得不到更改。

BP神经网络

8b588d96fcfff8ff9f1e88f344958ed8.png

4e61a8edd544625e304e1cf423f13e9e.png

卷积神经网络

下面我们来看一下卷积神经网络中神经元所代表的实质含义。下面的gif图展示了size为3X3 ,strides为2的filter (分别有蓝绿两个filters)对一个 5X5 的输入图像做卷积的情形。

在该示例中,蓝色和绿色的 3X3  filter就是神经元。仍然套用上面所提到的3个步骤来分析这个卷积过程:

1.输入:接收前一层的输入数据,即 5X5X3 的图像。可以看出,此时的输入数据已经不是传统机器学习中所定义的高维向量了,而是一个多维的张量(Tensor)。

2. 操作

1.   注意到,神经元内的参数个数(本例中为 3X3X3 )通常小于输入数据的个数(本例中为 5x5x3 ),神经元会和在输入数据上进行滑动计算,直到扫过输入数据中的每个部分,并求出多个值,最终产生一个二维矩阵,通常也被称为feature map。(补充一下:三维张量经过卷积后变成了二维矩阵是因为原始尺寸只有长宽不同,而深度是相同的。假如filter和输入数据的尺寸在深度上也不同,那么输出的结果还是一个三维张量)

2.   在上述大框架的基础上,我们还需要引入bias和非线性变换。关于bias,仍然是一个神经元配备一个bias。以绿色神经元为例,对于其4个输出值,均加上相同的bias值 b0 。在加完bias之后,再对这4个输出值apply非线性变换,通常会是ReLU(Rectified Linear Unit)整流线性单元

3. 输出:经过卷积、加bias、非线性变换操作之后的feature map(特征映射).

通过对比BP神经元和卷积神经元的异同,我们似乎可以得出以下总结:

·       神经元的存在形式是一系列参数,并且是可学习的参数。

·       神经元可以接受不同尺寸的输入,也可以将输入与内部参数进行任意运算/操作(但操作方式应予输入尺寸相匹配)。

·       神经元的输出大小取决于输入数据的大小和神经元操作方式

·       神经元在输出前需要apply非线性函数

·       一个神经元配备一个bias

另一个思考的角度是:经过神经元的处理,输出通常都会比输入数据的尺寸要小,这其实是一种信息整合的体现。具体而言:

·       BP的神经元一次性就能够看见前一层传递过来的所有信息(正因此被称为全连接),因此操作时能将全部信息整合在一起,产生的输出为一个数。

·       而卷积神经元一次只能看到前一层传递过来信息的一个局部,需要通过滑动来将信息cover全,因此其产生的输出不止一个数,并且输出的信息凝聚程度也不如全链接。尽管如此,CNN还是更适合用来处理图像的,因为它保留了图像的二维结构,且所需神经元参数个数相对少、计算也相对少。和BP相比,相同的网络参数个数情形下,CNN可以更深,也就意味着非线性变换可以更多。


深入理解神经网络

1.   MP神经元

08869144d5ea6d676087f852ccf151f6.png

2.   感知机

205cb8cbc19d754697ec4e5dab551aa3.png

eae158c8000c776952ff3cd3f403ee4f.png

9474396e7a077aa26cea2f8120a65d51.png

来源:《机器学习》——周志华

3.   多层前馈神经网络

855a0fd8e68bf1417cbe8047a75cd53d.png

f60d1c943ee1c04b0b74f8f84e9c9b7f.png

来源:《机器学习》——周志华

4.   误差逆传播算法(BP算法)

6090a7e64c9ac98a9547d9de9947536a.png

2268bad0f79a868820239d4d9e7d248a.png

d9c9f3f8e07c2138b564cc71fe62894b.png

参考:

1>https://zhuanlan.zhihu.com/p/37773135 反向传播之二:sigmoid函数 - 知乎 (zhihu.com)

2>https://zhuanlan.zhihu.com/p/28735794 人工神经网络中的神经元 - 知乎 (zhihu.com)

3>https://wasedamagina.github.io/archives/  归档 | Magina的个人小站 (wasedamagina.github.io)

4>https://zhuanlan.zhihu.com/p/35407734 深入理解神经网络 - 知乎 (zhihu.com)

5> https://www.cnblogs.com/maybe2030/p/5597716.html [Deep Learning] 神经网络基础 - Poll的笔记 - 博客园 (cnblogs.com)

6> https://blog.csdn.net/fsfjdtpzus/article/details/106256925 机器学习笔记丨神经网络的反向传播原理及过程(图文并茂+浅显易懂)_アルゴノゥト的博客-CSDN博客_神经网络反向传播原理

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值