1.思想简介:
协同过滤,从字面上理解,包括协同和过滤两个操作。所谓协同就是利用群体的行为来做决策(推荐)。对于推荐系统来说,通过用户的持续协同作用,最终给用户的推荐会越来越准。而过滤,就是从可行的决策(推荐)方案(标的物)中将用户喜欢的方案(标的物)找(过滤)出来。协同过滤分为基于用户的协同过滤和基于标的物(物品)的协同过滤两类算法。
基于协同过滤的两种推荐算法,核心思想是很朴素的”物以类聚、人以群分“的思想。所谓物以类聚,就是计算出每个标的物最相似的标的物列表,我们就可以为用户推荐用户喜欢的标的物相似的标的物,这就是基于物品(标的物)的协同过滤。所谓人以群分,就是我们可以将与该用户相似的用户喜欢过的标的物的标的物推荐给该用户(而该用户未曾操作过),这就是基于用户的协同过滤。
基于协同过滤的两种推荐算法,核心思想是很朴素的”物以类聚、人以群分“的思想。所谓物以类聚,就是计算出每个标的物最相似的标的物列表,我们就可以为用户推荐用户喜欢的标的物相似的标的物,这就是基于物品(标的物)的协同过滤。所谓人以群分,就是我们可以将与该用户相似的用户喜欢过的标的物的标的物推荐给该用户(而该用户未曾操作过),这就是基于用户的协同过滤。具体思想可以参考下面的图
2.算法原理
协同过滤的核心是怎么计算标的物之间的相似度以及用户之间的相似度。我们可以采用非常朴素的思想来计算相似度。我们将用户对标的物的评分(或者隐式反馈,如点击、收藏等)构建如下图为用户行为矩阵图:
矩阵的某个元素代表某个用户对某个标的物的评分,其中行向量代表某个用户对所有标的物的评分向量,列向量代表所有用户对某个标的物的评分向量。有了行向量和列向量,我们就可以计算用户与用户之间、标的物与标的物之间的相似度了。具体来说,行向量之间的相似度就是用户之间的相似度,列向量之间的相似度就是标的物之间的相似度。
相似度的计算可以采用cosine余弦相似度算法来计算两个向量(可以是上图中的行向量或者列向量)之间的相似度:
计算完了用户(行向量)或者标的物(列向量)之间的相似度,那么下面说说怎么为用户做个性化推荐。
2.1基于用户的协同过滤推荐:
用户u对标的物s的喜好度sim(u,s)可以采用如下公式计算,其中U是与该用户最相似的用户集合(我们可以基于用户相似度找到与某用户最相似的K个用户),score(ui,s)是用户ui对标的物的喜好度(对于隐式反馈为1,而对于非隐式反馈,该值为用户对标的物的评分),sim(u,ui)是用户ui 与用户u的相似度。
有了用户对每个标的物的评分,基于评分降序排列,就可以取topN推荐给用户了。
2.2基于标的物的协同过滤:
用户u对标的物s的喜好度sim(u,s)可以采用如下公式计算,其中S是所有用户操作过的标的物的列表,score(u,si) 是用户u对标的物si的喜好度,sim(si,s)是标的物 与s的相似度。
有了用户对每个标的物的评分,基于评分降序排列,就可以取topN推荐给用户了。
3.实践部分
3.1基于标的物的协同过滤python实践
(1)准备数据
数据格式: uid,itemid,score(ui矩阵)
(2)实现代码
1.归一UI矩阵,对item进行聚合
代码1_gen_ui_map.py:
#!/usr/local/bin/python
import sys
for line in sys.stdin:
ss=line.strip().split('\t')
#ss=line.strip().split(',')
if len(ss) != 3:
continue
u, i, s = ss
print "%s\t%s\t%s" % (i, u, s)
1_gen_ui_reduce.py:
#!/usr/local/bin/python
import sys
import math
cur_item = None
user_score_list = []
for line in sys.stdin:
item, user, score = line.strip().split("\t")
if not cur_item:
cur_item = item
if item != cur_item:
sum = 0.0
for tuple in user_score_list:
(u, s) = tuple
sum += pow(s, 2)
sum = math.sqrt(sum)
for tuple in user_score_list:
(u, s) = tuple
print "%s\t%s\t%s" % (u, cur_item, float(s / sum))
user_score_list = []
cur_item = item
user_score_list.append((user, float(score)))
sum = 0.0
for tuple in user_score_list:
(u, s) = tuple
sum += pow(s, 2)
sum = math.sqrt(sum)
for tuple in user_score_list:
(u, s) = tuple
print "%s\t%s\t%s" % (u, cur_item, float(s / sum))
2_gen_ii_pair_map.py:
#!/usr/local/bin/python
import sys
for line in sys.stdin:
u, i, s =line.strip().split('\t')
print "%s\t%s\t%s" % (u, i, s)
2_gen_ii_pair_reduce.py:
#!/usr/local/bin/python
import sys
cur_user = None
item_score_list = []
for line in sys.stdin:
user, item, score = line.strip().split("\t")
if not cur_user:
cur_user = user
if user != cur_user:
for i in range(0, len(item_score_list) - 1):
for j in range(i + 1, len(item_score_list)):
item_a, score_a = item_score_list[i]
item_b, score_b = item_score_list[j]
print "%s\t%s\t%s" % (item_a, item_b, score_a * score_b)
print "%s\t%s\t%s" % (item_b, item_a, score_a * score_b)
item_score_list = []
cur_user = user
item_score_list.append((item, float(score)))
for i in range(0, len(item_score_list) - 1):
for j in range(i + 1, len(item_score_list)):
item_a, score_a = item_score_list[i]
item_b, score_b = item_score_list[j]
print "%s\t%s\t%s" % (item_a, item_b, score_a * score_b)
print "%s\t%s\t%s" % (item_b, item_a, score_a * score_b)
3_sum_map.py:
#!/usr/local/bin/python
import sys
for line in sys.stdin:
i_a, i_b, s = line.strip().split('\t')
print "%s\t%s" % (i_a + "" + i_b, s)
3_sum_reduce.py:
#!/usr/local/bin/python
import sys
cur_ii_pair = None
score = 0.0
for line in sys.stdin:
ii_pair, s = line.strip().split("\t")
if not cur_ii_pair:
cur_ii_pair = ii_pair
if ii_pair != cur_ii_pair:
#item_a, item_b = cur_ii_pair.split('')
ss = cur_ii_pair.split('')
if len(ss) != 2:
continue
item_a, item_b = ss
print "%s\t%s\t%s" % (item_a, item_b, score)
cur_ii_pair = ii_pair
score = 0.0
score += float(s)
ss = cur_ii_pair.split('')
if len(ss) != 2:
sys.exit()
item_a, item_b = ss
print "%s\t%s\t%s" % (item_a, item_b, score)
运行脚本run.sh:
HADOOP_CMD="/usr/local/src/hadoop-2.6.5/bin/hadoop"
STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.5/share/hadoop/tools/lib/hadoop-streaming-2.6.5.jar"
INPUT_FILE_PATH_1="/music_uis.data"
#INPUT_FILE_PATH_1="/part-00151"
OUTPUT_PATH_1="/output1"
OUTPUT_PATH_2="/output2"
OUTPUT_PATH_3="/output3"
$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH_1
$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH_2
$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH_3
# Step 1.
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $INPUT_FILE_PATH_1 \
-output $OUTPUT_PATH_1 \
-mapper "python 1_gen_ui_map.py" \
-reducer "python 1_gen_ui_reduce.py" \
-file ./1_gen_ui_map.py \
-file ./1_gen_ui_reduce.py
# Step 2.
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $OUTPUT_PATH_1 \
-output $OUTPUT_PATH_2 \
-mapper "python 2_gen_ii_pair_map.py" \
-reducer "python 2_gen_ii_pair_reduce.py" \
-file ./2_gen_ii_pair_map.py \
-file ./2_gen_ii_pair_reduce.py
# Step 3.
$HADOOP_CMD jar $STREAM_JAR_PATH \
-input $OUTPUT_PATH_2 \
-output $OUTPUT_PATH_3 \
-mapper "python 3_sum_map.py" \
-reducer "python 3_sum_reduce.py" \
-file ./3_sum_map.py \
-file ./3_sum_reduce.py
运行结果如下图:
3.2基于用户的协同过滤实践
只需将以上代码的1_gen_ui_map.py文件稍作修改即可
1_gen_ui_map.py:
#!/usr/local/bin/python
import sys
for line in sys.stdin:
ss=line.strip().split('\t')
#ss=line.strip().split(',')
if len(ss) != 3:
continue
u, i, s = ss
print "%s\t%s\t%s" % (u, i, s)
3.3基于标的物的协同过滤Spark实践
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable.ArrayBuffer
import scala.math._
object cf {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setMaster("local")
conf.setAppName("CF")
val input_path = args(0).toString
val output_path = args(1).toString
val sc = new SparkContext(conf)
val lines = sc.textFile(input_path)
val max_prefs_per_user = 20
val topn = 5
// Step 1. normalization
val ui_rdd = lines.map { x =>
val fileds = x.split("\t")
(fileds(0).toString, (fileds(1).toString, fileds(2).toDouble))
}.groupByKey().flatMap { x =>
val user = x._1
val is_list = x._2
var is_arr = is_list.toArray
var is_list_len = is_arr.length
if (is_list_len > max_prefs_per_user) {
is_list_len = max_prefs_per_user
}
var i_us_arr = new ArrayBuffer[(String, (String, Double))]
for (i <- 0 until is_list_len) {
i_us_arr += ((is_arr(i)._1, (user, is_arr(i)._2)))
}
i_us_arr
}.groupByKey().flatMap { x =>
val item = x._1
val u_list = x._2
val us_arr = u_list.toArray
var sum:Double = 0.0
for (i <- 0 until us_arr.length) {
sum += pow(us_arr(i)._2, 2)
}
sum = sqrt(sum)
var u_is_arr = new ArrayBuffer[(String, (String, Double))]
for (i <- 0 until us_arr.length) {
u_is_arr += ((us_arr(i)._1, (item, us_arr(i)._2 / sum)))
}
u_is_arr
}.groupByKey()//.saveAsTextFile(output_path)
// Step 2. unpack:gen pairs
val unpack_rdd = ui_rdd.flatMap { x=>
val is_arr = x._2.toArray
var ii_s_arr = new ArrayBuffer[((String, String), Double)]()
for (i <- 0 until is_arr.length - 1) {
for (j <- i+1 until is_arr.length) {
ii_s_arr += (((is_arr(i)._1, is_arr(j)._1), is_arr(i)._2 * is_arr(j)._2))
ii_s_arr += (((is_arr(j)._1, is_arr(i)._1), is_arr(i)._2 * is_arr(j)._2))
}
}
ii_s_arr
}//.saveAsTextFile(output_path)
// Step 3. pack
unpack_rdd.groupByKey().map { x =>
val ii_pair = x._1
val s_list = x._2
val s_arr = s_list.toArray
var score:Double = 0.0
for (i <- 0 until s_arr.length) {
score += s_arr(i)
}
(ii_pair._1, (ii_pair._2, score))
}.groupByKey().map { x =>
val item_a = x._1
val item_list = x._2
val bs_arr = item_list.toArray.sortWith(_._2 > _._2)
var len = bs_arr.length
if (len > topn) {
len = topn
}
val s = new StringBuilder
for (i <- 0 until len) {
val item = bs_arr(i)._1
val score = "%1.4f" format bs_arr(i)._2
s.append(item + ":" + score)
s.append(",")
}
item_a + "\t" + s
}.saveAsTextFile(output_path)
}
}
来源:CSDN
作者:戮默。
链接:https://blog.csdn.net/qq_28286027/article/details/103937010