WonderPlanet Tech Blog

アドバンストテクノロジー部やCTO室のメンバーを中心に最新の技術情報を発信しています。

Alexa for Business でスキルを有効化する方法。Zoom for Alexaを試してみた。

こんにちはアドバンストテクノロジー部の@y-matsushitaです。
今回は先日発表されたAlexa for Businessのスキルを試しに有効化するまで触ってみたので、ご紹介します。
有効化してみたスキルはオンライン会議サービスであるZoomのAlexa for Business用スキルです。
こちらのスキルを使って声で通話を始めるスマート会議の構築にチャレンジしてみます。
Zoom for Alexa以外のスキルを有効化する場合にも参考になるかと思います。

Zoomについては紹介記事を投稿しているので、ご興味があればご覧ください。 tech.wonderpla.net

Alexa for Businessとは

Alexaを搭載するスマートスピーカーを一元管理したり、自社専用の「プライベートスキル」を開発できます。
Alexa for Business用に公開されているスキルもあり、社内で活用できる様々なスキルが利用できます。

aws.amazon.com

注意点

現時点(2018/1/15)でAlexa for Businessは日本語未対応です。
端末をAlexa for Businessに登録すると端末が英語版になってしまうため、日本語での操作が一切できなくなります。
元に戻すことは可能ですが、端末を再セットアップする必要があります。

必要なもの

・Amazon Echo(Alexa対応端末)
・AWSアカウント
・Windows機 *1
 Microsoft Windows 7 or later, the AWS Tools for Windows PowerShell, and the .NET Framework 3.5 or later.)
・Zoom Roomsアカウント*2

手順

手順としては以下のように進めていきます。

端末登録
Alexa for Businessでの設定
 ・ Room profileの作成
 ・ Skill groupの作成
 ・ Roomの作成
 ・ Skillの追加と「Zoom for Alexa」の有効化
 ・ Zoom for AlexaをSkill groupに登録
 ・ Zoom for AlexaとZoomアカウントの連携
 ・ Provider設定にZoomを追加
設定内容の確認
Zoom for Alexaを使う


端末登録

それでは早速はじめていきます。
まずはAlexa for BusinessにEchoを端末登録します。
AWSのAlexa for Businessを開き、Shared devicesから「Set up devices」をクリックします。
Alexa for Businessを利用するにはリージョンをバージニア北部に設定しなければいけない点に注意してください。 Alexa for Business Shared devices

セットアップ用のexeのダウンロードリンクが出るのでクリックしてダウンロードします。
f:id:y-matsushita:20171222122541j:plain:w500

ダウンロードしたexeを実行します。当然ですが実行はWindows環境で行います。
f:id:y-matsushita:20171222122743j

もし以下のような画面が出てたらインターネット回線が繋がっているか確認しましょう。
An Error occurred attempting to install DeviceSetupTool.

Access key IDとSecret access keyを入力します。
入力するKeyはAWSのIAMロールで発行します。
Alexa for Businessへのアクセス権限とプログラムによるアクセス許可をしておきましょう。 Setup AWS security credentials

認証が成功したらEchoとセットアップ用のPCをWi-Fiで接続します。
Select Alexa-enabled devices

この状態でEchoをセットアップモードにすると、EchoとDevice Setup Toolを接続させることができます。
セットアップモードにするには下図のボタンをオレンジ色が点灯するまで長押しします。
Echoのセットアップ

上手く検出できるとDevice Setup ToolにAmazon-◯◯◯という名前でデバイスが表示されます。 Echoのセットアップ

Echoで使用するWi-Fiの接続情報を入力します。 f:id:y-matsushita:20171222143318p

設定内容に問題がなければStatusがSuccessに変わります。
ここまででEchoとAlexa for Businessの連携は完了です。
f:id:y-matsushita:20171222145212j:plain:w700


Alexa for Businessでの設定

ここからはAWS上での操作になります。
Alexa for Business側を開いてEcho Deviceが新たに追加されていることを確認しましょう。
f:id:y-matsushita:20171222145738p:plain:w700

ここからはDeviceにSkillを追加する手順を記載します。
DeviceはRoomごとに異なるSkillを設定可能です。
必要な作業は以下の通りです。設定内容は後から変更可能です。

・Room profileとSkill groupを作成。
・Skillを有効化してSkill groupに追加。
・Roomを作成してRoom profileとSkill groupとDeviceを設定。
・Providerの追加。(Zoom for Alexa用)

Room profileの作成

まずはRoom profileとSkill groupを作成しましょう。
画面左のConfigurationからRoom profilesを開き、「Create room profile」をクリックします。
f:id:y-matsushita:20171222152213j:plain:w700

「Create room profile」で設定内容を記入します。
f:id:y-matsushita:20171222152346j:plain:w700
・Profile name : Room profile名
・Address : 所在地
・Time zone : タイムゾーン
・Wake word : ウェイクアップワード(Alexa, Amazon, Echo, Computerから選択)
・Temperature units : 摂氏・華氏の選択
・Distance units : フィート・メートルの選択
・Maximum volume : デバイス音量の最大値(6~10)
・Device setup mode : セットアップモードの有効化。Offの場合、セットアップモードにならなくなる
・Outbound calling : 電話の発信の有効化

Skill groupの作成

次はスキルを管理するためのSkill groupを作成します。
あとで「Zoom for Alexa」のスキルを有効化してグループ化させるためのものです。
「Create skill group」をクリックしましょう。
Alexa for Business Skill groupの作成

Skill groupの名前を入力するだけで、ひとまずの作成は完了です。
f:id:y-matsushita:20171222155208j:plain:w500

Roomの作成

画面左上のRoomsから「Create room」をクリックしてRoomを作成します。
Alexa for Business Roomの作成

Room名と先ほど作成したRoom profileを設定します。
Alexa for Business Room名の設定

Roomに先ほど端末登録したEchoを追加します。
端末登録したEchoを設定

Roomに先ほど作成したSkill groupを設定します。 Skill groupを設定

問題なければ作成完了です。
Roomの作成完了


Skillの追加と「Zoom for Alexa」の有効化

画面左のSkillsをクリックします。
Alexa for Business スキルの設定

Alexa skills storeタブを開いて「zoom」で検索しましょう。
Zoom for Alexa がヒットしたら Enable をクリックします。
Zoom for Alexaを有効化

Zoom RoomsアカウントでログインしてZoom for Alexa を有効化します。
Zoomアカウントと連携

問題なく有効化できればEnable skillsにZoom for Alexa が現れます。
Zoom for Alexaが追加される

Zoom for AlexaをSkill groupに登録

Skill groupsから先ほど作成したSkill groupを選択します。
Zoom for AlexaをSkill groupに登録

Add skillsからSkill groupにZoom for Alexaを追加します。
Zoom for Alexaを追加

追加されると以下のようにSkill groupに追加されます。
Zoom for Alexaを追加完了

Zoom for AlexaとZoomアカウントの連携

SkillsのZoom for AlexaをクリックしてZoom for Alexaの詳細設定を確認します。
「Link account to this room」が未設定になっている場合は、設定を行います。
Zoom for Alexaとアカウント連携

Zoom Roomsアカウントでログインして連携します。
Zoom for Alexaとアカウント連携

上手くいけばボタンの表示が「Link master account」になります。
ここでの連携は結構失敗しやすいですが、失敗したときの履歴が残っていると連続で失敗してしまうため、
上手くいかない場合は以下のようなことを試すと上手くいくかもしれません。

・一旦ブラウザでZoomのサイトへ行ってログアウトしてから管理者のZoom roomsアカウントで試す。
・ブラウザのシークレットウィンドウで試す。
・Configurationに適当なテキストを設定する。

Zoom for Alexa設定完了

Provider設定にZoomを追加

画面左のConferencingを開き、「Add provider」をクリックします。
ProviderにZoomを追加

Providerの設定情報を入力します。特に設定を変更しなくても動作しますが、
ミーティングスタート時にMeeting PINの要求が必要であれば、OptionalかRequiredに変更します。
ZoomのProvider設定

追加が完了すると以下のようにProviderとしてZoomが表示されます。
ZoomProvider追加完了


設定内容の確認

大変長くなりましたが、以上で設定は完了です。
確認として以下の点が問題なければ設定は大丈夫です。

・RoomにDeviceが設定されているか。
・RoomにRoom profileとZoom for Alexa スキルの入ったSkill groupが設定されているか。
・Zoom for AlexaスキルとZoom Roomsアカウントが連携できているか。
・ProviderにZoomが追加されているか。

あとは最後に重要な点ですが、Alexa for Businessのスキルを有効化するために、Echoを再起動しましょう。
どうやらスキルの追加をした後に再起動をしないと、有効化されないようなので注意が必要です。
(私はここに気付かず、何時間も設定を見直すハメになりました。)

スキルが動かない場合は適当に別のスキルを試して動くか確認してみると良いでしょう。
Alexa for Businessには「Crazy Jokes」のような単純にジョークを言うだけのスキルもあるので、
それらを有効化して動くかどうかを見るだけでも見直す箇所が絞り込めるかと思います。


Zoom for Alexaを使う

それでは、Zoom for Alexaを使ってみましょう。
Echoに「アレクサ、スタート、ミーティング!」と話しかけると、「ミーティングIDは何ですか?」と英語で返してくれます。
ここで返すミーティングIDはZoom Roomsだけでなく個人アカウントのミーティングIDでもOKです。

今回は試しに個人アカウントのzoom.usに繋げてみます。
zoom.usを開き、以下の画面に書かれている番号をAlexaに向かって英語で読み上げてください。
ZoomのミーティングID

しばらく待っていると、zoom.usのほうがホスト状態になっていれば自然に繋がります。
ホスト状態じゃなかった場合は、Alexaから軽快な音楽が流れ始めて待機状態になるようです。
待機状態のときにzoom.us側でインスタントミーティングを始めるとAlexaとの接続が開始されます。
ただし待機状態になっていたときにzoom.us側には通知などがされなかったので、
事前に繋げることを伝えておくか、常にホスト状態にしておく等の運用をする必要がありそうです。 Zoom for Alexaを繋げた結果

実際に動かしてみた様子です。 Amazon EchoとPCのZoomのクライアントで通話ができました!
Echoに向かって話しかけると、ちゃんとPCから音声が返ってきます。 (映像は繋がってません。)
Zoom for Alexa実行中

まとめ

今回はAlexa for BusinessのスキルとしてZoom for Alexaを試してみました。
ハンズフリーで相手と通話を可能にするのは未来感がありますね。
ただし相手のミーティングIDを読み上げなければいけなかったり、言語が英語だったりとまだまだ使い辛さを感じます。

個人的には「アレクサ、ズームルームでホールに接続して!」と日本語で言うだけで映像と音声を繋げたいですね。
Zoom RoomsにはAPIがあるので、次はそれを使って上記のような会議室同士をつなぐAlexaアプリを開発してみます。
上手くいったら、またこちらのブログで皆様にご紹介いたします!

参考

Alexa for Business Administration Guide
Zoom with Alexa for Business: Meet Without Lifting a Finger

*1:EchoをAlexa for Businessに端末登録するためにexeの実行環境が必要です。

*2:Zoom for Alexaを有効化するために必要。他のスキルであれば不要です。

Kerasで複数のGPUを使い、学習を高速化してみた

こんにちは。アドバンストテクノロジー部のR&Dチーム所属岩原です。
今回はKerasで複数のGPUを使う方法を書きたいと思います。

Keras 2.0.9から簡単に複数GPUを使用した高速化が可能に。

Keras2.0.9からtraining_utilsというモジュールにmulti_gpu_modelという関数が追加されました。
コレを使うと、学習を複数のGPUで行わせることが可能になります。
inputをGPUの数だけ分割することによって、並列化を実現しているようです。
keras/training_utils.py at master · keras-team/keras

では、実際に試してみましょう。

環境

  • AWS EC2(p2.8xlarge) -> GPU8本
  • Deep Learning Base AMI (Ubuntu) Version 2.0 (ami-041db87c) -> CUDAやCuDNN、Python3.5などがセットアップ済み
  • Keras 2.1.2
  • tensorflow-gpu 1.4.1
  • Python 3.5.2

今回使用するコード

Kerasの例にあるcifar10_cnn.pyを複数GPUに対応させてみたいと思います。
keras/cifar10_cnn.py at master · keras-team/keras

まずはGPU1つのみの場合はどれくらいかかったのかを以下に示します。

~~~ 省略 ~~~
Epoch 90/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7667 - acc: 0.7457 - val_loss: 0.6644 - val_acc: 0.7838
Epoch 91/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7694 - acc: 0.7457 - val_loss: 0.6627 - val_acc: 0.7803
Epoch 92/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7692 - acc: 0.7449 - val_loss: 0.7553 - val_acc: 0.7680
Epoch 93/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7721 - acc: 0.7448 - val_loss: 0.7210 - val_acc: 0.7862
Epoch 94/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7751 - acc: 0.7436 - val_loss: 0.6743 - val_acc: 0.7811
Epoch 95/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7781 - acc: 0.7412 - val_loss: 0.7047 - val_acc: 0.7725
Epoch 96/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7781 - acc: 0.7427 - val_loss: 0.6371 - val_acc: 0.7909
Epoch 97/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7720 - acc: 0.7452 - val_loss: 0.6331 - val_acc: 0.7949
Epoch 98/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7917 - acc: 0.7399 - val_loss: 0.7105 - val_acc: 0.7699
Epoch 99/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7829 - acc: 0.7414 - val_loss: 0.6481 - val_acc: 0.7859
Epoch 100/100
1563/1563 [==============================] - 21s 13ms/step - loss: 0.7868 - acc: 0.7404 - val_loss: 0.6266 - val_acc: 0.8005

ということで、学習に21 sec / 1epoch かかっていることになります。
では、コレを単純に8つのGPUで実行した場合にどのような結果になるのか試してみましょう。

単純に8つのGPUで実行した場合

importの追加

まずは必要なモジュールのimportを追加します。
モデルのビルドはCPUで行う必要があるため、Tensorflowをimportします。

import tensorflow as tf # add
from keras.utils.training_utils import multi_gpu_model # add

GPU数の定数を追加とbatch_sizeの変更

GPUの数を定数として定義しておきます。今回は8つ使用するので、8を指定します。
また、batch_sizeは並列で処理を行うために元々のbatch_sizeをGPUの数だけ掛けます。

gpu_count = 8 # add

batch_size = 32 * gpu_count # modify

モデルの構築はCPUで行う

モデルの構築はOOMエラー対策のため、CPUで明示的に行う必要があるので、
tf.deviceを使用します。

with tf.device("/cpu:0"): # add
    model = Sequential()
    model.add(Conv2D(32, (3, 3), padding='same',
                     input_shape=x_train.shape[1:]))
# 以下略

複数GPU対応する

modelをコンパイルする直前に、multi_gpu_model関数を呼び出すようにします。
引数gpusにはGPUの数を指定します。1を指定すると実行時エラーになるので注意して下さい。

model = multi_gpu_model(model, gpus=gpu_count) # add

コード全体

追記変更を含めたコード全体は以下のとおりです。

'''Train a simple deep CNN on the CIFAR10 small images dataset.

It gets to 75% validation accuracy in 25 epochs, and 79% after 50 epochs.
(it's still underfitting at that point, though).
'''

from __future__ import print_function
import keras
from keras.datasets import cifar10
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
import os
import tensorflow as tf # add
from keras.utils.training_utils import multi_gpu_model # add

gpu_count = 8 # add

batch_size = 32 * gpu_count # modify
num_classes = 10
epochs = 100
data_augmentation = True
num_predictions = 20
save_dir = os.path.join(os.getcwd(), 'saved_models')
model_name = 'keras_cifar10_trained_model.h5'


# The data, shuffled and split between train and test sets:
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

# Convert class vectors to binary class matrices.
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
with tf.device("/cpu:0"): # add
    model = Sequential()
    model.add(Conv2D(32, (3, 3), padding='same',
                     input_shape=x_train.shape[1:]))
    model.add(Activation('relu'))
    model.add(Conv2D(32, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))

    model.add(Conv2D(64, (3, 3), padding='same'))
    model.add(Activation('relu'))
    model.add(Conv2D(64, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))

    model.add(Flatten())
    model.add(Dense(512))
    model.add(Activation('relu'))
    model.add(Dropout(0.5))
    model.add(Dense(num_classes))
    model.add(Activation('softmax'))
model = multi_gpu_model(model, gpus=gpu_count) # add
# initiate RMSprop optimizer
opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)

# Let's train the model using RMSprop
model.compile(loss='categorical_crossentropy',
              optimizer=opt,
              metrics=['accuracy'])

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

if not data_augmentation:
    print('Not using data augmentation.')
    model.fit(x_train, y_train,
              batch_size=batch_size,
              epochs=epochs,
              validation_data=(x_test, y_test),
              shuffle=True)
else:
    print('Using real-time data augmentation.')
    # This will do preprocessing and realtime data augmentation:
    datagen = ImageDataGenerator(
        featurewise_center=False,  # set input mean to 0 over the dataset
        samplewise_center=False,  # set each sample mean to 0
        featurewise_std_normalization=False,  # divide inputs by std of the dataset
        samplewise_std_normalization=False,  # divide each input by its std
        zca_whitening=False,  # apply ZCA whitening
        rotation_range=0,  # randomly rotate images in the range (degrees, 0 to 180)
        width_shift_range=0.1,  # randomly shift images horizontally (fraction of total width)
        height_shift_range=0.1,  # randomly shift images vertically (fraction of total height)
        horizontal_flip=True,  # randomly flip images
        vertical_flip=False)  # randomly flip images

    # Compute quantities required for feature-wise normalization
    # (std, mean, and principal components if ZCA whitening is applied).
    datagen.fit(x_train)

    # Fit the model on the batches generated by datagen.flow().
    model.fit_generator(datagen.flow(x_train, y_train,
                                     batch_size=batch_size),
                        epochs=epochs,
                        validation_data=(x_test, y_test),
                        workers=4)

# Save model and weights
if not os.path.isdir(save_dir):
    os.makedirs(save_dir)
model_path = os.path.join(save_dir, model_name)
model.save(model_path)
print('Saved trained model at %s ' % model_path)

# Score trained model.
scores = model.evaluate(x_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])

結果

~~~ 省略 ~~~

Epoch 90/100
196/196 [==============================] - 18s 90ms/step - loss: 0.7307 - acc: 0.7453 - val_loss: 0.6331 - val_acc: 0.7831
Epoch 91/100
196/196 [==============================] - 18s 90ms/step - loss: 0.7301 - acc: 0.7457 - val_loss: 0.6280 - val_acc: 0.7817
Epoch 92/100
196/196 [==============================] - 18s 89ms/step - loss: 0.7324 - acc: 0.7445 - val_loss: 0.6149 - val_acc: 0.7870
Epoch 93/100
196/196 [==============================] - 18s 91ms/step - loss: 0.7239 - acc: 0.7494 - val_loss: 0.6257 - val_acc: 0.7822
Epoch 94/100
196/196 [==============================] - 18s 90ms/step - loss: 0.7247 - acc: 0.7472 - val_loss: 0.6101 - val_acc: 0.7879
Epoch 95/100
196/196 [==============================] - 18s 89ms/step - loss: 0.7207 - acc: 0.7482 - val_loss: 0.6233 - val_acc: 0.7860
Epoch 96/100
196/196 [==============================] - 18s 89ms/step - loss: 0.7191 - acc: 0.7489 - val_loss: 0.6349 - val_acc: 0.7798
Epoch 97/100
196/196 [==============================] - 18s 90ms/step - loss: 0.7111 - acc: 0.7514 - val_loss: 0.6057 - val_acc: 0.7912
Epoch 98/100
196/196 [==============================] - 18s 90ms/step - loss: 0.7118 - acc: 0.7524 - val_loss: 0.6084 - val_acc: 0.7894
Epoch 99/100
196/196 [==============================] - 18s 90ms/step - loss: 0.7126 - acc: 0.7523 - val_loss: 0.6026 - val_acc: 0.7887
Epoch 100/100
196/196 [==============================] - 18s 89ms/step - loss: 0.7035 - acc: 0.7567 - val_loss: 0.6052 - val_acc: 0.7932

21秒よりは少し早くなりましたが、思ってたよりは早くなっていません。

仮説

実際に学習をしている様子を眺めているとわかるのですが、進捗が頻繁に止まります。
これはImageDataGeneratorが画像を作る処理がボトルネックになっていそうです。
画像を生成する処理が追いついていないのではないか、という仮説に基づいて、対応してみましょう。
例のコードではfit_generatorを使うようになっているので、それをfit関数を使ってImageDataGeneratorが関与しないように変更してみましょう。

fit_generatorではなくfitを使うようにした場合

data_augmentation = True

data_augmentation = False

に変えるだけでOKです。

なお、サンプル数は変わらず50000になります。

Epoch 90/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.5075 - acc: 0.8223 - val_loss: 0.6516 - val_acc: 0.7786
Epoch 91/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4984 - acc: 0.8247 - val_loss: 0.6293 - val_acc: 0.7859
Epoch 92/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4987 - acc: 0.8237 - val_loss: 0.6290 - val_acc: 0.7860
Epoch 93/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4925 - acc: 0.8277 - val_loss: 0.6383 - val_acc: 0.7849
Epoch 94/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4922 - acc: 0.8274 - val_loss: 0.6283 - val_acc: 0.7839
Epoch 95/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4892 - acc: 0.8292 - val_loss: 0.6435 - val_acc: 0.7832
Epoch 96/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4850 - acc: 0.8298 - val_loss: 0.6449 - val_acc: 0.7820
Epoch 97/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4823 - acc: 0.8325 - val_loss: 0.6250 - val_acc: 0.7878
Epoch 98/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4741 - acc: 0.8337 - val_loss: 0.6227 - val_acc: 0.7902
Epoch 99/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4692 - acc: 0.8345 - val_loss: 0.6559 - val_acc: 0.7794
Epoch 100/100
50000/50000 [==============================] - 6s 113us/step - loss: 0.4684 - acc: 0.8354 - val_loss: 0.6374 - val_acc: 0.7840

今度は6秒になりました。やはりImageDataGeneratorがネックとなっていそうです。
メモリが潤沢にある場合やサンプル数が沢山用意できる環境であればよいのですが、
大抵はそのような恵まれた環境ではないかと思います。
ImageDataGenerator自体を並列化することで対応してみたいと思います。

ImageDataGeneratorのプロセス並列化

実は、ImageDataGeneratorのflowメソッドがSequenceを継承したIteratorオブジェクトを返すので、プロセス並列化出来たりします。

ImageDataGeneratorのプロセス並列化をした場合

    model.fit_generator(datagen.flow(x_train, y_train,
                                     batch_size=batch_size),
                        epochs=epochs,
                        validation_data=(x_test, y_test),
                        workers=4)

    model.fit_generator(datagen.flow(x_train, y_train,
                                     batch_size=batch_size),
                        epochs=epochs,
                        validation_data=(x_test, y_test),
                        workers=32,
                        max_queue_size=64,
                        use_multiprocessing=True)

に変え、

data_augmentation = False

data_augmentation = True

に戻します。

結果

Epoch 90/100
196/196 [==============================] - 7s 37ms/step - loss: 0.7232 - acc: 0.7481 - val_loss: 0.6056 - val_acc: 0.7893
Epoch 91/100
196/196 [==============================] - 7s 36ms/step - loss: 0.7233 - acc: 0.7486 - val_loss: 0.5949 - val_acc: 0.7953
Epoch 92/100
196/196 [==============================] - 7s 36ms/step - loss: 0.7175 - acc: 0.7488 - val_loss: 0.5963 - val_acc: 0.7908
Epoch 93/100
196/196 [==============================] - 7s 36ms/step - loss: 0.7159 - acc: 0.7502 - val_loss: 0.6006 - val_acc: 0.7905
Epoch 94/100
196/196 [==============================] - 7s 36ms/step - loss: 0.7119 - acc: 0.7528 - val_loss: 0.6063 - val_acc: 0.7882
Epoch 95/100
196/196 [==============================] - 7s 36ms/step - loss: 0.7115 - acc: 0.7502 - val_loss: 0.5908 - val_acc: 0.7923
Epoch 96/100
196/196 [==============================] - 7s 36ms/step - loss: 0.7057 - acc: 0.7556 - val_loss: 0.5935 - val_acc: 0.7925
Epoch 97/100
196/196 [==============================] - 7s 36ms/step - loss: 0.6995 - acc: 0.7586 - val_loss: 0.5920 - val_acc: 0.7910
Epoch 98/100
196/196 [==============================] - 7s 36ms/step - loss: 0.6939 - acc: 0.7586 - val_loss: 0.5929 - val_acc: 0.7967
Epoch 99/100
196/196 [==============================] - 7s 37ms/step - loss: 0.6946 - acc: 0.7573 - val_loss: 0.5752 - val_acc: 0.8007
Epoch 100/100
196/196 [==============================] - 7s 37ms/step - loss: 0.6966 - acc: 0.7566 - val_loss: 0.5837 - val_acc: 0.7966

7秒まで早くなりました。並列化することでボトルネックをある程度解消できた事になります。

まとめ

複数GPUによる高速化は可能です。
ただし、fit_generatorを使う場合、generator部がボトルネックにならないように気をつける必要があります。

注意

今回提示したソースコードだと、modelのsave時にエラーになります。
原因はmulti_gpu_modelのDocに書いてあります。

# On model saving
    To save the multi-gpu model, use `.save(fname)` or `.save_weights(fname)`
    with the template model (the argument you passed to `multi_gpu_model`),
    rather than the model returned by `multi_gpu_model`.

したがって、モデルをsaveしたい時は、multi_gpu_model返すmodelではなく、
multi_gpu_model渡したmodelをsaveするようにしてください。

今回は本質と関係ない部分なので、対応は省略しました。

ファインチューニングをやってみた

こんにちは。アドバンストテクノロジー部のR&Dチーム所属岩原です。
今回はファインチューニングについて色々と調査しました。

ファインチューニング(fine tuning)とは

既存のモデルの一部を再利用して、新しいモデルを構築する手法です。
優秀な汎用モデル(VGG16など)を使い、自分たち用のモデルを構築したり出来ます。
少ないデータ(といっても数十〜数百ぐらいは必要ですが)で、結構精度の良いモデルが構築できたりします。

全く違う方向性(写真画像系のモデルを元に、イラストの判定モデルを作るなど)だと余り効果が出てこないようですが、
元のモデルより更に詳細な特徴を抽出したい、などの用途だと効果が高いようです。

転移学習(transfer learning)という呼び方もされるみたいですが、使い分けとかどんな感じなんでしょうね?

実際にやってみた

環境

  • Ubuntu16.04(GTX1080Ti)
  • Keras 2.0.8
  • Tensorflow 1.3.0
  • nvidia-docker 1.0.1

AWSのGPU Computeインスタンス(p2やp3)でも使いまわせるようにDockerizeしています。

使用するデータセット

Food-101 -- Mining Discriminative Components with Random Forests
101ラベルの料理画像を計101000枚(1ラベル1000枚)用意しているデータセットです。
枚数としては心もとない気がしますが、ファインチューニングを試してみるにはちょうどよい枚数かと思います。

3つのデータパターン

データの数によってどこまで変わるか、という検証のため、パターンを3つ用意しました。

  • データ数を訓練データ100枚、検証データ25で行うパターン(パターン1)
    データ数を少なくし、ファインチューニングの効果を検証するパターンです。

  • パターン1のデータをImageDataGeneratorで水増ししたパターン(パターン2)
    限られたデータ数を水増しし、どこまで効果が出るのかを検証するパターンです。
    fit_generatorの引数steps_per_epochを2000(batch_sizeは32なので、2000 * 32で64000枚)、引数validation_stepsを500(同じく500 * 32で16000枚)に設定しました。

  • データセットの全てのデータ(訓練データ75750枚、検証データ25250枚)で学習を行うパターン(パターン3)
    データセット全てを学習&検証に回し、水増しとの違いを検証するパターンです。
    バッチサイズは32を設定しました。

3つのモデル

ファインチューニングの方法によってどこまで差が出るのかの検証のため、さらにモデルを3つ用意しました。

  • ピュアなVGG16(モデル1)
    重みを初期化したVGG16構造のモデルです。
    ファインチューニングしない場合の検証を行うパターンになります。

f:id:m_iwahara:20171212170350p:plain

Kerasを使用したコードはこんな感じになります。

def create_none_weight_vgg16_model(size):
    model_path = "./models/vgg16_none_weight.h5py"
    if not os.path.exists(model_path):
        input_tensor = Input(shape=(224,224,3))
        model = VGG16(weights=None, include_top=True, input_tensor=input_tensor, classes=size)
        model.save(model_path) # 毎回ダウンロードすると重いので、ダウンロードしたら保存する
    else:
        model = load_model(model_path) 
    model.compile(loss='categorical_crossentropy',
                  optimizer='adam',
                  metrics=['accuracy'])
    return model
  • VGG16の全結合層のみを取り替え(モデル2)
    全結合層を取っ払い、新たに全結合層をくっつけたモデルです。
    全結合層以外の層の重みは学習済みのパラメータをそのまま使用し、学習しないようにします。
    全結合層のみファインチューニングした場合の検証を行うパターンになります。
    ついでに、全結合層にDropoutを付けてみたりしています。
    なお、最適化関数は学習率を極端に抑えたSGDを使用しています。

f:id:m_iwahara:20171212170417p:plain

Kerasを使用したコードはこんな感じになります。

def get_vgg16_model():
    model_path = "./models/vgg16.h5py"
    if not os.path.exists(model_path):
        input_tensor = Input(shape=(224,224,3))
        # 出力層側の全結合層3つをモデルから省く
        model = VGG16(weights='imagenet', include_top=False, input_tensor=input_tensor)
        model.save(model_path) # 毎回ダウンロードすると重いので、ダウンロードしたら保存する
    else:
        model = load_model(model_path)
    return model

def create_fullconnected_fine_tuning(classes):
    # vgg16モデルを作る
    vgg16_model = get_vgg16_model()

    input_tensor = Input(shape=(224,224,3))

    for layer in vgg16_model.layers:
        layer.trainable = False

    x = vgg16_model.output
    x = Flatten()(x)
    x = Dense(2048, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(1024, activation='relu')(x)
    predictions = Dense(classes, activation='softmax')(x)
    model = Model(inputs=vgg16_model.input, outputs=predictions)


    model.compile(loss='categorical_crossentropy',
                  optimizer=SGD(lr=1e-4, momentum=0.9),
                  metrics=['accuracy'])
    return model
  • VGG16の最後の畳み込み層と全結合層を取り替え(モデル3)
    最後の畳み込み層の重みを初期化して学習するようにし、全結合層を取り替えたモデルです。
    上記以外の層は再学習をしないようにします。
    最後の畳み込み層と全結合層をファインチューニングした場合の検証を行うパターンになります。

f:id:m_iwahara:20171212170432p:plain

Kerasを使用したコードはこんな感じになります。

def create_last_conv2d_fine_tuning(classes):
    # vgg16モデルを作る
    vgg16_model = get_vgg16_model()

    input_tensor = Input(shape=(224,224,3))

    x = vgg16_model.output
    x = Flatten()(x)
    x = Dense(2048, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(1024, activation='relu')(x)
    predictions = Dense(classes, activation='softmax')(x)
    model = Model(inputs=vgg16_model.input, outputs=predictions)
    # 最後の畳み込み層より前の層の再学習を防止
    for layer in model.layers[:15]: 
        layer.trainable = False

    model.compile(loss='categorical_crossentropy',
                  optimizer=SGD(lr=1e-4, momentum=0.9),
                  metrics=['accuracy'])
    return model

その他

過学習に陥る前に学習を止めるEarlyStoppingを導入済みです。

結果

上記データパターンとモデルパターンの組み合わせを検証。

パターン1

モデル1

精度

f:id:m_iwahara:20171213091424p:plain

損失

f:id:m_iwahara:20171213091443p:plain

モデル2

精度

f:id:m_iwahara:20171213091528p:plain

損失

f:id:m_iwahara:20171213091541p:plain

モデル3

精度

f:id:m_iwahara:20171213091609p:plain

損失

f:id:m_iwahara:20171213091622p:plain

パターン2

モデル1

精度

f:id:m_iwahara:20171213091648p:plain

損失

f:id:m_iwahara:20171213091700p:plain

モデル2

精度

f:id:m_iwahara:20171213091714p:plain

損失

f:id:m_iwahara:20171213091725p:plain

モデル3

精度

f:id:m_iwahara:20171213091740p:plain

損失

f:id:m_iwahara:20171213091749p:plain

パターン3

モデル1

精度

f:id:m_iwahara:20171213091803p:plain

損失

f:id:m_iwahara:20171213091813p:plain

モデル2

精度

f:id:m_iwahara:20171213091825p:plain

損失

f:id:m_iwahara:20171213091837p:plain

モデル3

精度

f:id:m_iwahara:20171213091902p:plain

損失

f:id:m_iwahara:20171213091914p:plain

結果まとめ

「データ数は多いほうが良い。ファインチューニングはゼロから学習させるよりもかなり有効で、収束も早い。最後の畳み込み層からファインチューニングした方が精度が良い。」という感じですね。
枚数が足りないのか、過学習の傾向はどれも見られますが…。
なお、1 epoch辺りの学習時間は モデル1 > モデル3 > モデル2 といった結果になりました。
ファインチューニングを行うと学習時間も節約できるのでおすすめです。

参考

VGG16のFine-tuningによる犬猫認識 (2) - 人工知能に関する断創録

nvidia-docker2を使ってみる

こんにちは。アドバンストテクノロジー部のR&Dチーム所属岩原です。
今回は、nvidia-dockerをdocker-composeから使う - WonderPlanet Tech Blogの記事が、
nvidia-dockerのversion2.0の登場によって過去のものになってしまったので、対応した記事を新たに書きました。

検証環境

  • AWS(p2.xlarge)
  • Ubuntu 16.04 LTS

CUDAやドライバー,Dockerのインストールなど

nvidia-dockerをdocker-composeから使う - WonderPlanet Tech Blogの各項目を参照してください。
バージョンなどは上がっているかと思うので、そこは最新に合わせてください。
念のため、各項目のインストールページを載せておきます。

また、前回書き忘れていたのですが、dockerグループにユーザーを所属させると、
dockerの実行にsudoが必要なくなるので、やっておくと良いかと思います。
手順は以下のとおりです。

  • 現在のユーザーをdockerグループに所属させる
sudo gpasswd -a $USER docker

docker-compose対応

add support for runtime option in service definitions by cuckoohello · Pull Request #5405 · docker/composeによると、
docker-composeの正式対応(runtime指定)は1.19ぐらいになりそうです。
したがって、それまではdocker deamonのデフォルトのコンテナランタイムをnvidia-dockerに変える必要があります。

兎にも角にも、最新のdocker-composeを入れましょう。
前回と同じ手順ですが、バージョン変わってるのでついでにコマンドを載せておきます。
現在(2017/12/13)の最新安定版は1.17のようです。

sudo curl -L https://github.com/docker/compose/releases/download/1.17.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version

でバージョン情報が出力されればOKです。

nvidia-dockerのインストール

前準備として、GPGキーの登録やリポジトリの追加などを行います。

curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update

上記が正常終了したら、次はnvidia-docker2のインストールです。

sudo apt-get install nvidia-docker2

インストールが完了したら、docker daemonの設定をリロードさせます。

sudo pkill -SIGHUP dockerd

問題なければ動作確認してみましょう。

docker run --runtime=nvidia --rm nvidia/cuda nvidia-smi

nvidia-smiの結果が出力されればOKです。

出力例(AWSのp2.xlargeの場合)

Wed Dec 13 05:28:55 2017
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 387.26                 Driver Version: 387.26                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla K80           Off  | 00000000:00:1E.0 Off |                    0 |
| N/A   50C    P0    57W / 149W |      0MiB / 11439MiB |     99%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

docker-composeで使う(v1.19.0未満の場合)

dockerdのdefault runtimeをnvidiaに設定します。
nvidia-dockerのインストール直後は未設定なので、runcと言うものDockerデフォルトのものになっています。

sudo vi /etc/docker/daemon.json

でDocker daemonの設定ファイルを開き、"default-runtime": "nvidia",を追加します。
追加後のファイルの内容は以下の通りになるかと思います。

{
    "default-runtime": "nvidia", 
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}

これで、デフォルトで使われるdockerコンテナのランタイムがruncからnvidiaに変わります。
一旦、この状態で再起動かけると設定が適用されます。

この状態で、以下をdocker-compose.ymlとして保存し、

version: '3'
services:
  nvidia:
    image: nvidia/cuda
    command: nvidia-smi

docker-compose upしてnvidia-smiの結果が出力されればOKです。

出力例(AWS のp2.xlargeの場合)

Creating network "ubuntu_default" with the default driver
Creating ubuntu_nvidia_1 ...
Creating ubuntu_nvidia_1 ... done
Attaching to ubuntu_nvidia_1
nvidia_1  | Wed Dec 13 05:13:22 2017
nvidia_1  | +-----------------------------------------------------------------------------+
nvidia_1  | | NVIDIA-SMI 387.26                 Driver Version: 387.26                    |
nvidia_1  | |-------------------------------+----------------------+----------------------+
nvidia_1  | | GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
nvidia_1  | | Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
nvidia_1  | |===============================+======================+======================|
nvidia_1  | |   0  Tesla K80           Off  | 00000000:00:1E.0 Off |                    0 |
nvidia_1  | | N/A   49C    P0    57W / 149W |      0MiB / 11439MiB |     99%      Default |
nvidia_1  | +-------------------------------+----------------------+----------------------+
nvidia_1  |
nvidia_1  | +-----------------------------------------------------------------------------+
nvidia_1  | | Processes:                                                       GPU Memory |
nvidia_1  | |  GPU       PID   Type   Process name                             Usage      |
nvidia_1  | |=============================================================================|
nvidia_1  | |  No running processes found                                                 |
nvidia_1  | +-----------------------------------------------------------------------------+

volumesとかdevicesとかは必要なくなりましたが、Docker daemonの設定を弄らないといけないのは面倒です。
また、コンテナごとにコンテナランタイムを変えられないのはちょっと不便ですね。
そこら辺はdocker-composeのバージョンアップを待ちましょう。

docker-composeで使う(v1.19.0以上の場合)

まだリリースされていません(2017/12/13時点では1.18rc2が最新)が、前述のプルリクエストを見る限り、
以下のようにdocker-compose.ymlにruntime: nvidiaを追記するだけです。

version: '3'
services:
  nvidia:
    image: nvidia/cuda
    runtime: nvidia
    command: nvidia-smi

簡単ですね。 Docker daemonの設定を弄る必要はありません。
コンテナごとにruntimeを変えることも可能となるでしょう。

実践

nvidia-dockerをdocker-composeから使う - WonderPlanet Tech Blogでやったのと同じ、
Kerasのサンプルの1つである、mnist + CNNを動かすDockerイメージを作ります。
詳細は元記事を参照してください。

元記事と違う点として、docker-compose.ymlからvolume周りの設定が全て消えました。

version: "3"
services:
    tensorflow_keras:
        build:
            context: ./tensorflow_keras
            dockerfile: Dockerfile

上記以外は元記事と一緒です。

結果

tensorflow_keras_1  | 2017-12-13 05:55:43.084455: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla K80, pci bus id: 0000:00:1e.0)

きちんとGPUが認識されているようです。

複数GPUは認識されるのか

インスタンスタイプをp2.8xlarge(8GPU)に上げてやってみました。 これで複数GPUが認識されるのかどうか確かめてみます。

tensorflow_keras_1  | 2017-12-13 06:09:22.743220: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla K80, pci bus id: 0000:00:17.0)
tensorflow_keras_1  | 2017-12-13 06:09:22.743232: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:1) -> (device: 1, name: Tesla K80, pci bus id: 0000:00:18.0)
tensorflow_keras_1  | 2017-12-13 06:09:22.743238: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:2) -> (device: 2, name: Tesla K80, pci bus id: 0000:00:19.0)
tensorflow_keras_1  | 2017-12-13 06:09:22.743243: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:3) -> (device: 3, name: Tesla K80, pci bus id: 0000:00:1a.0)
tensorflow_keras_1  | 2017-12-13 06:09:22.743252: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:4) -> (device: 4, name: Tesla K80, pci bus id: 0000:00:1b.0)
tensorflow_keras_1  | 2017-12-13 06:09:22.743257: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:5) -> (device: 5, name: Tesla K80, pci bus id: 0000:00:1c.0)
tensorflow_keras_1  | 2017-12-13 06:09:22.743262: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:6) -> (device: 6, name: Tesla K80, pci bus id: 0000:00:1d.0)
tensorflow_keras_1  | 2017-12-13 06:09:22.743270: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:7) -> (device: 7, name: Tesla K80, pci bus id: 0000:00:1e.0)

全て認識されています。

使用するGPUを指定する方法

環境変数NVIDIA_VISIBLE_DEVICESを使います。 以下のように、NVIDIA_VISIBLE_DEVICESの値として0,1,2を指定すると、 0番目、1番目、2番目のGPUを指定したことになります。 他にもallnoneなども指定できます。 詳細はNVIDIA/nvidia-container-runtime: NVIDIA container runtimeで確認してください。

version: "3"
services:
    tensorflow_keras:
        build:
            context: ./tensorflow_keras
            dockerfile: Dockerfile
        environment:
            - NVIDIA_VISIBLE_DEVICES=0,1,2

実行してみると、

tensorflow_keras_1  | 2017-12-13 06:19:44.448613: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla K80, pci bus id: 0000:00:17.0)
tensorflow_keras_1  | 2017-12-13 06:19:44.448624: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:1) -> (device: 1, name: Tesla K80, pci bus id: 0000:00:18.0)
tensorflow_keras_1  | 2017-12-13 06:19:44.448630: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:2) -> (device: 2, name: Tesla K80, pci bus id: 0000:00:19.0)

のように3つ使用されているのが確認できます。

結論

docker-composeは1.19が出るまで待つと良いです。
現状では、docker-composeを使用するとnvidiaのランタイムかDockerのランタイムどちらかしか使用できません。
ただし、volumeとか作る必要はなくなったので、そこは良くなった点ですね。

参考

Azure Custom VisionのAPIで画像分類 Swift版

ようやくAmazon Echo Dotをゲットして、毎日Alexaさんと楽しくおしゃべりしているアドバンストテクノロジー部の近藤です。

今回はMicrosoft Azure Custom Vision Serviceで作成した学習モデルをAPI経由で分類したい画像を入力して、出力をJSONで受け取る方法を紹介します。

Custom Vision Serviceについて

Microsoft Azureのサービスのひとつで、画像分類をする学習モデルを作成できるサービスです。
詳細はこちらの記事で紹介しています。 tech.wonderpla.net

Custom VisionのAPIについて

上記の記事を参考にCustom Visionで学習モデルを作成すると、API経由で分類するためのURLも発行されます。

「Performance」→「Prediction URL」のメニューを開くと、画面の右側にURLが記載されています。
今回は画像のファイルデータを送信して分類するので、「If you have an image file:」の方を利用します。 赤文字で書いてある必要なヘッダを設定してリクエストします。 f:id:HidehikoKondo:20171211163833p:plain

ドキュメント

ドキュメントはこちら
<Microsoft> developer portal

Swift版

公式サイトに各種言語でサンプルコードが公開されていますが、SwiftのサンプルコードがなかったのでSwiftで作ってみました。
パンダの画像をAPI経由で分類します。分類した結果はJSONで受け取ることが出来るので、それをそのままTextViewに表示しています。
APIのURLとPrediction-Keyは皆様のアカウントで発行されたものに差し替えてください。

Storyboardには結果の表示用のUITextVIewと分類する画像を表示したUIImageViewを配置します。
「@IBAction func prediction(_ sender: Any) 」にはStoryboardで配置したUIButtonに接続します。

import UIKit

class AzureViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate  {

    @IBOutlet weak var resultTextView: UITextView!
    @IBOutlet weak var animalPhoto: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func prediction(_ sender: Any) {
        // APIのURLをここにコピペする(スクショ参照)
        var path: NSString = "●●●●●●●"
        let image: UIImage = self.animalPhoto.image!
        let pngData: Data = UIImagePNGRepresentation(image)!
        let requestParams: NSArray = ["entities=true"]
        let parameter: String = requestParams.componentsJoined(by: "&")
        path = path.appendingFormat("?%@", parameter)

        //Httpリクエストの設定
        var request: URLRequest = URLRequest.init(url: URL.init(string: path as String)!)
        request.httpMethod = "POST"
        request.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type")

        // Prediction-Keyをここにコピペする(スクショ参照)
        request.addValue("●●●●●●●", forHTTPHeaderField: "Prediction-key")
        request.httpBody = pngData

        // APIに対して画像を送って、分類した結果をJSONで受け取る
        let session: URLSession = URLSession.init(configuration: URLSessionConfiguration.default)
        let task = session.dataTask(with: request, completionHandler: {
            data, response, error in

            if let data = data, let response = response{
                print(response)
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
                    DispatchQueue.main.async {
                        self.resultTextView.text = String.init(describing: json)
                    }
                } catch {
                    print("Serialize Error")
                }
            } else {
                print(error ?? "Error")
            }
        })
        task.resume()
    }
}

動物の画像判別

f:id:HidehikoKondo:20171204191123p:plain Custom Visionのコンソールで、「パンダ」「クマ」「コアラ」の顔を学習させたモデルを作成しました。
これをAPI経由でパンダの画像を判別させた結果がこちらです。
「Probability(確率)」が0.38.... で、「panda」であるという結果が帰ってきたのがわかります。

f:id:HidehikoKondo:20171201201431p:plain

注意事項

APIに送信して分類できる画像のサイズは4MBまでです。 4MB超えてた画像を送信すると、エラー(Bad Request)が返ってきます。

メリットとデメリット

メリット

  • 学習モデルを作成はCustom Visionのコンソール上で完結し、学習モデルを作るための環境を自分で用意する必要がありません。
  • Custom Visionで作成した学習モデルは、Webの画面上で再学習などの調整ができます。
  • Core MLに変換した学習モデルの場合は、アプリにインポートしてアプリをビルドし直す必要がありますが、API経由であれば新しい学習モデルに即切り替えることが可能となります。
  • クロスプラットフォームで利用できます!

デメリット

  • Core MLの学習モデルはローカルのみで完結するのに対し、API経由だとネットワークを使うためレスポンスは遅くなります。

まとめ

このようにCustom VisionのAPIを利用して、簡単に画像の分類をすることができます。
API経由であればスマホアプリでもWebアプリでも利用でき、いろんなプラットフォームで活用できると思います。
これだけ手軽に機械学習の機能を利用することができるようになると、いろんなサービスがこれから沢山出てくるんではないでしょうか?
機械学習界隈がこれからどんな風に盛り上がっていくか楽しみですね。