Pythonでデータ分析:Catboost
導入
2017年7月に、ロシアのGoogleと言われている(らしい)Yandex社から、Catboostと呼ばれるGradient Boostingの機械学習ライブラリが公開されています。
ここ何ヶ月か調整さんになっていて分析から遠ざかりがちになりやすくなっていたのですが、手を動かしたい欲求が我慢できなくなってきたので、いい機会だと思って触ってみることにしました。
使用データセット:Titanic
Titanic号の乗客の情報および生存・死亡のデータを用いて、どの程度の精度のモデルができるかを試してみます。
データセットは、Kaggleのページからダウンロードしました。
Titanic: Machine Learning from Disaster | Kaggle
train.csvとtest.csvの2つを利用します。train.csvはSurvived(生存なら1、死亡なら0)のカラムがあるデータ、test.csvはSurvivedのカラムが無いデータになっています。train.csvを用いてモデルを作成し、test.csvからSurvivedの予測値を出し、Kaggleにsubmitすることで、モデルの精度(今回はAccuracy)を見ます。
train.csvのデータはdf_raw、test.csvのデータはdf_raw_testという名前で読み込んでおきます。
データ加工(笑)
Titanicのデータは、いくつか欠損があるカラムがあるのですが、今回は雑に-999で埋めます。また、乗客の名前などの非構造データも幾つかあるのですが、それらは予めインデックスを保持しておき、モデリングの際によしなにカテゴリカル変数として処理してもらうことにします(Catboostはそういった機能があったので、挙動確認も込めてあえて特徴量加工を頑張らないことにしました)。
# pandasやnumpyはインポート済み # 欠損値補完 df_raw.fillna(-999, inplace=True) # 説明変数・目的変数 X = df_raw.drop('Survived', axis = 1) y = df_raw['Survived'] # カテゴリカル変数として扱うカラム指定 categorical_features_indices = np.where(X.dtypes != np.float)[0]
訓練用と検証(Validation)用にデータを分割しておきます。
rom sklearn.model_selection import train_test_split X_train, X_validation, y_train, y_validation = train_test_split(X, y, test_size = 0.3)
モデリング
今回はクラス分類なので、catboost.CatBoostClassifierという関数を利用します。
import catboost mod = catboost.CatBoostClassifier(iterations=5000, calc_feature_importance = True, use_best_model=True, eval_metric = 'Accuracy' ) mod.fit(X_train, y_train, cat_features=categorical_features_indices, eval_set=(X_validation, y_validation), plot=True)
オプションについては、例えば以下のページにあります。
tech.yandex.com
今回は、CatBoostClassifierにはiterations:何回学習と検証を繰り返すか、calc_feature_importance:変数重要度を計算するか、use_best_model:iterationの中で一番検証用の指標が良いものを選ぶか、eval_metric:評価指標をオプションとして選んでいます。
モデル作成は、sklearnのようにfitメソッドで行います。cat_featuresでカテゴリカル変数として扱うカラムを指定し、eval_setで最初のvalidationに使用するデータを指定します。
実行後は、以下のような図がプロットされます。
これは、訓練・検証用データに対してAUCをプロットしたものです。点線が訓練用、普通の線が検証用なので、結構過学習しちゃってます(汗)。検証用データで一番良い場合はaccuracyが0.88程度のようです。
from sklearn.metrics import accuracy_score y_val_pred = mod.predict(X_validation) print('accuracy : %.2f' % accuracy_score(y_val_pred, y_validation)) # 出力 accuracy : 0.88
テストデータを用いて、予測してみます。これはsklearnの場合と同様に、predictメソッドで出力できます。ただし、floatとして出て来るので、出力後にintに変換します。こうしておかないと、Kaggleにsubmitしたときにscoreが0になります。
# テストデータはdf_raw_test df_raw_test.fillna(-999, inplace = True) y_test = mod.predict(df_raw_test) est = DataFrame() test['PassengerId'] = df_raw_test['PassengerId'] test['Survived'] = y_test # 予測値をintに変換 test['Survived'] = test['Survived'].astype('int') # 保存 test.to_csv('out/benchmark_catboost.csv', index = False)
この結果をKaggleにsubmitすると....
ほぼほぼ何も頑張ってないのにaccuracyが0.8を超えました。ただ、過学習しているモデルなので、より安定して精度を出したかったらパラメータ調整などをおこなって汎化性能を上げる必要があるかと思います。そういった努力は今後の課題にします。
変数重要度
Catboostはfeature_importances_で変数の重要度も出力することができます(ソートはされていない)。ざっくり可視化しておきます。
FI = DataFrame() FI = FI.append(mod.feature_importances_) FI.index = X.columns FI.columns = ['Feature_Importance'] # 可視化 import matplotlib.pyplot as plt %matplotlib inline FI.plot(kind='bar')
Pclassは乗客の社会的地位、Sexは性別、Ageは年代で、地位の高い女性や子供が優先的に救出されたという歴史的経緯が概ね反映されています。Ticletが効いているのはちょっと難しいですが。
Next Step
大雑把には使い方が分かったので、今後はGrid Searchなどを詰めていって、より使いこなせるようにしていこうと思います。
Memo:MacOS SierraでXGboostをpipで入れる
XGboostを自宅のMacに入れようとしても入らなかったので、調べてみたことを備忘録として残しておきます。
以前との差分を考えてみたら、MacOSをSierraにアップデートしてたことに気が付き、調べると以下の記事がヒットしました。
上の記事では、clang-ompのエイリアスの設定もやっているんですが、自分の場合はそれをやらなくてもXGboostがpipで入れられることを確認しました。またまたちょっと調べると、LLVM 8.1から公式のclangがOpenMPに対応したため、clang-ompはこれ以上更新されないと。そのため最新のLLVMを導入したほうがいいとのこと。自分はclang-ompを入れていないので、気にしなくても大丈夫(だったと理解)。
というわけで、OSが変わると色々面倒なことがあるということを再確認したのでした。
NN論文の読み会で発表した
はじめて外部勉強会なるもので発表をしました。
tfug-tokyo.connpass.com
今回の論文のテーマはDeep Learning + 自然言語処理系で、私は全然キャッチアップしてなかったところだったので、勉強(炎上ラーニング)を兼ねて申し込んでみました。私が選んだ論文は、Amazonが行っているソーシャルボット(対話型ボット)のコンペ出場チームの報告論文です。
www.slideshare.net
昔ながらの人工無脳から、NNを使った最近の手法をふんだんに使って複数の会話の応答候補を出力し、その中でよいものを深層強化学習(実際にはそこまで強化学習してない)で最適化するという内容で、いろいろ知識を得るのに非常に役立ちました。2週間くらいで前提知識ほぼゼロからキャッチアップして発表というスケジュールだったので、非常に苦しかったですが(笑)
発表はあまりうまくできなかった気がしますが、いろいろな人からたくさん質問をいただいて、非常に楽しい時間でした。また次回以降、もしくは別の勉強会でも発表して多くの人と交流していきたいと思いました。
社内でKDD2017論文を紹介した
社内の有志でKDD2017の論文紹介をしました。
紹介した内容はスパース推定に関するアルゴリズムの話です。
発表資料をslideshareに公開したので、そのリンクをこちらにもはっておきます。
Qualityがよくないかもしれませんので、ご質問やご意見がありましたらご連絡いただけると幸いです。
www.slideshare.net
KerasでDeep Learning:LSTMで日経平均株価を予測してみる
導入
前回までで、画像データに関してDeep Learningを試してきました。画像データは、各データが独立と期待されるようなタイプのデータです。しかしながら、Deep Learningはこのような各データが独立であるような場合だけでしかできないというわけではありません。データ間に相関がある場合の代表例として、時系列データがあります。今回は、時系列データに対して威力を発揮するネットワークをKerasで実装してみます。
使用データ
人工データを使うのもあれなので、より現実的で身近なデータを使ってみます。今回は、日経平均株価の終値(日次)を使います。日経平均株価のデータは、以下のサイトからダウンロードしました。
日経平均株価 1時間足 時系列データ CSVダウンロード
あるだけ(2007年以降)全てダウンロードし、それらを結合して一つのファイルを作っておきます。
終値(finish)をプロットすると、以下のようになっています。
今回は、このデータの90%を学習と検証に使用し、残り10%で予実比較をします。
参考
ネットワークの組み方は、以下を参考にしました。
qiita.com
ただし、こちらのKerasのVersionは古いので、いくつかオプション名が変わっています。そこはdocumentなどを参照して適宜書き換えます。
前処理
今回、予測の仕方を少しだけ工夫します。直前の50営業日の株価データを用いて、次の営業日の株価を予想する、という方法です。その用途に即したデータ加工を以下の関数で行います。
import numpy as np import pandas as pd from pandas import Series, DataFrame # 直前のn_prev 日分のデータと、その次の営業日のデータを生成 def _load_data(data, n_prev = 50): docX, docY = [], [] for i in range(len(data)-n_prev): docX.append(data.iloc[i:i+n_prev].as_matrix()) docY.append(data.iloc[i+n_prev].as_matrix()) alsX = np.array(docX) alsY = np.array(docY) return alsX, alsY # 学習用とテスト用データを分割、ただし分割する際に_load_data()を適用 def train_test_split(df, test_size=0.1, n_prev = 50): """ This just splits data to training and testing parts """ ntrn = round(len(df) * (1 - test_size)) ntrn = int(ntrn) X_train, y_train = _load_data(df.iloc[0:ntrn], n_prev) X_test, y_test = _load_data(df.iloc[ntrn:], n_prev) return (X_train, y_train), (X_test, y_test)
train_test_split()にしかるべき株価データを投入すれば、所定の形式の学習・テストデータを生成することができます。株価データは、dfというDataFrameに格納されており、終値のカラム名はfinishであるとします。スケールを狭めるのに、全体の平均値で割った操作をした終値を目的変数とします。
# 全体の平均値で割る df['obs'] = df['finish'] / df['finish'].mean() length_of_sequences = 50 (X_train, y_train), (X_test, y_test) = train_test_split(df[['obs']], test_size = 0.1, n_prev = length_of_sequences)
LSTM
ここでは、時系列データを扱うネットワークであるRecurrent Neural Network(RNN)の一つである、LSTMというネットワークを用いてモデル化します。LSTMに関する詳しい説明は、例えば以下の書籍やサイトなどを参照ください*1。
bookclub.kodansha.co.jp
qiita.com
Kerasで必要なライブラリをインポートし、前回までと同じような感じでネットワークを組んでみます。
from keras.models import Sequential from keras.layers.core import Dense, Activation from keras.layers.recurrent import LSTM in_out_neurons = 1 hidden_neurons = 100 model = Sequential() model.add(LSTM(hidden_neurons, batch_input_shape=(None, length_of_sequences, in_out_neurons), return_sequences=False)) model.add(Dense(in_out_neurons)) model.add(Activation("linear"))
これは可視化すると以下のようなネットワークを組んだことになっています。
書籍などで勉強するとわかりますが、このLSTMというところは、中身は結構複雑です。
学習をさせてみます。
model.compile(loss="mean_squared_error", optimizer="adam") history = model.fit(X_train, y_train, batch_size=600, epochs=50, validation_split=0.2)
Train on 1822 samples, validate on 456 samples Epoch 1/50 1822/1822 [==============================] - 2s - loss: 0.5133 - val_loss: 0.4263 Epoch 2/50 1822/1822 [==============================] - 2s - loss: 0.0918 - val_loss: 0.0283 Epoch 3/50 1822/1822 [==============================] - 2s - loss: 0.0574 - val_loss: 0.0162 Epoch 4/50 1822/1822 [==============================] - 2s - loss: 0.0108 - val_loss: 0.0325 Epoch 5/50 1822/1822 [==============================] - 2s - loss: 0.0109 - val_loss: 0.0707 Epoch 6/50 1822/1822 [==============================] - 2s - loss: 0.0179 - val_loss: 0.0485 Epoch 7/50 1822/1822 [==============================] - 2s - loss: 0.0080 - val_loss: 0.0116 Epoch 8/50 1822/1822 [==============================] - 1s - loss: 0.0025 - val_loss: 0.0016 Epoch 9/50 1822/1822 [==============================] - ETA: 0s - loss: 0.006 - 2s - loss: 0.0065 - val_loss: 0.0016 Epoch 10/50 1822/1822 [==============================] - 2s - loss: 0.0043 - val_loss: 0.0070 Epoch 11/50 1822/1822 [==============================] - 2s - loss: 0.0020 - val_loss: 0.0177 Epoch 12/50 1822/1822 [==============================] - 2s - loss: 0.0032 - val_loss: 0.0188 Epoch 13/50 1822/1822 [==============================] - 2s - loss: 0.0028 - val_loss: 0.0108 Epoch 14/50 1822/1822 [==============================] - 2s - loss: 0.0018 - val_loss: 0.0049 Epoch 15/50 1822/1822 [==============================] - 2s - loss: 0.0019 - val_loss: 0.0039 Epoch 16/50 1822/1822 [==============================] - 2s - loss: 0.0018 - val_loss: 0.0056 Epoch 17/50 1822/1822 [==============================] - 2s - loss: 0.0015 - val_loss: 0.0075 Epoch 18/50 1822/1822 [==============================] - 2s - loss: 0.0015 - val_loss: 0.0072 Epoch 19/50 1822/1822 [==============================] - 2s - loss: 0.0014 - val_loss: 0.0057 Epoch 20/50 1822/1822 [==============================] - 2s - loss: 0.0013 - val_loss: 0.0045 Epoch 21/50 1822/1822 [==============================] - 1s - loss: 0.0012 - val_loss: 0.0041 Epoch 22/50 1822/1822 [==============================] - 2s - loss: 0.0012 - val_loss: 0.0039 Epoch 23/50 1822/1822 [==============================] - 2s - loss: 0.0011 - val_loss: 0.0037 Epoch 24/50 1822/1822 [==============================] - 2s - loss: 0.0010 - val_loss: 0.0036 Epoch 25/50 1822/1822 [==============================] - 2s - loss: 9.9451e-04 - val_loss: 0.0035 Epoch 26/50 1822/1822 [==============================] - 2s - loss: 9.4513e-04 - val_loss: 0.0028 Epoch 27/50 1822/1822 [==============================] - 2s - loss: 9.0099e-04 - val_loss: 0.0026 Epoch 28/50 1822/1822 [==============================] - 2s - loss: 8.5874e-04 - val_loss: 0.0023 Epoch 29/50 1822/1822 [==============================] - 1s - loss: 8.1982e-04 - val_loss: 0.0020 Epoch 30/50 1822/1822 [==============================] - 2s - loss: 7.9007e-04 - val_loss: 0.0018 Epoch 31/50 1822/1822 [==============================] - 2s - loss: 7.5337e-04 - val_loss: 0.0019 Epoch 32/50 1822/1822 [==============================] - 2s - loss: 7.1939e-04 - val_loss: 0.0019 Epoch 33/50 1822/1822 [==============================] - 2s - loss: 6.9869e-04 - val_loss: 0.0016 Epoch 34/50 1822/1822 [==============================] - 2s - loss: 6.7346e-04 - val_loss: 0.0013 Epoch 35/50 1822/1822 [==============================] - 2s - loss: 6.7397e-04 - val_loss: 0.0013 Epoch 36/50 1822/1822 [==============================] - 2s - loss: 6.4443e-04 - val_loss: 0.0013 Epoch 37/50 1822/1822 [==============================] - 2s - loss: 6.2074e-04 - val_loss: 0.0014 Epoch 38/50 1822/1822 [==============================] - 2s - loss: 6.3547e-04 - val_loss: 0.0014 Epoch 39/50 1822/1822 [==============================] - 2s - loss: 6.0775e-04 - val_loss: 0.0012 Epoch 40/50 1822/1822 [==============================] - 2s - loss: 6.3238e-04 - val_loss: 0.0012 Epoch 41/50 1822/1822 [==============================] - 2s - loss: 6.0300e-04 - val_loss: 0.0014 Epoch 42/50 1822/1822 [==============================] - 2s - loss: 6.1697e-04 - val_loss: 0.0012 Epoch 43/50 1822/1822 [==============================] - 2s - loss: 6.3159e-04 - val_loss: 0.0012 Epoch 44/50 1822/1822 [==============================] - 2s - loss: 6.1184e-04 - val_loss: 0.0013 Epoch 45/50 1822/1822 [==============================] - 2s - loss: 6.1628e-04 - val_loss: 0.0013 Epoch 46/50 1822/1822 [==============================] - 2s - loss: 5.9740e-04 - val_loss: 0.0012 Epoch 47/50 1822/1822 [==============================] - 2s - loss: 6.0511e-04 - val_loss: 0.0012 Epoch 48/50 1822/1822 [==============================] - 2s - loss: 6.0267e-04 - val_loss: 0.0014 Epoch 49/50 1822/1822 [==============================] - 2s - loss: 6.1846e-04 - val_loss: 0.0012 Epoch 50/50 1822/1822 [==============================] - 2s - loss: 6.0198e-04 - val_loss: 0.0012
途中少しぶれがあれど、train、validationともにepoch数が大きくなるにつれてlossが減少しています。
念のため可視化すると以下の感じになっています。
予測
いよいよ予実比較してみます。
# 予測値算出 predicted = model.predict(X_test) # 実績と比較 dataf = pd.DataFrame(predicted) dataf.columns = ["predict"] dataf["input"] = y_test dataf.plot(figsize=(15, 5))
可視化すると以下のようになっています。
うーん、見た目的には似てる感じですが、これをもって予測がうまくいっていると言えるのか…?色々試して考察しなきゃいけない事案な気がするので、いったんはコメントを控えておこう(笑)ここでのメッセージはKerasを使うとDeep Learningによる株価予測をお手軽にできるよ、ということで。
まとめ
今回は株価データに対してLSTMを適用してみました。今回はかなり単純なセットアップをとったので、まだまだやりようはありそうです*2。そういった試行錯誤は気が向いたらやります(笑)
次回以降はまた画像に戻って画像生成とか考えてみようかな、と妄想中です。