u++の備忘録

LightGBMでdownsampling+bagging

はじめに

新年初の技術系の記事です。

年末年始から最近にかけては、PyTorchの勉強などインプット重視で過ごしています。その一環で不均衡データの扱いも勉強しました。

上記のツイートを契機に多くのリプライなどで情報を頂戴しましたが、以前に話題になった「downsampling+bagging」の手法が良さそうでした。本記事では、模擬的に作成したデータセットにLightGBMを使い、「downsampling+bagging」の手法を試してみたいと思います。

tjo.hatenablog.com

データセットの作成

データセットの作成に当たっては、下記の記事を参考にしました。

blog.amedama.jp

from sklearn.datasets import make_classification
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import StratifiedShuffleSplit

args = {
    'n_samples': 7000000,
    'n_features': 80,
    'n_informative': 3,
    'n_redundant': 0,
    'n_repeated': 0,
    'n_classes': 2,
    'n_clusters_per_class': 1,
    'weights': [0.99, 0.01],
    'random_state': 42,
}

X, y = make_classification(**args)

目的変数は{0, 1}の2値分類で、合計700万件のデータのうち正例(ラベル1)が約1%の不均衡データを作成しました。

f:id:upura:20190112140214p:plain

ラベルの割合が均等になるように、データを学習・検証・テスト用に分割しておきます。

def imbalanced_data_split(X, y, test_size=0.2):
    sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=0)
    for train_index, test_index in sss.split(X, y):
        X_train, X_test = X[train_index], X[test_index]
        y_train, y_test = y[train_index], y[test_index]
        return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test = imbalanced_data_split(X, y, test_size=0.2)
# for validation
X_train2, X_valid, y_train2, y_valid = imbalanced_data_split(X_train, y_train, test_size=0.2)

LightGBM

まずは、普通にLightGBMを試してみます。

import lightgbm as lgb
from sklearn.metrics import roc_auc_score

lgbm_params = {
    'learning_rate': 0.1,
    'num_leaves': 8,
    'boosting_type' : 'gbdt',
    'reg_alpha' : 1,
    'reg_lambda' : 1,
    'objective': 'binary',
    'metric': 'auc',
}

def lgbm_train(X_train_df, X_valid_df, y_train_df, y_valid_df, lgbm_params):
    lgb_train = lgb.Dataset(X_train_df, y_train_df)
    lgb_eval = lgb.Dataset(X_valid_df, y_valid_df, reference=lgb_train)

    # 上記のパラメータでモデルを学習する
    model = lgb.train(lgbm_params, lgb_train,
                      # モデルの評価用データを渡す
                      valid_sets=lgb_eval,
                      # 最大で 1000 ラウンドまで学習する
                      num_boost_round=1000,
                      # 10 ラウンド経過しても性能が向上しないときは学習を打ち切る
                      early_stopping_rounds=10)
    
    return model

モデルの学習時間は2min 21sでした。

%%time
model_normal = lgbm_train(X_train2, X_valid, y_train2, y_valid, lgbm_params)
(前略)
[62]	valid_0's auc: 0.831404
Early stopping, best iteration is:
[52]	valid_0's auc: 0.831614
CPU times: user 2min 16s, sys: 4.87 s, total: 2min 21s
Wall time: 58.7 s

テストデータで予測してみたところ、aucで0.829287295077となりました。

y_pred_normal = model_normal.predict(X_test, num_iteration=model_normal.best_iteration)

# auc を計算する
auc = roc_auc_score(y_test, y_pred_normal)
print(auc)

downsampling

次いで、downsamplingを試してみます。

downsamplingは、不均衡データの多い方のラベルのデータを、少ない方のラベルのデータ数と等しくなるまでランダムに除外する手法です。今回の場合、負例(ラベル0)のデータを大量に捨ててしまいます。

imbalanced-learnというライブラリで、簡単に処理を記述できます。

imbalanced-learn.org

from imblearn.under_sampling import RandomUnderSampler

sampler = RandomUnderSampler(random_state=42)
# downsampling
X_resampled, y_resampled = sampler.fit_resample(X_train, y_train)
# for validation
X_train2, X_valid, y_train2, y_valid = imbalanced_data_split(X_resampled, y_resampled, test_size=0.2)

f:id:upura:20190112142420p:plain

(学習データの)正例の数に揃えているので、データサイズはかなり小さくなっています。

先ほどと同じくLightGBMで学習させたところ、モデルの学習時間は5.24 sまで短縮されました。

%%time
model_under_sample = lgbm_train(X_train2, X_valid, y_train2, y_valid, lgbm_params)
(前略)
[38]	valid_0's auc: 0.83336
Early stopping, best iteration is:
[28]	valid_0's auc: 0.833389
CPU times: user 5.02 s, sys: 229 ms, total: 5.24 s
Wall time: 2.76 s

テストデータで予測してみたところ、aucは0.828820480993になりました。aucは多少悪化しています。

手法 auc 実行時間
LightGBM 0.829287295077
2min 21s
LightGBM + downsampling 0.828820480993
5.24 s

downsampling+bagging

最後に、baggingを追加してみます。

baggingは、最初の不均衡データから重複を許して複数個のデータセットを作成し、それぞれ学習させたモデルをアンサンブルする手法です。

imbalanced-learnのRandomUnderSampler()では、replacementの引数をTrueにすることで、重複を許したデータ抽出を実行してくれます。

今回は乱数のseedを変えながら、10個のモデルを学習させてみます。

def bagging(seed):
    sampler = RandomUnderSampler(random_state=seed, replacement=True)
    X_resampled, y_resampled = sampler.fit_resample(X_train, y_train)
    X_train2, X_valid, y_train2, y_valid = imbalanced_data_split(X_resampled, y_resampled, test_size=0.2)
    model_bagging = lgbm_train(X_train2, X_valid, y_train2, y_valid, lgbm_params)
    return model_bagging

10個のモデルの学習時間は、1min 24sでした。

%%time
models = []

for i in range(10):
    models.append(bagging(i))
(前略)
CPU times: user 1min 17s, sys: 6.4 s, total: 1min 24s
Wall time: 47.9 s

今回のアンサンブルでは、それぞれのモデルで予測した結果の平均値を、全体の予測値とみなしてみます。
aucを計算したところ、単独のモデルよりも少々高い0.829094611662になりました。

y_preds = []

for m in models:
    y_preds.append(m.predict(X_test, num_iteration=m.best_iteration))

y_preds_bagging = sum(y_preds)/len(y_preds)
# auc を計算する
auc = roc_auc_score(y_test, y_preds_bagging)
print(auc)
手法 auc 実行時間
LightGBM 0.829287295077
2min 21s
LightGBM + downsampling 0.828820480993
5.24 s
LightGBM + downsampling + bagging (10 models) 0.829094611662
1min 24s

おわりに

本記事では不均衡データの扱い方の勉強として、LightGBMを使い、「downsampling+bagging」の手法を試しました。

当然ながらデータの不均衡度合いや大きさなどの特性に依存する部分が大きいと思いますが、今回作成したデータに関していえば、以下のような実感を抱きました。

  • downsamplingで大雑把にデータを捨てても、そこまで性能は悪化しない
  • 削減された実行時間を利用して、特徴量を増やしたりアンサンブルをしたりで性能を担保できそう

世の中で扱うデータには不均衡データが多いので、今後いろいろなデータに対して試していきたいアプローチだと思いました。

実装はGitHubで公開しています。
github.com

東大の本郷キャンパスから柏キャンパスまで歩いてみた

2019年初挑戦として、東大の本郷キャンパスから柏キャンパスまで歩いてみました。

次の記事を書いた、2017年9月以来の「歩いてみた」企画です。

upura.hatenablog.com

出発は11:00で、iPhoneの地図アプリでは徒歩で「6時間45分」かかる経路でした。

東京都を脱出し埼玉県に移動するのに、2時間30分かかりました。足立区が長くて心が折れかけました。

その後2時間で千葉県に突入しました。江戸川の渡り方が分からなくて時間を無駄に費やしました。

30分の休憩を含め、到着したのは17時30分でした。途中適度にジョギングしていたので、休憩を含めて地図アプリの予測とほぼ同じになりました。

結局言いたいのは、↓のツイートです。

Twitterで言いたいこと言い切ってしまったので、ほぼ貼り付けて終わってしまいました。

2019年もよろしくお願いします。

2018年の振り返りと2019年の目標

2018年

仕事をした

本記事を書くにあたって、ちょうど1年前に何をしていたか振り返ったところ、Javaの勉強をしていました。

upura.hatenablog.com

私は2017年10月に現在の会社に就職したため、2018年は「社会人1年目」の年でもありました。

upura.hatenablog.com

会社では、大学時代から取り組んでいたデータ分析だけではなく、当初は完全に門外漢だったWeb・API開発にも携わることになりました。会社で実際に書いているのはNode.jsですが、それまでPythonやRなどのスクリプト言語しか書いた経験がなかったため、Javaを勉強していた次第です。

この1年、会社で「データ分析」「Web・API開発」の両者の仕事を経験でき、自分の扱える範疇が増えていくのを実感する日々でした。

前者に関しては、入社年など関係なく活発な議論を受け入れてくれるチームリーダーに恵まれ、充実した日々を送れています。

後者に関しては、ド素人だった自分を暖かく迎え入れてくれ、現在進行系で懇切丁寧な指導をしてくださっているチームメンバに、心より感謝したいです。データ活用という文脈の中で、単にデータ分析をするだけではなく「より多くの方々にデータを届ける」という部分のイメージが明瞭になりました。データを扱う仕事をしていく上で、視野を大きく広げられていると実感しています。

両者のリーダーに限らず、幸運なことに良い上司に恵まれていると思います。

9月には海外出張にも派遣してもらいました。

upura.hatenablog.com

はてなブログを今年も書いた

f:id:upura:20181230165309j:plain

昨年に引き続き、今年もはてなブログを定期的に執筆できました。4分の3の期間が学生だった2017年を上回る記事数です。

今年は6月ごろから「はてなブックマーク」に掲載される記事も増えてきました。

今年公開した記事で、100はてなブックマーク以上を達成したのは、次の2記事です。その他、12本の記事が10以上のはてなブックマークを頂くことができ、ブログを続ける大きな動機づけになりました。

upura.hatenablog.com

upura.hatenablog.com

12月には、kaggle Advent Calendarを埋めるべく、積極的に記事を投稿していました。

qiita.com

勢い余って「その2」も作って、合計10程度の記事を公開しています。

qiita.com

Kaggleを本格的に始めた

今年の自分を語る上で欠かせないのは、Kaggleです。過去も存在は認知していましたが、今年のゴールデンウィークを機に、本格的に取り組み始めました。

語り尽くすとキリがないので諸々を割愛しますが、無事に8月に当初目的にしていた「Kaggle Expert」の称号を得ることができました。

この時は「Kaggle Meetupに参加しやすくなったのが、何より嬉しい」と発言していましたが、これも勢い余ってLT発表までしてしまいました。

upura.hatenablog.com

競技プログラミングも始めた

今年は、競技プログラミングも始めました。次の記事でも言及していますが、データサイエンスが「バブル」の如く流行している中で、きちんと自分の手を動かせる人間でありたいと感じたのがきっかけの一つです。

upura.hatenablog.com

ひとまず当初の目的だったレート1000、緑色までは達成しましたが、まだまだ伸び代があると思います。単純に頭の体操としても面白いので、ボチボチと精進していく所存です。

upura.hatenablog.com

イベント参加・登壇をするようになった

イベント参加・登壇も意識的に行った年でした。

ジャンルもさまざまで、例えば野球のデータを分析するハッカソンや、機械学習に関する章を執筆した「技術書展5」などに参加しました。

upura.hatenablog.com

upura.hatenablog.com

TokyoRでのLT発表もしましたし、ご縁あって阪大AIメディカル研究会でも発表をしました。

upura.hatenablog.com

upura.hatenablog.com

振り返ると、充実した活動ができた1年だったと思います。

はてなブログの読者が増えた

そんなこんなで、2018年ははてなブログの読者が大幅に増えた飛躍の年となりました。

f:id:upura:20181230164514p:plain

10月末に「はてなブログの読者が100人になりました」という記事を書いたばかりだったのですが、(実際に書くかは別にして)「はてなブログの読者が200人になりました」という記事が書ける日も近いかもしれません。

upura.hatenablog.com

上記のブログにも書いた通り、最近はありがたいことにイベントなどでお会いした方に「Twitterやブログ見てます」と認知していただいている事例も増えてきました。社会人になると一般に会社の外での交流が少なくなる傾向にあるかと思いますが、Twitterやブログを通じて、いろいろな方と現実世界でもお会いできているのは本当に楽しいです。

皆さま、2018年は大変お世話になりました。

2019年

2019年の目標

本記事のタイトルに「2019年の目標」という文言を入れましたが、今年は「長い時間軸での目標は立てない」つもりです。

例えば2017年末に立ち戻ってみます。1年以上続けてきたブログの読者が10数人だった当時、2018年の現実的な目標を「ブログの読者170人」には設定できなかったと思います。線形的な成長を考えると、とても真剣に想像できない数字だからです。

現時点で長い時間軸での目標を立ててしまうと、現時点の自分が想像できる範疇の目標になってしまう気がします。

強いて言えば、地道にJavaの入門書に取り組んでいた2017年末のように、引き続き「地道にコツコツ取り組む」のが目標です。2019年末に振り返った際に、「現時点の自分が想像できないような自分」がそこに居たら嬉しいなと思っています。

「地道にコツコツ取り組む」のは2019年からに限らない話なので、今日も大晦日恒例の「ガキの使い」を見ながらPyTorchを勉強します。

参加予定

以下は、現時点で2019年に参加予定のイベントです。よろしくお願いします。

TokyoR #75

tokyor.connpass.com


Sports Analyst Meetup #1

connpass.com

2018年のKaggleの"leak"を眺めてみる

はじめに

2018年のKaggleでは、何かと "leak" という単語が話題になった印象があります。

https://www.kaggle.com/docs/competitions#leakage

今回は、Meta Kaggleのデータを分析し、2018年のコンペで話題になった "leak" を眺めてみました。

Meta Kaggle

分析の概要

  • 2018年に公開された全てのdiscussionから、タイトルに "leak" を含むものを抽出
  • ただし対象コンペは、2018年に開始したレート変動を含むものとする

対象コンペの抽出

discussionに関するデータセットには、コンペ名が記載されていませんでした。今回は "ForumId" をキーに結合することで、discussionとコンペ名を紐づけます。

compe = pd.read_csv("Competitions.csv")

# datetime型に変換し、開始日が2018年のデータを抽出
compe['EnabledDate'] = pd.to_datetime(compe['EnabledDate'])
compe2018 = compe[compe['EnabledDate'].dt.year == 2018]

# レート変動を含むコンペに絞る
compe2018 = compe2018[compe2018['CanQualifyTiers']]

# ForumIdがfloat型だったので、int型に変換
compe2018['ForumId'] = compe2018['ForumId'].astype(int)

全17件のコンペを抽出できました。

f:id:upura:20181229150033p:plain

"leak" を含むdiscussionの抽出

df = pd.read_csv("ForumTopics.csv.zip")

# datetime型に変換し、作成日が2018年のデータを抽出
df['CreationDate'] = pd.to_datetime(df['CreationDate'])
df2018 = df[df['CreationDate'].dt.year == 2018]

# タイトルに "leak" を含むデータの抽出
df2018 = df2018[df2018['Title'].str.lower().str.contains('leak', na=False)]

タイトルに"leak" を含むdiscussionを抽出できました。なお事前に大文字を小文字に変換した上で文字列検索しているため "Leak" なども抽出できています。

f:id:upura:20181229150605p:plain

データの結合

これら2つのデータを "ForumId" をキーに結合します。

merged = pd.merge(compe2017[['ForumId', 'Title']], df2017, on='ForumId', how='left')
merged[['Title_x', 'Title_y', 'TotalMessages', 'CreationDate']].sort_values('CreationDate').reset_index(drop=True)

f:id:upura:20181229140347p:plain

時系列順に "leak" を含むdiscussionをコンペ名と紐づけて表示できました。

結合結果がNullになったコンペ、すなわち一度も "leak" を含むdiscussionが投稿されなかったコンペは下記の通りです。

f:id:upura:20181229140350p:plain

結果を眺める

「Santander Value Prediction Challenge」の登場回数が圧倒的に多いですね。

登場回数が1件より多い、上位の3つのコンペを少し詳しく見てみます。

merged['Title_x'].value_counts()
Santander Value Prediction Challenge                 26
TalkingData AdTracking Fraud Detection Challenge      4
Airbus Ship Detection Challenge                       3
RSNA Pneumonia Detection Challenge                    1
Google Landmark Recognition Challenge                 1
Google AI Open Images - Visual Relationship Track     1
TrackML Particle Tracking Challenge                   1
Home Credit Default Risk                              1
Quick, Draw! Doodle Recognition Challenge             1
Avito Demand Prediction Challenge                     1
2018 Data Science Bowl                                1
PLAsTiCC Astronomical Classification                  1
The 2nd YouTube-8M Video Understanding Challenge      1
Google Landmark Retrieval Challenge                   1
TGS Salt Identification Challenge                     1
Google AI Open Images - Object Detection Track        1
Inclusive Images Challenge                            1

Santander Value Prediction Challenge

このコンペでは、IDとカラムをうまく並び替えることでtargetの値が分かる "leak" がありました。後に優勝するGiba氏によって "leak" が暴露されると、探索方法や見つけた個数などが活発に議論され、多くのスレッドが立てられました。

TalkingData AdTracking Fraud Detection Challenge

このコンペでは、未来の情報を特徴量として利用できました。この意味でのモデルの "leak" が話題に挙がったと認識しています。

f:id:upura:20181229155710p:plain

http://www.matthewemery.ca/presentation/talking-data/#/17

また4thの方の解法として、重複するデータに異なるラベルが付与される "leak" があったそうです。

As you have all noticed, there were duplicate samples with different labels.

Solution to Duplicate Problem by Reverse Engineering (0.0005 Boost) | Kaggle

このコンペは十分にdiscussionを終えていなかったので、他に記載するべき情報があれば教えてください。

Airbus Ship Detection Challenge

このコンペでは、テストデータの画像が学習データの画像をシフトしたものだと判明しました。最終的には中断期間を経て、新たなテストデータが用意されました。

Data Leak and Next Steps | Kaggle

[追記 20181230]

今回の集計からは漏れたコンペ

2018年のKaggleを追っていた方は、いくつかのコンペが漏れているとお気づきかもしれません。

筆者が把握している限り、次のコンペでは "leak" に関する話題がありました。

本記事でこれらを抽出できなかった理由ですが、1つ目の塩コンペの場合は "leak" ではなく "puzzle" という表現が使われていたためです。

Salt Jigsaw Puzzles | Kaggle

残りの2つについては、Meta KaggleのCompetitions.csv内に、まだ終わってないコンペは入っていないのが原因でした。

Two Sigmaコンペは2ステージ制なのですが、1stステージの予測対象が過去の株価であったため、外部データを参照することで値を当てられる仕組みになっていました。しかし最終的な順位決定には未来のデータを利用する(2ndステージ)ということで、運営の判断としてコンペは続行されています。

Rコンペは、テストデータにGoogle Analyticsのデモ用のデータが使われていると判明し、テストデータを変更して再始動することになりました。

Competition Relaunch Details | Kaggle

またPlaygroundのコンペですが、PUBG Finish Placement Prediction (Kernels Only)でも "leak" があったようです。

Data Leak and Next Steps | Kaggle

[追記おわり]

おわりに

本記事は、Meta Kaggleのデータを分析し、2018年のコンペで話題になった "leak" を眺めてみました。年末のお遊びなので、特にオチはないです。

タイトルだけでなく、discussionの中身に "leak" の文字列がある場合を考慮に入れても良かったかなと思っています。あと今回は割愛しましたが、同様の分析を年別に実施することで、「本当に今年 "leak" が多かったのか」の確認もできそうな気がしました。

実装はGitHubで公開しています。

github.com

【Kaggleのフォルダ構成や管理方法】タイタニック用のGitHubリポジトリを公開しました

はじめに

本記事では、Kaggle用フォルダ構成や管理方法について、現時点での自己流の方法をまとめます。「現状自分はこういうやり方を試している」という話なので、よりよい方法などあれば、ぜひTwitterなどで教えてください。

具体例がないと抽象的で分かりづらいと思ったので、Kaggleのタイタニックを題材にしました。GitHubリポジトリも公開しています。

Kaggleのタイタニック

Kaggleのタイタニックとは、Kaggleのチュートリアル的な問題として認知度の高いコンペティションです。タイタニック号の乗客の属性情報(性別・年齢・チケットの種類など)から、生存したか否かを予測します。

Titanic: Machine Learning from Disaster | Kaggle

執筆の経緯

Twitterで今月冒頭にkaggle Advent Calendarに書く話題を募集したところ、次のようなリプを頂きました。本記事は、これらの質問にお答えできるような内容にしたいと思います。


大まかな方針

  • 大雑把に機能別でフォルダを切り「どこに何が書いてあるか」を分かりやすくする
  • コンペのデータに依存する要素はconfigsやrun.pyなど一部のみに限定し、その他は編集無しで使い回せるようにする

参考にした情報

Kaggleの「TalkingData AdTracking Fraud Detection Challenge」で優勝したflowlightさんのリポジトリを非常に参考にしています。

github.com

特徴量の管理に関する部分では、同じくflowlightさんのリポジトリを参考に執筆されたというamaotoneさんのブログが理解の助けになりました。

amalog.hateblo.jp

フォルダ構成

フォルダ構成は以下の通りです。

f:id:upura:20181219082838p:plain

  • configs
  • data
    • input
    • output
  • features
  • logs
  • models
  • notebook
  • scripts
  • utils

configs

jsonファイルで、諸設定を記載しています。

記載している情報は「利用している特徴量」「学習器のパラメータ」などです。

{
    "features": [
        "age",
        "embarked",
        "family_size",
        "fare",
        "pclass",
        "sex"
    ],
    "lgbm_params": {
        "learning_rate": 0.1,
        "num_leaves": 8,
        "boosting_type": "gbdt",
        "colsample_bytree": 0.65,
        "reg_alpha": 1,
        "reg_lambda": 1,
        "objective": "multiclass",
        "num_class": 2
    },
    "loss": "multi_logloss",
    "target_name": "Survived",
    "ID_name": "PassengerId"
}

またコンペのデータに依存するカラム名なども、このjsonファイルから読み取る形式にしています。

data

dataフォルダは、input/outputに分けています。

input

inputフォルダには、元データのcsvファイルや、feather形式に変換したファイルなどを配置しています。

output

outputフォルダには、提出用のcsvファイルを出力します。ファイル名は「sub_(year-month-day-hour-min)_(score)」のように設定し、後述するログと照合できるようにしています。

features

featuresフォルダには、train/testから作成した各特徴量を保存しています。

base.pyなど、詳細については先に紹介したamaotoneさんのブログをご参照ください。

amalog.hateblo.jp

1階層深いimportancesフォルダは、特徴量の重要度を出力するために用意しました。

logs

logsフォルダには、計算の実行ごとに下記の情報などを出力しています。ファイル名は「log_(year-month-day-hour-min).log」のように設定し、前述した通り提出用のcsvファイルと照合できるようにしています。

  • 利用した特徴量
  • trainのshape
  • 学習器のパラメータ
  • cvのスコア

LightGBMのログ出力についても、amaotoneさんの次のブログを参考にしました。

amalog.hateblo.jp

loggerに関しては、現在こちらのブログを読みながら更なる良い方法を検討しているところです。

icebee.hatenablog.com

models

modelフォルダには、学習器を用意しています。下記のように汎用的に作ることで、別のコンペでも使い回せることを意識しています。

  • 入力:pandas.DataFrame、パラメータ
  • 出力:予測結果

notebook

notebookフォルダには、探索的データ分析などで利用したJupyter Notebookを配置しています。ここで試行錯誤した結果を、適切なフォルダ内のpythonファイルに取り込んでいきます。

scripts

scriptsフォルダには、汎用的なpythonファイルを配置します。例えば convert_to_feather.py ファイルは、csvファイルをfeather形式のファイルに変換します。

import pandas as pd

target = [
    'train',
    'test',
]

extension = 'csv'

for t in target:
    (pd.read_csv('./data/input/' + t + '.' + extension, encoding="utf-8"))\
        .to_feather('./data/input/' + t + '.feather')

utils

utilsフォルダには、汎用的に使える関数を書いています。

計算の実行

リポジトリのルートで以下を実行します。

このrun.pyは、コンペに応じて比較的自由に記述することにしています。このrun.pyから、汎用的に書いた各フォルダの要素を取り出してくるイメージです。

python run.py

Git管理

リポジトリはGit/GitHubで管理しています。自分一人でしか作業しないので、masterに直pushする運用をしています。

tkmさんのブログに書いてある方針が近い印象です。

yutori-datascience.hatenablog.com

おわりに

本記事では、Kaggle用フォルダ構成や管理方法について、現時点での自己流の方法をまとめました。

Kaggleを始めた当初はJupyter Notebookだけで処理していましたが、再現性が破綻しかけたこともあり、最近は適切にフォルダを分割して疎結合でファイルを管理するよう意識しています。最初にフォルダ構成を検討する際には時間がかかりましたが、コンペに依存しないよう切り分けておくことで業務などでの再利用も可能で、良い循環になっているかと思います。

日付のフォーマットが混在するDataFrameを扱う

はじめに

今回は日付のフォーマットに関する試行錯誤のメモです。

"Zuerich monthly sunspot numbers 1749-1983" データセット

時系列データで遊びたくて "Zuerich monthly sunspot numbers 1749-1983" というデータセットcsvでダウンロードしてみました。

Zuerich monthly sunspot numbers 1749-1983 — Dataset — DataMarket

データの末尾に余計な要素が入っていたので、手動で削除して、pandasで読み込みます。

import matplotlib.pyplot as plt
%matplotlib inline
import pandas as pd

df = pd.read_csv(
    'zuerich-monthly-sunspot-numbers-.csv',
)

ごく普通のフォーマット。。。かと思ったら、どこかのタイミングから日付のフォーマットが変わっている???

f:id:upura:20181227120702p:plain

pandasはかなり優秀で、フォーマットが混在していても普通にplotで可視化してくれました。

df['Zuerich_monthly_sunspot_numbers_1749-1983'].plot()

f:id:upura:20181227120932p:plain

日付から特徴量を作る

ここからが本題です。

日付は文字列型で格納されており、機械学習の特徴量として扱うため、値をdatetime型に変換して年や月などを取り出したいです。しかし、このDataFrameでは日付のフォーマットが混在しているため、例えば単純に次のようにしてもエラーが発生してしまいます。これは、フォーマットの指定が一部で正しくないためです。

df['datetime'] = pd.to_datetime(df['Month'], format='%Y-%m')
ValueError: time data 'Jan-00' doesn't match format specified

フォーマット指定をなくせば、よしなに処理してくれるかと期待しましたが、あまり一般的なフォーマットでないためか、エラーとなりました。

df['datetime'] = pd.to_datetime(df['Month'])
ValueError: day is out of range for month

結局今回は、try~exceptを使い、フォーマットごとに処理する関数を作成しました。この関数をmapの引数として、DataFrameを処理します。

def convert_to_datetime(date):
    try:
        return pd.to_datetime(date, format='%Y-%m')
    except:
        return pd.to_datetime(date, format='%b-%y')

df['datetime'] = df['Month'].map(convert_to_datetime)

フォーマットの指定に当たっては、公式の「ディレクティブ」を確認しました。
datetime — Basic date and time types — Python 3.7.2 documentation

この 'datetime' を用いることで、年や月などの特徴量を作成できました。

df['year'] = df['datetime'].dt.year
df['month'] = df['datetime'].dt.month

おまけ

世紀の指定がない2桁の年を処理する際に、自動補完される世紀が正しくない値になっていました。

例えば「1934年」を意味する「34」が、「2034年」として扱われていました。

df['year'].plot()

f:id:upura:20181227122928p:plain

今回は暫定的に次の処理で回避しましたが、この辺りの仕様はきちんと確認しておきたいです。

df['year'] = [y if y < 2000 else (y - 100) for y in df['year']]

f:id:upura:20181227122940p:plain

u++の成果物

ブログ

対外発表

出版

イベント

ウェブ公開

機械学習コンペティション競技プログラミング

アカデミック

講座

スポーツ