ml5.jsでKNNクラスタリングしてみる。

先日の技術書展 6での購入物の中に
ml5.js と p5.js でつくる機械学習コンテンツプログラミングがあった。
連休中に取り組んだら、内容が面白かったので興味から「ml5.js」の提供する他の機能を使ってみたので、そんなメモ。

目次

やりたいこと

ml5.js」は機械学習のライブラリなので、
「学習」をしてみたかった。
人間の姿勢取得や動体検知も面白いのだけど、
興味があったのは学習することだったので、タグ付けした多数の画像を入力にして、
学習データを作る。推論することを試すことにした。

学習する、推論するだけなら、ブラウザいらないよね!

ダメだった。
ml5.js を参照しているコードを node のコマンドラインで実行したら、
window オブジェクトが無いという趣旨のエラーで解決できなかった。
悔しいが断念。

コード

コマンドライン実行ができなかったので、
学習自体もブラウザで実行することにする。

ml5.js を npm からインストールしてきたが、使いたい KNN 分類器がどうも動作できず、
Github の ml5.js の dist
の ml5.min.js と ml5.min.js.map をダウンロードして、node_modules\ml5\distの中身を差し替えておく。
(この辺は github の使い方がちゃんとしてる人は、もっと違うやり方をするんだろうか?)

ディレクトリ構成は以下の通り、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
│  package-lock.json
│ package.json
│ webpack.config.js
├─node_modules
├─public
│ │ index.html
│ │ list.json
│ │
│ └─img
│ │ test.jpg
│ │
│ ├─A
│ │ A1.jpg
│ │ 画像多数
│ │
│ └─B
│ B1.jpg
│ 画像多数

└─src
index.js

読み込みする画像と、タグ付けの内容を list.js に定義する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
{"data":[
{"path":"\/img\/A\/A1.jpg","tag":"A"},



{"path":"\/img\/B\/B23.jpg","tag":"B"}
]}
```

実装コードは以下の通り、
```javascript index.js

const ml5 = require("ml5");
const axios = require("axios");

let imageEl = document.getElementById("image");
let messageEl = document.getElementById("message");
let probabilityEl = document.getElementById("probability");

let KnnClassifier;
let featureExtractor;
let datalist = [];

let setup = async () => {
console.log("[exe]", "setup");

//データを準備
datalist = await axios.get("/list.json").then(res => {
return res.data.data;
});
console.table(datalist);

//KNN分類器を作成
KnnClassifier = ml5.KNNClassifier();
featureExtractor = ml5.featureExtractor("MobileNet", culc);
};

let showresult = async results => {
console.log("[exe]", "showresult");
console.table(results);
messageEl.innerText = `[判定結果]:${results.label}`;

let text = (() => {
let keys = Object.keys(results.confidencesByLabel);
let tm = "判定確率\n";
for (let i = 0; i < keys.length; i++) {
tm += `[${keys[i]}]:${results.confidencesByLabel[keys[i]]}\n`;
}
return tm;
})();

probabilityEl.innerText = text;
};

let ch_image = function(el, path) {
console.log("[exe]", "ch_image");
el.src = "";
el.src = path;
return new Promise(resolve => {
el.onload = () => {
resolve();
};
});
};

let setlabel = async function(el, path, label) {
console.log("[exe]", "setlabel");

//画像差し替え
await ch_image(el, path);
//特徴量を取得し
const features = featureExtractor.infer(imageEl);
//knn分類機にlabelの名称で登録
KnnClassifier.addExample(features, label);
};

let culc = async () => {
console.log("[exe]", "culc");

//モデル作成
for (let i = 0; i < datalist.length; i++) {
console.log("[img]", i);
await setlabel(imageEl, datalist[i].path, datalist[i].tag);
}

//推論
await ch_image(imageEl, "./img/test.jpg");
const featuresTest1 = featureExtractor.infer(imageEl);
console.log("推測開始:");
// KNN分類器で分類を開始
KnnClassifier.classify(featuresTest1, (err, result) => {
// エラーを表示する
if (err) {
console.error(err);
}
console.log("結果");
console.log(result);
showresult(result);
});
};

(async () => {
await setup();
})();

上記を作成し、npm run dev で実行する。
コンソールに表示された URL にアクセスすることで、
パラパラと画像が連続で読み出しされて最後に、推論結果を表示する。

学習対象画像とタグ付けは、動物でも何でもいいと思う。
ただ、画像のサイズはそろえたほうが精度がいいような気がした。
縮小専用。使うかjimpとか使えばいいと思う。

TensorFlow.jsで線形回帰分析だけ触って挫折した身からすると、
すごく触りやすかった。

ではでは。