はじめに
最近、機械学習に興味を持ち、少しずつですが勉強しています。
ただ、モチベーションを維持するのって結構難しいですよね...
そこで、モチベーションを維持するために、実際に動くものを作って遊んでみました。
この記事では、Node.jsでSVM(サポートベクターマシン)を簡単に実装できる手順を公開します。
対象者
- Node.jsを触ったことがある人
- npmの知識が多少ある人
- 機械学習に興味のあるけど、モチベーションが維持できない人
- (SVMの説明はこの記事では省略します)
今回作るもの
学習データからモデルを生成し、そのモデルからテストデータの分類を行うプログラムを作ります。
使用するモジュールとして、node-svmを使います。
バージョン
$ node -v
v10.16.0
$ npm -v
6.9.0
Node.jsのバージョンによって、node-svmをインストールできないので注意が必要です。
私は、v12.6.0でインストールできませんでした。
手順
準備
- 作業するディレクトリを作成
-
npm init
で初期化
$ mkdir test_svm
$ cd test_svm
$ npm init
モジュールのインストール
node-svm以外に、以下のモジュールをインストールします。
- 学習データやテストデータを読み込むために
fs
- 読み込んだデータをパースするために
csv-parse
$ npm install --save fs
$ npm install --save csv-parse
$ npm install --save node-svm
実装
// モジュールの読み込み
const fs = require("fs");
const csvSync = require("csv-parse/lib/sync");
const svm = require("node-svm");
// 学習データ、テストデータのパス
const learningDataPath = "learning_data.csv";
const testDataPath = "test_data.csv";
// 生成したモデルを格納するための変数
var newModel = null;
// メイン処理
if (require.main === module) {
var arrLD = readFile(learningDataPath);
var normArrLD = normalizeArr(arrLD);
var arrTD = readFile(testDataPath);
initModel(normArrLD).then(() => { // 学習データからモデルの生成
estimate(arrTD); // モデル生成完了後、テスト
})
}
/**
* ファイルを読み込み、配列として返す関数
* @param {string} filePath 読み込むファイルのパス
* @return {Array.<Array.<number>>} 学習データの二次元配列
*/
function readFile(filePath) {
var file = fs.readFileSync(filePath);
var arr = csvSync(file);
// 以下の処理で、配列内の値が文字列のため数値にパース
var columnNum = arr.length;
var rowNum = arr[0].length;
for(var i=0; i<columnNum; i++) {
for(var j=0; j<rowNum; j++) {
arr[i][j] = parseFloat(arr[i][j]);
}
}
return arr;
}
/**
* 二次元配列を入力データと正解データに分割し、正規化する関数
* @param {Array.<Array.<number>>} arr 正規化前の配列
* @return {Array.<Array.<number>, number>} 正規化後の配列
*/
function normalizeArr(arr) {
var columnNum = arr.length;
var rowNum = arr[0].length;
var normArr = Array(columnNum); // 長さががデータ数の配列を用意
for (var i=0; i<columnNum; i++) {
normArr[i] = Array(2); // 学習データと正解データを分割するための配列を用意 normArr[i]=[学習データ,正解データ]
normArr[i][0] = Array(rowNum-1);// 長さが学習データの次元数の配列を用意 normArr[i]=[[学習データ[0],...,学習データ[rowNum-1]],正解データ]
// 以下の処理で、正規化
for (var j=0; j<rowNum-1; j++) {
normArr[i][0][j] = arr[i][j];
}
normArr[i][1] = arr[i][rowNum-1];
}
return normArr;
}
/**
* 正規化された配列からモデルを生成する関数
* @param {Array.<Array.<number>, number>} arr 正規化された配列
* @return {Promise} Promiseのインスタンス
*/
function initModel(arr) {
return new Promise(function (resolve, reject) {
var clf = new svm.CSVC(); // 分類器の生成(今回の設定はデフォルト)
clf.train(arr).spread(function (model, report) {
newModel = svm.restore(model);// モデルの生成
resolve("モデル生成完了"); // モデル生成完了後、thenメソッドが呼ばれる
});
});
}
/**
* モデルからテストデータの分類を行う関数
* @param {Array.<Array.<number>>} テストデータ
*/
function estimate(arr) {
if(newModel) { // モデルのnullチェック
var result = Array(arr.length);
for(var i=0; i<arr.length; i++) {
result[i] = newModel.predictSync(arr[i]); // モデルからテストデータを分類
console.log(result[i]); // 結果の表示
}
}
}
モデルの生成が非同期で行われるため、Promiseを使って「モデル生成 -> テストデータの分類」の順で処理します。
学習データの準備
今回は、RGBの値から色を分類するための学習データを作ります。
左からR値、G値、B値、正解データとなっています。
正解データは、1が赤、2が緑、3が青とラベル付けしてあります。
255, 49, 44, 1
235, 114, 0, 1
185, 0, 0, 1
180, 9, 72, 1
92, 194, 24, 2
0, 144, 66, 2
69, 193, 112, 2
0, 228, 69, 2
0, 9 ,72, 3
65, 164, 255, 3
107, 180, 231, 3
108, 81, 194, 3
テストデータの準備
228,79,107
上の色をテストデータとしてセットします。
赤ぽいので、1(赤)と分類されると嬉しいですね。
学習データとテストデータはmain.js
と同じ階層に置きます。
ここまでで、ファイル構成が以下のようになると思います。
test_svm/
├ main.js
├ node_modules/
├ package.json
├ learning_data.csv
└ test_data.csv
実行
$ node main.js
1
今回生成したモデルは、[228,79,107]を1(赤)と分類しました。
期待通りの結果ですね!
最後に
私はSVMについてあまり知識がありませんが、モジュールを使うことで簡単に実装できました。
やっぱり実際に動いているものを見ると、中で何が行われているか気になりますよね〜
本記事では、色の分類をするモデルを生成しましたが、他のものを分類することもできます。
よかったら遊んでください。