サマリ
- Webフレームワークのズブの素人が、ボタンクリックでGANのGeneratorが走るようなWebアプリを作ってみた。
- Flaskは確かに短いコードでプロトタイプが作れるが、明示されていない所で「何をやっているか」を結局理解しないとハマるところも多い
モチベーション
一言でいうと、「PythonのWebフレームワークが使えれば一気通貫で機械学習システムのプロトタイプが作れる!」というアレがやりたかった。あと最近「自走プログラマー」を読んでいたので、なんだかWebフレームワークが触ってみたかった。
で、表題の組み合わせに至ったモチベーション。
Flask --> Webフレームワーク系はズブの素人なので、なるべく短いコードで済ませたい(そしてオリンピックが消え去った2020年7月の4連休でやり切ってしまいたい)。
Generator --> CNNの分類機を組み込むのはよく見るパターンだし別のことがやりたい。最近GANが自分の中でアツいのだが、GANは生成物を人に見せびらかしたくなるという特徴(個人の意見です)がある。参考書で動かしたことの有るDCGANはランダム値から画像を生成するので、アレなら重くないし組み込めるはず。
- DCGANについての解説は(手前味噌だが)筆者作成の本資料の終盤を参照。今回動かしたものも同じ。
実装結果
実装に至っては0から作るアプローチだと手詰まりになるのが見えていたので、近いアプローチを取っているコードを見つけてコピーし、自分の作りたいものに近づけていくアプローチで進めた。というわけで今回大変お世話になった実装例をまず最初に記載する。
モチベーションや目的含めてかなり近いものだったので、参考にさせていただいた。その上で出来上がったものが、こんな感じ。
表示している生成画像は「ship」である。たぶんCIFAR-10を存じない方からすると「どこが船やねん」だと思うが、コレ自体が低解像度の画像をかなり引き伸ばしているのと、元になっているデータセットのCIFAR-10が元々こんな解像度という事は注意されたい。個人的には薄目で遠ざけてみると3回に1回は船っぽく見えるのでなんだかんだDCGANスゲーな、と思っている。
というわけで以下は実装の詰まりどころメモ。
実装手順と詰まりどころ
Generatorの推論実行
今回使ったNotebookは以下になる。元はFranchois本8章の実装だ。
こちらの訓練イテレーション内でHDF5ファイルをsave_model
して、まず推論だけでも画像が出力されることを確認しておく。
問題なければhdf5をダウンロードし、Flask内のPOSTメソッドで実行するようにした。この辺は正直手を抜いていて、Flaskで動かすような軽量さを求めるならtfliteに変換してみたかったのだが、意外と手がかかりそう&今回の主目的ではないのでスキップ。一応実装においてもfrom tensorflow.keras.models import load_model
に留めておいたのでさほどメモリは専有していないと思いたい。
Flaskアプリの実装
次はFlaskアプリの実装で、このへんは先に先般紹介したブログでの実装例を実際にローカルで動かしてFlaskの大体の動きを自分なりに把握してから、書き出すことにした。
flask-with-gan-and-gcp/main_local.py at master · shinebalance/flask-with-gan-and-gcp · GitHub
期待通りというかFlaskの良い所は、この規模のアプリであればPython処理の部分は単一ファイルで完結してしまうところだと思う。html+cssは専門外だが、前にProgateで超ざっくり触っておいたので最低限、自分の意図通りに外見を変える等は対応できた。
静的jpgがキャッシュされて更新されない
今回最も手間取ったポイントがここで、Generatorが出力した画像をブラウザに表示するには、①jpgで特定ディレクトリに保存して表示②バッファを取得してデコードして表示のどちらかになるのだが、今回は①を選択した。しかしブラウザでは保管したjpgを表示、Generatorが走るたびに同一ファイル名のjpgを更新する…というこの方式だと、静的jpgファイルをブラウザがキャッシュしてしまい、内部的にはボタンを押下する都度画像を更新しているのに、表示上は変わらないという問題に行き当たった。
参考サイトを幾つかあたり、結果ファイルを読む際に合わせてタイムスタンプの取得文を追加することで対応できた。
qiita.com aroundthedistance.hatenadiary.jp
上記サイトではstatic配下のcssファイルに対してのタイムスタンプ付与が目的となっているが、このへんは読み替えて実装した。(最初は画像をstaticディレクトリ配下に保存していたのだが、諸事情で保管先をtmp/配下に変更した)
- flaskの処理
# 保存とタイムスタンプの取得(静的ファイルのキャッシュ対策) filepath = 'generated.jpg' savePath = './tmp/'+filepath resultImg.save(savePath) timeStamps = int(os.stat(savePath).st_mtime) # レンダリング時はドット(.)が不要 filepath = f'/tmp/{filepath}?q={timeStamps}'
- htmlタグ
<img src="{{filepath}}">
以上でどうにか完成した。正直なところ調査の殆どにかかった時間はこの問題への対処だった。
振り返りと疑問
プロトタイプを作りたいときの自分用の雛形を一個作る、という意味では概ね目的を達成できた。とはいえ、やはりWeb開発の作法を知らなさすぎるので、結局の所そこに関する疑問が多く残った。
WebアプリではなくWeb APIで似たような実装をしたい場合は、どうなる?
Flask自体はtemplateをベースに動的にhtmlを生成しているっぽいが、もう少しAPIベースにして疎結合な作りにはできないのだろうか?
- 所謂静的サイトを使った構成になるのだろうか?そういう構成だとVue.jsとかの名前をよく見るが……
とまぁこんな感じだ。もう少しWeb開発を掘っても面白いような気はするが、優先度は高くないのでまたの機会にしたいと思う。また、合わせてローカルで作ったこのアプリをPaaSへ展開する試みも行ったので、別記事にて記載する。
以上