奇异值分解, 也就是大家常说的SVD, 它既有背后的理论, 也有实际的实现方法。
奇异值分解这个说法呢, 就是指的英文里的 Value , 还有那个 SVD 这个东西, 它是一种非常重要的矩阵分解的方法。
那种方法, 其实就可以看作是在任意矩阵上对对称方正在进行推广之后所得到的结果, 而且这个方法在机器学习领域里面确实是占有着一个非常重要的地位。
咱们先来把SVD这个理论给讲清楚一点, 接着再去动手实现一下SVD, 最后再把SVD应用到图像压缩上面去。
1、奇异值分解(SVD):
存在一种分解方法是可以被做出来的,使得这个 A 它是具有一个 m 乘以 n 大小的实数矩阵这一事实能够成立。
U和V它们两个都是叫正交矩阵的, 意思就是。
Σ 是一个非负实对角矩阵, U 的列叫做 A 的左奇异向量, V 的列叫做 A 的右奇异向量, Σ 的对角线上的值叫做 A 的奇异值, 关于这三个矩阵的求解步骤, 如下所示:
1)U矩阵当中的列向量, 它们是由AAT的特征向量来构成的, 而且这些特征向量本身还是单位列向量。
2)这个矩阵的列向量, 是那个给定的特征向量的转置再乘以A之后的乘积, 并且这些被挑选出来的特征向量, 本身都是已经规范化了长度的单位向量。
3)那个被称为<Σ>的东西, 里面的对角线上的那些数值, 它们是来自于< AAT >或者是< ATA >的特征值的平方根。并且这些数值是按照从大到小这样的顺序来排列的。那个值变得越大, 我们就可以把这种情况理解为它越重要。
比如说, 要是假设。
先开始动手去算一算下面这个矩阵的特征值, 还有它对应的特征向量, 最后的结果是得出了四个特征值, 它们是90.这个数, 接着是0.这个数, 再后来是一个7减去负19所等于的数, 最后一个是0这个数字。
先对特征向量进行单位化处理, 接着按照特征值由大到小排序排列的顺序, 把处理好的特征向量放到U矩阵每一列当中。
用一样的办法去把下面这个矩阵的特征值还有那个特征向量都给算出来, 最后拿到手的成果就是有两个特征值嘛, 分别是0和90这两个数。
先把特征向量弄成单位化的状态, 然后, 按照特征值从大到小的顺序, 把排好序的特征向量一行行地放在一块儿, 这么做的话, 最后得到就是那个V矩阵了:
而 Σ 则是一个对角矩阵, 这个对角矩阵里的元素, 全是由特征值的平方根所构成的。
所以:
2、使用实现奇异值分解, 并且把它应用到了图像压缩这个方面里面。
1)咱们先一步, 就是去读取那一张图片。
括号内写着一千乘以六百八十五的坐标信息, 紧接着是代码解释器路径声明与字符编码格式说明, 并且调用了图像处理库中的图像模块。
os numpy as np as mpl .
把 plt 这个变量拿来当作导入的代号, 如果它是等于空字符串的这种状态的情况下, 那么就使用 mpl 这样的写法。
'font.sans-serif'
u''
mpl.
'axes.'
把变量A赋值为那个等于假的值。然后呢。把这个变量赋值成打开文件girl.jpg的图像对象的那个代码结果的样子。接着再写个小写的a。
np.array(A) #转换成矩阵
2)之后可以使用numpy这个库, 把彩色图像的3个通道拿出来, 分别做SVD分解这一步操作。
因为它是彩色的图像, 所以具有三个通道。变量a的最内部数组包含三个数值, 这些数值分别表示红色、绿色和蓝色, 其目的是为了表现一个像素点。至于u_r与v_r这两个变量则等于。
np..svd(a
:, :, 0

获取奇异矩阵 a, 进行奇异值分解, 返回的结果分别存入 u_g 和 v_g。
:, :, 1
) u_b,
, v_b = np..svd(a
:, :, 2
3)然后接着就可以根据实际的需要去压缩图像, 这里所说的压缩图像, 意思是把分解得来的那三个矩阵里面的某些数据给丢弃掉, 用到的奇异值的数量如果越少的话, 那么所达成的压缩效果也就是更加显著的, 接下来看一看在不同等级的压缩状况之下所形成的重构图像的清晰程度的表现是什么样的。
plt.( = 'w', = (10, 10)) # 奇异值个数依次取:1,2,...,12。来看看一下效果
对于从1到12这个范围内的每个数字k, 当K等于12的时候, 我们输出k的值, 然后设定R为包含u_r、v_r以及k这些内容的元素集合, 接着再处理G的部分。
(u_g, , v_g, k) B = (u_b, , v_b, k) I =
np.stack((R, G, B), axis = 2) # 将图片重构后的显示出来 plt.(3, 4, k) plt.(I)
plt.axis('off') plt.title(u'奇异值个数:%d' % k) plt.(u'SVD与图像分解', =
- plt.(0.1, rect = (0, 0, 1, 0.92)) plt.show()
在这当中, 函数的定义情况如下所示: def (u, sigma, v, k):, 代码内容为 m = len(u), 然后是 n = len(v), 最后是一个 a =。
np.zeros((m, n))这行代码的意思是创建一个大小为m乘以n的数组, 全部元素都是零, 这里用来重构图像, 而a等于u和另一个向量的点积。
:, :k
, np.diag(sigma
:k
)).dot(v
:k, :
上述语句等价于: # for i in range(k): # ui = u
:, i
这个向量具有一个形状, 它的值是等于一的状态。
.(1, n) # a += sigma
* np.dot(ui, vi) a
a < 0
= 0 a
a > 255
255 np.rint(a).('uint8')
在最后的阶段, 我们对所实现的效果进行观察和检查。
可以看到, 面上的图像随着奇异值个数的增多快速地变得清晰起来。如果在进行图像分解的时候, 只是选择保留前面的20个奇异数据的话, 那么U和V这两个矩阵就仅仅只需要使用到20个向量单位了, 整个过程中一共只需要保存的数值量就是3乘以1000再乘以20。
计算一下所需的数据量, 通过20加上20乘685的结果, 再乘以3, 等于3乘33720个这样的数据点就足够了, 可是原来的图片居然得存储3接着是左括号1000乘685右括号等于3乘一些数, 这样算下来压缩的比例接近20倍之多!