- 投稿日:2019-08-20T23:59:50+09:00
【Tensorflow-gpu 1.x系】複数のモデルを一つのプログラムで実行する
はじめに
以下のように複数のモデル(手検出モデルと指先検出モデル)を1つのプログラムで実行しようとした時にGPUメモリで躓いたためメモ。
以下のプログラムでは手検出で1つ、指先検出で1つの計2つのモデルを利用しています。複数モデル(手検出、指検出)を実行するプログラムを少々書き直し中。。。? pic.twitter.com/tx1fH3Ygkr
— 高橋かずひと@リベロ拝命? (@KzhtTkhs) August 20, 2019躓いたポイント
1つ目のモデルを読み込んだ後、2つ目のモデルを動作させようとするとGPUメモリ不足でプログラムが異常終了する。
原因
TensorFlowはデフォルト設定では、GPUのメモリのほぼ全てを使用して、メモリの断片化を軽減するようにしているようです。
1つ目のモデルの時点でメモリをほぼ全て使い切ったため、2つ目のモデルは動かせなかった模様。対処
Allowing GPU memory growthオプションを利用し、必要なメモリだけ確保するようにする。
※ただし、プログラム途中でGPUメモリ解放等はしないように注意が必要とのことソースコード
以下のような指定をする。
config = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True)) sess = tf.Session(graph=net_graph, config=config)
ソースコード全体のイメージ ※イメージです
#!/usr/bin/env python # -*- coding: utf-8 -*- import time import copy import cv2 as cv import tensorflow as tf import numpy as np def session_run(sess, inp): out = sess.run([ sess.graph.get_tensor_by_name('num_detections:0'), sess.graph.get_tensor_by_name('detection_scores:0'), sess.graph.get_tensor_by_name('detection_boxes:0'), sess.graph.get_tensor_by_name('detection_classes:0') ], feed_dict={ 'image_tensor:0': inp.reshape(1, inp.shape[0], inp.shape[1], 3) }) return out def main(): print("Hand Detection Start...\n") # カメラ準備 ############################################################## cap = cv.VideoCapture(0) cap.set(cv.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv.CAP_PROP_FRAME_HEIGHT, 720) # GPUメモリを必要な分だけ確保するよう設定 config = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True)) # 手検出モデルロード ###################################################### with tf.Graph().as_default() as net1_graph: graph_data = tf.gfile.FastGFile('frozen_inference_graph1.pb', 'rb').read() graph_def = tf.GraphDef() graph_def.ParseFromString(graph_data) tf.import_graph_def(graph_def, name='') sess1 = tf.Session(graph=net1_graph, config=config) sess1.graph.as_default() # 指先検出モデルロード ###################################################### with tf.Graph().as_default() as net2_graph: graph_data = tf.gfile.FastGFile('frozen_inference_graph2.pb', 'rb').read() graph_def = tf.GraphDef() graph_def.ParseFromString(graph_data) tf.import_graph_def(graph_def, name='') sess2 = tf.Session(graph=net2_graph, config=config) sess2.graph.as_default() while True: start_time = time.time() # カメラキャプチャ #################################################### ret, frame = cap.read() if not ret: continue debug_image = copy.deepcopy(frame) # 手検出実施 ########################################################## inp = cv.resize(frame, (512, 512)) inp = inp[:, :, [2, 1, 0]] # BGR2RGB out = session_run(sess1, inp) rows = frame.shape[0] cols = frame.shape[1] num_detections = int(out[0][0]) for i in range(num_detections): class_id = int(out[3][0][i]) score = float(out[1][0][i]) bbox = [float(v) for v in out[2][0][i]] if score < 0.8: continue x = int(bbox[1] * cols) y = int(bbox[0] * rows) right = int(bbox[3] * cols) bottom = int(bbox[2] * rows) # 指先検出実施 #################################################### if class_id == 3: trimming_image = debug_image[y:bottom, x:right] inp = cv.resize(trimming_image, (300, 300)) inp = inp[:, :, [2, 1, 0]] # BGR2RGB f_rows = trimming_image.shape[0] f_cols = trimming_image.shape[1] out = session_run(sess2, inp) f_num_detections = int(out[0][0]) for i in range(f_num_detections): f_score = float(out[1][0][i]) f_bbox = [float(v) for v in out[2][0][i]] f_x = int(f_bbox[1] * f_cols) f_y = int(f_bbox[0] * f_rows) f_right = int(f_bbox[3] * f_cols) f_bottom = int(f_bbox[2] * f_rows) if f_score < 0.4: continue # 指検出結果可視化 ######################################## cv.rectangle( trimming_image, (f_x, f_y), (f_right, f_bottom), (0, 255, 0), thickness=2) # 手検出結果可視化 ################################################ cv.rectangle( debug_image, (x, y), (right, bottom), (0, 255, 0), thickness=2) # 処理時間描画 ######################################################## elapsed_time = time.time() - start_time time_string = u"elapsed time:" + '{:.3g}'.format(elapsed_time) cv.putText(debug_image, time_string, (10, 50), cv.FONT_HERSHEY_COMPLEX, 1.0, (0, 255, 0)) # 画面反映 ############################################################ cv.imshow(' ', debug_image) cv.moveWindow(' ', 100, 100) key = cv.waitKey(1) if key == 27: # ESC break if __name__ == '__main__': main()以上。
- 投稿日:2019-08-20T21:19:02+09:00
「スタバなう」ツイートをニューラルネットで学習してラーメン判定器を作る(keras+Tensorflow+VGG16)
この記事の抜粋したコードの完全版はGitHubでご覧いただけます。
また、この記事で作成したモデルはTwitterのスタバ警察botで実際に試せるので、ご興味があれば適当な画像を「スタバなう」という文字列と一緒にリプライしてみてください。
こういうtweetが機械学習界隈からの怒りを買ってます(笑) https://t.co/COV1IHyh03
— Yuki Suga (@ysuga) July 26, 2019というツイートからも分かるように、現在のスタバなうツイートは完全に関係ない画像で蹂躙されており、実際にスタバで撮影された画像は全体の24%しかありません。
逆にここまで来ると、残り76%の画像に着目した方が良いのではという気すらしてきます。
というわけで、「スタバなうと言いながら投稿される関係ない画像」の筆頭であるラーメンの分類器を、スタバなうツイートだけで作れるかどうか試してみます。
手順やコードについてはGPUを使ってVGG16をFine Tuningして、顔認識AIを作って見た - Qiitaを大変参考にさせて頂きました。学習データは2019/07/21-2019/08/03の間に「スタバなう」とツイートされた4064枚の画像です。
あらかじめ、ラーメンかそうでないか、ついでにスタバかどうかの3つのラベルを振っておきます。枚数は以下の通りでした。
分類 枚数 全体 4064 ラーメン画像 617 スタバ画像 973 その他画像 2474 (画像の収集手順についてご興味があれば、こちらの記事もご覧ください→「スタバなう」でアップロードされたtwitterの画像をGoで収集してS/N比を調べる - Qiita)
これらをラベルごとのサブディレクトリに切っておきます。(この後、前処理を行うので、originalというsuffixを付けておきます)
$ pwd /path/to/images/train $ tree . . ├── other-original ├── ramen-original └── sutaba-original例えば
ramen-original
ディレクトリにはこんな感じの画像が入っています。これらの画像のうち、数十枚はtest用の画像として別に保存しておきます。(今回は各クラス70枚程度をtestとしています)
$ pwd /path/to/images/test $ tree . . ├── other-original ├── ramen-original └── sutaba-original前処理
今回はVGG16モデルを使って分類器を作ります。
画像サイズは224x224にしておくのが一般的?のようですが、当然ツイートから収集した画像はサイズも違いますし、そもそも正方形でないことが多いです。なので、あらかじめ正方形画像へ切り出し+リサイズを行っておきます。
numpyやpillowでも簡単に書けると思いますが、今回はmogrifyを利用して切り出しました。mogirifyは、画像のリサイズやクロップを行うためのImageMagickラッパーです。
ラーメン画像のリサイズを行う場合は以下を実行します。(パスは環境に合わせて変更してください。)mogrify \ -path /path/to/images/train/ramen \ -define jpeg:size=224x224 \ -thumbnail 224x224^ \ -gravity center \ -extent 224x224 \ /path/to/images/train/ramen-original/*.jpgこれで、
ramen
ディレクトリに整形後の画像が置かれます。
その他画像、スタバ画像に対してや、testディレクトリについても同様に実行してください。データの読み込み
まずtrainとvalidation用のgeneratorを作成します。
ImageDataGenerator
のvalidation_split
にvalidation_rate
を指定することで、その割合で学習データを分割してくれます。
データオーギュメンテーションについては、画像のズームと左右反転だけを入れています。
逆立ちしてラーメンを撮影する人はいないだろうということで、画像の回転はしていません。# 分類するクラス classes = ["sutaba","ramen", "other"] nb_classes = len(classes) # 画像の大きさを設定 img_width, img_height = 224, 224 # バッチサイズ batch_size = 300 # エポック数 nb_epoch = 100 # バリデーションにつかうデータの割合 validation_rate = 0.1 train_datagen = ImageDataGenerator( rescale=1.0 / 255, zoom_range=0.2, horizontal_flip=True, validation_split=validation_rate ) train_generator = train_datagen.flow_from_directory( train_data_dir, target_size=(img_width, img_height), color_mode='rgb', classes=classes, class_mode='categorical', batch_size=batch_size, # save_to_dir=aug_train_data_dir, shuffle=True, subset='training' ) validation_generator = train_datagen.flow_from_directory( train_data_dir, target_size=(img_width, img_height), color_mode='rgb', classes=classes, class_mode='categorical', batch_size=batch_size, # save_to_dir=aug_train_data_dir, shuffle=True, subset='validation' )モデルの準備
今回はImageNetで学習されたVGG16の全結合層だけを再学習していきます。
GPUを使ってVGG16をFine Tuningして、顔認識AIを作って見た - Qiitaのほぼそのままコピペです。
(書いてて気づきましたが、optimizerはAdamでも良かったかもしれません)def create_vgg16(): input_tensor = Input(shape=(img_width, img_height, 3)) vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor) top_model = Sequential() top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) top_model.add(Dense(256, activation='relu')) top_model.add(Dropout(0.5)) top_model.add(Dense(nb_classes, activation='softmax')) vgg_model = Model(input=vgg16.input, output=top_model(vgg16.output)) for layer in vgg_model.layers[:15]: layer.trainable = False vgg_model.compile(loss='categorical_crossentropy', optimizer=optimizers.SGD(lr=1e-3, momentum=0.9), metrics=['accuracy']) return vgg_modelこれで準備ができたので、
fit_generator
で実際に学習を開始します。vgg_model = create_vgg16(model_dir) n = datetime.datetime.now() nstr = f'{n.year}-{n.month:02}-{n.day:02}_{n.hour:02}-{n.minute:02}-{n.second:02}' fpath = f'/path/to/models/{nstr}' + 'weights.{epoch:02d}-{loss:.2f}-{acc:.2f}-{val_loss:.2f}-{val_acc:.2f}.hdf5' cp_cb = keras.callbacks.ModelCheckpoint(filepath=fpath, monitor='val_loss', verbose=1, save_best_only=True, mode='auto') class_weight = {0: other_train_num/sutaba_train_num, 1: other_train_num/ramen_train_num, 2: 1} history = vgg_model.fit_generator( train_generator, validation_data=validation_generator, samples_per_epoch=len(train_generator.classes), nb_epoch=nb_epoch, callbacks=[cp_cb], nb_val_samples=len(validation_generator.classes), class_weight=class_weight )
class_weight
は各クラスの重みを決定します。今回は画像枚数がクラス間で最大4倍程度違うため、その比率で重みを調整して、枚数の多いその他画像が過度に重要視されないようにします。keras.callbacks.ModelCheckpoint
をcallbacks
に設定することで、エポック毎にモデルを保存することができます。save_best_only=True
にしておくと、指定した値(ここではval_loss
)が改善した時にだけ保存するようになります。評価
test用generatorの生成はtrain/validationの時とだいたい同じですが、
batch_size
は1、shuffle
はFalse
にしておきます。test_datagen = ImageDataGenerator(rescale=1.0 / 255) test_generator = test_datagen.flow_from_directory( test_data_dir, target_size=(img_width, img_height), color_mode='rgb', classes=classes, class_mode='categorical', batch_size=1, shuffle=False) vgg_model.load_weights('/path/to/models/2019-08-07_13-54-18weights.01-0.38-0.90-0.40-0.85.hdf5') loss = vgg_model.predict_generator(test_generator, steps=len(test_generator.classes), verbose=1) prob = pd.DataFrame(loss, columns=classes) prob['predict'] = prob.idxmax(axis=1) prob['actual'] = [classes[c] for c in test_generator.classes] prob['path'] = test_generator.filenames prob.to_csv('results.csv')
results.csv
は以下のようになります。
i sutaba ramen other predict actual path 0 0.9999993 9.351573e-09 7.663224e-07 sutaba sutaba sutaba/1152864499935805440-0-D__MJnoUcAEiYc0.jpg 1 0.9680856 2.6841928e-05 0.03188745 sutaba sutaba sutaba/1152903458401292288-0-D__vlSZU4AA22-W.jpg 2 0.999997 6.129743e-08 2.8856034e-06 sutaba sutaba sutaba/1153073093935390720-0-EACJ3MVUYAAyBcA.jpg 3 0.92388695 5.6549048e-05 0.07605657 sutaba sutaba sutaba/1153104671583526912-0-EACmlFmUcAAXgEF.jpg 4 0.06483261 0.01827882 0.9168886 other sutaba sutaba/1153118856325414912-0-EACzez2UEAE7jDv.jpg (以下省略)
左から2~4列目のsutaba/ramen/otherが、それぞれスタバ/ラーメン/その他クラスの確信度を表します。
predictが推定したクラス(最大の確信度を持つクラス),actualが実際のクラスです。
predictとactualが同じクラスになっていれば、推定に成功していると言えます。
このデータを集計してモデルの精度を算出します。まずは混同行列を計算してみます。
from sklearn.metrics import confusion_matrix mat = confusion_matrix(prob['actual'], prob['predict'], labels=classes) print(mat) # Output # array([[66, 1, 6], # [ 0, 56, 11], # [ 2, 4, 64]])
スタバと判定 ラーメンと判定 その他と判定 実際にスタバの画像 66 1 6 実際にラーメンの画像 0 56 11 実際にその他の画像 2 4 64 ラーメン画像をスタバと間違えることは一度もありませんでしたが、16%ぐらいはその他と判定してしまっているようです。
逆に、スタバ画像なのにラーメンと評価してしまっている画像が1枚あります。対象の画像を含むツイートは以下です。彼氏とスタバなう pic.twitter.com/mgJKYPELLA
— Amelia (@kk66172001) July 22, 2019今回は、つけ麺もラーメン画像として含めていたので、「器が2つあって片方に暗い色の液体が入っている」写真は、つけ麺と判定されているのかもしれません。
最後に、ラーメンクラスに着目した精度を評価します。
t = prob['actual'] == 'ramen' p = prob['predict'] == 'ramen' print(confusion_matrix(t, p, labels=[True, False])) print('accuracy:', accuracy_score(t, p)) print('precision:', precision_score(t, p)) print('recall':, recall_score(t, p)) print('f1:', f1_score(t, p)) # Output # [[ 56 11] # [ 5 138]] # accuracy 0.9238095238095239 # precision 0.9180327868852459 # recall 0.835820895522388 # f1 0.875というわけで「スタバなう」ツイートだけで、F値87.5%でラーメンを判定することができました。?
このモデルはTwitterのスタバ警察botで実際に使っています。どれぐらいちゃんとラーメン(やスタバ)を判定できるのかご興味があれば試してみてください!ピピーッ❗️?⚡️スタバ警察です❗️??❗️ オオッこのツイート???...間違いなくスタバ❗️❗️???
— スタバ警察 (@sutaba_police) August 19, 2019
市民の協力に感謝するッッッ??❗❗ https://t.co/nXsfuHtdxx
- 投稿日:2019-08-20T13:45:47+09:00
tensorflowによる画像スタイル変換の体験
はじめに
画像のスタイル変換について学んだので、体験することを目的として詳しい部分には触れず、とりあえず誰にでも画像のスタイル変換の体験ができるように書いてみた。
準備
今回試すことは、画像のスタイル変換で、自分の好きな画像を他の画像の画風に変換する、といったような感じにすることができる。
なので、今回はりんごの写真をスタイル変換してみることにした。綺麗なりんごだ。
これがどんな感じになるんだろうか。まずGithub上から
lengstrom/fast-style-transfer
をダウンロードする。Github
https://github.com/lengstrom/fast-style-transfer$ git clone https://github.com/lengstrom/fast-style-transfer.gitそして、先程ダウンロードしたfast-style-transferディレクトリにまで移動する。
$ cd fast-style-transferこの中の
evaluate.py
に画像のスタイル変換のコードが書かれている。
このファイルを実行すれば画像のスタイル変換がなされるのだが、まずは画像と変換したいスタイル様式の準備が必要だ。
今回はりんごの画像を、
葛飾北斎の
と、
のようなスタイルに変化してみたいと思う。
これら2つのスタイルに変換するためのファイルを以下のリンクから取得し、
fast-style-transfer
ディレクトリ以下におく。
今回の2つのスタイルのファイルはwave.ckpt
とla-muse.ckpt
であるので、それらのファイルを以下のリンクから取得した。Google drive
https://drive.google.com/drive/folders/0B9jhaT37ydSyRk9UX0wwX3BpMzQ一応、確認。
$ ls README.md la_muse.ckpt style.py apple.jpg transform_video.py docs.md wave.ckpt evaluate.py setup.sh examples srcこれで準備完了。
実行
実行するときのコマンドは以下を参照にしてほしい。
$ python evaluate.py --checkpoint ./参照ファイル名 --in-path 選択画像名 --out-path ./出力画像名もし、
ImportError: No module named 'moviepy'というエラーが出ましたら、
evaluate.py
ファイルの13,14行目の部分を# from moviepy.video.io.VideoFileClip import VideoFileClip # import moviepy.video.io.ffmpeg_writer as ffmpeg_writerのようにコメントアウトしてください。
まずは葛飾北斎の画像のスタイルのりんごの画像を作成してみる。
出力画像の名前はoutput1.jpg
とした。$ python evaluate.py --checkpoint ./wave.ckpt --in-path apple.jpg --out-path ./output1.jpgこのコマンドの実行が完了したら、同ディレクトリに
output1.jpg
ファイルが作成されている。
その結果がこれ。
おお、ぽい!!って感じ笑
次はピカソの画像のスタイルのりんごの画像を作成してみる。
出力画像の名前はoutput2.jpg
とした。$ python evaluate.py --checkpoint ./la_muse.ckpt --in-path apple.jpg --out-path ./output2.jpgおおお、なんかいい感じ!!
終わりに
画像のスタイル変換についてわからない部分は多いながらも、体験はできたかと思います。
示しましたGoogleDriveのリンクには他のスタイルのファイルも存在していますので、ぜひそちらでも試してみてください。今回は試すことをメインにしたので、どうして出力されるのかが気になる方は
evaluate.py
の中のコードを読んだり、他の説明されているサイトなどを参考にしてください。最後まで見ていただき、ありがとうございました。
- 投稿日:2019-08-20T13:45:47+09:00
tensorflowによる画像スタイル変換
はじめに
画像のスタイル変換について学んだので、体験することを目的として詳しい部分には触れず、とりあえず誰にでも画像のスタイル変換の体験ができるように書いてみた。
準備
今回試すことは、画像のスタイル変換で、自分の好きな画像を他の画像の画風に変換する、といったような感じにすることができる。
なので、今回はりんごの写真をスタイル変換してみることにした。綺麗なりんごだ。
これがどんな感じになるんだろうか。まずGithub上から
lengstrom/fast-style-transfer
をダウンロードする。Github
https://github.com/lengstrom/fast-style-transfer$ git clone https://github.com/lengstrom/fast-style-transfer.gitそして、先程ダウンロードした
fast-style-transfer
ディレクトリにまで移動する。$ cd fast-style-transferこの中の
evaluate.py
に画像のスタイル変換のコードが書かれている。
このファイルを実行すれば画像のスタイル変換がなされるのだが、まずは画像と変換したいスタイル様式の準備が必要だ。
今回はりんごの画像を、
葛飾北斎の
と、
のようなスタイルに変化してみたいと思う。
これら2つのスタイルに変換するためのファイルを以下のリンクから取得し、
fast-style-transfer
ディレクトリ以下におく。
今回の2つのスタイルのファイルはwave.ckpt
とla-muse.ckpt
であるので、それらのファイルを以下のリンクから取得した。Google drive
https://drive.google.com/drive/folders/0B9jhaT37ydSyRk9UX0wwX3BpMzQ一応、確認。
$ ls README.md la_muse.ckpt style.py apple.jpg transform_video.py docs.md wave.ckpt evaluate.py setup.sh examples srcこれで準備完了。
実行
実行するときのコマンドは以下を参照にしてほしい。
$ python evaluate.py --checkpoint ./参照ファイル名 --in-path 選択画像名 --out-path ./出力画像名もし、
ImportError: No module named 'moviepy'というエラーが出ましたら、
evaluate.py
ファイルの13,14行目の部分を# from moviepy.video.io.VideoFileClip import VideoFileClip # import moviepy.video.io.ffmpeg_writer as ffmpeg_writerのようにコメントアウトしてください。
まずは葛飾北斎の画像のスタイルのりんごの画像を作成してみる。
出力画像の名前はoutput1.jpg
とした。$ python evaluate.py --checkpoint ./wave.ckpt --in-path apple.jpg --out-path ./output1.jpgこのコマンドの実行が完了したら、同ディレクトリに
output1.jpg
ファイルが作成されている。
その結果がこれ。
おお、ぽい!!って感じ笑
次はピカソの画像のスタイルのりんごの画像を作成してみる。
出力画像の名前はoutput2.jpg
とした。$ python evaluate.py --checkpoint ./la_muse.ckpt --in-path apple.jpg --out-path ./output2.jpgおおお、なんかいい感じ!!
終わりに
画像のスタイル変換についてわからない部分は多いながらも、体験はできたかと思います。
示しましたGoogleDriveのリンクには他のスタイルのファイルも存在していますので、ぜひそちらでも試してみてください。今回は試すことをメインにしたので、どうして出力されるのかが気になる方は
evaluate.py
の中のコードを読んだり、他の説明されているサイトなどを参考にしてください。最後まで見ていただき、ありがとうございました。
- 投稿日:2019-08-20T12:11:29+09:00
【論文読み】ディープラーニング×ガウス過程
カーネル主成分分析やSVM、ガウス過程で使われる「カーネル」は試行錯誤で
選定する必要があり、黒魔術的な要素を持っています。今回ご紹介する論文は、ディープラーニングにカーネルの選定を任せてしまう
非常に意欲的な研究です。※本稿の図は論文(Differentiable Compositional Kernel Learning for Gaussian Processes)より引用しています。
論文の概要
- ガウス過程で使われる「カーネルの選定」をディープラーニングに任せてしまう
- 全体は微分可能な形式となっており、勾配法で学習が可能
- 「回帰」、「ベイズ最適化」、「テクスチャーの補完」のタスクで良い成果を上げた
カーネルとは?
カーネルは、一般的に入力を非線形な空間へ飛ばし、そこでゴニョニョすることにより
線形分離不可能なデータであっても、うまく処理できる非常に便利なものです。しかし、カーネルには種類があり、そして組み合わせも多くあり、どのように設計
すべきか非常に頭を悩ませます。Neural Kernel Network
論文では、「ガウス過程」におけるカーネルの選定をディープラーニングに
任せてしまうことにより、最適な組み合わせを選ばせています。
これをNeural Kernel Network(NKN)と呼んでいます。ガウス過程で使われるカーネルは、基本的に二つの入力に対し似ているもの
同士であれば小さい出力を、似ていないもの同士だと大きな出力を出します。上の図は、ニューラルネットワークによるカーネル選定の様子を示しています。
$x_1$、$x_2$は入力を意味しています。二層目では多項式カーネルやガウスカーネルなど
を用意しておきます。三層目ではそれらに重みを付けて和をとります。四層目では
それらの積をとっています。ご覧のようにカーネル自体をニューラルネットワークに
埋め込んでいます。そして、カーネルのパラメータもニューラルネットワークのパラメータとして
学習させています。これにより人間はカーネルの設計や重みづけに悩まされることなく、ガウス過程を
使うことができます。要は楽になります。実験結果
- 回帰
まずは、回帰の結果です。
上の図は航空機の利用客数の推移をガウス過程で学習させたものです。
赤い点は学習データ、予測値は黒い線、グレーの領域は$1\sigma$です。ご覧のように、ヒューリスティックな手法よりNKNの方が良くフィッティング
しています。1961年以降、実測値はありませんが、恐らくNKNの方が誤差が
小さいと思われます。上の表は、種類の違う回帰のデータセットに対し、テストデータのRMSEを
計算したものです。他の手法に比べ、NKNの方がRMSEが小さくなっており、
より高精度に予測できているのが分かります。
- ベイズ最適化
次にベイズ最適化です。ベイズ最適化はよく油田の探索に使われています。
油田を探索するために穴を掘る必要がありますが、1つの穴を掘るのに莫大な
コストがかかるため、なるべく掘る回数を少なくしたいです。そのために、
堀った穴の状況から石油の分布を推測して、石油が一番多く埋蔵されている
場所を逐一予測する必要があります。そのときに使われるのがベイズ最適化です。つまり、ベイズ最適化とは「できるだけ少ない試行回数で最適値を見つける」
タスクです。上の図は、異なるデータセット(a),(b),(c)でベイズ最適化を適用したものです。
横軸は試行回数、縦軸は評価関数の値です。評価関数の値をできるだけ小さくする
タスクだと思われます。NKNは、Oracleには劣るものの、一般的に使われるRBF(ガウスカーネル)より
少ない試行回数で小さい値を見つけ出しています。
- テクスチャーの補完
最後にテクスチャーの補完です。
上の図で一番左(a)は学習データを意味し、真ん中がマスクされた図となっています。
(b)の図は正解を表しています。ご覧のとおり、NKNが一番うまく再現できています。
個人的には、これが一番の驚きでした。上の図も意味は同じです。
RBFだと歯が立たなかったものが、NKNだとうまく再現できています。実装
著者によるTensorFlowの実装が公開されています。
感想
- NKNはカーネルの設計をお任せできるが、基礎となるカーネルは設計者の手で選ばなければならない。
- 回帰やベイズ最適化で良い性能を示しており、カーネル選定で迷ったらNKNという使い方もアリだと思われる。
- テクスチャーの補完はかなり高精度で、非CNN(畳み込みニューラルネットワーク)ベースを考慮すると、かなりの成果だと思われる。
実はテクスチャーの補完はある応用が可能で、私の方でコードを改造して
既に良い成果も出ています。その報告は次回させていただきます。
- 投稿日:2019-08-20T10:44:09+09:00
Donkey Carを組み立てる前にシミュレーターで楽しんでみる Donkey Car 3.1.0編
はじめに
Donkey Carは市販のラジコンカーを改造して自律走行させることができるプラットフォームです。RCカーの改造の仕方や機械学習アルゴリズムがオープンソースで公開されいるので誰でも組み立てることができ、各地で走行会やレースが開催されています。
先日のMaker Faire Tokyo 2019でもAIでRCカーを走らせよう!コミュニティの主宰で走行会&レースが開催されました。
Donkey CarはRCカーにRaspberry Piなどの小型コンピュータとカメラを搭載してカメラで撮影した画像を機械学習アルゴリズムで認識し、RCカーのステアリングやスロットルをコンピュータ制御する仕組みになっています。搭載するコンピュータはRaspberry Pi 3b+の他、バージョン3.0.2からNVIDIA Jetson Nanoに対応しました。
この記事では、Donkey Carに関心を持たれた方々向けに、Donkey Simulatorを使ってPC上でDonkey Carのテスト走行、教師あり学習、自律走行をシミュレーションで楽しむ方法をご紹介します。
RCカーを購入する前にDonkey Carで必要な作業がどんなものなのか、どういったところが楽しいのかを体感していただければ幸いです。
以下、Macによる手順をご紹介しますがWindowsでもほぼ同じ手順で実現可能です。Windowsでセットアップする場合は、Anaconda NavigatorをインストールしてPythonの仮想環境を作成してから下記を実行すると便利だと思います。
Donkey Simulatorをインストールする
シミュレーターをインストールします。各プラットフォーム毎に用意されています
https://github.com/tawnkramer/gym-donkeycar/releasesMacの場合、最新版をダウンロードしてzipで展開すると
DonkeySimMac
が作成されます。その中にあるdonkey_sim
を/Application
にドラッグ&ドロップします。このようにセットアップしたDonkey Simulatorのロードモジュールのパスは以下のようになります/Applications/donkey_sim.app/Contents/MacOS/donkey_simこれのパスはあとで大事になるので覚えておいてください
DonkeyCarをセットアップする
Install Donkeycar on Macに従ってPCにdonkeycarをセットアップします。Donkey Carは現時点の最新バージョン
3.1.0
です。Donkey Carの開発は活発なのでバージョンアップされてセットアップ方法が変更される可能性があります以下の環境を想定しています
Mac 10.14.5
Python 3.7.3まず適当なディレクトリを作成します。ディレクトリ名はなんでもいいですが、ここでは
donkeypjtest
としましたmkdir ./donkeypjtest cd donkeypjtesttensorflowをインストールします。tensorflowでGPUが利用可能な環境であれば
tensorflow-gpu
をインストールすると高速に学習できますpip install tensorflowdonkeycarのレポジトリをcloneしてセットアップします
git clone -b master https://github.com/autorope/donkeycar pip install -e donkeycar上記が正常にセットアップできると、
donkey
コマンドが使えるようになりますgym-donkeycarをセットアップします
git clone https://github.com/tawnkramer/gym-donkeycar pip install -e gym-donkeycarプロジェクトを作成する
donkey createcar
コマンドでプロジェクトを作成しますdonkey createcar --path ./mycar cd ./mycarプロジェクトが作成されると以下のようなディレクトリ構成になります
tree . . ├── config.py ├── data ├── logs ├── manage.py ├── models ├── myconfig.py └── train.py 3 directories, 4 filesキモは
manage.py
です。基本的にpython manage.py train
とpython manage.py drive
という2つのコマンドを使いますpython manage.py using donkey v3.1.0 ... Usage: manage.py (drive) [--model=<model>] [--js] [--type=(linear|categorical|rnn|imu|behavior|3d|localizer|latent)] [--camera=(single|stereo)] [--meta=<key:value> ...] manage.py (train) [--tub=<tub1,tub2,..tubn>] [--file=<file> ...] (--model=<model>) [--transfer=<model>] [--type=(linear|categorical|rnn|imu|behavior|3d|localizer)] [--continuous] [--aug]
myconfig.py
を編集します。このプロジェクトをDonkey Simulatorに対応させるための設定を行います。200行目あたりの以下の3行のコメントを外し、DONKEY_GYM
をTrue
にDONKEY_SIM_PATH
にDonkey Simulatorのパスを設定しますmyconfig.pyDONKEY_GYM = True DONKEY_SIM_PATH = "/Applications/donkey_sim.app/Contents/MacOS/donkey_sim" DONKEY_GYM_ENV_NAME = "donkey-generated-track-v0"以上でDonkey Carシミュレーターで遊ぶ準備ができました
シミュレーターを起動してテスト走行する
donkey_sim
を起動します。コンフィギュレーションダイアログが表示されるのでScreen resolution
を800 x 600
、Windowed
にチェックを入れてPlay!
ボタンをクリックします。
Screen resolution
はなんでもよいです。ディスプレイの解像度に合わせて指定してください。Windowed
のチェックを外すとフルスクリーンモードで起動しますシミュレーターには複数のコースが用意されています。今回は左下の
Generated Track
をクリックします
Joystick/Keyboard No Rec
をクリックするとコースを走ることができます。キーボードのカーソルキー↑
で前進←
で左折→
で右折↓
でバックです。Stop
ボタンをクリックするとコースを終了しますしばらく走行してみて感覚をつかんでください。このあと教師用データを集めるために走行しますが、その前にキーボードでの走行に慣れておくほうがよいです
教師用データを取得する
Donkey Car実機では教師データを取得するために手動で記録用走行を行います。このとき前方に搭載したカメラで走行中の景色を画像データとして記録し、同時に手動操作されたスロットルやハンドルの操作履歴を記録してそれを教師用データとします。シミュレーターでは記録モードで走行することで実機と同様のデータを取得することができます。
まず、テスト走行のときと同様に
donkey_sim
を起動します。コースを選択する前に画面右下のLog dir
ボタンをクリックしますダイアログで先ほど作成した
mycar
プロジェクトのlogs
フォルダを指定してSelect
ボタンをクリックします
Generated Track
を選択します
Joystick/Keyboard w Rec
ボタンをクリックして記録用走行を開始しますカーソルキーでDonkey Carを操作してコースを周回します。左下の
Log:
の表示で記録されている画像の枚数を確認できます。だいたい5000〜10000くらいを目安にしてください。十分に記録ができたら右上のStop
ボタンでシミュレーターを終了します記録走行が終了すると、
./mycar/logs
に教師用データが保存されますカメラ画像は160x120ピクセルのJPEG画像で保存されています
スロットルやハンドル確度などの情報がJSONで保存されています。保存されているJSONデータは改行されていませんが、下記は読みやすさのため改行して記載しています
record_550.json{ "cam/image_array":"550_cam-image_array_.jpg", "user/throttle":10.0, "user/angle":0.0, "user/mode":"user", "track/lap":0, "track/loc":10 }学習する
mycar
プロジェクト下で以下のコマンドを実行して学習を開始します。ここで--type=categorical
の指定は大事なので覚えておいてくださいpython manage.py train --tub=logs --model=models/mypilot.h5 --type=categorical以下のように
./mycar/logs
のデータでモデルを訓練していきます。訓練が終わるまで待ちますusing donkey v3.1.0 ... loading config file: /src/work/donkeypjtest/mycar/config.py loading personal config over-rides config loaded ...中略... found 0 pickles writing json records and images in tub logs logs collating 7538 records ... train: 6030, val: 1508 total records: 7538 steps_per_epoch 47 Epoch 1/100 46/47 [============================>.] - ETA: 1s - loss: 2.0719 - angle_out_loss: 1.9795 - throttle_out_loss: 1.0821 - angle_out_acc: 0.4536 - throttle_out_acc: 0.5652 Epoch 00001: val_loss improved from inf to 1.35425, saving model to models/mypilot.h5 47/47 [==============================] - 83s 2s/step - loss: 2.0617 - angle_out_loss: 1.9745 - throttle_out_loss: 1.0744 - angle_out_acc: 0.4543 - throttle_out_acc: 0.5662 - val_loss: 1.3542 - val_angle_out_loss: 1.4905 - val_throttle_out_loss: 0.6090 - val_angle_out_acc: 0.5817 - val_throttle_out_acc: 0.6513 Epoch 2/100 17/47 [=========>....................] - ETA: 47s - loss: 1.3048 - angle_out_loss: 1.5335 - throttle_out_loss: 0.5380 - angle_out_acc: 0.6140 - throttle_out_acc: 0.7403学習が終わると
./mycar/models
下にmypilot.h5
という学習済モデルが出来上がります自律走行してみる
以下のコマンドを実行します。
--type=categorical
に注意してください。--type
で指定するモデルタイプは上記のpython manage.py train
のときに指定したものと必ず一致させる必要がありますpython manage.py drive --type=categorical --model=models/mypilot.h5上記を実行するとDonkey Simulatorのコンフィギュレーションダイアログが起動するのでテスト走行のときのように設定して
Play!
をクリックしますシミュレーターが起動してしばらくするとターミナルに以下のようなログが表示されます
finished loading in 3.236067771911621 sec. Adding part FileWatcher. Adding part FileWatcher. Adding part DelayedTrigger. Adding part TriggeredCallback. Adding part KerasCategorical. Adding part DriveMode. Adding part AiLaunch. Adding part AiRunCondition. Tub does NOT exist. Creating new tub... New tub created at: /src/work/donkeypjtest/mycar/data/tub_1_19-08-19 Adding part TubWriter. You can now go to <your pi ip address>:8887 to drive your car. Starting vehicle... 8887
Starting vehicle...
と表示されたら、Webブラウザで http://localhost:8887 にアクセスします。すると以下のようなDonkey Monitor
が表示されます画面右下のMode & Pilotのセレクトボックスから
Local Pilot(d)
を選択すると、シミュレーター上のDonkey Carが動き出しますこれが先ほど訓練した学習済みモデル
mypilot.h5
を使った自律運転です。学習が不十分だった場合はコースをうまく認識せずにコースアウトしたり、スタート地点から動かないことすらあります。そういったときには右下のExit
ボタンをクリックするとコースが再起動して運転を再開します同じ学習済みモデルを使っていても一回ごとにDonkey Carの運転は微妙に違いますし、新たに訓練したモデルを使うとまた違う動きをします。記録する画像の量も多ければよい運転をするとも限りません。記録走行での運転方法や画像の枚数などをいろいろ変えてみてうまくコースが周回できるモデルを作成してみてください
記録、訓練、自律走行までの一連の流れはDonkey Car実機でもほぼ同じです。シミュレーターでこの流れに慣れておけば実機を組み立てた際にスムーズに走らせることができるでしょう
シミュレーターの遊び方は以上です
遊ぶ上でのポイントとかコツとか
上記に慣れた後に楽しめるポイントやぼくが経験上得たコツなんかを記載しておきます。参考になれば幸いです
Colabで高速に訓練する
FaBo DonkeyCar Docs Colabでの学習(GPU)でGoogle ColabのGPUを利用できるnotebookが公開されています。このnotebookを使うとGPUを使って高速に訓練することができるので大変便利です。教師用画像データが5000枚程度であればだいたい5分くらいで訓練が完了します。使い方はNotebookに丁寧に記載されているので、記載どおりにすすめていけばよいです
いろんなモデルを訓練してみる
python manage.py train --type=[MODEL TYPE]
で指定できるモデルタイプは複数あり、様々なアルゴリズムを使って訓練させることができます。現時点でサポートされているタイプはlinear|categorical|rnn|imu|behavior|3d|localizer
となっています。各モデルの解説は公式ドキュメントのKerasに記載されています同じ教師データでも異なるモデルを訓練させることで全く違う運転をするのでいろいろ試してみてください。経験上
categorical
が安定している気がしています上述のとおり、訓練時と運転時の
--type
指定は同一にする必要があります。異なるモデルを指定した場合はpython manage.py drive
でのモデル読み込み時にエラーが発生します何度訓練してもスタート地点から発進しない場合
何度記録と訓練を繰り返してもDonkey Carがスタート地点から発進しないことがありました。いろいろ試したところ記録運転で周回しているときにスタート地点周辺でスロットルをオン(
↑
キー)していると発進しやすくなりました検証したわけではないですが、おそらくスタート地点周辺でスロットルを開けてるデータを学習させないと発進時にスロットルを開ける確率が低下してしまうためではないかと思います。スタート地点前後では
↑
キーを押し続けるとか小刻みに連打するなどして周辺でスロットルを開けることを教えてやるといいでしょうおわりに
Donkey Carでシミュレーターを使った遊び方を紹介しました。Donkey Carは実機を組み立てて現実世界のコースで走らせるのがなんといっても楽しいですが、組み立てる前の練習や訓練方法を試行するときなどにシミュレーターが役に立ちますし、シミュレーターであれこれ試行錯誤するだけでもなかなか楽しいものです。
Donkey Carに興味を持たれた方はぜひシミュレーターを使ってDonkey Carに対する知識を深めてください!