3

奇异值分解和图像压缩

 2 years ago
source link: https://cosx.org/2014/02/svd-and-image-compression/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

奇异值分解和图像压缩

关键词:SVD; 分解; 压缩; 图像; 奇异值分解; 矩阵

【2.18 更新】:楠神写了一个非常 gelivable 的 Shiny 应用,用来动态展示图片压缩的效果随 k 的变化情况。谢大大把这个应用放到了 RStudio 的服务器上,大家可以点进去玩玩看了。

===================== 代表正义的分割线 =====================

今天我们来讲讲奇异值分解和它的一些有意思的应用。奇异值分解是一个非常,非常,非常大的话题,它的英文是 Singular Value Decomposition,一般简称为 SVD。下面先给出它大概的意思:

对于任意一个m×n的矩阵M,不妨假设m>n,它可以被分解为

M=UDVT

  • U 是一个m×n的矩阵,满足UTU=In,In 是n×n的单位阵
  • V 是一个n×n的矩阵,满足VTV=In
  • D 是一个n×n的对角矩阵,所有的元素都非负

先别急,我看到这个定义的时候和你一样晕,感觉信息量有点大。事实上,上面这短短的三条可以引发出 SVD 许多重要的性质,而我们今天要介绍的也只是其中的一部分而已。

前面的表达式M=UDVT可以用一种更容易理解的方式表达出来。如果我们把矩阵U用它的列向量表示出来,可以写成

U=(u1,u2,…,un)

其中每一个ui被称为M的左奇异向量。类似地,对于V,有

V=(v1,v2,…,vn)

它们被称为右奇异向量。再然后,假设矩阵D的对角线元素为di(它们被称为M的奇异值)并按降序排列,那么M就可以表达为

M=d1u1vT1+d2u2vT2+⋯+dnunvTn=n∑i=1diuivTi=n∑i=1Ai

其中Ai=diuivTi是一个m×n的矩阵。换句话说,我们把原来的矩阵M表达成了n个矩阵的和。

这个式子有什么用呢?注意到,我们假定di是按降序排列的,它在某种程度上反映了对应项Ai在M中的 “贡献”。di越大,说明对应的 Ai在M的分解中占据的比重也越大。所以一个很自然的想法是,我们是不是可以提取出Ai中那些对M贡献最大的项,把它们的和作为对 M的近似?也就是说,如果令

Mk=k∑i=1Ai

那么我们是否可以用Mk来对Mn≡M进行近似?

答案是肯定的,不过等一下,这个想法好像似曾相识?对了,多元统计分析中经典的主成分分析就是这样做的。在主成分分析中,我们把数据整体的变异分解成若干个主成分之和,然后保留方差最大的若干个主成分,而舍弃那些方差较小的。事实上,主成分分析就是对数据的协方差矩阵进行了类似的分解(特征值分解),但这种分解只适用于对称的矩阵,而 SVD 则是对任意大小和形状的矩阵都成立。(SVD 和特征值分解有着非常紧密的联系,此为后话)

我们再回顾一下,主成分分析有什么作用?答曰,降维。换言之,就是用几组低维的主成分来记录原始数据的大部分信息,这也可以认为是一种信息的(有损)压缩。在 SVD 中,我们也可以做类似的事情,也就是用更少项的求和Mk来近似完整的n项求和。为什么要这么做呢?我们用一个图像压缩的例子来说明我们的动机。

我们知道,电脑上的图像(特指位图)都是由像素点组成的,所以存储一张 1000×622 大小的图片,实际上就是存储一个 1000×622 的矩阵,共 622000 个元素。这个矩阵用 SVD 可以分解为 622 个矩阵之和,如果我们选取其中的前 100 个之和作为对图像数据的近似,那么只需要存储 100 个奇异值di,100 个ui向量和 100 个vi向量,共计 100×(1+1000+622)=162300 个 元素,大约只有原始的 26% 大小。

【注:本文只是为了用图像压缩来介绍 SVD 的性质,实际使用中常见的图片格式(png,jpeg 等)其压缩原理更复杂,且效果往往更好】

为了直观地来看看 SVD 压缩图像的效果,我们拿一幅 1000×622 的图片来做实验(图片来源:http://www.bjcaca.com/bisai/show.php?pid=33844&bid=40

SVD演示图片,原图svd_1svd_5svd_20svd_50svd_100

可以看出,当取一个成分时,景物完全不可分辨,但还是可以看出原始图片的整体色调。取 5 个成分时,已经依稀可以看出景物的轮廓。而继续增加k的取值,会让图片的细节更加清晰;当增加到 100 时,已经几乎与原图看不出区别。

接下来我们要考虑的问题是,Ak 是否是一个好的近似?对此,我们首先需要定义近似好坏的一个指标。在此我们用B与M之差的 Frobenius 范数 ||M–B||F 来衡量B对M 的近似效果(越小越好),其中矩阵的 Frobenius 范数是矩阵所有元素平方和的开方,当其为 0 时,说明两个矩阵严格相等。

此外,我们还需要限定 Ak 的 “维度”(否则 M就是它对自己最好的近似),在这里我们指的是矩阵的。对于通过 SVD 得到的矩阵Mk,我们有如下的结论:

在所有秩为k的矩阵中,Mk能够最小化与M之间的 Frobenius 范数距离。

这意味着,如果我们以 Frobenius 范数作为衡量的准则,那么在给定矩阵秩的情况下,SVD 能够给出最佳的近似效果。万万没想到啊。

在 R 中,可以使用 svd() 函数来对矩阵进行 SVD 分解,但考虑到 SVD 是一项计算量较大的工作,我们使用了 rARPACK 包中的 svds() 函数,它可以只计算前k项的分解结果。完整的 R 代码如下:

library(rARPACK);
library(jpeg);

factorize = function(m, k)
{
    r = svds(m[, , 1], k);
    g = svds(m[, , 2], k);
    b = svds(m[, , 3], k);
    return(list(r = r, g = g, b = b));
}

recoverimg = function(lst, k)
{
    recover0 = function(fac, k)
    {
        dmat = diag(k);
        diag(dmat) = fac$d[1:k];
        m = fac$u[, 1:k] %*% dmat %*% t(fac$v[, 1:k]);
        m[m < 0] = 0;
        m[m > 1] = 1;
        return(m);
    }
    r = recover0(lst$r, k);
    g = recover0(lst$g, k);
    b = recover0(lst$b, k);
    m = array(0, c(nrow(r), ncol(r), 3));
    m[, , 1] = r;
    m[, , 2] = g;
    m[, , 3] = b;
    return(m);
}

rawimg = readJPEG("pic2.jpg");
lst = factorize(rawimg, 100);
neig = c(1, 5, 20, 50, 100);
for(i in neig)
{
    m = recoverimg(lst, i);
    writeJPEG(m, sprintf("svd_%d.jpg", i), 0.95);
}

普渡大学统计系博士,现为卡耐基梅隆大学博士后,感兴趣的方向包括计算统计学、机器学习、大型数据处理等,参与翻译了《应用预测建模》《R 语言编程艺术》《ggplot2:数据分析与图形艺术》等经典书籍,是 showtext、RSpectra、recosystem、prettydoc 等流行 R 软件包的作者。邱怡轩

敬告各位友媒,如需转载,请与统计之都小编联系(直接留言或发至邮箱:[email protected]),获准转载的请在显著位置注明作者和出处(转载自:统计之都),并在文章结尾处附上统计之都微信二维码。

统计之都微信二维码

← COS 每周精选:再谈 knitr COS 访谈第 15 期:Rob J. Hyndman →

发表 / 查看评论


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK