Facebookの時系列予測ライブラリProphetを使って株価予測をやってみた
ブログかなりご無沙汰になってました。
今回は、今年2月にFacebookから公開された時系列予測のためのPythonライブラリProphetを使って、株価データの時系列予測をやってみようと思います。
【環境】
Python 3.6.1(anaconda3-4.4.0)
Mac 10.12.4
※ コードはJupyter環境で書いています
Prophet
https://github.com/facebookincubator/prophet
まずはpipでProphetをインストールしましょう。
$ pip install fbprophet
ここでもし以下のようなのエラーが発生したら、 pipでPyStanをinstallしてからProphetをインストールしてください。
Command “python setup.py egg_info” failed with error code 1 https://github.com/facebookincubator/prophet/issues/140
$ pip install pystan $ python setup.py install
データの読み込み
インストールができたら、次はデータを読み込んでいきます。
データは、Yahoo!ファイナンスから取得した株価データを使います。
csvファイルを読み込む形でもできますが、今回はDBにデータを保持しているため、DBから引っ張ってきました。
%matplotlib inline import pandas as pd import numpy as np import MySQLdb as mysql # mysql con = mysql.connect(config) cur = con.cursor(mysql.cursors.DictCursor) price_data = [] -------------------- # mysqlからデータを取得 (今回は 2016年1月1日 ~ 2017年6月9日) price_data.append({'ds': ***, 'y': ***}) -------------------- max_price = max(price_data, key=(lambda x: x['y']))['y'] df = pd.DataFrame(price_data)
モデルの作成
予測モデルは簡単に作成することができます。
モデルを作成するにあたり、以下の点に注意する必要があります。
トレンド(線形、非線形)
時系列トレンドを線形(linear)として予測するか、非線形(logistic)として予測するかの設定です。
今回扱う株価データは非線形なので、growthにlogisticを指定します。また、logisticを指定した場合、上限(キャパシティ)を指定する必要があります。
周期性(週周期、年周期)
曜日に依存するデータか、季節に依存するデータかをそれぞれ設定します。
引数名はそれぞれ、weekly_seasonality, yearly_seasonalityです。
from fbprophet import Prophet model = Prophet(growth='logitic', weekly_seasonality=False, yearly_seasonality=True) df['cap'] = max_price # 上限の指定 model.fit(df) # 学習(フィッティング)
予測
予測したい期間を指定し、空のDataFrameを作り、予測をします。
future = model.make_future_dataframe(periods=5) future['cap'] = max_price forecast = model.predict(future) print(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail())
printの結果は以下のようになります。
(ここでは例として、トヨタ自動車(証券コード: 7203)の結果を記載します)
ds | yhat | yhat_lower | yhat_upper |
---|---|---|---|
2017-06-10 | 5937.594568 | 5743.531806 | 6127.172213 |
2017-06-11 | 5921.758918 | 5734.129259 | 6095.127343 |
2017-06-12 | 5902.988836 | 5699.916425 | 6092.724544 |
2017-06-13 | 5881.427094 | 5695.766267 | 6068.017899 |
2017-06-14 | 5857.283962 | 5659.352560 | 6044.636029 |
過去の時系列推移から、その後5日分の予測値を出しています。
(実際には10,11日は土日で、取引はないため、6月12日~16日の予測)
実際の株価は以下のようでした。
date | price |
---|---|
2017-06-12 | 5880 |
2017-06-13 | 5874 |
2017-06-14 | 5859 |
2017-06-15 | 5794 |
2017-06-16 | 5794 |
可視化
Prophetにはあらかじめ、plotというメソッドが用意されているので、 そのままplotを呼び出すだけで可視化してくれます。
model.plot(forecast)
まとめ
時系列分析についてはあまり勉強してこなかったので、 知識がそこまであるわけではないですが、ライブラリ自体が誰でも使えるように作られているので、 簡単に時系列分析をすることができました。
時系列データはそこかしこにあふれているので、 いろいろなデータに対して適用することができ、 分析から知見が得られると思うので、みなさんもぜひ試してみてください。
ウェアラブルデバイスのログで自己分析してみる
2ヶ月ほど前に自分自身の行動ログを収集したいなと思い、フランスのWithings社が出している腕時計型のウェアラブルデバイスWithings Pulse O2を購入しました。以来、ほぼ毎日、お風呂と仕事中以外は腕に付け、歩数、心拍数、睡眠時間などのログを取っています。
Withings Pulse O2 Wireless Activity and Heart Rate Tracker black [並行輸入品]
- 出版社/メーカー: Withings
- メディア: スポーツ用品
- この商品を含むブログを見る
今回はこのウェアラブルデバイスのデータをAPIで取得し、自分自身の行動を可視化してみました。
1. 準備
WithingsのAPIを使うためには、まずここでデベロッパー登録をする必要があります(Withingsのアカウントを持っていない場合はアカウントの作成から)。登録をすると、Consumer KeyとConsumer Secretが取得できるので、メモしておきましょう。
次に、Withings API developer documentationで書かれてある通りにStep1から順に実行していき、ユーザーIDとAccess Token, Access Token Secretを取得し、こちらもメモしておきましょう。
とりあえずこれで、データにアクセスする準備は出来ました。
2. データを取得・前処理
次に、実際にデータを取得し、Rで処理しやすいような形に整形します。 APIを叩く部分とデータの整形はRubyで実装します。Withings APIを使うための便利なgemがあるので、まずはsimplificator-withingsというgemをインストールしましょう。
> gem install simplificator-withings > gem install ruby-hmac
そして、以下が記録されたデータを取得するサンプルコードです。
実行すると、以下のようなcsvファイルが作成されます。
取得できるデータは、1日ごとの歩数(steps)、歩いた距離(distance)、消費カロリー(calories)、昇り降りの高低差(elevation)、軽めの運動をした時間(soft)、普通の運動をした時間(moderate)、激しい運動をした時間(intense)です。睡眠時間のデータも取得したかったのですが、まだできないようです。
ここまでで、必要なデータは揃ったので、後はExcelやRで見たいデータを可視化したり、統計量を求めたりしていきます。
3. Rで分析・可視化
前述のように、さまざまなデータが取得できますが、今回はその中でも最も特徴が出やすいであろう歩数(steps)について見てみます。 Rのsummary()関数を使って、基本統計量を求めると、
Min. 1st Qu. Median Mean 3rd Qu. Max. 88 3832 4800 5974 7170 14650
となりました。1日平均で約6,000歩しか歩いてません。目標にしている、1日平均1万歩にもっていくためには毎日今までの倍歩く必要があるようです。。 (最小値88は、ほぼ1日中つけ忘れていた日の値)
次に、日ごとの推移を見てみると、以下のようになりました。青の棒が土曜日、赤の棒が日曜日になっています。
この図から、平日に比べ、土日(特に日曜日)によく運動していることがわかります。曜日による差が明らかになるように、曜日別に箱ひげ図で可視化した結果が以下です。
明らかに平日に比べ土日の運動量が大きいです。原因は、スポーツクラブで運動しているからという単純な理由です。 そしてもうひとつ、月曜から金曜の部分も少しずつ違っていて、水曜、木曜の運動量が少なかったり、金曜が多かったりするので、なんとなく気分と相関してそうな気がしています。。
Rのコード
まとめ
実は、Withingsが提供しているダッシュボードを見れば日々の活動が可視化されているので、 今回のようなことをしなくてもおおまかなことはわかります。 しかし、当然ダッシュボードでは詳しい分析はされていないので、より詳しく知りたい場合に自分でデータをいじる必要があるという意味で今回のような分析は有用なのではないかと思います。
また、分析してみてわかったことは、運動量の大きい、つまりたくさん動いている日のほうが気分がいいし、それによって仕事の効率も良くなるのではないかということで、今後実践してみようと思いました。自分自身の気分と行動の関係などは自分自身でしかわからないので、分析してみるとおもしろいと思います。
ソーシャル×ECについて分析してみた
昨年10月に発表されたYahoo!ショッピングのeコマース革命など、非常にホットで、これからますます成長していくであろうEC分野ですが、ソーシャルとも非常に相性が良いということでも知られています。TwitterやFacebook上で友人・知人が話題にしている商品に反応し、思わず購入してしまった経験がある人も多いのではないでしょうか。特にTwitterは、ECにおいて商品選びに最も影響するSNSであると言われています。今回、Twitter上でどのような商品が最も話題になっているのか調べてみました。
分析方法
Amazon、楽天、Yahoo!ショッピングなどさまざまなECサイトがありますが、今回はAmazonの商品のみを対象としました。(1)ツイート本文中にアマゾンの短縮URL「amzn.to」(bit.lyの独自ドメイン利用)が含まれているツイートを収集し、(2)その短縮URLを展開、(3)そしてその展開したURLの中にアマゾンの商品コード(ASIN)が含まれていれば、それを集計するという流れで行いました。
(1) ツイートの収集
API制限にかからないよう、Twitterのsearch APIを1回/minの間隔でcron実行して叩きました。
(2) 短縮URLの展開
以下のようなスクリプトで展開し、展開したURLをDBに保存します。
(3) ASINの集計
一般に、世の中で売られている各商品には商品識別コードEAN(JAN)が振られており、商品ページのURL中にそのコードが含まれている場合が多いのですが、Amazonの場合は、URL中にEANは含まれておらず、ASINと呼ばれるAmazon内部の独自コードが含まれています。したがって、展開したURLからASIN部分のみを抜き出し、同じ商品がどのくらいつぶやかれているのかを集計しました。
分析結果1(bot込み)
分析対象日を2014年4月25日(金)として分析しました。まずは「amzn.to」を含むツイート件数の一分ごとの時系列推移です。
ツイート総数: 172,251
普通、ツイートの件数は昼に一度山があり、いったん下がって、夕方から夜にかけて再度件数が増えてくという曲線を描きますが、このグラフは1時間単位くらいの視点で見るとほとんど時間によって変動がありません。予想はしていましたが、Amazonの商品をおすすめしているのはほとんどがbotであるということがこのことからわかります。また、ある特定の時間だけツイート数が上昇していることからもbotが多いことが伺えます。
実際にツイートをしているクライアントツール上位15位は以下のようになりました。
統計によると、普段使用されるクライアントツールの上位1,2,3位はTwitter for iPhone, Twitter for Android, webであるらしいので、それとはかなり違うことがわかります。
そして、botによるツイートも含めて、ツイートされている商品の中で最もよく出現する商品(ASIN)のランキングは以下のようになりました。
1位.
- 作者: 宿利原卓
- 出版社/メーカー: ワニブックス
- 発売日: 2014/03/27
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
3位. iPhone5s iPhone5 iPad mini 対応 充電 データ通信用ケーブル 巻き取り式
4位. ドラゴンエイジ:インクイジション デラックス エディション (限定版) (ダウンロードコード(スカイホールドの王座、赤角のハラ、沼のユニコーン、審問会の炎)、デジタルサウンドトラック、ボーナスデジタルコンテンツ 同梱)
5位. 確実に稼げる不動産投資 副業入門
いかにもbotがつぶやいていそうな商品が上位を占めています。。。
これこそGIGO(Garbase in, Garbage out)で、ゴミをたくさん入れたらゴミな結果が出てきましたという感じなので、次にbotを除いたツイートのみで分析してみます。
分析結果2(人によるツイートのみ)
人間によるつぶやきのみを抽出して分析します。人によるツイートの抽出方法としては、人間が使用するクライアントツールであるTwitter for iPhone, Twitter for Android, web, Keitai Web, Twitter for iPadからの投稿を、簡易的に人間がつぶやいたものと判断し、それらのツイートを分析しました。まずはツイート件数の時系列推移から見てみます。
ツイート総数: 12,596
分析結果1と比較すると件数が非常に少ないことがわかります。Twitter上に出回るAmazon商品は意外と少ないんですね。
ツイートの中で最もよく出現する商品(ASIN)のランキングはこのようになりました。
1位.
- 出版社/メーカー: プレビジョン
- 発売日: 2014/04/28
- メディア: ムック
- この商品を含むブログを見る
3位. WHITE ALBUM2 雪が紡ぐ旋律 5 (GA文庫)
4位. 妖怪ウォッチ お知らせブザー ジバニャン YW-01A
5位. 医療につける薬: 内田樹・鷲田清一に聞く (筑摩選書)
こちらは、ランクインしている商品の商品ページを見てみると、最近話題になっているものや新着フラグがついているものなどが上位に来ていることから、その時話題になっている商品がきちんと抽出できているようです。アニメ系が多いのはTwitterとAmazonのユーザー層からしてなんとなくわかりますね。。
まとめ
Botによる投稿があまりにも多いので、実際に人がおすすめしている商品を抽出するには本分析の分析2のようなやり方をする必要があり、ノイズを取り除いた上でシグナルを見つけなければならないのだと思います。また、いくらECとソーシャルの相性が良いと言っても、これだけbotが多いと、本当の声がbotに打ち消されてしまう可能性が大きいよな、などと思ったりしました。
Chef Soloでelasticsearchの環境を作る
gemのバージョンはこんなかんじ。
berkshelf (2.0.10)
chef (11.8.2)
knife-solo (0.4.1)
複数台のサーバーにelasticsearchの環境を作ろうと思い、Chef Soloを使ってやってみた際のメモです。chefはあまり慣れてないので変なこと書いてるかもです。
リポジトリの作成
まずは、リポジトリを作成します。今回はchef-repoという名前のリポジトリにします。
$ knife solo init chef-repo $ tree chef-repo chef-repo ├── Berksfile ├── cookbooks ├── data_bags ├── environments ├── nodes ├── roles └── site-cookbooks
Cookbookを作成
今回はBerkshelfを使って、サードパーティのcookbookを使いたいので、Berksfileにelasticsearchの記述を追記し、cookbooksディレクトリの下にインストールします。cookbookをダウンロードするためには、Opscode Communityにユーザー登録したり、いろいろ設定したり必要ですが、それに関してはChef Soloの正しい始め方などを参考にされるとよいかと思います。
site :opscode cookbook ‘elasticsearch’
$ berks install -p cookbooks
roleを定義する
rolesディレクトリの下にelasticsearch.jsonファイルを作り、以下のように設定します。デフォルトでは、javaのバージョンが1.6.0_28、elasticsearchのバージョンが0.90.5でインストールされますが、より新しいバージョンのものを使いたいため、override_attributes
でattributesを上書きし、javaは1.7系、elasticsearchは0.90.7をインストールするように書いています。また、elasticsearchで使用するkuromojiなどのpluginのインストールも同時に行うように、elasticsearch::plugins
というかたちで書きます。pluginインストールの設定については後述。
{ “name”: “elasticsearch”, “default_attributes”: {}, “override_attributes”: { “java”: { “install_flavor”: “openjdk”, “jdk_version”: “7” }, “elasticsearch”: { “version”: “0.90.7” } }, “json_class”: “Chef::Role”, “description”: “”, “chef_type”: “role”, “run_list”: [ “recipe[java]”, “recipe[elasticsearch]”, “recipe[elasticsearch::plugins]” ] }
plugin
インストールしたいelasticsearchのpluginに関しては、data_bagで設定してあげる必要があります。data_bags以下にelasticsearchディレクトリを掘り、その下にplugins.jsonファイルを作ります。elasticsearchのバージョンが0.90.7をインストールするので、kuromojiは、1.6.0を使います。また、私はいつもHQ pluginを使っているので、そちらも同時にインストールします。
{ “id”: “plugins”, “_default”: { “plugins”: { “elasticsearch/elasticsearch-analysis-kuromoji”: { “version”: “1.6.0” }, “royrusso/elasticsearch-HQ”: {} } } }
cook
ここまでできたら、実際にcookしていきます。
$ knife solo prepare <host>
を実行し、nodes以下に作成された、\role[elasticsearch]
を書き入れます。
{ “run_list”: [ “role[elasticsearch]” ] }
そして、cookします。
$ knife solo cook <host>
終了したら、ssh \
$ java -version java version "1.7.0_45" OpenJDK Runtime Environment (rhel-2.4.3.4.el6_5-x86_64 u45-b15) OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)
$ curl -XPUT ‘localhost:9200/test/‘ -d ‘ { “index”:{ “analysis”: { “tokenizer”: { “kuromoji”: { “type”:”kuromoji_tokenizer”, “mode”:”search” } }, “analyzer” : { “kuromoji_analyzer” : { “type” : “custom”, “tokenizer” : “kuromoji_tokenizer” } } } } }' {"ok":true,"acknowledged":true}
こんなかんじになれば、きちんとインストールできているかと思います。
$ curl -XGET 'localhost:9200/test/_analyze?analyzer=kuromoji_analyzer&pretty' -d '関西国際空港' { "tokens" : [ { "token" : "関西", "start_offset" : 0, "end_offset" : 2, "type" : "word", "position" : 1 }, { "token" : "関西国際空港", "start_offset" : 0, "end_offset" : 6, "type" : "word", "position" : 1 }, { "token" : "国際", "start_offset" : 2, "end_offset" : 4, "type" : "word", "position" : 2 }, { "token" : "空港", "start_offset" : 4, "end_offset" : 6, "type" : "word", "position" : 3 } ] }
分かち書きもきちんとできました。 いろいろと細かい設定もしたいところですが、今回はここまで。
参考
TwitterネットワークにPagerankを適用する
前回の記事で、Pagerankを実装したので、今回はそのアルゴリズムを(自分の)Twitterのネットワークに適用してみました。
具体的な手順は、まず、Twitter APIを叩いて、対象アカウントがフォローしているユーザーのユーザーIDを取得します。このAPIは、一回のリクエストにつき最大5000件のIDを取得できます。それ以上フォローしている場合には、ページングして再度APIを叩き、次の5000件を取得します。また、API制限に引っかからないよう、sleepさせる処理が必要です。
次に、取得したIDのユーザーそれぞれがフォローしているユーザーを同様に取得します(かなり時間かかる)。そして、そのリストの中から、対象アカウントがフォローしているユーザー同士のリンクのみを抽出し、フォロー関係がある場合には1、ない場合には0とした行列を作ります。
そして最後に、出来上がった[フォロー数 × フォロー数]の大きさの行列にPagerankアルゴリズムを適用し、各ユーザーに対するPagerankの値を求めます。
これらの処理をRubyで実装したものが以下です。一部、DBを使ってます。
Calculate pagerank of twitter follow network.
API制限があることと、糞スパースな行列データにアルゴリズムを適用している影響で、フォロー人数が400人程度のアカウントで、処理が終わるまでに数時間かかりました。。。 (Pagerankの実装方法を違ったものにすればよかったと後悔)なので、nohupとかで実行するのがいいと思います。pagerank.csvとかのファイルに出力して、Rを使って可視化したものが以下です。
@hongo35がフォローしているアカウントの中で、ページランクの高かったアカウントTOP10は、こんなかんじになりました。
- @h_ototake
- @yukihiro_matz
- @KaworiM0531
- @sasakitoshinao
- @masason
- @AntiBayesian
- @t_wada
- @takapon_jp
- @meso
- @TechCrunch
すごく納得感があります。。
データ収集と計算に多少時間がかかってしまいますが、どんなアカウントのフォローネットワークに対しても適用することができるので、実際にやってみるとおもしろいかと思います。
RubyでPageRankアルゴリズムを実装する
みなさんご存知、Webページ重要度の自動判定アルゴリズムであるPageRankですが、この記事
PageRankアルゴリズムを使った人事評価についての実験 | 株式会社サイバーエージェント
を読んで、PageRankアルゴリズムを身の回りのさまざまなリンク構造を持ったデータに対して適用してみたいなぁと思い、実装してみました。Rとかで既存のライブラリを使って、データを入れたら結果だけが返ってきて、中でどんな計算してるかは知らない、みたいなのは何も理解したことにならないので、実際にアルゴリズムを調べて実装し、どんな計算をしているのかまで理解するためにやりました。
PageRankアルゴリズム
PageRankアルゴリズムは、「たくさんの良質なページからリンクされているようなページは、やはり良質なページである」という再帰的な関係をもとに、あらゆるページの重要度を計算したもので、引用に基づく学術論文の評価と似たアルゴリズムです。このアルゴリズムのポイントとしては、以下の3点が挙げられます。
1. 重要な論文はたくさんの論文から引用されるので、被引用数が多くなる
→ 重要なWebページはたくさんのページからリンクされるので、被リンク数が多くなる
2. 被引用数の多い論文から引用されている論文は、重要度が高い
→被リンク数の多いページからのリンクは価値が高い
3. 多くの論文を引用している論文からの引用は価値が低い
→ リンク元ページのリンク数が多いページからのリンクは価値が低い
(ページランク - Wikipediaより引用)
数学的に注意しなければいけないことは、ランダムウォーク(マルコフ連鎖)がエルゴード性(集合平均と時間平均が一致するという性質)を満たす必要があるということです。 そのエルゴード性を満たすためにテレポーテーションを導入します。つまり、
・確率αで, 今いるページにあるリンクを一つ等確率で選ぶ
・確率1 - αで、でたらめなページ(等確率で)にテレポート
という実装をします。そして、Rubyで実装したものがこちら。
実行結果
---------- alpha: 1.0 [0.16666674613952637, 0.27777934074401855, 0.33333325386047363, 0.22222065925598145] ---------- alpha: 0.8 [0.1785715084624, 0.2704091523808, 0.3214284915376, 0.22959084761919998] ---------- alpha: 0.5 [0.20000008174320807, 0.26000069598110753, 0.29999991825679195, 0.23999930401889247] ---------- alpha: 0 [0.25, 0.25, 0.25, 0.25]
参考資料で実装されているものと結果が同じになったので、これでおkかと。 一般的には、ダンピングファクターの値(コード中のalpha)は0.85くらいにします。
あとはTwitterのフォロー・フォロワーネットワークとかから行列データを作って、PageRankを実行してみたいなと思ってます。 それについてはまたこんど。
参考資料
Rubyで生成したネットワークデータをd3.jsで可視化する
学術分野としてはやや下火になっている分野ですが、私たちの友人ネットワークがどのように成長していくかをモデル化し、現実のソーシャルネットワークの形成過程を明らかにしようという研究があります。
一般的に、「友達の友達とは友達になる可能性が高い」や「コミュニティがネットワーク形成に大きく関係している」などと言われていますが、本記事では、「友達の友達とは友達になる可能性が高い」という特性に着目したモデルである、CNN(Connecting Nearest Neighbor)モデルを使ってネットワークデータを作り、そのデータをd3.jsで可視化してみたいと思います。
CNNモデル
CNN(Connecting Nearest Neighbor)モデルは、「友達の友達は友達」の関係に従ったネットワークモデルで、現実世界に存在するネットワークに共通する性質である「スケールフリー性」、「クラスター性」、「スモールワールド性」を満たすグラフを生成するアルゴリズムの一つとなっています。詳細アルゴリズムは、
(1) パラメータを設定する。
(2) 各ステップにおいて、以下のアルゴリズムを実行する。
(a) 確率でネットワーク中に新しいノードを追加し、すでにネットワーク中に存在するノードをランダムに選び、ノードとの間にリンクを張る。さらに、ノードの隣接ノードすべてに、ノードとの潜在的なリンク(ポテンシャルリンク)を設定する。
(b) 確率でランダムにポテンシャルリンクを選び、実際のリンクに変換する。
のようになっています。
ネットワークデータの準備
まず、CNNモデルを使って、ノードとリンクのデータを生成します。パラメータPの値と繰り返し回数については適宜変更してください。ただしこの後、d3.jsで可視化する際の描画負荷を考えると、繰り返しはmaxで500くらいにした方がいいです。
ruby cnn.rb > network.json
を実行すると、CNNモデルによって生成されたネットワークデータのjsonファイルができます。