6. SciPy之svd典型应用

上一章就scipy的方程求解介绍了很多的分解,通过分解求值变得比较简单,最后介绍了一下svd分解的基本使用,本章就svd的应用举两个例子:图像压缩和信号去噪,展示一下svd的现实应用。

6.1 svd实现图像的压缩的原理

任意形状的矩阵$A$经过svd分解会得到$U$,$\Sigma$,$V^T$(V的转置)三个矩阵,其中$U$是一个$M\times M$的方阵,被称为左奇异向量,方阵里面的向量是正交的;$\Sigma$是一个$M\times N$的对角矩阵,除了对角线的元素其他都是0,对角线上的值称为奇异值;$V^T$(V的转置)是一个$N \times N$的矩阵,被称为右奇异向量,方阵里面的向量也都是正交的。

$$ A_{m \times n} = U_{m \times m}\Sigma_{m \times n}V_{n \times n}^T $$ 可以计算$\Sigma$前x个奇异值的平方和占所有奇异值的平方和的比例,如果大于90%,我们就选这x个奇异值重构矩阵(剩余的数据代表的可能是噪声,无用数据)。 $$ x = \frac{\sum_{i=1}^{x}\sigma_i^2}{\sum_{i=1}^{n}\sigma_i^2} > 90\% $$ 那么重构的A'为: $$ A_{m \times n} = U_{m \times m}\Sigma_{m \times n}V_{n \times n}^T \approx U_{m \times x}\Sigma_{x \times x}V_{x \times n}^T $$ 以上的理论用代码表示如下:

#coding:utf-8
from scipy.linalg import svd
import matplotlib.pyplot as plt
import numpy as np
import numpy
a = [[0, 0, 0, 2, 2],
    [0, 0, 0, 3, 3],
    [0, 0, 0, 1, 1],
    [1, 1, 1, 0, 0],
    [2, 2, 2, 0, 0],
    [5, 5, 5, 0, 0],
    [1, 1, 1, 0, 0]]
A = np.array(a)
print A
U, s, Vh = svd(A)
print s.shape,s
# 选x
for i in range(s.shape[0]):
    percent = np.sum(np.square(s[:i+1])) * 1.0 / np.sum(np.square(s))
    if percent >= 0.9:
        print "x", i + 1
        break
# 重构A
for i in range(1, s.shape[0]):
    A = numpy.dot(U[:,0:i],numpy.dot(numpy.diag(s[0:i]),Vh[0:i,:]))
    print np.array(A, dtype=np.uint8),"#:", i

程序执行结果:

[[0 0 0 2 2]
 [0 0 0 3 3]
 [0 0 0 1 1]
 [1 1 1 0 0]
 [2 2 2 0 0]
 [5 5 5 0 0]
 [1 1 1 0 0]]
(5,) [  9.64365076e+00   5.29150262e+00   8.36478329e-16   6.91811207e-17
   3.04963694e-34]
x 2
[[0 0 0 0 0]
 [0 0 0 0 0]
 [0 0 0 0 0]
 [1 0 1 0 0]
 [2 1 2 0 0]
 [5 4 5 0 0]
 [1 0 1 0 0]] #: 1
[[0 0 0 1 1]
 [0 0 0 2 2]
 [0 0 0 1 1]
 [1 0 1 0 0]
 [2 1 2 0 0]
 [5 4 5 0 0]
 [1 0 1 0 0]] #: 2
[[0 0 0 1 1]
 [0 0 0 2 2]
 [0 0 0 1 1]
 [1 0 1 0 0]
 [2 1 2 0 0]
 [5 4 5 0 0]
 [1 0 1 0 0]] #: 3
[[0 0 0 1 1]
 [0 0 0 2 2]
 [0 0 0 1 1]
 [1 0 1 0 0]
 [2 1 2 0 0]
 [5 4 5 0 0]
 [1 0 1 0 0]] #: 4

从选择x那段代码的执行结果可以看出A矩阵svd分解出来的s前2个奇异特征值平方和大于总奇异值平和的90%。 从重构A下面的代码可以看出x = 2 的重构矩阵已经非常接近A矩阵了,可以发现原始数据中非零的部分都完整的保存了下来,说明选择的奇异值几乎能较完整地保存了所有有用信息。

6.2 svd实现对图像的压缩

有了上边的理论基础,接下来可以用一副具体的图来展示一下svd实现对图像的压缩。

import scipy.misc
from scipy.linalg import svd
import matplotlib.pyplot as plt
import numpy as np
import numpy
img = scipy.misc.face()[:,:,0]

print img.shape,type(img)
img = np.matrix(img)
U,s,Vh=svd(img)
plt.gray()

plt.subplot(221,aspect='equal')
plt.title("orignal")
plt.imshow(img)
plt.imsave('org.png', img)

A = numpy.dot(U[:,0:10],numpy.dot(numpy.diag(s[0:10]),Vh[0:10,:]))
plt.subplot(222,aspect='equal')
plt.title(":10")
plt.imshow(A)
plt.imsave('a10.png', A)

A = numpy.dot(U[:,0:50],numpy.dot(numpy.diag(s[0:50]),Vh[0:50,:]))
plt.subplot(223,aspect='equal')
plt.title(":50")
plt.imshow(A)
plt.imsave('a50.png', A)

A = numpy.dot(U[:,0:100],numpy.dot(numpy.diag(s[0:100]),Vh[0:100,:]))
plt.subplot(224,aspect='equal')
plt.title(":100")
plt.imshow(A)
plt.imsave('a100.png', A)

plt.show()

程序执行结果:

从上图可以看出选不同的奇异值个数得到的图像的清晰度是变化的。第一副图是原图的黑白显示,第2、3、4分别取了10、50、100个奇异值后重构了图像A。 下图的各个图像文件的大小。

说明选取奇异值的个数的不同,重构的图像文件大小不同,清晰度不同,正确的选择x决定图像的质量。

6.3 svd实现信号去噪

本例子先做一个sin函数的数据集a,a是由标准正弦波numpy.sin()加上噪声ss = np.random.normal(mu, sigma, 400)后形变(reshape)成一个矩阵而最终构成的a = (np.sin(x) + ss).reshape(20, 20)。 接下来就是调用scipy.linalg模块下的svd对a进行svd分解U,s,Vh = svd(a),得到U,s,Vh三个值。 s是奇异值主对角非0的对角阵,记录着各个奇异值,选择不同个数的奇异值A = numpy.dot(U[:,0:2],numpy.dot(numpy.diag(s[0:2]),Vh[0:2,:]))A = numpy.dot(U[:,0:1],numpy.dot(numpy.diag(s[0:1]),Vh[0:1,:]))可以重构矩阵a为A,从而实现信号去噪处理。

import matplotlib.pylab as plt
import numpy as np
import numpy
from scipy.linalg import svd
x = np.linspace(-np.pi, np.pi, 400)
mu, sigma = 0, 0.05
ss = np.random.normal(mu, sigma, 400)
a = (np.sin(x) + ss).reshape(20, 20)

U,s,Vh = svd(a)
print s
A = numpy.dot(U[:,0:2],numpy.dot(numpy.diag(s[0:2]),Vh[0:2,:]))
aa = A.reshape((400,))

plt.subplot(221,aspect='equal')
plt.plot(x, np.sin(x))
plt.title("sin")

plt.subplot(222,aspect='equal')
plt.plot(x, np.sin(x) + ss)
plt.title("sin with noise")

plt.subplot(223,aspect='equal')
plt.title("sin remove noise x = 2")
plt.plot(x, aa)

A = numpy.dot(U[:,0:1],numpy.dot(numpy.diag(s[0:1]),Vh[0:1,:]))
aa = A.reshape((400,))
plt.subplot(224,aspect='equal')
plt.title("sin remove noise x = 1")
plt.plot(x, aa)

plt.show()

程序执行结果: 第一副图是纯sin波形,第2副图是加了噪声的波形,第三副选择2个奇异值重构A结果很接近标准sin波形实现了去噪,第四副图是选了1个奇异值重构A的结果。

6.4 总结

svd分解在机器学习和深度学习以及计算机视觉等领域都有很多涉及,需明白基础不牢靠,学习机器学习也就是浮于表面。