机器学习(2) - KNN识别MNIST

代码

https://github.com/s055523/MNISTTensorFlowSharp

数据的获得

数据可以由http://yann.lecun.com/exdb/mnist/下载。之后,储存在trainDir中,下次就不需要下载了。

/// <summary>
        /// 如果文件不存在就去下载
        /// </summary>
        /// <param name="urlBase">下载地址</param>
        /// <param name="trainDir">文件目录地址</param>
        /// <param name="file">文件名</param>
        /// <returns></returns>
        public static Stream MaybeDownload(string urlBase, string trainDir, string file)
        {
            if (!Directory.Exists(trainDir))
            {
                Directory.CreateDirectory(trainDir);
            }

            var target = Path.Combine(trainDir, file);
            if (!File.Exists(target))
            {
                var wc = new WebClient();
                wc.DownloadFile(urlBase + file, target);
            }
            return File.OpenRead(target);
        }

数据格式处理

下载下来的文件共有四个,都是扩展名为gz的压缩包。

train-images-idx3-ubyte.gz  55000张训练图片和5000张验证图片

train-labels-idx1-ubyte.gz     训练图片对应的数字标签(即答案)

t10k-images-idx3-ubyte.gz   10000张测试图片

t10k-labels-idx1-ubyte.gz     测试图片对应的数字标签(即答案)

处理图片数据压缩包

每个压缩包的格式为:


偏移量


类型



意义


0


Int32


2051或2049


一个定死的魔术数。用来验证该压缩包是训练集(2051)或测试集(2049)


4


Int32


60000或10000


压缩包的图片数


8


Int32


28


每个图片的行数


12


Int32


28


每个图片的列数


16


Unsigned byte


0 - 255


第一张图片的第一个像素


17


Unsigned byte


0 - 255


第一张图片的第二个像素





因此,我们可以使用一个统一的方式将数据处理。我们只需要那些图片像素。

/// <summary>
        /// 从数据流中读取下一个int32
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        int Read32(Stream s)
        {
            var x = new byte[4];
            s.Read(x, 0, 4);
            return DataConverter.BigEndian.GetInt32(x, 0);
        }

        /// <summary>
        /// 处理图片数据
        /// </summary>
        /// <param name="input"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        MnistImage[] ExtractImages(Stream input, string file)
        {
            //文件是gz格式的
            using (var gz = new GZipStream(input, CompressionMode.Decompress))
            {
                //不是2051说明下载的文件不对
                if (Read32(gz) != 2051)
                {
                    throw new Exception("不是2051说明下载的文件不对: " + file);
                }
                //图片数
                var count = Read32(gz);
                //行数
                var rows = Read32(gz);
                //列数
                var cols = Read32(gz);

                Console.WriteLine($"准备读取{count}张图片。");

                var result = new MnistImage[count];
                for (int i = 0; i < count; i++)
                {
                    //图片的大小(每个像素占一个bit)
                    var size = rows * cols;
                    var data = new byte[size];

                    //从数据流中读取这么大的一块内容
                    gz.Read(data, 0, size);

                    //将读取到的内容转换为MnistImage类型
                    result[i] = new MnistImage(cols, rows, data);
                }
                return result;
            }
        }

准备一个MnistImage类型:

/// <summary>
    /// 图片类型
    /// </summary>
    public struct MnistImage
    {
        public int Cols, Rows;
        public byte[] Data;
        public float[] DataFloat;

        public MnistImage(int cols, int rows, byte[] data)
        {
            Cols = cols;
            Rows = rows;
            Data = data;
            DataFloat = new float[data.Length];
            for (int i = 0; i < data.Length; i++)
            {
                //数据归一化(这里将0-255除255变成了0-1之间的小数)
                //也可以归一为-0.5到0.5之间
                DataFloat[i] = Data[i] / 255f;
            }
        }
    }

这样一来,图片数据就处理完成了。

处理数字标签数据压缩包

数字标签数据压缩包和图片数据压缩包的格式类似。


偏移量


类型



意义


0


Int32


2051或2049


一个定死的魔术数。用来验证该压缩包是训练集(2051)或测试集(2049)


4


Int32


60000或10000


压缩包的数字标签数


5


Unsigned byte


0 - 9


第一张图片对应的数字


6


Unsigned byte


0 - 9


第二张图片对应的数字





它的处理更加简单。

/// <summary>
        /// 处理标签数据
        /// </summary>
        /// <param name="input"></param>
        /// <param name="file"></param>
        /// <returns></returns>
        byte[] ExtractLabels(Stream input, string file)
        {
            using (var gz = new GZipStream(input, CompressionMode.Decompress))
            {
                //不是2049说明下载的文件不对
                if (Read32(gz) != 2049)
                {
                    throw new Exception("不是2049说明下载的文件不对:" + file);
                }
                var count = Read32(gz);
                var labels = new byte[count];

                gz.Read(labels, 0, count);

                return labels;
            }
        }

将数字标签转化为二维数组:one-hot编码

由于我们的数字为0-9,所以,可以视为有十个class。此时,为了后续的处理方便,我们将数字标签转化为数组。因此,一组标签就转换为了一个二维数组。

例如,标签0变成[1,0,0,0,0,0,0,0,0,0]

标签1变成[0,1,0,0,0,0,0,0,0,0]

以此类推。

/// <summary>
        /// 将数字标签一维数组转为一个二维数组
        /// </summary>
        /// <param name="labels"></param>
        /// <param name="numClasses">多少个类别,这里是10(0到9)</param>
        /// <returns></returns>
        byte[,] OneHot(byte[] labels, int numClasses)
        {
            var oneHot = new byte[labels.Length, numClasses];
            for (int i = 0; i < labels.Length; i++)
            {
                oneHot[i, labels[i]] = 1;
            }
            return oneHot;
        }

到此为止,数据格式处理就全部结束了。下面的代码展示了数据处理的全过程。

        /// <summary>
        /// 处理数据集
        /// </summary>
        /// <param name="trainDir">数据集所在文件夹</param>
        /// <param name="numClasses"></param>
        /// <param name="validationSize">拿出多少做验证?</param>
        public void ReadDataSets(string trainDir, int numClasses = 10, int validationSize = 5000)
        {
            const string SourceUrl = "http://yann.lecun.com/exdb/mnist/";
            const string TrainImagesName = "train-images-idx3-ubyte.gz";
            const string TrainLabelsName = "train-labels-idx1-ubyte.gz";
            const string TestImagesName = "t10k-images-idx3-ubyte.gz";
            const string TestLabelsName = "t10k-labels-idx1-ubyte.gz";

            //获得训练数据,然后处理训练数据和测试数据
            TrainImages = ExtractImages(Helper.MaybeDownload(SourceUrl, trainDir, TrainImagesName), TrainImagesName);
            TestImages = ExtractImages(Helper.MaybeDownload(SourceUrl, trainDir, TestImagesName), TestImagesName);
            TrainLabels = ExtractLabels(Helper.MaybeDownload(SourceUrl, trainDir, TrainLabelsName), TrainLabelsName);
            TestLabels = ExtractLabels(Helper.MaybeDownload(SourceUrl, trainDir, TestLabelsName), TestLabelsName);

            //拿出前面的一部分做验证
            ValidationImages = Pick(TrainImages, 0, validationSize);
            ValidationLabels = Pick(TrainLabels, 0, validationSize);

            //拿出剩下的做训练(输入0意味着拿剩下所有的)
            TrainImages = Pick(TrainImages, validationSize, 0);
            TrainLabels = Pick(TrainLabels, validationSize, 0);

            //将数字标签转换为二维数组
            //例如,标签3 =》 [0,0,0,1,0,0,0,0,0,0]
            //标签0 =》 [1,0,0,0,0,0,0,0,0,0]
            if (numClasses != -1)
            {
                OneHotTrainLabels = OneHot(TrainLabels, numClasses);
                OneHotValidationLabels = OneHot(ValidationLabels, numClasses);
                OneHotTestLabels = OneHot(TestLabels, numClasses);
            }
        }

        /// <summary>
        /// 获得source集合中的一部分,从first开始,到last结束
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="first"></param>
        /// <param name="last"></param>
        /// <returns></returns>
        T[] Pick<T>(T[] source, int first, int last)
        {
            if (last == 0)
            {
                last = source.Length;
            }

            var count = last - first;
            var ret = source.Skip(first).Take(count).ToArray();
            return ret;
        }

        public static Mnist Load()
        {
            var x = new Mnist();
            x.ReadDataSets(@"D:\人工智能\C#代码\MNISTTensorFlowSharp\MNISTTensorFlowSharp\data");
            return x;
        }

在这里,数据共有下面几部分:

  1. 训练图片数据55000 TrainImages及对应标签TrainLabels
  2. 验证图片数据5000 ValidationImages及对应标签ValidationLabels
  3. 测试图片数据10000 TestImages及对应标签TestLabels

KNN算法的实现

现在,我们已经有了所有的数据在手。需要实现的是:

  1. 拿出数据中的一部分(例如,5000张图片)作为KNN的训练数据,然后,再从数据中的另一部分拿一张图片A
  2. 对这张图片A,求它和5000张训练图片的距离,并找出一张训练图片B,它是所有训练图片中,和A距离最小的那张(这意味着K=1)
  3. 此时,就认为A所代表的数字等同于B所代表的数字b
  4. 重复1-3,N次

首先进行数据的收集:

//三个Reader分别从总的数据库中获得数据
        public BatchReader GetTrainReader() => new BatchReader(TrainImages, TrainLabels, OneHotTrainLabels);
        public BatchReader GetTestReader() => new BatchReader(TestImages, TestLabels, OneHotTestLabels);
        public BatchReader GetValidationReader() => new BatchReader(ValidationImages, ValidationLabels, OneHotValidationLabels);

        /// <summary>
        /// 数据的一部分,包括了所有的有用信息
        /// </summary>
        public class BatchReader
        {
            int start = 0;
            //图片库
            MnistImage[] source;
            //数字标签
            byte[] labels;
            //oneHot之后的数字标签
            byte[,] oneHotLabels;

            internal BatchReader(MnistImage[] source, byte[] labels, byte[,] oneHotLabels)
            {
                this.source = source;
                this.labels = labels;
                this.oneHotLabels = oneHotLabels;
            }

            /// <summary>
            /// 返回两个浮点二维数组(C# 7的新语法)
            /// </summary>
            /// <param name="batchSize"></param>
            /// <returns></returns>
            public (float[,], float[,]) NextBatch(int batchSize)
            {
                //一张图
                var imageData = new float[batchSize, 784];
                //标签
                var labelData = new float[batchSize, 10];

                int p = 0;
                for (int item = 0; item < batchSize; item++)
                {
                    Buffer.BlockCopy(source[start + item].DataFloat, 0, imageData, p, 784 * sizeof(float));
                    p += 784 * sizeof(float);
                    for (var j = 0; j < 10; j++)
                        labelData[item, j] = oneHotLabels[item + start, j];
                }

                start += batchSize;
                return (imageData, labelData);
            }
        }

然后,在算法中,获取数据:

        static void KNN()
        {
            //取得数据
            var mnist = Mnist.Load();

            //拿5000个训练数据,200个测试数据
            const int trainCount = 5000;
            const int testCount = 200;

            //获得的数据有两个
            //一个是图片,它们都是28*28的
            //一个是one-hot的标签,它们都是1*10的
            (var trainingImages, var trainingLabels) = mnist.GetTrainReader().NextBatch(trainCount);
            (var testImages, var testLabels) = mnist.GetTestReader().NextBatch(testCount);

            Console.WriteLine($"MNIST 1NN");

下面进行计算。这里使用了K=1的L1距离。这是最简单的情况。

            //建立一个图表示计算任务
            using (var graph = new TFGraph())
            {
                var session = new TFSession(graph);

                //用来feed数据的占位符。trainingInput表示N张用来进行训练的图片,N是一个变量,所以这里使用-1
                TFOutput trainingInput = graph.Placeholder(TFDataType.Float, new TFShape(-1, 784));

                //xte表示一张用来测试的图片
                TFOutput xte = graph.Placeholder(TFDataType.Float, new TFShape(784));

                //计算这两张图片的L1距离。这很简单,实际上就是把784个数字逐对相减,然后取绝对值,最后加起来变成一个总和
                var distance = graph.ReduceSum(graph.Abs(graph.Sub(trainingInput, xte)), axis: graph.Const(1));

                //这里只是用了最近的那个数据
                //也就是说,最近的那个数据是什么,那pred(预测值)就是什么
                TFOutput pred = graph.ArgMin(distance, graph.Const(0));

最后是开启Session计算的过程:

                var accuracy = 0f;

                //开始循环进行计算,循环trainCount次
                for (int i = 0; i < testCount; i++)
                {
                    var runner = session.GetRunner();

                    //每次,对一张新的测试图,计算它和trainCount张训练图的距离,并获得最近的那张
                    var result = runner.Fetch(pred).Fetch(distance)
                        //trainCount张训练图(数据是trainingImages)
                        .AddInput(trainingInput, trainingImages)
                        //testCount张测试图(数据是从testImages中拿出来的)
                        .AddInput(xte, Extract(testImages, i))
                        .Run();

                    //最近的点的序号
                    var nn_index = (int)(long)result[0].GetValue();

                    //从trainingLabels中找到答案(这是预测值)
                    var prediction = ArgMax(trainingLabels, nn_index);

                    //正确答案位于testLabels[i]中
                    var real = ArgMax(testLabels, i);

                    //PrintImage(testImages, i);

                    Console.WriteLine($"测试 {i}: " +
                        $"预测: {prediction} " +
                        $"正确答案: {real} (最近的点的序号={nn_index})");
                    //Console.WriteLine(testImages);

                    if (prediction == real)
                    {
                        accuracy += 1f / testCount;
                    }
                }
                Console.WriteLine("准确率: " + accuracy);

对KNN的改进

本文只是对KNN识别MNIST数据集进行了一个非常简单的介绍。在实现了最简单的K=1的L1距离计算之后,正确率约为91%。大家可以试着将算法进行改进,例如取K=2或者其他数,或者计算L2距离等。L2距离的结果比L1好一些,可以达到93-94%的正确率。

原文地址:https://www.cnblogs.com/haoyifei/p/9028235.html

时间: 2024-08-07 09:58:16

机器学习(2) - KNN识别MNIST的相关文章

机器学习(1) - TensorflowSharp 简单使用与KNN识别MNIST流程

机器学习是时下非常流行的话题,而Tensorflow是机器学习中最有名的工具包.TensorflowSharp是Tensorflow的C#语言表述.本文会对TensorflowSharp的使用进行一个简单的介绍. 本文会先介绍Tensorflow的一些基本概念,然后实现一些基本操作例如数字相加等运算.然后,实现求两个点(x1,y1)和(x2,y2)的距离.最后,通过这些前置基础和一些C#代码,实现使用KNN方法识别MNIST手写数字集合(前半部分).阅读本文绝对不需要任何机器学习基础,因为我现在

机器学习算法&#183;KNN

机器学习算法应用·KNN算法 一.问题描述 验证码目前在互联网上非常常见,从学校的教务系统到12306购票系统,充当着防火墙的功能.但是随着OCR技术的发展,验证码暴露出的安全问题越来越严峻.目前对验证码的识别已经有了许多方法,例如CNN,可以直接输入图片进行识别.验证码分为许多种类,本文以传统的字符验证码作为研究对象,进行图片分割成单一图片作为训练集,构架以测KNN,决策树或者朴素贝叶斯这三个算法为核心的验证码识别算法,进一步体会三个算法的特点. 二.数据准备 2.1数据说明 对于比较简单的字

机器学习-SVM-手写识别问题

机器学习-SVM-手写识别问题 这里我们解决的还是之前用KNN曾经解决过的手写识别问题(https://www.cnblogs.com/jiading/p/11622019.html),但相比于KNN,SVM好的地方在于一旦我们的模型训练完成,我们就可以得到一个确定的决策超平面,当然这个超平面的w是用所有的支持向量来描述的,这就表示我们发布模型的时候只需要包括所有的支持向量在内就可以了,剩下所有的向量都可以舍弃,和每次都需要所有向量的KNN相比,这就大大减小了模型的大小. 注意,这里举的是一个二

TensorFlow 入门之手写识别(MNIST) softmax算法

TensorFlow 入门之手写识别(MNIST) softmax算法 MNIST 卢富毓 softmax回归 softmax回归算法 TensorFlow实现softmax softmax回归算法 我们知道MNIST的每一张图片都表示一个数字,从0到9.我们希望得到给定图片代表每个数字的概率.比如说,我们的模型可能推测一张包含9的图片代表数字9的概率是80%但是判断它是8的概率是5%(因为8和9都有上半部分的小圆),然后给予它代表其他数字的概率更小的值. 这是一个使用softmax回归(sof

基于scikit-learn包实现机器学习之KNN(K近邻)-完整示例

基于scikit-learn包实现机器学习之KNN(K近邻) scikit-learn(简称sklearn)是目前最受欢迎,也是功能最强大的一个用于机器学习的Python库件.它广泛地支持各种分 类.聚类以及回归分析方法比如支持向量机.随机森林.DBSCAN等等,由于其强大的功能.优异的拓展性以及易用性,目 前受到了很多数据科学从业者的欢迎,也是业界相当著名的一个开源项目之一. 基于上一篇的k近邻原理讲解,我们这一片主要是利用相应的工具包实现机器学习,为了逐步掌握这样成功的工具包,我们 从简单的

matlab练习程序(神经网络识别mnist手写数据集)

记得上次练习了神经网络分类,不过当时应该有些地方写的还是不对. 这次用神经网络识别mnist手写数据集,主要参考了深度学习工具包的一些代码. mnist数据集训练数据一共有28*28*60000个像素,标签有60000个. 测试数据一共有28*28*10000个,标签10000个. 这里神经网络输入层是784个像素,用了100个隐含层,最终10个输出结果. arc代表的是神经网络结构,可以增加隐含层,不过我试了没太大效果,毕竟梯度消失. 因为是最普通的神经网络,最终识别错误率大概在5%左右. 迭

tensorflow识别Mnist时,训练集与验证集精度acc高,但是测试集精度低的比较隐蔽的原因

tensorflow识别Mnist时,训练集与验证集精度acc高,但是测试集精度低的比较隐蔽的原因除了网上说的主要原因https://blog.csdn.net/wangdong2017/article/details/90176323 之外,还有一种是比较隐蔽的原因(可能对于大多数人不会犯这种低级错误),作为新手的我找了半天才找到,原因是在程序中创建了一个会话之后又重新创建了一个会话,代码程序见我博客https://www.cnblogs.com/hujinzhou/p/guobao_2020

机器学习在用到mnist数据集报错No module named &#39;tensorflow.examples.tutorials&#39;解决办法

检查一下安装有tensorflow包的目录下的examples这个文件夹. 每个人的文件路径是不同的,我的在...\Python3\Lib\site-packages,该目录下有文件夹tensorflow, tensorflow_core, tensorflow_estimator等文件夹.进入tensorflow文件夹,里面发现一个examples文件夹,但是文件夹下只有saved_model这个文件,没有找到tutorials. 接下来我们进入github的tensorflow主页下载缺失的

机器学习框架Tensorflow数字识别MNIST

SoftMax回归  http://ufldl.stanford.edu/wiki/index.php/Softmax%E5%9B%9E%E5%BD%92 我们的训练集由  个已标记的样本构成: ,其中输入特征.(我们对符号的约定如下:特征向量  的维度为 ,其中  对应截距项 .) 由于 logistic 回归是针对二分类问题的,因此类标记 .假设函数(hypothesis function) 如下: 我们将训练模型参数 ,使其能够最小化代价函数 : 在 softmax回归中,我们解决的是多分