見出し画像

わずか3行のJavaScriptで手描き画像認識をする! 第1章❸ 変数の「引数」と「配列」を理解しよう ー 『JavaScriptでやる1分間プログラミング』

前回のパート❷では、DoodleNetという手書き画像の認識モデルをブラウザにロードしました。モデルの読み込みには時間がかかるので、読み込みが終わったら「モデルの読み込み完了!」というメッセージを表示するよう、「書き置き」のメカニズム、コールバック”を使って、時間差でそのめせーじを表示させるところまでやりました。


さあ、モデルが準備完了なのですから、さっそく描いた絵をモデルに渡して、それがどう反応するか見たいですよね。ただ、モデルにどうやって絵を渡すのかちょっと考えてみます。

描き終えて認識か、描きながら認識か?

普通であれば、まず絵を描いてからボタンを押すと、書いた絵が機械学習モデルに渡されて、「ネコですね!」のようなメッセージが表示さえるようなデザインを考えます。下の図で上部に示した流れです。

画像1

でも今回のコーディングでは、「絵を描き進むにつれ、何の絵かを推測してくれる」というデザインにしています。

上の図では、まずネコの耳の部分を書くと、そこで「動物ですか?」という推測が出てきます。耳だけでは何であるかは分かりません。そして顔のラインを完成させ、目をつけると今度は「ネズミかな?」と出てきます。まだよくわかりません。でも髭を付けたとたんに「ネコですね」という推測が出てきます。

このようにしたほうがダイナミックなデザインになるので、面白みが増すことでしょう。

では前回示したコーディングのステップをもう一度おさらいします。

ステップ❶:画像分類をする”モデル”をウェブページ内に読み込む
ステップ❷:キャンバスに描いた絵をモデルに渡す
ステップ❸:モデルは何の絵であるか推測する
ステップ❹:モデルが推測した絵の名前を画面に表示させる

ステップ❶は前回完了したのでステップ❷からやっていきます。

プロジェクトフォルダの用意

前回はOekaki3にコードを書いていきました。今回用にOekaki4というフォルダを新規作成し、その中にOekaki3のファイル(index.html, sketch.js, style.css)をコピペしてください。準備ができたらVSCodeを立ち上げ、「ファイル」ー「フォルダを開く」からOekaki4フォルダを選択してください。

ステップ❷:キャンバスの絵をモデルに渡す

まず前回までのJavaScriptファイル、Sketch.jsを確認してください。

let canvas; //絵を描くエリア
let clearButton; //消すボタン

//キャンバスの設定
function setup() {
   //キャンバスを用意
   canvas = createCanvas(400, 400);//Canvasを作成
   canvas.parent('canvas-div'); //@@@ここを追加@@@
   background(255); //Canvasの背景を白にする
   //ボタンを用意
   clearButton = createButton('消す');//ボタンを作成
   clearButton.parent('canvas-div'); //@@@ここを追加@@@
   clearButton.mousePressed(clearCanvas);//ボタンクリックの関数を指定
     
   //モデルを読み込む
   doodleClassifier = ml5.imageClassifier('DoodleNet', modelReady);
   
   //ロード中のメッセージを表示
   resultsDiv = createDiv("モデルをロードしています・・・");
   resultsDiv.parent('results-div');
}

//モデルがロードされた時の処理
function modelReady() {
   resultsDiv.html("モデル準備完了!");
}

//マウスで絵を描くための関数
function draw() {
 if (mouseIsPressed) {
   strokeWeight(18);
   line(mouseX, mouseY, pmouseX, pmouseY);
 }
}

//絵を全て消すボタンの動作
function clearCanvas() {
   background(255);
}

そこで、モデルのロードが完了したらコールバックとして呼ばれるmodelReady関数に1行を加えます。つまり、モデルの読み込みが完了したら「モデル準備完了!」というメッセージを出したので、その次に画像を認識させます。「//キャンバスの絵を認識する」とコメントが付いている1行を加えてください。

//モデルがロードされた時の処理
function modelReady() {
 resultsDiv.html("モデル準備完了!");

 //キャンバスの絵を認識する
 doodleClassifier.classify(canvas, gotResults);
}

次に示した1行は、setup関数のところでDoodleNetのモデルを読み込むコードですが、ブラウザ内にロードされたモデルそのものはdoodleClassifierという変数に入っています。。

//モデルを読み込む
doodleClassifier = ml5.imageClassifier('DoodleNet', modelReady);

つまりdoodleClassifierがモデルそのものです。では、それに対してキャンバスの絵を渡してあげます。

//キャンバスの絵を認識する
doodleClassifier.classify(canvas, gotResults);

まず、doodleClassifierというモデルにはclassify(分類する)というメソッドがあります。それに対してキャンバスの絵(あなたがブラウザの白いキャンバスで書いた絵)、つまりcanvasそのものを渡してやるだけで完了です。機械学習モデルを「使う」という部分はこのたった1行で完了です!

ただ、画像認識というのもそれなりに時間がかかります。こんな時にコールバックを使うのでしたよね。

時間がかかる処理は「書き置き=コールバック」を使う

そこで”書き置き関数”としてgotResult(結果が出た)というコールバック関数を新に用意します。前回のモデルの読み込みと同様、classifyメソッドによる画像認識が終わると、このgotResult関数を実行してくれます。

ステップ❸:モデルが何の絵であるか推測する

さて、doodleClassifierにcanvasの絵を渡せば、画像認識が終わると”書き置き”に指定しておいたgotResultsという関数を呼び出してくれます。では、モデルがどんな絵だと推測したのか、その結果を取得するコードを書いてみます。modelReady関数のすぐ下に次の2行の関数定義を書いてください。

//認識結果を処理する
function gotResults(error, results) {
   resultsDiv.html(results[0].label);
}

ちょっと注意点がありますが、doodleClassifierのメソッド、classifyに渡したコールバックの関数には必ずerror(エラー)とresults(結果)という、関数を呼び出した際に渡す「値」を指定してあげます。これはML5で決められているルールだと理解してください。この関数に渡す値のことを「パラメータ」と言います。

パラメターがとうのこうのと、少々ややこしい話になってきましたね。そこで、関数の仕組みをもう少し理解するために、次のメモnoteを是非読んで、関数の「パラメータ」と「引数」について理解してください

さて、このgotResult関数でerrorresultsというのが関数で指定する「パラメータ」だということは理解してもらったと思いますが、では一体何なのでしょう、そのエラー結果というのは?

先に触れた通り、この2つのパラメータはML5が提供するclassifyメソッドで既に決められていることなのです。まず、画像認識をした時に問題が発生することがあります。そんな時にどんな問題が起きているかを教えてくれるのがerrorです。そして問題なく画像認識が行われた場合にその結果を教えてくれるのが文字通りresultsです

まずは認識の結果を知りたいのであれば、resultsの処理の仕方を理解し、機械学習モデルがどんな絵だと判断したのかを取得してみます。

そこでこのresultsという変数は単なる入れ物ではないのです!ここをじっくりと解説していきます。

画像10

【技術解説】画像認識の結果は「配列」に入っている

さて、ここから機械学習の中核に少し足を踏み入れます。自分で描いた画像をDoodleNetモデルに渡すと、モデルは答えをいくつか言ってきます。それは機械もこんな風に考えているからです。

「このへたくそな絵、ネズミのようも見えるし、リスのようでもあり、ネコかもしれない・・・」

機械学習のモデルというのは滅多なことがないと「絶対これだ!」という決めつけはしません。常にあらゆる可能性を考えた”推測”をするのです。画像を見たときにもresultsの中にはこんな情報が入っているのです。

● ネズミ = 55%
● リス = 31%
● ネコ = 18%
● ・・・

つまり、可能性のあるものの「名前」(これをラベルと言います)をいくつか持ち、それぞれに「可能性」(これを確率または信頼度と言います)が割り当てられています。

みなさんだって次の絵を見てはっきり何であるかはわかりませんよね。ネズミみたいだし、ネコのようでもあるし、あるいは何かの虫を描いたのかもしれません。

画像2

そこでresultsという変数の中には、ネコやネズミや虫など、いくつかの”候補者”が入っています。でも変数の入れ物にはそんなにたくさんの価が入るのでしょうか。それを実現している仕組みが「配列」というものです。

”間仕切り型”の「配列」

例えば文字を入れる変数nameを作ると、そこには文字列は一つしか入りません。入れ物としてはこんな単純な感じです。

画像4

ところが、入れ物の中に同じ種類のものを何個も入れないといけないケースがあります。入れ物のイメージはこんな感じ。

画像5

これが「配列」という仕組みです。まずは次の概念図をご覧ください。

画像3

今回の画像認識の結果が入っているresultsという入れ物には結果がいつくも入っています。前に説明した通り、ネコ、ネズミ、虫などなど。そうやって複数あるデータには番号は振られています。0, 1, 2, 3, ....et.

results -> results[0], results[1], results[2], results[3], .... et.c

コンピュータが数を数える時は1ではなく0から始めるので気を付けてください。

ステップ❹:モデルが推測した絵の名前を画面に表示させる

そこで配列の一番最初、results[0]をみてみると、そこにはさらに2つのデータが入っています。一つは認識したものの名前で「label:ラベル」と言います。そしてもう一つはその確率で「confidence:信頼度」です。ではまず、結果の中でも一番最初(モデルが一番自信を持っているものです)のラベルを表示してみます。

前出のgotResultsメソッドにはすでに画面表示のコードが含まれています。

//認識結果を処理する
function gotResults(error, results) {
   resultsDiv.html(results[0].label);
}

このresults[0]は結果の一番最初の項目で、そこの属性を .rlabelというように書いてラベル名を取得しています。それをresutlsDiv(前回、「モデル準備完了!」といったメッセージを表示させた場所)に表示するようにしています。

gotResults関数を書いたらファイルを保存し、index.htmlをダブルクリックしてブラウザで表示させてみましょう。あれ?モデルが準備完了になったとたん、いきなりline(線)という文字が出てきます。しかも何か書いても表示は変わりません。一体何が起きているのでしょう。

画像6

描画するたびにモデルに認識してもらう

どうしてこうなるかは、よく考えると分かります。

modelReady関数はモデルがロードし終わった時に呼ばれるコールバック関数でした。その中ではロードが完了したというメッセージを表示させ、そしてdoodleClassifierのClassifyメソッドにキャンバスの絵を渡していましたよね。でも、モデルのロードが終わった瞬間はまだ絵を何もかいていません。つまりclassifyメソッドには白紙のキャンバスを渡しただけ。どうやらこのDoodleNetのモデルは、白紙の絵を渡すと「何かの線ですか?」と推測するようです。だからlineという結果が出てきて、そこでフリーズしてしまうわけです。

そこでgotResults関数に次の一行を追加します。そうです、classifyメソッドの呼び出しの行をもう一度ここに書き加えるだけです。

//認識結果を処理する
function gotResults(error, results) {
   //認識結果の最初のラベルを表示する
   resultsDiv.html(results[0].label);
   //すかさずclassifyメソッドでまた認識してもらう
   doodleClassifier.classify(canvas, gotResults);
}

これで何が起きるかと言うと、classifyは認識が終わるとgotResults関数を呼び出します。でもそのコードはgotResults関数の中にあります。つまり、自分で自分を呼ぶという形になるのが分かりますか?

こうすることによってこのgotResults関数が何度も何度も呼び出され、その都度キャンバスにある絵を認識し、結果をresultDivに書き出されます。つまり永遠と認識作業を続けるわけです。

まずはどうなるか確かめてみます。コードを加えてファイルを保存し、ブラウザを更新してください。今度は絵を描くにつれ、認識結果のテキストが目まぐるしく変わりますよね。

キャプチャ

例えばネコを書いてみてください。こんなひどい絵でも最終的には「Cat」と表示されます。

画像8

すごい!! 認識がうまくいっています!

画像9

次回は認識結果から確率を表示させます

まずはいろんな絵を描いて遊んでみてください。同じネコでも描き方によってすぐにCatと出てくる場合もあれば、なかなか出てこないケースもあります。認識にどんなクセがあるのか確かめてみるのも面白いですよ。

なお、認識できるのは345種類の絵ですが、他に何があるかは前回パートをご覧ください。また、結果はすべて英語で出てきます。分からない名前が出てきたら辞書で調べてみてください。

では次回は結果表示をもう少し工夫してみます。まず、トップ1だけではなく2番目のも表示させて、さらにそれぞれ何パーセントの確率で推測しているのか、その数字も表示させてみます。すると認識の状況がもっと詳しく理解できるはずです。

乞うご期待!!


この記事が気に入ったらサポートをしてみませんか?