首页 > 技术文章 > 机器学习-SVD(隐语义模型)协同过滤

EnzoDin 2020-03-21 16:38 原文

SVD(隐语义模型)协同过滤

隐语义模型,数学上称为SVD,奇异值分解。该算法最早在文本挖掘领域被提出,用于找到文章的隐含主题,也被称为主题模型。

隐语义模型的核心思想是通过隐含特征(Latent Factor)计算用户和物品的相似性。

SVD是将矩阵A分解成以下形式

A=U∑VT

其中U和V均为单位正交阵,UUT=E, VVT=E, U称为左奇异矩阵,V称为右奇异矩阵,∑仅在对角线上有值,我们称之为奇异值,其他值均为0.

维度:U:m*m  V:n*n  ∑:m*n

关于相似性的计算存在一些问题

1.物品的人为分类对推荐算法造成影响。分类是人为指定的,不同的分类标准对不同的用户带来预测精度的问题

2.类中物品的相似度是一个消费行为的问题,需要针对不同的用户确定不同的权重,这样做的可能性不大

3.即使能够构建权重和分类,也不能完全确定某个用户对某类产品感兴趣的程度。

 

因此,我们需要一种针对每类用户的不同消费行为来计算对不同物品的相似度的算法。

假设有一个新用户E,对物品的评分为[5,5,0,0,0,5].现在在现有矩阵中找出与其最相似的用户,并把这个用户感兴趣的物品推荐给新用户。

使用夹角余弦求用户之间的相似度,即选中与新用户之间夹角最小(余弦值)最大的那个。

 

from numpy import *
import numpy as np

# 夹角余弦距离公式
def cosSim(inA, inB):
    denom = linalg.norm(inA) * linalg.norm(inB)
    return float(inA * inB.T) / (denom + eps)

A = mat([[5, 5, 3, 0, 5, 5],
         [5, 0, 4, 0, 4, 4],
         [0, 3, 0, 5, 4, 5],
         [5, 4, 3, 3, 5, 5]])
# 其中s是对矩阵a的奇异值分解。s除了对角元素不为0,其他元素都为0,并且对角元素从大到小排列。s中有n个奇异值,一般排在后面的比较接近0,所以仅保留比较大的r个奇异值。
U, S, VT = linalg.svd(A)
print(S)

# 避免除0
eps = 1.0e-6
# 新数据
new = mat([[5,5,0,0,0,5]])
U,S,VT = linalg.svd(A.T)
V = VT.T
# 对角矩阵(diagonal matrix)是一个主对角线之外的元素皆为0的矩阵,常写为diag
Sigma = diag(S)
r = 2   # 取前两个奇异值
# 得到金丝猴的U\S\V值
Ur = U[:,:r]
Sr = Sigma[:r,:r]
Vr = V[:,:r]
newresult = new*Ur*linalg.inv(Sr)   # 计算User E的坐标值
print(newresult)

maxv = 0    # 最大的余弦值
maxi = 0    # 最大值的下标
indx = 0
for vi in Vr:
    temp = cosSim(newresult, vi)
    if temp > maxv:
        maxv = temp
        maxi = indx
    indx += 1
print(maxv,maxi)

[[-0.37752201 -0.08020351]]
0.9867908785596432 0 

稍微封装下

from numpy import *
# 夹角余弦距离公式
def cosSim(inA, inB):
    eps = 1.0e-6
    denom = linalg.norm(inA) * linalg.norm(inB)
    return float(inA * inB.T) / (denom + eps)

# dataSet:训练集
# testVect:测试集
# r:取前r个近似值
# rank:结果排序
# distCalc: 相似度计算函数
def recommand(dataSet, testVect, r=3, rank=1, distCalc=cosSim):
    m,n = shape(dataSet)
    limit = min(m, n)
    if r > limit:
        r = limit
    U,S,VT = linalg.svd(dataSet.T)  # SVD分解
    V = VT.T
    # 取前r个U\S\V值
    Ur = U[:, :r]
    Sr = diag(S)[:r, :r]
    Vr = V[:, :r]
    testresult = testVect * Ur * linalg.inv(Sr) # 计算User E的坐标值
    resultarray = array([distCalc(testresult, vi) for vi in Vr])
    descindx = argsort(-resultarray)[:rank] # 结果排序,降序
    return descindx, resultarray[descindx]  # 排序后的索引和值


A = mat([[5, 5, 3, 0, 5, 5],
         [5, 0, 4, 0, 4, 4],
         [0, 3, 0, 5, 4, 5],
         [5, 4, 3, 3, 5, 5]])

# 新数据
new = mat([[5,5,0,0,0,5]])

indx, resultarray = recommand(A, new, r=2, rank=2,distCalc=cosSim)
# 索引值
print(indx)
# 相似度
print(resultarray)

[0 3]
[0.98679088 0.95536738]

计算后得知,与之相似的用户坐标为0,即[5,5,3,0,5,5].此时可将该用户的第三、五个物品推荐给新用户

与第一个用户的相似度为98.68%, 与第四个用户的相似度为95.53%

SVD取前部分奇异值复原图片

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np

# 读取图片
# img_eg = mpimg.imread("/Users/FengZhen/Desktop/image/bing/road.jpeg")
img_eg = mpimg.imread("/Users/FengZhen/Desktop/image/timg.jpeg")
print(img_eg.shape)

# 奇异值分解
# 我们先将图片变成500×2250,再做奇异值分解。从svd函数中得到的奇异值sigma它是从大到小排列的。
img_temp = img_eg.reshape(500, 750 * 3)
U,Sigma,VT = np.linalg.svd(img_temp)

# 取前部分奇异值重构图片
# 取前60个奇异值
sval_nums = 60
img_restruct1 = (U[:, 0:sval_nums]).dot(np.diag(Sigma[0:sval_nums])).dot(VT[0:sval_nums, :])
img_restruct1 = img_restruct1.reshape(500, 750, 3)

# 取前120个奇异值
sval_nums = 120
img_restruct2 = (U[:, 0:sval_nums]).dot(np.diag(Sigma[0:sval_nums])).dot(VT[0:sval_nums, :])
img_restruct2 = img_restruct2.reshape(500, 750, 3)

# 取前180个奇异值
sval_nums = 180
img_restruct3 = (U[:, 0:sval_nums]).dot(np.diag(Sigma[0:sval_nums])).dot(VT[0:sval_nums, :])
img_restruct3 = img_restruct3.reshape(500, 750, 3)

fig, ax = plt.subplots(1, 4, figsize=(24, 32))

ax[0].imshow(img_eg)
ax[0].set(title="src")
ax[1].imshow(img_restruct1.astype(np.uint8))
ax[1].set(title="nums of sigma = 60")
ax[2].imshow(img_restruct2.astype(np.uint8))
ax[2].set(title="nums of sigma = 120")
ax[3].imshow(img_restruct3.astype(np.uint8))
ax[3].set(title="nums of sigma = 180")

原图

 

分别取前60、120、180个奇异值复原的图片如下

推荐阅读