天色グラフィティ

機械学習やプログラミングでいろいろ作って遊ぶブログ

LightGBMのcallbackを利用して学習履歴をロガー経由で出力する

KaggleなどでLightGBMを使っていて学習履歴を見たとき、ログファイルにも残してほしいと思うことがあります。

公式にはそのような機能は実装されていないようなので、LightGBMのコールバックで対応したいと思います。 LightGBMではfitメソッドの引数としてコールバック関数を渡すことができ、内部的にはEarlyStoppingや学習履歴を標準出力に吐くのに使われています。

LightGBMのコールバックを実装

lightgbm/callback.pyを見ると、学習時に標準出力に履歴を表示する関数があります。

def print_evaluation(period=1, show_stdv=True):
    """Create a callback that prints the evaluation results.
    Parameters
    ----------
    period : int, optional (default=1)
        The period to print the evaluation results.
    show_stdv : bool, optional (default=True)
        Whether to show stdv (if provided).
    Returns
    -------
    callback : function
        The callback that prints the evaluation results every ``period`` iteration(s).
    """
    def _callback(env):
        if period > 0 and env.evaluation_result_list and (env.iteration + 1) % period == 0:
            result = '\t'.join([_format_eval_result(x, show_stdv) for x in env.evaluation_result_list])
            print('[%d]\t%s' % (env.iteration + 1, result))
    _callback.order = 10
    return _callback

これをベースにしてカスタマイズしてあげればよさそうです。

実装例は以下のとおりです。基本的にはprint_evaluationと一緒で、logging.Loggerとログレベルを新たに受け取れるようにしてあります。

import logging

from lightgbm.callback import _format_eval_result


def log_evaluation(logger, period=1, show_stdv=True, level=logging.DEBUG):
    def _callback(env):
        if period > 0 and env.evaluation_result_list and (env.iteration + 1) % period == 0:
            result = '\t'.join([_format_eval_result(x, show_stdv) for x in env.evaluation_result_list])
            logger.log(level, '[{}]\t{}'.format(env.iteration+1, result))
    _callback.order = 10
    return _callback

使い方

ロガーを予め作成しておいて log_evaluation の引数に渡してあげれば動くはずです。

# ロガーの作成
logger = logging.getLogger('main')
logger.setLevel(logging.DEBUG)
sc = logging.StreamHandler()
logger.addHandler(sc)
fh = logging.FileHandler('hoge.log')
logger.addHandler(fh)

# データのロードなどは省略

# 訓練時にコールバックのリストを渡す
clf = lgb.LGBMClassifier()
callbacks = [log_evaluation(logger, period=10)]
clf.fit(X_train, y_train, eval_set=[(X_val, y_val)], callbacks=callbacks)

LightGBMのコールバックの実装方法

コールバック関数のリストをfit()時のcallbacks引数に渡してあげるとコールバックが呼ばれます。

コールバック関数は引数をひとつ取る必要があります。その引数には以下のように学習状況などが入ったnamedtupleが渡されます。

namedtuple(
    "LightGBMCallbackEnv",
    ["model",
     "params",
     "iteration",
     "begin_iteration",
     "end_iteration",
     "evaluation_result_list"])

例えば env.iteration のようにすれば現在のイテレーション数が取れます。(0始まりなので+1するのが良いと思います)

コールバック関数にパラメータを設定したい場合は今回のようにクロージャ(関数を返す関数)にしてあげると良いです。

まとめ

  • LightGBMのコールバックを作成すると学習履歴をロガーに渡すことができる
  • lightgbm/callback.pyはシンプルなので読んでみるとよい

お役立ちリンク

2018年10月を振り返ってみる

最近、Dynalistというサービスを使ってやったことをメモしていました。それを元に今月を振り返りをやっていきます。

やったこと

毎日GitHubに草をはやした

f:id:ejinote:20181031173219p:plain

前半はGoのツールで、後半はPLAsTiCCコンペでコミットを稼いだ。 苦し紛れでAtCoderの適当な問題を埋めてたりするけど、個人的には結構がんばったつもり。

Goで簡単なCLIツールを書いた

まとまった単位でコードを書いていかないといつまで経っても書けるようにならないという知見を得た。

初めて勉強会でLTした

勉強会駆動開発というのをしてみた。だいたい1週間くらいで発表のネタを作って、ぱぱっとconnpassで登録した。何事も飛び込んでみるもんだ。

面白かった記事

買った本

結構本を買った。前半が技術書で、後半がその他。

今月のコンセプトは「わかったつもりになったものを、体系的に学び直す」みたいな感じ。 Webから仕入れた知識だとどうしても断片的になってしまう。わかったつもりになってる内容でも、書籍で体系的に学び直すと新たな気付きがあって良い。

技術書

Docker/Kubernetes 実践コンテナ開発入門

Docker/Kubernetes 実践コンテナ開発入門

  • Kubernetesに入ったところまで読んだ
  • 最近KaggleでDocker使っていて大変はかどっている
  • ハンズオンのような進め方で、進み方自体にも違和感がなくて良い

入門 自然言語処理

入門 自然言語処理

  • 前半を読み終わった時点で途中を飛ばし、日本語に対する自然言語処理について読んだ

Vue.js入門 基礎から実践アプリケーション開発まで

Vue.js入門 基礎から実践アプリケーション開発まで

  • Vueはざっと使えるつもりだけど、コンポーネントの分割のベストプラクティスとかが知りたくて買った

ゼロから作るDeep Learning ? ―自然言語処理編

ゼロから作るDeep Learning ? ―自然言語処理編

  • 積んでいる

技術同人誌を書こう!  アウトプットのススメ (技術書典シリーズ(NextPublishing))

技術同人誌を書こう! アウトプットのススメ (技術書典シリーズ(NextPublishing))

  • アウトプットの機運が高まって買った
  • 技術書展出たい
  • 予想以上に実践的な内容。同人誌の表紙はどうつくるか、サークルの登録はどうするか、締切の設定方法、とかが書いてあったりする。

その他

なんか研究室のノリで統計検定1級を受けることになったが、全然できなくて絶望している。

数理統計学 (数学シリーズ)

数理統計学 (数学シリーズ)

日本統計学会公式認定 統計検定 1級・準1級 公式問題集[2016〜2017年]

日本統計学会公式認定 統計検定 1級・準1級 公式問題集[2016〜2017年]

欲しい本

Go言語による並行処理

Go言語による並行処理

速習 強化学習 ―基礎理論とアルゴリズム―

速習 強化学習 ―基礎理論とアルゴリズム―

  • 作者: Csaba Szepesvari,小山田創哲,前田新一,小山雅典,池田春之介,大渡勝己,芝慎太朗,関根嵩之,高山晃一,田中一樹,西村直樹,藤田康博,望月駿一
  • 出版社/メーカー: 共立出版
  • 発売日: 2017/09/21
  • メディア: 単行本
  • この商品を含むブログを見る

なんと、ここに私の欲しいものリストがあります。

使い始めたツール・サービス

  • Kindle Unlimited
    • かゆいところには全く手が届かないラインナップ
    • 読みたい本はだいたい読んだので、しばらくしたら退会するつもり
  • Dynalist
    • アウトライナー
    • 無料、デザインがきれい、取り回しに違和感がない、の三拍子
    • ブログの下書きとか、思考の整理とかに使う。この記事の下書きもDynalist
    • Boostnoteから乗り換え
    • エクスポートが柔軟にできると嬉しいなー

最近意識していること

  • ドキュメント駆動開発
    • 闇雲にコードを書くのではなく、先にドキュメントを書いてそれに沿って開発をすすめる
    • やるべきタスクがクリアになるので効率が上がる
    • これに限らず、自分が今何をすべきかを意識することの大切さを知った

11月に向けて

  • 修士研究
  • GitHubの草継続
  • PLAsTiCC Astronomical Classificationコンペ
  • 統計検定

あたりを頑張りたいと思います。

Goあんこ4kgでLTしました

知らない人にはGoあんこってなんだよって話だと思いますが。

GoあんことはGo(Un)Conferenceの略で、Go言語のゆるふわLT会のことです。4kgは4回目って意味です。かわいい。

発表資料はこちらです。

主催者は@deadcheatさんと@syossan27さんのおふたりで、株式会社アイスタイル(@cosmeを作ってる会社)で開催されました。 久しぶりにギロッポン行きましたけど、ギロッポンってなんか「ビジネスーーーーッ! オラオラオラーーーッ」みたいな雰囲気して近寄りがたくないですか?

他の登壇者はみなさん業務でバリバリGoを使ってらっしゃるようで、僕はハードル下げる枠でした。初心者歓迎ゆるふわLT会たらしめる役目は果たしたと思います。

発表の要点をかいつまんで書くと、

  • 新しい言語を勉強した人は「本当になんでもいいから」アウトプットしよう
  • Goを使うと最初のハードルがほんとに低いのでおすすめ
  • LTの予定を入れて勉強会駆動開発しよう

の3点です。

プログラミング言語って本やチュートリアルで勉強しただけだと全く身につかなくて、何かに使って初めて身につくものですよね。 でも、初心者はこう考えてしまいます。 「作れるものはたかが知れてるし、ツールを実装しようにもアイディアがないし」 そんなことを言ってるうちにせっかく勉強した言語は忘れてしまうでしょう。

そこで「全く役に立たないツールを作ろう」と発想を転換すると、あら不思議。ツールのアイディアが湧いてきます。 ぱぱっと作ってぱぱっとLTしちゃいましょう。

みたいな話をしました。

僕みたいなにわかGopherが会場にも隠れキリシタンのようにいらっしゃったでしょうし、「Goあんこ行ってみたいけど……」みたいにslideshareを眺めてる方もいらっしゃると思います。そんな方々に届いてほしいLTでした。

聴衆のみなさまもあたたかく、とても良い勉強会でした。また参加したいと思います。

次回は「Goで始めるシステムトレード」みたいなタイトルで発表できたら良いな、なんて考えています。

以下は面白かった発表です。資料がアップロードされたら詳細を追記します。

  • 自宅サーバーを実現するためにwanpollをつくった話」 @kawasin73 さん
  • 「複雑になってきたCLIコマンドをちゃんとテストする」@LightTiger2505 さん
  • 「Wrap(err) in production」 @izumin5210 さん

もらったフェルトのGopherくん。

Golangでどうでもいい知識を教えてくれるCLIツールを作った

f:id:ejinote:20181006195046p:plain

最近ghqの作者@motemenさんのインタビューpecoの作者@lestrratさんのインタビューを読んでいてCLIツールを作りたい欲がむらむらと湧いていました。

@motemenさんのインタビューによると、普段から「これ不便だな。ツールにならないかな」とアンテナを貼っていることがツール作成においては大事だそうです。これを読んで僕もアンテナ高くしようと思った次第なのですが、思ってすぐに役に立つツールのアイデアが湧いてきたら苦労しないわけでして。

今回はCLIツールを作る練習として、「そんなに役には立たないけど、面白い」みたいなツールを作ってみました。 成果物はこちらです。

github.com

trivia コマンドを打つとWikipediaからランダムに単語を引っ張ってきて説明をしてくれます。日本語版Wikipediaを検索してるのにカウ高校とパハラ小学校(ハワイの学校)とか出てきて結構面白いです。

使う言語は勉強も兼ねてGolangを選択しました。 go get で簡単にインストールできるので配布もしやすいですし、何より書いててちょっと楽しいです。

インストールは

$ go get -u github.com/amaotone/trivia

でOKです。デフォルトの言語設定が(ちょっとイキった結果)英語になっているので、

$ trivia set -l ja

で日本語に変えて

$ trivia

でどうでもいい知識を教えてくれます。

続きを読む

KaggleのHome Creditコンペで銀メダルを取った話と、チームで動く際のノウハウとか

書く書くといっておきながらなかなか書かないでいたらGoogle Analyticsコンペが始まってしまいました。慌ててこの参戦記を書いています。

Home Credit Default Riskコンペに参加し、166位で銀メダルを取りました!

僕は同じ研究室の@sugawarya、東大松尾研のみなさまとチームを組んで参戦しました。

f:id:ejinote:20180917002544p:plain

僕は2枚めの銀メダルを獲得し、Kaggle Masterまであと金メダル1枚というところに来ました。就職するまでにKaggle Masterになっていたいものです。

さて、ここからコンペの振り返りをしていくのですが、ありがたいことにKaggle始めたての方も多少見てくださっているようですので、少し丁寧に書こうと思います。また、今回はじめての大きなチーム戦で試行錯誤したので、チームで進める際にこんなことをするといいんじゃないかなーと思うことも最後に書きました。

  • コンペ概要
  • 僕(たち)がやったこと
    • 前処理
    • 特徴量作成
    • モデル
    • アンサンブル
  • 上位の解法まとめ
    • 特徴量
    • モデル
    • アンサンブル
    • その他面白かった解法
    • 反省まとめ
  • チームでの動き方
    • 目的を共有する
    • 情報を共有する
    • 役割を分担する
  • まとめと感想

コンペ概要

Home Credit Default RiskコンペはKaggleで行われたコンペのひとつです。 Home Credit社は、信用の積み重ねが足りずに融資を受けることができない顧客にも融資を行う会社で、今回のコンペは債務不履行(デフォルト, default)になる顧客を予測する、というものです。

与えられたデータは、

  • application_train/test
    • それぞれの行が融資の申込みを表し、それが不履行になるかどうかを予測するのが今回のタスク
  • bureau
    • Home Credit社以外のクレジットビューローでの融資情報
  • bureau balance
    • bureauの負債残高の履歴
  • previous applications
    • Home Credit社における過去の融資情報
  • installment payments
    • 過去の融資の残高履歴
  • credit card balance
    • Home Credit社のカードの残高履歴
  • POS cash balance
    • Home Credit社の店頭融資の残高履歴

など、多数のファイルに分けて与えられています。

applicationの1行に対しbureauやpreviousなどのサブファイルが複数行対応付けられているため、サブファイルの情報をどうやって抽出するかがキモとなったコンペと言えるでしょう。

続きを読む

Kaggleで使えるFeather形式を利用した特徴量管理法

みなさま、Kaggle楽しんでいますでしょうか。 僕は現在Home Credit Default RiskSantander Value Prediction Challengeに参加しています。

前回のKaggle記事ではpandasのテクニックについてまとめました。 多くのアクセスをいただき、人生初のホッテントリ入りまで経験してたいそう嬉しかったです。ありがとうございました!

amalog.hateblo.jp

さて。みなさんはKaggleをやっているとき、どのようにして特徴量を管理していますか?

Titanicくらいならその都度計算すれば十分ですが、 ある程度データのサイズが大きくなり、さまざまな特徴量を取捨選択するようになると特徴量のシリアライズ(保存)が欠かせません。

そこで、今回は僕が行っている特徴量管理方法を紹介したいと思います。 僕の方法はTalkingdata Adtracking Fraud Detectionコンペの1位、flowlightさんのリポジトリを参考にしています。

概要

主要なポイントをまとめると以下のとおりです。

目次

  • 概要
  • 目次
  • Feather形式でのシリアライズ
  • 基底クラスの実装
    • timer()
    • Featureクラス
  • argparseを利用したコマンドラインツール化
    • get_arguments()
    • get_features()
    • generate_features()
  • 利用例
  • 特徴量の読み込み
  • まとめ
続きを読む

ARC100に参加した(510位)

f:id:ejinote:20180705221406p:plain

ARC100に参加しました。結果は510位で、レートは1429→1427と微減しました。

問題としてはC問題を1WAしながらACし、D問題が解けませんでした。久しぶりにDで手こずった気がします。

今回から、解けなかった問題については典型要素の抽出をやってみたいと思います。

前回の記事:

amalog.hateblo.jp

振り返り

C - Linear Approximation

数列 Aと整数 bについて \sum_{i}  | A_{i} - (b - i) |の最小値を求める問題。

予め数列から iを引いておけば、あとはL1ノルムを最小化する代表値(=中央値)を bに設定すればよい。

思いつくためには、横軸 i、縦軸 A_{i}-iのプロットを書いてみるとよい。横線を一本引いたときに、その横線より上に来てる点と下に来てる点が同じ数のところが最善であることに気がつくはず。

#!/usr/bin/env python
N = int(input())
A = list(map(int, input().split()))
A = sorted([a-(i+1) for i, a in enumerate(A)])
m = A[N//2]
ans = 0
for a in A:
    ans += abs(a-m)
print(ans)

D - Equal Cut

数列を4つに分けて、それぞれの和の最大と最小の差を最小化する問題。

端から貪欲でやるのはNG。たとえば1, 1, 1, 1, 10000とかのとき、1つの部分の合計は理想的には合計/4=2501だけど、2501になるように貪欲でとっていくと4つに分けられない。

解法は以下の通り。

  1. まず2つに分け終わったとする
  2. それぞれの部分については、理想的な分割箇所(=2つの合計ができるだけ近くなるとこ)は一意に定まる
  3. なので、最初の分割点を全探索して、それぞれの部分は理想的な分割にすれば、トータルの理想的な分割も定まるはず
  4. ちなみに、最初の分割点を右にずらすと、2段階目の分割点はそれぞれ変わらないor右にずれるのいずれかになるので、探索を減らせる

こんな感じ。これはできてもよかったな……

典型要素としては、

  • 区間の累積和は予め全体の累積和を取っておけば差を利用することで O(1)で求まる
  • 「ここさえ探索すればあとは一意に定まる」みたいな感じで探索すべき場合の数を減らす

ってなところか。

#!/usr/bin/env python
from itertools import accumulate

N = int(input())
A = list(map(int, input().split()))
Acum = list(accumulate([0] + A))

def cumsum(left, right):
    # [i, j)
    return Acum[right] - Acum[left]

def find_best_cut(left, right, prev):
    prev = max(prev, left+1)
    now = prev
    res = now
    best = abs(cumsum(left, prev) - cumsum(prev, right))
    while True:
        now += 1
        score = abs(cumsum(left, now) - cumsum(now, right))
        if score < best:
            best = score
            res = now
        else:
            return res

left = 0
right = 0
best = float('inf')
for center in range(1, N-1):
    left = find_best_cut(0, center, left)
    right = find_best_cut(center, N, right)
        
    t = [cumsum(0, left), cumsum(left, center), cumsum(center, right), cumsum(right, N)]
    score = max(t) - min(t)
    if score < best:
        best = score
    
print(best)

気づいたこと

D問題まではPythonで書いて、E以降はC++で書こうと思っていたけど、E問題が解ける頻度はまだ少ないからC++で書く練習がなかなか積めないという問題点に気づいた。