基于SVD矩阵分解的用户商品推荐(python实现)

加粗样式## SVD矩阵分解
SVD奇异值分解

优点:简化数据,去除噪声,提高算法的结果
缺点:数据的转换可能难以理解
适用范围:数值性数据

原始数据data,我们把它分解成3个矩阵。

其中

只有对角元素,是奇异值。奇异值中数值是按从大到小排列的,r个之后数值为0.。 这点高等数学里有。
如果我们把第二个奇异值矩阵中只保留一些值,把其他值全设为0.。从右到左重构数据即得到简化后的数据。
如奇异值数据中只保留最大2个奇异值,再重构数据。
可以认为原始数据去除了噪声。

python实现svd

import numpy as np
U,sigma,VT=np.linalg.svd([[1,1],[7,7]])
print('U:',U)
print('sigma:',sigma)
print('VT:',VT)

其中sigma以对角矩阵出现,我们需要记住它其实是个矩阵。

svd重构

import numpy as np
def loadExData():
    return [[1,1,1,0,0],[2,2,2,0,0],[1,1,1,0,0],[5,5,5,0,0],[1,1,0,2,2],[0,0,0,3,3],[0,0,0,1,1]]
data=loadExData()
U,sigma,VT=np.linalg.svd(data)
print('U:',U)
print('sigma:',sigma)
print('VT:',VT)

可以发现sigma从大到小排列,如果我们只取前面3个奇异值。
则矩阵分解变成

import numpy as np
def loadExData():
    return [[1,1,1,0,0],[2,2,2,0,0],[1,1,1,0,0],[5,5,5,0,0],[1,1,0,2,2],[0,0,0,3,3],[0,0,0,1,1]]
data=loadExData()
U,sigma,VT=np.linalg.svd(data)

#重构实现
sig3=np.mat([[sigma[0],0,0],[0,sigma[1],0],[0,0,sigma[2]]])
#sig3=np.mat(np.eye(3)*sigma[:3])
print('sig3:',sig3)
data1=U[:,:3]*sig3*VT[:3,:]
print('重构后的数据',data1)
print('原始数据维度',np.array(data).shape)
print('重构后数据维度',np.array(data1).shape)

sig3=np.mat([[sigma[0],0,0],[0,sigma[1],0],[0,0,sigma[2]]])
sig3=np.mat(np.eye(3)*sigma[:3])

我们如何得知我们需要三个奇异值呀。
把奇异值求取平方和,将奇异值平方和累积到总值的0.9或者其他值即停止累积。即得到我们需要几个奇异值。

用户商品推荐

用户商品推荐在现实生活中有很多应用场景。比如如果你在今日头条上经常看某一类新闻,然后系统会经常给你推送该类新闻而不是其他类。

推荐算法:分为基于商品和基于用户。

基于商品:是比较商品之间的相似度,如果你喜欢某类电影,如果有一款新的电影出现啦,你还未看过,系统会计算该部电影与你看过的电影的相似度,如果相似度高则认为该类电影是你喜欢的,会把该类电影推送给你。

基于用户:计算不同用户之间的相似度,如果相似度高。会把其他用户看过的电影而你没有看过的电影推送给你。

1.相识度公式
欧式距离

我们希望相似度在0到1之间,
可以用相似度=1/(1+距离)

from numpy import linalg as la
def ecludSim(inA, inB):
    return 1.0 / (1.0 + la.norm(inA - inB))

皮尔逊相关系数
皮尔逊公式相比较于欧式距离有个优势:对数值的大小不敏感。比如一个极端值对某一商品打分为5分,而另一个则打1分,皮尔逊系数会认为两者是等价的。
皮尔逊系数是-1到1之间。我们希望得到的值是0到1之间。
所以0.5+0.5*皮尔逊系数

因为皮尔逊系数得到的是一个系数矩阵,我们只取一个值就可以啦,所以系数后面有[0][1]

from numpy import linalg as la
def pearsSim(inA, inB):
    # 此时两个向量完全相关
    return 0.5 + 0.5 * np.corrcoef(inA, inB, rowvar=0)[0][1]

余弦距离

余弦距离得到的值为-1到1,我们将其映射到0到1

from numpy import linalg as la
def cosSim(inA, inB):
    num = float(inA.T * inB)
    denom = la.norm(inA) * la.norm(inB)
    return 0.5 + 0.5 * (num / denom)

2.基于用户和基于商品方法选择:

算法复杂度都会随着数量增加而增加。基于商品是商品增加复杂度增加,基于用户时用户数量增加而复杂度增加。对于绝大多数商店而言,用户数远远大于商品数,所以本文选择基于商品。
3.推荐流程

推荐系统的工作流程:给定一个用户,系统会为此用户返回N个最好的推荐菜。
(1):寻找用户没有评级打分的菜肴,即在用户-物品矩阵中的0值。
(2):在用户没有评级的所有物品中,对每个物品预计一个可能的评级分数。这就是说我们认为用户可能对物品的打分(这就是相似度计算的初衷)
(3):对这些物品的评分从高到底进行排序,返回前N 个商品。

4.原始数据

"""
函数说明:加载数据,菜肴矩阵
行:代表人
列:代表菜肴名词
值:代表人对菜肴的评分,0代表未评分
"""
def loadExData():
    return[[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
           [0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
           [0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
           [3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
           [5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
           [0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
           [4, 3, 4, 0, 0, 0, 0, 5, 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],
           [0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
           [1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]

5.寻找两个用户都评级的物品

"""
Parameters:
    dataMat - 训练数据集
    user - 用户编号
    simMeas - 相似度计算方法
    item - 未评分的物品编号
    
Returns:
    评分(0~5之间的值)
"""
def svdEst(dataMat, user, simMeas, item):
    #假设user=1
    # 得到数据集中的物品数目
    n = np.shape(dataMat)[1]
    # 初始化两个评分值
    simTotal = 0.0
    ratSimTotal = 0.0
    # 奇异值分解
    # 在SVD分解之后,我们只利用包含90%能量值的奇异值,这些奇异值会以Numpy数组形式得以保存
    U, Sigma, VT = la.svd(dataMat)
    # 如果要进行矩阵运算,就必须要用这些奇异值构造出一个对角阵
    Sig4 = np.mat(np.eye(4) * Sigma[: 4])
    # 利用U矩阵将物品转换到低维空间中,构建转换后的物品(物品的4个主要特征)
    xformedItems = dataMat.T * U[:, :4] * Sig4.I#shape(11,4)
    # 遍历行中的每个物品(对用户评过分的物品进行遍历,并将它与其他物品进行比较)
    for j in range(n):
        userRating = dataMat[user, j]
        # 如果某个物品的评分值为0,则跳过这个物品
        if userRating == 0:
            continue
        # 相似度的计算也会作为一个参数传递给该函数
        similarity = simMeas(xformedItems[item, :].T, xformedItems[j, :].T)
        print('商品 %d 和商品 %d 相似度similarity is: %f' % (item, j, similarity))
        # 相似度会不断累加,每次计算时还考虑相似度和当前用户评分的乘积
        # similarity 用户相似度   userRating 用户评分
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    # 通过除以所有的评分和,对上述相似度评分的乘积进行归一化,使得最后评分在0~5之间,这些评分用来对预测值进行排序
    else:
        return ratSimTotal / simTotal

为了说明该函数功能,我们假设当前用户user=1,未评分商品为0

dataMat=np.mat(loadExData())
#假设当前用户时1
print('查看用户1未评级商品:',np.nonzero(dataMat[1, :].A == 0)[1])
# 得到数据集中的物品数目
n = np.shape(dataMat)[1]
# 初始化两个评分值
simTotal = 0.0
ratSimTotal = 0.0
# 奇异值分解
# 在SVD分解之后,我们只利用包含90%能量值的奇异值,这些奇异值会以Numpy数组形式得以保存
U, Sigma, VT = la.svd(dataMat)
# 如果要进行矩阵运算,就必须要用这些奇异值构造出一个对角阵
Sig4 = np.mat(np.eye(4) * Sigma[: 4])
# 利用U矩阵将物品转换到低维空间中,构建转换后的物品(物品的4个主要特征)
xformedItems = dataMat.T * U[:, :4] * Sig4.I
print('降维重构后的数据:',xformedItems)

重构后的数据行为商品,列为商品的几个特征。
实现降维

# 遍历行中的每个物品(对用户评过分的物品进行遍历,并将它与其他物品进行比较)
#假设未评分商品为第0个,开始计算
for j in range(n):
    userRating = dataMat[1, j]
    # 如果某个物品的评分值为0,则跳过这个物品
    if userRating == 0:
        continue
    # 相似度的计算也会作为一个参数传递给该函数
    similarity = cosSim(xformedItems[0, :].T, xformedItems[j, :].T)
    print('商品 %d 和商品 %d 相似度is: %f' % (0, j, similarity))
    # 相似度会不断累加,每次计算时还考虑相似度和当前用户评分的乘积
    # similarity 用户相似度   userRating 用户评分
    simTotal += similarity
    ratSimTotal += similarity * userRating
if simTotal == 0:
    print('商品0得分:',0)
    # 通过除以所有的评分和,对上述相似度评分的乘积进行归一化,使得最后评分在0~5之间,这些评分用来对预测值进行排序
else:
    print( '商品0得分:',ratSimTotal / simTotal)

通过此方法,我们可以给未知得分的商品0打上分啦。
xformedItems[0, :].T 这里有装置,我们采用的方法是基于商品。

简单说下该方法思路:
1.对于一个用户,找到他未打分的商品,和已打分过的商品。
2.计算未打分商品与已打分商品的相似度,这里的相似度计算输入的所有用户对该商品的打分。
3.将相似度乘以那个已打分的商品得分值。
4.求和 归一化 得到未打分商品的得分。未打分商品的得分主要贡献来源:未知商品与已打分商品相似度,已打分商品的得分

6.寻找前个未评级商品 推荐给用户

"""
函数说明:推荐引擎

Parameters:
    dataMat - 训练数据集
    user - 用户编号
    N- 产生N个推荐结果
    simMeas - 相似度计算方法
    estMethod - 推荐引擎方法

Returns:
    评分(0~5之间的值)
"""


def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=svdEst):
    # 寻找未评级的物品
    # 对给定的用户建立一个未评分的物品列表
    unratedItems = np.nonzero(dataMat[user, :].A == 0)[1]
    # 如果不存在未评分物品,那么就退出函数
    if len(unratedItems) == 0:
        return ('本店所有商品你均尝试过,打过分')
    # 物品的编号和评分值
    itemScores = []
    # 在未评分的物品上进行循环
    for item in unratedItems:
        estimatedScore = estMethod(dataMat, user, simMeas, item)
        # 寻找前N个未评级的物品,调用svdEst()来产生该物品的预测得分,该物品的编号和估计值会放在一个元素列表itemScores中
        itemScores.append((item, estimatedScore))
    # 返回元素列表,第一个就是最大值
    return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[: N]
    

7.全部代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author: yudengwu
# @Date  : 2020/8/22
import numpy as np
from numpy import linalg as la

#相似度
def cosSim(inA, inB):
    num = float(inA.T * inB)
    denom = la.norm(inA) * la.norm(inB)
    return 0.5 + 0.5 * (num / denom)

#数据导入
"""
函数说明:加载数据,菜肴矩阵
行:代表人
列:代表菜肴名词
值:代表人对菜肴的评分,0代表未评分
"""

def loadExData():
    return[[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
           [0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
           [0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
           [3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
           [5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
           [0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
           [4, 3, 4, 0, 0, 0, 0, 5, 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],
           [0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
           [1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]

"""
给未打分的商品打分

Parameters:
dataMat - 训练数据集
user - 用户编号
simMeas - 相似度计算方法
item - 未评分的物品编号

Returns:评分(0~5之间的值)
"""
def svdEst(dataMat, user, simMeas, item):
    #假设user=1
    # 得到数据集中的物品数目
    n = np.shape(dataMat)[1]
    # 初始化两个评分值
    simTotal = 0.0
    ratSimTotal = 0.0
    # 奇异值分解
    # 在SVD分解之后,我们只利用包含90%能量值的奇异值,这些奇异值会以Numpy数组形式得以保存
    U, Sigma, VT = la.svd(dataMat)
    # 如果要进行矩阵运算,就必须要用这些奇异值构造出一个对角阵
    Sig4 = np.mat(np.eye(4) * Sigma[: 4])
    # 利用U矩阵将物品转换到低维空间中,构建转换后的物品(物品的4个主要特征)
    xformedItems = dataMat.T * U[:, :4] * Sig4.I#shape(11,4)
    # 遍历行中的每个物品(对用户评过分的物品进行遍历,并将它与其他物品进行比较)
    for j in range(n):
        userRating = dataMat[user, j]
        # 如果某个物品的评分值为0,则跳过这个物品
        if userRating == 0:
            continue
        # 相似度的计算也会作为一个参数传递给该函数
        similarity = simMeas(xformedItems[item, :].T, xformedItems[j, :].T)
        print('商品 %d 和商品 %d 相似度similarity is: %f' % (item, j, similarity))
        # 相似度会不断累加,每次计算时还考虑相似度和当前用户评分的乘积
        # similarity 用户相似度   userRating 用户评分
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    # 通过除以所有的评分和,对上述相似度评分的乘积进行归一化,使得最后评分在0~5之间,这些评分用来对预测值进行排序
    else:
        return ratSimTotal / simTotal

"""
函数说明:推荐引擎

Parameters:
    dataMat - 训练数据集
    user - 用户编号
    N- 产生N个推荐结果
    simMeas - 相似度计算方法
    estMethod - 推荐引擎方法

Returns:
    评分(0~5之间的值)

Modify:
    2018-08-08
"""


def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=svdEst):
    # 寻找未评级的物品
    # 对给定的用户建立一个未评分的物品列表
    unratedItems = np.nonzero(dataMat[user, :].A == 0)[1]
    # 如果不存在未评分物品,那么就退出函数
    if len(unratedItems) == 0:
        return ('本店所有商品你均尝试过,打过分')
    # 物品的编号和评分值
    itemScores = []
    # 在未评分的物品上进行循环
    for item in unratedItems:
        estimatedScore = estMethod(dataMat, user, simMeas, item)
        # 寻找前N个未评级的物品,调用svdEst()来产生该物品的预测得分,该物品的编号和估计值会放在一个元素列表itemScores中
        itemScores.append((item, estimatedScore))
    # 返回元素列表,第一个就是最大值
    return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[: N]

if __name__ == '__main__':
    myMat = np.mat(loadExData())
    recommend(myMat, 1, estMethod=svdEst)#对用户1进行推荐
    print('\n----------------------------------------\n')
    A = recommend(myMat, 1, estMethod=svdEst, simMeas=cosSim)
    A=np.array(A)
    for i in range(A.shape[0]):
      print('给您推荐的商品是{},计算得分是{}'.format(A[i][0],A[i][1]))

给用户1的推荐结果是商品4,7,9

如果不使用SVD矩阵分解
未打分商品得分计算函数

"""
函数说明:基于物品相似度的推荐引擎
计算某用户未评分物品,以对该物品和其他物品评分的用户的物品相似度,然后进行综合评分

Parameters:
    dataMat - 训练数据集
    user - 用户编号
    simMeas - 相似度计算方法
    item - 未评分的物品编号
    
Returns:
    评分(0~5之间的值)

Modify:
    2018-08-08
"""
def standEst(dataMat, user, simMeas, item):
    # 得到数据集中的物品数目
    n = np.shape(dataMat)[1]
    # 初始化两个评分值
    simTotal = 0.0
    ratSimTotal = 0.0
    # 遍历行中的每个物品(对用户评过分的物品进行遍历,并将它与其他物品进行比较)
    for j in range(n):
        userRating = dataMat[user, j]
        # 如果某个物品的评分值为0,则跳过这个物品
        if userRating == 0:
            continue
        # 寻找两个用户都评级的物品
        # 变量overLap给出的是两个物品当中已经被评分的那个元素的索引ID
        # logical_and计算x1和x2元素的逻辑与
        overLap = np.nonzero(np.logical_and(dataMat[:, item].A > 0, dataMat[:, j].A > 0))[0]
        print(dataMat[:, item].A)
        # 如果相似度为0,则两者没有任何重合元素,终止本次循环
        if len(overLap) == 0:
            similarity = 0
        # 如果存在重合的物品,则基于这些重合物重新计算相似度
        else:
            similarity = simMeas(dataMat[overLap, item], dataMat[overLap, j])
        print('the %d and %d similarity is: %f' % (item, j, similarity))
        # 相似度会不断累加,每次计算时还考虑相似度和当前用户评分的乘积
        # similarity 用户相似度   userRating 用户评分
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    # 通过除以所有的评分和,对上述相似度评分的乘积进行归一化,使得最后评分在0~5之间,这些评分用来对预测值进行排序
    else:
        return ratSimTotal / simTotal

电气专业的计算机萌新,写博文不容易,在大佬面前献丑啦。如果你觉得本文对你有用,请点个赞支持下,谢谢。

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/kobeyu652453/article/details/108148708