- 投稿日:2020-01-26T18:03:13+09:00
画像を加工・変形して機械学習のためのデータ拡張をしてみた
概要
ディープラーニングでは、効果的な学習用を行なうために大量のデータを準備する必要があります。しかし、大量のデータを準備することが難しい場合、データ拡張(Data Augmentation)というテクニックによって、少量の手元のデータを水増し(割り増し、増殖)して学習に利用することがあります。
データが「画像」の場合、次のように 平行移動、回転、拡大縮小、上下反転、左右反転、明度調整などの変形や加工を組み合わせた画像処理によってデータ拡張を行ないます。
Tensorflow(2.x) + Keras の環境では、データ拡張用に
ImageDataGeneratorというクラスが用意されており、これを使えばランダムな画像処理を加えたデータ(これを、この記事では「拡張画像」とします)を比較的簡単に生成することができます。この記事では、このImageDataGeneratorを使ったデータ拡張について説明します。また、
ImageDataGeneratorは使わずに、OpenCV と tf.keras のアフィン変換などのライブラリ(cv2.warpAffineとtensorflow.keras.preprocessing.image.apply_affine_transform)を使って平行移動、回転、拡大縮小の処理を施して、マニュアルでのデータ拡張を試みます。また、両ライブラリの処理速度を比較します(結果は OpenCV の圧勝)。実行環境
Google Colab. 環境で、実行の確認をしています。
opencv-python 4.1.2.30 tensorflow 2.1.0rc1ImageDataGenerator による画像データの拡張
準備:ライブラリのインポート
Tesorflow のバージョン切り替えと、ライブラリのインポートをします。また、処理前後の画像を確認するために matplotlib を使うので、それもインポートします。
準備:ライブラリのインポート%tensorflow_version 2.x import numpy as np import tensorflow as tf import matplotlib.pyplot as plt準備:画像データの準備
処理を施すための対象データを取得してきます。ここでは「CIFAR-10」のトレーニングデータを利用します。CIFAR-10は「飛行機」「自動車」「トリ」「ネコ」「シカ」「イヌ」「カエル」「ウマ」「船」「トラック」の10種の画像から構成されるデータセットです。
準備:画像データの準備(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data() img_cifar10 = x_train/255. print(img_cifar10.shape) # -> (50000, 32, 32, 3) 32x32 3ch RGB
img_cifar10には、$32\times 32$ px の $3$ch-RGB の $50,000$ 枚の画像が、 numpy.ndarray 形式で格納されています。準備:画像表示関数の定義
画像を表示するための関数を作成します。画像を1枚だけを表示する
showImage(...)と、画像配列を与えて複数画像を同時表示するshowImageArray(...)を定義します。準備:画像表示関数の定義def showImage(img,title=None): plt.figure(figsize=(3, 3)) plt.gcf().patch.set_facecolor('white') plt.xticks([]) plt.yticks([]) plt.title(title) plt.imshow(img) plt.show() plt.close() def showImageArray(img_arry): n_cols = 8 n_rows = ((len(img_arry)-1)//n_cols)+1 fig, ax = plt.subplots(nrows=n_rows, ncols=n_cols, figsize=(10, 1.25*n_rows)) fig.patch.set_facecolor('white') for i,ax in enumerate( ax.flatten() ): if i < len(img_arry): ax.imshow(img_arry[i]) ax.set_xticks([]) ax.set_yticks([]) else : ax.axis('off') # 余白処理 plt.show() plt.close()これらの関数は、次のように呼び出します。
画像表示関数の呼出し# img_cifar10 の 12枚目の画像を表示 showImage(img_cifar10[12],title='CIFAR-10 Train Data [12]') # 0枚目から39枚目までの画像(=40枚)を表示 showImageArray(img_cifar10[:40])
showImage(...)には、shape が(32,32,3)の numpy.ndarray を引数として与えます。また、showImageArray(...)には、shape が(arr_len,32,32,3)の numpy.ndarray を引数として与えます(ここでarr_lenは画像配列の長さです)。ImageDataGenerator による画像データの拡張
ImageDataGeneratorクラス は、指定ディレクトリにある画像ファイルをロードしたり、データ拡張したりすることができます。ここでは、データ拡張の用途で使用します。
データ拡張に使用する場合、初期化時に「どんな処理(移動?回転?拡大?)を、どの程度の強度でランダムに行なうのか」のパラメータを与えます。例えば、次のようにパラメータを与えて初期化します。
ImageDataGeneratorの初期化ImageDataGenerator = tf.keras.preprocessing.image.ImageDataGenerator image_data_generator = ImageDataGenerator( rotation_range=20, # ランダムに±20度範囲で回転 width_shift_range=8, # ランダムに±8px範囲で左右方向移動 height_shift_range=4, # ランダムに±4px範囲で上下方向移動 zoom_range=(0.8, 1.2), # ランダムに0.8~1.2倍の範囲でズーム horizontal_flip=True, # ランダムで左右反転する channel_shift_range=0.2) # チャンネル値(明度)のランダムシフト範囲
width_shift_rangeとheight_shift_rangeは $1$ 未満の小数値で与えると、画像サイズに対する割合でランダム範囲を指定できます。また、今回は使っていませんがvertical_flip=Trueで上下反転の処理も加えることができます。拡張画像を1枚だけ生成
このジェネレータを使って、
img_cifar10[12]の「ウマ」の画像を対象にデータ拡張をしてみます(まずは1枚だけ拡張画像を生成します)。次のコードを実行すると、上記の初期化で与えたパラメータ範囲内で、ランダムに画像処理が行われます。拡張画像を1枚だけ生成org_img = img_cifar10[12].copy() # ターゲットは12番目の「ウマ」 ex_img = image_data_generator.flow( org_img.reshape(1,32,32,3), batch_size=1)[0][0] print(ex_img.shape) # -> (32, 32, 3) showImage(ex_img,title='CIFAR-10 Train Data [12] Ex')拡張した画像を得るためには
flow(...)メソッドを使用します。引数として、オリジナルの画像配列(単一入力には対応していないので.reshape(1,32,32,3)で1要素を持つ配列に変換して)を与えます。戻り値は、NumpyArrayIterator になるので、[0][0]で評価と0番目要素の取得をして、ex_imgに格納しています。実行結果は、次のようになります。左右が反転し、水平移動がかかり、全体的に明るくなっています(実行結果は、実行毎に変化します)。
ところで、画像を回転させたり、上下左右に移動させたりすると隙間ができますが、そこは自動的に画像端が引き伸ばされて埋められます(自然な画像に仕上がります)。この処理を適用したくない場合は、初期化で
fill_mode='constant'を指定します。すると、次のように余白は黒色で埋められます。そのほかに、fill_mode='reflect'やfill_mode='wrap'、fill_mode='nearest'(デフォルト)が指定できます。
拡張画像を複数枚生成
つづいて、
img_cifar10[12]の「ウマ」の画像から39枚の拡張画像を生成します。image_data_generator.flow(...)は、NumpyArrayIteratorを返すので、next()を使って順次画像を取り出します。拡張画像を1複数枚生成org_img = img_cifar10[12].copy() ex_img = np.empty([40, 32, 32, 3]) # オリジナルを含め40枚格納可能な領域を準備 ex_img[0,:,:,:] = org_img # 0枚目にオリジナルを格納 iter_ = image_data_generator.flow( org_img.reshape(1,32,32,3), batch_size=1) for i in range(1,40): ex_img[i,:,:,:] = iter_.next()[0] # 生成した画像を逐次格納 showImageArray(ex_img)CIFAR-10 の 0~23枚目までの画像について、各1枚の拡張画像を生成
前セクションでは、1枚の画像について複数枚の拡張画像を生成しました。今度は、CIFAR-10 のトレーニングデータの 0~23枚目までの40枚の画像について、各1枚の拡張画像を生成します。
画像配列に対して各1枚の拡張画像を生成showImageArray(img_cifar10[:24]) # CIFAR-10 オリジナル 0~23枚目を表示 ex_img = np.empty([24, 32, 32, 3]) ex_img = image_data_generator.flow(img_cifar10[:24], batch_size=24, shuffle=False)[0] showImageArray(ex_img) # CIFAR-10 拡張 0~23枚目 を表示実行結果は次のようになります。まず、こちらが CIFAR-10 のトレーニングデータの 0~23枚目までのオリジナルデータです。
つづいて、こちらが ImageDataGenerator で拡張した画像になります。各画像に対して、それぞれ異なる処理(移動、回転、拡大縮小、反転などの組合せ)が適用されていることが分かります。
なお、
flow(...)の引数に、shuffle=Falseを指定しないと、出力される画像配列の順番がシャッフルされます。OpenCV と tf.keras による処理の比較
移動量や回転角、倍率をマニュアルで指定し、OpenCV と tf.keras の各ライブラリで拡張画像を生成します。また、処理時間の比較をします。
回転
画像を反時計方向に45度回転させる処理を行ないます。回転軸は、画像の中心とします。
OpenCV のライブラリを使うプログラムは次のようになります。OpenCV では反時計方向が「正」なので、そのまま回転角
45を与えています。cv2.warpAffine(...)が処理の本体になります。borderMode=cv2.BORDER_REPLICATEを指定しないと隙間が黒で塗りつぶされます。OpenCV版のCIFAR10の50,000枚の回転処理import time import cv2 deg = 45 # 反時計方向が「正」 w, h = 32, 32 # 画像サイズ m = cv2.getRotationMatrix2D((w/2,h/2), deg, 1) # 変換行列 img_cifar10_ex = np.empty_like(img_cifar10) # 結果の格納先 np.empty([50000,32,32,3])と同じ t1 = time.time() for i,img in enumerate( img_cifar10 ) : img = cv2.warpAffine(img, m, (w,h), borderMode=cv2.BORDER_REPLICATE) img_cifar10_ex[i,:,:,:] = img t2 = time.time() print(f'処理時間 {t2-t1:.1f}[sec]') showImageArray(img_cifar10_ex[:24]) # 24枚だけを表示実行結果は次のようになります。また、処理時間は 1.3[sec] でした。
一方、tf.keras のライブラリを使ったプログラムは次のようになります。こちらは、反時計方向が「負」になるので、回転角には
-45を与えています。tf.keras版のCIFAR10の50,000枚の回転処理import time from tensorflow.keras.preprocessing.image import apply_affine_transform deg = -45 # 反時計方向が「負」 img_cifar10_ex = np.empty_like(img_cifar10) # 結果の格納先 t1 = time.time() for i,img in enumerate( img_cifar10 ) : img = apply_affine_transform(img, theta=deg) img_cifar10_ex[i,:,:,:] = img t2 = time.time() print(f'処理時間 {t2-t1:.1f}[sec]') showImageArray(img_cifar10_ex[:24])実行結果は次のようになります。また、処理時間は 16.2[sec] でした。処理速度は、圧倒的に OpenCV のほうが高速でした。
平行移動
画像を、右方向に $2$px、下方向に $5$px 移動させる処理を行ないます。
まずは、OpenCV版です。変換行列
mの内容が違うだけで、処理はさきほどの「回転」と同じです。OpenCV版のCIFAR10の50,000枚の移動処理import time import cv2 tx, ty = 2, 5 w, h = 32, 32 # 画像サイズ m = np.float32([[1,0,tx],[0,1,ty]]) # 変換行列 img_cifar10_ex = np.empty_like(img_cifar10) # 結果の格納先 t1 = time.time() for i,img in enumerate( img_cifar10 ) : img = cv2.warpAffine(img, m, (w,h), borderMode=cv2.BORDER_REPLICATE) img_cifar10_ex[i,:,:,:] = img t2 = time.time() print(f'処理時間 {t2-t1:.1f}[sec]') showImageArray(img_cifar10_ex[:24])実行結果は、次のようになります。処理時間は 1.3[sec] でした(本質的な処理は「回転」と同じなので、実行時間も変わりません)。
次に、tf.keras 版です。どうしてそのような設計になっているのか分からなかったのですが「右方向に2px、下方向に5px移動させるためには、
apply_affine_transform(img, tx=-5, ty=-2)のように」指定しないといけません。謎です。tf.keras版のCIFAR10の50,000枚の回転処理import time from tensorflow.keras.preprocessing.image import apply_affine_transform tx, ty = 2, 5 img_cifar10_ex = np.empty_like(img_cifar10) # 結果の格納先 t1 = time.time() for i,img in enumerate( img_cifar10 ) : img = apply_affine_transform(img, tx=-ty, ty=-tx) # 引数の与え方に注意 img_cifar10_ex[i,:,:,:] = img t2 = time.time() print(f'処理時間 {t2-t1:.1f}[sec]') showImageArray(img_cifar10_ex[:24])実行結果は、次のようになります。処理時間は 13.9[sec] で、さきほどと同様に OpenCV の処理時間よりも約10倍の時間がかかっています。
拡大
画像を、1.2倍に拡大する処理を行ないます(拡大しても画像サイズは変化させません)。
まずは、OpenCV版です。
resizeで拡大して、その後、$32\times 32$ px にトリミングしています。OpenCV版のCIFAR10の50,000枚の拡大処理import cv2 f = 1.2 w, h = 32, 32 # 画像サイズ w2, h2 = int(w*f), int(h*f) # 拡大画像のサイズ tx, ty = int((w2-w)/2),int((h2-h)/2) # トリミングの開始座標 m = np.float32([[1,0,tx],[0,1,ty]]) # 変換行列 img_cifar10_ex = np.empty_like(img_cifar10) # 結果の格納先 t1 = time.time() for i,img in enumerate( img_cifar10 ) : img = cv2.resize(img,(w2,h2)) img_cifar10_ex[i,:,:,:] = img[tx:tx+w,ty:ty+h,:] # 拡大画像から32x32をトリミング t2 = time.time() print(f'処理時間 {t2-t1:.1f}[sec]') showImageArray(img_cifar10_ex[:24])実行結果は、次のようになります。処理時間は 1.2[sec] でした。
次に、tf.keras 版です。先ほど同様に大変に分かりにくいのですが、
apply_affine_transform(img, zx=1/f, zy=1/f)のように引数zxとzyには「倍率の逆数」を与えます。tf.keras版のCIFAR10の50,000枚の拡大処理import time from tensorflow.keras.preprocessing.image import apply_affine_transform f = 1.2 img_cifar10_ex = np.empty_like(img_cifar10) # 結果の格納先 t1 = time.time() for i,img in enumerate( img_cifar10 ) : img = apply_affine_transform(img, zx=1/f, zy=1/f) # 引数注意 img_cifar10_ex[i,:,:,:] = img t2 = time.time() print(f'処理時間 {t2-t1:.1f}[sec]') showImageArray(img_cifar10_ex[:24])
- 投稿日:2020-01-26T16:05:52+09:00
マイコン(Arduino)でTensorFlowを動かしてみた!
この記事の目的
最近、個人的にTensorflow Liteを使ってIoTエッジデバイスで機械学習モデルを実行してみたいと
思って調べている中で、ArduinoでもDeepLearningモデルが簡単に実行できると知り、実際に試してみました。ArduinoでTensorflow Liteを動かしてみたので、その流れを書いてみたいと思います。
基本的には、公式ページの内容を試した形になっています。この記事で使用したもの
- Arduino Nano 33 BLE Sense ※購入ページ
- 開発用PC ※私の場合は、macOS Catalina バージョン10.15.2 で行いました。
- Arduino IDE 1.8.10
開発環境のセットアップ
Tensorflow Liteをマイコンで実施するには、公式ページに記載されている通りいくつか選択肢があります。
今回は、一番上に記載されているArduino Nano 33 BLE Senseというマイコンを使用しました。
Arduino Nano 33 BLE Sense の特徴
デバイス詳細はこちらをご確認ください。
- デバイス概要
- 機械学習モデルが実行できるBLE搭載のArduino Nano。
- 特徴、スペック
- 9軸慣性センサー、温湿度センサー、気圧センサー、マイク
- ジェスチャ、近接、光の色、光強度センサー
- 価格
- 約$30
Arduino IDEの設定
サンプルスケッチをCompileするためのArduino IDEの設定を行います。
- Arduino IDEをインストール
- Arduinoのページから、ご使用のOSに合わせてインストールを行ってください。
- ボードをインストール
- ライブラリをインストール
- ボードの選択
- Tools -> Board から Arduino Nnao 33 BLEを選択してください。
サンプルスケッチの実行
TensorflowLiteのサンプルスケッチがいくつか用意されています。参考
- hello world
- magic wand
- micro speech
- person detection
- カメラで人検出するサンプル。
- Arducam Mini 2MP Plusが別途必要になりますので、今回は実行できませんでした。
注意事項
- 私のMacはOSがCatalinaだったため、コンパイルで下記のようなエラーが出ていました。Arduino IDE 1.8.0から1.8.10にアップデートすることで、エラーは解消されました。
- コンパイルエラー:bad CPU type in executable...
終わりに
- 意外につまることなく、サンプルが実行できました。ただ、コードやライブラリは理解できていないので、オリジナルモデルを使って、カメラモジュールを使った独自の検出機能を作ったら理解も進んで面白そうだなーと思いました。時間があればやってみようと思います。
- 先日、Tensorflow User Groupに参加した時の感想ですが、エッジデバイスモデルの軽量化、デバイスへの組み込みについては奥が深く、個人ではなかなか手が出せない部分なので、このように簡単に試せるようになっていることに感動しました。
参考文献
- 投稿日:2020-01-26T12:00:44+09:00
アラフォーのおっさんが一ヶ月でWebサービスを作った話
初めてWebサービスを作ってみました。
年末年始を使い、約1ヶ月程度で、どうにか公開までこぎつけられたので、振り返ってみたいと思います。モチベーション
筆者は職業プログラマーではありませんが、将来に不安を感じ、プログラミングスキルを磨くことにしました。
「プログラマ35歳定年説」に言われる30代半ばを突き抜けた、まもなく40歳になるオッサンが、プログラミングスキルへ投資することに賭けたのです。アラフォーになるまでの経験値
- ユーザ系システムインテグレータに勤務
- 数年前まではインフラ業務を担当(「VMware」とか)
- 今は社内システム(バッケージソフトウェアで構築)の保守を担当
- 数年前から深層学習(「TensorFlow」とか)の本を読み始めた
作り始める前(1ヶ月前)のレベル
- 「jQuery」が少し分かる
- 最近のWebシステムのトレンドって何?
- フレームワークって何を使うといいの?「React」、「Vue」とか一杯あるけど
- 「WebPack」?「Bazel」?何それ?
- いつか「Docker」を使ってみたい
- バックエンドはConcurrent(並行)処理に強い「Go」がいいらしい?
どんなWebサービスを作ったのか
手書き文字(カタカナ)の認識です。
深層学習ライブラリ「TensorFlow」を使った、「MNIST」等の手書き数字認識のカタカナ版になります。[カタカナ道場]
https://katakanadojo.tokyo筆者には子供がいますが、この子の書くカタカナが怪しい。
(「シ」と「ツ」、「ソ」と「ン」が同じに見えるなど)「コンピュータが読める字を書けた方がいいぞ!」とモチベーションを促し、字の上達につなげてもらう目論見で作りました。
※突貫工事で作ったので今後、手直ししていく予定です。メンテナンス中で落ちている場合があるのでご容赦ください。他人様に見せられる状態になったらソースコードもアップします。
まずは認識モデルを作る
画像認識の界隈では、「VGG-19」などの事前学習モデルを使った転移学習モデルが高精度の結果を出していますが、シンプルな畳み込み層だけで実装することにしました。
実際にご自身で試していただきたいと思いますが、雑に書いた字でも認識してくれるので、重厚なモデルは使わなくても十分かと考えます。
得点は認識モデルが出力した確率を1万倍しています。
最初はパーセンテージで出力しようとしたのですが、小さい子にパーセンテージの概念は難しいようだったので整数にしました。認識モデルが返すJSON出力はこんな感じです。
{ predict: 'MO', top_3: [ { label: 'MO', probability: 0.93235594 }, { label: 'NE', probability: 0.05294613 }, { label: 'SE', probability: 0.013120668 } ] }トレーニングセットはETL文字データベースを使わせていただきました。
http://etlcdb.db.aist.go.jp/?lang=jaWeb化する
先述の認識モデルを作る際の言語は「Python」を使いました。
pythonでWeb化するには、ということで調べてみると、「Django」や「Flask」というWebフレームワークがあることがわかりました。
どちらも「WSGI」と呼ばれるインターフェースをベースにしているようなので、以下のパッケージを使って試しに書いてみました。from wsgiref.simple_server import make_serverここで疑問が
- もっと速くできないか?
- 認識モデルをメモリに常駐させておくとして、同時アクセスに耐えられるか?
- フレームワークを使えばスレッドをうまく使ってくれるのか?
そうだ、「Go」にしよう
「TensorFlow」ライブラリは、「Python」以外の言語版APIも公開されているということで、「Go」版を調べてみました。
[TensorFlow Go API]
https://www.tensorflow.org/install/lang_goCaution: The TensorFlow Go API is not covered by the TensorFlow API stability guarantees.
「安定性は保証しない」とあります。
これが企業の開発なら即刻、候補から外されるのでしょうが、個人の開発なので試してみることにしました。
「Go API」の詳細については、他の方が書いた記事をご覧ください。[GoでTensorFlowのAPIを使ってみた話]
https://qiita.com/yasuno0327/items/8f8fa5629df3243347bd結果は・・・速い。採用!!!
見た目をリッチにする
フロントエンドに着手します。
どのフレームワークを使うか、この辺の技術選定は本当に悩ましい。
筆者には遠回りしている時間がない。これから生き残るであろう技術を選びたい。
今回は「React」を使うことにしました。
理由はモバイル開発にもつなげられそうだと感じたからです。バックボーンや目的によって違ってくるかと思います。他の方が書いた記事をご覧ください。
[ReactとVueのどちらを選ぶか]
https://qiita.com/yoichiwo7/items/236b6535695ea67b4fbe「Material-UI」といった「UIフレームワーク」が公開されているので活用させていただきました。
クライアント側は、画像をBase64エンコードして、Ajax通信でサーバ(Goで実装)にPOSTします。VPSを借りる
「create-react-app」コマンドで「React」アプリを作り、それをbuildすると、画面に「ZEIT Now」を紹介する出力が出てきます。
Webのフロント部分だけなら、「Zeit Now」でいいと思いました。
ドメイン、証明書まで付いていて無料。すごい。しかし、今回は「TensorFlow」のモデルを動かすので、ある程度のリソース(CPU、メモリ)は確保したい。
NTTPC社が提供するVPSサービス「WebARENA」を使うことにしました。
https://web.arena.ne.jp/indigo/メモリ1GBプラン、月額349円(税込)をチョイス。
「1vCPU」、「1GBメモリ」、OSは「CentOS」か「Ubuntu」を選択可能。この辺はVPSサービスから探し始めて、わりと直感的に決めてしまった面があり、「AWS」や「Azure」といったクラウドサービスは検討していません。
時代はパブリッククラウドの流れにあるし、後になって検討すべきだったと悔いるかもしれません。でも、いいのだ。コンテナ化する
「Ubuntu」上に「Docker」エンジンを入れます。
Dokerホストが1台だけなので「kubernetes」は無し。
(「kubernetes」がどんなものなのか興味はありましたが、今回はパス)
下記3つのコンテナを立てました。
- HTTPS化、証明書周り(「https-portal」)
- リクエストを振り分けるリバースプロキシ(「Node.js」)
- Base64エンコードされた画像を認識し判別結果をJOSNで返す(「Go」)
図にすると以下のようなイメージになります。
1vCPU、1GBメモリのゲストOS上で動いていることに驚嘆します。
ドメインを取る
「ムームードメイン」で、1年契約の「katakanadojo.tokyo」を取りました。
消費税込みで「110円」でした。
「お名前.com」と「ムームードメイン」の2つを見て、安い方にしました。かかった費用
VPS利用料が毎月349円、ドメイン契約料が毎年発生するして、ランニング費用は月額500円を切るといったところでしょうか。
個人で容易にサービスを公開できる時代になったと感じます。
このサービスをいつまで維持するかは未定です。終わりに
ざっと振り返った流れは以上になります。
一ヶ月前のことなのに忘れかけている部分も多く、せっかく苦労して乗り越えた部分を残せていないと感じました。
もし興味を持っていただける方がいらっしゃいましたら、詳細を加筆していきたいと思います。最後まで読んでいただき、ありがとうございました。
- 投稿日:2020-01-26T12:00:44+09:00
アラフォーが一ヶ月でWebサービスを作った話
初めてWebサービスを作ってみました。
年末年始を使い、約1ヶ月程度で、どうにか公開までこぎつけられたので、振り返ってみたいと思います。モチベーション
筆者は職業プログラマーではありませんが、将来に不安を感じ、プログラミングスキルを磨くことにしました。
「プログラマ35歳定年説」に言われる30代半ばを突き抜けた、まもなく40歳になるオッサンが、プログラミングスキルへ投資することに賭けたのです。アラフォーになるまでの経験値
- ユーザ系システムインテグレータに勤務
- 数年前まではインフラ業務を担当(「VMware」とか)
- 今は社内システム(バッケージソフトウェアで構築)の保守を担当
- 数年前から深層学習(「TensorFlow」とか)の本を読み始めた
作り始める前(1ヶ月前)のレベル
- 「jQuery」が少し分かる
- 最近のWebシステムのトレンドって何?
- フレームワークって何を使うといいの?「React」、「Vue」とか一杯あるけど
- 「WebPack」?「Bazel」?何それ?
- いつか「Docker」を使ってみたい
- バックエンドはConcurrent(並行)処理に強い「Go」がいいらしい?
どんなWebサービスを作ったのか
手書き文字(カタカナ)の認識です。
深層学習ライブラリ「TensorFlow」を使った、「MNIST」等の手書き数字認識のカタカナ版になります。[カタカナ道場]
https://katakanadojo.tokyo筆者には子供がいますが、この子の書くカタカナが怪しい。
(「シ」と「ツ」、「ソ」と「ン」が同じに見えるなど)「コンピュータが読める字を書けた方がいいぞ!」とモチベーションを促し、字の上達につなげてもらう目論見で作りました。
※突貫工事で作ったので今後、手直ししていく予定です。メンテナンス中で落ちている場合があるのでご容赦ください。他人様に見せられる状態になったらソースコードもアップします。
まずは認識モデルを作る
画像認識の界隈では、「VGG-19」などの事前学習モデルを使った転移学習モデルが高精度の結果を出していますが、シンプルな畳み込み層だけで実装することにしました。
実際にご自身で試していただきたいと思いますが、雑に書いた字でも認識してくれるので、重厚なモデルは使わなくても十分かと考えます。
得点は認識モデルが出力した確率を1万倍しています。
最初はパーセンテージで出力しようとしたのですが、小さい子にパーセンテージの概念は難しいようだったので整数にしました。認識モデルが返すJSON出力はこんな感じです。
{ predict: 'MO', top_3: [ { label: 'MO', probability: 0.93235594 }, { label: 'NE', probability: 0.05294613 }, { label: 'SE', probability: 0.013120668 } ] }トレーニングセットはETL文字データベースを使わせていただきました。
http://etlcdb.db.aist.go.jp/?lang=jaWeb化する
先述の認識モデルを作る際の言語は「Python」を使いました。
pythonでWeb化するには、ということで調べてみると、「Django」や「Flask」というWebフレームワークがあることがわかりました。
どちらも「WSGI」と呼ばれるインターフェースをベースにしているようなので、以下のパッケージを使って試しに書いてみました。from wsgiref.simple_server import make_serverここで疑問が
- もっと速くできないか?
- 認識モデルをメモリに常駐させておくとして、同時アクセスに耐えられるか?
- フレームワークを使えばスレッドをうまく使ってくれるのか?
そうだ、「Go」にしよう
「TensorFlow」ライブラリは、「Python」以外の言語版APIも公開されているということで、「Go」版を調べてみました。
[TensorFlow Go API]
https://www.tensorflow.org/install/lang_goCaution: The TensorFlow Go API is not covered by the TensorFlow API stability guarantees.
「安定性は保証しない」とあります。
これが企業の開発なら即刻、候補から外されるのでしょうが、個人の開発なので試してみることにしました。
「Go API」の詳細については、他の方が書いた記事をご覧ください。[GoでTensorFlowのAPIを使ってみた話]
https://qiita.com/yasuno0327/items/8f8fa5629df3243347bd結果は・・・速い。採用!!!
見た目をリッチにする
フロントエンドに着手します。
どのフレームワークを使うか、この辺の技術選定は本当に悩ましい。
筆者には遠回りしている時間がない。これから生き残るであろう技術を選びたい。
今回は「React」を使うことにしました。
理由はモバイル開発にもつなげられそうだと感じたからです。バックボーンや目的によって違ってくるかと思います。他の方が書いた記事をご覧ください。
[ReactとVueのどちらを選ぶか]
https://qiita.com/yoichiwo7/items/236b6535695ea67b4fbe「Material-UI」といった「UIフレームワーク」が公開されているので活用させていただきました。
クライアント側は、画像をBase64エンコードして、Ajax通信でサーバ(Goで実装)にPOSTします。VPSを借りる
「create-react-app」コマンドで「React」アプリを作り、それをbuildすると、画面に「ZEIT Now」を紹介する出力が出てきます。
Webのフロント部分だけなら、「Zeit Now」でいいと思いました。
ドメイン、証明書まで付いていて無料。すごい。しかし、今回は「TensorFlow」のモデルを動かすので、ある程度のリソース(CPU、メモリ)は確保したい。
NTTPC社が提供するVPSサービス「WebARENA」を使うことにしました。
https://web.arena.ne.jp/indigo/メモリ1GBプラン、月額349円(税込)をチョイス。
「1vCPU」、「1GBメモリ」、OSは「CentOS」か「Ubuntu」を選択可能。この辺はVPSサービスから探し始めて、わりと直感的に決めてしまった面があり、「AWS」や「Azure」といったクラウドサービスは検討していません。
時代はパブリッククラウドの流れにあるし、後になって検討すべきだったと悔いるかもしれません。でも、いいのだ。コンテナ化する
「Ubuntu」上に「Docker」エンジンを入れます。
Dokerホストが1台だけなので「kubernetes」は無し。
(「kubernetes」がどんなものなのか興味はありましたが、今回はパス)
下記3つのコンテナを立てました。
- HTTPS化、証明書周り(「https-portal」)
- リクエストを振り分けるリバースプロキシ(「Node.js」)
- Base64エンコードされた画像を認識し判別結果をJOSNで返す(「Go」)
図にすると以下のようなイメージになります。
1vCPU、1GBメモリのゲストOS上で動いていることに驚嘆します。
ドメインを取る
「ムームードメイン」で、1年契約の「katakanadojo.tokyo」を取りました。
消費税込みで「110円」でした。
「お名前.com」と「ムームードメイン」の2つを見て、安い方にしました。かかった費用
VPS利用料が毎月349円、ドメイン契約料が毎年発生するして、ランニング費用は月額500円を切るといったところでしょうか。
個人で容易にサービスを公開できる時代になったと感じます。
このサービスをいつまで維持するかは未定です。終わりに
ざっと振り返った流れは以上になります。
一ヶ月前のことなのに忘れかけている部分も多く、せっかく苦労して乗り越えた部分を残せていないと感じました。
もし興味を持っていただける方がいらっしゃいましたら、詳細を加筆していきたいと思います。最後まで読んでいただき、ありがとうございました。
























