SVD初探
SVD是一种最常见的矩阵分解技术,SVD将原始的数据Data分解为U Sigma和
,U为m行m列,Sigma为m行n列,
为n行n列。Sigma矩阵只有对角元素,其他元素均为0,Sigma的对角元素是从大到小排列的,这些对角元素称为奇异值。这里的奇异值是矩阵Data*Data.T特征值的平方根。
这里我们直接调用numpy的linalg模块来实现矩阵的SVD处理
svddata = np.array([[1,1],[7,7]])
U,Sigma,VT = np.linalg.svd(svddata)
print(U,'\n',Sigma,'\n',VT)
output:
[[-0.14142136 -0.98994949]
[-0.98994949 0.14142136]]
[10. 0.]
[[-0.70710678 -0.70710678]
[-0.70710678 0.70710678]]
奇异值分解是一件很神奇的事情,我们接下来来看下
def loaddata():
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 = loaddata()
U,Sigma,VT = np.linalg.svd(data)
Sig3 = np.array([[Sigma[0],0,0],[0,Sigma[1],0],[0,0,Sigma[2]]])
np.dot(np.dot(U[:,:3],Sig3),VT[:3,:])#我们取前三个奇异值合成运算
#可以看到返回的数据几乎与原数据一样
output:
array([[ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00,
-3.06102944e-16, -3.24716158e-16],
[ 2.00000000e+00, 2.00000000e+00, 2.00000000e+00,
1.56507791e-16, 1.19281363e-16],
[ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00,
1.59285357e-15, 1.57424036e-15],
[ 5.00000000e+00, 5.00000000e+00, 5.00000000e+00,
-1.16268486e-17, -1.04909757e-16],
[ 1.00000000e+00, 1.00000000e+00, -1.08845596e-15,
2.00000000e+00, 2.00000000e+00],
[ 2.54588945e-16, 1.60102837e-15, -1.68123482e-15,
3.00000000e+00, 3.00000000e+00],
[ 1.30086946e-16, 5.78900087e-16, -6.70962949e-16,
1.00000000e+00, 1.00000000e+00]])
基于协同过滤的推荐引擎
首先我们考虑相似度的概念,相似度的计算方式有欧氏距离、皮尔逊相关系数和余弦相似度。
def eclud(ina,inb):
return 1.0/(1.0+np.linalg.norm(ina-inb))#np.linalg.norm 求范数
def pears(ina,inb):
if len(ina)<3: return 1.0
return 0.5+0.5*np.corrcoef(ina,inb,rowvar=0)[0][1]#第0组数组和第一组数组的相关系数
def coss(ina,inb):
num = np.dot(ina.T,inb).astype(float)
denom = np.linalg.norm(ina)*np.linalg.norm(inb)
return 0.5+0.5*(num/denom)
基于物品相似度的推荐引擎
第一个standest函数是给定数据以及用户和物品编号,计算基于相似度的估计,通过计算每一个物品与该物品的相似度乘上其值,这里相似度类似于权重,相似度越高,权重越大。获取的和值除以权重的和值得到最终的估计。
recommend推荐函数获取某一用户未评价的物品也即矩阵的0项,对于每一未评价的项计算基于相似度的估计值,以tuple的形式保存到列表中。
def standest(data,user,simeas,item):#user给定
n = data.shape[1]
simtal = 0.0
ratsimtal = 0.0
for j in range(n):
userrating = data[user,j]
if userrating == 0: continue
overlap = np.nonzero(np.logical_and(data[:,item],data[:,j]))[0]#获取两个物品都非0的索引
if len(overlap)==0:
similar = 0
else: similar = simeas(data[overlap,item],data[overlap,j])#计算j和item的物品都非0的相似度(默认余弦相似度)
print('the %d and %d similarity is: %f' %(item,j,similar))
simtal += similar
ratsimtal += similar*userrating#每一个特征的值乘相关系数
if simtal ==0: return 0
else: return ratsimtal/simtal
def recommend(data,user,N=3,simeas=coss,estmethod=standest):
#print(np.nonzero(data[user,:]==0))
unrated = np.nonzero(data[user,:]==0)[0]# 获取列索引
if len(unrated)==0: return 'you rated everything'
itemscores = []
for item in unrated:
estscore = estmethod(data,user,simeas,item)
itemscores.append((item,estscore))
return sorted(itemscores,key=lambda jj:jj[1],reverse=True)[:N]
利用SVD提高推荐的效果
通过下面代码可以发现,选取三个奇异值就可以包含90%以上的能量
def loaddata2():
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]]
data4 = np.array(loaddata2())
U,Sigma,VT = np.linalg.svd(np.array(data4))
print(Sigma)
Sigma2 = Sigma**2
sums = np.sum(Sigma2)
print(np.sum(Sigma2[:3]),0.9*sums)
output:
[15.77075346 11.40670395 11.03044558 4.84639758 3.09292055 2.58097379
1.00413543 0.72817072 0.43800353 0.22082113 0.07367823]
500.5002891275791 487.7999999999994
接下来我们对之前的评分基于SVD修改
svdEst函数与之前的基于相似度的估计类似,不同点在于这里计算相似度的矩阵是通过奇异值分解后选取4个奇异值合成的矩阵。
#基于SVD评分设计
def svdest(data,user,simeas,item):
n = data.shape[1]
simtal = 0.0
ratsimtal = 0.0
U,Sigma,VT = np.linalg.svd(data)
sig4 = np.eye(4)*Sigma[:4]
formitem = np.dot(np.dot(data.T,U[:,:4]),np.linalg.inv(sig4))
print(formitem)
for j in range(n):
userrating = data[user,j]
if userrating ==0 or j ==item:
continue
similar = simeas(formitem[item,:].T,formitem[j,:].T)
print('the %d and %d similarity is: %f' %(item,j,similar))
simtal += similar
ratsimtal += similar*userrating
if simtal ==0: return 0
else: return ratsimtal/simtal
recommend(data4,1,estmethod=svdest,simeas=pears)#获取数据中第一个用户的0物品的估计
基于SVD的图像压缩
下面代码我们通过选择前numsv个奇异值构建的矩阵,可以看到只需要两个奇异值就能相当精确地对图像实现重构。U和V.T都是32x2的矩阵,有两个奇异值,因此总数字数据为64+64+2=130,与原数字相比,几乎压缩了10倍。
#示例:基于SVD的图像压缩
def printmat(inmat,thresh=0.8):
for i in range(32):
for j in range(32):
if inmat[i,j]>thresh:
print(1,end='')
else: print(0,end='')
print('')
def imgcompress(numsv=3,thresh=0.8):
my1=[]
for line in open(filename).readlines():
newrow=[]
for i in range(32):
newrow.append(int(line[i]))
my1.append(newrow)
mymat = np.array(my1)
print('**********original array*************')
print(mymat.shape)
printmat(mymat,thresh)
U,Sigma,VT = np.linalg.svd(mymat)
sigrecon = np.zeros((numsv,numsv))
for k in range(numsv):
sigrecon[k,k] = Sigma[k]
reconmat = np.dot(np.dot(U[:,:numsv],sigrecon),VT[:numsv,:])
print('**********reconstructed array using %d singular values*************'% numsv)
print(reconmat.shape)
printmat(reconmat,thresh)