<実践>HomeCredit作業記録(6) - 進捗とメモ

f:id:kurupical:20180615203929p:plain:w500

いいところまで来ました。
淡々と、定石通りにやってるだけのつもりなんですが・・・

ただ、手法のPrivate shareが禁止ということで、規約にひっかかりそうなので過去の記事を非公開にしました。

いま使っているLightGBMがブラックボックスすぎるので、そこの勉強をしたいです。

ここまで作業時間40時間くらい、思ったことややったことメモ

Kaggleだと限界がわかる

自分が1位じゃないかぎり、スコアの伸びしろがわかるっていうのは大きいですね。
実務だと一人でやるので、どこまでできるのかが不透明です。

しばらく誰も100m10秒切れなかったのに切ったとたん、他の人も10秒切れるようになった、みたいな感じですね
1位じゃないからまだまだやれるんだなというのがわかるのは大きいです。

Discussionが参考になる

今私は特徴量作成をやっているのですが、それすらも自動化する「Featuretools」というツールが出てるみたいですね。
Kaggleのディスカッションは以下です。
https://www.kaggle.com/willkoehrsen/intro-to-tuning-automated-feature-engineering

まだ使っていないのですが、いつかは使ってみたいです。

仕事でもプライベートでも作れるEDAツールにする

多分次の分析でもやるだろうな〜と思ったデータ加工のプログラムは、再利用可能+テストを書いて多少の品質を確保しています。

データサイエンティストの人を片っ端からフォローした

議論を追ったりするとめっちゃ勉強になります!

<調査>Kaggle過去コンペ上位入賞者のSolutionから学ぶ(1)

blog.kaggle.com
この記事に、「コンペに優勝するためには過去のコンペから学ぶことが大事だ」と書いてあったので、過去のコンペを調べることにしました。
過去のコンペのDiscussionを見ると、上位入賞者が解法を公開してくれています。

HomeCreditと同じ「分類」タスクのコンペを探しました。

以下、自分のメモ書きです。

Porto Seguro’s Safe Driver Prediction 1st solution

Porto Seguro’s Safe Driver Prediction | Kaggle

1ファイルのコンペ(主キー1に対して、レコードがN行みたいなことがない)

手法

  • Representation learning
    • DAE(Deep Auto Encoder)による特徴抽出。
    • Feature Engineeringが嫌い!それを自動化してこそAIだ!ということだそうです。
  • Normalizationには"RankGauss"を使った
  • inputSwapNoize

    • 0.15 means 15% of features replaced by values from another row.
    • これって以下の認識であってるんでしょうか?
      • DAEはノイズ除去の役割を果たす
      • 人工的にノイズを作るため、各特徴の15%は別の行とスワップする
        f:id:kurupical:20180605205648p:plain:w500
  • GANはやってみたけど失敗

    • 連続値とカテゴリ変数が混在する場合のGANは難しい
    • 疑問:GANを何に使おうとしたのでしょうか?
  • NN5個とLGBM1個をstacking(重みはすべて同じ)

感想

  • DAEによる特徴抽出が強い!今のコンペでもやってみたいです。ただ、主キー1に対してN行データがある場合は入力が動的になるから難しいですね。この問題が解決できたら、いよいよFeature Engineeringが要らなくなる?
  • inputSwapNoiseの考え方が個人的にとても面白かったです。

<実践>HomeCredit作業記録(5) - FeatureEngineering続き

Kaggle挑戦中です!
債務不履行になる人を予想するタスクです。
Home Credit Default Risk | Kaggle

やったこと

  • FeatureEngineering

FeatureEngineering

前回の記事では、一つ一つの特徴量をじっくり見ていました
kurupical.hatenablog.com

ですが、やっぱりキリがないので、機械的に全部のデータをいろいろな形で集計しました。
参考にしたKernelは以下などです。
Good_fun_with_LigthGBM | Kaggle
LightGBM - all tables included [0.778] | Kaggle

結果

score: 0.735 -> 0.782
順位: 214位/1642 (上位15%)

今後やっていきたいこと

いったんFeatureEngineeringはやめて他のタスクを。

  • パラメタチューニング
    RandomisedSearch, GridSearch以外もいろいろ試したいです。

  • 別のアルゴリズムも使う
    今はLightGBMを使っているのですが、XGBoostやDeepLearningも試してみたいです。

  • アンサンブル学習
    複数の学習器でStackingをやりたいです。

その他

参考になったDiscussion


  1. この特徴量生成は正直引きました。え、そこまでやるの?みたいな
    Hybrid Jeepy and LGB | Kaggle
    いろんな特徴量を足したり引いたりミニマム取ったりtanh取ったり・・・
    そこまでやるならDeepLearningでやればよいのでは?なんて思ったり。
    でも、こういうアプローチもあるのかと勉強になりました。


  2. Home Credit Default Risk | Kaggle
    書き込んでみました。笑 質問した後、自分の凡ミスに気づくという・・・

<実践>HomeCredit作業記録(4) - FeatureEngineeringのためのツール作成

Kaggle挑戦中です!
債務不履行になる人を予想するタスクです。
Home Credit Default Risk | Kaggle

やったこと

FeatureEngineeringのつづき

  • 可視化ツールの作成
  • 特徴量の作成

可視化ツールの作成

f:id:kurupical:20180601223524j:plain:w500

def visualize_bar(df,
                  target_name="TARGET",
                  output_path=None):
    """
    分析対象のcolumnとTARGETの関係を棒グラフにする

    :param df:
        columns=[分析対象column, TARGET]
    :return:
    """

    column = list(df.drop([target_name], axis=1).columns)
    df_count = df.groupby(column).count().sort_index()
    print("columns_unique_num:{}".format(len(df_count.values)))

    if len(df_count.values) > 200:
        print("columns is too big to output bar")
        return

    yticks = []
    for index, count in zip(df_count.index, df_count.values):
        tmp = "{} (N={})".format(index, count[0])
        yticks.append(tmp)

    plt.figure(figsize=(8, len(df_count)*0.6))
    sns.barplot(x=target_name, y=column[0], data=df, orient="h")

    plt.axvline(df[target_name].mean(), ls=":", label="all_mean")
    for i in range(len(df_count.values)):
        plt.text(0, i, "N={}".format(df_count.values[i]))
    plt.legend()
    plt.savefig(output_path)
    plt.close()

このツールを作ったことで、自分が作った特徴量の評価がしやすくなりました。

特徴量の作成

データから、「顧客IDごとの、現在返済中借金の件数」という特徴量を作りました。
棒グラフがカラフルなのはデフォルト設定で、僕のセンスじゃないです。

f:id:kurupical:20180601223822j:plain:w500

現在返済中借金の件数が多いほど、債務不履行になる確率が高くなるのがわかります!

以下は学習器が出力する「分類に使った大事な特徴ランキング」なのですが、僕が作った項目(*ACTIVE_COUNT)が上位にランクインしています。(全部で200項目くらいあるうちの30位) f:id:kurupical:20180601224432p:plain:w500

まとめ

  • 特徴量作成が捗るツールを作った。
  • いい特徴量が1個つくれた。

こんな感じで、全体データを眺めては特徴量を作成する、というのをしばらくはもくもくと続けることになりそうです。

<実践>HomeCredit作業記録(3) - FeatureEngineeringのアプローチ

Kaggle挑戦中です!
債務不履行になる人を予想するタスクです。
Home Credit Default Risk | Kaggle

最近は特徴量エンジニアリング(FeatureEngineering)をやっています。

やっている中で気づいたことがあるのでブログにします

特徴量エンジニアリングのアプローチ

特徴量エンジニアリングを、ボトムアップでやっていたのですが、どうやら最初は「トップダウン」でやるほうがよさそうです。

※業務経験2ヶ月の新米データサイエンティストのいち考察として読んでいただければ幸いです。。

ボトムアップ

https://www.kaggle.com/c/home-credit-default-risk/data データの特徴量(=項目)を1つずつ吟味して、特徴量を生成する
以下は、「クレジットカードの返済期限を過ぎてしまった回数」という特徴量を作成しました。
(返済期限を過ぎた人が多いほど債務不履行になるのでは?という私の推測から)

def count_credit_day_overdue(df):
    """
    credit_day_overdue>0の件数を、ユーザーIDごとに数える
    :param df:
    :return:
    """
    def _count_over_zero(array):
        count = 0
        for val in array["CREDIT_DAY_OVERDUE"].values.flatten():
            if val > 0:
                count += 1
        return count

    df_tmp = df[["SK_ID_CURR", "CREDIT_DAY_OVERDUE"]]

    series_count_by_id = df_tmp.groupby("SK_ID_CURR").apply(_count_over_zero)
    series_count_by_id.name = "*DAY_OVERDUE_COUNT"

    df_count_by_id = pd.DataFrame(series_count_by_id).reset_index()
    return df_count_by_id

・・・こんな感じで2,3項目を作ってみたのですが、これじゃ日がくれそうです。
KaggleのKernelを見ると・・・トップダウンアプローチで作っている人が多いです。

トップダウン

    output_dir = "../../data/edited/all_mean"
    label_enc = LabelEncoder()

    def labelencode_cat(df):
        df = df.copy()
        cat_var = df.select_dtypes(['object']).columns.tolist()
        for col in cat_var:
            df["category_{}".format(col)] = label_enc.fit_transform(df[col].astype('str'))
            df = df.drop(col, axis=1)
        return df

    original_dir = "../../data/original"

    def output_mean(df, filename):
        df_avg = df.groupby("SK_ID_CURR").mean().reset_index()
        output_csv(df=df_avg,
                   output_path="{}/{}.csv".format(output_dir, filename))

    print("bureau")
    fname = "bureau"
    df_bureau = pd.read_csv("{}/{}.csv".format(original_dir, fname)).pipe(labelencode_cat)
    df_bureau = df_bureau.drop("SK_ID_BUREAU", axis=1)
    output_mean(df=df_bureau,
                filename=fname)
    del df_bureau

ざっくりと全部の項目についてユーザーごとに値の平均を取って、特徴量としてしまう。
なんと大雑把なー!!と思ったのですがこっちのほうが良さそうです。

なぜトップダウンがいいのか?

ローコスト

とにかく楽です。30行くらいのコードで、全ファイルの特徴量ごとの平均が取れちゃいました。

大事な項目を絞り込める

上記のような形で全部のデータをLightGBMやXGBoostなどの学習器で学習させると、feature_importances_を出力してくれます。

f:id:kurupical:20180530213313p:plain:w500

この図でわかることは、「予測にあたり学習器が重要視した項目」です。
つまり、(平均という大雑把なやり方とはいえ)重要な項目がなんとなくつかめます。
ここから、各項目について「ボトムアップ」で、特徴量作成するのがいいような気がします。

トップダウンのデメリット

カテゴリ変数(=文字列項目)の平均?

機械学習にかけるときは数値変換をする必要があります。
動物であれば、うさぎ=1、象=2、犬=3、・・・など。1
カテゴリ変数の「平均」を取ると、うさぎと犬の平均は象!?みたいな変なことになります。
トップダウン的なアプローチは、「数値」にのみ有効なんだと思います。

まとめ

最初からボトムアップで泥臭くやるより、トップダウン→特徴見てボトムアップのほうが当たりがつきやすく、素人にはよいのかもしれません。

途中経過

f:id:kurupical:20180530214633p:plain:w500

score: 0.706 -> 0.735
順位: 936位/1197 (上位80%)


  1. LightGBMはカテゴリ変数に対応しており、OneHotEncodeしなくてもよい

<実践>HomeCredit作業記録(2) - LightGBMを使って全特徴量を突っ込んだ

Kaggle挑戦中です!
Home Credit Default Risk | Kaggle

今日やったこと

LightGBMで、application_{train/test}.csvの全パラメータを突っ込んで

  1. 特徴の重要さ(feature importance)を見る
  2. LightGBMの予想を投稿する

そのために、

  • LightBGMの仕様調査
  • データの予想を投稿
    • データの前処理
    • train/predict

をやりました。

LightBGMの仕様調査

勾配ブースティングのLightGBMのパラメタについて調べました。
www.analyticsvidhya.com
rautaku.hatenablog.com

ここで書いてるパラメータを指定しても、「そないなパラメータあらへんで!」といわれてしまいます。
もう少し調査が必要そうです。

データの前処理

HomeCreditRisk : Extensive EDA + Baseline Model JP | Kaggle 和訳していただいた神Kernelをパクって実装。
このカーネルにはなかったのですが、「カテゴリ変数」をわかるようにカラム名をつけてみました。

train/predict

ハイパーパラメータはこんな感じ(チューニングはしてない)

params = {'task': 'train',
          'boosting_type': 'gbdt',
          'objective': 'binary',
          'metric': 'auc',
          'learning_rate': 0.01,
          'n_estimators': 50,
          'max_depth': 8,
          'subsample': 0.8,
          'verbose': 1,
          'num_iteration': 3000}

あとは、カテゴリ変数のカラム名を取得する関数を書いて

def _get_categorical_features(df):
    feats = [col for col in list(df.columns) if col[:9] == "category_"]
    return feats
data_cat = _get_categorical_features(X)

訓練時に、カテゴリ変数はこれだよーって教えてあげます。
OneHotEncodeとかしなくてよいから便利!(実際どういう仕組みなのか全くわからないし、どこに載ってるんだろう)

model = lgb.train(params,
                  lgb_train,
                  categorical_feature=data_cat,
                  valid_sets=lgb_test,
                  early_stopping_rounds=150,
                  verbose_eval=100)

feature_importanceも出してみました。
判定にあたり、100以上の特徴量の中でどれが大事だったか。 f:id:kurupical:20180528215509p:plain

この図を見ると、以下の特徴量が大事だということがわかります。

  • ORGANAIZATION_TYPE: どういった組織で働いてるか(ex. school/goverment...)
  • EXT_SOURCE: 別審査機関のクレジットスコア
  • DAYS_BIRCH: 年齢(生まれてから経過した日)

こういった分析は、すでにKaggleのDiscussion・Kernelで行われています。
しかし、そこでは「ORGANAIZATION_TYPE」が大事だとは書かれていませんでした。
自分の手で動かしてやってみるのは大事ですね!

score: 0.629→0.706
上位90%→上位85%(859位/1006)

signateは最初の投稿で結構いい順位についたけど、Kaggleはまだまだ全然駄目ですね・・・。

今回は1ファイルだけを使って予想しましたが、実はあと4ファイルくらいあります。
次はそのファイルも使って学習させてみたいと思います。

<基礎学習>機械学習、DeepLearningを使わずにFXの分析をしてみた(2)

以下前回記事の続きです。
kurupical.hatenablog.com

概要

  • 未知のデータに対して予想できるようにした
  • 「次の日上がるか下がるか」ではなく、「N%上がるが先か下がるが先か」を当てるようにした
  • いろんなパラメータをグリッドサーチ(総当たり)した

目次

  1. 前回の反省
  2. 予想の対象を変更
  3. 与えるパラメータ
  4. 結果、考察
  5. 今後

1.前回の反省

前回の記事の「2-2.セグメントの組み合わせ」で正解率を出し、このトレードアルゴリズムは使えそうだ・・・と思ったのですが、初歩的なミスがありました。
「未知のデータに対する検証をしていない」ことです。

このアルゴリズムは未来においても有効なのか?を示す必要があります。

f:id:kurupical:20180418193502p:plain

前回の考え方は、集計に使うデータと性能評価に使うデータが同じでした。
そのため、その期間の値動きに特化した予想になってしまいます。たとえ、性能評価をして勝率が高かったからといって、未来(未知)の値動きに対応できるかは保証できません。

ですので、このような考え方にしました

f:id:kurupical:20180418193944p:plain

上記の図は、
(1) 2016/1〜2016/6のデータで集計をし、2016/7〜2016/9の値動きで性能評価を行う
(2) 2016/4〜2016/9のデータで集計をし、2016/10〜2016/12の値動きで性能評価を行う



と最新のデータまで続け、それぞれで行った性能評価を集計し、アルゴリズムの性能評価とします。
これで、「未知のデータに対する性能評価」を行うことができます。
・・・いろいろDeepLearningとか機械学習をやってるのに我ながら信じられないミスでした。笑

2. 予想の対象を変更

2-1. 前回までの予想対象とその難点

前回は、「過去の1時間足のチャートの各種データをクロス集計して、次の1時間で上がるか下がるか」を予想するプログラムでした。
このアルゴリズムには難点があります。

1時間足なので利幅が少ない→手数料負けしがち
以下を仮定します。

  • ドル円で1時間で動く値幅の平均が5pips(0.05円)だとします。
  • 取引にかかるコストは一定で、0.4pipsとスリップ幅0.6pipsみて1pips(0.01円)。

この場合、勝てば5 - 1 = +4pips、負ければ -5 -1 = -6pipsとなります。
期待値プラスになるための勝率を { \displaystyle p }と置くと、

{ \displaystyle
4*p + (-6) * (1-p) > 0
} を満たす必要があります。

これを解くと、{ \displaystyle p > 0.6 }が求まります。
60%の勝率を保ってイーブンというのはかなり厳しいですね。

2-2. あたらしい予想対象

なので、次の1時間であがるか下がるかではなく、「N%上がるかN%上がるかどっちが先か」を予想することにしました。
そうすることで、利幅を自分で決めることができます。
たとえば、

  • N=0.5%(ドル円だとおおよそ50pips(0.5円)

この場合、先ほどの計算を考えると、勝てば 50-1=+49pips、負ければ-50-1=-51pipsとなります。
同様に期待値がプラスになるための勝率を以下式により求めます。

{ \displaystyle
49*p + (-51) * (1-p) > 0
}

これを解くと、{ \displaystyle p > 0.51 }が求まります。
51%の勝率であればなんとかなりそうですね。

3.与えるパラメータ

あとは、パラメータを試行錯誤するだけです。
以下のパラメータを調整しました。

  • 前回記事2-1.優位性のありそうなセグメントの抽出
    • 必要なデータ量(前回記事のa)
    • クロス集計の偏り具合(前回記事のb) (e.g. MA25=0.9975/MA75=1.0025の場合60%の確率で「N%下がるが先」)
    • 組み合わせ数(前回記事のc)
  • 予想する値動き幅N%
  • 値動き集計、性能評価に使うデータの期間
  • チャートの足(15分足、30分足、1時間足、2時間足)

4. 結果と考察

4-1. 結果

1時間足で5年分検証した結果が下図です。
f:id:kurupical:20180418202101p:plain:w500
縦軸の黄色で塗りつぶしているのが「勝率」、その右隣が「取引件数」です。

4-2. 考察

4-2-1. 数字だけを見る

勝率が1(=100%)になっているものもありますが、取引件数が少なすぎるので信用できません。
大数の法則からも、1000件以上あるデータで上から探していくと...
赤文字で塗っている行が、なんと1420回取引して勝率79%。
む、無敵だ・・・

早速採用!!と思いましたが、11月に作ったDeepLearningのアルゴリズムをろくすっぽ検証もせず取引したらアルゴリズムの考え方がバグっていて、5000円を失ったことを思い出しました。。
今回は注意深く。
念のため、月ごとの取引件数を勝率を見てみました。

f:id:kurupical:20180418202817p:plain:w500

なんと。。2016年から全く取引してないです。
これじゃ使えませんね。(過学習とはいえ、2014/4〜2014/6で200回トレードの勝率100%なのは驚異的です。なんかバグってそう・・・)

他の勝率高いデータも確認してみましたが、特定月に取引が偏っているものが多く、実運用はできそうにないものが多かったです。
全部のデータについて取引の分布をしらみつぶしに見ていくのはしんどいので、集計に工夫をしました。

4-2-2. 取引件数のばらつきを考慮してアルゴリズムを選定する

集計を以下のようにしました。

f:id:kurupical:20180418204307p:plain:w500

右側に、月ごとの取引件数の平均と標準偏差を取りました。
標準偏差が低い=月ごとの取引件数にばらつきが少ないということになります。
このデータで、「勝率60%以上、取引件数1000件以上」で標準偏差が低い順に並べてみました。

上2つのデータについて、月ごとの取引回数を確認してみます。
f:id:kurupical:20180418204004p:plain:w500
[f:id:kurupical:20180418210021p:plain:w500
] 2個目は特になかなか安定してそうです。
1個目は利幅0.5%、2個目は利幅0.1%に設定しているので、2.で説明した「必要な勝率」が違うことに注意が必要です。
勝率やグラフは2個目のグラフがキレイですが、実際どちらが利益を生むかは別問題です。

5. 今後

いろいろなパラメータでチューニングして、「利幅」と「勝率/取引のばらつき」が一番良いものを選びたいと思います。
仮想通貨にも同じアルゴリズムを適用してどうなるかもあわせて検証しています。

あとは、「資金管理」についても考えていかなくては・・・。