阪神タイガース、今季初の八回から逆転勝利 昨日までの「0勝50敗」は他チームと比べ酷い数字か検証する
はじめに
本日朝、サンスポに次の記事が掲載されました。阪神タイガースは今季、「七回終了時にリードを許している試合で0勝50敗」という少し衝撃的なデータです。
この記事に刺激されたか否かは分かりませんが、阪神は本日の試合で八回表に一挙6得点。今季初の八回からの逆転勝利を収めました。
今回は「七回終了時にリードを許している試合で0勝50敗」という数字が他チームと比べてどれほど突出している数字なのか(はたまた特に驚くべき数字ではないのか)、他チームも含めたデータ分析を通じて検証してみたいと思います。
データの取得
どこかに良さげなデータベースがあるかと思い10分程度探しましたが見つからなかったので、Pythonを用いて自分で収集しました。
import pandas as pd from tqdm import tqdm result = [] match_id = [] for i in tqdm(range(330, 826)): for j in range(6): url = 'https://baseball.yahoo.co.jp/npb/game/20180' + str(i) + '0' + str(j+1)+ '/top' try: fetched_dataframes = pd.io.html.read_html(url) result.append(fetched_dataframes[0]) match_id.append('20180' + str(i)) except: pass
データの前処理
最初に、分析に必要な情報をデータから抽出します。今回は、resultというリストにpandas.DataFrame形式でスコアボードが格納されています。
result[0]
このデータから「七回終了時にリードを許している試合か否か」の情報を取り出します。具体的な処理については、雑なpandasの処理をしてしまったので割愛しますが、興味のある方はGitHubのipynbをご参照ください。
前処理を終えた後のDataFrameの最初の10行を、下記に示します。上から2行ずつ区切りの情報になっており、例えば最初の2行は「阪神ー巨人戦で、阪神も巨人も八回から逆転勝利しなかった(False)」という意味です。9, 10行目は「楽天ーロッテ戦で、楽天は八回から逆転勝利した(True)」ことを表しています。
データの分析
本題の分析に入る前に、きちんと全データが抽出できているかを確認しておきます。
データをチーム名でまとめ上げて(groupby)、合計を出力した結果を、実際の試合数を比較しました。オールスター戦2試合も含めて、昨日(2018年8月25日)までの全試合の情報がデータに含まれていると確認できました。
オリックス 114 ソフトバンク 108 ヤクルト 109 ロッテ 109 中日 115 全セ 2 全パ 2 巨人 117 広島 110 日本ハム 112 楽天 112 西武 111 阪神 106 DeNA 11
そしていよいよ、「七回終了時にリードを許していた試合」に関する情報を出してみます。ここではオールスター戦の情報は省きました。
冒頭のサンスポの記事通り、阪神は「0勝50敗」でした。
「割合」を見ると、巨人が「1勝48敗」でワースト2位。本日阪神が1勝目を挙げたので、本日時点では阪神と並んでワーストになりました。
個人的に、広島は終盤の逆転が多いイメージがありましたが、3試合のみで割合はワースト4位でした。しかし、そもそも「七回終了時にリードを許していた試合数」が38と、他チームと比べて圧倒的に少ないです。序盤から優位に試合を進めている様子がうかがえます。
DeNAは「七回終了時にリードを許していた試合数」が64で12チーム中最多。広島の約1.7倍もありました。
楽天は55試合中8試合で八回から逆転勝利に成功。順位ではパ・リーグ最下位に沈んでいますが、逆転した試合数・割合ともに12チーム中1位になりました。
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で公開しています。
システムの概要
構築したシステムの概要を下図に示します。
大まかな処理の流れは以下の通りです。
以下では、個々の処理について説明していきます。
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や下記の記事などをご確認ください。
今回はKernelの情報を取得したかったので、以下のコマンドを実行しました。ここでオプションとしてコンペティションの指定をしています。
kaggle kernels list --competition home-credit-default-risk --sort-by 'dateCreated'
前回実行時からKernelの情報に差分があるか確認
この処理以降、Pythonに委託することにしました。単純に情報を前回実行時と比較し、変更があるか否かを確認しました。
LINE APIでの通知
LINE APIの導入については、下記を参考にしました。
以下の関数を用いて、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)
おわりに
今回は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に解法を投稿していますが、ブログにも日本語で共有します。
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は下記の通りです。
もちろん、こちらは概要で、実際にはもっと色々なことを試しています。
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が〜」というオジサンではなく、きちんと自分の手を動かせる人間でありたいという気持ちです。
やったこと
AtCoder Beginner Contestの過去問を解く
いくつか過去問を解き、自分の現状を把握しました。
本日ABCの過去問をざっと解いた結果、現時点の実力として
— u++ (@upura0) July 28, 2018
A, B→解ける
C→頭が固いとミスる時あり
D→ほぼ無理
みたいな感じだ。Dを解けるようになるには、アルゴリズムやデータ構造を何か体系的に学ぶ必要があるっぽいな。
AtCoder Beginner Contest 104に参加
初コンテスト参加でしたが、AとBしか解けませんでした。参加者の反応を見るとC問題としては難しかったようですが、ABCの問題は基本的に全完できるようにするのが目標なので、良い教訓になりました。
今後
↑のツイートへの返信で、現状からステップアップするためには何か書籍で体系的に勉強した方が良さそうだとご教示いただきました。可能な限りコンテストには参加しつつ、書籍での勉強を進めていく予定です。
Jリーグの戦評、「1秒あまり」で自動作成 Jリーグ公式サイトからテキスト速報をスクレイピングして試合を要約する
神戸新聞社が開発した「経過戦評ロボットくん」
記事などから読み取れた情報を基に、こちらのプログラムのアルゴリズムを、以下に示します。
- 神戸新聞が運営する「兵庫の高校野球|神戸新聞NEXT」にて、リアルタイムに(手動で)一打席速報を掲載
- 試合終了を契機にプログラムが起動
- 一打席速報のデータを読み込む
- ひとつひとつの打席結果を分析し、事前に用意した「得点表」を基にそれぞれの得点シーンについて勝敗への影響度を算出
- 高い点数がついた得点シーンを複数組み合わせ、文章として違和感なくまとめる
事前に用意した「得点表」は、記者が執筆し神戸新聞に掲載されてきた試合の戦評を基に、機械学習で作成されたとのことです。
詳細は記事などに記載がなかったので私の推察になりますが、以下のような教師あり学習だと思います。
- 「一打席速報」の文章を単語単位に形態素解析して分かち書きし、Bag-of-words形式で特徴ベクトルにする
- それぞれの「一打席速報」の文章が「記者が執筆し神戸新聞に掲載されてきた試合の戦評」に採用されていれば目的変数を1、されていなければ0にする
- とのペアを訓練データとして、重回帰分析などでに寄与しているの要素を特定する
若干想像しづらいと思うので、架空の具体例を用いて説明します。
とある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で勝利を収めた。
この場合、訓練データとなるとのペアは以下のようになります。ただしの数値はそれぞれ(ライト, スタンド,ホームラン, 先制, 二死, ランナー無し, サード, 内野フライ, レフト, 逆転, サヨナラ, 満塁)の登場回数です。この単語群は、一打席速報の文章から作成しています。
(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リーグ版を作成しました。
生成した戦評
先に結果をお見せすると、例えばとあるJリーグの試合(浦和レッズVS川崎フロンターレ, 2018年8月1日(水)19:03KO)について、次のような戦評を生成できました。
[2-0でホームの浦和が勝利。J1通算400勝を達成した] 浦和は前半7分、岩波からのロングボールに武藤が抜け出す。持ち上がった武藤は右サイドの敵陣深くからグラウンダーで折り返すと、興梠が右足でチョンソンリョンの頭上を越えるループシュートを放ち、ゴールに吸い込まれる。浦和は後半47分、李が自陣からドリブルで持ち上がる。ペナルティエリア左に進入すると、鈴木にタックルで倒されてPKを獲得。キッカーはファブリシオ。チョンソンリョンの逆を突き、ゴール左に沈め、リードを広げる。2-0でホームの浦和が勝利。J1通算400勝を達成した。
アルゴリズムの概要
アルゴリズムの概要を以下に示します。いろいろ検討したのですが、サッカーの場合は野球ほど1試合当たり点数が入らないので、「経過戦評ロボットくん」のように機械学習で事前に「得点表」を用意する必要がない気がしました。ガバガバですが、ゴールが入ったプレー全てを戦評に含めるルールベースの実装になっています。
テキスト速報のスクレイピング
以下のURLをプログラムにコマンドライン引数として渡すことで、サイトからテキスト速報をスクレイピングします。
スクレイピングには、seleniumを利用しています。最初はbeautifulsoup4を使おうと思いましたが、上記のサイトはテキスト速報部分を別途動的に読み込む仕様になっていたのでブラウザ経由でhtmlを取得できるseleniumを採用しました。
ゴールが入ったプレーか否かの判定
ゴールが入ったプレーのテキスト速報には、下図のように「GOOOOOOOOOOAL!」という画像が挿入されていました。
このことを活かし、画像タグが挿入されているか否かで判断をするロジックを組みました。
# ゴール判定 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
- https://arxiv.org/abs/1807.08459
- Qiang Zhu, Xixiang Lv / School of Cyber Engineering, Xidian University, Xian 710071, China
概要
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よりも高い性能を発揮した。
所感
MLaaSは大変便利だが、会社の制約として顧客情報を他社サーバにアップロードできないのが一般的な現状だと思う。その現状を鑑みて、暗号化したデータセットでも高い性能を発揮できる仕組みは需要が大きい気がする。