Christmas Tree Drawn with LightGBM
It is Christmas, so I painted Christmas tree with LightGBM.
This post is highly inspired by the following post:
The data was downloaded from the author's Github. And I added new data containing a new label representing the root of a tree.
import random random.seed(100) x_add = [random.random() *6 - 3 for i in range(100)] y_add = [-1 * random.random() *1.5 - 2.7 for i in range(100)] label_add = [2 if abs(i) < 0.6 else 1 for i in x_add] df_add = pd.DataFrame({ 'x': x_add, 'y': y_add, 'label': label_add }) df = pd.concat([df, df_add])
The plot_decision_regions of mlxtend library was used to draw decision boundaries of LightGBM. This is very easy to use, just passing the learned model and data.
from mlxtend.plotting import plot_decision_regions import lightgbm as lgb from sklearn.model_selection import train_test_split X = df[['x', 'y']] y = df['label'] X_train, X_valid, y_train, y_valid= train_test_split(X, y, random_state = 0) lgb_train = lgb.Dataset(X_train, y_train) lgb_eval = lgb.Dataset(X_valid, y_valid, reference=lgb_train) lgbm_params = { 'learning_rate': 0.2, 'num_leaves': 8, 'boosting_type': 'gbdt', 'reg_alpha': 1, 'reg_lambda': 1, 'objective': 'regression', 'metric': 'mae', } model = lgb.train( lgbm_params, lgb_train, valid_sets=lgb_eval, num_boost_round=1000, early_stopping_rounds=10, ) plt.figure(figsize=(10,10)) plt.xticks(color="None") plt.yticks(color="None") plt.tick_params(length=0) plot_decision_regions(np.array(X), np.array(y), clf=model, res=0.02, legend=2, colors='limegreen,white,brown')
Merry Christmas!
The ipynb can be seen on my GitHub.
LightGBMでクリスマスツリーを描く
本記事は、kaggle Advent Calendar 2018 その2の25日目の記事です。意図的にフライングして前日の24日、クリスマスイブに投稿します。
クリスマス用の記事として、LightGBMでクリスマスツリーを描いてみました。
なお「決定境界を用いて絵を描く」というアイディアは、4年前にTJOさんの投稿を見て以来、頭の片隅にありました。今年はKaggleに打ち込んでLightGBMに大変お世話になったので、最後までLightGBMを使い倒して、このアイディアを昇華させようと考えた次第です。
データセットはTJOさんのGitHubからダウンロードしました。
そのままでは工夫がないので「木の根元」に当たる新しいラベルもデータセットに加えました。
import random random.seed(100) x_add = [random.random() *6 - 3 for i in range(100)] y_add = [-1 * random.random() *1.5 - 2.7 for i in range(100)] label_add = [2 if abs(i) < 0.6 else 1 for i in x_add] df_add = pd.DataFrame({ 'x': x_add, 'y': y_add, 'label': label_add }) df = pd.concat([df, df_add])
LightGBMの決定境界を描く際には、mlxtendライブラリのplot_decision_regionsを使いました。学習済モデルとデータを渡すだけで、非常に簡単に利用可能です。
from mlxtend.plotting import plot_decision_regions import lightgbm as lgb from sklearn.model_selection import train_test_split X = df[['x', 'y']] y = df['label'] X_train, X_valid, y_train, y_valid= train_test_split(X, y, random_state = 0) lgb_train = lgb.Dataset(X_train, y_train) lgb_eval = lgb.Dataset(X_valid, y_valid, reference=lgb_train) lgbm_params = { 'learning_rate': 0.2, 'num_leaves': 8, 'boosting_type': 'gbdt', 'reg_alpha': 1, 'reg_lambda': 1, 'objective': 'regression', 'metric': 'mae', } model = lgb.train( lgbm_params, lgb_train, valid_sets=lgb_eval, num_boost_round=1000, early_stopping_rounds=10, ) plt.figure(figsize=(10,10)) plt.xticks(color="None") plt.yticks(color="None") plt.tick_params(length=0) plot_decision_regions(np.array(X), np.array(y), clf=model, res=0.02, legend=2, colors='limegreen,white,brown')
特にオチはないです。Merry Christmas!
実装はGitHubで公開しました。
2泊3日京都・大阪独り旅に行ってきました
単なる旅行記。
12月21〜23日に、2泊3日の京都・大阪の独り旅に行ってきました。普段あまり旅行をしないので、記録として残しておきます。
12/21(金)に有給とって、21〜23日くらいで関西いく予定を立ててる。今の所、京都・奈良の寺院を回るくらいしか考えていない。
— u++ (@upura0) December 6, 2018
奈良にも行くつもりでしたが、無計画すぎて行けませんでした。
1日目
ノープラン関西独り旅の幕開け pic.twitter.com/301Atv46To
— u++ (@upura0) December 21, 2018
1日目は京都で、清水寺と知恩院に行きました。あいにく、両者とも工事中で景観的には残念でした。
晩御飯は、京都の九条ネギを添えた餃子にしました。
2日目
朝一で龍安寺に行きました。
龍安寺、一番好き pic.twitter.com/CdXz8TLq3j
— u++ (@upura0) December 22, 2018
その後、下記イベントに参加するべく大阪大学の吹田キャンパスに向かいました。
大阪大学に降臨した……が、キャンパス内が広いので、まだまだ歩く🚶♂️ pic.twitter.com/MTzRgzotO9
— u++ (@upura0) December 22, 2018
広大な敷地のキャンパス内に、僕が見かけただけでも池が2つあり「これぞ理系キャンパス」という印象を受けました。
近隣に食事処がなかったので、学食でお茶漬けを食べました。
夜は、天満という呑み屋街で「お好み焼き」→「地酒」→「居酒屋」の3軒をハシゴしました。
大阪だーー pic.twitter.com/2PSkSoWJT3
— u++ (@upura0) December 22, 2018
第43回阪大AIメディカル研究会にて「野球データ分析ハッカソン準優勝解法と特徴量重要度」の題目で発表しました
はじめに
12月22日に開催された「第43回阪大AIメディカル研究会」にて「野球データ分析ハッカソン準優勝解法と特徴量重要度」の題目で発表しました。
下記の記事で取り上げたハッカソンのについての発表です。
12月21〜23日に京都・大阪に独り旅をしているのですが、ご縁あって発表の機会を頂くことになりました。
[第43回の開催について]
— 阪大AIメディカル研究会 (@AI49064710) December 13, 2018
日時:12/22(土)12:30〜
場所: 阪大吹田CP 産学共創本部C棟3F
発表内容:@namamono02go ~「可視化ライブラリplotlyの布教」
nさん~「ニューラルネット生成モデル(GAN)について」@Py2K4 ~「二分探索の解説」(内容変更?)
<特別ゲスト>@upura0
今回は4人発表します。
発表資料
その他の発表(メモ)
可視化ライブラリplotlyの布教
- カーソルを当てた時に数字やラベルなどを表示してくれる
- 日本語がデフォルトで問題なく使える利点あり
- Jupyter Labだとextensionを追加する必要がある
- チートシートもあるらしい
- 「HoloViews」を使っている人もいた
ずっと惰性でmatplotlibを使っているので、もう少しいろんな可視化ライブラリを検討しても良いなと思いました。notebookを共有する形式の発表で、再現性の面でも布教活動として素晴らしいです。
ニューラルネット生成モデル(GAN)について
- GANについて、数式を交えて分かりやすく解説
- the-gan-zooというリポジトリがある
- GANの問題点
- Mode collapse
- 生成器が観測データの一部の分布だけを学習し、特定の最頻値に近い値のみを出力してしまう現象
- GANの目的関数の設計上「一部だけでも、騙せれば良い」という発想になっている
- 明確な評価方法がない
- 学習が不安定(どこで打ち切れば良いのか)
- Mode collapse
- さまざまな対応策が進展しつつある
- 目的関数に「輸送コスト」の概念を与えることで理論的に安定な学習法(WGAN)
- 重みに制約を与えることで、安定かつ性能が高い(WGAN-GP)
GANは最近有象無象に研究が進展している印象でしたが、時代の流れに沿ってGANの課題と理論的な対応策を数式も含めてまとめてくださっており、自分の理解の整理ができました。
おわりに
突然お邪魔したにもかかわらず、暖かく迎えていただき、ありがとうございました!さまざま議論もできて、非常に勉強になりました。
運営の皆さま、参加者の皆さまに改めてお礼申し上げます。
pandas.DataFrameに祝日の特徴量を作る
はじめに
昨日公開した下記の記事で、以下のような感想を書きました。
祝日フラグは、手動で作成したので地味に辛かった思い出があります。
何となく書いたボヤキだったのですが、ありがたいことに次のリプライを頂きました。
休日フラグはこれ使いましたhttps://t.co/oEgMmeH3Pi
— wakame@kaggleやるマン (@wakame1367) December 20, 2018
お恥ずかしながら、私はこのパッケージの存在を知りませんでした。次の記事でも紹介されている通り、かなり使い勝手の良いライブラリのようです。
本記事では、反省と次なる機会への準備を兼ねて、pandas.DataFrameに祝日の特徴量を作る方法をまとめておきます。
データの準備
まずは、日付の行を持つpandas.DataFrameを作成します。
import pandas as pd from datetime import date, timedelta today = date.today() date_time = [] for i in range(1000): date_time.append(today + timedelta(days = i)) df = pd.DataFrame({ 'datetime': date_time }) print(df.head())
datetime 0 2018-12-21 1 2018-12-22 2 2018-12-23 3 2018-12-24 4 2018-12-25
祝日の特徴量の追加
datetimeごとに、関数を適用します。それだけだとTrue/Falseで値が返ってきますが、機械学習アルゴリズムへの適用を考えると1/0が望ましいので、intに変換しておきました。
import jpholiday df['is_holiday'] = df['datetime'].map(jpholiday.is_holiday).astype(int) print(df.head())
datetime is_holiday 0 2018-12-21 0 1 2018-12-22 0 2 2018-12-23 1 3 2018-12-24 1 4 2018-12-25 0
単なる祝日だけではなく、振替休日も取り扱っています。
signate「国立公園の観光宿泊者数予測」コンペで10位でした
本記事は、kaggle Advent Calendar 2018 その2の21日目の記事です。
はじめに
signateで開催されていた「国立公園の観光宿泊者数予測」コンペに参加し、10位でした。
参加者は471人、一度でも投稿した人が124人だったので、それなりの順位ではありますが、上位陣と大幅に差をつけられてしまい個人的には非常に残念な結果となりました。
本記事では、備忘録も兼ねて、私の解法をまとめておきたいと思います。
但し書き
本記事は、signate「国立公園の観光宿泊者数予測」の参加規約を遵守した範囲で作成しています。
https://signate.jp/competitions/141#terms
「第4条(秘密保持)」には、秘密情報には含まれないものに「開示されたいかなる情報にもよらずに独自に開発した情報(入賞対象者の評価対象提出物を除く)」とあります。私は2018年12月17日の「予測モデルソースコードの提出締切(※入賞候補連絡を受け取った方)」までに連絡を受け取っていないため「入賞対象者」になりえません。
これをもって、参加規約に反しない範囲で本記事を執筆します。
コンペの概要
本コンペでは、8つの国立公園周辺の観光宿泊者数を予測しました。
- 阿寒摩周国立公園
- 十和田八幡平国立公園
- 日光国立公園
- 伊勢志摩国立公園
- 大山隠岐国立公園
- 阿蘇くじゅう国立公園
- 霧島錦江湾国立公園
- 慶良間諸島国立公園
学習データの期間は「2015/1/1~2016/12/31」、テストデータの期間は「2017/1/1~2017/12/31」でした。
その他に以下のデータが提供されました。また外部データの利用も可能でした。
- SNSデータ(株式会社ホットリンク)
- ロケーション付SNSデータ(株式会社ナイトレイ)
- メッシュ型流動人口データ(株式会社Agoop)
- 公共交通検索ログデータ(ジョルダン株式会社)
- 国別月別来訪者数集計データ(株式会社コロプラ)
- 気象データ(気象庁)
- 積雪気象観測データ(防災科学技術研究所(NIED))
時系列データの予測というタスクのため「前日以前のデータを元に翌日以降の宿泊者数が予測可能なモデルを作成する」ことが制約条件となっていました。
出典:https://signate.jp/competitions/141#abstract
精度評価は「Mean Absolute Error(MAE)」でした。
関連コンペ
直近の関連コンペとしては「Recruit Restaurant Visitor Forecasting」がありました。レストランの過去の来店者数・予約数・天気などの情報を基に、将来の来店者数を予測するコンペでした。
Recruit Restaurant Visitor Forecasting | Kaggle
コンペの構造が非常に似ているため、こちらのコンペの解法を読んで自分の方針を検討しました。
Kaggle Tokyo Meetup #4では、pocketさんが解法を発表しています。
特徴
作成した特徴の概要を以下に示します。
日付に関する特徴
- 年
- 月
- 日
- 曜日
- 祝日フラグ
- 前日休みフラグ
- 翌日休みフラグ
- 六曜(大安とか仏滅とか)
- 循環性をsin, cosに落とし込んだ値
祝日フラグは、手動で作成したので地味に辛かった思い出があります。
「循環性をsin, cosに落とし込んだ値」は、全特徴量の中でも一番効果を発揮していました。下図のように、1月1日から12月31日までで一周するような円を仮定し、それぞれの日付のsin, cosを特徴として抽出します。2年間の学習データが持つ循環性を良い具合に表現できたのではないかと思います。
公共交通検索ログデータに関する特徴
このデータは「Recruit Restaurant Visitor Forecasting」における「予約」として見なせると考えました。具体的には、公園別・日別に、前日までに検索された回数を集計して特徴として加えました。
jorudan = pd.read_feather('./data/input/jorudan.feather') jorudan = jorudan[ jorudan['access_date'] < jorudan['departure_and_arrival_date'] ] jorudan = jorudan.groupby( ['departure_and_arrival_date', 'park'] ).count()['access_date'].reset_index()
この特徴は「循環性をsin, cosに落とし込んだ値」に次いで効果を発揮しました。
カテゴリ変数
カテゴリ変数や、それらの組み合わせで「Target Encoding」をしました。カテゴリ変数の特徴量エンジニアリングについては、以下のスライドが参考になると思います。
気象データに関する特徴
前日までの情報しか特徴として使えなかったので、1日分スライドして特徴として採用しました。
モデル
それぞれの公園ごとに、sklearnのGradientBoostingRegressorを利用しました。データ数の問題か、LightGBMよりも良い結果を出していました。
全ての公園をまとめて学習する方法も試しましたが、私の場合は個別で学習させた方が精度が高かったです。まとめて学習させた際には、緯度経度の情報も特徴に加えましたが、特に効果は発揮しませんでした。
ちなみに「Recruit Restaurant Visitor Forecastingでは緯度と経度の四則演算が効果を発揮した」という謎の情報を得たので試しましたが、全く効果はありませんでした笑*1。
時系列データということを意識し、交差検証に当たっては「TimeSeriesSplit」を利用しました。
sklearn.model_selection.TimeSeriesSplit — scikit-learn 0.20.2 documentation
その他の試行錯誤
以下の2つは、理論的には非常に効果を発揮しそうですが、今回はうまく機能しませんでした。
- 「Target Encoding」に当たって、学習データのうちテストデータに近いものをより重要視するような重み付け
- ラグを特徴量に使うような実装
後者は、下記の記事のようなイメージです。
結果
public LB20位くらいから、private LB10位になりました。時系列の検証をある程度意識したおかげか、private LBの順位が上がったのは朗報でした。
おわりに
本記事では「国立公園の観光宿泊者数予測」コンペの私の解法をまとめました。
おそらく上位陣は何か本コンペ特有のfindingsがあったのではないかと見込んでいます。可能ならば、何かしらの形で上位陣の解法をお聞きしたいです。
signateは今回が初参戦だったのですが、本コンペではKaggleのようなKernelやDiscussionがなく、他の参加者の取り組みを勉強できないのが心残りとなっています。Jリーグコンペのような公式のアフターイベントはなさそうなので、非公式な反省会を開催するのもありかもしれませんね。
[追記20181223]
僕の観測の範囲内で、解法を共有いただいた方のツイートをまとめました。
*1:
redashのpythonデータソースでカラム名を指定する時に使える型
BIツールのredashでpythonデータソースを使う際、カラム名を指定するために以下の関数を用いる。
add_result_column(result, column_name, friendly_name, column_type)
ここで4つ目の引数 "column_type" を指定した際に「この型はサポートしていません」というエラーが出た。GitHubのソースコードを追って使える型を確認したので、過程を含め記録しておく。
結論:使える型
- 'integer'
- 'float'
- 'boolean'
- 'string'
- 'datetime'
- 'date'
過程
関数 "add_result_column()" は「redash/redash/query_runner/python.py」内で定義されている。サポートしていない型を指定した場合には、下記でエラーが投げられる。
if column_type not in SUPPORTED_COLUMN_TYPES: raise Exception("'{0}' is not a supported column type".format(column_type))
"SUPPORTED_COLUMN_TYPES" は「redash/redash/query_runner/__init__.py」内で定義されている。文字列の完全一致が条件なので、例えば 'integer' を 'int' と指定するとエラーになる。
# Valid types of columns returned in results: TYPE_INTEGER = 'integer' TYPE_FLOAT = 'float' TYPE_BOOLEAN = 'boolean' TYPE_STRING = 'string' TYPE_DATETIME = 'datetime' TYPE_DATE = 'date' SUPPORTED_COLUMN_TYPES = set([ TYPE_INTEGER, TYPE_FLOAT, TYPE_BOOLEAN, TYPE_STRING, TYPE_DATETIME, TYPE_DATE ])