乐趣区

关于python:NumPy之多维数组中的线性代数

简介

本文将会以图表的模式为大家解说怎么在 NumPy 中进行多维数据的线性代数运算。

多维数据的线性代数通常被用在图像处理的图形变换中,本文将会应用一个图像的例子进行阐明。

图形加载和阐明

相熟色彩的敌人应该都晓得,一个色彩能够用 R,G,B 来示意,如果更高级一点,那么还有一个 A 示意透明度。通常咱们用一个四个属性的数组来示意。

对于一个二维的图像来说,其分辨率能够看做是一个 X * Y 的矩阵,矩阵中的每个点的色彩都能够用(R,G,B)来示意。

有了下面的常识,咱们就能够对图像的色彩进行合成了。

首先须要加载一个图像,咱们应用 imageio.imread 办法来加载一个本地图像,如下所示:

import imageio
img=imageio.imread('img.png')
print(type(img))

下面的代码从本地读取图片到 img 对象中,应用 type 能够查看 img 的类型,从运行后果,咱们能够看到 img 的类型是一个数组。

class 'imageio.core.util.Array'

通过 img.shape 能够失去 img 是一个 (80, 170, 4) 的三维数组,也就是说这个图像的分辨率是 80*170,每个像素是一个(R,B,G,A)的数组。

最初将图像画进去如下所示:

import matplotlib.pyplot as plt
plt.imshow(img)

图形的灰度

对于三维数组来说,咱们能够别离失去三种色彩的数组如下所示:

red_array = img_array[:, :, 0]
green_array = img_array[:, :, 1]
blue_array = img_array[:, :, 2]

有了三个色彩之后咱们能够应用上面的公式对其进行灰度变换:

Y=0.2126R + 0.7152G + 0.0722B

上图中 Y 示意的是灰度。

怎么应用矩阵的乘法呢?应用 @ 就能够了:

 img_gray = img_array @ [0.2126, 0.7152, 0.0722]

当初 img 是一个 80 * 170 的矩阵。

当初应用 cmap=”gray” 作图:

plt.imshow(img_gray, cmap="gray")

能够失去上面的灰度图像:

灰度图像的压缩

灰度图像是对图像的色彩进行变换,如果要对图像进行压缩该怎么解决呢?

矩阵运算中有一个概念叫做奇怪值和特征值。

设 A 为 n 阶矩阵,若存在常数 λ 及 n 维非零向量 x,使得 Ax=λx,则称 λ 是矩阵 A 的特征值,x 是 A 属于特征值 λ 的特征向量。

一个矩阵的一组特征向量是一组正交向量。

即特征向量被施以线性变换 A 只会使向量伸长或缩短而其方向不被扭转。

特色合成(Eigendecomposition),又称谱合成(Spectral decomposition)是将矩阵合成为由其特征值和特征向量示意的矩阵之积的办法。

如果 A 是 m n 阶矩阵,q=min(m,n),A A 的 q 个非负特征值的算术平方根叫作 A 的奇怪值。

特征值合成能够不便的提取矩阵的特色,然而前提是这个矩阵是一个方阵。如果是非方阵的状况下,就须要用到奇怪值合成了。先看下奇怪值合成的定义:

$A=UΣV^T$

其中 A 是指标要合成的 m n 的矩阵,U 是一个 m m 的方阵,Σ 是一个 m n 的矩阵,其非对角线上的元素都是 0。$V^T$ 是 V 的转置,也是一个 n n 的矩阵。

奇怪值跟特征值相似,在矩阵 Σ 中也是从大到小排列,而且奇怪值的缩小特地的快,在很多状况下,前 10% 甚至 1% 的奇怪值的和就占了全副的奇怪值之和的 99% 以上了。也就是说,咱们也能够用前 r 大的奇怪值来近似形容矩阵。r 是一个远小于 m、n 的数,这样就能够进行压缩矩阵。

通过奇怪值合成,咱们能够通过更加大量的数据来近似代替原矩阵。

要想应用奇怪值合成 svd 能够间接调用 linalg.svd 如下所示:

U, s, Vt = linalg.svd(img_gray)

其中 U 是一个 m m 矩阵,Vt 是一个 n n 矩阵。

在上述的图像中,U 是一个 (80, 80) 的矩阵,而 Vt 是一个(170, 170) 的矩阵。而 s 是一个 80 的数组,s 蕴含了 img 中的奇怪值。

如果将 s 用图像来示意,咱们能够看到大部分的奇怪值都集中在前的局部:

这也就意味着,咱们能够取 s 中后面的局部值来进行图像的重构。

应用 s 对图像进行重构,须要将 s 还原成 80 * 170 的矩阵:

# 重建
import numpy as np
Sigma = np.zeros((80, 170))
for i in range(80):
    Sigma[i, i] = s[i]

应用 U @ Sigma @ Vt 即可重建原来的矩阵,能够通过计算 linalg.norm 来比拟一下原矩阵和重建的矩阵之间的差别。

linalg.norm(img_gray - U @ Sigma @ Vt)

或者应用 np.allclose 来比拟两个矩阵的不同:

np.allclose(img_gray, U @ Sigma @ Vt)

或者只取 s 数组的前 10 个元素,进行从新绘图,比拟一下和原图的区别:

k = 10
approx = U @ Sigma[:, :k] @ Vt[:k, :]
plt.imshow(approx, cmap="gray")

能够看到,差别并不是很大:

原始图像的压缩

上一节咱们讲到了如何进行灰度图像的压缩,那么如何对原始图像进行压缩呢?

同样能够应用 linalg.svd 对矩阵进行合成。

然而在应用前须要进行一些解决,因为原始图像的 img_array 是一个 (80, 170, 3) 的矩阵 – 这里咱们将透明度去掉了,只保留了 R,B,G 三个属性。

在进行转换之前,咱们须要把不须要变换的轴放到最后面,也就是说将 index=2,换到 index= 0 的地位,而后进行 svd 操作:

img_array_transposed = np.transpose(img_array, (2, 0, 1))
print(img_array_transposed.shape)

U, s, Vt = linalg.svd(img_array_transposed)
print(U.shape, s.shape, Vt.shape)

同样的,当初 s 是一个 (3, 80) 的矩阵,还是少了一维,如果重建图像,须要将其进行填充和解决,最初将重建的图像输入:

Sigma = np.zeros((3, 80, 170))

for j in range(3):
    np.fill_diagonal(Sigma[j, :, :], s[j, :])

reconstructed = U @ Sigma @ Vt
print(reconstructed.shape)

plt.imshow(np.transpose(reconstructed, (1, 2, 0)))

当然,也能够抉择后面的 K 个特征值对图像进行压缩:

approx_img = U @ Sigma[..., :k] @ Vt[..., :k, :]
print(approx_img.shape)
plt.imshow(np.transpose(approx_img, (1, 2, 0)))

从新构建的图像如下:

比照能够发现,尽管损失了局部精度,然而图像还是能够分辨的。

总结

图像的变动会波及到很多线性运算,大家能够以此文为例,认真钻研。

本文已收录于 http://www.flydean.com/08-python-numpy-linear-algebra/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版