みどりのあくま

勉強したことをアウトプットしていきます。

今年の目標

今かよ!感ありますが、今転職を考えていてちょうど自分について振り返るのにいい機会なので書いてみます。

これまで

元々プログラミングはどちらかというと苦手でした。
大学は数学科出身で、プログラミングの授業の課題は友達に聞いていました。
そんな状態からゲーム業界に入り、サーバーをやっている感じです。
MySQL?何それ?おいしいの?状態だったのに関わらず、いきなりローカル環境構築してねといわれ、wikiのURLだけ共有されました。
しかも、その手順書通りやっても正しく動かなくてw
質問しようにも全てがわからない(ターミナルをどう起動するかもわかってない)状態だったので、そもそも何を質問すればいいのか状態でした。
元々新卒受け入れている会社ではなかったのでいきなり出来る前提で来るのも仕方ないですが、ちょっと辛かったです。
それで業務についていく為に家帰ってからも勉強していました。
python、flask、MySQL、Redisなど業務に使うことを徐々に学習して行きました。
その時なんとなく新しいものを覚える際にどうすればいいのかとのコツを掴んだ感じです。
なので、今は自分の興味があることに関して幅広くアンテナを張って色々試している感じです。

興味・これからやっていきたいこと

これまでは仕事で使う物やただ単に興味があることを勉強していました。
それを踏まえて来年について考えていこうと思います。

GCPkubernetesを使った開発

ここ最近のGCPは勢いがすごくGoogleにしか出来ないクラウドサービスをどんどん出して来ています。
例えばライブマイグレーションやSpannerなどがあげられます。
いかにすごいかは私の拙い説明より、他の方の記事を読んだ方がわかりやすいと思います。

qiita.com

qiita.com

またGCPk8sを用いた開発に適しているのも魅力的なポイントです。
今後のサーバーアプリケーション開発ではコンテナを使う機会がどんどん増えていくと思われます。
なので、k8sを使ったコンテナ開発・運用については知っておく必要はあります。
AWSもECSやEKSなどコンテナサービスを出して来ていますが、今のところGCPが一歩進んでいるイメージです。

CPU、TCP/IPlinuxなど低いレイヤーの知識

何もわかっていないまま業界に飛び込んだので、とにかく目の前の業務をこなせるレベルに自分を無理やり持っていく必要がありました。
なので、割と低いレイヤーの領域に関しての知識はほぼないです。
新しい言語やフレームワークを使えるレベルまでスキルを伸ばすことは容易ですが、深いレベルなるとまだまだ勉強が足りないのが現状です。
より深い理解には低いレイヤーの知識は必須になって来るので、今後は時間を割いて勉強して行きたいと考えてます。

Golangの学習

低いレイヤーの学習において、実際に動くものを作る時にGoがいいのではと考えています。
もちろんCでやれるのであればやった方がいいのですが、自分の今までの言語歴的にLL言語に近いけど低いレイヤーに関する開発も行いやすいGoがいいのではと考えています。
また、最近サーバーの言語としても注目されていて業界的に需要が多いです。
いい機会なので、チャレンジしてもいいのではと考えています。

QIitaやブログでのアウトプット

これまでは「仕事=アウトプット」として成り立っていましたが、低いレイヤーのことに関して学習していくことを考えると、アウトプットするのに会社以外の場所が必要になります。
なので、今後はブログやQiitaでアウトプットを行なっていこうと考えています。
あと、文章を書くのクソ下手くそんなのでそれの勉強にもあればと考えています。

まとめ

まとめると

が今年のキーワードになって来ると思います。
特に低いレイヤーの学習は重点を置いてやっていきたいと思います。
これ以外にもElixirやUnityについても興味があるのですが…欲張っては全部失敗するので、これらはまた来年の課題にしたいと思います。

一年前に作ったWebアプリをアップデートする案

以前まではブログに書く内容はもう少しまとまってからにしようと思っていたのですが、こちらのnoteの記事を読んでもう少し頻繁に書いていこうかと思います。

note.mu

今日は以前自分が作ったアプリをアップデートしたいと思ったので、そのプランについて書いていこうと思います。

作ったアプリについて

まず今のアプリについて話します。
ちょうど去年の今くらいに「けものフレンズ解析機」というアプリをリリースしました。

orange634.hatenablog.com

ボクニマカセテ! フレンズ解析機

今まで自分でサーバーを立てたことないので、その練習用に作成しました。
また、興味があった機械学習と業務でpythonを使っていることもあり、機械学習にチャレンジもしてみました。

実装方法

webアプリはconohaでVPCを借りて、nginx + gunicorn + flaskで実装しています。
機械学習部分はtensorflow + dlibを使っています。
webフロントはjQueryで実装しています。

アップデート案

目的

このアプリをアップデートをしようと考えています。
理由としては以下の4つが上げられます。

  • アプリの特性上一発ネタなので、今はアクセスはゼロに近い。でもサーバー費はわずかながらかかっている。勿体無い。
  • 表示をjQuerydisplay: blockdisplay: none を切り替えることで実現していますが、正直微妙
  • 最近はwebフロントに触るようになり、jQuery以外の選択肢がある
  • タスクキューを使うなど、より実践的な実装を試してみたい

特にサーバー費を浮かしたいのが今回の目的にメインです。
たかが500円、されど500円。

方法

それぞれに関して案を考えてみました。

サーバー費節約

案1:dcockerを使い他のアプリと同一サーバーに複数アプリを同居させる

サーバー一つをこのアプリに使っているから勿体無く感じるので、複数のアプリがデプロイ出来ればいいのではと考えました。
自分専用botのサーバーとかちょうど欲しかったので合い乗せできるならそこまでそんな感じはしないかなと思いました。 合い乗せにする際も、dockerを使ってアプリ同士の依存なく稼働させるように作りたいと考えています。

案2:herokuを使う

どうやらherokuでopencvやtensorflowを動かすことが可能みたいです。

tomoto335.hatenablog.com

qiita.com

正直ネタアプリなので、常に稼働している必要はなく、動くならherokuを利用してもいいかもしれません。
何よりタダと言うのがいいですね。

これに関しては案2を先に進めて、難しい場合は案1に戻る方法を取ろうと思います。

フロントの書き換え

jQueryでなんちゃってSPAを実装していましたがこれも書き換えたいと考えています。
自分が使える、VueかHyperappにしようと考えています。
こちらもとりあえずHyperappで作成して、無理そうならVueに置き換えるようにしたいと思います。

タスクキューの導入

あんまり必要ないので後回しになりますがこれもやってみようかと思います。
前に使ったことのあって、pythonで動くceleryを考えています。
ただherokuを選んだ時、celeryが動くようになるのか…
ちょっとわからないので、おまけで出来ればやる程度の認識です。

判定方法やフレンズ数を増やす

dlibで顔の位置を判定して、tensorflowでどのフレンズか判別しています。
dlibで学習したデータはどれも正面を向いているものばかりなので、ちょっとでも傾いていると判定が出来ません。
今後は画像を徐々に回転して判定出来るようにしたいと思っています。
そうすることで、より判定の制度をあげるとこができます。

また、今は4人のフレンズしか判定出来ないので、学習し直して判定できるフレンズを増やしたいです。

まとめ

つらつら書きましたが、まとめると以下のようになります。

  • サーバーをherokuに移す
    • 上手くいかなかったら、元サーバー上にdockerで稼働するようにする
  • WebフロントをHyperappで書き換える
    • 上手くいかなかったら、Vueでもやってみる
  • 検証しながらceleryなどのタスクキューも入れていきたい
  • 判定方法やフレンズ数を増やす

移行作業の過程も積極的に記事に書いていこうと思います。

勉強会参加:はてなブログの実例で学ぶ「はてな流」大規模Webサービス開発の勘所

社内Wikiで共有したところ好評だったので、ブログの方にも投稿しておきます。

こちらの勉強会に参加してきました。
https://supporterzcolab.com/event/246/

色々話されたのですが、自分が「お!」っと思ったところだけ2点抜粋して書いていきます。

新規機能実装はプルリクは細かく、リリースも細かく

一機能を同一のプルリクで作業すると。。。

  • マージする際のコンフリクト地獄
  • レビューの労力がかかってしまい安定性が失われてしまう
  • 大量のレビューのフィードバック対応

など問題があったので、細かくプルリクを書いて、細かくリリースする方法を行なっているようです。

よくある方法として、リリースブランチを切ってそこにマージしていく方法があるが、それもコンフリクト地獄は発生するし、他の部分との兼ね合いで大量に新機能の一部をcherry pickすることになってします。
この方法でも依然問題が残ります。

なのでユーザーに見えないような工夫をして、実装を行い細かくリリースしていっています。
例えば、ロジック部分のみの実装や動線を消した状態の実装を行なっています。

また、プルリクも細かく行なっています。
これもコンフリクトをなるべく無くしたり、レビューの労力を軽減する為です。
意識するのはだいたい1日の作業分が1プルリクになるようにしているそうです。
例えば、モデルだけとか、コントローラだけとかそういった分割しやすい単位で分けているみたいです。
*ただし場合よってはモデルとコントローラーを一緒にやった方がいい場合もあるので、臨機応変に粒度は変えている。

その他具体的な方法は以下の記事により詳細が書いてあるので、そちらも確認していただければと思います。
http://developer.hatenastaff.com/entry/2017/08/07/170000

Mackerelのちょっと変わった利用方法

https://mackerel.io/ja/
Mackerelは、はてなが開発したサーバー監視ツールです。
一般的にはもちろん、サーバー監視に使っています。
ただ、それ以外の方法で使っており、いくつか例を教えてもらいました。

デプロイにかかる時間の計測

Makarelの機能を使って計測しているようです。
以前はてなではデプロイに時間がかかっていたので、そういったことを行なっているみたいです。
話は変わりますが、デプロイを短縮させたがたい的な方法は以下の記事により詳細に書いてあるので、そちらも確認していただければと思います。
http://this.aereal.org/entry/2016/12/16/170000

プルリク数の可視化

Mackerelを使ってプルリク数を可視化しています。
修正行数とかも計測しているようです。
そうすることで、実装の忙しさを見れるようにして、新規実装のタイミング等を図っています。
こういったことはmakarelのcliツールで簡単に行えます。

nodeのアップデートが必要なモジュールの数をトラッキング

以前はデプロイの度にモジュールのアップデートを行なっていたとのことです。 Mackerelでアップデートが必要なモジュール数を計測して可視化し、溜まって来たタイミングでアップデートをかけるそうです。

まとめ

プルリクを細かく分けて出す方法は今まで経験がないので一度試してみたいなと思いました。
Mackerelを使った方法ですが、特に「プルリク数の可視化」は非常にいいと思いました。
チケットベースで行なっていれば必要ないように思いますが、実際にのアウトプットもキチンと可視化することも重要に思いました。

またこのようなチーム運用を可視化する考えを発展させて、バグ・不具合の可視化も行なってみるのも面白いと思いました。
どういった場合のバグが多いのか、そして多いケースがわかれば、どのようにすればバグを減らせるのかの対策がより狙いやすくなる気がしています。

courseraの機械学習コースの受講終了しての感想

f:id:orange634:20171120005247p:plain

courseraの機械学習コースを受講し、終了しました!

www.coursera.org

courseraの機械学習コースとは、機械学習を勉強するならとりあえずこれをやれ!的な感じくらい有名なオンライン講座です。(私調べ)
基本無料ですが、認定書を発行してもらうには学費を払う必要があります。
私はお金に余裕はないので無料で受けさせていただきました。

受講したきっかけ

このコースを受講したのは以前「けものフレンズ判定機」なるものを作成したのがきっかけです。

orange634.hatenablog.com

一応動くものはできたものの、なんだかよくわからないけど出来たみたいな感じで全く機械学習については理解できませんでした。
自分の性格的に基礎からしっかりやらないとダメだなと感じたので、色々調べた結果一番求めているものに近かったので本コースを受講しました。

さて、実際に体験レポート等は他の人がよりすばらしい記事があるのでそちらに任せます。
今回は僕が特に感じたことを3点だけ書きます。

ニューラルネットワークがやっと理解できた

一番この講義でよかったと思う部分です。
昨今のニューラルネットワークブームに乗ろうかと色々な入門書を本屋で見ていたが、どれも初めの方にニューラルネットワークを説明する際に人間の脳細胞との類似性について記載されています。

それが個人的に府に落ちずそこから理解が進まないままでした。
ニューラルネットワークの式がただの記号の集まりにしか感じらず、理解が進まないままでした。

余談ですが、僕は数学は基礎からより、歴史に沿って勉強した方が理解しやすいと感じています。
いきなり定理を持ってこられると、こんな天才的な発想理解できねーよと感じてしまいます。
定理や法則の式を読んでもただの意味のない記号にしか見えません。
でも歴史に沿うとその定理なり法則なりが成立するまでの悩みや試行錯誤の過程をて意見することで、その定理の記号一つ一つに意味があることが理解でいます。
自分の中ではこれを「定理の心を理解する」と呼んでいますw
さらに、こういった理解の方が定理をきちんと理解しているので、応用が効きやすいです。

とにかく自分の中では「脳細胞のモデル」としてニューラルネットワークを理解するということがあまりに突飛すぎて全然理解できないものでした。

本講義ではニューラルネットワークより前の回帰分析から始まり、ニューラルネットワークまで解説してくれます。
これでやっと自分の中でニューラルネットワークの「心」を理解できました。

ニューラルネットワーク以外の機械学習についても学べた

講義をきちんと受ける前には僕の中では今時の機械学習=ニューラルネットワークに近い認識でした。(他の考え方はすでに古く廃れてしまっている、と思っていました)
講義を受けて、ニューラルネットワーク銀の弾丸ではなく、適材適所がありどういった場合に有効かを学べました。
例えば、不良品のジェットエンジンの検出のように、圧倒的に不良品のサンプルや複数の原因がある場合はニューラルネットワークよりアノマリー検出の方がより適しています。

さらに講義の後半は実践的な機械学習のシステムを構築する上での注意や引っ掛けについても講義がありました。
こういったことも実践前に知れてよかったです。

講義についてくのは大変

結構、他の人のレポート等を読んでいるとそこまで難しくないような感じを受けましたが、全然難しかったです。。。
そもそもビデオだけでも1,2時間、長い時は2,3時間かかります。ビデオを見るだけで結構苦労します。
しかも、英語。しかも、octaveという使いなれない言語(特に行列周りは難しいです。。。)
大学でいうと4単位分の授業料があるのではなかという量です。
しかも毎週課題のデッドラインがあるので本当についていくのに必死でした。
仕事が忙しい週は夜遅くまで勉強してなんとか課題をこなしました。
また、ビデオという形式も気楽にパソコンを開いてできないので、疲れている日はなかなか手をつけづらく、課題の期限直前に慌ててこなす感じでした。 ブログの更新が止まっていたのも課題に必死だった関係です。
特にネタがないというのもあるのですがw

この課題の提出期限があったから頑張れたところもあるので、なんともいえませんが。
ただ、しばらくはオンラインの授業は遠慮させていただきますw

まとめ

ちょっとスパルタでしたけど、本当に受けてよかったと思いました。
以前作ったけものフレンズ判定機も今回の勉強の成果を生かして改良していきたいと思います。

そして久しぶりに勉強がもっとしたくなりました。特に座学ですね。
実践で勉強するのはいいのですが、より基礎になる座学はやっぱりいいなと思いました。
また、時間ができたらcourseraは他にも授業があるので受けてみたいと思います!

(追記:私、顔写真付きの身分証持っておらず正式には登録できていないので、卒業できていないみたいです。。。まあ、今回は勉強が目的なので問題ないですね。)

顔画像周りのデータを扱うアプリをelectron-vueで作ってみた

この前けものフレンズ解析機を作った際にdlibやtensorflowを利用したのですが、その時顔の位置に関した情報を扱うことが多かったです。
dlibだと顔の位置判別の学習に、tensorflowは学習データを作成する際に顔画像を切り出すのに使いました。

orange634.hatenablog.com

なので、後々のデータセットを準備する作業のことを考えて、そういった顔の位置の情報を上手く扱えるようなアプリがあると便利だろうなーと思い今回作成しました。

概要

f:id:orange634:20170908131738p:plain

起動した画面はこんな感じです。
「Load」ボタンから画像が入ってるフォルダを選択して、フォルダ内部画像データを取得します。
再帰的にファイルを取得しないのでフォルダ直下の画像のみ読み込まれます。

f:id:orange634:20170908131850p:plain

実際にロードしたらこんな感じになります。

f:id:orange634:20170908131943g:plain

各画像の上に四角の描画、ドラッグ、リサイズ、削除が行うことができ、複数の四角を描画することも可能です。

描画が完了したら「SAVE」ボタンを押して保存します。
保存しないでページを変えると描画したデータが消えてしまいます。

f:id:orange634:20170908132245p:plain

「Publish」ボタンを押すと、上の画像のように画面に書かれた正方形の位置(x, y)と大きさ(rectLength)のデータをdata.jsonとして選択したフォルダ内部に出力します。
また、ロードした際にディレクトリにdata.jsonがあった場合はそのデータを元に画像に正方形を描画します。 なので、何回でも調整可能ということです。
また、dlibで検出した顔の位置の情報をdata.jsonと同じ形式にすれば、そのデータも利用可能です。 (ただし画像に合わせて倍率を修正する必要があります)

f:id:orange634:20170908132228p:plain

mustacheというテンプレートエンジンを利用してtemplate.xmlからdata.xmlを出力できるようにしました。
出力される位置とうの情報は実際に画像に合わせて倍率をかけて四捨五入しています。

ただ、今回あくまで機械学習がメインであまり時間をかけるのは適切でないと判断して以下の部分で妥協しています。

  • 画像の横幅は1000px固定(実際の大きさに戻す時に倍率の計算が楽)
  • 縦長の画像は途切れて実質使えない(アニメの画像のみ使う想定なのでこれで横長画像しか使わない想定なので)
  • リサイズするのに右下一箇所でしか出来ない
  • vuexが上手く扱えず厳格モードではない

技術

今回アプリ作成に置いて、electron-vueを利用しました。
仕事で一度使ったことがあり、なんとなくですが知見があるからです。

vueではなくreactを使おうかと考えましたが、あくまで機械学習がメインでアプリ制作は手っ取り早く(それでも1ヶ月かかりましたが…)作ってしまいたかったので、経験のあるvueを採用しました。

四角の描画

顔を囲む四角はsvgを利用して記述しています。
svgを扱うのにライブラリは一切利用していません。
本当はsvg.jsを使いたかったのですが、vueと相性が悪いようで正しく動かなかったの(どうやらDOMを直接いじるのがvueだと上手く動かない)で諦めました。
正直正方形を記述するだけなので簡単だと思っていましたが、そんなことはなく苦労しました。

fabricを利用してのcanvasでの描画も考えたが、リサイズした時に大きさによっては四角にジャギが入るので、謎のこだわりでリサイズしても綺麗なsvgにしました。
正直canvasでよかったように思います…

またsvgはvueでhtmlのタグを扱うように扱えるので、vueの単一コンポーネントも使えます。

四角の描画部分はmouseup、mousedown、mousemove等と今は何を選択しているかで描画、ドラッグ、リサイズ等を行なっています。
正直コードは実験しながら行なっていたので、あまりよくありません。

ファイルの取得と画像の描画

nodeのfs(FileSystem)を利用しています。
画像はsvgのimageタグで表示させました。
file://<url>を使えるようんするためにelectronのwebPreferenceswebSecurityfalseにしています。
canvasで読み込んで表示させる方法も考えましたが、個人利用の範囲ならこの設定でも問題ないかなと思い、楽しています。

画像の切り出し方法

こちらはpythonのコードを利用する想定です。
nodeで画像を切り取ることは可能ですが、pythonのコードがすでにあるのでそちらを使ったほうが早いと思い切り出す機能はつけていません。
時間に余裕があればつけたいと思っています。
なので「画像を切り出しソフト」はあまり正しくないのですが、いいネーミングがないので暫定でこの名前にしています。

問題

正しくビルドされない

現在なぜかビルドが正しく出来ないでいます。原因は不明です。 ビルドは正しく終わるのですが起動してみると真っ白のままです。
mainWindow.openDevTools()を追記して、ビルドされたもののデバッグツールを確認すると、axiosというモジュールが見つかんないとエラーがでます。
これは素のelectron-vue(つまりvue init simulatedgreg/electron-vue applicationのテンプレートのままのこと)でも上手く行かないようです。
webpack等のフロントのビルドに関してはテンプレートを信じて使っており、あまり詳しくないので一旦諦めました。
原因がわかれば追記します。

Vuexの厳格モードでのデータの扱い

Vuexを利用してgetterで受け取ったarrayを変更すると(今回の場合は正方形の情報)Vuexの厳格モードだとエラーが発生します。
jsは普段あまりつかわないのでうまい解決策がわからず、今回は厳格モードオフで作成しました。
後ほどVuexのベストプラクティスを調べてここら辺に理解を深めたいと思っています。

感想

だらだらっと箇条書きで感想を書いていきます。

svgは初めて利用しましたがvue扱うのに全く問題はありませんでした。
ずっと昔から簡易イラレ版みたいなの欲しいけど、インクスケープ操作感が悪く使いづらかたので、時間がある時にsvgを利用して作れないかな考えています。(時間ないけど)

electron-veuは昔使った時よりさらにドキュメントやツール周りがさらに強化されているなーという印象でした。
vue周りの発展の速度がすごい早いので驚きです。

Vuexを利用する想定がある場合は早めに利用したほうがいいです。
中盤くらいまでVuexを使わないで作っており、いざVuexを使うことを考えると書き直しが発生して、余計に作業が増えてしまいました…

mustacheはシンプルが故に簡単な演算は無理みたいなので、作業的に必要になったら他のテンプレートエンジンの利用も考えてみます。 でも今はこれで問題ないです。

これできっと機械学習のデータセットを作るのが多少楽になるはず…!

追記

python製のラベリングをサポートするツールがありました… これ使った方がいいですね… github.com

けものフレンズ解析機作ってみた。 - 後編 -

前回、機械学習でフレンズを判定できるようになったので、次はこれをwebアプリとして使えるように実装していきたいと思います。 前回の記事はこちら

orange634.hatenablog.com

前回の機械学習部分において参考にさせていただいたこちらの記事の例だと、アップロードした画像を1回保存して、顔を切り出したり、線で囲った画像を別ディレクトリで保存して結果画面に表示するというものです。
この方法だと公開することを考えると、容量的な問題(金銭的に容量は最小にせざる負えない)で同じ方法は難しいと判断しました。
そこで、サーバ側ではあくまで判定だけ行いその結果をブラウザのcanvasで表示する方法をとりました。
以下のような流れをイメージして実装しようと思いました。

  • 画像アップロード
  • canvasで読み込んで表示
  • サーバへ送信
  • 判定、結果を返す
  • 結果を受けて顔の枠線と判定結果の描画

フロント

画面の切り替え

canvasでロードした画像を保持したまま通信や結果の表示させたいので、ブラウザの読みこみなしで表示の切り替えが行えるようにしました。
仕組みは簡単で各ページの要素にpage-xxxxのように命名したclassをつけます。
そのclassの全要素を表示・非表示にして操作します。
切り替えるのは以下の関数で行います。

  /* 定数 */
  const PAGES = {
    // ページクラス: ボスのメッセージ
    'page-top': 'ガゾウ ヲ エランデ ネ。',
    // ...(略)

  /* グローバル変数 */
  var now = 'page-top';

  // 次のページに遷移
  function movePage(targetPage) {
    // 現在のページの要素を非表示
    changeAllDisplayState(now, 'none');
    // 現在のページを次のページに変更
    now = targetPage;
    // 次のページの要素を表示
    changeAllDisplayState(now, 'block');
    // メッセージをセットする
    setMessage(PAGES[now]);
  }

  // 同一クラスの表示変更
  function changeAllDisplayState(className, displayState) {
    $('.' + className).css('display', displayState);
  }

  // ボスのメッセージを書き換え
  function setMessage(msg) {
    $('#boss-msg').html(msg);
  }

表示を切り替える時はmovePage('page-xxx')で行います。
また、ページを読み込んだ時一瞬要素が見えてしまうので、表示する必要ないpag-xxxがついた要素はdisplay: noneにして表示できないようにしておきます。

ファイルアップロード

ファイル選択のボタンがブラウザのデフォルトだと、統一感がないので他のボタンと同じcssを適用させたいと思います。
labelで囲って実装する方法をとりました。
下の例はわかりやすいように説明に不要なclassは消してあります。

<label for="sendFile" class="button">
  ファイル センタク
   <input type="file" name="file" accept="image/*" id="sendFile">
</label>

ファイルがアップロードされたら、そのファイルの画像をcanvasで読み込むようにします。

  // ファイルアップロード時
  $('#sendFile').on('change', function (evt) {
    handleFileSelect(evt);
  });

  // アップロードした画像から情報取得
  function handleFileSelect(evt) {
    var files = evt.target.files;
    if (!files.length) {
      failDetecting();
      return;
    }
    loadImageFile(files[0]);
    movePage('page-ready');
  }

  // アップロードした画像をcanvasで描画
  function loadImageFile(uploadFile) {
    var canvas = $('#cnvs');
    var ctx = canvas[0].getContext('2d');
    var image = new Image();
    var fr = new FileReader();
    // ファイルをロードしたらコールバック
    fr.onload = function(evt) {
      // 画像をロードしたらコールバック
      image.onload = function() {
        var cnvsW = image.width;
        var cnvsH = cnvsW*image.naturalHeight/image.naturalWidth;
        canvas.attr('width', cnvsW);
        canvas.attr('height', cnvsH);
        ctx.drawImage(image, 0, 0, cnvsW, cnvsH);
        // 画像の表示
        isUpload = true;
        displayCanvasImg(isUpload);
      }
      image.src = evt.target.result;
    }
    fr.readAsDataURL(uploadFile);
  }

file要素の変更を検知してcanvasに画像読み込みとページ遷移を行います。
また、canvasの幅を適切に設定して、画面外に画像が出ないよう、アスペクト比を保つようにしています。

ajaxでファイル送信・結果の描画

jQueryを使って行います。
解析に時間がかかることを考えてtimeoutを60000にしています。

  // 画像投稿
  $('#imgForm').on('submit', function(e) {
    e.preventDefault();
    var formData = new FormData($(this).get(0));
    $.ajax($(this).attr('action'), {
      type: 'post',
      timeout: 60000,
      processData: false,
      contentType: false,
      data: formData,
      success: movePage('page-detecting')
    }).done(function(response){
      drawResult(response['result']); // 結果の描画
      movePage('page-result')
    }).fail(function() {
      failDetecting();
    });
    return false;
  });

canvasで結果の描画

判定結果はcanvasの機能で描画しています。
特に難しいことはしていません。

リセット

初期状態に戻すために、以下のようにしています。

  • 選択したファイルの値を空にする
  • canvasの画像を全部書き換える
  • 変数のリセット
  • 表示ページを変更
  function resetAll() {
    // ファイルをリセット
    $('#sendFile').val('');
    // canvasの中の画像を削除
    var canvas = $('#cnvs');
    var ctx = canvas[0].getContext('2d');
    ctx.clearRect(0, 0, canvas.width(), canvas.height());
    // アップロード状態をリセット
    isUpload = false;
    displayCanvasImg(isUpload);
    // topへ遷移
    movePage('page-top');
  }

デザイン

cssライブラリとしてNormalize.cssMilligramを使用しています。
フロントは詳しくないので、chromeで動くことしか考えないでcssを書いていきました。
ieとかedgeとかは気にしないフレンズ。

デザインはできるだけ「けものフレンズ」ぽくしたかたので、本家のサイト等を参考にそれっぽく作ってみました。
華やかさも欲しかったのでこちらのロゴジェネレータを使ったり、ボスの画像やfaviconドット絵で作ってみました。
本当はここまでこだわる予定ではなかったのですが、時間をかけすぎてしましました。。。

バック

あんまり特殊なことはしていないので、箇条書きでざっくり書いていきます。 書いていないところは基本的に普通のflaskアプリと同じようなことをしてます。

  • アップロードファイルの読み込みはこちらの記事にあるようにopencvで扱えるようにしました。
  • 切り出した顔画像に関して、画像データ、元画像から抜き出した位置情報、解析結果を一緒に扱えるにclassにまとめました。
  • evalとmainで別れていたファイルを一つに結合して、扱いやすくしました。
  • detector.svmmodel.ckptlibというディレクトリに置いてstaticと分けています。 staticにおくと公開してしまい、誰でもダウンロード出来てしまうので、あまり良くないかなと思い分けています。

こちらのwebアプリ部分に関してはgithubに参考となるコードを上げておきます。
よかったらそちらも参照してください。

github.com

サーバにデプロイ

これも特別なことはしていません。
サーバは僕が書いた以下2記事と同じ方法を用いています。

qiita.com

qiita.com

またあらかじめvagrantで開発環境の構築を何回か練習しました。
サーバを立てること自体が、初だったので練習できてよかったと思います。

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  config.vm.box = "centos7.1" # 公式からcentos7.1最小構成のboxを取ってきています

  config.vm.network "private_network", ip: "192.168.33.10"

  config.vm.synced_folder "./www", "/var/www/app", create: true
end

ただ今は更新したら、いちいちssh接続してgit pullしているのでjenkins等で簡略化できないか考えています。

全体的な感想

長い道のりでしたが、フレンズ解析機を作成できました。
仕事でwebアプリを作成したことがあったが、このように一から自分で作ったことがなかったので、色々考えが不足しているところや、他の人が担当してどうなっているのかいまいちわからないことがいくつも出てきて、制作を通して知見が広がったように思います。

一度完成させるのは難しいかも(サーバのセットップあたり)と諦めかけました。
しかし、職場の同僚や上司、友人などにすでに話していたのでやめるわけに行かないと、モチベーションを上げてなんとか完成にこぎつきました。

これからはこのアプリを作成して見えてきた問題点を改良していって、ちょっとずつ成長させていこうと思います。
直近の目標としては、解析できるキャラを増やしたり、顔認識の精度をあげたり、アニメ以外のイラストもキャラの推測を行えるようにしたいと思います。

けものフレンズ解析機作ってみた。- 前編 -

はじめに

会社で学べること以外に自分で勉強して行かないといけないと思い、今流行りの機械学習でwebアプリを作ることに挑戦してみました。
何番煎じかわかりませんが、自分が好きな「けものフレンズ」の顔認識+判定機を作成してみることにしました。

記事公開しようと思っている矢先にネタが被った上になんかもっといい感じのやつが出ていました。。。
yokoeworld.hatenablog.com フ、フレンズによって得意なことが違うから!

ちなみに、僕は機械学習に関してはほとんどわからない状態です。
色々な記事を追ったり、たまにコードをコピペして動くのをみていた程度です。
開発にはmacを使用しました。

目的

今回は以下2つを目的に作成しました。

  • とりあえず機械学習を用いてなんらかのwebアプリを作成する
  • サーバを立てて、公開しても問題ないようにする

作業をしていく上でつまるところが出てくる(特に機械学習部分)のでそこは、動けばとして次に進むことにします。
というのも、今まで期間をかけすぎてダレてしまって最終的に完成しなかったことが何回もあり、繰り返さないためにも自分でこういったルールを設けます。

また、今まで自分一人でサーバを立てて公開したことがないので、今後のことを考えると公開できるよにセットアップできるようにすることも学習の目的です。

製作物

f:id:orange634:20170812132020p:plain

こちらに公開しています。よかったら試してみてください。

ボクニマカセテ! フレンズ解析機

現在は「サーバル」「カバン」「アライさん」「フェネック」の4種類しか判定できません。
その他はフレンズと判定されるはずです。

最終的に製作に約2ヶ月かかりました。
作成期間中、空いている時間のほとんどを作業に当てていました。
なんとか満足がいくものができてよかったです。

構成

大まかの構成は以下のようになっています。
細かい内容に関しては後で解説していきます。

  • 判定・学習:dlib、tensorflow、opencv
  • webアプリ:flask、jQuery
  • サーバ:nginx、gunicorn

機械学習

初心者にも優しそうなでかつ今回のアプリを作るのに役に立ちそうな、以下の2記事を読み込んで実行してみました。

qiita.com

bohemia.hatenablog.com

今回機械学習において、ほとんどコードを参考にしたサイトから拝借してます。
一応参考にしたコードの多くはpython2だったのでそれをpython3に直して使っています。

フレンズの顔画像収集

まず機械学習に必要な顔画像を収集していきます。
事例を調べた限りだと、大量のスクショを集める→顔を判定して画像を切り出す→画像をラベルごとに整理する、という流れで作業を行うようです。
もし顔認識がうまくできないと、手動で全部切り出していくみたいです。
なので、まず顔認識を行えるか確認していきます。

顔認識について

アニメ顔に近いとlbpcascade_animefaceというもの先人の方々の知恵が使えるのですが、残念ながらフレンズの顔は一般的なアニメ顔とは異なるようで、判定はできるものの精度はそこまで高くありませんでした。
そのため自分で分類機を作成する必要が出てきて、こちらの記事にあるようにdlibにて判定機を作成することにしました。
作成用のコードも同じ記事から拝借して使用しています。

インストール

以下の4つのライブラリをインストールする必要があります。

調べてところopencvを入れるのにanacondaを使用している例が多かったので、pyenvでanaconda3-4.0.0を入れました。
ただ、この際にanacondaのroot環境に直で入れてしまいました。一般的に複数バージョンを扱えるように環境を分けることが多いので、本当は分けたほうがいいです。
当時はそこまで気が回っていなかったのでそのままインストールしました。
opencvはそのまま入れただけでは動かないかも(メモを紛失してしまい定かではありませんが)しれないのでエラーでググって解決しましょう。
他のライブラリも全部anacondaからインストールしました。

brew等でもboostやpython-boostは入れられるのですが対象としているpythonのバージョンが異なったり、実行してもエラーが多発して、悩んだ挙句最終的に全部アンインストールしてanacondaでいいれたらすんなり入ったかからです。。。
詳しくない方はanacondaに任せるのがいいでしょう。

学習と確認

約150枚の画像から顔の位置をだいたい正方形になるようにして取ってきて、ファイルに保存、xmlファイル生成しました。
120枚を学習、30枚をチェックに使用して学習させました。
軽くチェックを行なった結果、かなりの精度で取れるのを確認しました。

また、学習に使ったのはサーバル、カバン、アライさん、フェネックの画像であったが、精度は高くないが他のフレンズの顔も検出できていました。
ただ、なぜかプリンセスのヘッドフォンの赤い丸を検出していました。。。
原因は不明ですが、目視でデータ整理する時に省くので問題はありませんでした。

顔画像の収集と水増し

次にdlibにて作成した判定装置を使って、大量のスクショから顔の画像を取り出しました。
スクショの画像は1から12話でなるべくシーンが被らないようにスクショしていきました。
この際、フレンズの顔以外のものを検出していたり、画像ファイルが破損している物があったので削除して整理しました。
集まった画像の枚数は以下の通りになります。

フレンズ 画像枚数
サーバル 208
カバン 167
アライさん 40
フェネック 42

どうしても出番が少ないアライさんとフェネックの画像数が減ってしまいました。
そこで、画像の水増しを行い6倍にします。

pillowを用いて一つの画像から

  • 元の画像より明るいもの
  • 元の画像より暗いもの
  • 元の画像を反転したもの
  • 元の画像を反転して明るくしたもの
  • 元の画像を反転して暗くしたもの

を生成した水増しすることにしました。
これでそれぞれアライさんが236枚、フェネックが248枚になり十分な量の画像が用意できました。
何枚か画像が破損しているのもがあったので枚数は6倍ぴったりではないです。

カバンの画像が他に比べ少ないですが、多分大丈夫だろうと信じて水増しはしていません。
たつきを信じろ。

tensorflowでの学習

顔の画像が集まったのでtensorflowを用いた学習に入ります。
基本的にこちらの記事の手法とコードを拝借して行いました。
インストールしたtensorflowのバージョンが参考にしたサイトのものと異なるようで所々警告やエラーが出ました。
こちらの解説にあるように対応表を確認しながら修正しました。

ちなみに、学習の何回か行なったのですがうまくいかない時もありました。
ここの原因は不明です。

学習が終わるといくつかの中間データや最終生成物が出てきます。
以下の3つのファイルを判定に用います。

model.ckpt.data-00000-of-00001
model.ckpt.index
model.ckpt.meta

バージョンの違いから参考にしたサイトの生成物が異なり、使用する際も以下のようにコードを修正するが必要があります。

# 前
saver.restore(sess, "/tmp/model.ckpt")
# 後
saver = tf.train.import_meta_graph('/tmp/model.ckpt.meta')
saver.restore(sess, "/tmp/model.ckpt")

参考 : https://stackoverflow.com/questions/41265035/tensorflow-why-there-are-3-files-after-saving-the-model
また、こちらのevalを実行する際に再帰的に判定する場合にはリセットが必要らしいので下記のリンクを参考に修正しました。
ただ、本質的に理解をしていないのでもしかしたら正しくないかもしれません。
参考 : https://stackoverflow.com/questions/41400391/tensorflow-saving-and-resoring-session-multiple-variables
元のeval.pyをdlibで判定できるように修正しました。

f:id:orange634:20170812142353p:plain

画像が排出され、ターミナルで判定結果が出ていることが確認でき正常に動いていることがわかりました。

これで機械学習部分は終わりです。

一応動くものはできましたが、コードをかけるようになるにはまだ勉強が必要だと思いました。
実際、学習させる顔画像の解像度をあげる方すらわかりません。。。
今回は動くものを作るのが目的なので、次にステップに進むことにしました。

あと、内容と関係ないのですが、長文を書くのは難しいなと痛感しました。
下手な小学生の日記みたいな「~でした。」の繰り返しで、すごく単調な文章になってしまいました。。。
ここら辺も勉強が必要なようです。

次はwebアプリの実装を行なっていこうと思います。

orange634.hatenablog.com