使用 JavaScript 实现机器学习(二)

原文:Machine Learning with JavaScript : Part 2
作者:Abhishek Soni
译者:聂震坤
审校:苏宓

深度监督学习:

到 kNN 登场的时候了。kNN 的意思是 k-Nearest-Neighbours,是一种监督学习算法。它可以被用于分类,以及回归问题。首先,我们要简单介绍一下 kNN,但如果你愿意,可以直接跳到代码。Github :

kNN 算法原理

kNN 根据数据点属于同一类的最大邻居数来决定新数据点的类。

如果一个新数据点的邻居如下,NY:7,NJ:0,IN:4,则新数据点的类将为 NY。

假设你在邮局工作,你的工作就是在邮政人员之间组织和发信,以减少不同社区的旅行次数。既然我们只是想象中的东西,我们可以假设只有七个不同的社区。这是一种分类问题。您需要将字母分成类,这里的类指的是朝阳区,东城区等。

如果您喜欢浪费时间和资源,您可以向每个邮递员发送一封来自每个社区的一封信,并希望他们在同一个社区相遇,发现您的邪恶计划。这是你可以实现的最糟糕的分配。

另一方面,你可以根据哪些地址彼此靠近来组织信件。您可以从“如果它在三个区间范围内开始,请给同一个邮递员”。最近的块数是k来自哪里。可以不断增加块数,直到达到有效的分配。这是k分类问题的最有效的价值。
所以,根据一些参数,像这里的房子的地址,你分类一个字母是否属于朝阳区,东城区等。

kNN 示例 | 代码

如上一张所说,我们将用 ml.js 的 knn 模块来训练我们的 kNearestNeihbors 分类器。每一种机器学习问题都需要数据,此教程将使用 IRIS 数据集。

Iris 数据集包括3种不同类型的虹膜(Setosa,Versichelour和Virginica)花瓣和萼片长度,以及表示其各自类型的场。

第一步:安装库

$ yarn add ml-knn csvtojson prompt

或者使用 npm

$ npm install ml-knn csvtojson prompt

ml-knn:k Nearest Neighbors
csvtojson:转化数据
prompt:允许用户提示进行预测

第二步:初始化库并加载数据

Iris 数据集由加利福尼亚大学尔湾分校提供。然而,由于它的组织方式,必须在浏览器中复制内容(选择全部|复制)并将其粘贴到名为 iris.csv 的文件中。文件可以随意命名,但拓展名必须是 .csv。

现在,初始化数据并载入它。假设你已经有一个空的 npm 项目,但你并不熟悉,这里有一个快速介绍。

const KNN = require('ml-knn');
const csv = require('csvtojson');
const prompt = require('prompt');
let knn;

const csvFilePath = 'iris.csv'; // Data
const names = ['sepalLength', 'sepalWidth', 'petalLength', 'petalWidth', 'type']; // For header

let seperationSize; // To seperate training and test data

let data = [],
    X = [],
    y = [];

let trainingSetX = [],
    trainingSetY = [],
    testSetX = [],
testSetY = [];

header names 是用来可视化方便理解的,一会儿会移除;
separationSize 是用来将数据分割成小的训练用数据集。

我们加载了 csvtojson 包,现在是使用它的时候了。使用 fromFile 来加载数据。

csv({noheader: true, headers: names})
    .fromFile(csvFilePath)
    .on('json', (jsonObj) => {
        data.push(jsonObj); // Push each object to data Array
    })
    .on('done', (error) => {
        seperationSize = 0.7 * data.length;
        data = shuffleArray(data);
        dressData();
    });

我们正在将每一行数据推送到数据变量,当进程完成后,我们将 seperationSize 设置为数据集中样本数量的0.7倍。请注意,如果训练样本的大小太小,则分类器可能不会像更大的集合一样执行。

由于我们的数据集是按照类型(console.log 来进行确认)进行排序的,所以使用 shuffleArray函数来洗牌数据集以允许分割。(如果你不洗牌,你可能会得到一个可以在前两个类中工作正常的模型,但是第三个失败)

代码如下:

/**
 * https://stackoverflow.com/a/12646864
 * Randomize array element order in-place.
 * Using Durstenfeld shuffle algorithm.
 */
function shuffleArray(array) {
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    return array;
}

第三步:包装数据

我们将数据以如下格式排版:

{
 sepalLength:5.1’,
 sepalWidth: ‘3.5’,
 petalLength: ‘1.4’,
 petalWidth: ‘0.2’,
 type: ‘Iris-setosa’ 
}

在将数据提供给 knn 分类器之前,需要做两件事:

  1. 将 String 的值变成浮点
  2. 将 type 变成数字类
function dressData() {

    /**
     * There are three different types of Iris flowers
     * that this dataset classifies.
     *
     * 1\. Iris Setosa (Iris-setosa)
     * 2\. Iris Versicolor (Iris-versicolor)
     * 3\. Iris Virginica (Iris-virginica)
     *
     * We are going to change these classes from Strings to numbers.
     * Such that, a value of type equal to
     * 0 would mean setosa,
     * 1 would mean versicolor, and
     * 3 would mean virginica
     */

    let types = new Set(); // To gather UNIQUE classes

    data.forEach((row) => {
        types.add(row.type);
    });

    typesArray = [...types]; // To save the different types of classes.

    data.forEach((row) => {
        let rowArray, typeNumber;

        rowArray = Object.keys(row).map(key => parseFloat(row[key])).slice(0, 4);

        typeNumber = typesArray.indexOf(row.type); // Convert type(String) to type(Number)

        X.push(rowArray);
        y.push(typeNumber);
    });

    trainingSetX = X.slice(0, seperationSize);
    trainingSetY = y.slice(0, seperationSize);
    testSetX = X.slice(seperationSize);
    testSetY = y.slice(seperationSize);

    train();
}

如果你不熟悉集合,它们与数学集合是对应的:不能有重复的元素,并且它们的元素没有顺序。

第四步:训练你的模型然后测试它

function train() {
    knn = new KNN(trainingSetX, trainingSetY, {k: 7});
    test();
}

Train 方法需要两个参数。输入数据,如花瓣长度与萼片宽度等。他可能还需要一个可选的参数,它只是用来传递调整内部JS参数的对象。我们将可选参数设为k,k的默认值是5。

现在模型已经被训练了,来看看它在测试集上的表现。主要对发生错误的分类数量感兴趣。

function test() {
    const result = knn.predict(testSetX);
    const testSetLength = testSetX.length;
    const predictionError = error(result, testSetY);
    console.log(`Test Set Size = ${testSetLength} and number of Misclassifications = ${predictionError}`);
    predict();
}

错误被计算如下。我们使用简单的for循环来循环数据库,看看预测输出是否不等于实际输出。这是一个错误的分类。

function error(predicted, expected) {
    let misclassifications = 0;
    for (var index = 0; index < predicted.length; index++) {
        if (predicted[index] !== expected[index]) {
            misclassifications++;
        }
    }
    return misclassifications;
}

第五步:开始预测(可选)

是时候来进行一波预测了

function predict() {
    let temp = [];
    prompt.start();

    prompt.get(['Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width'], function (err, result) {
        if (!err) {
            for (var key in result) {
                temp.push(parseFloat(result[key]));
            }
            console.log(`With ${temp} -- type =  ${knn.predict(temp)}`);
        }
    });
}

第六步:完成!

如果你紧跟以上几步,你的 index.js 现在应该如下:

const KNN = require('ml-knn');
const csv = require('csvtojson');
const prompt = require('prompt');
let knn;
const csvFilePath = 'iris.csv'; // Data
const names = ['sepalLength', 'sepalWidth', 'petalLength', 'petalWidth', 'type']; // For header

let seperationSize; // To seperate training and test data

let data = [], X = [], y = [];

let trainingSetX = [], trainingSetY = [], testSetX = [], testSetY = [];

csv({noheader: true, headers: names})
    .fromFile(csvFilePath)
    .on('json', (jsonObj) => {
        data.push(jsonObj); // Push each object to data Array
    })
    .on('done', (error) => {
        seperationSize = 0.7 * data.length;
        data = shuffleArray(data);
        dressData();
    });

function dressData() {

    let types = new Set(); // To gather UNIQUE classes

    data.forEach((row) => {
        types.add(row.type);
    });

    typesArray = [...types]; // To save the different types of classes.

    data.forEach((row) => {
        let rowArray, typeNumber;

        rowArray = Object.keys(row).map(key => parseFloat(row[key])).slice(0, 4);

        typeNumber = typesArray.indexOf(row.type); // Convert type(String) to type(Number)

        X.push(rowArray);
        y.push(typeNumber);
    });

    trainingSetX = X.slice(0, seperationSize);
    trainingSetY = y.slice(0, seperationSize);
    testSetX = X.slice(seperationSize);
    testSetY = y.slice(seperationSize);

    train();
}

function train() {
    knn = new KNN(trainingSetX, trainingSetY, {k: 7});
    test();
}

function test() {
    const result = knn.predict(testSetX);
    const testSetLength = testSetX.length;
    const predictionError = error(result, testSetY);
    console.log(`Test Set Size = ${testSetLength} and number of Misclassifications = ${predictionError}`);
    predict();
}

function error(predicted, expected) {
    let misclassifications = 0;
    for (var index = 0; index < predicted.length; index++) {
        if (predicted[index] !== expected[index]) {
            misclassifications++;
        }
    }
    return misclassifications;
}

function predict() {
    let temp = [];
    prompt.start();

    prompt.get(['Sepal Length', 'Sepal Width', 'Petal Length', 'Petal Width'], function (err, result) {
        if (!err) {
            for (var key in result) {
                temp.push(parseFloat(result[key]));
            }
            console.log(`With ${temp} -- type =  ${knn.predict(temp)}`);
        }
    });
}

/**
 * https://stackoverflow.com/a/12646864
 * Randomize array element order in-place.
 * Using Durstenfeld shuffle algorithm.
 */
function shuffleArray(array) {
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    return array;
}

在控制台里运行 node index.js 吧!

$ node index.js
Test Set Size = 45 and number of Misclassifications = 2
prompt: Sepal Length:  1.7
prompt: Sepal Width:  2.5
prompt: Petal Length:  0.5
prompt: Petal Width:  3.4
With 1.7,2.5,0.5,3.4 -- type =  2

大功告成!

kNN 算法的一个很大的方面是 k 的值,它被称为超参数。我从 Quora 的这个答案中解释一下,“超参数是从常规训练过程中不能直接学到的一些参数。这些参数表示模型的“更高级”属性,如其复杂性或应该学习多快。它们被称为超参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值