20190820のTensorFlowに関する記事は6件です。

【Tensorflow-gpu 1.x系】複数のモデルを一つのプログラムで実行する

はじめに

以下のように複数のモデル(手検出モデルと指先検出モデル)を1つのプログラムで実行しようとした時にGPUメモリで躓いたためメモ。
以下のプログラムでは手検出で1つ、指先検出で1つの計2つのモデルを利用しています。

躓いたポイント

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()

以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「スタバなう」ツイートをニューラルネットで学習してラーメン判定器を作る(keras+Tensorflow+VGG16)

この記事の抜粋したコードの完全版はGitHubでご覧いただけます。
また、この記事で作成したモデルはTwitterのスタバ警察botで実際に試せるので、ご興味があれば適当な画像を「スタバなう」という文字列と一緒にリプライしてみてください。


というツイートからも分かるように、現在のスタバなうツイートは完全に関係ない画像で蹂躙されており、実際にスタバで撮影された画像は全体の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ディレクトリにはこんな感じの画像が入っています。

ramen.jpg

これらの画像のうち、数十枚は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を作成します。
ImageDataGeneratorvalidation_splitvalidation_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.ModelCheckpointcallbacksに設定することで、エポック毎にモデルを保存することができます。save_best_only=Trueにしておくと、指定した値(ここではval_loss)が改善した時にだけ保存するようになります。

評価

test用generatorの生成はtrain/validationの時とだいたい同じですが、batch_sizeは1、 shuffleFalseにしておきます。

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枚あります。対象の画像を含むツイートは以下です。

今回は、つけ麺もラーメン画像として含めていたので、「器が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で実際に使っています。どれぐらいちゃんとラーメン(やスタバ)を判定できるのかご興味があれば試してみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tensorflowによる画像スタイル変換の体験

はじめに

画像のスタイル変換について学んだので、体験することを目的として詳しい部分には触れず、とりあえず誰にでも画像のスタイル変換の体験ができるように書いてみた。

準備

今回試すことは、画像のスタイル変換で、自分の好きな画像を他の画像の画風に変換する、といったような感じにすることができる。
なので、今回はりんごの写真をスタイル変換してみることにした。

apple.jpg

綺麗なりんごだ。
これがどんな感じになるんだろうか。

まず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に画像のスタイル変換のコードが書かれている。
このファイルを実行すれば画像のスタイル変換がなされるのだが、まずは画像と変換したいスタイル様式の準備が必要だ。
今回はりんごの画像を、
葛飾北斎の
wave.jpg

と、

ピカソの
la_muse.jpg

のようなスタイルに変化してみたいと思う。

これら2つのスタイルに変換するためのファイルを以下のリンクから取得し、fast-style-transferディレクトリ以下におく。
今回の2つのスタイルのファイルはwave.ckptla-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ファイルが作成されている。
その結果がこれ。
output.jpg

おお、ぽい!!って感じ笑

次はピカソの画像のスタイルのりんごの画像を作成してみる。
出力画像の名前はoutput2.jpgとした。

$ python evaluate.py --checkpoint ./la_muse.ckpt  --in-path apple.jpg --out-path ./output2.jpg

結果がこれ。
output2.jpg

おおお、なんかいい感じ!!

終わりに

画像のスタイル変換についてわからない部分は多いながらも、体験はできたかと思います。
示しましたGoogleDriveのリンクには他のスタイルのファイルも存在していますので、ぜひそちらでも試してみてください。

今回は試すことをメインにしたので、どうして出力されるのかが気になる方はevaluate.pyの中のコードを読んだり、他の説明されているサイトなどを参考にしてください。

最後まで見ていただき、ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tensorflowによる画像スタイル変換

はじめに

画像のスタイル変換について学んだので、体験することを目的として詳しい部分には触れず、とりあえず誰にでも画像のスタイル変換の体験ができるように書いてみた。

準備

今回試すことは、画像のスタイル変換で、自分の好きな画像を他の画像の画風に変換する、といったような感じにすることができる。
なので、今回はりんごの写真をスタイル変換してみることにした。

apple.jpg

綺麗なりんごだ。
これがどんな感じになるんだろうか。

まず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に画像のスタイル変換のコードが書かれている。
このファイルを実行すれば画像のスタイル変換がなされるのだが、まずは画像と変換したいスタイル様式の準備が必要だ。
今回はりんごの画像を、
葛飾北斎の
wave.jpg

と、

ピカソの
la_muse.jpg

のようなスタイルに変化してみたいと思う。

これら2つのスタイルに変換するためのファイルを以下のリンクから取得し、fast-style-transferディレクトリ以下におく。
今回の2つのスタイルのファイルはwave.ckptla-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ファイルが作成されている。
その結果がこれ。
output.jpg

おお、ぽい!!って感じ笑

次はピカソの画像のスタイルのりんごの画像を作成してみる。
出力画像の名前はoutput2.jpgとした。

$ python evaluate.py --checkpoint ./la_muse.ckpt  --in-path apple.jpg --out-path ./output2.jpg

結果がこれ。
output2.jpg

おおお、なんかいい感じ!!

終わりに

画像のスタイル変換についてわからない部分は多いながらも、体験はできたかと思います。
示しましたGoogleDriveのリンクには他のスタイルのファイルも存在していますので、ぜひそちらでも試してみてください。

今回は試すことをメインにしたので、どうして出力されるのかが気になる方はevaluate.pyの中のコードを読んだり、他の説明されているサイトなどを参考にしてください。

最後まで見ていただき、ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【論文読み】ディープラーニング×ガウス過程

カーネル主成分分析やSVM、ガウス過程で使われる「カーネル」は試行錯誤で
選定する必要があり、黒魔術的な要素を持っています。

今回ご紹介する論文は、ディープラーニングにカーネルの選定を任せてしまう
非常に意欲的な研究です。

※本稿の図は論文(Differentiable Compositional Kernel Learning for Gaussian Processes)より引用しています。

論文の概要

  • ガウス過程で使われる「カーネルの選定」をディープラーニングに任せてしまう
  • 全体は微分可能な形式となっており、勾配法で学習が可能
  • 「回帰」、「ベイズ最適化」、「テクスチャーの補完」のタスクで良い成果を上げた

カーネルとは?

カーネルは、一般的に入力を非線形な空間へ飛ばし、そこでゴニョニョすることにより
線形分離不可能なデータであっても、うまく処理できる非常に便利なものです。

しかし、カーネルには種類があり、そして組み合わせも多くあり、どのように設計
すべきか非常に頭を悩ませます。

Neural Kernel Network

論文では、「ガウス過程」におけるカーネルの選定をディープラーニングに
任せてしまうことにより、最適な組み合わせを選ばせています。
これをNeural Kernel Network(NKN)と呼んでいます。

ガウス過程で使われるカーネルは、基本的に二つの入力に対し似ているもの
同士であれば小さい出力を、似ていないもの同士だと大きな出力を出します。

image.png

上の図は、ニューラルネットワークによるカーネル選定の様子を示しています。

$x_1$、$x_2$は入力を意味しています。二層目では多項式カーネルやガウスカーネルなど
を用意しておきます。三層目ではそれらに重みを付けて和をとります。四層目では
それらの積をとっています。ご覧のようにカーネル自体をニューラルネットワークに
埋め込んでいます。

そして、カーネルのパラメータもニューラルネットワークのパラメータとして
学習させています。

これにより人間はカーネルの設計や重みづけに悩まされることなく、ガウス過程を
使うことができます。要は楽になります。

実験結果

  • 回帰

まずは、回帰の結果です。

image.png

上の図は航空機の利用客数の推移をガウス過程で学習させたものです。
赤い点は学習データ、予測値は黒い線、グレーの領域は$1\sigma$です。

ご覧のように、ヒューリスティックな手法よりNKNの方が良くフィッティング
しています。1961年以降、実測値はありませんが、恐らくNKNの方が誤差が
小さいと思われます。

image.png

上の表は、種類の違う回帰のデータセットに対し、テストデータのRMSEを
計算したものです。他の手法に比べ、NKNの方がRMSEが小さくなっており、
より高精度に予測できているのが分かります。

  • ベイズ最適化

次にベイズ最適化です。ベイズ最適化はよく油田の探索に使われています。

油田を探索するために穴を掘る必要がありますが、1つの穴を掘るのに莫大な
コストがかかるため、なるべく掘る回数を少なくしたいです。そのために、
堀った穴の状況から石油の分布を推測して、石油が一番多く埋蔵されている
場所を逐一予測する必要があります。そのときに使われるのがベイズ最適化です。

つまり、ベイズ最適化とは「できるだけ少ない試行回数で最適値を見つける」
タスクです。

image.png

上の図は、異なるデータセット(a),(b),(c)でベイズ最適化を適用したものです。
横軸は試行回数、縦軸は評価関数の値です。評価関数の値をできるだけ小さくする
タスクだと思われます。

NKNは、Oracleには劣るものの、一般的に使われるRBF(ガウスカーネル)より
少ない試行回数で小さい値を見つけ出しています。

  • テクスチャーの補完

最後にテクスチャーの補完です。

image.png

上の図で一番左(a)は学習データを意味し、真ん中がマスクされた図となっています。
(b)の図は正解を表しています。

ご覧のとおり、NKNが一番うまく再現できています。
個人的には、これが一番の驚きでした。

image.png

上の図も意味は同じです。
RBFだと歯が立たなかったものが、NKNだとうまく再現できています。

実装

著者によるTensorFlowの実装が公開されています。

感想

  • NKNはカーネルの設計をお任せできるが、基礎となるカーネルは設計者の手で選ばなければならない。
  • 回帰やベイズ最適化で良い性能を示しており、カーネル選定で迷ったらNKNという使い方もアリだと思われる。
  • テクスチャーの補完はかなり高精度で、非CNN(畳み込みニューラルネットワーク)ベースを考慮すると、かなりの成果だと思われる。

実はテクスチャーの補完はある応用が可能で、私の方でコードを改造して
既に良い成果も出ています。その報告は次回させていただきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Donkey Carを組み立てる前にシミュレーターで楽しんでみる Donkey Car 3.1.0編

はじめに

Donkey Carは市販のラジコンカーを改造して自律走行させることができるプラットフォームです。RCカーの改造の仕方や機械学習アルゴリズムがオープンソースで公開されいるので誰でも組み立てることができ、各地で走行会やレースが開催されています。

AIカーが来てる! 自動運転でラジコンカーを走らせよう!

先日の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をインストールする

donkey_sim_icon.png

シミュレーターをインストールします。各プラットフォーム毎に用意されています
https://github.com/tawnkramer/gym-donkeycar/releases

Macの場合、最新版をダウンロードして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 donkeypjtest

tensorflowをインストールします。tensorflowでGPUが利用可能な環境であればtensorflow-gpuをインストールすると高速に学習できます

pip install tensorflow

donkeycarのレポジトリを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 trainpython 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_GYMTrueDONKEY_SIM_PATHにDonkey Simulatorのパスを設定します

myconfig.py
DONKEY_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 resolution800 x 600Windowedにチェックを入れてPlay!ボタンをクリックします。

Screen resolutionはなんでもよいです。ディスプレイの解像度に合わせて指定してください。Windowedのチェックを外すとフルスクリーンモードで起動します

スクリーンショット 2019-08-19 11.43.26.png

シミュレーターには複数のコースが用意されています。今回は左下のGenerated Trackをクリックします

スクリーンショット 2019-08-19 11.46.30.png

Joystick/Keyboard No Recをクリックするとコースを走ることができます。キーボードのカーソルキーで前進で左折で右折でバックです。Stopボタンをクリックするとコースを終了します

スクリーンショット 2019-08-19 11.52.46.png

しばらく走行してみて感覚をつかんでください。このあと教師用データを集めるために走行しますが、その前にキーボードでの走行に慣れておくほうがよいです

教師用データを取得する

Donkey Car実機では教師データを取得するために手動で記録用走行を行います。このとき前方に搭載したカメラで走行中の景色を画像データとして記録し、同時に手動操作されたスロットルやハンドルの操作履歴を記録してそれを教師用データとします。シミュレーターでは記録モードで走行することで実機と同様のデータを取得することができます。

まず、テスト走行のときと同様にdonkey_simを起動します。コースを選択する前に画面右下のLog dirボタンをクリックします

スクリーンショット 2019-08-19 16.13.25.png

ダイアログで先ほど作成したmycarプロジェクトのlogsフォルダを指定してSelectボタンをクリックします

スクリーンショット 2019-08-19 16.18.44.png

Generated Trackを選択します

スクリーンショット 2019-08-19 11.46.30.png

Joystick/Keyboard w Recボタンをクリックして記録用走行を開始します

スクリーンショット 2019-08-19 16.22.26.png

カーソルキーでDonkey Carを操作してコースを周回します。左下のLog:の表示で記録されている画像の枚数を確認できます。だいたい5000〜10000くらいを目安にしてください。十分に記録ができたら右上のStopボタンでシミュレーターを終了します

スクリーンショット 2019-08-19 16.24.43.png

記録走行が終了すると、./mycar/logsに教師用データが保存されます

スクリーンショット 2019-08-19 16.31.15.png

カメラ画像は160x120ピクセルのJPEG画像で保存されています

550_cam-image_array_.jpg

スロットルやハンドル確度などの情報が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という学習済モデルが出来上がります

スクリーンショット 2019-08-19 17.12.00.png

自律走行してみる

以下のコマンドを実行します。--type=categoricalに注意してください。--typeで指定するモデルタイプは上記のpython manage.py trainのときに指定したものと必ず一致させる必要があります

python manage.py drive --type=categorical --model=models/mypilot.h5 

上記を実行するとDonkey Simulatorのコンフィギュレーションダイアログが起動するのでテスト走行のときのように設定してPlay!をクリックします

スクリーンショット 2019-08-19 11.43.26.png

シミュレーターが起動してしばらくするとターミナルに以下のようなログが表示されます

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が表示されます

スクリーンショット 2019-08-19 22.40.40.png

画面右下のMode & PilotのセレクトボックスからLocal Pilot(d)を選択すると、シミュレーター上のDonkey Carが動き出します

スクリーンショット 2019-08-19 22.46.08.png

これが先ほど訓練した学習済みモデル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がスタート地点から発進しないことがありました。いろいろ試したところ記録運転で周回しているときにスタート地点周辺でスロットルをオン(キー)していると発進しやすくなりました

検証したわけではないですが、おそらくスタート地点周辺でスロットルを開けてるデータを学習させないと発進時にスロットルを開ける確率が低下してしまうためではないかと思います。スタート地点前後ではキーを押し続けるとか小刻みに連打するなどして周辺でスロットルを開けることを教えてやるといいでしょう

スクリーンショット 2019-08-19 16.25.36.png

おわりに

Donkey Carでシミュレーターを使った遊び方を紹介しました。Donkey Carは実機を組み立てて現実世界のコースで走らせるのがなんといっても楽しいですが、組み立てる前の練習や訓練方法を試行するときなどにシミュレーターが役に立ちますし、シミュレーターであれこれ試行錯誤するだけでもなかなか楽しいものです。

Donkey Carに興味を持たれた方はぜひシミュレーターを使ってDonkey Carに対する知識を深めてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む