トランプ氏のツイートを機械学習し、為替の予測をしてみた。〜GCP ML系使い倒し〜

GCPのML系機能を使いまくりたい・・という時にちょうど良い題材があったのでやってみました。GCPは機械学習を行う上で必要なデータ取得、preprocessing、学習と予測まで、フルマネージドな環境が揃っています。今回はその中で以下を使用しました。

- ML Engine
- Dataflow
- BigQuery
- Natural Language API
- Datalab

コードは全てDatalabで実行しました。開発環境を整える必要もなく、インタラクティブに結果を見られるのでGCPのML系を触るときは特におすすめです。

# 概要
色々発言が注目されるトランプ氏ですが、市場への影響はどれ位でしょうか？ツイートの後と通常(ランダムに時間帯を選択)でUSDJPYの価格変動がどう違うか比較します。
**ランダムな日時**
![ABibHYkyU1QAAAABJRU5ErkJggg==.png](https://qiita-image-store.s3.amazonaws.com/0/162369/bb6dd448-ea82-14bd-500b-893847200836.png)
**ツイート後**
![Ay6p1BCekqe0AAAAAElFTkSuQmCC.png](https://qiita-image-store.s3.amazonaws.com/0/162369/10a0d32e-8836-91d4-020f-7f0fafa7e6eb.png)

横軸は分、縦軸は変動(円)です。きちんと分散をみていませんが、ツイート後10分は荒れていますね。そこで今回は氏のツイート内容を感情分析し、その結果と為替の変動を学習し予測します。

# 1. Twitter APIでツイートを取得する
API自体は色んな方が紹介されていますね。早速トランプ氏のタイムラインを取得しましょう。まずはユーザー名を検索します。"donald trump"っと。
<img width="452" alt="donald.png" src="https://qiita-image-store.s3.amazonaws.com/0/162369/11e1721a-19b1-431f-1180-6c281bef6d76.png">
・・・何という事でしょう！同姓同名どころか、**オレンジ色の顔まで同じ人**がこんなにいるなんて・・・
とりあえず**本物**と自称しているrealDonaldTrumpさんのツイートでも取得してみます。*POTUSの方は本人がツイートしてるわけじゃなさそうなのでパス

```py:Tweet取得
def fetch_tweets(screen_name):
    from twitter import Twitter, OAuth
    from datetime import datetime
    import pytz
    
    token = '{token}'
    token_secret = '{token_secret}'
    consumer_key = '{consumer_key}'
    consumer_secret = '{consumer_secret}'

    t = Twitter(
        auth=OAuth(token, token_secret, consumer_key, consumer_secret),
        retry=True)
    
    max_count = 200
    kwargs = {'screen_name':screen_name,
              'count':max_count}
    while True:
        result = t.statuses.user_timeline(**kwargs)
        if len(result) == 0:
            break
        
        for r in result:
            tm = datetime.strptime(r['created_at'],
                                   '%a %b %d %H:%M:%S +0000 %Y').replace(tzinfo=pytz.UTC)
            yield {'time': tm.strftime('%Y-%m-%d %H:%M:%S'),
                   'text': r['text'],
                   'tid': r['id']}
        
        kwargs['max_id'] = result[-1]['id']
```
`max_id`で数珠繋ぎにPagingを繰り返します。Over Quota時のリトライはライブラリ側で勝手にやってくれるようです。


# 2. トランプ氏の感情を分析する
怒ってるだけでしょ？と思うなかれ、ツイートの内容から氏の感情を分析してみます。
感情分析自体はQiitaでも多くの方が書かれているので細かい話は書きませんが、ちゃんとやろうと思うと結構大変です。（ちゃんとやりたい方は[この辺](http://help.sentiment140.com/for-students)に学習データが置いてあるのでチャレンジしてみてください）
今回はGCP ML系使いこなしなので[Cloud Natural Language API](https://cloud.google.com/natural-language/)を使ってみます。
<img width="344" alt="nl.png" src="https://qiita-image-store.s3.amazonaws.com/0/162369/497da915-cb73-25fa-7de9-526b1e35cedf.png">
こちらのAPIはただテキストを渡せば感情をスコアで返してくれるので楽チンです。Scoreがプラスだとポジティブ、マイナスだとネガティブな文章と判断になり、Magnitudeはその強さの指標となります。[こちらの公式ブログ](https://cloud.google.com/blog/big-data/2017/01/new-features-in-the-google-cloud-natural-language-api-thanks-to-your-feedback)によると文章中の感情値の発生数により増加するとあります。

```py:感情分析
def analyze_sentiment(row):
    import time
    from google.cloud import language

    # Instantiates a client
    language_client = language.Client()

    # The text to analyze
    document = language_client.document_from_text(row['text'])

    # Detects the sentiment of the text
    while True:
        try:
            sentiment = document.analyze_sentiment().sentiment
            break
        except:
            time.sleep(100)
    
    ret = {'score': sentiment.score,
           'magnitude': sentiment.magnitude}
    ret.update(row)
    return ret
```
ほぼサンプルそのままです。100秒あたりのリクエスト数制限があるため、APIがエラーしたらウェイトを入れてリトライさせます。(後述するDataflowで並列に実行する場合は注意が必要です。急にウェイトが入るとタスクの実行時間推定が大きくバラつき、パフォーマンスが落ちる場合があります。今回はworker数を抑えているので安直にこうしています。)

# 3. BigQueryに結果を保存する
1-3は[Dataflow](https://cloud.google.com/dataflow/)で一気に実行します。

```py:Dataflowパイプライン
(p | 'user name'         >> beam.Create(['realdonaldtrump'])
   | 'fetch tweets'      >> beam.FlatMap(fetch_tweets)
   | 'analyze sentiment' >> beam.Map(analyze_sentiment)
   | 'write to bq'       >> beam.Write(beam.io.BigQuerySink('twitter.trump',
                              schema="tid:INTEGER, text:STRING, time:DATETIME, score:FLOAT, magnitude:FLOAT",
                              write_disposition=beam.io.BigQueryDisposition.WRITE_APPEND,
                              create_disposition=beam.io.BigQueryDisposition.CREATE_IF_NEEDED))
)

p.run() #.wait_until_finish()
```
はじめにユーザー名を指定すると、Twitterのタイムラインを取得し、NL APIで解析結果を付加しBigQueryに格納するまでを一気に行えます。これで「**そのオレンジは本物じゃない！**」と言われても安心です。ユーザー名を書き換えて実行し直せば良いだけですからね。
実行結果は以下のようになりました。
<img width="255" alt="dataflow.png" src="https://qiita-image-store.s3.amazonaws.com/0/162369/c2a80f32-9a1e-318e-e616-de3a4f000e88.png">
意外と早く終わったなぁと思ったら、Twitter APIでタイムラインは過去3200件までしか遡れないようです。氏のタイムラインだとちょうど１年分でした。

# 4. 分析結果を少しみてみる
感情分析したので、時系列的に傾向があるかどうかプロットして確かめてみます。縦軸が上に行くほどポジティブ、下に行くほどネガティブです。一年前(2016年4月頃、チャート左側)は割とポジティブですが、全体的にはネガティブな発言も多いようです。
![bAZsKuUgAAAAASUVORK5CYII=.png](https://qiita-image-store.s3.amazonaws.com/0/162369/3e32e6b2-af7a-a649-48c7-d4deabc60b10.png)
参考までに、オバマ前大統領の場合。
![Ac7UL5r6GmOlAAAAAElFTkSuQmCC.png](https://qiita-image-store.s3.amazonaws.com/0/162369/48b118da-0153-788f-e922-4c7c99c9adfc.png)
はい、ほぼポジティブですね。ツイート数が違うので期間は異なります(2014/7/31〜)が、正確に比較するまでもないでしょう。

トランプ氏のツイートに戻ります。
ちなみに一番ポジティブな発言は何でしょうか？BigQueryで調べてみます。

```sql
SELECT * FROM [twitter.trump] WHERE score > 0 ORDER BY magnitude DESC
```

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">A fantastic day in D.C. Met with President Obama for first time. Really good meeting, great chemistry. Melania liked Mrs. O a lot!</p>&mdash; Donald J. Trump (@realDonaldTrump) <a href="https://twitter.com/realDonaldTrump/status/796897928048766976">2016年11月11日</a></blockquote>

オバマ前大統領と会った時のツイートですね。確かにべた褒め。
次にネガティブですが・・

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">FAKE NEWS media knowingly doesn&#39;t tell the truth. A great danger to our country. The failing <a href="https://twitter.com/nytimes">@nytimes</a> has become a joke. Likewise <a href="https://twitter.com/CNN">@CNN</a>. Sad!</p>&mdash; Donald J. Trump (@realDonaldTrump) <a href="https://twitter.com/realDonaldTrump/status/835325771858251776">2017年2月25日</a></blockquote>

NYタイムズとCNNをディスってるツイートです。これ実はscoreは-0.3なのでまだまだ上があります。過激なので転載しません。。

---

さて、次は学習です。

# 5. モデル学習
特徴データには`Score`と`Maginitude`を、ラベルはツイート後10分の価格変動上下2クラスとします。

特徴もデータ数も少ないので小さめのDNNでいいでしょう。[こちらで紹介したMagicコマンド](http://qiita.com/hayatoy/items/5f44e28278931c2d60b7)を使うとDatalabから学習ジョブをCloud ML Engineに投げられます。

```py:学習
import tensorflow as tf

feature_columns = [tf.contrib.layers.real_valued_column("", dimension=2)]
classifier = tf.contrib.learn.DNNClassifier(feature_columns=feature_columns,
                                            hidden_units=[10, 20, 10],
                                            n_classes=2,
                                            model_dir="./trumpmodel")

classifier.fit(x=x_train,
               y=y_train,
               steps=2000,
               batch_size=50)
```

と、ここまでやっておいて学習するならやっぱりコーパスから特徴ベクトル作った方が良かったかなぁと後悔。でも一応ジョブを流してみました。

・・・うーん、学習が進みませんでした。感情分析結果をそのまま使ってトレードした方がまだ良さそうです。

例えばMagnitudeが1以上の時にLongした場合の資産推移。
![2MAQmtxZecAAAAABJRU5ErkJggg==.png](https://qiita-image-store.s3.amazonaws.com/0/162369/e8b2c49a-55a8-2af3-a559-7255af796d4f.png)
一応右肩上がりにはなりました。

# まとめ
今回、<strike>題材は置いておいて</strike>システムとしては機械学習のフローでベースとなるような構成になったのではないかと思います。
GCPは機械学習のベストプラットフォーム！だと信じてやまない私ですが、利用者が増えて色んな使い方がシェアされるといいですね。