St_Hakky’s blog

Data Science / Human Resources / Web Applicationについて書きます

Rで協調フィルタリングをやってみた

こんにちは。

実際のレコメンドシステムでは、こういったパッケージを利用するのではなく、独自アルゴリズムなどを開発して自社パッケージとして持っておくのが普通かなぁと思う。

んだけど、PoC(Proof of Concept)の段階、つまりレコメンドを実業務に導入する前の手間では、パッケージを使うのが手っ取り早いなぁと思うので、今回はパッケージを使っていく。

○Rのパッケージ:recommenderlab

このパッケージでは、「レーティングの予測」と「Top-Nリストの生成」の2種類のレコメンデーション機能を持っている。独自アルゴリズムを実装する際のコストも比較的小さく済むなど、拡張性に優れている。これらに興味がある場合や、Top-Nリストの生成についての興味がある場合は、以下の論文が参考になる。

Hahsler, Michael. "recommenderlab: A Framework for Developing and Testing Recommendation Algorithms." Southern Methodist University (2011).

○recommenderlabのインストールとパッケージの読み込み

普通にCRANから落としてこれるので、以下のコマンドを叩けば基本的には大丈夫。

# パッケージのインストール
install.packages("recommenderlab")

# パッケージの読み込み
library(recommenderlab)

○データの取得

■recommenderlabに用意されているサンプルデータ

今回使用するデータは、recommenderlabの中にあるデータセットである。recommenderlabには3つのデータセットが用意されている。

[工事中]

■recommenderlabの各種便利メソッドを使うためのデータ整形法

[工事中]

■データのロードと概要把握

今回は、MovieLenseというデータを用いて解析を行う

# dataのload
data(MovieLense)

# dataの確認
# 出力 : 943 x 1664 rating matrix of class ‘realRatingMatrix’ with 99392 ratings.
print(MovieLense)

# データのクラスの確認
# 出力:"realRatingMatrix"
class(MovieLense)

# "realRatingMatrix"のままではデータの中身が見れない。
# R汎用データ型の「data.frame」に変換してから出力。
head(as(MovieLense, "data.frame"))

f:id:St_Hakky:20170211183616p:plain

→こんなデータセットになっている。どうやらuser, item, ratingのカラムがあって、それぞれ値があることがわかる。user 1は、Toy Storyが好きな様子笑

ちなみに、ユーザーがすべての映画に評価をつけていることはないので、評価がついていない映画に対するratingはどうなっているかというと、

# 今度は「matrix」に変換して出力
as(MovieLense, "matrix")[100:110, 1:4]

f:id:St_Hakky:20170211184208p:plain

と、こんな感じでNAが入っているんだなぁってことがわかる。結構スパースだなぁ。ってか当たり前か。

次はデータの全体像を俯瞰するために、可視化させてみる。今回のデータのクラスである"realRatingMatrix"には、image関数がデータの特徴を俯瞰するために用意されている。

image(MovieLense, main="Raw Ratings")

f:id:St_Hakky:20170211184537j:plain


ユーザーを軸に、ユーザーごとのrating件数とratingの平均の傾向を確認する。今回のデータのクラスの"realRatingMatrix"には、rowCounts関数とrowMeans関数が用意されているので、これを使う。

# userごとのrating件数について
summary(rowCounts(MovieLense))

f:id:St_Hakky:20170211185007p:plain

# histgramを書いてみる
hist(rowCounts(MovieLense))

f:id:St_Hakky:20170211185708j:plain

→まぁ当然っちゃ当然みたいな結果だけど、それにしても結構みんなratingつけてるな笑

# userごとのrating平均について
summanry(rowMeans(MovieLense))

f:id:St_Hakky:20170211185112p:plain

# 同じくhistgram
hist(rowMeans(MovieLense))

f:id:St_Hakky:20170211185830j:plain

→結構4近いところで集中はあれど、って感じですな。

これらの結果から、全体的な傾向としては同じでも、人によって幾ばくかのレーティングバイアスがあることがわかるので、正規化を行う。center法をここでは使う。

summary(rowMeans(normalize(MovieLense, method="center")))

f:id:St_Hakky:20170211190432p:plain

# histgramも書いてみる
hist(rowMeans(normalize(MovieLense, method="center")))

f:id:St_Hakky:20170211190526j:plain

ふむ。だいたい正規化できたかなーと。

○レコメンデーションアルゴリズムの適用

■"realRatingMatrix"で使えるアルゴリズム

ここで、今回のデータ型である"realRatingMatrix"では、どんなアルゴリズムが適用可能なのかをみる。そのためには以下のコマンドを叩けば良い。

recommenderRegistry$get_entries(dataType = "realRatingMatrix")

ぶっちゃけここに出てくる内容を眺めるのは割と苦痛なのと、分かりにくいので、以下の表が参考になるかと。

Algorithm 概要
RANDOM アイテムをランダムに選んで、レコメンド
POPULAR 購入ユーザー数に基づいて人気度が高いアイテムをユーザーにレコメンド
UBCF ユーザーベース協調フィルタリング
IBCF イテムベース協調フィルタリング
PCA 次元削減手法の代表例である、主成分分析
SVD Singular Value Decompositionという、次元削減手法の代表的なものの一つ
アルゴリズムの実行の流れ

アルゴリズムを実行するには、大きく分けて以下の二つの処理が必要。

1. レコメンダーの作成
→訓練データを使ってレコメンダーを作成する
2. レコメンデーションの予測
→ユーザーへのTop-Nリストを作成したり、ユーザーへの未レーティングアイテムのレーティングを予測したりする。

■UBCFを適用してみる

とりあえず、MovieLenseのユーザー900人のデータを使ってレコメンダーモデルを構築する。

# methodはUBCF。パラメータは調整できるが、ひとまずデフォルトで攻める。
r <- Recommender(MovieLense[1:900], method="UBCF")

構築したレコメンダーを使って、901番目〜910番目のユーザーのレーティングを予測してみる。

# 構築したレコメンダーを使って、901番目〜910番目のユーザーのレーティングを予測
p <- predict(r, MovieLense[901:910], type="ratings")

# 結果の出力
print(as(p, "matrix")[1:10, 1:5])

以下が結果の図(一部)。
f:id:St_Hakky:20170211194614p:plain

結果は一部だけ表示されているが、以下の元のデータの中身と結果を比較すると、レーティング済みの映画は予測対象外となっていて、レーティングされていないところは、予測値が入っている。

f:id:St_Hakky:20170211194959p:plain

(→これは元のデータ)

アルゴリズムの評価

レーティングの予測結果としては、Netflix Prizeにも使われていたRMSEが有名。なので、今回もこれを使う。

今回は、8割を学習に使い、2割を評価用に使う。

# splitを使って、評価データを作成
# train=0.8で、8割のデータを使うとしている
# given=15で、残り2割の評価データのうち、15件を予測用評価データとして使うことを示し、その他のデータは予測誤差計算用評価データとして利用することを示す。
e <- evaluationScheme(MovieLense, method="split", train=0.8, given=15)

今回は、UBCFとRANDOMを比較してみる。

# 学習
r.ubcf <- Recommender(getData(e, "train"),method="UBCF")
r.random <- Recommender(getData(e, "train"),method="RANDOM")

# 予測用評価データとレコメンダーを使って、レーティング予測を実施
p.ubcf <- predict(r.ubcf, getData(e, "known"), type="ratings")
p.random <- predict(r.random, getData(e, "known"), type="ratings")

# 予測誤差計算用評価データを使って、予測用評価データより計算された、それぞれの誤差を計算
e <- rbind(calcPredictionAccuracy(p.ubcf, getData(e, "unknown")), calcPredictionAccuracy(p.random, getData(e, "unknown")))

# 結果を出力
rownames(e) <- c("UBCF", "RONDOM")
print(e)

f:id:St_Hakky:20170211200930p:plain

とまぁこんな感じで、UBCFの方が小さくなっていることがわかる。

今日は以上!!!!