
【第4回】ResponderとKerasを使って機械学習Webアプリケーションを作ってみた【アプリ完成編】
2021.12.20
第4回~ResponderとKerasで機械学習アプリケーションを作りたい!~
この記事は、「ResponderとKerasを使って機械学習Webアプリケーションを作ってみる」という連載記事になります。
連載終了後、「機械学習と相性が良いのでは!?」と思い立ち、「Responder」と「機械学習」を絡めた記事を書くことを決めました。
簡単なアプリケーションではありますが、Responderのさらに詳しい使い方が分かっていただける記事になるはずです!
まずは、第1回をお読みください
学習結果の表示

本アプリにおける最後の実装部分です。
前回は、「バックグラウンドで機械学習を行い、学習終了待機ページへのリダイレクト」まで実装しました。
今回は、「学習終了したら学習結果ページへ移行させる」処理を実装していきます!
まずは、第1回をお読みください
Responderのバックグラウンド処理は終了合図を送れない
早速ですが、筆者が遭遇した苦悩をご紹介します(笑)
普通は、非同期処理というと、他スレッドの処理が終わるのを、どこかのポイントで待機することが可能です。
しかし、Responderの非同期処理ではそれができないようです。
また、Responderでは、ビューの表示はルーティングされたクラスのメンバ関数全ての処理が終わるまではされないようなので、関数内で逐一ビューを変更させることも不可能でした。
Pythonの「threadingモジュール」で実装すれば解決するのですが、それだとResponderの恩恵がないので、今回はなんとかResponderのバックグラウンド処理を使って実現してみたいと思います。
少しゴリ押しな部分もありますが、暖かい目でみてください(笑)
試行錯誤の末…
結果どうしたかというと、作成した学習結果のグラフが出来上がったら、「学習結果ページへリダイレクト」とすることにしました。
これならバックグラウンド側から信号を送る必要もないし、間接的にバックグラウンド処理の終了を認知することができます。
しかし、ビューを表示させた段階では、すでに Python の手から離れており、もうブラウザ側でしか処理を行えません。
したがって、Javascript でファイルの有無を逐一確認すれば良さそうです。
が、これもうまくいきません。
クッキーのせいでうまくいかない
Web上で、画像形を処理するといくつか不具合が生じます。
まず、ブラウザでは2回目移行のページ訪問の際、余計な読み込みを防ぐために画像やCSS、Javascriptファイルなどはクッキーに保存されます。
したがって、最初に「画像がない」と判断された場合、しばらくそれがWeb上では反映されません。
「じゃあクッキーを保持しないようにすれば良い」というのもありですが、それもなかなかうまくいかず・・・
定期的にページを読み込ませる方法
最終的にとった方法は「定期リロード」です。
Responderでは、「URLにアクセスしたとき、URLを解析しコントローラに渡しビューを表示させる」といった処理を行なっています。
したがって、ページリロードを行えば、再びPythonの処理をいれることが可能です。
そのときに、学習結果画像があれば「学習結果ビューへ」、そうでなければ「学習中ビューへ」のように処理をわけることができます。
さっそく実装してみる
まずは、学習中のビューに定期リロードを行うように Javascript を書きます。
1 2 3 4 5 6 | <script> const timer = 5000; // ミリ秒で間隔の時間を指定 window.addEventListener('load',function(){ setInterval('location.reload()',timer); }); </script> |
記述する場所は、learning.htmlの {% endblock %} 前で構いません。
これで、5秒ごとにリロードしてくれます。
(秒数は適当に変更してもらって大丈夫です)
コントローラの修正
次に、コントローラを修正していきます。
まず、ファイル存在チェックを挟むので、以下を新たに controller.py にインポートします。
1 | import os |
その後は、以下のように修正しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class LearningController: async def on_get(self, req, resp, uid): img_path = 'static/images/' + uid + '_history.svg' if not os.path.isfile(img_path): title = 'ネットワークを学習中...' resp.html = api.template('learning.html', title=title, uid=uid) else: svg = open(img_path, 'r').readlines()[4:] # svgファイルを削除する場合 # os.remove(img_path) title = 'ネットワークの学習が完了しました' resp.html = api.template('result.html', title=title, uid=uid, svg=svg, result=result) |
ビューの作成
最後に、ビューを作成します。
先程、いくつか引数を受け取りましたが、それらを展開するだけです。
注意点は、Jinja2 でのリストの取り扱い位です。
これはコードを見て貰えれば、理解していただけるかと思います!
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 | {% extends "layout.html" %} {% block content %} <br> <h1>{{ title }}</h1> <hr> {% autoescape false %} {{ 'データセットを選択' | badge }} {{ 'ネットワークを作成' | badge }} {{ '学習' | badge }} {{ '結果' | badge_active }} <br> <br> <p><span class="text-secondary">ID: {{uid}}</span></p> <h4>学習結果</h4> <br> {% set size = result[0]|length %} <p>訓練精度: {{ result[0][size-1] }} <br> 訓練loss: {{ result[1][size-1] }}</p> <p>検証精度: {{ result[2][size-1] }} <br> 検証loss: {{ result[3][size-1] }}</p> {% for line in svg %}{{line}}{% endfor %} <br> <br> <div style="max-height: 300px; overflow-y: scroll"> <table class="table table-striped table-sm"> <thead class="thead-dark"> <tr> <th scope="col" class="text-center">epochs</th> <th scope="col">訓練精度(acc)</th> <th scope="col">訓練Loss(loss)</th> <th scope="col">検証精度(val_acc)</th> <th scope="col">検証Loss(val_loss)</th> </tr> </thead> <tbody> {% for acc in result[0] %} <tr> <td class="text-center">{{loop.index}}</td> <td>{{result[0][loop.index-1]}}</td> <td>{{result[1][loop.index-1]}}</td> <td>{{result[2][loop.index-1]}}</td> <td>{{result[3][loop.index-1]}}</td> </tr> {% endfor %} </tbody> </table> </div> {% endautoescape %} <br> <a href="/" class="btn btn-primary">トップページへ戻る</a> {% endblock %} |
学習経過を収めたテーブルは、学習回数によっては超長くなるので、表示サイズを指定し、縦スクロールができるようにしてみました。
1 | <div style="max-height: 300px; overflow-y: scroll"> <!-- 27行目 --> |
動作確認
では、動作確認をしてみましょう!
(以下は、MNISTで 784 - 1000 - 10 のネットワークの学習結果)

うまく動作しています!
よくあるエラーは、 {% endautoescape %} を書き忘れていたり、配列のセグメンテーション違反くらいですかね。
それだけ注意していただければ、うまくいくはずです。
さいごに
これで、本連載で作成しようとしていたアプリが完成しました。
この連載を通して、Responderの非同期処理の使い方や応用の仕方を理解できたかと思います。
ただ、本アプリも、まだまだ発展の余地があります。
例えば、「畳み込み層の追加をできる」ようにしたり、「使えるデータセットを増やす」などです。
はたまた、「中間層のニューロン数をもっと自由に指定できる」ようにしたりと、発展の仕方は無限大です。
ひとつ、すぐにでも改良すべき点は、今回端折ってしまいましたが、POSTデータの例外処理です。
現在は、初期学習率に数字以外を入れたりマイナスの値をいれても、エラー処理を実装していないので、思わぬ結果になってしまします。
アプリとしては、例外処理は必須なので、もしこのアプリをベースに発展させてければ、エラー処理から実装してみると良いかと思います。
弊社のブログ記事では多くの「実装してみよう」系の記事がありますので、ぜひ他の記事も読んでみてください!
全4回に渡り本連載をご愛読いただき、ありがとうございました!
なお、ResponderとKerasについての開発依頼・お見積もりはこちらまでお願いします。
また、本連載で出来上がったコードはGithubにあげていますのでご自由にお使いください!
【Github】
https://github.com/rightcode/ResponderKeras
第1回はこちら
こちらの記事もオススメ!
Kerasのオススメ本
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ライトコードの日常12月 1, 2023ライトコードクエスト〜東京オフィス歴史編〜
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン