原文: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 分类器之前,需要做两件事:
- 将 String 的值变成浮点
- 将 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 的这个答案中解释一下,“超参数是从常规训练过程中不能直接学到的一些参数。这些参数表示模型的“更高级”属性,如其复杂性或应该学习多快。它们被称为超参数。