博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
手写数字阅读器用户界面
阅读量:3523 次
发布时间:2019-05-20

本文共 8967 字,大约阅读时间需要 29 分钟。

目录


C#面向对象的神经网络,培训师和Windows窗体用户界面,用于识别手写数字。

介绍

该源代码演示了如何训练和使用神经网络来解释手写数字。

本文不会有任何数学知识。这完全是关于基础的机器学习的C#实现。

https://www.codeproject.com/KB/AI/1273125/Mnist_Test_Form_5.png

背景

使用2训练人工神经网络人工神经网络(ANN) 。这是数据科学领域的经典问题。它也被称为机器学习的Hello World应用程序 Code Project上已经发布了一些关于此主题的演示应用程序,但我认为我的源代码可以帮助某人。复杂的问题可能需要不止一个解释,我试图让它尽可能简单。

使用代码

Visual Studio 2017中下载,解压缩并打开解决方案。

解决方案

https://www.codeproject.com/KB/AI/1273125/SystemArchitecture.png

该解决方案包含五个项目:

项目

描述

骨架

DeepLearningConsole

训练控制台的入口点

.NET Core 2.2

DeepLearning

主库

.NET Standard 2.0

Data

解析数据。目前只有Mnist

.NET Standard 2.0

MnistTestUi

用于手动测试的用户界面

.NET Framework 4.7.1

Tests

一些单元测试

.NET Core 2.2

为什么这么多框架?

我意识到.NET Core.NET Framework快了大约30%,我想在Windows Forms应用程序中使用.NET Framework

遗憾的是,您无法从.NET Framework引用.NET Core库。它们不兼容。
为解决这个问题,我使用.NET Standard作为通用组件。

.NET Standard不是一个框架。它是.NET API的正式规范。

所有.NET实现都应该与它兼容,不仅仅是.NET Core.NET Framework,还有XamarineMonoUnityWindows Mobile
这就是为可重用组件选择目标框架(Target Framework)的原因。

解析Mnist数据

这些是Mnist数据库中的四个文件:

  • t10k-images-idx3-ubyte——测试图像
  • t10k-labels-idx1-ubyte——测试图像的标签
  • train-images-idx3-ubyte——培训图像(60000
  • train-labels-idx1-ubyte——60000次培训图像的标签

Mnist中的图像必须从字节数组转换为从01的双精度数组。

这些文件还包含一些标题字段。

private static List
LoadMnistImages(string imgFileName, string idxFileName, int imgCount){ var imageReader = File.OpenRead(imgFileName); var byte4 = new byte[4]; imageReader.Read(byte4, 0, 4); //magic number imageReader.Read(byte4, 0, 4); //magic number Array.Reverse(byte4); //var imgCount = BitConverter.ToInt32(byte4, 0); imageReader.Read(byte4, 0, 4); //width (28) imageReader.Read(byte4, 0, 4); //height (28) var samples = new Sample[imgCount]; var labelReader = File.OpenRead(idxFileName); labelReader.Read(byte4, 0, 4);//magic number labelReader.Read(byte4, 0, 4);//count var targets = GetTargets(); for (int i = 0; i < imgCount; i++) { samples[i].Data = new double[784]; var buffer = new byte[784]; imageReader.Read(buffer, 0, 784); for (int b = 0; b < buffer.Length; b++) samples[i].Data[b] = buffer[b] / 256d; samples[i].Label = labelReader.ReadByte(); samples[i].Targets = targets[samples[i].Label]; } return samples.ToList();}

解析过程产生两个训练和测试样本列表。

样本由图像像素数组和长度为10的目标数组组成,目标数组是图像所在的数字的信息。
数字零是数组:1,0,0,0,0,0,0,0,0,0
数字五是:0,0,0,0,1,0,0,0,0,0(五分之一),依此类推。

实例化和训练神经网络

要实例化新的人工神经网络(ANN),您需要提供其拓扑,层数和每层中的神经元数量。

必须有784个神经元输入用于mnist图像(28x28像素)。输出层必须具有10
培训师类使用TrainData指定的学习速率训练神经网络。

var neuralNetwork = new NeuralNetwork(rndSeed: 0, sizes: new[] { 784, 200, 10 });neuralNetwork.LearnRate = 0.3;var trainer = new Trainer(neuralNetwork, Mnist.Data);trainer.Train();

接下来,每个训练样本都被送到网络,以便学习。

我发现隐藏层中的200个神经元使得人工神经网络(人工神经网络(ANN))的准确率达到98.5%,这似乎已经足够了。
400个神经元,精度最高可达98.8%,但需要两倍的时间训练。

Mnist培训师

培训师反复训练人工神经网络(ANN),让它看到一个训练样本。

所有60000张训练图像中的一个循环称为纪元。

在每个纪元之后,人工神经网络(ANN)被序列化并保存到文件中。

然后针对测试样本测试人工神经网络(ANN),并将结果记录到csv文件中。
训练图像也在每个纪元之间进行混洗。

public void Train(int epochs = 100){                var rnd = new Random(0);    var name = $"Sigmoid LR{NeuralNetwork.LearnRate} HL{NeuralNetwork.Layers[1].Count}";    var csvFile = $"{name}.csv";    var bestResult = 0d;    for (int epoch = 1; epoch < epochs; epoch++)    {        Shuffle(TrainData.TrainSamples, rnd);        TrainEpoch();                        var result = Test();        Log($"Epoch {epoch} {result.ToString("P")}");        File.AppendAllText(csvFile, $"{epoch};{result};{NeuralNetwork.TotalError}\r\n");        if (result > bestResult)        {            NeuralNetwork.Save($"{name}.bin");            Log($"Saved {name}.bin");            bestResult = result;        }    } }

前进通行证

这通过总结所有先前神经元乘以其权重来计算每个神经元值。

然后通过激活函数传递该值。然后可以从最后一层(也称为输出层)获得结果或输出。

private void Compute(Sample sample, bool train){    for (int i = 0; i < sample.Data.Length; i++)        Layers[0][i].Value = sample.Data[i];    for (int l = 0; l < Layers.Length - 1; l++)    {        for (int n = 0; n < Layers[l].Count; n++)        {            var neuron = Layers[l][n];            foreach (var weight in neuron.Weights)                weight.ConnectedNeuron.Value += weight.Value * neuron.Value;        }        var neuronCount = Layers[l + 1].Count;        if (l + 1 < Layers.Count() - 1)             neuronCount--; //skipping bias        for (int n = 0; n < neuronCount; n++)        {            var neuron = Layers[l + 1][n];            neuron.Value = LeakyReLU(neuron.Value / Layers[l].Count);        }    }}

反向传播

该算法调整神经元之间的所有权重。它使网络学习并逐步提高其性能。

private void ComputeNextWeights(double[] targets){    var output = OutputLayer;    for (int t = 0; t < output.Count; t++)        output[t].Target = targets[t];    //Output Layer    foreach (var neuron in output)    {        neuron.Error = Math.Pow(neuron.Target - neuron.Value, 2) / 2;        neuron.Delta = (neuron.Value - neuron.Target) * (neuron.Value > 0 ? 1 : 1 / 20d));    }    this.TotalError = output.Sum(n => n.Error);    foreach (var neuron in Layers[1])    {        foreach (var weight in neuron.Weights)            weight.Delta = neuron.Value * weight.ConnectedNeuron.Delta;    }        //Hidden Layer    Parallel.ForEach(Layers[0], GetParallelOptions(), (neuron) => {        foreach (var weight in neuron.Weights)        {            foreach (var connectedWeight in weight.ConnectedNeuron.Weights)                weight.Delta += connectedWeight.Value * connectedWeight.ConnectedNeuron.Delta;            var cv = weight.ConnectedNeuron.Value;            weight.Delta *= (cv > 0 ? 1 : 1 / 20d);            weight.Delta *= neuron.Value;        }    });    //All deltas are done. Now calculate new weights.    for (int l = 0; l < Layers.Length - 1; l++)    {        var layer = Layers[l];        foreach (var neuron in layer)            foreach (var weight in neuron.Weights)                weight.Value -= (weight.Delta * this.LearnRate);    }}

Mnist测试用户界面

Test UI用于测试您自己的手写内容。它有两个面板。小面板解释单个绘制的数字,而在较大的底部,您可以绘制一个数字。

图像预处理

Mnist数据库主页说明:

来自NIST的原始黑白(双层)图像的大小被标准化以适应20x20像素的盒子,同时保留它们的宽高比。由于归一化算法使用的抗锯齿技术,得到的图像包含灰度级。通过计算像素的质心,并将图像平移以便将该点定位在28x28域的中心,图像以28x28图像为中心。

以下是如何使用位图和Windows窗体图形进行操作的说明。

https://www.codeproject.com/KB/AI/1273125/preprocess.png

首先,找到绘制数字周围的最小方块。

public Rectangle DrawnSquare(){    var fromX = int.MaxValue;    var toX = int.MinValue;    var fromY = int.MaxValue;    var toY = int.MinValue;    var empty = true;    for (int y = 0; y < Bitmap.Height; y++)    {        for (int x = 0; x < Bitmap.Width; x++)        {            var pixel = Bitmap.GetPixel(x, y);            if (pixel.A > 0)            {                empty = false;                if (x < fromX)                    fromX = x;                if (x > toX)                    toX = x;                if (y < fromY)                    fromY = y;                if (y > toY)                    toY = y;            }        }    }    if (empty)        return Rectangle.Empty;    var dx = toX - fromX;    var dy = toY - fromY;    var side = Math.Max(dx, dy);    if (dy > dx)        fromX -= (side - dx) / 2;    else        fromY -= (side - dy)/ 2;    return new Rectangle(fromX, fromY, side, side);}

裁剪出正方形并调整大小为20x20的新位图。

public DirectBitmap CropToSize(Rectangle drawnRect, int width, int height){    var bmp = new DirectBitmap(width, height);    bmp.Bitmap.SetResolution(Bitmap.HorizontalResolution, Bitmap.VerticalResolution);    var gfx = Graphics.FromImage(bmp.Bitmap);    gfx.CompositingQuality = CompositingQuality.HighQuality;    gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;    gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;    gfx.SmoothingMode = SmoothingMode.AntiAlias;    var rect = new Rectangle(0, 0, width, height);    gfx.DrawImage(Bitmap, rect, drawnRect, GraphicsUnit.Pixel);    return bmp;}

最后,绘制20 x 20的图像,其质心集中在28x28位图内。

public Point GetMassCenterOffset(){    var path = new List
(); for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) { var c = GetPixel(x, y); if (c.A > 0) path.Add(new Vector2(x, y)); } } var centroid = path.Aggregate(Vector2.Zero, (current, point) => current + point) / path.Count(); return new Point((int)centroid.X - Width / 2, (int)centroid.Y - Height / 2);}protected DirectBitmap PadAndCenterImage(DirectBitmap bitmap){ var drawnRect = bitmap.DrawnRectangle(); if (drawnRect == Rectangle.Empty) return null; var bmp2020 = bitmap.CropToSize(drawnRect, 20, 20); //Make image larger and center on center of mass var off = bmp2020.GetMassCenterOffset(); var bmp2828 = new DirectBitmap(28, 28); var gfx2828 = Graphics.FromImage(bmp2828.Bitmap); gfx2828.DrawImage(bmp2020.Bitmap, 4 - off.X, 4 - off.Y); bmp2020.Dispose(); return bmp2828;}

然后,只需从图像中提取字节并使用它们查询人工神经网络(ANN)

public byte[] ToByteArray(){    var bytes = new List
(); for (int y = 0; y < Bitmap.Height; y++) { for (int x = 0; x < Bitmap.Width; x++) { var color = Bitmap.GetPixel(x, y); var i = color.A; bytes.Add(i); } } return bytes.ToArray();}

如果您对它们的外观感到好奇,界面(UI)还具有显示Mnist图像的功能。但是我不会过多地介绍界面(UI)的每个细节,因为我觉得我们正在脱离主题。

最后

我希望你喜欢我的文章,也许你学到了一些你还不知道的东西。如果您有任何问题,意见或想法,请留言。

 

原文地址:

转载地址:http://muzhj.baihongyu.com/

你可能感兴趣的文章
Sqoop的安装及测试
查看>>
Kylin的简单使用
查看>>
Presto的概念和安装使用
查看>>
Druid的Web页面使用
查看>>
Scala-HelloWorld
查看>>
Scala-IDEA中环境部署
查看>>
Scala-HelloWorld解析
查看>>
Scala-变量和数据类型
查看>>
Scala-流程控制
查看>>
Scala-面向对象后章
查看>>
iOS蓝牙原生封装,助力智能硬件开发
查看>>
iOS 代码的Taste(品位)
查看>>
iOS开发代码规范
查看>>
iOS组件化实践(基于CocoaPods)
查看>>
【iOS学习】RxSwift从零入手 - 介绍
查看>>
数据结构之栈
查看>>
Elastic Stack简介
查看>>
关于deepin系统安装design compiler的问题解答
查看>>
Java Agent简介及使用Byte Buddy和AspectJ LTW监控方法执行耗时
查看>>
记录一下最近的学习经历
查看>>