注意:本篇重在说明公式推导,关于具体使用的话python有专门的机器学习库已经集成,直接用就可以啦,可以在读完本文的理论部分后再去看笔者另一篇应用了PCA的关于人脸识别的一个简单例子https://blog.csdn.net/weixin_42001089/article/details/79989788
(1)原理
---------------------------------------------------------------------------------------------------------------------------------------------------------------
关于怎么求特征值这里简单举一个小例子:会的话直接跳过就可以啦
我们通过上面求得该矩阵的2个特征值分别为1和3
上面通过矩阵化简可以得到解为
同理3这个特征值对应的解为:
所以特征向量分别是
---------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------
对应到上面的例子中特征向量应该是:
注意当求矩阵单个特征值时是,但当所有特征值放一起后是
而不是
这种形式。
即分解结果为:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
(2)应用
---------------------------------------------------------------------------------------------------------------------------------------------------------------
好了,代码也应该很简单啦:
def pca(datamat,N=3):
meanVals = mean(datamat,axis=0)
meanRemoved = datamat - meanVals
covmat = cov(meanRemoved,rowvar=0)
eigVals,eigVects = linalg.eig(mat(covmat))
eigValInd = argsort(eigVals)
eigValInd = eigValInd[:-(N+1):-1]
redEigVects = eigVects[:,eigValInd]
lowDDataMat = meanRemoved*redEigVects
reconMat = (lowDDataMat *redEigVects.T)+meanVals
return lowDDataMat ,reconMat
就是在求出协方差的特征值后进行排序,然后选取前N名(eigValInd)特征值及其对应的特征向量(redEigVects)
然后lowDDataMat就是其将原始矩阵降维后,变成的新维度,该维度与原始矩阵相比,行数不变,列数降维到N,但却保留了原始矩阵所蕴含的大部分信息
reconMat就是我们用lowDDataMat来重构回原始矩阵,为什么会是这样呢?
这里将 redEigVects 简写为red,证明过程使用到了上面所说的
重构只是为了验证一下而已,我们真正想要的正是lowDDataMat即返回的第一个矩阵。即假设datamat是m*m,那么降维后便是m*N
-------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
注意:上面的PCA的原始矩阵虽说可以不是方阵,但我们也不是求原始矩阵的特征值和特征向量呀,是求的其协方差,其协方差即这里的协方差cov一定是方阵对吧,所以其本质还是对方阵进行的分解,而下面介绍的svd正是直接对不是方阵的情况进行分解
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
(1)原理
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
对比一下方阵求特征值的公式:
(1)方阵分解那里对角线是特征值,这里是奇异值
(2)方阵分解那里左右维数相同,互为逆矩阵(和
),而这里是左右奇异矩阵
可以看到这两种分解在形式上大体相似,只是维数不同
-------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------
这里简单证明一下:
通过(4)和(5)可以看到U和V其实分别就是和
各自所有特征向量展开合并的矩阵
---------------------------------------------------------------------------------------------------------------------------------------------------------------
(2)应用
想PCA取前几个比较大的特征值一样,这里SVD是取前几个比较大奇异值
假设现在有一个矩阵,每一行代表一个用户,每列代表一个物品
辣条 | 薯片 | 可乐 | 冰红茶 | |
小花 | 0 | 0 | 2 | 4 |
小明 | 0 | 0 | 2 | 5 |
小李 | 3 | 6 | 0 | 0 |
其中的数字是评价等级1~5 , 0代表没有评价
我们将该矩阵分解,
这里假设我们取2个奇异值,那么便可以近似分解
--------------------------------------------------------------------------------------------------------------------------------------------------------------
例如:
U,Z,V = linalg.svd(datamat)
这里的datamat就是我们要分解的矩阵。
现在比如一共求出有10个奇异值,我们取3个来进行降维,即我们要将原始矩阵的列数压缩到3维(也可以理解为对于每一个用户来说,原来是对很多商品进行了评价,现在我们要将其总结一下,使其结果是对总结后的3类商品进行的评价),即这里是求V:
假设原来datamat是15*10,那么降维后这里的X_tran就是15*3啦
同理如果是给行降维,即将人进行分类,就是说求V
X_tran = datamat.T*U[:,:3]*mat(eye(4)*U[:3]).I
假设原来datamat是15*10,那么降维后这里的X_tran就是3*10啦
--------------------------------------------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
可能上面说的有点不好理解,看一下代码吧,就好多啦:
代码参考;《机器学习实战》Peter Harrington著,李锐等译
def Similarity(datamat,user,simways,item):
n = shape(datamat)[1]
simTotal = 0.0
ratSimTotal = 0.0
U,Z,V = linalg.svd(datamat)
X_tran = datamat.T*U[:,:3]*mat(eye(4)*U[:3]).I
for j in range(n):
userRating = datamat[user,j]
if userRating==0 or j==item:
continue
similarity = simways(X_tran[item,:].T,(X_tran[j,:].T)
simTotal += similarity
ratSimTotal +=similarity * userRating
if simTotal=0:
return 0
else:
return ratSimTotal/simTotal
上面的函数有四个输入,原始矩阵,要给哪个用户推荐,计算相似度的方法,以及当前推荐物品的id
从中可以看到我们取了3个奇异值
X_tran = datamat.T*U[:,:3]*mat(eye(4)*U[:3]).I
对应我们求V的过程,即X_tran就是我们取3个奇异值后对应的V
if userRating==0 or j==item:
continue
对应部分就是说要计算item和已经评过分的物品的相似度,加入j之前也没评价过,那比较就没有意义啦,这里就相当于上面我们说的处理出R2的过程
similarity = simways(X_tran[item,:].T,(X_tran[j,:].T)
部分就是求两个向量的相似度
ratSimTotal +=similarity * userRating
部分就是相似度乘以权重,不难想象,如果j的评分高,则userRating高,权重大,此时如果相似度也高,那么最后结果也高,如果j的评分底,相似度高,相当于带来的惩罚也越多(和评分差的相似度高,当然要给与高惩罚)
return ratSimTotal/simTotal
部分就是将结果该归一化到1~5评分等级中
好啦!!!!!!!!!!!!!!!!!
有了上面的评分函数,接下来就是在外面遍历R1就可以啦
def recommend(datamat,user,N=3,simways):
unratedItems = nonzero(datamat[user,:].A==0)[1]
if len(unratedItems) == 0:
return 'you rated everything'
itemScores = []
for item in unratedItems:
estimatedScore = Similarity(datamat,user,simways,item)
itemScores.append((item, estimatedScore))
return sorted(itemScores,key=lambda x:x[1],reverse=True)[:N]
这里的 unratedItems就是我们上面讨论的R1集合,N参数就是我们要给该用户推荐物品的个数
--------------------------------------------------------------------------------------------------------------------------------------------------------