Access Tokenを取得してGoogle Cloud MLでオンライン予測


Google APIのAccess Tokenを取得してGoogle Cloud MLでオンライン予測のリクエストを投げています。そのときの記録です。[TensorFlow Object Detection API](https://github.com/tensorflow/models/tree/master/research/object_detection)を使って訓練したモデルを使っています。
[記事「Google API の Access Token を手動で取得する」](http://takaya030.hatenablog.com/entry/2016/09/04/164354)を参考にしています。

# 関連記事

- [TensorFlow Object Detection APIをUbuntuにインストール](https://qiita.com/FukuharaYohei/items/5e4081ba8f7f9255a8f6)
- [Object Detection APIを使って堀川君を検知](https://qiita.com/FukuharaYohei/items/fcad2d74f2b753191548)
- [Object Detection APIのsaved_model出力](https://qiita.com/FukuharaYohei/items/d404bacd45afbf0f068e)
- [Object Detectionのためのアノテーション(ラベリング)](https://qiita.com/FukuharaYohei/items/9bd1be8ba04430c1ebea)

#前提
Google APIが有効化済

# 手順
##1. GCPでOAuthクライアントID作成
[Google Cloud Consoleの認証ページ](https://console.developers.google.com/apis/credentials)にブラウザでアクセスします。
「認証情報を作成」から「OAuthクライアントID」を選択。
<img width="270" alt="10.Authorization01.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/134273/b06cede4-eb37-b1f4-6b8a-342a70ea7898.jpeg">

「その他」を選んで「名前」を入力して「作成」ボタン押下。
<img width="366" alt="10.Authorization02.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/134273/7eb66d07-6a68-af11-ae7a-b8315f5d501f.jpeg">

クライアントIDとクライアントシークレットを取得。
<img width="362" alt="10.Authorization03.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/134273/e6cf1851-dca8-468d-7e53-37320632894c.jpeg">

OAuthクライアントIDのJSONファイルをダウンロード。
<img width="538" alt="10.Authorization04.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/134273/95772653-bdc1-fc86-f1d8-9110e7e34bf6.jpeg">

JSONファイルの中身。

```json
{
	"installed": {
		"client_id": "<client_id>.apps.googleusercontent.com",
		"project_id": "cloud-ml01",
		"auth_uri": "https://accounts.google.com/o/oauth2/auth",
		"token_uri": "https://oauth2.googleapis.com/token",
		"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
		"client_secret": "<client_secret>",
		"redirect_uris": [
			"urn:ietf:wg:oauth:2.0:oob",
			"http://localhost"
		]
	}
}
```

##2. Authorization Code取得
client_idのパラメータを1で取得した値を設定してブラウザで下記のURLでアクセス。
※[Cloud Machine Learning Engineのスコープ](https://developers.google.com/identity/protocols/googlescopes#mlv1)

```
https://accounts.google.com/o/oauth2/auth?client_id=xxx&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/cloud-platform&response_type=code&approval_prompt=force&access_type=offline
```

認証を求められるので、「許可」を選択。画面でAuthorization Codeをコピーして取得。

##3. Access Token取得
今度はcurlを使ってAccess Tokenを取得。client_idとclient_secret、redirect_uriはjsonにある値を設定。codeは2で取得したAuthorization Codeを設定。

```bash
curl -k -d client_id=xxx -d client_secret=xxx -d redirect_uri=xxx -d grant_type=authorization_code -d code=xxxxx https://accounts.google.com/o/oauth2/token
```

JSON形式でAccess Token取得。

```json
{
  "access_token": "xxx",
  "expires_in": 3600,
  "refresh_token": "xxx",
  "scope": "https://www.googleapis.com/auth/cloud-platform",
  "token_type": "Bearer"
}
```

##4. Httpのテスト(Postman)
[Postman](https://www.getpostman.com/)を使ってHttpでのリクエストテストをします。
ヘッダのAuthorizationにBearerに続いてAccess Tokenを貼り付けます(BearerとAccess Tokenの間にスペースが必要)。

- http method: POST
- URL: https://ml.googleapis.com/v1/projects/project_name/models/model_name:predict
- Headers:
    - Content-Type: application/json
    - Authorization: Bearer AccessToken_xxxx
- Body:下記json形式のrawデータ

```json
{
    "instances": [
        {
            "inputs":[[[numpy形式]]]
        }
    ]
}
```

ちなみに画像のnumpy形式を出力するために以下のpythonプログラムを作成。どうしてもデータ量が重くなるので、10kb程度の画像を使っています。pillowとnumpyをインストールしたpython3.5.6環境で実行しています。

```python
from PIL import Image
import numpy as np
import json

def load_image_into_numpy_array(image):
  (im_width, im_height) = image.size
  return np.array(image.getdata()).reshape(
      (im_height, im_width, 3)).astype(np.uint8)

image = Image.open(r'C:\tmp\tensorflow\od\horikawa\test01.jpg')
image_np = load_image_into_numpy_array(image)
print(image_np.shape)

b = image_np.tolist()
json_file = r"C:\tmp\image.json"

with open(json_file, 'w', encoding='utf-8') as f:
    instance = {"instances":[{ "inputs": b} ]}
    json.dump(instance, f , sort_keys=True)
    f.write("\n")

print(json.dumps(instance))
```

#おまけ
[Google公式のサンプル](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/ml_engine/online_prediction/predict.py)を参考にpythonでhttpを投げるとこんな感じです。コードが汚くてすいません。前提として[「認証情報設定」](https://qiita.com/FukuharaYohei/items/2a435ef83ecf1702395c#14-%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E8%A8%AD%E5%AE%9A)に従ってサービスアカウントを作成して秘密鍵をjosn形式でダウンロードしておく必要があります。

```python
import base64, json, httplib2, six, googleapiclient.discovery
from oauth2client.service_account import ServiceAccountCredentials
from googleapiclient import errors
from PIL import Image
import numpy as np

def load_image_into_numpy_array(image):
    (im_width, im_height) = image.size
    return np.array(image.getdata()).reshape(
      (im_height, im_width, 3)).astype(np.uint8)

# [START predict_json]
def predict_json():
    """Send json data to a deployed model for prediction.
    Args:
        project (str): project where the Cloud ML Engine Model is deployed.
        model (str): model name.
        instances ([Mapping[str: Any]]): Keys should be the names of Tensors
            your deployed model expects as inputs. Values should be datatypes
            convertible to Tensors, or (potentially nested) lists of datatypes
            convertible to tensors.
        version: str, version of the model to target.
    Returns:
        Mapping[str: any]: dictionary of prediction results defined by the
            model.
    """
    # Create the ML Engine service object.
    # To authenticate set the environment variable
    # GOOGLE_APPLICATION_CREDENTIALS=<path_to_service_account_file>
    SCOPES = ['https://www.googleapis.com/auth/cloud-platform']
#   API & Services -> Credentials -> Create Credential -> service account key
#   Set json file
    SERVICE_ACCOUNT_FILE = r'C:\xxxx.json'
    
    credentials = ServiceAccountCredentials.from_json_keyfile_name(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES)

    # Need to set timeout, since prediction takes time
    http = httplib2.Http(timeout=2000)
    http = credentials.authorize(http)
    service = googleapiclient.discovery.build('ml', 'v1', http=http)

    #use default version,  change project name and model name
    name = 'projects/{}/models/{}'.format('project name', 'model name')

    image = Image.open(r'C:\test01.jpg')
    print(image)
    image_np = load_image_into_numpy_array(image)
    print(image_np.shape)
    image_list = image_np.tolist()
    
    print('predict start:'+ name)
    response = service.projects().predict(
            name=name,
            body={'instances':[ { "inputs": image_list} ]}
        ).execute()
        
    print('predict end:'+ name)

    if 'error' in response:
        raise RuntimeError(response['error'])
        
    json_dump_data = json.dumps(response['predictions'])
    json_data = json.loads(json_dump_data)
    
    return json_data

def main():
    """Send user input to the prediction service."""
    try:
        result = predict_json()
    except RuntimeError as err:
        print(str(err))
    return result

if __name__ == '__main__':
    result = main( )
    print(result)
    print(result[0]["detection_scores"][0])
    print(result[0]["num_detections"])
    print(result[0]["detection_boxes"][0])
    print(result[0]["detection_classes"][0])
```