JavaScript机器学习之KNN算法

译者按: 机器学习原来很简单啊,不妨动手试试!

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。另外,我们修正了原文代码中的错误

上图使用plot.ly所画。

上次我们用JavaScript实现了线性规划,这次我们来聊聊KNN算法。

KNN是k-Nearest-Neighbours的缩写,它是一种监督学习算法。KNN算法可以用来做分类,也可以用来解决回归问题。

GitHub仓库: machine-learning-with-js

KNN算法简介

简单地说,KNN算法由那离自己最近的K个点来投票决定待分类数据归为哪一类

如果待分类的数据有这些邻近数据,NY: 7, NJ: 0, IN: 4,即它有7个NY邻居,0个NJ邻居,4个IN邻居,则这个数据应该归类为N

假设你在邮局工作,你的任务是为邮递员分配信件,目标是最小化到各个社区的投递旅程。不妨假设一共有7个街区。这就是一个实际的分类问题。你需要将这些信件分类,决定它属于哪个社区,比如上东城曼哈顿下城等。

最坏的方案是随意分配信件分配给邮递员,这样每个邮递员会拿到各个社区的信件。

最佳的方案是根据信件地址进行分类,这样每个邮递员只需要负责邻近社区的信件。

也许你是这样想的:"将邻近3个街区的信件分配给同一个邮递员"。这时,邻近街区的个数就是k。你可以不断增加k,直到获得最佳的分配方案。这个k就是分类问题的最佳值。

KNN代码实现

上次一样,我们将使用mljsKNN模块ml-knn来实现。

每一个机器学习算法都需要数据,这次我将使用IRIS数据集。其数据集包含了150个样本,都属于鸢尾属下的三个亚属,分别是山鸢尾变色鸢尾维吉尼亚鸢尾。四个特征被用作样本的定量分析,它们分别是花萼花瓣的长度和宽度。

1. 安装模块

$ npm install ml-knn@2.0.0 csvtojson prompt

ml-knn: k-Nearest-Neighbours模块,不同版本的接口可能不同,这篇博客使用了2.0.0

csvtojson: 用于将CSV数据转换为JSON

prompt: 在控制台输入输出数据

2. 初始化并导入数据

IRIS数据集由加州大学欧文分校提供。

curl https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data > iris.csv

假设你已经初始化了一个NPM项目,请在index.js中输入以下内容:

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

var knn;

const csvFilePath = 'iris.csv'; // 数据集
const names = ['sepalLength', 'sepalWidth', 'petalLength', 'petalWidth', 'type'];

let seperationSize; // 分割训练和测试数据

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

let trainingSetX = [],
    trainingSetY = [],
    testSetX = [],
    testSetY = [];
  • seperationSize用于分割数据和测试数据

使用csvtojson模块的fromFile方法加载数据:

csv(
    {
        noheader: true,
        headers: names
    })
    .fromFile(csvFilePath)
    .on('json', (jsonObj) =>
    {
        data.push(jsonObj); // 将数据集转换为JS对象数组
    })
    .on('done', (error) =>
    {
        seperationSize = 0.7 * data.length;
        data = shuffleArray(data);
        dressData();
    });

我们将seperationSize设为样本数目的0.7倍。注意,如果训练数据集太小的话,分类效果将变差。

由于数据集是根据种类排序的,所以需要使用shuffleArray函数对数据进行混淆,这样才能方便分割出训练数据。这个函数的定义请参考StackOverflow的提问How to randomize (shuffle) a JavaScript array?:

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;
}

3. 转换数据

数据集中每一条数据可以转换为一个JS对象:

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

在使用KNN算法训练数据之前,需要对数据进行这些处理:

  1. 将属性(sepalLength, sepalWidth,petalLength,petalWidth)由字符串转换为浮点数. (parseFloat)
  2. 将分类 (type)用数字表示
function dressData()
{
    let types = new Set(); 
    data.forEach((row) =>
    {
        types.add(row.type);
    });
    let typesArray = [...types]; 

    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();
}

4. 训练数据并测试

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

train方法需要2个必须的参数: 输入数据,即花萼花瓣的长度和宽度;实际分类,即山鸢尾变色鸢尾维吉尼亚鸢尾。另外,第三个参数是可选的,用于提供调整KNN算法的内部参数。我将k参数设为7,其默认值为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();
}

比较预测值与真实值,就可以得到出错个数:

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

5. 进行预测(可选)

任意输入属性值,就可以得到预测值

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)}`);
        }
    });
}

6. 完整程序

完整的程序index.js是这样的:

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

var knn;

const csvFilePath = 'iris.csv'; // 数据集
const names = ['sepalLength', 'sepalWidth', 'petalLength', 'petalWidth', 'type'];

let seperationSize; // 分割训练和测试数据

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

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

csv(
    {
        noheader: true,
        headers: names
    })
    .fromFile(csvFilePath)
    .on('json', (jsonObj) =>
    {
        data.push(jsonObj); // 将数据集转换为JS对象数组
    })
    .on('done', (error) =>
    {
        seperationSize = 0.7 * data.length;
        data = shuffleArray(data);
        dressData();
    });

function dressData()
{
    let types = new Set(); 
    data.forEach((row) =>
    {
        types.add(row.type);
    });
    let typesArray = [...types]; 

    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();
}

// 使用KNN算法训练数据
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)}`);
        }
    });
}

// 混淆数据集的顺序
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

参考链接

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了7亿+错误事件,得到了Google、360、金山软件、百姓网等众多知名用户的认可。欢迎免费试用!

版权声明

转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/07/10/javascript-machine-learning-knn/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容