機械学習のことはまだあまり良くわかってないけど、Amazon Machine Learningを使って売上を予測するBOTをつくった

とあるECサイトを運用しているのですが、Amazon Machine Learning（以下AmazonML）を使って17時段階の売上金額から、その日の売上を予想するBOTを作ってみました。

![karin.png](https://qiita-image-store.s3.amazonaws.com/0/6808/8787c472-c716-887e-fd84-b3b815eae7fc.png "karin.png")
（※プライバシー保護の観点から目線をいれております）

処理は
**heroku（node.js） -> API Gateway -> Lambda -> Amazon Machine Learning -> chatworkへ**

となっています。

## ■AmazonMLの設定

機械学習を使ってみる導入として、AmazonMLは手軽に使えるのでとってもいいですね。
ただ、やっぱりなんも知識がないと、ただ設定をするだけでも結構しんどいです。

さらにAmazonMLは日本語対応はまだなので、意味がよくわかっていないものを英語で書かれていて2倍苦しいです。

そこで、最低限覚えておくべきポイントがあります。
下記が参考になるので、一通り目を通しておくといいかと思います。
http://dev.classmethod.jp/cloud/start-amazon-machine-learning/

特に分析方法の

- 二項分類（Binary Classification）
- 多項分類（Multiclass Classification）
- 回帰分析（Regression）

は抑えておきましょう。
自分がやりたい分析はこの3つの中のどれなのかを、知っておくと理解が深まります。


設定の大枠の設定の流れは
[Amazon Machine Learningを理解するために3つの方法で天気予測をしてみた（回帰分析編）](http://recipe.kc-cloud.jp/archives/7753)


こちらを参考にするとわかりやすいです。

### == 学習用データを用意する ==
まずはデータがないと始まりません。
データの置き場所として、AmazonMLはS3とRedshifが選択できます。
今回は手軽にできるS3を使ってデータをAmazonMLに登録してみます。

#### CSVデータの用意

S3の場合はCSVデータをS3上に配置した後、AmazonML側でデータソースの場所を指定することになるので、まずはデータをCSV形式で用意します。

この時ヘッダー（カラム名）は含めても、含めなくてもよいのですが、カラム名がないと後でみた時にそれぞれの列の意味がわからなくなってしまうので、 **カラム名の行を含めたCSVファイルを作成することをおすすめ。**

カラム名は現在日本語だと文字化けしちゃうので、英語表記がいいです。

今回は

- 日にち
- 曜日
- 17時時点での売上金額
- 17時時点での販売個数
- その日の売上金額

という感じのCSVデータを2016年1月〜2016年7月までの分だけ用意しました。
なぜ2016年1月からのデータかといいますと、、、サービスが新しいサービスでかつ去年終わりぐらいから急成長している関係上、去年のデータはあまりあてにならないと思いデータソースとして使うのは辞めました。

正直学習データとしては少ないので、あんまり正確なデータは出せないと思ったのですが、そんなに正確性を求められるものでもないし、その辺は今後のバージョンアップで対応をしようかなと思ってます。

#### データソースの作成

ここからはAmazonMLの画面上で操作していきます。
各画面の細かい設定は省いてポイントだけ上げていきます。

Create new... -> Create Datasource を選択

**CSVファイルを指定**
S3に上げたデータソース用のCSVファイルを指定します。

**スキーマの設定**
用意したCSVファイルで、それぞれのカラムのデータタイプを指定します。  
画面上には先ほど選択したCSVデータがテーブル形式で表示されています。

まずは
**Does the first line in your CSV contain the column names？**
をYesにしておきましょう。


すると、これが
![schema1.png](https://qiita-image-store.s3.amazonaws.com/0/6808/8007a2de-52ec-41c2-2962-1f0e1efbd77c.png "schema1.png")


こうなります
![shcema2.png](https://qiita-image-store.s3.amazonaws.com/0/6808/2d11fdd1-8cb9-bc3a-cd62-2d664e4607aa.png "shcema2.png")

断然見やすい！

後はDataTypeの設定を適宜直していきましょう。
ただデフォルト設定で、大体問題ないかと思います。

**ターゲットの選択**
CSVデータの中から機械学習を用いて、分類、もしくは予測をしたいデータを選択します。

例えば与えたデータから男性と女性に分ける二項分類をしたい場合には、学習データの中に男性か女性かを判別するカラムがあるはずなので、そのカラムを選択します。

今回は売上の予測をするので、売上カラムを選択しました。
カラムを選択すると、それに合わせて分析方法も自動的に選択されます。

売上予測は回帰分析になるのですが、ちゃんとRegressionが選択されました。


### == モデルを作成 ==
Create new... -> Create ML Modelを選択
モデルの作成は基本全部デフォルトで問題ないです。

作成したモデルを元に次の評価に移るのですが、そこでの精度に問題がありそうな場合は、このモデルの設定をチューニングしていくことになるかと思います。


### == モデルの評価(Evaluations) ==
作成したモデルを評価します。
しばらくすると、選択した分析タイプに応じた結果が表示されます。

今回は回帰分析（Regression)なのでRMSEという数値に注目します。
RMSE自体はよくわかってないのですが、とりあえずここの数値が0に近いほうが正しい値がとれるということらしい。

![Amazon Machine Learning Management Console.png](https://qiita-image-store.s3.amazonaws.com/0/6808/58ebb177-0e34-d4df-8e81-eb12a0c06625.png "Amazon Machine Learning Management Console.png")

全然ダメですね...
まあ今回はそもそもデータが200件ぐらいしかないのでしょうがないです。


### == リアルタイムのエンドポイント作成 ==

外部アプリケーションからAmazonMLの結果を取得できるように、リアルタイムエンドポイントを作成します。

#### どんな値が返ってくるか確認してみる
Dashboard -> <作成したモデル> -> Try real-time predictions
からAmazonMLのAPIを実行した時にどんな値が返却されるのかを確認できます。

予測に必要なデータを入力して、「Create prediction」ボタンを押してみましょう。

すると、、、

```
{
    "Prediction": {
        "details": {
            "Algorithm": "SGD",
            "PredictiveModelType": "REGRESSION"
        },
        "predictedScores": {},
        "predictedValue": 1034293.6875
    }
}
```

返却されるJSONが表示されます！
predictedValueが予測した値になるので、こいつを取得すればOKですね！

#### エンドポイントを作る
返却される値が問題なさそうであれば、次にAPIの受け口をつくります。（お金かかります）

Dashboard -> <作成したモデル> -> Summary -> Predictions -> Create endpoint

でさくっと作れます。

ここで、エンドポイント用のURLが発行されるのでこれを呼び出すようにしてあげればいける。


## ■Amazon Lambdaの設定

AmazonMLの設定は終わったので、ここからは先ほど作成したAPIを呼び出すようにしてみます。

### == ロールの作成 ==
LambdaからAmazonMLにアクセスするためのロールを作成しておきます。
1. IAMの管理ページにアクセス
2. ロール -> 新しいロールの作成
3. ロールタイプに「AmazonMachineLearningRealTimePredictionOnlyAccess」を選択
4. ロールの作成


### == Lambda Function ==
Lambdaを介してAmazonMLを実行するので、Lambdaに呼び出し用のコードを書きます。

#### 1： 「Create a Lambda Function」をクリック

![lambda_create.png](https://qiita-image-store.s3.amazonaws.com/0/6808/e754031a-82a3-abf8-3ce6-511f8ba9c74b.png "lambda_create.png")

#### 2： 「Configure function」を選択

![lambda_config.png](https://qiita-image-store.s3.amazonaws.com/0/6808/2323d9c3-6a9b-6920-7374-35f452a72cf9.png "lambda_config.png")

#### 3： Lambda Functionのソースを記述

```
exports.handler = (event, context) => {
    var aws = require('aws-sdk');
    aws.config.region = 'us-east-1'; //日本リージョンは無い
    
    var ml = new aws.MachineLearning();
    data = [];
    data["week"] = String(event["week"]);
    data["sum_payment_total_mid"] = String(event["sum_payment_total_mid"]);
    var params = {
        MLModelId: '<ml-xxxxxx>の形式のモデルID',
        PredictEndpoint: 'AmazonMLのエンドポイントURL',
        Record: data
    };
    
    ml.predict(params, function(err, data){
        if(err) {
            context.fail(err.stack);
        }else{
            context.succeed(data['Prediction']);
        }
        
    });
};
```

わざわざStringでキャストしているのですが、Numericでデータタイプを登録していても、文字列として渡さないとだめだったのでこうしました。

`{"errorMessage":"InvalidParameterType: Expected params.Record['hogehoge'] to be a string`
↑こう怒られます。

#### 4: ロールを選択
![select_role.png](https://qiita-image-store.s3.amazonaws.com/0/6808/7b9669b2-14e7-7c44-f580-feb6b2872252.png "select_role.png")


### == 作成したLambda Functionをテスト ==

1. Functionsから作成したLambda Functionを選択

2. Actions -> Configure test event

![スクリーンショット 2016-08-12 18.32.42.png](https://qiita-image-store.s3.amazonaws.com/0/6808/61308542-1c99-469a-8674-858470335ab9.png "スクリーンショット 2016-08-12 18.32.42.png")

3. POSTしたいJSONを入力
（valueは全部文字列として書かないと多分エラーになる）


## ■API Gatewayの設定
API Gatewayを使うのは直でAmazonMLのAPIを呼びだそうとすると認証やらなにやらで面倒になりそうなので使ってます。

#### 1: APIの作成
![api.png](https://qiita-image-store.s3.amazonaws.com/0/6808/2a719f13-cf3e-f18e-32da-bf234a932ae9.png "api.png")


#### 2: メソッドの作成

POSTを選択
![api_post.png](https://qiita-image-store.s3.amazonaws.com/0/6808/bee94f64-1c87-8432-c344-27080b1da302.png "api_post.png")

#### 3: 統合タイプを選択

![api_lambda.png](https://qiita-image-store.s3.amazonaws.com/0/6808/a551a22d-60c8-427f-aa33-e058edf9dbb6.png "api_lambda.png")

LambdaリージョンとLambda関数も作成したものに合わせて選ぶ

するとAPI GatewayのURLが発行されます。
認証が必要な場合は適宜APIキーを設定。


## Chatworkボットの作成
Hubotを使ってAdapterにchatwork adapterを使用します。
Hubot x chatworkは
http://qiita.com/shibasaka/items/c76cb639c6ddfc60cdc6

この辺りがよいです。

実際のAPIを呼び出しているあたりのソースを見てみましょう
エラーハンドリングとかは結構適当...

```
cronJob = require('cron').CronJob
request = require("request")
require("date-utils")


module.exports = (robot) ->
  send = (channel, msg) ->
    robot.send {room: channel}, msg

  # Crontabの設定方法と基本一緒 *(sec) *(min) *(hour) *(day) *(month) *(day of the week)
  new cronJob('0 00 17 * * 1-5', () ->
    # 今の売上を取得
    options =
      url: "<売上取得用のAPI>"
      json: true
    request.get options, (error, response, body) ->
      if !error && response.statusCode == 200
        sales = <現在の売上>

        dt = new Date()
        today_data = {
          "create_date": dt.toFormat("YYYY-MM-DD"),
          "week": dt.getDay(),
          "sum_payment_total_mid": sales
        }

        # API Gatewayに設置したAPIを実行
        # Lambdaを介してAmazonMLにデータをPOSTする
        options =
          url: "<API GatewayのURL>"
          json: today_data
          
        request.post options, (error, response, body) ->
          if !error && response.statusCode == 200
            # 最後に結果をchatworkにpost
            send process.env.HUBOT_CHATWORK_ROOMS, "ふむ、今日は........." + Math.floor(body["predictedValue"]) + "円じゃな"
  ).start()
```


### 使ったライブラリとか

今回はbotへ呼びかけるタイプではなく、定期的に呼び出すようにしたかったので`node-cron`のライブラリを使います。
これを使うとcronっぽい設定ができるので、定期的に何かをbotに実行させるのに便利。

http://qiita.com/hkusu/items/fce98bb52a14e47c06d9


売上の取得は **re:dash** を使ってます。
re:dashを使えばSQLの結果をAPI化してくれたりするので、お手軽にいろんなことができるようになります。
http://redash.io/

## つくってみて

とりあえず機会学習を絡めた何かをやってみたかったので、今回はAmazonMLを使ってみようと思ったのですが、せっかくなんで何かしら動くものをつくろうと思って始めました。

とっかかりでの理解は結構時間がかかりましたが、雰囲気がつかめてくるとなんだかいろいろできそうなことがあるなーとイメージ湧いてきて楽しいです。

今回でてきた、とあるECサイトはこちら
https://www.canvath.jp/