かつてML.NET による画像分類をしたのですが、これをWebアプリとして公開する方法はないのでしょうか?
Microsoft.ML, Microsoft.ML.Vision, Microsoft.ML.ImageAnalytics, SciSharp.TensorFlow.Redist V2.3.1 など必要なものをインストールしてASP.Net Coreでアプリを作ってみると自分のPC環境では動作するのですが、サーバー上で動かそうとするとライブラリが不足しているというエラーメッセージがでて動いてくれません。そして不足しているライブラリをインストールしたくても権限がないのでインストールできません。このあたりはPVSであればできるのかもしれませんが、共用サーバーではどうすることもできません。
あきらめていたところJavaScriptのライブラリでml5.jsの存在を知りました。TensorFlow.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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>よくあるml5.jsをつかった画像分類</title> <script src="https://unpkg.com/ml5@0.4.3/dist/ml5.min.js"></script> </head> <body> <img id="image" src="1.png"> <div><input type="file" accept="image/*" id="file-input" ></div> <p>モデルデータ <span id="isModelLoaded"></span></p> <div><button id="run" onclick="run()">実行</button></div> <div id="results"></div> <script> const $isModelLoaded = document.getElementById('isModelLoaded'); $isModelLoaded.innerText = '読み込み中'; const $results = document.getElementById('results'); const $run = document.getElementById('run'); $run.style.display = 'none'; const $fileInput = document.getElementById('file-input'); $fileInput.style.display = 'none'; // 画像の準備 const $image = document.getElementById('image'); // 画像分類器の生成 const classifier = ml5.imageClassifier('MobileNet', modelLoaded) // モデル読み込み完了時に呼ばれる // モデルの読み込みが完了した旨を表示するとともにボタンとファイル選択 input 要素を表示させる function modelLoaded() { $isModelLoaded.innerText = '読み込み完了'; $run.style.display = 'block'; $fileInput.style.display = 'block'; } // 画像分類 // 結果を表示する function classify(image){ classifier.classify($image, (err, results) => { console.log(results); let html = ''; html += '<p>結果</p>'; html += '<table>'; for(let i=0; i<results.length; i++){ $results.innerHTML = results[i]['label']; let confidence = results[i]['confidence']; html += `<tr><td>${results[i]['label']}</td><td>${confidence}</td></tr>`; } html += '</table>'; $results.innerHTML = html; }); } function run(){ classify($image); } // 選択されているファイルが変更されたら反映させる $fileInput.addEventListener('change', handleFileSelect); function handleFileSelect() { const files = $fileInput.files; const reader = new FileReader(); reader.readAsDataURL(files[0]); reader.onload = (e) => { const imageUrl = e.target.result; $image.src = imageUrl; } } </script> </body> </html> |
自分で選んだ画像で学習させる
ml5.jsは機械学習のライブラリなので、やるなら自分で学習させてみたいものです。タグ付けした画像から学習データを作り、これをつかって画像分類する処理をやってみます。画像は犬と猫と鳩だけです。それ以外のものもやろうとすると処理に時間がかかるので、これくらいで妥協します。
まずHTML部分を示します。
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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ml5.jsで画像分類</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script> <style> #file-input { margin-bottom: 10px; display: block; } #classify-after-train { margin-bottom: 10px; display: block; } #classify { margin-bottom: 10px; display: block; } #download { margin-bottom: 10px; display: block; } </style> </head> <body> <p><img id ="image" src="hato.png" /></p><!-- 最初は鳩の画像を表示させておく。この部分は変更できる --> <img id ="train-image" /><!-- 学習に読み込んだ画像をここに表示させる。実際には非表示にするので見えない --> <div id = "message"></div><!-- 現在の状態(学習中とか学習完了とか)を表示する --> <!-- ファイルを選択したり処理を開始するためのボタンなど --> <input type="file" accept="image/*" id="file-input" > <button id="classify-after-train" onclick="classifyAfterTrain()">学習後に推論する</button> <button id="classify" onclick="classify()">既存のモデルで推論する</button> <button id="download" onclick="download()">モデルをダウンロードする</button> <!-- ここに分類結果を表示する --> <div id = "result"></div> <button id="clear" onclick="textClear()">推論結果をクリア</button> <script src="./app.js"></script> </body> </html> |
以下、JavaScript部分を示します。
画像ファイルを読み込んで学習データを生成
まずはサーバー上にあるファイルを読み込んで学習データを生成する処理を示します。この処理はページを読み込んだら自動的に開始されます。
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 |
let $trainImage = document.getElementById("train-image"); let $classifyAfterTrain = document.getElementById("classify-after-train"); let $classify = document.getElementById("classify"); let $download = document.getElementById("download"); let $image = document.getElementById("image"); let $message = document.getElementById("message"); let $result = document.getElementById("result"); // 準備ができるまで使えないボタンは非表示にする $trainImage.style.display = 'none'; $classifyAfterTrain.style.display = 'none'; $classify.style.display = 'none'; $download.style.display = 'none'; let KnnClassifier; let featureExtractor; // 学習につかわれる画像ファイルとタグ(犬・猫・鳩) class data{ constructor(imagePath, imageTag){ this.path = imagePath; this.tag = imageTag; } } async function train() { // 学習用のデータは各種15枚。相対urlでアクセスできるようにする let datalist = []; for(let i = 1; i <= 15; i++) datalist.push(new data(`./train-data/cat/${i}.png`, '猫')); for(let i = 1; i <= 15; i++) datalist.push(new data(`./train-data/dog/${i}.png`, '犬')); for(let i = 1; i <= 15; i++) datalist.push(new data(`./train-data/hato/${i}.png`, '鳩')); // KNN分類器を作成する。完了するまで待機 await new Promise(resolve => { KnnClassifier = ml5.KNNClassifier(); featureExtractor = ml5.featureExtractor("MobileNet", () => resolve('')); }) // モデル作成 // 画像差し替えながらそれらの特徴量を取得し、分類機にタグ名で登録 for (let i = 0; i < datalist.length; i++) { $message.innerText = `[学習中]:${datalist[i].path}`; await changeImage(datalist[i].path); const features = featureExtractor.infer($trainImage); KnnClassifier.addExample(features, datalist[i].tag); } // 完了したら操作用のボタンを表示させて操作可能にする $classifyAfterTrain.style.display = 'block'; $download.style.display = 'block'; $message.innerText = '学習完了'; } function changeImage(path) { $trainImage.src = ""; $trainImage.src = path; return new Promise(resolve => { $trainImage.onload = () => { resolve(''); }; }); } window.onload = ()=>{ train(); loadClassifier(); // こちらは後述 } |
学習後に推論する
[学習後に推論する]ボタンがクリックされたら、上記の処理で生成された学習データをつかって現在表示されている画像が犬、猫、鳩のどれなのかを推論して結果を表示します。その処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function classifyAfterTrain() { KnnClassifier.classify(featureExtractor.infer($image), (err, result) => { if (err) // エラーの場合はそれを表示する console.error(err); showResult(result); }); } // 推論できたら結果を表示する function showResult(results) { let keys = Object.keys(results.confidencesByLabel); let text = "判定確率\n"; let label = ''; let max = 0; for (let i = 0; i < keys.length; i++) { text += `[${keys[i]}]:${results.confidencesByLabel[keys[i]]}\n`; if(max < results.confidencesByLabel[keys[i]]){ max = results.confidencesByLabel[keys[i]]; label = keys[i]; } } $result.innerText = `[判定結果]:${label}\n\n${text}`; } |
ユーザーが画像ファイルを選択したときの処理を示します。これは最初のサンプルコードで示したものと同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const $fileInput = document.getElementById('file-input'); $fileInput.addEventListener('change', handleFileSelect); // 選択されているファイルが変更されたら・・・ function handleFileSelect() { const files = $fileInput.files; const reader = new FileReader(); reader.readAsDataURL(files[0]); reader.onload = (e) => { const imageUrl = e.target.result; $image.src = imageUrl; } } |
学習データの保存とロード
生成された学習データをダウンロードできるようにします。ボタンをクリックするとmyKNN.jsonという名前でファイルをダウンロードできます。これを同じディレクトリにアップロードすればこれをつかって推論ができるようになります。
1 2 3 |
function download(){ KnnClassifier.save(); } |
すでに存在する学習データから推論する処理を示します。
loadClassifier関数もページが読み込まれたら自動的に呼び出されます。そしてロードされたら[既存のモデルで推論する]ボタンをクリック可能にします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
let knnClassifier2; let featureExtractor2; async function loadClassifier(){ knnClassifier2 = ml5.KNNClassifier(); await new Promise(resolve => { knnClassifier2.load('./myknn.json', () => resolve('')); }); await new Promise(resolve => { featureExtractor2 = ml5.featureExtractor("MobileNet", ()=> resolve('')); }); $classify.style.display = 'block'; } |
[既存のモデルで推論する]ボタンをクリックすると推論が行なわれます。
1 2 3 4 5 6 7 |
function classify(){ knnClassifier2.classify(featureExtractor2.infer($image), (err, results) => { if (err) // エラーの場合はそれを表示する console.error(err); showResult(results); }); } |
[学習後に推論する]も[既存のモデルで推論する]も結果的に同じ学習データを使うので同じ結果になります。両方の結果の違いがわかるように[クリア]ボタンをクリックしたら表示されている結果がクリアされるようにしました。
1 2 3 |
function textClear(){ $result.innerText = ''; } |