Google Assistant で 深層学習 - KerasをRaspberry Piで音声操作する 

Google Assistant + Raspberry Piを使って深層学習ライブラリ「Keras」を動作させてみました。まずは深層学習のHelloWorld、「赤りんごと青りんごを見分ける」のを人工知能にさせてみました。こんな感じです。

<blockquote class="twitter-video" data-lang="ja"><p lang="ja" dir="ltr">グーグル・アシスタントさんがりんごを見分けるよ。<br>Google assistant classifies apples.<a href="https://twitter.com/hashtag/GoogleAssistant?src=hash">#GoogleAssistant</a> <a href="https://twitter.com/hashtag/RaspberryPi?src=hash">#RaspberryPi</a> <a href="https://twitter.com/hashtag/TensorFlow?src=hash">#TensorFlow</a> <a href="https://twitter.com/hashtag/keras?src=hash">#keras</a> <a href="https://t.co/lmk9cyklZH">pic.twitter.com/lmk9cyklZH</a></p>&mdash; ポンダッド (@pon_dad) <a href="https://twitter.com/pon_dad/status/869563809521844224">2017年5月30日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

GoogleAssistantから「アクション（ここでは`Samantha`）」を呼び出し、「インテント（意図）」によりりんごの写真を撮影します。「Say　Cheese」で写真を撮影し、保存します。

![Screenshot from DSC_0112.MOV.png](https://qiita-image-store.s3.amazonaws.com/0/47128/6feff22e-56de-11d7-65ce-5bdf4be2051b.png)

「インテント」「What is this?」の呼びかけで画像認識を開始します。

今回はここからりんごを深層学習ライブラリ「Keras」を利用して「赤りんご」か「青りんご」かを判別させます。ここでは認識率90％以上で正と判断させました。

![Screenshot from DSC_0112.MOV - 1.png](https://qiita-image-store.s3.amazonaws.com/0/47128/4f7fcc5a-56d3-ba1e-c407-ca329dc2e3c0.png)

「Green Apple」と正しく判定しました。


## 環境

環境は[前回記事](http://qiita.com/PonDad/items/809615c71f3c13c288c3)と同じです。

![ファイル_000.jpeg](https://qiita-image-store.s3.amazonaws.com/0/47128/069cbffd-8cf7-33d2-c94a-5e1588cd8ea5.jpeg)

ちょこちょこと改造しているのですが、今回はプッシュボタンと合わせてLEDをGPIOに取り付けています。興味のある方はこちらの動画 [Button Trigger for Google Assistant on Raspberry Pi　ーYouTube](https://www.youtube.com/watch?v=ImrN404aDcc) で詳しく取り付け方やコードを紹介しているのご覧になると良いでしょう。

これでモニターが無い環境でも、LEDが点灯するので音声待受状態を確認することが出来ます。

[pushtotalk.py - Gist](https://gist.github.com/PonDad/6b27864666bb493dca074e895ab0afa7#file-pushtotalk-py)

## 大まかな仕組み

### Google Cloud Platform

![Data centers – Google Data centers.png](https://qiita-image-store.s3.amazonaws.com/0/47128/14da9f3f-ea0e-02f2-f287-dd973297c933.png)

画像は[Google  Data Centers](https://www.google.com/about/datacenters/) より引用

Google Cloud Platformに設置したGoogle Assistantを呼び出して会話を行います。

API.aiキーワードの設定し、「Google Cloud Functions」で実際のアクションを設定します。

「Google Cloud Functions」はNode.jsを利用して、リクエストを受ける度にイベントを駆動させます。

![無題の図形描画.png](https://qiita-image-store.s3.amazonaws.com/0/47128/875c82fc-e410-3da6-2dc8-67cf52576027.png)

```js:index.js
'use strict';

process.env.DEBUG = 'actions-on-google:*';
const App = require('actions-on-google').ApiAiApp;

const exec = require('child_process').exec;
const camera_adress = "https://xxxxxxx.ap.ngrok.io/camera"
const what_adress = "https://xxxxxxx.ap.ngrok.io/what"

// [START YourAction]
exports.myAction = (request, response) => {
  const app = new App({request, response});
  console.log('Request headers: ' + JSON.stringify(request.headers));
  console.log('Request body: ' + JSON.stringify(request.body));

  function responseCamera (app) {
    exec(`curl ${camera_adress}`, (error, stdout, stderr) => {
      if (error) {
        console.error(`exec error: ${error}`);
        return;
      }
      console.log(`${stdout}`);
      app.ask(`<speak>I took <break time='1' /></speak>` + `${stdout}`);
    });
  }

  function responseWhat (app) {
    exec(`curl ${what_adress}`, (error, stdout, stderr) => {
      if (error) {
        console.error(`exec error: ${error}`);
        return;
      }
      console.log(`${stdout}`);
      app.ask(`<speak>This is a <break time='1' /></speak>` + `${stdout}`);
    });
  }

  function responseCorrect (app) {
      app.ask(`<speak>Thank you.</speak>`);
  }

  const actionMap = new Map();
  actionMap.set('talk.camera', responseCamera);
  actionMap.set('talk.what', responseWhat);
  actionMap.set('talk.correct', responseCorrect);

  app.handleRequest(actionMap);
};
// [END YourAction]

```
「Google Cloud Functions」を利用して、RaspberryPiにたてたローカルサーバーWebhookを送信します。ファイアウォールを超えるためにここではngrokを利用してアクセスします。

### Raspberry Pi

![ファイル_000 (1).jpeg](https://qiita-image-store.s3.amazonaws.com/0/47128/a94bc9b6-c831-887b-5e8a-c158ecab0c68.jpeg)

以前作成した（[Raspberry Pi 深層学習で「赤りんご」と「青りんご」を見分ける（Keras・Open CV）](http://qiita.com/PonDad/items/9e1dc2752a089b753fe5)）学習用のapple.npyファイルを利用して再度学習させました。

Kerasが2.0にアップデートし、一部APIが変更になったので以下のようにモデル作成用のファイルを書き換えました。

```py:apple_keras.py
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.utils import np_utils

root_dir = "./image/"
categories = ["red_apple", "green_apple"]
nb_classes = len(categories)
image_size = 32

def main():
    X_train, X_test, y_train, y_test = np.load("./image/apple.npy")
    X_train = X_train.astype("float") / 256
    X_test  = X_test.astype("float")  / 256
    y_train = np_utils.to_categorical(y_train, nb_classes)
    y_test  = np_utils.to_categorical(y_test, nb_classes)
    model = model_train(X_train, y_train)
    model_eval(model, X_test, y_test)

def build_model(in_shape):
    model = Sequential()
    model.add(Conv2D(32, 3, 3,
	border_mode='same',
	input_shape=in_shape))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))
    model.add(Conv2D(64, 3, 3, border_mode='same'))
    model.add(Activation('relu'))
    model.add(Conv2D(64, 3, 3))
    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(nb_classes))
    model.add(Activation('softmax'))
    #model.compile(loss='binary_crossentropy',
	#optimizer='rmsprop',
    #metrics=['accuracy'])
    return model

def model_train(X, y):
    model = build_model(X.shape[1:])
    print(X.shape[1:])
    history = model.fit(X, y, batch_size=32, nb_epoch=10, validation_split=0.1)
    hdf5_file = "./image/apple-model.h5"
    model.save_weights(hdf5_file)
    return model

def model_eval(model, X, y):
    score = model.evaluate(X, y)
    print('loss=', score[0])
    print('accuracy=', score[1])

if __name__ == "__main__":
    main()
```
再度学習したモデルを使いカメラで読み込んだ画像を`predict()`メソッドを利用して推測させます。

```py3:whatisthis.py
from flask import Flask
from flask import request, jsonify
import json
import numpy as np
import cv2
import subprocess
import time
import apple_keras as apple
import sys, os
import cv2
from keras.preprocessing.image import load_img, img_to_array
from keras import backend as K

app = Flask(__name__)

image_size = 32
categories = ["red apple", "green apple"]

@app.route('/camera')
def camera():
    cap = cv2.VideoCapture(0)
    cap.set(3, 760)
    cap.set(4, 480)
    while(True):
        ret, frame = cap.read()
        cv2.imshow('frame', frame)
        subprocess.call( ["aplay", "camera.wav"] )
        time.sleep(0.5)
        cv2.imwrite("apple.png", frame)
        text = "picture"
        break

    cap.release()
    cv2.destroyAllWindows()

    return text

@app.route('/what')
def whatisthis():
    X = []
    img = load_img("./apple.png", target_size=(image_size,image_size))
    in_data = img_to_array(img)
    X.append(in_data)
    X = np.array(X)
    X  = X.astype("float")  / 256

    model = apple.build_model(X.shape[1:])
    model.load_weights("./image/apple-model.h5")

    pre = model.predict(X)
    print(pre)
    if pre[0][0] > 0.9:
        print(categories[0])
        text = categories[0]
        text = text.encode('utf-8')
        return text
    elif pre[0][1] > 0.9:
        print(categories[1])
        text = categories[1]
        text = text.encode('utf-8')
        return text

if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8080)
```
Raspberry側ではローカルサーバーをFlaskで立て、エンドポイントにWebhookでリクエストを受けた際に実行を行い戻り値を`return`で返します。

ここでは「青りんご」の推測値が90%以上のため、「GreenApple」を返しています。

## まとめ

Googele　Assistantは音声認識からアクション実行まで5秒以上経過するとエラー処理を起こすようです。今回は比較的簡単なモデルですので推測実行がRaspberryPiでもなんとか行えました。（動画をご覧になれば分かるのですがそれでも結構時間が掛かってしましまいます。）

もう少し複雑な深層学習モデルを利用する際は、RaspberryPiで処理を行うよりもクラウドにKerasの実行環境を作ったほうが良いかも知れません。

GCPでKerasを行う方法はこんな感じでまとめられています。

[Deep Learning with Keras on Google Compute Engine - Medium](https://medium.com/google-cloud/keras-inception-v3-on-google-compute-engine-a54918b0058)

GCPの使い方を覚えたら今度やってみたいと思います。ではまた。

## ご参考

興味のある方は過去記事も合わせてご参照下さい。

- [Raspberry Pi 深層学習ライブラリで物体認識（Keras with TensorFlow・Open CV）](http://qiita.com/PonDad/items/c5419c164b4f2efee368)
- [Raspberry Pi 深層学習で「赤りんご」と「青りんご」を見分ける（Keras・Open CV）](http://qiita.com/PonDad/items/9e1dc2752a089b753fe5)
- [Raspberry Pi 人工知能ツールをインストール【Python3】](http://qiita.com/PonDad/items/9fbdf4d694f825dd1b6e)
- [Raspberry Pi にGoogle Assistant SDKを搭載して「OK Google」してみる。](http://qiita.com/PonDad/items/52eaa93338496b06cb1a)
- [Raspberry Pi に Actions on Google で会話アプリをつくる 【Google Assistant SDK/API.ai】](http://qiita.com/PonDad/items/bd54cf02805cddc064b2)
- [Raspberry Pi で音声カメラ 【Actions on Google/Google Assistant SDK/API.ai】](http://qiita.com/PonDad/items/809615c71f3c13c288c3)