u++の備忘録

Kaggle APIとLINE APIを用いたKernelの新規投稿を通知する仕組みの構築

はじめに

Kaggle*1において、上位の成績を収めるためには日々投稿されるDiscussionやKernel、変化するLeaderboardの動向を注視することが大切です。

現状、Discussionについては公式で新しい投稿をメール通知する仕組みがあります*2が、KernelやLeaderboardについては存在していません。8月上旬には、日本人のKaggle参加者が集まるコミュニティ「kaggler-ja」*3での会話を発端として、Leaderboardの通知に関する要望も提案されています*4

もちろん公式で実装されるのが一番良いですが、今回は暫定的な対応としてKaggle公式が提供する「Kaggle API*5を利用して、Kernelの新規投稿をLINE通知する仕組みを構築しました。

同様の枠組みを用いることで、Leaderboardの情報などKaggle APIで扱える範囲の通知機能を実装することが可能です。私自身が一番欲しかったのがKernelの通知機能だったので、優先的に実装しました。実装はGitHubで公開しています。

github.com

システムの概要

構築したシステムの概要を下図に示します。

f:id:upura:20180825162810p:plain

大まかな処理の流れは以下の通りです。

  1. 「launchd」を用いて30秒に一度シェルスクリプトを定期実行
  2. シェルスクリプトの処理
    1. Kaggle APIを実行して、Kernelの最新の情報を取得
    2. Pythonを呼び出し、前回実行時からKernelの情報に差分があるか確認
    3. 差分がある場合には、「LINE API*6で通知を送る

以下では、個々の処理について説明していきます。

launchdによる定期実行

launchdを用いることで、Mac OS Xスクリプトを定期実行する仕組みを実現できます。詳細は、私が実装時に参考にした記事*7をご参照ください。

端的に説明すると ~/Library/LaunchAgents/ に以下のようなファイルを配置して、定期実行の条件を設定できるようになっています。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
    <string>kernel-notify</string>
  <key>ProgramArguments</key>
    <array>
      <string>/usr/local/bin/zsh</string>
      <string>/Users/{user_name}/{project_name}/kernel-notify.sh</string>
    </array>
  <key>StartInterval</key>
    <integer>30</integer>
  <key>RunAtLoad</key>
    <true/>
  <key>StandardOutPath</key>
    <string>/Users/{user_name}/{project_name}</string>
  <key>StandardErrorPath</key>
    <string>/Users/{user_name}/{project_name}</string>
</dict>
</plist>

Kaggle APIの実行

Kaggle API自体の導入については、GitHubのREADMEや下記の記事などをご確認ください。

www.currypurin.com

今回はKernelの情報を取得したかったので、以下のコマンドを実行しました。ここでオプションとしてコンペティションの指定をしています。

kaggle kernels list --competition home-credit-default-risk --sort-by 'dateCreated'

前回実行時からKernelの情報に差分があるか確認

この処理以降、Pythonに委託することにしました。単純に情報を前回実行時と比較し、変更があるか否かを確認しました。

LINE APIでの通知

LINE APIの導入については、下記を参考にしました。

s4t.hatenablog.com

以下の関数を用いて、LINEに通知を送ることができます。文言も自由に編集できるので、今回は新規投稿されたKernelのURLを通知することにしました。

def line(Me):
    line_notify_token = 'YOUR TOKEN'
    line_notify_api = 'https://notify-api.line.me/api/notify'
    message = '\n' + Me
    payload = {'message': message}
    headers = {'Authorization': 'Bearer ' + line_notify_token}
    line_notify = requests.post(line_notify_api, data=payload, headers=headers)

デモンストレーション

実際に本日15時44分ごろに新規投稿されたKernel*8について、LINE通知が確認できました。

投稿されたKernel
f:id:upura:20180825171132p:plain
LINE通知
f:id:upura:20180825171002p:plain

おわりに

今回はKaggle APIとLINE APIを用いて、Kernelの新規投稿を通知する仕組みを構築しました。この仕組みを応用することで、Kaggle APIで扱える範囲のさまざまな条件下の通知機能を実装できます。

こういった仕組みも活用して、より一層Kaggleを楽しんでいきたいと思います。

脚注

KaggleのSantander Value Prediction Challengeで銀メダルを取るためにしたこと(85th place solution)

注釈

初回公開時から89→90→85位に順位変動しました。

Santander Value Prediction Challengeが本日終わり、順位は8985位で銀メダルでした。既にdiscussionに解法を投稿していますが、ブログにも日本語で共有します。

f:id:upura:20180821095435p:plain

85th place solution

主な戦略は、以下の組み合わせです。

  • "leak" が発表される以前から作成していた単一のlightGBMモデル
  • @amrrs らによる "leak" 発見モデル

単一のモデルでは、Public LBで1.37を得ていました(ローカルcvでは1.30でした)。

また、次のように "leak" を利用しています。

df1[~test_leak['compiled_leak'].isnull()] = df0[~test_leak['compiled_leak'].isnull()]

ここで、df1, df0, test_leakは下記の通りです。

  • df1は単一モデルのsubmission csvファイル
  • df0は@amrrsのsubmission csvファイル
  • test_leakは@amrrs'sの "test_leak.csv"

もちろん、こちらは概要で、実際にはもっと色々なことを試しています。

Santander Value Prediction Challengeに携わった全ての方々に感謝したいと思います!

English

85th place solution

The main strategy is the combination of the following:

  • The single lightGBM model I've created before discovering"leak"
  • The "leak" finding model shared by @amrrs

With a single model, I got 1.37 on public LB (and 1.30 on local cv).

In addition, I utilize "leak" in the following way:

df1[~test_leak['compiled_leak'].isnull()] = df0[~test_leak['compiled_leak'].isnull()]

where

  • df1 is the submission csv file of my single model
  • df0 is the submission csv file of @amrrs's
  • test_leak is "test_leak.csv" of @amrrs's

Of course, this is a simple description and I've tried a lot more.

I'd like to thank everyone involved in Santander Value Prediction Challenge!

【Pandas】ある条件の列名の列を足し合わせる

業務で書いた。いろいろググったのでメモ。

要件

以下のようなテーブルがあったときに、2018年6月の合計を計算したい、つまり"201806"から始まる列の値を足し合わせたい。

user id 20180601 20180602 20180603 20180604 20180807
aaaaa 0 500 0 500 1000
aaaab 10 500 0 500 1000
aaaac 0 200 300 100 400

実装

df['201806'] = df.iloc[:,df.columns.str.startswith('201806')].sum(axis=1)

df.columns.str.startswith('201806')

  • df.columns で列名の一覧を出す
  • .str.startswith('201806') で、それぞれの列名が'201806'で始まるか否かを判定

df.iloc

  • 行や列を指定して取り出す

.sum(axis=1)

  • 列を足し合わせる

競技プログラミングを始めた&ABC104

競技プログラミングを始めました。「AIが〜」というオジサンではなく、きちんと自分の手を動かせる人間でありたいという気持ちです。

やったこと

環境構築

下記サイトを参考にしました。
VSCodeで始める競技プログラミング(環境構築編)

AtCoder Beginners Selection

下記記事を読みながら、問題を解きました。
qiita.com

AtCoder Beginner Contestの過去問を解く

いくつか過去問を解き、自分の現状を把握しました。


AtCoder Beginner Contest 104に参加

初コンテスト参加でしたが、AとBしか解けませんでした。参加者の反応を見るとC問題としては難しかったようですが、ABCの問題は基本的に全完できるようにするのが目標なので、良い教訓になりました。

AtCoder Beginner Contest 104 - AtCoder

今後

↑のツイートへの返信で、現状からステップアップするためには何か書籍で体系的に勉強した方が良さそうだとご教示いただきました。可能な限りコンテストには参加しつつ、書籍での勉強を進めていく予定です。

Jリーグの戦評、「1秒あまり」で自動作成 Jリーグ公式サイトからテキスト速報をスクレイピングして試合を要約する

はじめに

先日(2018年7月24日)公開された下記の記事を見て、自分でも似たものを作ってみたくなりました。

www.itmedia.co.jp

神戸新聞社が開発した「経過戦評ロボットくん」

www.kobe-np.co.jp

記事などから読み取れた情報を基に、こちらのプログラムのアルゴリズムを、以下に示します。

  1. 神戸新聞が運営する「兵庫の高校野球|神戸新聞NEXT」にて、リアルタイムに(手動で)一打席速報を掲載
  2. 試合終了を契機にプログラムが起動
  3. 一打席速報のデータを読み込む
  4. ひとつひとつの打席結果を分析し、事前に用意した「得点表」を基にそれぞれの得点シーンについて勝敗への影響度を算出
  5. 高い点数がついた得点シーンを複数組み合わせ、文章として違和感なくまとめる

事前に用意した「得点表」は、記者が執筆し神戸新聞に掲載されてきた試合の戦評を基に、機械学習で作成されたとのことです。

詳細は記事などに記載がなかったので私の推察になりますが、以下のような教師あり学習だと思います。

  1. 「一打席速報」の文章を単語単位に形態素解析して分かち書きし、Bag-of-words形式で特徴ベクトル Xにする
  2. それぞれの「一打席速報」の文章が「記者が執筆し神戸新聞に掲載されてきた試合の戦評」に採用されていれば目的変数 Yを1、されていなければ0にする
  3.  X Yのペアを訓練データとして、重回帰分析などで Y=1に寄与している Xの要素を特定する

若干想像しづらいと思うので、架空の具体例を用いて説明します。

とあるA高校とB高校の試合で得られた「一打席速報」
1回表 A高校 3番 XXX「ライトスタンドへのホームランが飛び出し、A高校が1点を先制」
3回表 A高校 9番 YYY 「二死ランナー無しで、サードへの内野フライ」
9回裏 B高校 4番 ZZZ 「レフトスタンドへの逆転サヨナラ満塁ホームラン」

A高校とB高校について、実際に掲載された戦評
1回表、A高校は3番XXXのライトスタンドへのソロホームランで先制。しかし9回裏にB高校は4番ZZZが起死回生の逆転サヨナラ満塁ホームランを放ち、4-1で勝利を収めた。

この場合、訓練データとなる X Yのペアは以下のようになります。ただし Xの数値はそれぞれ(ライト, スタンド,ホームラン, 先制, 二死, ランナー無し, サード, 内野フライ, レフト, 逆転, サヨナラ, 満塁)の登場回数です。この単語群は、一打席速報の文章から作成しています。

 X  Y
(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0) 1
(0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0) 0
(0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1) 1

この訓練データを基に何かしらのアルゴリズム教師あり学習させることで、どの単語が含まれている「一打席速報」が、実際に「記者が執筆し神戸新聞に掲載されてきた試合の戦評」に入っているかが分かります。ここから、どの「一打席速報」を基にして戦評を作成すれば良いかを類推できるという仕組みです。

(今回の例で言うと「スタンド」「ホームラン」などが含まれる「一打席速報」は、戦評に含まれるべきと判定されます)

Jリーグ版を作った

読み取った情報を基に、Jリーグ版を作成しました。

github.com

生成した戦評

先に結果をお見せすると、例えばとあるJリーグの試合(浦和レッズVS川崎フロンターレ, 2018年8月1日(水)19:03KO)について、次のような戦評を生成できました。

[2-0でホームの浦和が勝利。J1通算400勝を達成した]
浦和は前半7分、岩波からのロングボールに武藤が抜け出す。持ち上がった武藤は右サイドの敵陣深くからグラウンダーで折り返すと、興梠が右足でチョンソンリョンの頭上を越えるループシュートを放ち、ゴールに吸い込まれる。浦和は後半47分、李が自陣からドリブルで持ち上がる。ペナルティエリア左に進入すると、鈴木にタックルで倒されてPKを獲得。キッカーはファブリシオ。チョンソンリョンの逆を突き、ゴール左に沈め、リードを広げる。2-0でホームの浦和が勝利。J1通算400勝を達成した。

アルゴリズムの概要

アルゴリズムの概要を以下に示します。いろいろ検討したのですが、サッカーの場合は野球ほど1試合当たり点数が入らないので、「経過戦評ロボットくん」のように機械学習で事前に「得点表」を用意する必要がない気がしました。ガバガバですが、ゴールが入ったプレー全てを戦評に含めるルールベースの実装になっています。

  1. Jリーグ公式サイトからプレーのテキスト速報をスクレイピング
  2. ゴールが入ったプレーのテキスト速報を基に、文言を微調整して戦評を作成
テキスト速報のスクレイピング

以下のURLをプログラムにコマンドライン引数として渡すことで、サイトからテキスト速報をスクレイピングします。

www.jleague.jp

スクレイピングには、seleniumを利用しています。最初はbeautifulsoup4を使おうと思いましたが、上記のサイトはテキスト速報部分を別途動的に読み込む仕様になっていたのでブラウザ経由でhtmlを取得できるseleniumを採用しました。

ゴールが入ったプレーか否かの判定

ゴールが入ったプレーのテキスト速報には、下図のように「GOOOOOOOOOOAL!」という画像が挿入されていました。

f:id:upura:20180805153153p:plain

このことを活かし、画像タグが挿入されているか否かで判断をするロジックを組みました。

    # ゴール判定
    goal_list = driver.find_elements_by_class_name('spotRightTxt')    # クラス名'spotRightTxt'の全要素をリスト形式で取得
    goals = [1 if '<img class=' in goal.get_attribute("innerHTML") else 0 for goal in goal_list]    # 要素に画像タグがあれば1, なければ0のリスト内包表記
文言を微調整して戦評を作成

テキスト速報の末尾には句点がなかったり、PKの場合にはPKが発生した文章も戦評に含めたかったりと、今回の要件に沿うように戦評を作成しました。

おわりに

より高度な戦評を作成する場合、機械学習を用いた「得点表」の作成も選択肢に入ってくるかと思います。例えば分量の制限があり全得点については掲載できない場合や、重要な選手交代やファールについて取り上げる場合なども想定できます。こちらは訓練データを作成するのが大変だと思うので、もしやる気が出たらやってみようと思います。

【論文メモ】プライバシーを守るDeep Neural Network 暗号化したMNISTでも分類性能97%

2P-DNN : Privacy-Preserving Deep Neural Networks Based on Homomorphic Cryptosystem

概要

Microsoft Azure, Amazon AWSといったMachine Learning as a Service (MLaaS)の利便性が増しているが、ユーザのプライバシー保護の観点からMLaaSのサーバへのアップロードがはばかられる場合も多く、プライバシー保護のため暗号化されたデータを用いるとDNNモデルの性能を十分に発揮できない。

本研究では、"Paillier homomorphic cryptosystem"という暗号化の仕組みを用いた Privacy-Preserving Deep Neural Networks (2P-DNN) を提案。下図のように暗号化したMNISTデータセットでの分類性能が97%を示し、既存のプライバシー保護DNNよりも高い性能を発揮した。

https://user-images.githubusercontent.com/31459778/43379567-1c5b50fc-9408-11e8-8890-b88dbc46daa0.png

所感

MLaaSは大変便利だが、会社の制約として顧客情報を他社サーバにアップロードできないのが一般的な現状だと思う。その現状を鑑みて、暗号化したデータセットでも高い性能を発揮できる仕組みは需要が大きい気がする。

25歳になりました

本日、下記の記事でも書いた通り、25歳の誕生日を迎えました。なんかキリの良い数字な気がするので、直近1年の振り返りと今年の目標をつらつらと書きます。

upura.hatenablog.com

f:id:upura:20180725174942j:plain
↑お祝いで食べたケーキ(の代わり)

24歳の1年でやったこと

大学院を退学した

去年の9月末に大学院を辞めました。

upura.hatenablog.com

・社会人になった

10月1日付けで企業に正社員として入社しました。主にSQL, Python, Rを用いて、営業・マーケティング向けデータ分析を担当しています。概ね満足しています。

upura.hatenablog.com

・食生活管理と運動・筋トレを継続的に

相変わらずお酒は好きですが、アラサーが見えてきたので暴飲暴食を控える意識をしています。あと定期的なウォーキング・ジョギング・筋トレを継続しています。

・Kaggle・競技プログラミングを始めた

AIが相変わらず流行している今だからこそ、きちんと自分で手を動かす力を身に付けたいと思って始めました。ぼちぼちやっていきたいです。

今年の目標

1年後など想像しても仕方ないので、ざっくりとした目標だけ。25歳も精進していきます。

・勉強をする

心までおじさんにならないよう、常に学び続けたいです。

・アウトプットをする

twitterやブログ更新や対外発表など、定期的にしていきたいです。インプットとアウトプットのバランスの良さを大切にしたいです。

・自分を労る

いろいろ無理がきかなくなってくる歳だと思うので、精神的にも身体的にも健康的に生きていきたいです。