当前位置:   article > 正文

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

c#神经网络源码

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


样本数据

3d6fef02a170d2abfaa1f1444dec2618.png

蓝莓

4b2e08eece34acabdbb507bd6576b31e.png

草莓(好想吃啊)

训练神经网络-四步

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

初始化训练集和测试集

  1. //蓝莓图片目录
  2. string BlueberryDirectory = @"D:\test/Image Data/Blueberries";
  3. //草莓图片目录
  4. string StrawberryDirectory = @"D:\test/Image Data/Strawberries";
  5. List<Data> STImageData = new List<Data>();//草莓图片映射的数据列表
  6. List<Data> BLImageData = new List<Data>();//蓝莓图片映射的数据列表
  7. List<Data> NewTrainingImageData = new List<Data>();//新训练图像数据列表
  8. List<Data> NewTestingImageData = new List<Data>();//新测试图像数据列表
  9. foreach (string BLimage in Directory.GetFiles(BlueberryDirectory))//遍历所有蓝莓图像
  10. {
  11. List<float> PixelValues = new List<float>(); //归一化的像素的R通道值列表
  12. //从指定的现有图像初始化Bitmap,缩放到指定的大小。
  13. Bitmap image = new Bitmap(Image.FromFile(BLimage), newSize: new Size(ImageResolution, ImageResolution));
  14. Data newData = new Data();//创建图像映射数据
  15. //获取所有
  16. for (int i = 0; i < ImageResolution; i++)
  17. {
  18. for (int j = 0; j < ImageResolution; j++)
  19. {
  20. float ColorValue = image.GetPixel(i, j).R;//读取像素的R通道值 红色
  21. PixelValues.Add(Remap(ColorValue, 0, 255, 0, 1));//将R值映射到【0,1】区间
  22. }
  23. }
  24. newData.IsBlueBerry = 1;//蓝莓设置为1
  25. newData.Values = PixelValues.ToArray();//归一化的R通道值数组
  26. BLImageData.Add(newData);//添加到 图像映射数据 列表
  27. }
  28. foreach (string STimage in Directory.GetFiles(StrawberryDirectory))//遍历所有草莓图像
  29. {
  30. List<float> PixelValues = new List<float>();//创建像素值列表
  31. // Bitmap image = new Bitmap(STimage);//读取草莓图像 225x225像素。这里应该需要缩放到统一大小 //分辨率16x16 : 网络精度 92.8% 90.6% 分辨率225x225 :
  32. Bitmap image = new Bitmap(Image.FromFile(STimage), newSize: new Size(ImageResolution, ImageResolution));//78.7% 73.9%
  33. Data newData = new Data();
  34. for (int i = 0; i < ImageResolution; i++)
  35. {
  36. for (int j = 0; j < ImageResolution; j++)
  37. {
  38. float ColorValue = image.GetPixel(i, j).R;
  39. PixelValues.Add(Remap(ColorValue, 0, 255, 0, 1));//获取草莓 归一化的R通道像素值列表
  40. }
  41. }
  42. newData.IsBlueBerry = 0;//草莓设置为0
  43. newData.Values = PixelValues.ToArray();//草莓图像归一化的R通道像素值数组
  44. STImageData.Add(newData);//添加到 草莓图像映射数据列表
  45. }
  46. int BLSeparationNumber = Convert.ToInt32((float)BLImageData.Count * 0.26f);//分割蓝莓样本数据:训练集 测试集
  47. int STSeparationNumber = Convert.ToInt32((float)STImageData.Count * 0.26f);//分割草莓样本数据:26%测试集
  48. for (int i = 0; i < STImageData.Count; i++)
  49. {
  50. if (i < STSeparationNumber)
  51. {
  52. NewTestingImageData.Add(STImageData[i]);//草莓测试集添加到总的测试集
  53. }
  54. else
  55. {
  56. NewTrainingImageData.Add(STImageData[i]);//添加其余的到训练集
  57. }
  58. }
  59. for (int i = 0; i < BLImageData.Count; i++)
  60. {
  61. if (i < BLSeparationNumber)
  62. {
  63. NewTestingImageData.Add(BLImageData[i]);//蓝莓测试集添加到总的测试集
  64. }
  65. else
  66. {
  67. NewTrainingImageData.Add(BLImageData[i]);// 其余添加到训练集
  68. }
  69. }
  70. TrainingData = NewTrainingImageData.ToArray();//所有训练集
  71. TestingData = NewTestingImageData.ToArray();//所有测试集
  72. RandomizeTrainingData();//打乱顺序
  73. RandomizeTestingData();

初始化神经网络

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

前馈神经网络算法

  1. //将数据输入网络以获得输出 Feed Data into the Network to get an output
  2. static void FeedForward()//前馈
  3. {
  4. Inputs.SetValues(data.Values);//输入层:映射数据。测试时需要给全局变量data赋值
  5. for (int i = 1; i < Layers.Length; i++)//遍历非输入层的所有层
  6. {
  7. Layer layer = Layers[i]; // 第i层,下一层
  8. int index = 0;
  9. foreach (Neuron neuron in layer.Neurons)//遍历下一层的神经元,计算神经元的输出值
  10. {
  11. float newValue = 0;//
  12. foreach (Neuron prevNeuron in Layers[i - 1].Neurons)//遍历前一层神经元
  13. {
  14. newValue += prevNeuron.Value * prevNeuron.Weights[index];//前一层神经元值的加权和
  15. if (Double.IsNaN(Sigmoid((newValue + neuron.Bias))))//判断加权和的非线性变换 是否不为数字
  16. {
  17. 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;
  18. }
  19. }
  20. newValue += neuron.Bias;//加权和+偏置
  21. neuron.Value = Sigmoid(newValue);//非线性变换后得到神经元的输出值
  22. index++; //计算下一神经元的输出
  23. }
  24. }
  25. }

反向传播算法

  1. //调整偏置和权重--反向传播 Adjust the biases and weights
  2. static void BackPropagate()
  3. {
  4. float LearningRate = 0.005f;//学习率
  5. float BiasLearningRate = 0.001f;//偏置学习率
  6. float[] expectedAnswer;//期望输出数组
  7. if (data.IsBlueBerry == 1)
  8. expectedAnswer = new float[] { 1, 0 };//蓝莓
  9. else
  10. expectedAnswer = new float[] { 0, 1 };//草莓
  11. //Begin with changing the Weights connected to the output layer
  12. //首先更改连接到输出层的权重
  13. //foreach Neuron in Outputs 遍历输出层的神经元,计算输出层神经元的gamma,调整上一层神经元的权重和偏置。
  14. for (int NeuronNum = 0; NeuronNum < Outputs.Length; NeuronNum++) //遍历输出层的神经元
  15. {
  16. Neuron neuron = Outputs[NeuronNum];//取输出层的一个神经元
  17. // Gamma= (实际输出-期望输出)*激活函数的导数 可以把导数理解为该神经元对残差的贡献比例 。也可理解为连续激活的概率。
  18. float Gamma = (neuron.Value - expectedAnswer[NeuronNum]) * (1 - (neuron.Value) * (neuron.Value));//残差*该神经元sigmoid导数
  19. neuron.Gamma = Gamma;//分到输出层神经元的锅,
  20. //遍历倒数第二层神经元 foreach Neuron in Previous Layer
  21. for (int PrevLayerNum = 0; PrevLayerNum < Layers[Layers.Length - 2].Length; PrevLayerNum++)
  22. {
  23. //计算要减去的新值 Calculate a New Value to Subtract
  24. float Delta = Gamma * Layers[Layers.Length - 2][PrevLayerNum].Value;//为降低代价函数 计算每一神经元调整量
  25. //应用新值来调整指向神经元的权重 Apply the New Value to Adjust the Weight thats pointing to the neuron
  26. Layers[Layers.Length - 2][PrevLayerNum].Weights[NeuronNum] -= Delta * LearningRate;//调整权重 减去所需调整量*学习率
  27. //应用新值来调整先前权重的偏差 Apply the New Value to Adjust the Bias of the Previous Weight
  28. Layers[Layers.Length - 2][PrevLayerNum].Bias -= Delta * BiasLearningRate;//调整偏置
  29. }
  30. }
  31. for (int LayerNum = Layers.Length - 2; LayerNum > 0; LayerNum--)//反向遍历所有层 ,从倒数第二层开始计算。
  32. {
  33. Layer layer = Layers[LayerNum];//取一层神经元,第一次执行为倒数第二层
  34. for (int NeuronNum = 0; NeuronNum < layer.Length; NeuronNum++)//遍历该层所有神经元
  35. {
  36. Neuron neuron = layer[NeuronNum];//取一个神经元
  37. float Gamma = 0;//初始化神经元改分的锅 第一次执行为倒数第二层的神经元
  38. for (int NeuronNum2 = 0; NeuronNum2 < Layers[LayerNum + 1].Length; NeuronNum2++)//遍历后一层神经元- 相对的输出层。第一次执行是最后一层即输出层。
  39. {
  40. Gamma += Layers[LayerNum + 1][NeuronNum2].Gamma * Layers[LayerNum][NeuronNum].Weights[NeuronNum2];//后一层的NeuronNum2神经元的gamma * 该神经元的NeuronNum2权重 再求和。加权残差
  41. }
  42. Gamma *= (1 - neuron.Value * neuron.Value);//加权残差 * sigmoid导数
  43. neuron.Gamma = Gamma;//神经元分到的锅。第一次执行为倒数第二层
  44. //遍历前一层神经元 计算其新的权重和偏置 foreach Neuron in Previous Layer
  45. for (int PrevLayerNum = 0; PrevLayerNum < Layers[LayerNum - 1].Length; PrevLayerNum++)
  46. {
  47. //计算要减去的新值
  48. float Delta = Gamma * Layers[LayerNum - 1][PrevLayerNum].Value;
  49. //应用新值来调整指向神经元的权重
  50. Layers[LayerNum - 1][PrevLayerNum].Weights[NeuronNum] -= Delta * LearningRate;
  51. //应用新值来调整先前权重的偏置
  52. Layers[LayerNum - 1][PrevLayerNum].Bias -= Delta * BiasLearningRate;
  53. }
  54. }
  55.             }
  56. }

运行神经网络

  1. static void RunNetwork(int Iterations, bool log)
  2. {
  3. Console.WriteLine("\n\nRunning Network...\n\n");
  4. for (int i = 0; i < Iterations; i++)
  5. {
  6. print($"Iteration {i + 1} / {Iterations} Completed\n");
  7. GetNextTrainingData();//获取打乱顺序的训练数据集
  8. FeedForward();//前馈
  9. BackPropagate();//反向传播
  10. }
  11. void print(string str)
  12. {
  13. if (log)
  14. {
  15. Console.WriteLine(str);//控制台输出 准确率百分比
  16. }
  17. }
  18. }

测试神经网络

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

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博客_神经网络反向传播原理

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/347138
推荐阅读
相关标签
  

闽ICP备14008679号