u++の備忘録

非負値行列因子分解を用いたKaggleコンペ推薦

非負値行列因子分解を用いて、Kaggleコンペを推薦する仕組みを考えました。

f:id:upura:20200905115018p:plain

手法

いくらでも高度なやり方はあり得ますが、手っ取り早い方法として行列分解を試しました。行列分解は基本的な推薦手法の一つです*1。今回用いる手法を試している記事*2もネットに多く公開されています。

具体的には、次のような手続きを考えました。

  1. 過去のユーザのコンペ参加記録から「ユーザ×コンペ」の行列を作成(値は参加した場合にのみ1を与える)
  2. 行列を非負値行列因子分解して、2つの行列を得る
  3. 2つの行列を掛け合わせた結果を初期の行列と比較し、推薦結果を出力する

f:id:upura:20200905115628p:plain
図は Non-negative matrix factorization, Wikipedia*3 より引用。

初期の行列(上)と、2つの行列を掛け合わせた結果(下)を以下に示します。行列を一度分解した上で掛け合わせることで、元々は0だった箇所にも0より大きい値が入っていると分かります。今回はこの値を「参加確率」と見なすことで、推薦に利用するといった枠組みになります。

f:id:upura:20200905121723p:plain

実験

データセット

過去のユーザのコンペ参加記録は、Kaggle公式が公開している「Meta Kaggle」*4から作成しました。いくつかのテーブルを結合・加工し、「ユーザ×コンペ」の行列を作成します。

実装

実装のコードは、次のNotebookで確認できます。非負値行列因子分解はsklearnの実装*5を利用しました。

www.kaggle.com

非負値行列因子分解の該当部分は下記で、とても簡単な記述で済んでいます。

model = NMF(n_components=2, init='random', random_state=0)
W = model.fit_transform(X)
H = model.components_
R = np.dot(W, H)

デモアプリ

実装内容のデモアプリは、pythonスクリプトから手軽にWebアプリを実装できる「Streamlit」*6を用いました。ソースコードGitHubで公開済*7です。

今後の展望

コールドスタート

当然のことながら、行列分解の手法では(本来最も推薦を欲しているはずの)コンペ参加が少ない人への推薦が難しくなります。このコールドスタート問題に如何に対処するかは重要な課題です。

デプロイ

本システムをデプロイするに当たって、「ユーザ×コンペ」の行列が非常に大きいことが問題になりました。joblibで圧縮したファイルでも数GBの大きさになります。単純に大きいインスタンスを利用するという手もありますが、実運用に当たっては様々に考慮すべき要素が出そうです。(データベースをどこで持つか、更新をどうするか、どこまでをサービス側で持つか、などなど)

おわりに

本記事では、非負値行列因子分解を用いたKaggleコンペ推薦の方法について述べました。自分の手を動かして推薦システム構築に取り組むと、デプロイも含めて様々な課題に直面して学びが深いと感じています。Kaggleコンペ推薦はドメイン知識もあって取っつきやすいタスクなので、今後もゆるりと取り組んでいきたいところです。

「Basketball Behavior Challenge BBC2020」で4チーム中1位に

9月1日まで開催されていた「Basketball Behavior Challenge BBC2020」*1というコンペで1位になりました*2。選手とボールの座標推移からスクリーンプレイの有無を判定するタスクで、分析していて楽しいコンペでした。

f:id:upura:20200903155012p:plain

解法の概要とスコアの推移は下図の通りです。詳細は、コンペ中に利用していたGitHubリポジトリ*3をご覧ください。

f:id:upura:20200902032536p:plain f:id:upura:20200902032539p:plain

本コンペの存在は、運営に関わっている「Sports Analyst Meetup #7」*4にて、LT発表してくださった本コンペのデータ提供者である藤井先生に教えていただきました。優勝者は名古屋大学にてシンポジウム発表できるはずで、衣錦還郷を狙いましたがCOVID-19で中止になり残念です。とはいえ、スポーツのデータを触ることができ、楽しいコンペでした。

自分のTwitter投稿内のURLを分析してみた

これは何?

次の記事に着想を得て、自分のデータでやってみました。具体的には、2015年6月から現在までの自分のTwitter投稿から、全部のURLを抽出し、ドメイン単位で集計しました。

www.buzzfeed.com

データの取得

twilog*1に自分の投稿データが保存されていたので、ダウンロードしました。なおTwitterでは自分の投稿をダウンロードする機能が公式に提供*2されていますが、今年7月のインシデントの影響で現在は使えない状況のようです*3

分析結果

全投稿数は40903でした。Excelで処理するには少し大きいので、手慣れているPythonで分析しました。最終的には、頻度の高い上位40サイトを棒グラフで表示しました。

import collections
import itertools
import re

import pandas as pd


df = pd.read_csv(
    "drive/My Drive/twitter/upura0200827.csv",
    header=None,
    names=["id", "timestamp", "text"]
)

print(df.loc[0, "text"])
"""
初回の題材はVRらしい。シーズン18の最終回はディープフェイクだったし、技術的な話題を盛り込んでるな。

『相棒シーズン19』10月スタート!水谷豊、21年目の“一心同体” 反町隆史も続投(サンケイスポーツ)
#Yahooニュース
https://news.yahoo.co.jp/articles/dfffd7fab8172ba9d8997f54ab2314259ce7c811
"""

pattern = "https?://[\w/:%#\$&\?\(\)~\.=\+\-]+"
urls = re.findall(pattern, ' '.join(list(itertools.chain(df["text"]))))
print(len(urls))    # 12314
domains = [url.split('/')[2] for url in urls]

c = collections.Counter(domains)
res = pd.DataFrame(c.most_common()[:40])
res.columns = ["Domain", "Frequency"]
res.plot.bar(x="Domain", y="Frequency", figsize=(13, 5))

f:id:upura:20200827031251p:plain

考察

上位10件を以下に示します。最も多い pic.twitter.com と 3番目の twitter.com はそれぞれ、画像と引用リツイートだと考えられます。

f:id:upura:20200827031504p:plain

2、4、6番目はニュースサイトでした。headlines.yahoo.co.jpnews.yahoo.co.jp を足すと 942 + 437 = 1379個 で www.nikkei.com の1205個を上回ります。ニュースの情報源として、主に2種類のサイトを見ていることが読み取れます。8番目にも www.asahi.com が来ています。残りは技術的なサイトで、自身の技術ブログ upura.hatenablog.com や Qiita qiita.comarXiv arxiv.orgGitHub github.com が続きました。

おわりに

本記事では、BuzzFeedの記事に倣って自分のTwitter投稿内のURLを分析しました。個人の興味が色濃く出た結果のように感じます。2017年にソフトウェアエンジニアとして就職した後は少し技術的な投稿が比重が増えている認識なので、時系列で分割した分析も面白そうだなと思っています。

Kaggleの「おすすめコンペは何?」への答えを考えた

「Rist主催 Kaggle Workshop #1」にてLT発表しました。テーマは「おすすめコンペは何?」という質問への答え方です。発表資料やプレゼンテーション動画を掲載したので、ご興味あらばご覧いただければと思います。

最近Kaggle系のイベントに飢えていたので、このようなイベントに参加できて楽しかったです。

イベント概要

rist.connpass.com

発表資料

動画

youtu.be

Pandasのパイプラインを作る「pdpipe」を使ってみた

Pandasのパイプラインを作る「pdpipe」というライブラリを知ったので、少し触ってみました。本記事では、簡単な使い方および良かった点・悪かった点をまとめます。

使い方

KaggleのTitanicデータセットで検証しました。一連の処理はNotebookを公開しています。

import pandas as pd


train = pd.read_csv('../input/titanic/train.csv')
test = pd.read_csv('../input/titanic/test.csv')

www.kaggle.com

インストール

公式ドキュメントに記載の通り、pipでインストール可能です。

pdpipe.github.io

pip install pdpipe

パイプラインの構築

パイプラインの記述する方法は、いくつか存在します。ここではリストで列挙する方法を採用しましたが、例えば+演算子で羅列することも可能です。

pdpipe.github.io

import pdpipe as pdp


CATEGORICAL_COL = ['Sex', 'SibSp', 'Embarked']
DROP_COL = ['PassengerId', 'Name', 'Ticket', 'Parch', 'Cabin']

pipeline = pdp.PdPipeline([
    pdp.Encode(CATEGORICAL_COL),
    pdp.ColDrop(DROP_COL)
])

今回は簡単な処理として、カテゴリ変数 CATEGORICAL_COL のラベルエンコーディングと、一部のカラム DROP_COL の削除を実行します。

前処理

「悪かった点」で後述しますが、ラベルエンコーディングの対象となるカラムに欠損値が含まれる場合に、次のエラーが発生します。

TypeError: Encoders require their input to be uniformly strings or numbers. Got ['float', 'str']

この問題に対処すべく、事前にカテゴリ変数の欠損値処理が必要です。「パイプラインで事前に定義しよう・・・」と考えたのですが、現状は欠損値を埋める処理が用意されておらず、独自に対応する必要がありました。

github.com

for c in CATEGORICAL_COL:
    train[c].fillna('<missing>', inplace=True)
    test[c].fillna('<missing>', inplace=True)

パイプラインの実行

構築したパイプラインは、sklearnの要領でtrain, testに適用できます。

train = pipeline.fit_transform(train, verbose=True)
test = pipeline.transform(test)
- Encode Sex, SibSp, Embarked..
100%|██████████| 3/3 [00:00<00:00, 132.06it/s]
- Drop columns PassengerId, Name, Ticket, Parch, Cabin..

before

f:id:upura:20200728042242p:plain

after

f:id:upura:20200728042248p:plain

良かった点

事前にパイプラインを定義することで、処理の見通しは非常に良くなると感じました。trainとtestに同一のパイプラインを適用する形式のため、処理の整合性が取れる利点もあると思います。

悪かった点

まずはカテゴリ変数に欠損値が含まれる場合のラベルエンコーディングでエラーが発生する点および、その回避に別の処理が必要な点が挙げられます。せめてパイプライン内に欠損値処理を盛り込めればなと感じました。issueコメントを見るに、contributionのチャンスかもしれません。

同じくカテゴリ変数のラベルエンコーディングの処理で、testで新たに出現する値に対してエラーが発生するのも辛いところでした。パイプライン内で sklearn.preprocessing.LabelEncoder() をラッピングしている都合上、このエラーを回避する処理を外部から付け加えるのは難しいです。許される場合は、次のようにtrainとtestを結合してから処理するのが現実的な解決策かもしれません。

data = pd.concat([train, test], sort=False)

おわりに

本記事では、Pandasのパイプラインを作るライブラリ「pdpipe」の簡単な使い方および良かった点・悪かった点をまとめました。手軽に利用でき、パイプラインを構築する上での利点は感じられた一方で、現時点の機能では若干の使いづらさを感じたというのが率直な印象です。余裕があれば、PRを出してみようかなと思いました。

streamlitでwebアプリ作成

少し前に話題になっていた「streamlit」を用いて、簡単なwebアプリを作ってみました。デザイン部分をほとんど意識せず、お手軽にwebアプリを作成できます。公式ドキュメントも充実しており、分かりやすかったです。細かいデザインに手を入れる必要がない場合など、サクッとデモを作りたい場合には重宝する仕様になっているなと感じました。

www.streamlit.io

デモ

ソースコード

github.com