开篇
此前所总结学习的机器学习算法无不可以运用到各种数据中,在有些算法的实现中合适的数据真的不那么的好找,特别是对于原生的算法来说甚至是自己生成有噪音的数据。但作为已经出现几十年的Internet,人们利用它去交流去交易去购买去娱乐同样也太久,也太多。没错,这些就是天然的信息,也是天然的数据,大数据时代我们了无隐私可言,而技术门槛从这里开始。
传统的推荐系统
一般都是根据大量用户的活动所产生的大量信息,然后所产生的群体偏好再加以利用,比如某宝的商品推荐,热门视频,猜你喜欢,相亲匹配等等。如果想了解商品,影片,最没有技术含量的方法就是向我们的朋友询问,然后采纳他的建议。所以很自然能想到寻找相同品味的人,然后根据最相似的他人喜好给出推荐就可以了。这就是协同过滤(Collaborative Filtering,CF)的基本想法了:借鉴相关人群的观点来进行推荐。这不就是kNN?虽然大体一致,但实现下细微差异还是在这个领域叫做Top-N推荐。
在展开之前了解一下推荐系统所想要达到的目标,不妨换位思考作为一名顾客或者用户来说,我们最想要得到的效果是什么呢?首当其冲自然是满意度要高,所以在推荐的准确度一定要好,尽可能不要推荐不喜欢的,但是新颖性和惊喜度也要好,另外对于商家来说覆盖率(尽可能所有的商品都有被推荐出去),多样性也很重要。最后对于开发者时间,空间,任务还有如何实时等等等问题,所以如何在保证大多数用户推荐准确性的同时增加推荐的多样性个性,还要使特殊喜好的小群体用户得到相对完美的快速推荐呢?
基于内容:只基于用户喜欢的物品进行推荐,无需考虑用户之间的关系。具体实现往往直接计算关于item内容的相似度。
协同过滤:主要是分析用户在物品上的行为,即同一类用户/用一用户可能喜欢不同的商品。其实现包括在线的协同和离线的过滤两部分。
- 在线协同,就是通过在线数据找到用户可能喜欢的物品
- 离线过滤,则是过滤掉一些不值得推荐的数据,比如推荐值评分低的数据,或者虽然推荐值高但是用户已经购买的数据。
它的实现主要分为两个方面:
- 基于用户的实现和基于物品的实现。不过类似上看就是对一大堆用户或者物品进行搜索,然后用欧几里德,皮尔逊,jaccard系数,曼哈顿计算等等去计算相似度。再根据是具体的标量物体或者是应用在类似电影的评分这种连续性数据,看做是一个普通的分类或者回归问题。
- 另外还存在基于模型的协同过滤,比如它会提前提取出一个特征向量x,然后针对每个用户建模,即每个用户打的分值作为y值,利用这些已有的分值y和特征值x去训练回归模型(最常见的就是线性回归),这样就可以预测那些用户没有评分的分数。从另一个角度来看,也可以是先给定每个用户对某种物品的喜好程度(即权值),然后学出每种物品的特征,最后采用回归来预测那些没有被评分的物品。具体的推导如下的MF推导。
注:使用均值规范化可以缓解冷启动问题。(即对于新用户可以推荐热门的给TA)
比如类似的数据集Datas={‘PA’:{‘WA’:2 ,‘WB’:3,‘WC’:5},‘PB’:{‘WA’:4,‘WC’:2},‘PC’:{‘WB’:2,‘WC’:3}},P和W分别表示用户和物品的相关评判做一个小例子。
from math import sqrt
def sim_distance(prefs,person1,person2): #欧几里得计算用户相似度
wu={}
for item in prefs[person1]: #找出都有过操作的物品,key为物品编号,值为1
if item in prefs[person2]:
wu[item]=1
if len(wu)==0: #如果完全没有相同的
return 0
#计算所有差值的平方和
for item in prefs[person1]:
if item in prefs[person2]:
sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2)])
return 1/(1+sqrt(sum_of_squares))
'''#皮尔逊计算用户相似度
sum1=sum([prefs[p1][it] for it in wu]) #分别求和共同拥有物品的评分
sum2=sum([prefs[p2][it] for it in wu])
sum1Sq=sum([pow(prefs[p1][it],2)for it in wu])
sum2Sq=sum([pow(prefs[p2][it],2)for it in wu])
pSum=sum([prefs[p1][it]*prefs[p2][it] for it in wu])
num=pSum-(sum1*sum2/n) #计算皮尔逊相似度
den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))
if den == 0:
return 0
return(num/den)
‘’‘
def topN(prefs,person,n=5,similarity=sim_pearson): #找出最相似的N个,默认为5
scores=[(similarity(prefs,person,other),other)for other in prefs if other != person]
scores.sort()
scores.reverse()
return scores[0:n]
def getRecommendations(prefs,person,similarity=sim_pearson): #推荐
totals={}
simSums={}
for other in prefs:
if other==person:
continue
sim=similarity(prefs,person,other)
if sim<=0:
continue
for item in prefs[other]: #只对未操作的进行评价
if item not in prefs[person] or prefs[person][item]==0:
totals.setdefault(item,0)
totals[item]+=prefs[other][item]*sim #相似度*评价值
simSums.setdefault(item,0) #没有就新建
simSums[item]+=sim
rankings=[(total/simSums[item],item)for item,total in totals.items()] #建立列表
rankings.sort()
rankings.reverse()
return rankings #返回经过排序的列表
如何应对大量的用户与物品的关系?
首先在实际的计算中两者关系的存贮都是会用到矩阵的。
MF矩阵分解,MF其实就是基于模型的协同过滤,因为协同过滤的本质就是矩阵隐语义分解和矩阵填充 。但凡是学过线代的人应该提到矩阵分解就应该想到它–奇异值分解(SVD)。此时可以将这个用户物品对应的m×n矩阵M进行SVD分解,并通过选择部分较大的一些奇异值来同时进行降维(k),也就是说矩阵MM此时分解为:
M
m
×
n
=
U
m
×
k
Σ
k
×
k
V
k
×
n
T
M_{m \times n}=U_{m \times k}\Sigma_{k \times k}V_{k \times n}^T
Mm×n=Um×kΣk×kVk×nT所以如果我们要预测第i个用户对第j个物品的评分Mij,则只需要计算
u
T
i
Σ
v
j
u^TiΣvj
uTiΣvj即可。通过这种方法进行评分,然后找到最高的若干个评分对应的物品推荐给用户就行了。
虽然SVD简单粗暴,但是SVD分解要求矩阵是稠密的。但是对于推荐系统来说用户物品矩阵往往稀疏,如果要全部补全难度太大,耗时太长。于是FunkSVD算法出现了:
M
m
×
n
=
P
m
×
k
T
Q
k
×
n
M_{m \times n}=P_{m \times k}^TQ_{k \times n}
Mm×n=Pm×kTQk×n它只要求变成两个矩阵从而避开稀疏性问题,但是两个矩阵我们没有公式呀?没关系,机器学习世界换汤不换药!!所以尝试寻找最好的两个矩阵P,Q使最后的评分误差最小就行了,也就是
a
r
g
m
i
n
⏟
p
i
,
q
j
∑
i
,
j
(
m
i
j
−
q
j
T
p
i
)
2
+
λ
(
∣
∣
p
i
∣
∣
2
2
+
∣
∣
q
j
∣
∣
2
2
)
\underbrace{arg\;min}_{p_i,q_j}\;\sum\limits_{i,j}(m_{ij}-q_j^Tp_i)^2 + \lambda(||p_i||_2^2 + ||q_j||_2^2 )
pi,qj
argmini,j∑(mij−qjTpi)2+λ(∣∣pi∣∣22+∣∣qj∣∣22)没错,让我们求导,让我们梯度下降。
∂
J
∂
p
i
=
−
2
(
m
i
j
−
q
j
T
p
i
)
q
j
+
2
λ
p
i
\frac{\partial J}{\partial p_i} = -2(m_{ij}-q_j^Tp_i)q_j + 2\lambda p_i
∂pi∂J=−2(mij−qjTpi)qj+2λpi
∂
J
∂
q
j
=
−
2
(
m
i
j
−
q
j
T
p
i
)
p
i
+
2
λ
q
j
\frac{\partial J}{\partial q_j} = -2(m_{ij}-q_j^Tp_i)p_i + 2\lambda q_j
∂qj∂J=−2(mij−qjTpi)pi+2λqj
设置a步长不断迭代如
p
i
=
p
i
+
α
(
(
m
i
j
−
q
j
T
p
i
)
q
j
−
λ
p
i
)
p_i = p_i + \alpha((m_{ij}-q_j^Tp_i)q_j - \lambda p_i)
pi=pi+α((mij−qjTpi)qj−λpi)就可以求出P,Q了。
或者通过ALS交替最小二乘法,不断的交替固定q,p进行求解。一般来说SGD可以快速收敛但不适合海量数据,此时用ALS会更好。目前Spark Mllib中的协同过滤算法就是基于ALS的,其优点主要有1.可并行化处理更新q和p2.对于利用隐式行为后,用户行为矩阵不会那么稀疏,ALS更合适。
矩阵分解的优化
通过整合其他的信息可以提升预测准确度。
- FunkSVD的概率解释就是pmf(概率质量函数);
- 加上用户偏置就是biasSVD;特别是用户只对极少的物品进行评分。
- 加上隐式反馈信息就是SVD++;通过得到一个隐式的中间兴趣偏好。
- 加上时间动态变化就是timeSVD;使从静态可以根据用户兴趣的变化而变化。
优势:
最后整体来说协同过滤根据用户对物品或者信息的偏好,发现物品或者内容本身的相关性,或者是发现用户的相关性,不需要对物品或者用户进行严格的建模,而且不要求物品的描述是机器可理解的,所以这种方法也是领域无关的。
缺点:
在于它方法的核心是基于历史数据,所以对新物品和新用户都有“冷启动”的问题。推荐的效果依赖于用户历史偏好数据的多少和准确性。而且用户历史偏好是用稀疏矩阵进行存储的,而稀疏矩阵上的计算有些明显的问题,包括可能少部分人的错误偏好会对推荐的准确度有很大的影响等等。