電車好きの子供からの猛攻に耐える妻を救うLINE Botを作る

こちらの記事に発想を得て、自分でもLINE Botを作った。

誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた
https://qiita.com/Teru_3/items/80cecd138860fbd0c924

Qiitaで公開していただいたことにとても感謝いたします。

# 仕様
<B>「LINEから電車の画像(写真でも絵でも)を送ると、その系統・形式を教えてくれる」</B>という、至ってシンプルなもの。

うちの子供が電車をとてもだーーい好きなのだが、普段から

<FONT COLOR="blue">「(駅のホームに入ってきたのを指しながら)あの電車なーんだ？」</FONT>

とか

<FONT COLOR="blue">「(自分が描いた絵を見せながら)何系描いたかわかる？」</FONT>

といった質問を、(子供にとっては残念なことだが)電車に全く興味のないママに対して飽きもせず浴びせまくっており、ママがほとほと疲れている・・・というのを科学の力で助けてあげよう！ということで手がけてみた。

なお、今回は、今現在子供が一番興味を持っている「東急」を対象にした。ちょっと前は「京急」だったし、その前は乗ったこともない「京阪」だったりしたので、興味の対象が移ってしまう前に仕上げなければならない。

# 準備
### 開発環境
先日、[React学習のために導入した環境](https://qiita.com/faz479704/items/1f568c34ad707a114070)をそのまま利用。

- VSCode(Visual Studio Code) 1.51.1
- npm 6.14.8
- Node.js 14.15.1

### heroku
Node.jsを動かすための環境としてherokuを利用する。

(1) 下記サイトにてアカウントを作成する

heroku
https://jp.heroku.com/

(2) 「herokuのCLI」をインストールする

heroku CLI(英語)
https://devcenter.heroku.com/articles/heroku-cli

Ubuntu 16+はsnapを使う方法が掲示されているが、自分の開発環境にsnap入れていなかったので、「Other installation methods」の「Ubuntu / Debian apt-get」の方法を取った。

```
$ curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
```

### LINE Messaging API
今時LINEアカウントを持っていない人はなかなかいないと思うので、アカウントの用意については問題ないと思う。

LINE Developers
https://developers.line.biz/ja/

プロバイダー作成→チャネル作成→アクセストークン生成と進めることで、チャネルID、チャネルシークレット、チャネルアクセストークンの３つのデータを得る。Webhookはherokuへのデプロイが終わった後に設定した。

ただ、必要な情報を設定・取得するのに、LINE DevelopersとOffcial Account Managerの２つのサイトを行き来するのがやや面倒に感じた。

### AIメーカー
こちらはTwitterアカウントが必要ということで、全くと言っていいほど使っていなかったTwitterアカウント(パスワード忘れてた)を掘り起こした。

AIメーカー
https://aimaker.io/

無料の範囲で使わせていただくのだが、こういったサイトを個人で開発・運営される才能というか能力は本当にすごいと思う。

### 電車の画像
一番面倒なのは**「AIメーカーに学習させる画像の収集」**と考えていたのだが、AIメーカーのサイトでは検索文字列を入れると自動的に画像を集めてくれる機能(50個ほど)があり、非常に助かった。

集められた画像が「学習に使えるのか」の判断は必要だが、それでも５０枚ｘ判別させたい車両型番の数を集めてアップすることを考えたら、かかる時間と手間は雲泥の差。本当にすごい。

なお、東急が予想以上に形式が多い＆ラッピング車のバリエーションがあったので、~~全部やるのは面倒~~比較的新しい電車に絞ることにした。

- 5000系(田園都市線、緑と赤)
- 5000系5050番台(東横線、ピンクと赤)
- 5000系5080番台(目黒線、青と赤)
- 2020系(田園都市線、緑)
- 3020系(目黒線、水色)
- 6020系(大井町線、オレンジ)

# 実装
参考記事にjsのソースが掲載されていたので、まずは動かしてみることを最優先として

- LINEのチャネルID、トークン、シークレットを変更
- AIメーカーのモデルID、トークンを変更
- AIメーカーからの応答のlabelを見て判断するswitch文の変更

を施してデプロイした。

しかし、これでは全く勉強にならないので、ソースを読んで自分で説明ができるように理解しよう！と思い、ソースを印刷しようとしたら、いくら探してもVSCodeに印刷機能が無くて驚いた(拡張機能にはあるようですが)。

今時、印刷なんかしないんですかね、やっぱ・・・。

# トラブル
### git commitでエラー
ソースを変更＆保存後、VSCodeのターミナルから以下の順番にコマンドを実行したが、commitでエラーとなった。

```
$ git init
$ heroku create
$ git add .
$ git commit -m 'init'
(こんな感じのエラーでした)
Run
  git config --global user.email "yourmail@domain"
  :
error
```

このエラーメッセージに何やら指示(RUN)があったので、以下のコマンドを実行。

```
$ git config --global user.email "herokuに登録したアカウントのメアド"
$ git config --global user.name "herokuに登録した姓名"
```

そうしたところ無事、git commit出来るようになったが、メアドも名前もherokuのものにしなくてもよかったのでは？と後で思った。

### LINE「正常以外の応答が返された」
gitのcommitが出来たら、herokuにデプロイする。

```
$ git push heroku master
：
(結構時間かかった)
：
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/xxxxxx-xxxxxx-99999.git
   194da99..029fa94  master -> master
```

こんな感じに「master　-> master」と出ればOK。

早速、LINE Developerの管理画面の「Webhookの検証」を実行したところ、正常以外の応答が返されたとのダイアログが出てきた(まぁ予想通り)。

しかし、果たして何が起きたのか？を調べる術がわからなかったので、ひとまずブラウザからherokuにデプロイした先のURLを入力して実行したところ、アプリケーションエラーとなったので、jsに問題があることはわかった。

そのエラー画面に「heroku logs --tailしてね」とあったので、VSCodeのターミナルで実行すると、herokuのログがずらずらっと表示された。そこで再度、実行するとログがばーっと動き、エラーコード503を返していることが判明。

さらにログを遡っていくと、jsの最初の頭の方でクラッシュしており、

```
require('request');
```

が悪さをしているようであった。

ということで対処として

```
$ npm install request  <--- まさか入っていないとは・・・
$ git add .
$ git commit -m 'init'
$ git push heroku master
```

再デプロイ後、LINE Developerから検証すると「成功」となり、ログでもステータスコード200を返しているのが見えたので、この問題は解決した。

# 動作確認
早速、LINEから色々な画像を送ってみた。

- 実車の写真
![qiita_20201126_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/56abf842-8179-dcac-e3da-f32e59c3ddd8.png)

- 実車に近い形のおもちゃの写真
![qiita_20201126_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/62f62ae6-9e65-e796-6a76-9df53f40a65d.png)

- プラレール
![qiita_20201126_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/51c426bd-e709-870b-0cb4-738caf62678d.png)

- 子供が自分で描いたものをプラレールにかぶせたもの
![qiita_20201126_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/fdde35fe-9eda-6d7f-92d3-d0d8a37de34c.png)

といった感じで、送った画像の実体が実車でもおもちゃでも
ちゃんと判定できていた。

が、実際には的中率は５割程度といったところで、外れた場合はなぜか5080番台、または3020番台(どちらも青系の色が入った目黒線)の判定に偏っている感じがした。

# 外れる原因
ひとえに<B>「学習させた画像が偏っている」</B>ことが原因だろう。

試しにGoogleで「東急」を画像検索すると
![qiita_20201126_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/2f613970-11f5-2349-4d72-5e582fdf0c81.png)
といった感じで、ほとんどが「前面をメインに、車体は後ろに細く伸びている」といった体の画像である。

今回対象にした50xx系およびxx20系は、前面はほぼ同じ形状かつ色の差異はほとんどない一方、側面には特徴(色)が強く出ている。想像だが、電車に乗る人は電車の前面より側面を見てる割合が多く、鉄道会社も側面で判断できるように配色や行先表示を配しているのだろう。

この学習させた画像を見るに

- 差異が少ない前面を複数のラベルに亘って学習している
- 差異が多いはずの側面の学習量が少ない

というのが今の学習状況になっているのではないかと想像する。

実際にいくつかの画像で試した限りではあるが

- 前面の情報量が多い画像
 - 50xx系なのか、xx20系なのかは当たっている(＝形の違いは判断出来ている)
 - 緑と青の判定が難しいのか、田園都市線と目黒線の取り違えが多い(スコアが近い)
- 側面の情報量が多い画像
 - 緑を含む2020系を青を含む5080番台と判定する場合があるなど、形・色ともにハズレ度合いが大きい
 - ただし、ピンクが特徴的だからか、東横線(5050番台)は間違えないことが多い

といった傾向が見られたので、自分の想像もあながち間違ってはいないと思う。

# 改善(？)
しかし、このままでは

「ママはやっぱわかってないねー(やれやれ)」

とか

「パパが作ったのはハズレてばっかりじゃん(やれやれ)」

ということで親の沽券に関わる事態に瀕するので、どうにか対処しなければならない。

本来ならばAIの調整をするべきなのだろうが、良い方向に改善できるかわからないし、子供の興味が次の鉄道会社に移ってしまえば出番がなくなってしまうので、今出来ることをやろう。

AIメーカーからの応答にはラベルとともにスコア(<1)が含まれており、1に近いほど似ているという判断が可能になる。これを利用して

- １番高いスコアが0.8を超えるときは信頼できるとして「これだね！」と断定する
- 0.8未満の場合はスコアの合計が0.8を超えるまでが「あれか、それか、どれか・・・だよね？」と列挙する。ただし、いっぱい出すと却って怪しくなるので３こまで。

と判定結果の応答のバリエーションを増やして、<font color="red"><B>答えを数打って当たりを拾いに行く方式</B></font>に改善？した。

```tokyu_ai.js
:
            if (labels[0].label && labels[0].score)
            {
/* ここから変更　*/
                const threshold = 0.8   /* しきい値 */
                if(labels[0].score >= threshold) {   /* しきい値以上の場合は断定する */
                    message = 'この電車はきっと「' + labels[0].label + '」ですよね。' + trainMessage[labels[0].label];                    
                }
                else {   /* しきい値未満の場合は列挙する　*/
                    message = 'この電車は';

                    let totalScore = 0.0;
                    let i;
                    for(i = 0; (totalScore < threshold) && (i < 3); i++){
                        message = message + '「' + labels[i].label + '」';
                        totalScore = totalScore + labels[i].score;
                    }
                    
                    if(i > 2){
                        message = message + 'のどれかじゃないかな？';
                    }
                    else {
                        message = message + 'のどっちかじゃないかな？';
                    }
                }
/* ここまで変更　*/
:
```

とりあえずベタに書いてみたが、もっときれいに書けるとは思う。

# 改善？後の動作確認
いろいろな画像を判定させてみた。

- 側面：5050番台 →セーフ

![qiita_20201126_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/c3f7a121-b68d-0806-e5bd-3516973992a7.png)

- 前面：6020系のプラレール → セーフ
![qiita_20201126_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/7d591376-0cec-12da-9761-aea7955edafc.png)

- 以前はアウトだったものも・・・
![qiita_20201126_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/fdd2fab7-a4b8-09c7-6e4a-808f4620cb33.png)

- セーフになった！
![qiita_20201126_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/bc34c5cf-babb-81e6-cf5a-15f81ae8dcd2.png)

- 子供が描いた、実際には無い編成でも
![qiita_20201126_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/224cf5c5-98fd-ae62-c363-e80d1a05f9c2.png)
と候補２つとも正解だった。

これなら何とか親の面目は保てそう。

# おわりに
勝手ながらやり方はほぼ丸パクリさせていただいたが、Node.jsを使ってLINEと他サービスをつなぐやり方の１つを知れたのはとても有意義だった。今回の経験を踏まえてあれやこれやと出来るのでは想像が膨らむし、チャレンジしたくもなる。

また、AIの判断精度を高めるためには、学習コスト(時間、手間)をおろそかにしてはならない(というか肝だろ)こともよくわかった。

少なくとも

![qiita_20201126_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552413/a632ec67-baef-ef22-d2f2-af5f3eecb3cc.png)

と断定してしまうような学習状況というのはよろしくない(正解は5000系の田園都市線だが、AIメーカーのスコアは0.81と試した中では相当スコアが高い)ので、機を見て側面も含めた再学習をさせてみたいと思う。

ちなみに[東急電鉄の現有形式はこれだけある](https://ja.wikipedia.org/wiki/%E6%9D%B1%E6%80%A5%E9%9B%BB%E9%89%84#%E7%8F%BE%E6%9C%89%E5%BD%A2%E5%BC%8F)そうで、さらにはラッピング車などが数種類(リバイバルの緑、黄色のHikarie号など)あるので、全部やろうとすると・・・