SVD降维的基本原理,可以详见以前文章:https://blog.csdn.net/u012421852/article/details/80433463
降维的理论以及和意义不再赘述,此处仅仅给出SVD分解降维的一个应用示例。
Step1:准备要降维的数据矩阵M,以及奇异值开方和占比阈值percentage
data = np.array([[5, 5, 0, 5],
[5, 0, 3, 4],
[3, 4, 0, 3],
[0, 0, 5, 3],
[5, 4, 4, 5],
[5, 4, 5, 5]])
percentage = 0.9
Step2:对数据矩阵M进行奇异值分解
(U, S, VT) = np.linalg.svd(M)
解释np.linalg.svd(M)的返回值U,S,VT依次对应于下面SVD公式中的U,∑,V.T,
注意:svd()方法返回的第三个值是SVD中的V.T,而不是V,后面降维时直接使用VT[:K, :len(VT)]作为降维后的SVD公式中的VT
注意:S是M的所有奇异值的数组
求得的U,S,VT如下所示:
降维前的U,S,VT依次为:
(6, 6) U:
[[-0.44721867 0.53728743 0.00643789 -0.50369332 -0.38572204 -0.32982993]
[-0.35861531 -0.24605053 -0.86223083 -0.14584826 0.07797125 0.20015231]
[-0.29246336 0.40329582 0.22754042 -0.10376096 0.4360044 0.70652449]
[-0.20779151 -0.67004393 0.3950621 -0.58878098 0.02599042 0.06671744]
[-0.50993331 -0.05969518 0.10968053 0.28687443 0.59460659 -0.53714128]
[-0.53164501 -0.18870999 0.19141061 0.53413013 -0.54845844 0.24290419]]
(4,) S:
[ 17.71392084 6.39167145 3.09796097 1.32897797]
(4, 4) VT:
[[-0.57098887 -0.4274751 -0.38459931 -0.58593526]
[ 0.22279713 0.51723555 -0.82462029 -0.05319973]
[-0.67492385 0.69294472 0.2531966 -0.01403201]
[ 0.41086611 0.26374238 0.32859738 -0.80848795]]
Step3:根据percentage求k值
def _calc_k(self, percentge):
'''确定k值:前k个奇异值的平方和占比 >=percentage, 求满足此条件的最小k值
:param percentage, 奇异值平方和的占比的阈值
:return 满足阈值percentage的最小k值
'''
self.k = 0
#用户数据矩阵的奇异值序列的平方和
total = sum(np.square(self.S))
svss = 0 #奇异值平方和 singular values square sum
for i in range(np.shape(self.S)[0]):
svss += np.square(self.S[i])
if (svss/total) >= percentge:
self.k = i+1
break
return self.k
求得的k值为:2
即成立:前2个奇异值平方和 / 所有奇异值平方和 >= 0.9
Step4:根据求得的k构造k阶奇异值对角阵
def _buildSD(self, k):
'''构建由奇异值组成的对角矩阵
:param k,根据奇异值开放和的占比阈值计算出来的k值
:return 由k个前奇异值组成的对角矩阵
'''
#方法1:用数组乘方法
self.SD = np.eye(self.k) * self.S[:self.k]
#方法2:用自定义方法
e = np.eye(self.k)
for i in range(self.k):
e[i,i] = self.S[i]
return self.SD
Step5:根据求得的k求降维后的U和VT
new_U = U[:len(U), :k]
new_VT = VT[:k, :len(VT)]
k=2时,降维后的U和VT为:
降维后的U, VT依次为:
(6,) new_U=U[:6,:2]:
[[-0.44721867 0.53728743]
[-0.35861531 -0.24605053]
[-0.29246336 0.40329582]
[-0.20779151 -0.67004393]
[-0.50993331 -0.05969518]
[-0.53164501 -0.18870999]]
(2, 4) new_VT=VT[:2, :4]:
[[-0.57098887 -0.4274751 -0.38459931 -0.58593526]
[ 0.22279713 0.51723555 -0.82462029 -0.05319973]]
Step6:用降维后的U,SD,VT求降维后的用户数据矩阵new_M
记原始用户数据矩阵的被评价的商品数为n,用户数为m降维后的SVD公式称为:
new_M = U(n,k)SD(k,k)VT(k,m)
注:U,SD,VT之间是矩阵乘操作np.dot(),不是矩阵点乘np.multiply()
求得降维后的用户数据矩阵为:
用户对商品的评分矩阵M(4个用户对6个商品的评分矩阵):
[[5 5 0 5] #所有用户对商品1的评分值序列
[5 0 3 4] #所有用户对商品2的评分值序列
[3 4 0 3] #所有用户对商品3的评分值序列
[0 0 5 3] #所有用户对商品4的评分值序列
[5 4 4 5] #所有用户对商品5的评分值序列
[5 4 5 5]] #所有用户对商品6的评分值序列
降维后的评分矩阵new_M:
[[ 5.28849359 5.16272812 0.21491237 4.45908018]
[ 3.27680994 1.90208543 3.74001972 3.80580978]
[ 3.53241827 3.54790444 -0.13316888 2.89840405]
[ 1.14752376 -0.64171368 4.94723586 2.3845504 ]
[ 5.07268706 3.66399535 3.78868965 5.31300375]
[ 5.10856595 3.40187905 4.6166049 5.58222363]]
可以直观会发现,降维后的数据矩阵new_M 和 原始用户数据矩阵M 很接近,new_M能够表达原始数据矩阵M的中所有数据的相关性。
也会发现,SVD降维是一种数据的有损压缩.
此代码是降维应用实例的精简代码,如果是理解SVD分解降维示例的数学求解过程,可以看4节给出的Debug版本代码。
人肉出品,代码详见如下:
# -*- coding: utf-8 -*-
"""
@author: 蔚蓝的天空Tom
Talk is cheap, show me the code
Aim:svd分解降维应用示例的代码实现(Release版本)
CSDN URL:https://mp.csdn.net/postedit/80450590
"""
import numpy as np
class CSVD(object):
'''
实现svd分解降维应用示例的Python代码
'''
def __init__(self, data):
self.data = data #用户数据
self.S = [] #用户数据矩阵的奇异值序列 singular values
self.U = [] #svd后的单位正交向量
self.VT = [] #svd后的单位正交向量
self.k = 0 #满足self.p的最小k值(k表示奇异值的个数)
self.SD = [] #对角矩阵,对角线上元素是奇异值 singular values diagonal matrix
def _svd(self):
'''
用户数据矩阵的svd奇异值分解
'''
self.U, self.S, self.VT = np.linalg.svd(self.data)
return self.U, self.S, self.VT
def _calc_k(self, percentge):
'''确定k值:前k个奇异值的平方和占比 >=percentage, 求满足此条件的最小k值
:param percentage, 奇异值平方和的占比的阈值
:return 满足阈值percentage的最小k值
'''
self.k = 0
#用户数据矩阵的奇异值序列的平方和
total = sum(np.square(self.S))
svss = 0 #奇异值平方和 singular values square sum
for i in range(np.shape(self.S)[0]):
svss += np.square(self.S[i])
if (svss/total) >= percentge:
self.k = i+1
break
return self.k
def _buildSD(self, k):
'''构建由奇异值组成的对角矩阵
:param k,根据奇异值开放和的占比阈值计算出来的k值
:return 由k个前奇异值组成的对角矩阵
'''
#方法1:用数组乘方法
self.SD = np.eye(self.k) * self.S[:self.k]
#方法2:用自定义方法
e = np.eye(self.k)
for i in range(self.k):
e[i,i] = self.S[i]
return self.SD
def DimReduce(self, percentage):
'''
SVD降维
:param percentage, 奇异值开方和的占比阈值
:return 降维后的用户数据矩阵
'''
#Step1:svd奇异值分解
self._svd()
#Step2:计算k值
self._calc_k(percentage)
print('\n按照奇异值开方和占比阈值percentage=%d, 求得降维的k=%d'%(percentage, self.k))
#Step3:构建由奇异值组成的对角矩阵singular values diagonal
self._buildSD(self.k)
k,U,SD,VT = self.k,self.U, self.SD, self.VT
#Step4:按照svd分解公式对用户数据矩阵进行降维,得到降维压缩后的数据矩阵
a = U[:len(U), :k]
b = np.dot(SD, VT[:k, :len(VT)])
newData = np.dot(a,b)
return newData
def CSVD_manual():
##训练数据集,用户对商品的评分矩阵,行为多个用户对单个商品的评分,列为用户对每个商品的评分
data = np.array([[5, 5, 0, 5],
[5, 0, 3, 4],
[3, 4, 0, 3],
[0, 0, 5, 3],
[5, 4, 4, 5],
[5, 4, 5, 5]])
percentage = 0.9
svdor = CSVD(data)
ret = svdor.DimReduce(percentage)
print('====================================================')
print('原始用户数据矩阵:\n', data)
print('降维后的数据矩阵:\n', ret)
print('====================================================')
if __name__=='__main__':
CSVD_manual()
runfile('C:/Users/tom/svd_reduceDim_release.py', wdir='C:/Users/tom')
按照奇异值开方和占比阈值percentage=0, 求得降维的k=2
====================================================
原始用户数据矩阵:
[[5 5 0 5]
[5 0 3 4]
[3 4 0 3]
[0 0 5 3]
[5 4 4 5]
[5 4 5 5]]
降维后的数据矩阵:
[[ 5.28849359 5.16272812 0.21491237 4.45908018]
[ 3.27680994 1.90208543 3.74001972 3.80580978]
[ 3.53241827 3.54790444 -0.13316888 2.89840405]
[ 1.14752376 -0.64171368 4.94723586 2.3845504 ]
[ 5.07268706 3.66399535 3.78868965 5.31300375]
[ 5.10856595 3.40187905 4.6166049 5.58222363]]
====================================================
注:此代码为展示示例的数学求解过程的代码,不是精简代码
# -*- coding: utf-8 -*-
"""
@author: 蔚蓝的天空Tom
Talk is cheap, show me the code
Aim:svd分解降维应用示例的代码实现
CSDN URL:https://mp.csdn.net/postedit/80450590
"""
import numpy as np
from numpy import linalg as LA
class CSVD(object):
'''
实现SVD分解降维应用示例的数学求解过程的Python代码
'''
def __init__(self, data):
self.data = data #用户数据
self.S = [] #用户数据矩阵的奇异值序列 singular values
self.U = [] #svd后的单位正交向量
self.VT = [] #svd后的单位正交向量
self.k = 0 #满足self.p的最小k值(k表示奇异值的个数)
self.SD = [] #对角矩阵,对角线上元素是奇异值 singular values diagonal matrix
#svd奇异值分解
self._svd()
def _svd(self):
'''
用户数据矩阵的svd奇异值分解
'''
u,s,v = np.linalg.svd(self.data)
(self.U, self.S, self.VT) = (u, s, v)
return self.U, self.S, self.VT
def _calc_k(self, percentge):
'''确定k值:前k个奇异值的平方和占比 >=percentage, 求满足此条件的最小k值
:param percentage, 奇异值平方和的占比的阈值
:return 满足阈值percentage的最小k值
'''
self.k = 0
#用户数据矩阵的奇异值序列的平方和
total = sum(np.square(self.S))
svss = 0 #奇异值平方和 singular values square sum
for i in range(np.shape(self.S)[0]):
svss += np.square(self.S[i])
if (svss/total) >= percentge:
self.k = i+1
break
return self.k
def _buildSD(self, k):
'''构建由奇异值组成的对角矩阵
:param k,根据奇异值开放和的占比阈值计算出来的k值
:return 由k个前奇异值组成的对角矩阵
'''
#方法1:用数组乘方法
self.SD = np.eye(self.k) * self.S[:self.k]
#方法2:用自定义方法
e = np.eye(self.k)
for i in range(self.k):
e[i,i] = self.S[i]
return self.SD
def DimReduce(self, percentage):
'''
SVD降维
:param percentage, 奇异值开方和的占比阈值
:return 降维后的用户数据矩阵
'''
#计算k值
self._calc_k(percentage)
print('\n按照奇异值开方和占比阈值percentage=%d, 求得降维的k=%d'%(percentage, self.k))
#构建由奇异值组成的对角矩阵singular values diagonal
self._buildSD(self.k)
k,U,SD,VT = self.k,self.U, self.SD, self.VT
#按照svd分解公式对用户数据矩阵进行降维,得到降维压缩后的数据矩阵
print('\n降维前的U,S,VT依次为:')
print(np.shape(U), 'U:\n', U)
print(np.shape(self.S), 'S:\n', self.S)
print(np.shape(VT), 'VT:\n', VT)
print('\n降维后的U,SD,VT依次为:')
print(np.shape(U[:len(U),k]), 'U=U[:%d,:%d]:\n'%(len(U),k), U[:len(U), :k])
print(np.shape(SD), 'SD=SD[:%d, :%d]:\n'%(k,k), SD[:k, :k])
print(np.shape(VT[:k, :len(VT)]), 'VT=VT[:%d, :%d]:\n'%(k, len(VT)), VT[:k, :len(VT)])
a = U[:len(U), :k]
b = np.dot(SD, VT[:k, :len(VT)])
newData = np.dot(a,b)
return newData
def CSVD_manual():
##训练数据集,用户对商品的评分矩阵,行为多个用户对单个商品的评分,列为用户对每个商品的评分
data = np.array([[5, 5, 0, 5],
[5, 0, 3, 4],
[3, 4, 0, 3],
[0, 0, 5, 3],
[5, 4, 4, 5],
[5, 4, 5, 5]])
percentage = 0.9
svdor = CSVD(data)
ret = svdor.DimReduce(percentage)
print('====================================================')
print('原始用户数据矩阵:\n', data)
print('降维后的数据矩阵:\n', ret)
print('====================================================')
if __name__=='__main__':
CSVD_manual()
按照奇异值开方和占比阈值percentage=0, 求得降维的k=2
降维前的U,S,VT依次为:
(6, 6) U:
[[-0.44721867 0.53728743 0.00643789 -0.50369332 -0.38572204 -0.32982993]
[-0.35861531 -0.24605053 -0.86223083 -0.14584826 0.07797125 0.20015231]
[-0.29246336 0.40329582 0.22754042 -0.10376096 0.4360044 0.70652449]
[-0.20779151 -0.67004393 0.3950621 -0.58878098 0.02599042 0.06671744]
[-0.50993331 -0.05969518 0.10968053 0.28687443 0.59460659 -0.53714128]
[-0.53164501 -0.18870999 0.19141061 0.53413013 -0.54845844 0.24290419]]
(4,) S:
[ 17.71392084 6.39167145 3.09796097 1.32897797]
(4, 4) VT:
[[-0.57098887 -0.4274751 -0.38459931 -0.58593526]
[ 0.22279713 0.51723555 -0.82462029 -0.05319973]
[-0.67492385 0.69294472 0.2531966 -0.01403201]
[ 0.41086611 0.26374238 0.32859738 -0.80848795]]
降维后的U,SD,VT依次为:
(6,) U=U[:6,:2]:
[[-0.44721867 0.53728743]
[-0.35861531 -0.24605053]
[-0.29246336 0.40329582]
[-0.20779151 -0.67004393]
[-0.50993331 -0.05969518]
[-0.53164501 -0.18870999]]
(2, 2) SD=SD[:2, :2]:
[[ 17.71392084 0. ]
[ 0. 6.39167145]]
(2, 4) VT=VT[:2, :4]:
[[-0.57098887 -0.4274751 -0.38459931 -0.58593526]
[ 0.22279713 0.51723555 -0.82462029 -0.05319973]]
====================================================
===原始用户数据矩阵:
[[5 5 0 5]
[5 0 3 4]
[3 4 0 3]
[0 0 5 3]
[5 4 4 5]
[5 4 5 5]]
===降维后的数据矩阵:
[[ 5.28849359 5.16272812 0.21491237 4.45908018]
[ 3.27680994 1.90208543 3.74001972 3.80580978]
[ 3.53241827 3.54790444 -0.13316888 2.89840405]
[ 1.14752376 -0.64171368 4.94723586 2.3845504 ]
[ 5.07268706 3.66399535 3.78868965 5.31300375]
[ 5.10856595 3.40187905 4.6166049 5.58222363]]
====================================================
参考文献:
[1][机器学习笔记]奇异值分解SVD简介及其在推荐系统中的简单应用
[2]【机器学习】推荐系统、SVD分解降维
[3]SVD在推荐系统中的应用
[4]SVD Recommendation System in Ruby
[5]矩阵特征值分解与奇异值分解含义解析及应用
[6]We Recommend a Singular Value Decomposition
(end)