はじめに
MovieLensデータセット[1]は、ミネソタ大学のGroupLens Researchによって公開されている映画のレーティングのデータセットです。推薦システムに関する研究では定番のデータセットの一つで、私も推薦技術に関する検証を行う際には利用することが多いです。
よく利用される理由はいくつかあると思いますが、私の思いつく範囲では、以下のような特徴が理由として考えられます。
- 様々なデータサイズが提供されており、小規模なものであれば、手軽に試すことができる。
- データセットが比較的密であり、推薦モデルが機能しやすい。
- ユーザ、アイテムの属性データが存在しており、かつ品質も高い。
一点目に関しては、より大規模なデータセットからサンプリングするという手段も考えられますが、サンプリング方法に恣意性が出てしまうこともあるため、結果を比較しやすいという意味では、小規模なデータが公開されているという点はありがたいです。
二点目は、推薦のデータセットは非常にスパースなものが多いのですが、MovieLensは比較的密なため、モデルの学習がうまくいきやすいです。そのため、十分なデータがある際に手法がちゃんとワークするのかどうか、というモデルの妥当性を確認するためには有用です。一方で、実務のデータセットは、もっとスパースなものが多いので、利用したいシーンで効果を発揮するかどうかは、別途検証する必要があります。
三点目についてですが、推薦システムの場合、ユーザとアイテム双方の件数が非常に多いです。この際、アイテムに付随する属性情報は欠損があったり、アイテムの種類によって情報の密度が異なったりすることがあります。この点、MovieLensは比較的ユーザ、アイテムどちらをみても極端にデータが欠損していたりするケースは少ないため、内容ベースのアルゴリズムの検証には適しています。
このような特徴から、よく利用されるMovieLensデータセットですが、実際に色々な推薦技術の検証をしていると、想定外な結果が出てくることもあり、データセットを観察してみると、意外なデータの分布に気が付くこともあります。
今回は、私が実際にMovieLensの1Mを触っていて、個人的に印象に残ったデータの特徴を紹介したいと思います。
基礎情報
まずは基本的な情報について確認しておきます。データの件数は下記のとおりです。
項目 | 件数 |
---|---|
レーティング件数 | 1,000,209 |
ユーザ数 | 6,040 |
アイテム数 | 3,706 (3,883) |
大きな特徴としては、各ユーザについて、最低20件のレーティングが存在している点です。多くの推薦データセットでは、レーティング件数が1件のユーザが多く存在しているのですが、MovieLens 1Mデータセットは、一定の件数が保証されています。このため、モデルの学習が行いやすく、かつ評価の面でも良い精度が出やすいです。
その他のデータセットを用いる場合に、自分で件数でフィルタリングをすることはありえますが、自分で定めた適当な閾値でフィルタリングをして、性能を評価すると、恣意的な評価になってしまうため、あらかじめデータセット提供側でこのような処理をしてもらえているのはありがたいです。
一方で、映画側に着目すると、ムービーデータ側に記載されているもので、レーティングが一切ついていないものがあります。ムービーデータは全部で3,883件ですが、レーティング側に出現するものは3,706件になります。
次に各データの中身を見ていきます。
レーティングデータ
レーティングデータの1行はユーザID、ムービーID、レーティング、タイムスタンプの四つ組で構成されます。レーティングの値は1〜5です。また、タイムスタンプはエポック秒となっており、基準時からの経過時間が秒単位で記録されています。
ユーザデータ
各ユーザのデータは、ユーザID、性別、年齢、職業、ZIPコード(郵便番号)から構成されます。年齢と職業はカテゴリ情報となっており、事前に定められたいくつかの区分によって、定められます。
ZIPコードはあまり利用することはない印象ですが、約6,000人のユーザに対し、約3,400種のZIPコードが登録されていました。
ムービーデータ
各映画のデータは、ムービーID、タイトル、カテゴリから構成されます。
タイトルには、リリースされた年が含まれる形になっているため、利用しようと思えばリリース年の情報も利用可能です。
カテゴリは事前に定められた18のカテゴリに対し、マルチラベルとして付与されています。
データの観察
以上のデータについて、実際にいくつかの観点で観察を行ってみた上で、個人的に興味深かった結果を記載します。
アイテムの人気度順
推薦システムのベースラインとして、単純にアイテムを人気度順に並べたものを推薦する、といったルールベースの手法を置くことがあります。
MovieLens 1Mについて、評価値は一旦考えず、レビュー件数順にソートすると以下のような結果が得られます。
data["movieID"].value_counts().head(10)
movieID | 件数 |
---|---|
2858 | 3,428 |
260 | 2,991 |
1196 | 2,990 |
1210 | 2,883 |
480 | 2,672 |
2028 | 2,653 |
589 | 2,649 |
2571 | 2,590 |
1270 | 2,583 |
593 | 2,578 |
上位10件をみてみると、3000件近いレビューがあります。全体のユーザ数が6000人程度なので、半数近いユーザがこれらの映画をレビューしていることになります。この比率の高さは特徴的な点だと思います。
推薦モデルの評価タスクは様々ありますが、例えば、あるユーザが将来その映画を見るかどうか、を予測するとします。ここで、8割のユーザを学習用とし、残り2割のユーザを完全コールドスタートユーザとみなし、一切履歴がない状態で推薦を行うことを考えてみます。
すると、この人気度順のアルゴリズムはPrecision@10で簡単に0.46といった値が出てしまいます。
from sklearn.model_selection import train_test_split
# データをトレーニング用とテスト用に分割
train_users, test_users = train_test_split(data["userID"].unique(), test_size=0.2, random_state=0)
train_data = data[data["userID"].isin(train_users)]
test_data = data[data["userID"].isin(test_users)]
# トレーニングデータを用いて、アイテムの人気度順を算出
popular_items = train_data["movieID"].value_counts().head(10).index
# スコアを計算
def precision_top_10(user_id, item_list):
answer_data = test_data[test_data["userID"] == user_id]["movieID"].values
score = 0
for item in item_list:
if item in answer_data:
score += 1
return score / 10
scores = [precision_top_10(user_id, popular_items) for user_id in test_users]
print(np.mean(scores))
# 0.46614238410596026
分析結果からすれば当然ですし、ユーザの履歴の全アイテムを評価対象としていることもあり、やや高めの値が出やすい設定になっている部分はありますが、それを差し引いても完全コールドスタートを想定した実験としては、かなり高い値が出ているように感じます。
以前投稿した記事で対話型推薦アルゴリズムに関する検証を行ったのですが、ここでは協調フィルタリングの枠組みを利用しており、完全コールドスタートユーザに対して、既存ユーザの埋め込み表現の平均値を、新規ユーザの埋め込み表現とみなして、最初の推薦を行なっていました。
実は、この方法で同等の評価を行うと、Precision@10は、0.1程度しか出ません。ある程度人気順に近いような出力になるかなと想像していたのですが、この結果と比較してみると、全くそうではないことがわかり、個人的には意外な結果でした。
レビューのタイムスタンプ
レーティングデータには、タイムスタンプが付いており、そのレビューが行われた時刻がわかります。この情報を利用することで、各ユーザが最初にレビューをした時刻から、最後にレビューをした時刻までの期間を計算することができます。
users = data["userID"].unique()
time_data = list()
num_record_data = list()
for user_id in users:
user_data = data[data["userID"] == user_id]
last_time = user_data["timestamp"].max()
first_time = user_data["timestamp"].min()
num_days = (last_time - first_time) / (3600 * 24) # 日数に変換
time_data.append(num_days)
num_record_data.append(len(user_data))
まず、最初のレビューから最後のレビューまでの期間がどのぐらい空いているのかをみてみます。
print(min(time_data), max(time_data))
# 0.0012268518518518518 1032.9605555555556
最小で0.0012日(100秒程度)、最大で1000日程度となりました。ここまでは、特に違和感はないデータかなと思います。
次に分布をみてみます。
plt.hist(time_data)
これも予想通りですが、ほとんどのユーザが100日未満です。100日未満をさらに細かくみてみますが、こちらも同等の傾向です。
plt.hist(time_data, range=(0, 100))
最後まではやりませんが、1日以下のユーザがほとんどなります。
次に、レビュー投稿期間とレビュー件数の関係性をみてみます。
plt.scatter(num_record_data, time_data)
横軸がレビュー投稿数、縦軸が投稿期間になります。
この図をみてもらうとわかるのですが、期間としては非常に短いのに、数百件のレビューをしているユーザがたくさんいることがわかります。
投稿期間を1日以内に絞ってみても、以下のようなプロットになり、1000件を超えるユーザも複数見受けられます。
plt.scatter(num_record_data, time_data)
plt.ylim(0, 1)
もう少し詳しくみてみましょう。
df = pd.DataFrame({"userID":users, "duration":time_data, "n_records":num_record_data})
print(len(df[(df["n_records"] >= 500) & (df["duration"] < 0.1)]))
# 32
例えば、0.1日以内にレビューを500件記入し、それ以後一切レビューしていないようなユーザが32人いることになります。
ただ、このようなユーザを取り出して確認してみたところ、極端にレーティングが偏っていることもなく、1〜5まである程度存在しており、スパムとみなしていいのかもよくわかりません。
結果的にどう取り扱うべきなのかの結論は出せていないのですが、短期に過剰な量のレビューが集中しているユーザが意外と多くいるので、時系列的な分析をやる際には注意が必要です。
例えば、推薦システムにおいて、時系列的な興味の変化というのは一つのトピックとしてあげられますが、1日以内で興味が大きく変わることもないと思うので、適切な取り扱いが必要になります。
まとめ
本記事では、MovieLensデータセットを使って推薦モデルの検証を行う際に、個人的に印象的だったデータセットの特徴をまとめました。
MovieLensを用いて、推薦モデルを作ったことがある方の中には、元々ご存じの方も多いかとは思いますが、あまり意識せずに、とりあえずデータをモデルに入れて、結果を見ていた方もいらっしゃるのではないでしょうか。
検証したいタスク設定や使うモデルによっては、これらの特徴が評価に影響することもあります。推薦タスクに限った話ではないですが、データの観察はやはり重要であると再認識させられました。