赞
踩
读代码学编程、读代码学理论、读代码学技术、读代码学专业英语
样本数据
蓝莓
草莓(好想吃啊)
训练神经网络-四步
- 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% 。图像目标识别还得用卷积神经网络。
运行效果
笔记:
Sigmoid函数
Sigmoid函数导数最大值为0.25,因链式法则需要连乘,故进行反向传播时容易导致梯度消失。前边神经元的权重可能得不到更改。
BP神经网络
下面我们来看一下卷积神经网络中神经元所代表的实质含义。下面的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神经元
2. 感知机
来源:《机器学习》——周志华
3. 多层前馈神经网络
来源:《机器学习》——周志华
4. 误差逆传播算法(BP算法)
参考:
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博客_神经网络反向传播原理
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。