『集合知プログラミング』をR言語で書く

集合知プログラミング

集合知プログラミング

「2.6 del.icio.usのリンクを推薦するシステムを作る」は思い切って省略。
PythonでRあたりをよく読めばPython用のAPIを使って何とかなるかもしれないけど。

2.7.1 アイテム間の類似度のデータセットを作る

 この項では先に作ったtopMatches関数を使って作品ごとに類似性スコア高いほかの作品のリストを作っている。
 だけど、R言語では作品ごとに処理するよりも行列として処理したほうが簡単なので、topMatches関数は使わずに類似性スコアの行列を直接作ってみる。
 それと例によって類似性の指標をオプションで選べるようにしてある。

# 作品同士の類似性スコア行列を算出する関数。
# prefs      行が作品、列が評者のマトリクス
# person     対象者
# similarity 類似性の指標。既定値はユークリッド平方距離。
#            "euclidean"	ユークリッド平方距離
#            "euclidean.center"	評者ごとに中心化したユークリッド平方距離
#            "manhattan"	マンハッタン距離
#            "manhattan.center"	評者ごとに中心化したマンハッタン距離
#            "pearson"	ピアソンの積率相関係数
#            "spearman" 	スピアマンの順位相関係数
#            "kendall" 	ケンドールの順位相関係数
calculateSimilarItems <- function(prefs, similarity="euclidean"){
    switch(similarity,
        "euclidean" = sim.index <- as.matrix(1/(1+dist(prefs)^2)),
        "euclidean.center" = sim.index <- as.matrix(1/(1+dist(scale(prefs, scale=FALSE))^2)),
        "manhattan" = sim.index <- as.matrix(1/(1+dist(prefs, method="manhattan"))),
        "manhattan.center" = sim.index <- as.matrix(1/(1+dist(scale(prefs, scale=FALSE), method="manhattan"))),
        "pearson" = sim.index <- cor(t(prefs), use="pairwise.complete.obs"),
        "spearman" = sim.index <- cor(t(prefs), use="pairwise.complete.obs", method="spearman"),
        "kendall" = sim.index <- cor(t(prefs), use="pairwise.complete.obs", method="kendall")
        )
    sim.index[is.na(sim.index)] <- 0
    diag(sim.index) <- NA
    sim.index
    }

使い方。

(itemsim <- calculateSimilarItems(critics))
# 出力
#                    Lady.in.the.Water Snakes.on.a.Plane Just.My.Luck Superman.Returns You.Me.and.Dupree The.Night.Listener
# Lady.in.the.Water         0.00000000        0.16949153   0.10909091       0.06666667        0.27586207         0.22222222
# Snakes.on.a.Plane         0.16949153        0.00000000   0.06299213       0.16666667        0.04428044         0.16000000
# Just.My.Luck              0.10909091        0.06299213   0.00000000       0.03791469        0.11267606         0.09411765
# Superman.Returns          0.06666667        0.16666667   0.03791469       0.00000000        0.04606526         0.08921933
# You.Me.and.Dupree         0.27586207        0.04428044   0.11267606       0.04606526        0.00000000         0.11049724
# The.Night.Listener        0.22222222        0.16000000   0.09411765       0.08921933        0.11049724         0.00000000

う〜ん、p25に載っているサンプルコードの結果と合わないぞ。
どうもdist関数におけるNAの扱いが要因みたい。
RjpWikiの初級Q&A アーカイブより、NAを含む行列を関数distで計算
相関係数のほうは一致する。

逐語訳ではなくて意訳、超訳と宣言しましたので、ここはRの流儀のままで行きたいと思います。

2.7.2 推薦を行う

事前に用意した映画の類似度行列に基づいて、任意のユーザーに次に見るべき映画を推薦する。
ここで評価と類似度を乗算したものを類似度の合計で割るという計算をしているのだけど、これも納得がいかないので算術平均を使うことにする。
前述したように評者ごとの個人差を補正するなら相関係数を使うか、評者ごとに中心化した距離行列を使えばいいと思う。

# 任意のユーザーの評価と事前に用意した映画の類似度行列に基づいて、任意のユーザーに次に見るべき映画を推薦する関数。
# itemMatch  calculateSimilarItemsで作った映画の類似度行列
# user       任意のユーザーの評点ベクトル
getRecommendedItems <- function(itemMatch, user){
    x1 <- apply(as.matrix(itemMatch*user), 2, mean, na.rm=TRUE)
    x2 <- x1[is.na(user)]
    sort(x1[is.na(user)], decreasing=TRUE)
    }

使い方

# 先に類似度行列を用意しておく。
# 今回は評者ごとに中心化したユークリッド平方距離
itemsim <- calculateSimilarItems(critics, "euclidean.center")
getRecommendedItems(itemsim, Toby)
# 出力
#  Lady.in.the.Water The.Night.Listener       Just.My.Luck 
#          0.4350802          0.3957915          0.1825998 

2.8 MovieLensのデータセットを使う

MovieLens Data Sets | GroupLens Researchから「100,000 Data Set (.zip)」をダウンロード。
解凍したら、Rのワーキング・ディレクトリに「u.data」「u.item」を置く。
とりあえずファイル全体を読み込んでから必要な列だけを取り出すことにする。

u.item.list <- c("movie.id", "movie.title", "release.date",
                 "video.release.date", "IMDb.URL", "unknown", "Action",
                 "Adventure", "Animation", "Children's", "Comedy", "Crime",
                 "Documentary", "Drama", "Fantasy", "Film-Noir", "Horror",
                 "Musical", "Mystery", "Romance", "Sci-Fi", "Thriller", "War",
                 "Western")
u.item <- read.table("u.item2", sep="|", col.names=u.item.list)
# 出力
# 以下にエラー scan(file, what, nmax, sep, dec, quote, skip, nlines, na.strings,  : 
#   '15' 行目には,24 個の要素がありません 

む、エラーが出た。
どうも区切り記号をうまく読めていないみたい。
試行錯誤してみましたが、u.data は読み込めるものの u.item が読み込めない。
ひとまず、ここはスキップして3章へ進むことにします。

3.3 階層的クラスタリング

「3.3.2 フィード中の単語を数える」は省略。
tmパッケージあたりを使えば良さそうですが。
Rを使ったテキストマイニングについていえば、近々本も出版されるみたいです。

Rによるテキストマイニング入門

Rによるテキストマイニング入門

JIN'S PAGEの2008年3月以降をまとめた物でしょうか。

それと個人的には「階層*型*クラスタリング」の方がなじみのある訳語なので、ここではそう書きます。

まずはブログのデータを抽出したデータセットをダウンロードしてきます。
http://kiwitobes.com/clusters/blogdata.txt
このblogdata.txtをRのワーキングディレクトリに入れます。
このデータセットはタブ区切りなのでread.delim関数でそのまま読めます。

blogdata <- read.delim("blogdata.txt")
ncol(blogdata)
# [1] 707
nrow(blogdata)
# [1] 99

このデータセットのサイズは99行(ブログの数)×707列(単語の数)ということです。

続けて、ピアソンの積率相関係数に基づいてブログ間の距離(非類似度)行列を算出します。
1列目にはブログ名が入っているのでこれは除きます。

# データセットを転置する
blogdata2 <- (t(blogdata[,-1]))
colnames(blogdata2) <- blogdata[,1]
# 距離行列を算出する
blog.dist <- as.dist(1-cor(blogdata2))

そして、クラスタリングします。本文を読む限り、群平均法を採用しているようです。

blog.hc <- hclust(blog.dist, "average")

3.4 デンドログラムを描く

クラスタリングが終わっていれば、樹形図を描くのは至極簡単。

 plot(blog.hc, main="ブログのクラスタのデンドログラム", cex=0.75, hang=-1)

サンプルサイズが99もあるのでちょっと見にくいですね。
普通、階層型クラスタリングを使うのはサンプルサイズが50個ぐらいまでの場合です。

サンプルではjpegで樹形図を出力してますが、グラフですからPNGで出力した方がいいように思います。

# なんとなく壁紙サイズで出力
png("cluster.png", width=1280, height=1024)
plot(blog.hc, main="ブログのクラスタのデンドログラム", cex=0.75, hang=-1)
dev.off()

FotoLifeに壁紙サイズで置いてあります。

「3.5 列のクラスタリング」は省略。データセットを転置するだけなのと、サンプルサイズが大きくなり過ぎて階層型クラスタリングは適切じゃないから。