1.SVD降維的基本原理
SVD降維的基本原理,可以詳見以前文章:https://blog.csdn.net/u012421852/article/details/80433463
2.降維示例展示
降維的理論以及和意義不再贅述,此處僅僅給出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降維是一種數據的有損壓縮.
3.Python代碼實現(Release版本)
3.1代碼
此代碼是降維應用實例的精簡代碼,如果是理解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()
3.2運行結果
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]]
====================================================
4.Python代碼實現(debug版本)
4.1代碼
注:此代碼爲展示示例的數學求解過程的代碼,不是精簡代碼
# -*- 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()
4.2運行結果
按照奇異值開方和佔比閾值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簡介及其在推薦系統中的簡單應用
[3]SVD在推薦系統中的應用
[4]SVD Recommendation System in Ruby
[6]We Recommend a Singular Value Decomposition
(end)