推荐系统(三):基于矩阵分解的推荐算法

一、矩阵分解原理

1.1、奇异值分解

奇异值分解(Singular Value Decomposition,SVD)是一种常见的矩阵分解方式,对于一个 的矩阵,可定义其SVD为:

其中为矩阵,是矩阵,为矩阵,其除了对角线元素外全为0,即
\boldsymbol{\Sigma}=\left(\begin{array}{cccccc}{\lambda_{1}} & {0} & {0} & {\cdots} & {\cdots} & {0}\\ {0} & {\lambda_{2}} & {0} & {\cdots} & {\cdots} & {0}\\ {0} & {0} & {\cdots} & {\cdots} & {\cdots} & {0}\\ {\cdots} & {\cdots} & {\cdots} & {\lambda_{n-1}} & {\cdots}& {0} \\ {\cdots} & {\cdots} & {\cdots} & {\cdots} & {\lambda_{n}} & {0} \\ {0}& {0}& {0}& {0}& {0}& {\mathbf{0}}\end{array}\right)
由于奇异值矩阵包含了原矩阵的信息,其其中主要信息由较大的几个奇异值所表征,因此奇异值分解可用来作为矩阵降维,即:

在推荐系统中,代表样本的用户数,维度通常会很高,当时会大大减轻线上存储和计算的压力。基于矩阵分解的推荐算法的步骤可以分为:
(1)加载用户对物品的评分矩阵;
(2)矩阵分解,求奇异值,根据奇异值的能量占比确定降维至的数值;
(3)使用矩阵分解对物品评分矩阵进行降维;
(4)使用降维后的物品评分矩阵计算物品相似度,对用户未评分过的物品进行预测;
(5)产生前个评分值高的物品,返回编号并预测评分值。

1.2、PQ分解

SVD计算之前需要先把评分矩阵补全,将稀疏矩阵变为稠密矩阵,这样就会带来一些问题:1. 稠密矩阵需要耗费巨大的存储空间;2. SVD计算复杂度很高,在大规模稠密矩阵上计算不现实。隐语义模型也是基于矩阵分解的,但是是将原始矩阵分解成两个矩阵相乘的形式:

其中为用户因子矩阵,为物品因子矩阵。
通常上式不会精确相等,需要最小化二者之间的差异,可通过如下损失函数来表达:

其中表示用户对物品的评分交互,1表示有交互,0表示无交互,表示用户偏爱某个商品的置信参数,交互次数多权重就会增加,如果用表示交互次数的话,则可表示为。
通过上述方法将协同过滤问题转化为了一个优化问题,求解上述损失函数通常采用交替最小二乘法(alternating least squares) ,计算过程如下:
(1)随机初始化,对上式中求偏导,令导数为0,得到当前最优解

(2)固定,对上式求偏导,令导数为0,得到当前最优解

(3)固定,对上式中求偏导,令导数为0,得到当前最优解
(4)重复以上(2)到(3),直至达到迭代次数或收敛

二、算法实现

1、SVD的实现与说明

为简明说明SVD的作用过程,采用维的用户评分矩阵,行代表用户,列代表物品,数值代表评分,未评分值为0。

import numpy as np
def load_data():
    return [[0,0,1,0,0,2,0,0,0,0,5],
            [0,0,0,5,0,3,0,0,0,0,3],
            [0,0,0,0,4,1,0,1,0,4,0],
            [3,3,4,0,0,0,0,2,2,0,0],
            [5,4,2,0,0,0,0,5,5,0,0],
            [0,0,0,0,5,0,1,0,0,0,0],
            [4,1,4,0,0,0,0,4,5,0,1],
            [0,0,0,4,0,4,0,0,0,0,4],
            [0,0,0,2,0,2,5,0,0,1,2],
            [1,0,0,4,0,0,0,1,2,0,0]
            ]
data = load_data()
mat = np.mat(data)
原始数据
U,Sigma,VT = np.linalg.svd(mat)

rate = sum(Sigma[0:4]**2)/sum(Sigma**2)
rawdata = U[:,:10]*np.mat(np.diag(Sigma[:10]))*VT[:10,:]
recondata = U[:,:4]*np.mat(np.diag(Sigma[:4]))*VT[:4,:]

进行SVD分解,可以得到三个矩阵,值如下:

Sigma

可见前4个值能量占比为 ,说明了SVD的降维作用,下表分别为全10个奇异值的重构表和前4个奇异值的重构表,可以看出后者能够较好地代表前者。
全10个奇异值的重构表

前4个奇异值的重构表

即将物品的评分矩阵映射到低维空间,亦即矩阵的转置,如下:
降维后的物品评分矩阵

2、SVD推荐算法的实现

数据加载与相似度计算函数
定义几种相似度计算方法,包括Cosine相似度、欧几里得距离、皮尔逊相关系数,本文采用Cosine相似度。

import data
import numpy as np

def cos_sim(X,Y):
    num = float(X.T*Y)
    denum = np.linalg.norm(X)*np.linalg.norm(Y)
    return 0.5+0.5*(num/denum)
def eclud_sim(X,Y):
    return 1.0/(1.0+np.linalg.norm(X-Y))
def pears_sim(X,Y):
    if len(X)<3:
        return 1.0
    return 0.5+0.5*np.corrcoef(X,Y,rowvar=0)[0][1]

SVD评分估计
基于矩阵奇异值分解转换的商品评价估计,实现过程的详述见注释

def svd_estimate(data,user,sim_measure,item):
    n = data.shape[1]   # 列维度,物品数量
    sim_total,rate_sim_total = 0.0,0.0  # 相似度初始化
    
    U,Sigma,VT = np.linalg.svd(data)   #SVD分解
    low_dim = data.T * U[:,:4] * np.mat(np.diag(Sigma[:4])).I  # 将数据降到低维空间
    for j in range(n):  #对于给定用户,循环所有物品,计算与item的相似度
        user_rating = data[user,j]  #用户user对物品j的评分
        if user_rating == 0 or j == item:  # 未评价 或 item本身
            continue
        similarity = sim_measure(low_dim[item,:].T,low_dim[j,:].T)  #相似度计算
        print ('%d and %d similarity is:%f'%(item,j,similarity))
        
        sim_total += similarity  #相似度求和
        rate_sim_total += similarity*user_rating  #对相似度及评分值的乘积求和
    if sim_total == 0:
        return 0 
    else:
        return rate_sim_total/sim_total

基于SVD进行推荐
寻找未评级的物品,对给定用户建立一个未评分物品列表,并计算评价值,进而推荐

def recommend(data,user,N=3,sim_measure=cos_sim,est_method=svd_estimate):
    unrated_item = np.nonzero(data[user,:].A == 0)[1]  #未评价的物品
    if len(unrated_item) == 0:
        return ('you rated everything')
    print(unrated_item)
    item_score = []    
    for item in unrated_item:  #在未评价的物品中
        estimate_score = est_method(data,user,sim_measure,item)  #计算评价值
        item_score.append((item,estimate_score))  #记录商品及对应评价值
    return sorted(item_score,key=lambda x: x[1],reverse=True)[:N]  #推荐前N个未评价物品

结果分析

data = np.mat(data.load_data())
result = recommend(data,2,N=3,sim_measure=cos_sim,est_method=svd_estimate)
print(result)

对于第2号用户,,其未评价的列表为,则可计算得到未评价物品与所有已评价物品的相似度为:

0 and 4 similarity is:0.481378
0 and 5 similarity is:0.457935
0 and 7 similarity is:0.986661
0 and 9 similarity is:0.481274
1 and 4 similarity is:0.488477
1 and 5 similarity is:0.453733
1 and 7 similarity is:0.973832
1 and 9 similarity is:0.489637
2 and 4 similarity is:0.461096
2 and 5 similarity is:0.587373
2 and 7 similarity is:0.825805
2 and 9 similarity is:0.479893
3 and 4 similarity is:0.476120
3 and 5 similarity is:0.693686
3 and 7 similarity is:0.545381
3 and 9 similarity is:0.487154
6 and 4 similarity is:0.874726
6 and 5 similarity is:0.869111
6 and 7 similarity is:0.526060
6 and 9 similarity is:0.906349
8 and 4 similarity is:0.481115
8 and 5 similarity is:0.445549
8 and 7 similarity is:0.980209
8 and 9 similarity is:0.477279
10 and 4 similarity is:0.447311
10 and 5 similarity is:0.933246
10 and 7 similarity is:0.453173
10 and 9 similarity is:0.496646

对所有未评分物品的评分为:

[(0, 2.1996914641365213), (1, 2.219755646468587), (2, 2.1991365553488067), (3, 2.3121588279962424), (6, 2.6822454316204456), (8, 2.205955763398433), (10, 2.2151987747133908)]

推荐其中的前三个:

[(6, 2.6822454316204456), (3, 2.3121588279962424), (1, 2.219755646468587)]

3、PQ分解推荐算法的实现

按照上述算法实现PQ分解的过程如下:

def matrix_factorization(matrix,k,lr):
    matrix = np.mat(matrix)
    m,n = matrix.shape
    P = np.mat(np.random.random((m,k)))
    Q = np.mat(np.random.random((k,n)))
    loss = 1.0
    epoch = 0
    while loss>=0.0005 and epoch<=epochs:
        loss = 0.0
        for i in range(m):
            for j in range(n):
                r = matrix[i,j]
                r_ = 0
                l2_p,l2_q = 0,0
                for t in range(k):
                    r_ += P[i,t]*Q[t,j]
                    l2_p += P[i,t]**2
                    l2_q += Q[t,j]**2
                e = r-r_
                loss += e**2+beta*l2_p+gamma*l2_q
                for t in range(k):
                    P[i,t] += lr*(2*e*Q[t,j]-2*beta*P[i,t])
                    Q[t,j] += lr*(2*e*P[i,t]-2*gamma*Q[t,j])
        epoch += 1
    return P,Q

设置参数并对比分解后复原的结果原数据的差异

beta = 0.001
gamma = 0.001
epochs = 20000
data = np.mat(data.load_data())
P,Q = matrix_factorization(data,10,0.002)
result = P*Q
print(P*Q)

可见当k=10时,与原数据已十分接近,k=4时也比较接近,说明了矩阵分解能够表征原数据。


k=10时的结果

k=4时的结果

另外可以看出P,Q矩阵分别从横轴和纵轴提取了原矩阵的信息,进而可利用Q矩阵替代上文的V矩阵的作用而进行推荐,在此不予赘述。


P值

Q值

参考资料

[1]. https://blog.csdn.net/xiaoxiaowenqiang/article/details/78076984
[2]. 推荐系统与深度学习. 黄昕等. 清华大学出版社. 2019.
[3]. 推荐系统算法实践. 黄美灵. 电子工业出版社. 2019.
[4]. 推荐系统算法. 项亮. 人民邮电出版社. 2012.
[5]. 美团机器学习实践. 美团算法团队. 人民邮电出版社. 2018.
[6]. https://blog.csdn.net/recall_tomorrow/article/details/80218051

花萼楼前雨露新,长安城里太平人。—— 张说《十五日夜御前口号踏歌词二首》

你可能感兴趣的:(推荐系统(三):基于矩阵分解的推荐算法)