一、引入
主成分分析PCA、潜在语义分析都会用到SVD
不要求A矩阵是方阵,SVD是线性代数中相似对角化的延伸
任意mn的矩阵都可以用三个矩阵相乘的形式表示
分别是m阶正交矩阵、由降序排列的非负的对角线元素构成的mn矩形对角矩阵、n阶正交矩阵
矩阵奇异值分解一定存在但不唯一
SVD可以看作矩阵压缩数据的一种方法,这种近似是在平方损失意义下的最优近似
二、SVD的定义、性质
定义
A=UΣVT
满足以下条件
UUT=E;
VVT=E;
Σ=diag(σ1,σ2,…σp),其中σ1>=σ2>=…>=σp>=0;p=min{m,n}
例题
奇异值分解一定存在
设定m>=n,分三步完成证明
1、确定v和Σ
①Σ
ATA的特征值都是非负的||Ax||2=xTATAx=λxTx=λ||x||2
所以λ=|Ax||2/||x||2>=0
假设正交矩阵V的列的排列使对应的特征值降序排列:λ1>=λ2>=λ3>=…>=λn
计算矩阵A的奇异值σj=√λj, j=12,…n
说明:ATA的特征值与A的特征值是平方关系
ATA=(UΣVT)T(UΣVT)=V(ΣTΣ)VT;
AAT=(UΣVT)(UΣVT)T=U(ΣΣT)UT;
V的列向量是ATA的特征向量,U的列向量是AAT的特征向量,Σ的奇异值是ATA和AAT的特征值的平方根,ATA和AAT的特征值相同
R(A)=r,R(ATA)=r,由于ATA是对称矩阵,它的秩等于正的特征值的个数
所以剩下的n-r个特征值为0。所以σ同理
Σ= ②v
V1=[v1,v2,…vr];V2=[vr+1,…vn]
其中V1对应的是ATA的正特征值对用的特征向量, 其中V2对应的是ATA的0特征值对用的特征向量V=[V1,V2]
V2
ATAx=0(V2的列向量构成了ATA的零空间N(ATA)=N(A),所以V2的列向量构成A的零空间的一组标准正交基)
Ax=0(正交矩阵的转置乘以正交矩阵等于单位矩阵)
③U
uj=1/σjAvj,j=1,2…r
U1=[u1,u2,…ur]
则AV1=U1Σ1
U2与V~2同理
紧奇异值分解和截断奇异值分解
紧奇异值分解(无损压缩,与原始矩阵秩相同)
截断奇异值分解(有损压缩,小于原始矩阵的秩):满足了秩的要求以后其余元素都变成零
几何解释
被分成的三个矩阵可以解释为,一个坐标轴的旋转或反射变换、一个坐标轴的缩放变换、另一个坐标轴的旋转或反射变换。
三、SVD算法
计算过程
①计算ATA的特征值和特征向量,特征值开方从大到小排序即为Σ
注意:因为A不是方阵,所以在构造的时候,要将格式修改为m*n的形式,缺的位置补零。
②求n阶正交矩阵V:特征向量单位化
③求m阶正交矩阵U:
uj=1/σjAvj (j=1,2,3…,r) U1=[u1,u2,u3,…ur]
求AT的零空间的一组标准正交基{ur+1,ur+2,…um}
(ATx=0,求出特征向量以后记得标准化)
U=[U1,U2]
④得到奇异值分解
四、SVD与矩阵近似
弗罗贝尼乌斯范数:是向量L2范数的直接推广,对应机器学习中的平方损失函数
矩阵的外积展开式
若A的秩为n,Ak的秩为k, 且Ak是秩为k的矩阵中在弗罗贝尼乌斯范数意义下A的最优近似矩阵。那么Ak就是A的截断奇异值分解。
通常奇异值σi递减的很快,所以k取很小值时,AK也可以对A有很好的近似。
五、python实现
import numpy as np
a = np.random.randint(-10,10,(4, 3)).astype(float)
print(a)
print("-----------------")
u, sigma, vT = np.linalg.svd(a)
print(u)
print("-----------------")
print(sigma)
print("-----------------")
print(vT)
print("-----------------")# 将sigma 转成矩阵
SigmaMat = np.zeros((4,3))
SigmaMat[:3, :3] = np.diag(sigma)
print(SigmaMat)
print("------验证-------")
a_ = np.dot(u, np.dot(SigmaMat, vT))
print(a_)
六、应用
推荐算法
import numpy as np
import random
class SVD:
def __init__(self,mat,K=20):
self.mat=np.array(mat)
self.K=K
self.bi={}
self.bu={}
self.qi={}
self.pu={}
self.avg=np.mean(self.mat[:,2])
for i in range(self.mat.shape[0]):
uid=self.mat[i,0]
iid=self.mat[i,1]
self.bi.setdefault(iid,0)
self.bu.setdefault(uid,0)
self.qi.setdefault(iid,np.random.random((self.K,1))/10*np.sqrt(self.K))
self.pu.setdefault(uid,np.random.random((self.K,1))/10*np.sqrt(self.K))
def predict(self,uid,iid): #预测评分的函数
#setdefault的作用是当该用户或者物品未出现过时,新建它的bi,bu,qi,pu,并设置初始值为0
self.bi.setdefault(iid,0)
self.bu.setdefault(uid,0)
self.qi.setdefault(iid,np.zeros((self.K,1)))
self.pu.setdefault(uid,np.zeros((self.K,1)))
rating=self.avg+self.bi[iid]+self.bu[uid]+np.sum(self.qi[iid]*self.pu[uid]) #预测评分公式
#由于评分范围在1到5,所以当分数大于5或小于1时,返回5,1.
if rating>5:
rating=5
if rating<1:
rating=1
return rating
def train(self,steps=30,gamma=0.04,Lambda=0.15): #训练函数,step为迭代次数。
print('train data size',self.mat.shape)
for step in range(steps):
print('step',step+1,'is running')
KK=np.random.permutation(self.mat.shape[0]) #随机梯度下降算法,kk为对矩阵进行随机洗牌
rmse=0.0
for i in range(self.mat.shape[0]):
j=KK[i]
uid=self.mat[j,0]
iid=self.mat[j,1]
rating=self.mat[j,2]
eui=rating-self.predict(uid, iid)
rmse+=eui**2
self.bu[uid]+=gamma*(eui-Lambda*self.bu[uid])
self.bi[iid]+=gamma*(eui-Lambda*self.bi[iid])
tmp=self.qi[iid]
self.qi[iid]+=gamma*(eui*self.pu[uid]-Lambda*self.qi[iid])
self.pu[uid]+=gamma*(eui*tmp-Lambda*self.pu[uid])
gamma=0.93*gamma
print('rmse is',np.sqrt(rmse/self.mat.shape[0]))
def test(self,test_data): #gamma以0.93的学习率递减
test_data=np.array(test_data)
print('test data size',test_data.shape)
rmse=0.0
for i in range(test_data.shape[0]):
uid=test_data[i,0]
iid=test_data[i,1]
rating=test_data[i,2]
eui=rating-self.predict(uid, iid)
rmse+=eui**2
print('rmse of test data is',np.sqrt(rmse/test_data.shape[0]))
def getData(): #获取训练集和测试集的函数
import re
f=open('C:/Users/xuwei/Desktop/data.txt','r')
lines=f.readlines()
f.close()
data=[]
for line in lines:
list=re.split('\t|\n',line)
if int(list[2]) !=0: #提出评分0的数据,这部分是用户评论了但是没有评分的
data.append([int(i) for i in list[:3]])
random.shuffle(data)
train_data=data[:int(len(data)*7/10)]
test_data=data[int(len(data)*7/10):]
print('load data finished')
print('total data ',len(data))
return train_data,test_data
train_data,test_data=getData()
a=SVD(train_data,30)
a.train()
a.test(test_data)
代码链接:https://blog.youkuaiyun.com/akiyamamio11/article/details/79042688