SVD适用于对任意矩阵进行矩阵分解,是一种重要的矩阵分解方法。
SVD对应的公式为: A m × n = U m × n S m × n V m × n T { A_{m \times n}=U_{m \times n } S_{m \times n } V^T_{m \times n}} Am×n=Um×nSm×nVm×nT ,简记为: A = U S V T { A=USV^T} A=USVT,其中 U {U} U 和 V {V} V 是正交矩阵(酉矩阵),即满足 U T U = E m × m {U^TU=E_{m \times m}} UTU=Em×m , V T V = E n × n {V^TV=E_{n \times n}} VTV=En×n。
U {U} U矩阵:左奇异矩阵,列由 A A T {AA^T} AAT 的特征向量组成,且特征向量为单位向量;
S {S} S矩阵:奇异值矩阵,对角元素来源于 A A T {AA^T} AAT 或 A T A {A^TA} ATA 的特征值的平方根,并且按降序排列,值越大可以理解为越重要;
V {V} V矩阵:右奇异矩阵,列由 A T A {A^TA} ATA 的特征向量组成,且特征向量为单位向量;
import numpy as np
from numpy import linalg as la
A = np.array([[1,5,7,6,1],[2,1,10,4,4],[3,6,7,5,2]])
U, S, VT = la.svd(A)
Sigma = np.zeros(np.shape(A))
Sigma[:len(S),:len(S)] = np.diag(S) # 生成奇异值矩阵,该矩阵的对角元素为奇异值
print('左奇异值矩阵:\n', U)
print('奇异值:', S)
print('奇异值矩阵:\n', Sigma)
print('右奇异值矩阵的转置:\n', VT)
print('--------------------------------------')
# 利用SVD重构矩阵
B = U.dot(Sigma.dot(VT))
print('原矩阵A:\n', A)
print('重构后的矩阵B:\n', B)
print('原矩阵A与重构后的矩阵B是否相同', np.allclose(A,B))
输出为:
左奇异值矩阵:
[[-0.55572489 0.40548161 -0.72577856]
[-0.59283199 -0.80531618 0.00401031]
[-0.58285511 0.43249337 0.68791671]]
奇异值: [18.53581747 5.0056557 1.83490648]
奇异值矩阵:
[[18.53581747 0. 0. 0. 0. ]
[ 0. 5.0056557 0. 0. 0. ]
[ 0. 0. 1.83490648 0. 0. ]]
右奇异值矩阵的转置:
[[-0.18828164 -0.37055755 -0.74981208 -0.46504304 -0.22080294]
[ 0.01844501 0.76254787 -0.4369731 0.27450785 -0.38971845]
[ 0.73354812 0.27392013 -0.12258381 -0.48996859 0.36301365]
[ 0.36052404 -0.34595041 -0.43411102 0.6833004 0.30820273]
[-0.5441869 0.2940985 -0.20822387 -0.0375734 0.7567019 ]]
--------------------------------------
原矩阵A:
[[ 1 5 7 6 1]
[ 2 1 10 4 4]
[ 3 6 7 5 2]]
重构后的矩阵B:
[[ 1. 5. 7. 6. 1.]
[ 2. 1. 10. 4. 4.]
[ 3. 6. 7. 5. 2.]]
原矩阵A与重构后的矩阵B是否相同 True
实现对原图像进行SVD,使用更少的像素表示原图像,实现图像压缩。
(1)读取图像,将图片像素分解成三个矩阵,分别为RGB;
(2)对RGB三个通道分别进行SVD并压缩:先进行SVD得到对应的奇异值,然后按照一定标准进行奇异值筛选,即保留 S {S} S 矩阵的前 k {k} k 个,有两种方式可以进行筛选,一种是取整体奇异值的百分比,另一种是取奇异值之和的百分比,然后进行重构,得到压缩后的图像;
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
'''
函数功能:SVD并还原压缩后的数据。
参数说明:data代表原始矩阵percent代表奇异值总和的百分比
'''
def get_approx_SVD1(data,percent):
U, s, VT = np.linalg.svd(data) # 进行SVD
Sigma = np.zeros(np.shape(data))
Sigma[:len(s),:len(s)] = np.diag(s)
count = (int)(sum(s))*percent
k = -1 # k是奇异值总和的百分比的个数
curSum = 0 # 初值为第一个奇异值
while curSum <= count :
k += 1
curSum += s[k]
D = U[:,:k].dot(Sigma[:k,:k].dot(VT[:k,:])) #SVD还原后的数据
D[D<0] = 0
D[D>255] = 255
return np.rint(D).astype("uint8")
'''
函数功能:SVD并还原压缩后的数据。
参数说明:data代表原始矩阵percent代表奇异值个数的百分比
'''
def get_approx_SVD2(data,percent):
U, s, VT = np.linalg.svd(data) # 进行SVD
Sigma = np.zeros(np.shape(data))
Sigma[:len(s),:len(s)]=np.diag(s) # 获得奇异值矩阵
k = (int)(percent*len(s)) # k是奇异值个数的百分比的个数
D = U[:,:k].dot(Sigma[:k,:k].dot(VT[:k,:])) # SVD还原后的数据
D[D < 0] = 0
D[D > 255] = 255
return np.rint(D).astype("uint8")
'''
函数功能:导入图像,进行SVD压缩,并重构图像
参数说明:filename代表文件名,p代表百分比,
get_approx_SVD代表调用的SVD筛选方法
'''
def rebuild_img(filename,p,get_approx_SVD):
img = Image.open(filename, 'r') # 打开文件
a = np.array(img) # 获得色素值
R0 = a[:, :, 0] # 获得红色的色素值
G0 = a[:, :, 1] # 获得绿色的色素值
B0 = a[:, :, 2] # 获得蓝色的色素值
R = get_approx_SVD(R0,p) # 对红色进行SVD还原
G = get_approx_SVD(G0,p) # 对绿色进行SVD还原
B = get_approx_SVD(B0,p) # 对蓝色进行SVD还原
I = np.stack((R, G, B), 2)
img_svd = Image.fromarray(I)
return img_svd
filename="qin.jpg"
img = Image.open(filename).convert('RGB')
img_svd1 = rebuild_img(filename,0.2,get_approx_SVD1)
img_svd2 = rebuild_img(filename,0.4,get_approx_SVD1)
img_svd3 = rebuild_img(filename,0.6,get_approx_SVD1)
img_svd4 = rebuild_img(filename,0.8,get_approx_SVD1)
img_svd5 = rebuild_img(filename,1.0,get_approx_SVD1)
plt.figure(1)
plt.subplot(2,3,1)
plt.imshow(img)
plt.axis('off'),plt.title('原图')
plt.subplot(2,3,2)
plt.imshow(img_svd1)
plt.axis('off'),plt.title('p=0.2')
plt.subplot(2,3,3)
plt.imshow(img_svd2)
plt.axis('off'),plt.title('p=0.4')
plt.subplot(2,3,4)
plt.imshow(img_svd3)
plt.axis('off'),plt.title('p=0.6')
plt.subplot(2,3,5)
plt.imshow(img_svd4)
plt.axis('off'),plt.title('p=0.8')
plt.subplot(2,3,6)
plt.imshow(img_svd5)
plt.axis('off'),plt.title('p=1.0')
输出为:
利用get_approx_SVD2,同样可以得到:
由此可知,在相同的百分比下,按奇异值个数重构,可以保留更多的信息。
参考资料
人工智能数学基础(北京大学出版社)