开发者社区> 异步社区> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

《Arduino家居安全系统构建实战》——1.3 我们的第一个模型(C#版本)

简介:
+关注继续查看

本节书摘来异步社区《机器学习项目开发实战》一书中的第1章,第1.3节,作者:【美】Mathias Brandewinder(马蒂亚斯·布兰德温德尔),更多章节内容可以访问云栖社区“异步社区”公众号查看

1.3 我们的第一个模型(C#版本)

我们从C#实现开始热身(这应该是读者熟悉的领域),并在Visual Studio中创建一个C#控制台应用程序。我将自己的解决方案称作DigitsRecognizer(数字识别器),C#控制台应用名为CSharp——尽管取些比我更有创意的名称!

1.3.1 数据集组织

显然,首先我们需要数据。我们从http://1drv.ms/1sDThtz下载trainingsample.csv数据集,保存在机器上的某个位置。同一个位置还有第二个文件—— validationsample.csv,这个文件以后才会用到,但是我们现在就获取它。这两个文件采用CSV格式(逗号分隔值),结构如图1-5所示。第一行是文件头,后面的每行代表一个图像。第一列(“label”)表示图像代表的数字,接下来的784列(“pixel0”,“pixel11”)代表原始图像的每个像素,按照灰度编码(0~255,0表示纯黑,255表示纯白,中间的任何值是灰度级)。

9ef4881dc79fde0f62e58dcef2b6672b7bcf0c2e

例如,第一行的数据表示数字1,如果我们打算从这行数据重建真实的图像,可以将行分成28个“切片”,每个切片代表图像中的一行:像素(Pixel)0,像素1…像素27编码图像的第一行,像素28,像素29…像素55代表第二行,依次类推。我们最终一共有785列数据:一列用于标签,其他784列代表28行×28列=784个像素。图1-6所示用简化的4×4图像描述了编码机制:

真实的图像是1(第1列),然后是代表每个像素灰度的16列数据。

bded90b1c72d91437accc48559020bf543d78b63

■ 注意:

如果认真观察,就会注意到trainingsample.csv文件只有5000行,而不是前面提到的50000行。创建较小的文件是为了方便,只保留了原始数据最开始的一部分。50000行这个数字并不算大,但是足以让我们的进展变得很慢,这令人不快,此时在较大的数据集上工作也没有什么价值。
1.3.2 读取数据
我们将用典型的C#风格,围绕表示问题领域的几个类和接口构造代码。我们将在Observation类中保存每个图像的数据,用IClassifier接口表示算法,这样就可以在以后创建模型的变种。

第一步,我们需要从CSV文件读取数据,放入一组观测值中。下面我们进入解决方案,并在CSharp控制台项目中添加一个类,以保存观测值。

程序清单1-1 在Observation类中保存数据

public class Observation
{
    public Observation(string label, int[] pixels)
    {
        this.Label = label;
        this.Pixels = pixels;
    }

    public string Label { get; private set; }
    public int[] Pixels { get; private set; }
}```
接下来,我们添加一个DataReader类,用于从数据文件中读取观测值。这里实际上是执行两个不同的任务:从文本文件中提出每个相关的行,将每行转换为观测值类型。我们将这些任务分别放在两个方法中。

程序清单1-2 用DataReader类从文件读取

public class DataReader
{

private static Observation ObservationFactory(string data)
{
    var commaSeparated = data.Split(',');
    var label = commaSeparated[0];
    var pixels =
        commaSeparated
        .Skip(1)
        .Select(x => Convert.ToInt32(x))
        .ToArray();

    return new Observation(label, pixels);
}

public static Observation[] ReadObservations(string dataPath)
{
    var data =
        File.ReadAllLines(dataPath)
        .Skip(1)
        .Select(ObservationFactory)
        .ToArray();

    return data;
}

}`
注意,我们的代码主要是LINQ表达式!面向表达式的代码(如LINQ或者后面将会看到的F#)有助于编写非常清晰的代码,以直接的方式表达意图,通常比过程式编码有效得多。这种代码读起来更像自然语言:“读取所有行,跳过文件头,根据逗号拆分各行,解析为整数,为我提供新的观测值”。如果我和同行交谈,这就是描述意图的方法,而这个意图很清晰地反映在代码中。这种代码特别适合于数据操纵任务,因为它提供了描述数据转换工作流的自然手段,是机器学习的基础。毕竟,这就是LINQ的设计目的——“语言集成查询”!

我们已经有了数据、阅读器和保存它们的结构——下面在控制台应用中将其组合起来,用本地机器上实际数据文件的路径代替trainingPath中的PATH-ON-YOUR-MACHINE。

程序清单1-3 控制台应用程序

class Program
{
    static void Main(string[] args)
    {
        var trainingPath = @"PATH-ON-YOUR-MACHINE\trainingsample.csv";
        var training = DataReader.ReadObservations(trainingPath);

        Console.ReadLine();
    }
}```
如果你在代码块的最后放置一个断点,然后以调试模式运行,应该看到training变量是一个包含5000个观测值的数组。很好——一切似乎都很正常。

下一个任务是编写一个分类器,获得一个图像时,分类器将其与数据集中的每个Observation比较,找出最类似的,返回其标签。为此,需要两个元素:Distance(距离)和Classifier(分类器)。

####1.3.3 计算图像之间的距离
我们从距离开始,所需的是一个方法,用以取得两个像素数组并返回描述它们的差异的数字。距离是算法中的易变领域,我们很可能想要试验不同的图像比较方法,找出最合适的方法。因此采用一种设计,使我们能够轻松地替换不同的距离定义且不需要做很多的代码更改,是最为可取的。接口提供了一种方便的机制,可以使用它避免紧密耦合,确保在以后想要更改距离代码时,不需要遇到令人烦恼的重构问题。所以,我们从一开始就提取一个接口。

程序清单1-4 IDistance接口

public interface IDistance
{

double Between(int[] pixels1, int[] pixels2);

}`
有了接口之后,需要一个实现。同样,我们现在将采用可能有效的最简单的方法。例如,如果我们想要的是计量两个图像的差异,为什么不逐个像素比较,计算每个像素的差值,然后加总其绝对值?完全相同的图像距离为0,两个像素相差越远,两个图像的距离就越大。这种距离的名称为“曼哈顿距离”,实现起来相当简单,如程序清单1-5所示。

程序清单1-5 计算图像之间的曼哈顿距离

public class ManhattanDistance : IDistance
{
    public double Between(int[] pixels1, int[] pixels2)
    {
        if (pixels1.Length != pixels2.Length)
        {
                throw new ArgumentException("Inconsistent image sizes.");
        }

        var length = pixels1.Length;

        var distance = 0;

        for (int i = 0; i < length; i++)
        {
            distance += Math.Abs(pixels1[i] - pixels2[i]);
        }

        return distance;
    }
}```
有趣的事实:曼哈顿距离


前面我已经提到过,距离可以用多种方法计算。我们在这里使用的具体公式被称作“曼哈顿距离”。这个名称来自于这样一个事实:如果你是纽约市的出租车司机,这就是计算两点之间驾车距离的方法。因为所有街道都被组织为一个完善的矩形网格,可以计算东/西位置和南/北位置之间的绝对距离,这就是我们的代码中所完成的工作。这种方法还有一种不那么有诗意的名称——L1距离。
我们取两个图像并逐个像素比较,计算差值并返回总数,代表两个图像的距离。注意,这里使用的代码采用了过程式风格,完全没有使用LINQ。实际上,我最初使用LINQ编写了这一段代码,但是老实说不喜欢结果的显示方式。在我看来,在某种程度(或者对于某些运算来说)上,以C#语言编写的LINQ代码看起来有点过于复杂,这主要是因为C#很冗长,尤其是其函数构造(Func<A,B,C>)。这也是比较两种风格的有趣示例。在此,要理解代码所做的是什么,需要逐行阅读并将其翻译为“人类的描述”。这段代码还使用了突变(Mutation)——这是一种需要注意的风格。

MATH.ABS( )


你可能觉得奇怪,为什么我们要使用绝对值?为什么不简单地计算差值?为了理解这成为问题的原因,考虑如下的例子:

<div style="text-align: center"><img src="https://yqfile.alicdn.com/7f59e33503eb3fce6e1fd3bf6ab20fac4a6377c9.png" width="250" height="">
</div>

如果我们使用像素颜色的“简单”差值,就会遇到一个微妙的问题。计算第一个和第二个图像的差值将得到−255+255−255+255=0,和第一个图像与自身的距离相同。这明显是不正确的。第一个图像明显和自身完全相同,而按照该指标,不同的图像1和2在外观上可能是相同的!使用绝对值的理由就是:如果不这么做,方向相反的差异就会相互抵消,结果是完全不同的图像可能会产生很高的相似度。绝对值保证不会有这种问题:任何差异都根据其量级进行处置,而不考虑其符号。
####1.3.4 编写分类器
我们已经有了比较图像的方法,现在可以从一个通用接口开始,编写分类器了。在每种情况下,我们都预期使用一个两步的过程。提供一个已知观测值的训练集对分类器进行训练,一旦训练完成,我们就应该能够预测某个图像的标签了,见程序清单1-6。

程序清单1-6 IClassifier接口

public interface IClassifier
{

void Train(IEnumerable<Observation> trainingSet);
string Predict(int[] pixels);

}`
下面是实现前述算法的多种方法之一。

程序清单1-7 基本分类器实现

public class BasicClassifier : IClassifier
{
    private IEnumerable<Observation> data;

    private readonly IDistance distance;

    public BasicClassifier(IDistance distance)
    {
        this.distance = distance;
    }

    public void Train(IEnumerable<Observation> trainingSet)
    {
         this.data = trainingSet;
    }

    public string Predict(int[] pixels)
    {
        Observation currentBest = null;
        var shortest = Double.MaxValue;

        foreach (Observation obs in this.data)
        {

            var dist = this.distance.Between(obs.Pixels, pixels);
            if (dist < shortest)
            {
                shortest = dist;
                currentBest = obs;
            }
        }

        return currentBest.Label;
    }
}```

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
《用于物联网的Arduino项目开发:实用案例解析》—— 第1章 Arduino入门 1.1 学习目标
Arduino是一个开源平台,它包括了非常简单易用的硬件和软件。简而言之,Arduino可以读到传感器的数据,然后来控制像灯光、电机、加热器和车库门这样的装置。Arduino主要用于原型制作,所以很适合用它编写适合IoT初学者的读物。
1170 0
《计算机科学与工程导论:基于IoT和机器人的可视化编程实践方法第2版》一2.3.7 建立一个2-1多路选择器
本节书摘来华章计算机《计算机科学与工程导论:基于IoT和机器人的可视化编程实践方法第2版》一书中的第2章 ,第2.3.7节,陈以农 陈文智 韩德强 著 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
774 0
+关注
异步社区
异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
文章
问答
文章排行榜
最热
最新
相关电子书
更多
如何用阿里云快速构建游戏发行技术体系
立即下载
ReactNative框架在京东无线端的实践
立即下载
小米的混合开发框架实战
立即下载