u++の備忘録

Christmas Tree Drawn with LightGBM

It is Christmas, so I painted Christmas tree with LightGBM.

This post is highly inspired by the following post:

tjo.hatenablog.com

The data was downloaded from the author's Github. And I added new data containing a new label representing the root of a tree.

github.com

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.

rasbt.github.io

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')

f:id:upura:20181215165835p:plain

Merry Christmas!

The ipynb can be seen on my GitHub.

github.com

LightGBMでクリスマスツリーを描く

本記事は、kaggle Advent Calendar 2018 その2の25日目の記事です。意図的にフライングして前日の24日、クリスマスイブに投稿します。

qiita.com

クリスマス用の記事として、LightGBMでクリスマスツリーを描いてみました。

なお「決定境界を用いて絵を描く」というアイディアは、4年前にTJOさんの投稿を見て以来、頭の片隅にありました。今年はKaggleに打ち込んでLightGBMに大変お世話になったので、最後までLightGBMを使い倒して、このアイディアを昇華させようと考えた次第です。

tjo.hatenablog.com

データセットはTJOさんのGitHubからダウンロードしました。

github.com

そのままでは工夫がないので「木の根元」に当たる新しいラベルもデータセットに加えました。

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を使いました。学習済モデルとデータを渡すだけで、非常に簡単に利用可能です。

qiita.com

rasbt.github.io

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')

f:id:upura:20181215165835p:plain

特にオチはないです。Merry Christmas!

実装はGitHubで公開しました。

github.com

2泊3日京都・大阪独り旅に行ってきました

単なる旅行記

12月21〜23日に、2泊3日の京都・大阪の独り旅に行ってきました。普段あまり旅行をしないので、記録として残しておきます。

奈良にも行くつもりでしたが、無計画すぎて行けませんでした。

1日目

1日目は京都で、清水寺知恩院に行きました。あいにく、両者とも工事中で景観的には残念でした。

f:id:upura:20181223073439j:plain
f:id:upura:20181223073444j:plain

晩御飯は、京都の九条ネギを添えた餃子にしました。

f:id:upura:20181223073716j:plain
f:id:upura:20181223073646j:plain

2日目

朝一で龍安寺に行きました。

その後、下記イベントに参加するべく大阪大学の吹田キャンパスに向かいました。

upura.hatenablog.com

広大な敷地のキャンパス内に、僕が見かけただけでも池が2つあり「これぞ理系キャンパス」という印象を受けました。

f:id:upura:20181223074307j:plain

近隣に食事処がなかったので、学食でお茶漬けを食べました。

f:id:upura:20181223074430j:plain

夜は、天満という呑み屋街で「お好み焼き」→「地酒」→「居酒屋」の3軒をハシゴしました。

f:id:upura:20181223074433j:plain


3日目

最終日は大阪で、大坂城四天王寺を見ました。

f:id:upura:20181223123828j:plain


おわりに

この旅の前・最中に、TwitterのリプライやDMで多くの方から見どころ紹介などアドバイスを頂きました。ありがとうございました。

第43回阪大AIメディカル研究会にて「野球データ分析ハッカソン準優勝解法と特徴量重要度」の題目で発表しました

はじめに

12月22日に開催された「第43回阪大AIメディカル研究会」にて「野球データ分析ハッカソン準優勝解法と特徴量重要度」の題目で発表しました。

下記の記事で取り上げたハッカソンのについての発表です。

upura.hatenablog.com

12月21〜23日に京都・大阪に独り旅をしているのですが、ご縁あって発表の機会を頂くことになりました。


発表資料


その他の発表(メモ)

可視化ライブラリplotlyの布教

  • カーソルを当てた時に数字やラベルなどを表示してくれる
  • 日本語がデフォルトで問題なく使える利点あり
  • Jupyter Labだとextensionを追加する必要がある
  • チートシートもあるらしい
  • 「HoloViews」を使っている人もいた

qiita.com

ずっと惰性でmatplotlibを使っているので、もう少しいろんな可視化ライブラリを検討しても良いなと思いました。notebookを共有する形式の発表で、再現性の面でも布教活動として素晴らしいです。

ニューラルネット生成モデル(GAN)について

  • GANについて、数式を交えて分かりやすく解説
  • the-gan-zooというリポジトリがある
  • GANの問題点
    • Mode collapse
      • 生成器が観測データの一部の分布だけを学習し、特定の最頻値に近い値のみを出力してしまう現象
      • GANの目的関数の設計上「一部だけでも、騙せれば良い」という発想になっている
    • 明確な評価方法がない
      • 学習が不安定(どこで打ち切れば良いのか)
  • さまざまな対応策が進展しつつある
    • 目的関数に「輸送コスト」の概念を与えることで理論的に安定な学習法(WGAN)
    • 重みに制約を与えることで、安定かつ性能が高い(WGAN-GP)

GANは最近有象無象に研究が進展している印象でしたが、時代の流れに沿ってGANの課題と理論的な対応策を数式も含めてまとめてくださっており、自分の理解の整理ができました。

二分探索の解説

  • KaggleのKernelを用いて「二分探索」を解説
  • AtCoderような自動ジャッジシステムを、KaggleのKernel上に実装
  • Kernelの仕様のせいか、黒魔術的なコードが多かった

KaggleのKernelはユーザが環境を準備する必要もなく使えるので、その上このような自動ジャッジシステムがあれば、初心者の入門教材として非常に優れていると思いました。

[追記 20181223] この発表内容を元にしたQiita記事が公開されました。
qiita.com

おわりに

突然お邪魔したにもかかわらず、暖かく迎えていただき、ありがとうございました!さまざま議論もできて、非常に勉強になりました。

運営の皆さま、参加者の皆さまに改めてお礼申し上げます。

pandas.DataFrameに祝日の特徴量を作る

はじめに

昨日公開した下記の記事で、以下のような感想を書きました。

祝日フラグは、手動で作成したので地味に辛かった思い出があります。

upura.hatenablog.com

何となく書いたボヤキだったのですが、ありがたいことに次のリプライを頂きました。

お恥ずかしながら、私はこのパッケージの存在を知りませんでした。次の記事でも紹介されている通り、かなり使い勝手の良いライブラリのようです。

st-hakky.hatenablog.com

本記事では、反省と次なる機会への準備を兼ねて、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

単なる祝日だけではなく、振替休日も取り扱っています。

おまけ

数日前にversion 0.0.6がリリースされています。

天皇の即位の日及び即位礼正殿の儀の行われる日を休日とする法律に対応しました。
変更点は下記のとおりです

www.lalcs.com

print(jpholiday.is_holiday_name(date(2019, 5, 1)))
天皇の即位の日

最新情報が迅速に盛り込まれていて、素晴らしいパッケージだと感じました。

signate「国立公園の観光宿泊者数予測」コンペで10位でした

本記事は、kaggle Advent Calendar 2018 その2の21日目の記事です。

qiita.com

はじめに

signateで開催されていた「国立公園の観光宿泊者数予測」コンペに参加し、10位でした。

signate.jp

参加者は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))

時系列データの予測というタスクのため「前日以前のデータを元に翌日以降の宿泊者数が予測可能なモデルを作成する」ことが制約条件となっていました。

f:id:upura:20181220132715p:plain
出典:https://signate.jp/competitions/141#abstract

精度評価は「Mean Absolute Error(MAE)」でした。

f:id:upura:20181220140503p:plain

関連コンペ

直近の関連コンペとしては「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年間の学習データが持つ循環性を良い具合に表現できたのではないかと思います。

f:id:upura:20181220140045p:plain

公共交通検索ログデータに関する特徴

このデータは「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日分スライドして特徴として採用しました。

使わなかったデータ

以下のデータは、一部試行錯誤したものもありますが、結局うまい具合に使いこなせず特徴には利用できませんでした。恐らく、この部分の特徴の作り込みで、最終的な順位の差が出たのだと考えています。

  • SNSデータ(株式会社ホットリンク)
  • ロケーション付SNSデータ(株式会社ナイトレイ)
  • メッシュ型流動人口データ(株式会社Agoop)
  • 国別月別来訪者数集計データ(株式会社コロプラ

モデル

それぞれの公園ごとに、sklearnのGradientBoostingRegressorを利用しました。データ数の問題か、LightGBMよりも良い結果を出していました。

全ての公園をまとめて学習する方法も試しましたが、私の場合は個別で学習させた方が精度が高かったです。まとめて学習させた際には、緯度経度の情報も特徴に加えましたが、特に効果は発揮しませんでした。

ちなみに「Recruit Restaurant Visitor Forecastingでは緯度と経度の四則演算が効果を発揮した」という謎の情報を得たので試しましたが、全く効果はありませんでした笑*1

時系列データということを意識し、交差検証に当たっては「TimeSeriesSplit」を利用しました。

sklearn.model_selection.TimeSeriesSplit — scikit-learn 0.20.2 documentation

upura.hatenablog.com

その他の試行錯誤

以下の2つは、理論的には非常に効果を発揮しそうですが、今回はうまく機能しませんでした。

  • 「Target Encoding」に当たって、学習データのうちテストデータに近いものをより重要視するような重み付け
  • ラグを特徴量に使うような実装

後者は、下記の記事のようなイメージです。

qiita.com

結果

f:id:upura:20181220150004j:plain

public LB20位くらいから、private LB10位になりました。時系列の検証をある程度意識したおかげか、private LBの順位が上がったのは朗報でした。

おわりに

本記事では「国立公園の観光宿泊者数予測」コンペの私の解法をまとめました。

おそらく上位陣は何か本コンペ特有のfindingsがあったのではないかと見込んでいます。可能ならば、何かしらの形で上位陣の解法をお聞きしたいです。

signateは今回が初参戦だったのですが、本コンペではKaggleのようなKernelやDiscussionがなく、他の参加者の取り組みを勉強できないのが心残りとなっています。Jリーグコンペのような公式のアフターイベントはなさそうなので、非公式な反省会を開催するのもありかもしれませんね。

[追記20181223]

僕の観測の範囲内で、解法を共有いただいた方のツイートをまとめました。

togetter.com

*1:

redashのpythonデータソースでカラム名を指定する時に使える型

BIツールのredashでpythonデータソースを使う際、カラム名を指定するために以下の関数を用いる。

add_result_column(result, column_name, friendly_name, column_type)

redash.io

ここで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))

github.com

"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
])

github.com