这里主要用来搜集整理常用的工具、软件,旨在提高学习、办公效率。
FIM是Fbi IMproved的缩写,是Linux中利用系统帧缓冲区直接在命令行(Terminal)中显示图像的软件,可支持常见的如BMP、GIF、JPEG、PNG等各式的图片。Debian系列的Linux可以直接使用下述命令安装:1
sudo apt-get install fim
对于其他系统,可参考官方文档进行安装。安装完成后,直接执行:1
fim [image_name]
即可显示图像,再结合Xshell的XForward功能,堪称神器。详细的参数使用自行查阅。
MTR是My Traceroute的缩写,是Linux下的网络诊断工具,整合了常见的traceroute、ping的功能,安装方式:
1 | // Debian系列 |
工具简单用法:
1 | // --port 指定端口参数 |
欧姆龙(Omron)是来自日本的知名电子和自控设备制造商,其中、小型PLC在国内市场有较高的占有率,有CJ、CM等系列。PLC可以支持Fins、Host link等协议进行通信。
支持以太网的欧姆龙PLC CPU、以太网通信模块根据型号的不同,一般都会支持FINS(Factory Interface Network Service)协议,一些模块也会支持EtherNet/IP协议。Omron fins协议缺省TCP/UDP端口号为9600。Fins协议封装在TCP/UDP之上,需要注意的是基于TCP的Fins数据包和基于UDP的包在头部上差异较大。协议的具体构造可以参考欧姆龙官方文档。
FINS协议实现了OMRON PLC与上位机以太网通信。Fins基于TCP/UDP的报文的概览如下:
1 | Fins over TCP |
由上图可知,Fins/TCP实际上是将Fins/UDP报文作为其负载,在其前面加了一个Fins/TCP报头。需要注意的是,Fins/TCP报文中负载即Fins/UDP部分不一定会出现,它可以只有一个简单的Fins/TCP报头。
因此我们先介绍Fins/UDP报文的组成,然后再介绍Fins/TCP结构。
基于UDP的Fins协议的结构相对简单,整体结构由报头和数据两部分组成,如下所示:
1 | Fins over UDP |
其中报头部分为必要组成部分,为固定长度12个字节,数据部分非必现。
报头部分由11个定长字段组合而成,前面10个字段分别简称为:ICF、RSV、GCT、DNA、DA1、DA2、SNA、SA1、SA2、SID,如下所示:
1 | 0 1 2 6 7 |
上述11个字段中,除最后一个Command Code字段为2个字节外,其余所有字段均只占1个字节。
第一个字段被称为ICF字段(即Information control filed,信息控制字段),1个字节,由4个子字段组成,分述如下:
0x01
表示使用;0x01
表示为响应,0x00
表示命令;0x01
表示非必需回应,0x00
表示必须进行回应。 第二个字段被称为RSV(即Reserved,保留字段),1个字节,置0x00
。
第三个字段被称为GCT(即Gateway count ,网关计数),1个字节,置为0x02
。
第四个字段被称为DNA(即Destination network adrress,目标网络地址),1个字节,取值如下:
00
:表示本地网络;0x01
~0x7F
:表示远程网络。第五个字段被称为DA1(即Destination node number,目标节点编号),1个字节,取值如下:
0x01
~0x3E
: SYSMAC LINK
网络中的节点号;0x01
~0x7E
: YSMAC NET
网络中的节点号;0xFF
: 广播传输。Omron的官方手册中,该字段只能取上述值,然而网上的实际抓包发现会有其它值出现,被wireshark标记为unknown。
第六个字段被称为DA2(即Destination unit address,目标单元地址),1个字节,取值如下:
0x00
:PC(CPU);0xFE
: SYSMAC NET Link Unit or SYSMAC LINK Unit connected to network;0x10
~0x1F
:CPU总线单元 ,其值等于10 + 单元号(前端面板中配置的单元号)。Omron的官方手册中,该字段只能取上述值,然而网上的实际抓包发现会有其它值出现,被wireshark标记为unknown。
第七个字段被称为SNA(即Source network address,源网络地址),1个字节,取值及含义同DNA字段。
第八个字段被称为SA1(即Source node number,源节点编号),1个字节,取值及含义同DA1字段。
第九个字段被称为SA2(即Source unit addess,源单元地址),1个字节,取值及含义同DA2字段。
第十个字段被称为SID(即Service ID,服务ID**),1个字节,取值0x00
~0xFF
,产生会话的进程的唯一标识。
这个字段占2个字节,其取值由第一个字节表示的大分类和第二个字节表示的子分类复合而成,取值及其含义如下表:
Command Code | 英文释义 | 中文释义 | |
---|---|---|---|
1st Byte | 2nd Byte | ||
0x01 | 0x01 | MEMORY AREA READ | 内存区域读取 |
0x02 | MEMORY AREA WRITE | 内存区域写入 | |
0x03 | MEMORY AREA FILL | 内存区域填充 | |
0x04 | MULTIPLE MEMORY AREA READ | 多内存区域读取 | |
0x05 | MEMORY AREA TRANSFER | 内存区域传输 | |
0x02 | 0x01 | PARAMETER AREA READ | 参数区域读取 |
0x02 | PARAMETER AREA WRITE | 参数区域写入 | |
0x03 | PARAMETER AREA CLEAR | 参数区域清除 | |
0x20 | DATA LINK TABLE READ | 数据链表读取 | |
0x21 | DATA LINK TABLE WRITE | 内存区域传输 | |
0x03 | 0x04 | PROGRAM AREA PROTECT | 程序区保护 |
0x05 | PROGRAM AREA PROTECT CLEAR | 程序区保护清除 | |
0x06 | PROGRAM AREA READ | 程序区读取 | |
0x07 | PROGRAM AREA WRITE | 程序区写入 | |
0x08 | PROGRAM AREA CLEAR | 程序区清除 | |
0x04 | 0x01 | RUN | 执行 |
0x02 | STOP | 停止 | |
0x03 | RESET | 重置 | |
0x05 | 0x01 | CONTROLLER DATA READ | 控制器数据读取 |
0x02 | CONNECTION DATA READ | 连接数据读取 | |
0x06 | 0x01 | CONTROLLER STATUS READ | 控制器状态读取 |
0x02 | NETWORK STATUS READ | 网络状态读取 | |
0x03 | DATA LINK STATUS READ | 数据连接状态读取 | |
0x20 | CYCLE TIME READ | 循环次数读取 | |
0x07 | 0x01 | CLOCK READ | 时钟读取 |
0x02 | CLOCK WRITE | 时钟写入 | |
0x08 | 0x01 | LOOP-BACK TEST/ INTERNODE ECHO TEST | 环路测试/内部节点响应测试 |
0x02 | BROADCAST TEST RESULTS READ | 广播测试/结果读取 | |
0x03 | BROADCAST TEST DATA SEND | 广播测试数据发送 | |
0x09 | 0x20 | MESSAGE READ | 消息读取 |
MESSAGE CLEAR | 消息清除 | ||
FAL/FALS READ | FAL/FALS读取 | ||
0x0C | 0x01 | ACCESS RIGHT ACQUIRE | 访问权限获取 |
0x02 | ACCESS RIGHT FORCED ACQUIRE | 访问权限强制获取 | |
0x03 | ACCESS RIGHT RELEASE | 访问权限释放 | |
0x21 | 0x01 | ERROR CLEAR | 错误清除 |
0x02 | ERROR LOG READ | 错误日志读取 | |
0x03 | ERROR LOG CLEAR | 错误日志清除 | |
0x22 | 0x01 | FILE NAME READ | 文件名读取 |
0x02 | SINGLE FILE READ | 单文件读取 | |
0x03 | SINGLE FILE WRITE | 单文件写入 | |
0x04 | MEMORY CARD FORMAT | 记忆卡格式化 | |
0x05 | FILE DELETE | 文件删除 | |
0x06 | VOLUME LABEL CREATE/DELETE | 卷标创建/删除 | |
0x07 | FILE COPY | 文件复制 | |
0x08 | FILE NAME CHANGE | 文件名更改 | |
0x09 | FILE DATA CHECK | 文件数据校核 | |
0x0A | MEMORY AREA FILE TRANSFER | 内存区域文件传输 | |
0x0B | PARAMETER AREA FILE TRANSFER/td> | 参数区域文件传输 | |
0x0C | PROGRAM AREA FILE TRANSFER | 程序区域文件传输 | |
0x0F | FILE MEMORY INDEX READ | 文件内存索引读取 | |
0x10 | FILE MEMORY READ | 文件内存块读取 | |
0x11 | FILE MEMORY WRITE | 文件内存块写入 | |
0x23 | 0x01 | FORCED SET/RESET | 强制设置/重置 |
0x02 | FORCED SET/RESET CANCEL | 取消强制设置/重置 | |
0x0A | MULTIPLE FORCED STATUS READ | 多强制状态读取 | |
0x26 | 0x01 | NAME SET | 名称设置 |
0x02 | NAME DELETE | 名称删除 | |
0x03 | NAME READ | 名称读取 |
合计:14个大类,57个子类,共57个Command Code。
基于TCP的FINS报文结构也不复杂,无非就是一个FINS/TCP头部(必选),加上FINS/UDP报文(可选),如下所示:
1 | Fins over TCP |
Fins/TCP的报头与Fins/UDP不同的是,它由4个固定字段和2个可选字段组成,如下所示:
1 | Fins/TCP Header |
因此Fins/TCP的头部长度范围为$[16, 24]$字节,每个字段的长度均为固定的4个字节。
第一个字段为Magic Bytes字段,从字面意思看是魔数字段,其ASCII码(0x46494E53
)刚好是FINS这个单词,因此可以推测这个字段的值是恒定的。
第二个字段为Length字段,这个字段的值表示其后所有字段(包括可能出现的Fins/UDP包)的总长度,用公式表达为:
$$
\begin{split}
Length &= len(TCP_PAYLOAD) - len(Magic Bytes) - len(Length) \\
&= len(TCP_PAYLOAD) - 4 - 4 \\
&= len(TCP_PAYLOAD) - 8 \\
\end{split}
\tag{2 - 1}
$$
也就是说,该字段的值等于TCP负载的总长度减去8个字节。
第三个字段为Command字段,这个字段表示消息中随附的命令的类型。这个字段的取值直接决定了后续可选的字段Client Node Address、Server Node Address是否出现,具体情况如下所示:
0x00000000
:节点地址数据已发送(C->S),此时仅有Client Node Address字段;0x00000001
:节点地址数据已发送(S->C),此时Client/Server Node Address字段均出现;第四个字段为Error Code字段,这个字段表示错误代码。根据wireshark源代码所提供的信息,错误代码目前共定义了10种类型,如下所示:
错误码 | 对应含义 |
---|---|
0x00000000 | Normal |
0x00000001 | The header is not ‘FINS’ (ASCII code) |
0x00000002 | The data length is too long |
0x00000003 | The command is not supported |
0x00000020 | All connections are in use |
0x00000021 | The specified node is already connected |
0x00000022 | Attempt to access a protected node from an unspecified IP address |
0x00000023 | The client FINS node address is out of range |
0x00000024 | The same FINS node address is being used by the client and server |
0x00000025 | All the node addresses available for allocation have been used |
这两个字段是Fins/TCP的客户端/服务器建立连接的时候的类似DHCP协议客户端获取IP地址的时候才会出现的,如下所示:
由上可以看出,Fins/TCP协议的客户端/服务器在传输有效的命令数据之前,由客户端先向服务器发送一个包含Client Node Address字段的报文申请节点地址,如下图所示:
类似DHCP协议,由于客户端申请的时候还没有节点地址,因此该字段被置为0x00000000
。
服务器收到客户端请求后,给客户端分配相应的节点地址并通告给客户端,同时在报文中包含服务器自己的节点地址信息,如下所示:
客户端收到服务器的响应报文后,即使用分配的节点地址与服务器进行通信,由此客户端/服务器之间就建立起了有效的长连接。
这个字段是Fins/UDP、Fins/TCP协议的实际负载部分,鉴于其涵盖内容较多(官方手册就有两百多页),目前未对其进行深入解析,后续根据需要再深入解析。
1. Omron-Fins官方手册;
]]> 正式文章,默认放到_posts目录下,使用默认参数启动Hexo时会显示在文章列表中,会发布在博客上。1
$ hexo new "文章名字"
如果创建文章的时候要制定布局,需要加入布局参数:1
$ hexo new 布局名称 "文章名字"
执行后相应的文章会放到布局文件夹下,Hexo默认的布局有三种:
_posts
;_pages
;_drafts
; 创建一篇草稿,默认放到_drafts目录下,启动Hexo时要带--draft
参数才能看到,否则不会在显示在文章列表中,不会发布在博客上,要发布需要先转成正式文章。1
$ hexo new draft "草稿名字"
注:该功能正好提供了一个私密空间(文章仅自己可见),可以利用该特性保留一下不想删除但又不想发布在博客上的文章。
发布前最好先执行下清理工作,再重新生成,最后发布。前面两步非必须。
执行该命令会先清理本机中已生成的文章(缓存),非必须。1
hexo clean
执行该命令会在本地重新生成要发布的文章(静态文件),非必须,但如果先前执行了清理工作,就需要执行该命令。1
$ hexo generate
或者简写为:1
$ hexo g
执行该命令会发布本地生成的博客文章(不会发布草稿)。1
$ hexo publish
或者简写为:1
$ hexo p
如果要将一篇草稿发布为正式文章,则使用:1
$ hexo p draft "草稿名称"
这个命令实际做的工作是把你指定的_drafts
目录下的文章移动到_posts
目录下,从而成为正式文章。
执行该命令会将本地发布的博客部署到相应的服务器(Github)上。1
$ hexo deploy
或者简写为:1
$ hexo d
执行该命令,将会以默认模式(不显示草稿文章,默认端口4000)启动Hexo,启动后Hexo会自动追踪文章的更新,动态更新显示而无需重启Hexo服务。1
$ hexo server
或者简写为:1
$ hexo s
执行该命令,Hexo会启动为静态模式,此时Hexo只会处理 public 文件夹内的文件,而不会处理文件变动。在执行时,应该先自行执行hexo generate
,此模式通常用于生产环境(production mode)下。1
$ hexo server -s
或者简写为:1
$ hexo s -s
如果需要指定web访问端口及IP地址,需要分别指定-p、-i
参数:1
$ hexo s -p 端口号 -i IP地址
如果要在本地显示草稿文章,需要带上--draft
参数:1
$ hexo s --draft
Without the Agonizing Pain。——Edition $1 \frac{1}{4}$ · August 4, 1994 · Jonathan Richard Shewchuk
版 权 申 明
本文原始文章版权归Jonathan Richard Shewchuk(jrs@cs.cmu.edu)所有,中文翻译版版权归本博客主人所有。英文原版及翻译版均可随意复制、分发,但请务必完整保留英文原版及本条版权声明信息。
英文原版版权声明如下:
©1994 by Jonathan Richard Shewchuk. This article may be freely duplicated and distributed so long as no consideration is received in return, and this copyright notice remains intact.
This guide was created to help students learn Conjugate Gradient Methods as easily as possible. Please mail me (jrs@cs.cmu.edu) comments, corrections, and any intuitions I might have missed; some of these will be incorporated into a second edition. I am particularly interested in hearing about use of this guide for classroom teaching.
摘 要
共轭梯度法是稀疏线性方程组迭代求解法里面最优秀的方法。然而,大部分关于该算法的教科书即没有图示,讲解的也并不直观。因此,时至今日,仍然有许多这类教材的受害者在满是灰尘的图书馆角落里碎碎叨叨的胡诹一通。有鉴于此,几位睿智的精英们苦心孤诣的破解了前辈们留下的晦涩难懂的文字,并从几何角度深度的去阐释了这个算法。共轭梯度法本身也只是一个简单、优雅的复合方法。因此,睿智如你一定一学就会。
本文通过介绍二次型(Quadratic Form),然后据此引出最速下降(Steepest Descent)、共轭方向以及共轭梯度。同时还对特征向量做了解释,并用于检验雅可比方法(Jacobi Method)、最速下降以及共轭梯度。此外,还包括预处理和非线性共轭梯度法的一些问题。为了使的本文易读易懂,我可谓是煞费苦心。本文廊括了66个图示,同时避免出现晦涩的词汇。同一个概念也分别用了几种不同的方式来解释,大多数等式都配有直观的解释说明。
关键字:共轭梯度法,预处理,收敛性分析,通俗讲义
对想要了解更多关于迭代算法的读者,我强烈推荐William L.Briggs所著的A Multigrid Tutorial 即《多重网格法》,这是我读过的最好的数学教材之一。
特别鸣谢Omar Ghattas,他给我讲了许多关于数值方法的知识,也给本文的初稿提出了许多宝贵的意见。同时还要感谢James Epperson、David O’Hallaron、James Stichnoth、Nick Trefethen、Daniel Tunkelang给本文提出的建议。
为了帮助读者可以跳跃式的阅读本文,下面整理了一张章节之间的依赖关系图,如下所示:
本文适用于每一位像我一样喜欢大量使用图示说明同时工于计算的人。
目 录
当我决定学习共轭梯度法(简称CG,下同)时,读了有四种不同的关于该算法的描述文档,然而仍然分不清个子午卯酉,几乎是一无所获。这些文章大多数只是简单的“写”了下这个算法,然后对其特性做了推导证明。这些证明即没有任何直观的解释,也没有人提到CG算法的发明者灵感的来源。本文的诞生初衷正是源于本人在探索过程中所遭受的无数挫折,以期后人在学习CG算法的时候,学习的是一个全面、优雅的算法思想,而非一堆的公式证明。
CG是解决大型线性方程组问题最流行的迭代算法。它对于如下形式的问题特别有效:
$$
\textbf{A} \vec{x} = \vec{b}
\tag{1 - 1}
$$
式中,$\vec{x}$是我们要求解的未知向量,$\vec{b}$是一个已知的向量,$\textbf{A}$是一个对称的正定方阵。如果你不记得什么是正定矩阵的话,没关系,我们在后面会对这个知识点进行回顾。上述等式应用的范围非常广,比如有限差分和有限元法求解偏微分方程、结构分析、电路分析以及数学作业。
像CG这样的迭代算法适合于稀疏矩阵。如果上式中的$\textbf{A}$是稠密的,最好的求解方法是先对$\textbf{A}$进行因式分解,然后用置换法回代求解。对稠密矩阵$\textbf{A}$进行因式分解所需要的时间大致与迭代求解方程的时间相同。一旦$\textbf{A}$分解完毕,即使存在多个不同的$b$的值,也能使用回代法快速的求解出方程组的解。
对比此稠密矩阵和一个规模更大但占用内存总量相同的稀疏矩阵,稀疏矩阵因式分解出来的三角阵的非零元素通常要比其原始矩阵的多。因式分解很多时候受限于内存大小将无法进行,并且耗时也非常久,即使是回代求解的过程也可能会比迭代求解法慢。换句话说,大多数的迭代算法在稀疏矩阵的求解上即节省内存、又高效快捷。
本文假定读者已经学过线性代数相关的课程,对矩阵乘法、线性无关有着深刻的理解(即使你现在对这些概念有些淡忘也没关系)。基于此,我方能为你清晰的构建CG知识体系的大厦。
我们从一些符号的定义和注释开始。
如未特别指出,本文符号及其表示内容如下:
假定$\textbf{A}$是一个$n \times n$的矩阵,$x、b$为向量(即$n \times 1$的矩阵),则有如下等式:
$$
\begin{bmatrix}
A_{11} & A_{12} & \cdots & A_{1n} \\
A_{21} & A_{22} & \cdots & A_{2n} \\
\vdots & & \ddots & \vdots\\
A_{n1} & A_{n2} & \cdots & A_{nn}
\end{bmatrix}
\begin{bmatrix}
x_{11}\\
x_{21}\\
\vdots\\
x_{n1}
\end{bmatrix} =
\begin{bmatrix}
b_{1}\\
b_{2}\\
\vdots\\
b_{n}
\end{bmatrix}
\tag{2 - 1}
$$
所谓正定矩阵,是指对任意非零向量$\vec x$,恒满足如下不等式的矩阵:
$$
\vec{x}^T \textbf{A} \vec{x} > 0
\tag{2 - 2}
$$
这个解释可能对你来说还是过于抽象,从这个式子很难直观的看出所谓的正定矩阵和非正定矩阵的区别。别灰心,后面我们看到正定矩阵是如何影响二次型的形状的时候,就能对这个概念有个直观的理解了。
最后,别忘了这两个重要特性:
所谓的二次型,是关于向量的二次数值型函数,形如:
$$
f(\vec{x}) = \frac{1}{2} \vec{x}^T \textbf{A} \vec{x} - \vec{b}^T \vec{x} + c
\label{quadratic_form_expression} \tag{3 - 1}
$$
式中,$\textbf{A}$为一个矩阵,$\vec{x}、\vec{b}$为向量,$c$为一个常数。下面我先简单阐述一下,当$\textbf{A}$为对称正定阵的时候,$f(x)$的最小值由$\textbf{A} \vec{x} = \vec{b}$的解给出。
下面这个例子将贯穿本文:
$$
\textbf{A} =
\begin{bmatrix}
3 & 2 \\
2 & 6
\end{bmatrix}, \quad \quad
b =
\begin{bmatrix}
2 \\
-8
\end{bmatrix}, \quad \quad
c = 0
\tag{3 - 2}
$$
方程$\textbf{A} \vec{x} = \vec{b}$对应的图形如下所示:
更一般的,方程组的解$x$通常位于$n$维超平面(每一个是$n - 1$维)的交点处。就这个例子而言,解为:$\vec{x} = [2, \ -2]^T$。该例子对应的二次型的图像如下图所示:
因为矩阵$\textbf{A}$是正定的,因此其对应的二次型函数$f(\vec{x})$的图形像一个抛物型的碗,后面我们我作更详细的介绍。
二次型$f(\vec{x})$的等高线图如下所示:
二次型的梯度记作如下形式:
$$
f’(\vec{x}) =
\begin{bmatrix}
\frac{\partial}{\partial_{x_1}} f(\vec{x}) \\
\frac{\partial}{\partial_{x_2}} f(\vec{x}) \\
\vdots \\
\frac{\partial}{\partial_{x_n}} f(\vec{x})
\end{bmatrix}
\tag{3 - 3}
$$
上述梯度实际上是向量场,对于任意给定的值$\vec{x}$,梯度是使得二次型函数值$f(\vec{x})$增长最快的方向。
下图展示了上述二次型在给定式(3-2)的参数时的梯度向量:
结合图(3-2)、(3-4)可知,在图(3-2)的底部处,梯度为0,即图(3-4)中的小黑点。也就是说,将$f’(\vec {x})$设置为0并求解出对应的$\vec{x}$,我们就能求得二次型的最小值$f(\vec{x})_{min}$。
结合式(3-1)可以求出二次型的梯度为:
$$
f’(\vec{x}) = \frac{1}{2} \textbf{A}^T \vec{x} + \frac{1}{2} \textbf{A} \vec{x} - \vec{b}
\tag{3 - 4}
$$
译者注:这里的求解过程原文没有给出,这个过程也并不简单,为了便于线性代数较差的读者理解,现给出详细的求解步骤,以供参考。
考察式(3-3)中的任意项:
$$
\begin{split}
\frac{\partial}{\partial_{x_i}} f(\vec{x}) &= \frac{ \partial{ (\frac{1}{2} \vec{x}^T \textbf{A} \vec{x} - \vec{b}^T \vec{x} + c) } } {\partial_{x_i}} \\
&= \frac{1}{2} \cdot \frac{\partial{ (\vec{x}^T \textbf{A} \vec{x}) }} {\partial_{x_i}} - \frac{\partial{ (\vec{b}^T \vec{x}) }} {\partial_{x_i}} + \underbrace{ \frac{\partial{c}} {\partial_{x_i}} }_{0}
\end{split}
\tag{3 - 4 - 1}
$$
先来看上式中的第一项:
$$
\begin{split}
\frac{\partial{ (\overbrace{ \underbrace{\vec{x}^T \textbf{A}}_{g(\vec x)} \underbrace{\vec{x}}_{h(\vec x)} }^{f_1(\vec{x})}) }} {\partial_{x_i}} &= \underbrace{ \frac{\partial{ (\vec{x}^T \textbf{A} ) }} {\partial_{x_i}} }_{g’(\vec x)} \cdot \vec{x} + \vec{x}^T \textbf{A} \cdot \underbrace{ \frac{\partial{\vec{x} }} {\partial_{x_i}} }_{h’(\vec x)}
\end{split}
\tag{3 - 4 - 2}
$$
上式转换的依据是把$f_1(\vec{x}) = \vec{x}^T \textbf{A} \vec{x}$看作一个复合函数,由两个函数$g(\vec{x}) = \vec{x}^T \textbf{A}, \ h(\vec{x}) = \vec{x}$复合而成,即$f_1(\vec{x}) = g(\vec{x}) \cdot h(\vec{x})$,再由复合函数的求导法则即为上式。
上式右边仍然是由两部分组成,我们仍然一项一项来求解。对于第一项:
$$
\begin{split}
\frac{\partial{ (\vec{x}^T \textbf{A} ) }} {\partial_{x_i}} &= \frac{\partial}{\partial_{x_i}}
\Bigg (
[x_1, x_2, \cdots , x_n]
\times
\begin{bmatrix}
A_{11} & A_{12} & \cdots & A_{1n} \\
A_{21} & A_{22} & \cdots & A_{2n} \\
\vdots & & \ddots & \vdots\\
A_{n1} & A_{n2} & \cdots & A_{nn}
\end{bmatrix}
\Bigg )
\\
&= \frac{\partial}{\partial_{x_i}} [ \underbrace{ x_1 \cdot A_{11} + \cdots + x_n \cdot A_{n1}, \ \cdots , \ x_1 \cdot A_{1n} + \cdots + x_n \cdot A_{nn} }_{1 \times n \quad vector} ]
\end{split}
\tag{3 - 4 - 3}
$$
再由向量对标量的求导法则(参见这篇文章),上式最终变成:
$$
\begin{split}
\frac{\partial{ (\vec{x}^T \textbf{A} ) }} {\partial_{x_i}} &= [ \frac{\partial}{\partial_{x_i}} (x_1 \cdot A_{11} + \cdots + x_n \cdot A_{n1} ), \ \cdots , \ \frac{\partial}{\partial_{x_i}}(x_1 \cdot A_{1n} + \cdots + x_n \cdot A_{nn}) ] \\
&= [A_{i1}, \ A_{i2}, \ \cdots, \ A_{in}]
\end{split}
\tag{3 - 4 - 4}
$$
于是第一项为:
$$
\begin{split}
\frac{\partial{ (\vec{x}^T \textbf{A} ) }} {\partial_{x_i}} \cdot \vec{x} &= [A_{i1}, \ A_{i2}, \ \cdots, \ A_{in}] \cdot \vec{x} \\
&= A_{i1} \cdot x_1 + A_{i2} \cdot x_2 + \cdots + A_{in} \cdot x_n \\
&= \textbf{A}_{i, *} \cdot \vec{x}
\end{split}
\tag{3 - 4 - 5}
$$
如上式中所示,我们用$\textbf{A}_{i, *}$表示矩阵$\textbf{A}$的第$i$行的所有元素即$\textbf{A}$的第$i$个行向量。再来看式(3-4-2)的第二项,有:
$$
\begin{split}
\vec{x}^T \textbf{A} \cdot \frac{\partial{\vec{x}}} {\partial_{x_i}} &= \vec{x}^T \textbf{A} \cdot
[\frac{\partial{x_1}}{\partial{x_i}}, \ \cdots, \ \frac{\partial{x_n}}{\partial{x_i}}]^T \\
&= \vec{x}^T \textbf{A} \cdot [0, \ \cdots, \ \underbrace{1}_{ i^{th} }, \ 0]^T \\
&= \vec{x}^T \bigg (
\begin{bmatrix}
A_{11} & A_{12} & \cdots & A_{1n} \\
A_{21} & A_{22} & \cdots & A_{2n} \\
\vdots & & \ddots & \vdots\\
A_{n1} & A_{n2} & \cdots & A_{nn}
\end{bmatrix}
\times
\begin{bmatrix}
0 \\
\vdots \\
\underbrace{1}_{ i^{th} } \\
0
\end{bmatrix}
\bigg ) \\
&= [x_1, x_2, \cdots , x_n] \times
\begin{bmatrix}
A_{1i} \\
A_{2i} \\
\vdots \\
A_{ni}
\end{bmatrix} \\
&= x_1 \cdot A_{1i} + x_2 \cdot A_{2i} + \cdots + x_n \cdot A_{ni} \\
&= \textbf{A}_{*, i}^T \cdot \vec{x}
\end{split}
\tag{3 - 4 - 6}
$$
如上式中所示,我们用$\textbf{A}_{*, i}$表示矩阵$\textbf{A}$的第$i$列的所有元素即$\textbf{A}$的第$i$个列向量。于是有:
$$
\frac{\partial{ (\vec{x}^T \textbf{A}} \vec{x})} {\partial_{x_i}} = \textbf{A}_{i, *} \cdot \vec{x} + \textbf{A}_{*, i}^T \cdot \vec{x}
\tag{3 - 4 - 7}
$$
我们再来看式(3-4-1)的第二项,同样根据向量对标量的求导法则有:
$$
\begin{split}
\frac{\partial{ (\vec{b}^T \vec{x}) }} {\partial_{x_i}} &= \frac{\partial}{\partial_{x_i}}
\bigg(
[b_1, \ b_2, \ \cdots, b_n] \times
\begin{bmatrix}
x_1 \\
\vdots \\
\underbrace{x_i}_{ i^{th} } \\
\vdots \\
x_n
\end{bmatrix}
\bigg) \\
&= \frac{\partial (b_1 \cdot x_1 + \cdots + b_i \cdot x_i + \cdots + b_n \cdot x_n)} {\partial_{x_i}} \\
&= b_i
\end{split}
\tag{3 - 4 - 8}
$$
综合上式(3-4-7)、(3-4-8)有:
$$
\begin{split}
\frac{\partial}{\partial_{x_i}} f(\vec{x}) &= \frac{1}{2} (\textbf{A}_{i, *} \cdot \vec{x} + \textbf{A}_{*, i}^T \cdot \vec{x}) + b_i \\
&= \frac{1}{2} \textbf{A}_{i, *} \cdot \vec{x} + \frac{1}{2} \textbf{A}_{*, i}^T \cdot \vec{x} - b_i
\end{split}
\tag{3 - 4 - 9}
$$
于是(3-3)有:
$$
\begin{split}
f’(\vec{x}) &=
\begin{bmatrix}
\frac{1}{2} \textbf{A}_{1, *} \cdot \vec{x} + \frac{1}{2} \textbf{A}_{*, 1}^T \cdot \vec{x} - b_1 \\
\vdots \\
\frac{1}{2} \textbf{A}_{i, *} \cdot \vec{x} + \frac{1}{2} \textbf{A}_{*, i}^T \cdot \vec{x} - b_i \\
\vdots \\
\frac{1}{2} \textbf{A}_{n, *} \cdot \vec{x} + \frac{1}{2} \textbf{A}_{*, n}^T \cdot \vec{x} - b_n
\end{bmatrix} \\
&= \frac{1}{2} \begin{bmatrix}
\textbf{A}_{1, *} \cdot \vec{x} \\
\vdots \\
\textbf{A}_{i, *} \cdot \vec{x} \\
\vdots \\
\textbf{A}_{n, *} \cdot \vec{x}
\end{bmatrix} +
\frac{1}{2} \begin{bmatrix}
\textbf{A}_{*, 1}^T \cdot \vec{x} \\
\vdots \\
\textbf{A}_{*, i}^T \cdot \vec{x} \\
\vdots \\
\textbf{A}_{*, n}^T \cdot \vec{x}
\end{bmatrix} -
\begin{bmatrix}
b_1 \\
\vdots \\
b_i \\
\vdots \\
b_n
\end{bmatrix} \\
&= \frac{1}{2} \begin{bmatrix}
\textbf{A}_{1, *} \\
\vdots \\
\textbf{A}_{i, *} \\
\vdots \\
\textbf{A}_{n, *}
\end{bmatrix} \cdot \vec{x} + \frac{1}{2} \begin{bmatrix}
\textbf{A}_{*, 1}^T \\
\vdots \\
\textbf{A}_{*, i}^T \\
\vdots \\
\textbf{A}_{*, n}^T
\end{bmatrix} \cdot \vec{x} - \vec{b} \\
&= \frac{1}{2} \textbf{A} \vec{x} + \frac{1}{2} \textbf{A}^T \vec{x} - \vec{b}
\end{split}
\tag{3 - 4 - 10}
$$
求解完毕!
若$\textbf{A}$为对称阵即有$\textbf{A}^T = \textbf{A}$,则式(3-4)可进一步化简为:
$$
f’(\vec{x}) = \textbf{A}^T \vec{x} - \vec{b} = \textbf{A} \vec{x} - \vec{b}
\tag{3 - 5}
$$
再令上述梯度为0,即有我们要求解的线性方程(1-1)式。因此,通过上述的步骤,我们就把线性方程组$\textbf{A} \vec{x} = \vec{b}$的解和二次型函数图象的驻点联系起来了。如果$\textbf{A}$是对称正定阵,那么该驻点即为二次型函数$f(\vec{x})$的最小值点。因此,通过求解使得二次型函数$f(\vec{x})$值最小的点,我们就求出了线性方程组$\textbf{A} \vec{x} = \vec{b}$的解。
如果矩阵$\textbf{A}$为非对称阵,由于$\frac{1}{2}(\textbf{A}^T + \textbf{A})$为对称阵,因此(CG算法)让然能够求解出式(3-4)的解。
那么为何对称正定阵会有如此优良的特性呢?我们下面就来揭晓这个答案。考虑二次型函数$f(\vec{x})$曲面上的任意点$\vec{p}$以及最小值点$\vec{x} = \textbf{A}^{-1} \vec{b}$。将两个点$\vec{p}、\vec{x}$分别代入式(3-1),然后作差有如下结论:
$$
f(\vec{p}) = f(\vec{x}) + \frac{1}{2} (\vec{p} - \vec{x})^T \textbf{A} (\vec{p} - \vec{x})
\label{function_of_point_p} \tag{3 - 5}
$$
关于上述结论困的证明,英文原版在文档的附录C1中给出了详细步骤,这里贴出我的证明思路,以供参考。
将两个点带入式(3-1),并且结合$\textbf{A} \vec{x} = \vec{b}$以及$\textbf{A}^T = \textbf{A}$,然后作差,有:
$$
\begin{split}
f(\vec{p}) - f(\vec{x}) &= (\frac{1}{2} \vec{p}^T \textbf{A} \vec{p} - \vec{b}^T \vec{x} + c) - (\frac{1}{2} \vec{x}^T \textbf{A} \vec{x} - \vec{b}^T \vec{x} + c) \\
&= \frac{1}{2} \vec{p}^T \textbf{A} \vec{p} - \frac{1}{2} \vec{x}^T \textbf{A} \vec{x} - (\textbf{A} \vec{x})^T \vec{p} + (\textbf{A} \vec{x})^T \vec{x} \\
&= \frac{1}{2} \vec{p}^T \textbf{A} \vec{p} - \frac{1}{2} \vec{x}^T \textbf{A} \vec{x} - \vec{x}^T \textbf{A} \vec{p} + \vec{x}^T \textbf{A} \vec{x} \\
&= \frac{1}{2} \vec{p}^T \textbf{A} \vec{p} + \frac{1}{2} \vec{x}^T \textbf{A} \vec{x} - \vec{x}^T \textbf{A} \vec{p}
\end{split}
\tag{3 - 5 - 1}
$$
要进一步化简该式,还需要用到一个性质:若有矩阵$\textbf{A}^T = B$,且$\textbf{A}$为对称阵,则必有$\textbf{A} = \textbf{B}$。因为$(\vec{x}^T \textbf{A} \vec{p})^T = \vec{p} \textbf{A} \vec{x} $且$\vec{x}^T \textbf{A} \vec{p}$为标量(可视作$1 \times 1$的对称阵),所以必然有:$\vec{x}^T \textbf{A} \vec{p} = \vec{p} \textbf{A} \vec{x} $,于是上式可变成:
$$
\begin{split}
f(\vec{p}) - f(\vec{x}) &= \frac{1}{2} \vec{p}^T \textbf{A} \vec{p} + \frac{1}{2} \vec{x}^T \textbf{A} \vec{x} - \frac{1}{2} \vec{x}^T \textbf{A} \vec{p} - \frac{1}{2} \vec{x}^T \textbf{A} \vec{p} \\
&= \frac{1}{2} \vec{p}^T \textbf{A} \vec{p} + \frac{1}{2} \vec{x}^T \textbf{A} \vec{x} - \frac{1}{2} \vec{x}^T \textbf{A} \vec{p} - \frac{1}{2} \vec{p} \textbf{A} \vec{x} \\
&= \frac{1}{2}(\vec{p}^T \textbf{A} \vec{p} - \vec{p} \textbf{A} \vec{x}) + \frac{1}{2}(\vec{x}^T \textbf{A} \vec{x} - \vec{x}^T \textbf{A} \vec{p}) \\
&= \frac{1}{2} \vec{p}^T \textbf{A} (\vec{p} - \vec{x}) + \frac{1}{2} \vec{x}^T \textbf{A} (\vec{x} - \vec{p}) \\
&= \frac{1}{2} (\vec{p}^T \textbf{A} - \vec{x}^T \textbf{A}) (\vec{p} - \vec{x}) \\
&= \frac{1}{2} (\vec{p}^T - \vec{x}^T) \textbf{A} (\vec{p} - \vec{x}) \\
&= \frac{1}{2} (\vec{p} - \vec{x})^T \textbf{A} (\vec{p} - \vec{x})
\end{split}
\tag{3 - 5 - 2}
$$
证毕!
若$\textbf{A}$为正定阵,则由式(2-2)可知,若$\vec{p} \neq \vec{x}$,则上式(3-5)中的第二项恒为正,即有:
$$
\frac{1}{2} (\vec{p} - \vec{x})^T \textbf{A} (\vec{p} - \vec{x}) > 0, \ \vec{p} \neq \vec{x}
\tag{3 - 6}
$$
所以进一步有:$f(\vec{p}) > f(\vec{x}), \ \vec{p} \neq \vec{x}$。即点$\vec{x}$为二次型函数$f(\vec{x})$的全局最小值点。这就解释了为何对称正定阵会有如此优良的特性。
因此,对于正定阵最直观的理解方式就是它的二次型函数的图形是一个开口向上的抛物面。如果矩阵$\textbf{A}$非正定,那么其全局最小值点就可能不止一个。
如果$\textbf{A}$为负定矩阵,则其图形刚好为其对应的正定阵抛物面上下翻转后的图形。如果$\textbf{A}$为奇异矩阵(此时线性方程组解不唯一),解集为一条直线或超平面。如果$\textbf{A}$不属于上面情况中的任何一种,则其解$\vec{x}$为一个鞍点(Saddle Point),此时无论是最速下降法还是共轭梯度法都无法求解出来。
下图展示了上面所说的这些情况的二次型函数的图形。注意,函数中$\vec{b}、c$的取值仅仅影响图形最小值点的位置,但不会影响图形的状。
我们为什么要把解线性方程组的问题转换成一个貌似更棘手的问题呢?原因在于,我们研究的最速下降法和共轭梯度法均是由图(3-2)所示的求最小化的问题创造出来的,它远比图(3-1)所示的超平面相交问题更直观易懂。
在最速下降法中,我们的目标是从任意一个点$\vec{ x_{(0)} }$开始,下降到抛物面的底部。中间会经过一系列的点$\vec{x_{(1)}}、\vec{x_{(2)}}、\cdots $,直到足够接近真正的最小值点$\vec{x}$。
之所以称这种方法为最速下降法,是因为每当我们前进一步时,选择的方向必然是使得函数值$f(\vec{x})$减少的最多的方向,也即梯度$f’(\vec{x_{(i)}})$的反方向。根据式(3-5),前进的方向为$-f’(\vec{x_{(i)}}) = \vec{b} - \textbf{A} \vec{x_{(i)}}$。
为了方便理解后文,有些定义可能需要读者铭记在心,分别记:
有上述两个定义还可以推出以下结论:
$$
\begin{split}
\begin{cases}
\vec{r_{(i)}} &= -\textbf{A} \vec{e_{(i)}} \\
\vec{r_{(i)}} &= -f’(\vec{x_{(i)}})
\end{cases}
\end{split}
\tag{4 - 1}
$$
因此,我们既可以把残差向量视为误差向量$\vec{e_{(i)}}$经过矩阵$\textbf{A}$变换到与$\vec{b}$相同的向量空间后的结果,又可以把残差向量看作最速下降的方向。对于非线性问题(我们将在第14章讨论),就只能把残差向量看作最速下降的方向了。因此,建议读者在看到“残差(向量)”这个词时,脑海里一定要跟“最快速的下降方向”关联起来。
以实际例子来说,假定我们从点$\vec{x_{(0)}} = [-2, \ -2]^T$开始。迈出第一步时,根据最速下降方向(梯度反方向,下图中粗实线),我们将落在下图所示的实线上的某个点处。
换句话说,我们将选择一个点,其函数值满足:
$$
\vec{x_{(1)}} = \vec{x_{(0)}} + \alpha \vec{r_{(0)}}
\tag{4 - 2}
$$
译者注:再次强调,看到$\vec{r_{(i)}}$就要想到最速下降方向,这样就容易理解了。
那么问题又来了,方向有了,步长呢?也即$\alpha$应该取什么值才能使得迈出的下一步的函数值在所有可能的步长中最小呢?
线性搜索就是用来解决这类问题的,它将沿着指定的直线,寻找到使得函数值$f(\vec{x_{(i)}} + \alpha \vec{r_{(i)}})$最小的$\alpha$。下图展示了这个搜索过程:
由微积分的知识我们知道,当且仅当方向导数$\frac{d}{d_\alpha} f(\vec{x_{(1)}}) = 0$的时候,$\alpha$使得$f$最小。再由链式求导法则有:
$$
\begin{split}
\frac{d}{d_\alpha} f(\vec{x_{(1)}}) &= f’(\vec{x_{(1)}})^T \frac{d}{d_\alpha} \vec{x_{(1)}} \\
&= f’(\vec{x_{(1)}})^T \cdot \vec{r_{(0)}} \\
&= -\vec{r_{(1)}}^T \cdot \vec{r_{(0)}}
\end{split}
\label{directional_derivative_of_quadratic_function} \tag{4 - 3}
$$
译者注:注意上式中的$f’(\vec{x_{(1)}})^T$可不是笔误,$f’(\vec{x})$是一个$n \times 1$的向量,其转置则为$1 \times n$的向量。之所以这里会有转置,是因为函数的梯度(向量)是函数对每一个变量(标量)的偏导组成的向量。而函数的方向导数(标量),则是函数对向量的导数。举例来说,有二元函数$f(x_1, x_2)$,则其梯度和沿着任意方向$\vec{d} = [\alpha, \ \beta]^T$方向导数分别为:
$$
\begin{split}
\begin{cases}
f_{grad} &= [\frac{\partial f}{\partial x_1}, \ \frac{\partial f}{\partial x_2}]^T \\
f_{dir-der} &= \frac{\partial f}{\partial \alpha} \alpha + \ \frac{\partial f}{\partial \beta} \beta = [\frac{\partial f}{\partial \alpha}, \ \frac{\partial f}{\partial \beta}] \times [\alpha, \ \beta]^T = f_{grad}^T \cdot \vec{d} \\
\end{cases}
\end{split}
\tag{4 - 3 - 1}
$$
也就是说,方向导数等于梯度的转置与方向向量的内积。
令上式(4-3)等于0,可知,当且仅当$\alpha$的取值使得两次残差向量(反向梯度向量)相互正交的时候,函数值最小,如下图所示:
我们之所以要求这些向量在函数最小值处严格正交,更为直观的解释如下图所示:
图中展示了线性搜索直线上不同点(不同的点对应不同的移动步长)处的梯度向量(实线箭头)及其在该直线上的投影(虚线箭头)。梯度向量表示函数值$f$增长最快的方向,而其在搜索直线的投影则表示了其沿着搜索直线的增长的速度(大小)。由上图可以看出,在搜索直线上,当且仅当梯度方向与搜索直线正交的时候,函数值增加的最少。这个点对应的就是图(4-3(c))的最小值点。此时投影的大小为0。
下面就来求$\alpha$的具体表达式:
$$
\begin{split}
\vec{r_{(1)}}^T \cdot \vec{r_{(0)}} &= (\vec{b} - \textbf{A} \vec{x_{(1)}})^T \cdot \vec{r_{(0)}} \\
&= [\vec{b} - \textbf{A} (\vec{x_{(0)}} + \alpha \vec{r_{(0)}})]^T \cdot \vec{r_{(0)}} \\
&= (\vec{b} - \textbf{A} \vec{x_{(0)}} + \alpha \textbf{A} \vec{r_{(0)}})^T \cdot \vec{r_{(0)}} \\
&= (\vec{b} - \textbf{A} \vec{x_{(0)}})^T \cdot \vec{r_{(0)}} - \alpha (\textbf{A} \vec{r_{(0)}})^T \cdot \vec{r_{(0)}} \\
&= \vec{r_{(0)}}^T \cdot \vec{r_{(0)}} - \alpha \cdot \vec{r_{(0)}}^T \textbf{A} \cdot \vec{r_{(0)}}
\end{split}
\tag{4 - 4}
$$
因为上式等于0,所以可解得:$\alpha = \frac{\vec{r_{(0)}}^T \cdot \vec{r_{(0)}}} {\vec{r_{(0)}}^T \textbf{A} \cdot \vec{r_{(0)}}}$。
综上所述,最速下降的核心思想其实就两个,一个是移动的方向,另一个是移动的步长。移动的方向通过梯度向量来确定,确保每次移动函数值都是在减小的。移动的步长则是通过移动前后梯度向量的正交性来确定的,确保每次移动函数值减小量是最大的,即:
另外,移动后的点的计算公式为:
$$
\vec{x_{(i + 1)}} = \vec{x_{(i)}} + \alpha_{(i)} \vec{r_{(i)}}
\label{point_after_moved} \tag{4 - 5}
$$
下图展示了我们上面举的实例的下降过程,其收敛路径之所以呈现出“Z”字形,就是因为我们上面证明出来的梯度的正交性。
在迭代计算时,据方向和步长的计算公式可以看出,每轮迭代都需要进行两次矩阵-向量乘法计算,因此整个算法的计算效率就由这两次矩阵-向量乘法运算决定。幸运的是,通过变换我们可以消除其中的一个,只保留一个矩阵·向量运算。变换方法就是,将式(4-5)等式两边同时左乘一个矩阵$-\textbf{A}$,再同时加上$\vec{b}$,于是有:
$$
\vec{r_{(i + 1)}} = \vec{r_{(i)}} - \alpha_{(i)} \textbf{A} \vec{r_{(i)}}
\tag{4 - 6}
$$
由上式可以看出,在迭代计算方向和步长时,虽然仍然需要计算一次$\vec{r_{(0)}}$,但不再需要计算中间结果$\vec{x_{(i + 1)}}$,每轮迭代只有在计算$\alpha_{(i)}$的时候会进行一次矩阵·向量计算($\textbf{A} \vec{r_{(i)}}$同时出现在了$\alpha_{(i)}、\vec{r_{(i + 1)}}$的公式中,只需要计算一次即可)。也即每轮迭代,只需要按照如下公式计算:
$$
\begin{split}
\begin{cases}
\alpha_{(i)} &= \frac{\vec{r_{(i)}}^T \cdot \vec{r_{(i)}}} {\vec{r_{(i)}}^T \textbf{A} \cdot \vec{r_{(i)}}} \\
\vec{r_{(i + 1)}} &= \vec{r_{(i)}} - \alpha_{(i)} \textbf{A} \vec{r_{(i)}}
\end{cases}
\end{split}
\label{formula_of_step_size} \tag{4 - 7}
$$
上述递归计算的方式有个缺点,在计算式(4-6)的时候,完全不依赖于$\vec{x_{(i)}}$,这样由于迭代过程中浮点舍入误差的累积,最终只会收敛到最小值$\vec{x}$的附近,而不是最小值点本身。要避免这个问题也非常简单,定期使用$\vec{x_{(i)}}$计算残差向量$\vec{r_{(i)}}$。
讲完最速下降法的核心思想,在分析该算法的收敛性之前,我必须岔开主题先讲点其它的,以便确保读者对于特征向量有着深刻的理解。
我在上完第一节的线性代数课之后,就已经对特征值和特征向量了如指掌了。如果你的导师跟我导师一样,那么你现在仍然能回忆起解决问题的本征窍门(eigendoohickeys),但你却从未真正理解到它们。不幸的是,如果对它们没有一个直观的理解,那你也无法理解CG算法。当然,如果你已经在这方面禀赋非凡,请自动跳过本章节。
特征向量主要用作分析工具,因此,作为算法的一部分,最速下降法以及CG算法都不会计算任何的特征向量。
矩阵$\textbf{B}$的特征向量$\vec{\upsilon}$,是一个非零并且当矩阵$\textbf{B}$作用于它时其自身不会发生旋转的向量(作用后指向反方向这种情况除外)。特征向量$\vec{\upsilon}$可能会发生长度的改变或者变成反向向量,但不会发生侧向旋转。换句话说,存在常数$\lambda$,使得:$\textbf{B} \vec{\upsilon} = \lambda \vec{\upsilon}$,此常数$\lambda$即为(对应于特征向量$\vec{\upsilon}$的)矩阵$\textbf{B}$的特征值。对任意常数$\alpha$,向量$\alpha \vec{\upsilon}$也是特征值为$\lambda$的特征向量。因我们有:$\textbf{B}(\alpha \vec{\upsilon}) = \alpha \textbf{B} \vec{\upsilon} = \lambda \alpha \vec{\upsilon}$。换句话说,对一个特征向量进行缩放,并不会改变其特征向量的本质。
讲了半天,我们为什么要关心这个呢?原因就在于,迭代算法中通常会一次又一次的将一个矩阵$\textbf{B}$作用于一个向量。当矩阵$\textbf{B}$循环往复的作用于一个特征向量$\vec{\upsilon} $时,会有两种情况发生。
情况一:若$|\lambda| < 1$即特征值的绝对值小于1,则据等式$\textbf{B}^i \vec{\upsilon} = \lambda^i \vec{\upsilon} $可知,在$i \rightarrow + \infty$的过程中,$\textbf{B}^i \vec{\upsilon}$也将逐渐收缩减小,如下图所示:
情况二:若$|\lambda| > 1$即特征值的绝对值大于1,则据等式$\textbf{B}^i \vec{\upsilon} = \lambda^i \vec{\upsilon} $可知,在$i \rightarrow + \infty$的过程中,$\textbf{B}^i \vec{\upsilon}$也将逐渐伸张扩大,如下图所示:
若$\textbf{B}$为对称阵(通常情况下它都不是),那么矩阵$\textbf{B}$必然存在一组$n$个线性独立的特征向量,记作$\vec{\upsilon_1}, \ \vec{\upsilon_2}, \cdots, \ \vec{\upsilon_n}$。由于特征向量可以被任意非零常数缩放,因此这组特征向量并不唯一。与此同时,每一个特征向量有一个特征值与其对应,记作$\lambda_1, \ \lambda_2, \ \cdots, \ \lambda_n$。对于一个给定的矩阵,这些特征值是唯一的。特征值之间也许相同,也许不同。举例来说,单位阵$\textbf{I}$的特征值全为1,而其所有非零向量均为特征向量。
上面我们讨论了矩阵作用于特征向量的情况,那当矩阵$\textbf{B}$作用于一个非特征向量的普通向量时又会是什么样呢?理解线性代数的一个非常重要的技巧(当然也是本节要传授的技巧)就是,把一个行为未知的向量看作其它行为已知的向量的合成。考虑由一组特征向量${\vec{\upsilon_i}}$为基向量所构成的实数空间$\mathbb{R}^n$(因对称阵$\textbf{B}$必然存在$n$个线性无关的特征向量),任意$n$维向量都能由这些特征向量(基向量)表示,又由于矩阵·向量乘法满足分配率,因此我们可以通过单独分析矩阵$\textbf{B}$对每个特征向量的作用来分析矩阵$\textbf{B}$对整个向量的影响。
如下图所示,向量$\vec{x}$由两个特征向量$\vec{\upsilon_1}, \ \vec{\upsilon_2}$合成,即有$\vec{x} = \vec{\upsilon_1} + \vec{\upsilon_2}$。矩阵$\textbf{B}$作用于向量$\vec{x}$的效果等价于矩阵$\textbf{B}$分别作用于这两个特征向量的结果的合成。迭代应用这个结论我们有:$\textbf{B}^i \vec{x} = \textbf{B}^i {\vec{\upsilon_1}} + \textbf{B}^i {\vec{\upsilon_2}} = \lambda_1^i {\vec{\upsilon_1}} + \lambda_2^i {\vec{\upsilon_2}}$。
译者注:若$\vec{x} = k_1 \vec{\upsilon_1} + k_2 \vec{\upsilon_2}$,则有:
$$
\begin{split}
\textbf{B}^i \vec{x} &= \textbf{B}^i (k_1 \vec{\upsilon_1} + k_2 \vec{\upsilon_2}) \\
&= k_1 \textbf{B}^i \vec{\upsilon_1} + k_2 \textbf{B}^i \vec{\upsilon_2} \\
&= k_1 \lambda_1^i \vec{\upsilon_1} + k_2 \lambda_2^i \vec{\upsilon_2}
\end{split}
\tag{5 - 1 - 1}
$$
如果所有特征值的绝对值均小于1,由之前的结论可知,$\textbf{B}^i \vec{x}$最终依然会收敛至0(构成$\vec{x}$的特征向量由于$\textbf{B}$的迭代作用收敛至0)。而只要任意一个特征值的绝对值大于1,那么$\textbf{B}^T \vec{x}$长度将会发散至无穷大。这就是为何数值分析人员高度重视矩阵谱半径的原因。
对给定矩阵$\textbf{B}$,其谱半径定义为:
$$
\rho(\textbf{B}) = max|\lambda_i|, \quad \lambda_i为\textbf{B}的特征值
\tag{5 - 1}
$$
译者注:原文中只讨论了迭代过程中$\textbf{B}^T \vec{x}$长度的变化情况,并没有明确的对其方向的变化进行探讨(虽然细心的读者能从上图(5-3)中看出来),会对读者理解后面的有些章节造成困扰。因此这里我们专门讨论下方向的变化情况。
由$\textbf{B}^i \vec{\upsilon} = \lambda^i \vec{\upsilon}$可知:
- 1. 若$\lambda > 0$,即特征值为正,则恒有$\lambda^i > 0$。所以在迭代过程中,该特征值对应的特征向量方向的分量只会发生长度的变化,而不会有方向的改变,如上图(5-3)中的$\vec{\upsilon_1}$方向分量;
- 2. 若$\lambda < 0$,即特征值为负,则当$i = 2k + 1, \ k=0, 1, 2, \cdots$即迭代奇数次时,$\lambda^i < 0$。此时该特征值对应的特征向量方向的分量不仅长度会发生改变,方向还会旋转180度。而当$i = 2k, \ k=0, 1, 2, \cdots$即迭代偶数次时,$\lambda^i > 0$,方向保持不变。在迭代过程中其表现就是,方向一正一反,不断的发生翻转,如上图(5-3)中的$\vec{\upsilon_2}$方向分量。
即一个矩阵$\textbf{B}$的谱半径为其特征值绝对值的最大值。如果我们希望$\textbf{B}^i \vec{x}$能够快速的收敛至0,那么谱半径$\rho(\textbf{B})$必须小于1,并且越小越好。
即谱半径的大小直接决定了收敛速度的快慢!
译者注:这里原文档5.1小节中有两处小错误:
> If one of the eigenvalues has magnitude greater than one, x will diverge to infinity.
> If we want x to converge to zero quickly, $\rho(B)$ should be less than one, and preferably as small as possible.
这里的x
应该是$\textbf{B}^i \vec{x}$,x
是给定的向量,不会发生改变。
上面我们讲的是实对称阵,由于实对称阵必然存在$n$个线性无关的特征向量,因此上述结论对实对称阵而言是恒成立的。那对于非实对称阵呢,情况又会如何?
实际上对大多数非实对称阵而言,上述结论也成立(存在$n$个线性无关的特征向量)。但是这里还有必要提一下的是,有一类非实对称阵,它们不具备$n$个线性独立的特征向量,这类矩阵我们称之为退化矩阵(defective)。光从这个名字你就可以看出那些因研究该类矩阵而受挫的线性代数学家们对它当之无愧的抵触。
关于这类矩阵的细节问题由于太过复杂因此无法在本文中详述,请自行参考相关资料。但是这类矩阵的特性可以通过广义特征向量(generalized eigenvector)和广义特征值(generalized eigenvalue)来分析。对于退化矩阵,当且仅当其所有广义特征值的绝对值均小于1时,$\textbf{B}^i \vec{x}$方能收敛到0。要证明这点非常难,感兴趣可参考相关资料。
此外,还需要读者记住一条非常有用的结论:正定阵的特征值必然全为正数。
这点我们可利用特征值的定义来证明。据特征值定义有:$\textbf{B} \vec{\upsilon} = \lambda \vec{\upsilon} $,两边同时乘以特征向量的转置有:$\vec{\upsilon}^T \textbf{B} \vec{\upsilon} = \lambda \vec{\upsilon}^T \vec{\upsilon} $,又因为矩阵$\textbf{B}$为正定阵,因此由正定阵的定义有:
$$
\lambda \vec{\upsilon}^T \vec{\upsilon} = \vec{\upsilon}^T \textbf{B} \vec{\upsilon} > 0
\tag{5 - 2}
$$
因此必然有$\lambda > 0$($\vec{\upsilon}^T \vec{\upsilon} = (||\vec{\upsilon}||_2)^2 > 0$),即正定阵的特征值必然全为正值。
显然,一个必然收敛到0的算法还不足以让你呼朋引伴。我们来介绍一个解决线性方程组$\textbf{A} \vec{x} = \vec{b}$更有用的算法:即雅可比法。在这个算法中,我们把矩阵$\textbf{A}$拆成两部分:
由上可知:$\textbf{A} = \textbf{D} + \textbf{E}$,并由此引出我们的雅可比法:
$$
\begin{split}
\because \qquad \qquad \qquad \textbf{A} \vec{x} &= \vec{b} \\
\\
\therefore \qquad \qquad (\textbf{D} + \textbf{E}) \vec{x} &= \vec{b} \\
\textbf{D} \vec{x} + \textbf{E} \vec{x} &= \vec{b}\\
\textbf{D}^{-1} (\textbf{D} \vec{x} + \textbf{E} \vec{x}) &= \textbf{D}^{-1} \vec{b}\\
\vec{x} + \textbf{D}^{-1} \textbf{E} \vec{x} &= \textbf{D}^{-1} \vec{b}\\
\vec{x} &= \underbrace{-\textbf{D}^{-1} \textbf{E}}_{\textbf{B}} \vec{x} + \underbrace{\textbf{D}^{-1} \vec{b}}_{\vec{z}} \\
\vec{x} &= \textbf{B} \vec{x} + \vec{z}
\end{split}
\tag{5 - 3}
$$
如上所示,分别记:$\textbf{B} = -\textbf{D}^{-1} \textbf{E}, \quad \vec{z} = \textbf{D}^{-1} \vec{b}$。由于$\textbf{D}$为对角阵,因此其逆矩阵非常容易求解。上述恒等式可以通过如下的递归方程转换为迭代算法:
$$
\vec{x_{(i + 1)}} = \textbf{B} \vec{x_{(i)}} + \vec{z}
\tag{5 - 4}
$$
给定一个初始向量$\vec{x_{(0)}}$,利用上述递归方程就可以计算出一系列的向量。我们的期望是每一轮迭代新生成的向量都要比上一轮迭代生成的向量更接近于真实解$\vec{x}$。真实解$\vec{x}$我们称之为等式(5-4)的稳态点/驻点(stationary point),因为当$\vec{x_{(i)}} = \vec{x}$时,则必然有$\vec{x_{(i + 1)}} = \vec{x}$。
译者注:这里额外做些说明更容易理解。利用式(5-4)进行迭代时,如何判断我们已经找到了真实解了呢(即何时停止迭代)?因为只有真实解才必然满足上式的等号(注意,在迭代的时候,上式的等号应该理解为“赋值”操作。在判断解的时候,要理解为“等于”)。也就是说,迭代前后的值不再发生变化,此时的向量值即为真实解。
上面这个推导过程可能看起来比较随意(实际上也确实很随意)。我们可以用任意数量的关于$\vec{x}$的恒等式来代替上式。通过对矩阵$\textbf{A}$进行不同的拆解,也即选取不同的$\textbf{D}$和$\textbf{E}$,我们可以得到高斯·赛德尔法或者逐次超松弛法(SOR)。而我们的预期则是选取一种合理的拆分方法,使得最终的矩阵$\textbf{B}$的谱半径较小。本文为了简单起见,随意选择了雅可比拆分法。
假设我们从任意向量$\vec{x_{(0)}}$开始,每一轮迭代中我们用矩阵$\textbf{B}$作用于该向量,然后加上一个$\vec{z}$。那么每一轮迭代中我们具体做了些什么呢?
要解答这个问题,在这里我们再一次应用矢量合成原则(把一个向量视作其它若干向量的和)。每一轮迭代时,我们把$\vec{x_{(i)}}$用真实解$\vec{x}$和误差项$\vec{e_i}$来表示,于是上式等价于:
$$
\begin{split}
\vec{x_{(i + 1)}} &= \textbf{B} \vec{x_{(i)}} + \vec{z} \\
&= \textbf{B} (\vec{x} + \vec{e_{(i)}}) + \vec{z} \\
&= \underbrace{\textbf{B} \vec{x} + \vec{z}}_{据式5-3} + \textbf{B} \vec{e_{(i)}} \\
&= \vec{x} + \textbf{B} \vec{e_{(i)}}\\
\therefore \qquad \vec{e_{(i + 1)}} = \vec{x_{(i + 1)}} - \vec{x} &= \textbf{B} \vec{e_{(i)}}
\end{split}
\tag{5 - 5}
$$
根据上式可以这样来理解迭代过程:每一轮迭代时,不会影响迭代值$\vec{x_{(i)}}$对应于真实解的部分,而只是影响误差项。显见,若上式中矩阵$\textbf{B}$的谱半径$\rho(\textbf{B}) < 1$,则根据之前讨论的结果,误差项$\vec{e_{(i)}}$必然随着$i \rightarrow \infty$而收敛于0。
也就是说,在迭代过程中,通过不断的减小误差项而实现向真实解逼近。由此可知,初始向量$\vec{x_{(0)}}$的选择对于最终的结果没有任何影响。
当然,初始向量$\vec{x_{(0)}}$的选择也并非完全不重要。它虽然不影响最终的结果,但却会影响到收敛到给定误差范围内需要迭代的次数。跟谱半径$\rho(\textbf{B})$比起来,它的影响又要小一些,谱半径直接决定了收敛的速度。假设$\vec{\upsilon}_j$表示矩阵$\textbf{B}$的所有特征向量中特征值最大的那一个(即$\rho(\textbf{B}) = \lambda_j$)。若将初始误差向量$\vec{e_{(0)}}$用各个特征向量的线性组合表示,则误差向量沿$\vec{\upsilon}_j$那个方向的分量收敛速度是最慢的。
由于矩阵$\textbf{B}$并非总是对称阵(即使矩阵$\textbf{A}$是对称阵),甚至可能是退化矩阵。而雅可比算法的收敛速度又很大程度上依赖于谱半径$\rho(\textbf{B})$(而$\rho(\textbf{B})$又依赖于$\textbf{A}$),因此雅可比方法并非对于所有的$\textbf{A}$都能收敛,甚至并非所有的正定阵$\textbf{A}$都不一定收敛。
为了更好的展示该方法的思想,下面我们用本文最开始的例子点此回看来计算一下。首先,我们要找到一种求解给定的矩阵的特征向量和特征值的方法。由定义可知对给定矩阵的任意特征向量$\vec{\upsilon}$及其特征值$\lambda$有:
$$
\begin{split}
&\because \qquad \textbf{A} \vec{\upsilon} = \lambda \vec{\upsilon} = \lambda \textbf{I} \vec{\upsilon} \\
\\
&\therefore \qquad (\lambda \textbf{I} - \textbf{A}) \underbrace{\vec{\upsilon}}_{\neq 0} = 0 \\
& \therefore \qquad det(\lambda \textbf{I} - \textbf{A}) = 0
\end{split}
\tag{5 - 6}
$$
上式中矩阵$\lambda \textbf{I} - \textbf{A}$对应的行列式(即$det(\lambda \textbf{I} - \textbf{A})$)称为特征多项式(characteristic polynomial)。它是一个关于特征值$\lambda$的$n$阶多项式,它的所有根即对应所有特征值。则本文中矩阵$\textbf{A}$的特征多项式为:
$$
\begin{split}
det
\begin{bmatrix}
\lambda - 3 & -2 \\
-2 & \lambda - 6 \\
\end{bmatrix} = \lambda^2 - 9\lambda + 14 = (\lambda - 7)(\lambda - 2)
\end{split}
\tag{5 - 7}
$$
于是可解出特征值:$\lambda_1 = 7, \ \lambda_2 = 2$。求出了特征值,将其回代入上式(5-6)即可求得特征向量。比如要求$\lambda_1 = 7$对应的特征向量,我们有:
$$
\begin{split}
(\lambda \textbf{I} - \textbf{A}) \vec{\upsilon} &=
\begin{bmatrix}
4 & -2 \\
-2 & 1 \\
\end{bmatrix}
\begin{bmatrix}
\upsilon_1 \\
\upsilon_2 \\
\end{bmatrix}
= 0 \\
\\
\therefore \qquad \qquad \qquad \upsilon_2 &= 2 \upsilon_1
\end{split}
$$
任意满足上式的非零解都是$\lambda_1 = 7$对应的特征向量,比如$\vec{\upsilon} = [1, \ 2]^T$。同理可求得$\lambda_2 = 2$的特征向量如$\vec{\upsilon} = [-2, \ 1]^T$。由下图可以看出,这两个特征向量刚好与图中函数椭圆等高线的轴线相重合,同时大的特征值对应更陡的斜率(负的特征值表示函数值$f$沿着椭圆等高线轴向递减,如图(4-3b)、(4-4)所示)。
现在我们来看看雅可比法的迭代过程是怎样的。由给定矩阵$\textbf{A}$可知:
$$
\begin{split}
\begin{cases}
\textbf{D} &= \begin{bmatrix}3 && 0 \\ 0 && 6 \end{bmatrix}, \quad \textbf{E} = \begin{bmatrix}0 && 2 \\ 2 && 0 \end{bmatrix} \\
\textbf{B} &= -\textbf{D}^{-1} \textbf{E} = -\begin{bmatrix}3 && 0 \\ 0 && 6 \end{bmatrix}^{-1} \times \begin{bmatrix}0 && 2 \\ 2 && 0 \end{bmatrix} = \begin{bmatrix}0 && -\frac{2}{3} \\ -\frac{1}{3} && 0 \end{bmatrix} \\
\vec{z} &= \textbf{D}^{-1} \vec{b} = \begin{bmatrix}3 && 0 \\ 0 && 6 \end{bmatrix}^{-1} \times \begin{bmatrix}2 \\ -8 \end{bmatrix} = \begin{bmatrix}\frac{2}{3} \\ -\frac{4}{3} \end{bmatrix}\\
\end{cases}
\end{split}
\tag{5 - 8}
$$
因此有:
$$
\vec{x_{(i + 1)}} = \underbrace{ \begin{bmatrix}0 && -\frac{2}{3} \\ -\frac{1}{3} && 0 \end{bmatrix} }_{\textbf{B}} \vec{x_{(i)}} + \underbrace{ \begin{bmatrix}\frac{2}{3} \\ -\frac{4}{3} \end{bmatrix} }_{\vec{z}}
\tag{5 - 9}
$$
同理可以求出矩阵$\textbf{B}$的特征值及特征向量:
$$
\begin{split}
\begin{cases}
\vec{\upsilon_1} &= \begin{bmatrix}\sqrt{2} \\ 1 \end{bmatrix}, \qquad \lambda_1 = -\frac{\sqrt{2}}{3} \\
\vec{\upsilon_2} &= \begin{bmatrix}-\sqrt{2} \\ 1 \end{bmatrix}, \qquad \lambda_2 = \frac{\sqrt{2}}{3} \\
\end{cases}
\end{split}
\tag{5 - 10}
$$
矩阵$\textbf{B}$的特征值及特征向量如下图所示,需要注意的是,它的特征向量方向与矩阵$\textbf{A}$的并不重合,并且也与二次型函数的椭圆等高线的轴线没有必然联系。
下图展示了雅可比方法的收敛过程。
上图中,算法走出来的谜一样的路线可以通过分析每一轮的迭代误差项$\vec{e_i}$在矩阵$\textbf{B}$各个特征向量方向上的构成情况来理解。如下图(c)、(d)、(e)所示:
下图把迭代过程中解的变化情况和误差向量的情况都绘制出来了。图中的收敛速率都是由其特征值定义的,如图(5-3)所示。
译者注:这个图要彻底理解还是需要花点心思推导的,作者给这个图配的说明有点误导人。这个图里面的误差项在特征向量的分向量与图(5-7)的相比明显是反的。根据式(5-5),对任意两次连续的迭代我们有:
$$
\begin{split}
\begin{cases}
\vec{x_{(i + 1)}} &= \vec{x} + \textbf{B} \vec{e_{(i)}} \\
\vec{x_{(i + 2)}} &= \vec{x} + \textbf{B} \vec{e_{(i + 1)}} \\
\vec{e_{(i + 1)}} &= \textbf{B} \vec{e_{(i)}}
\end{cases}
\end{split} \\
\begin{split}
\therefore \quad \vec{x_{(i + 2)}} - \vec{x_{(i + 1)}} &= (\vec{x} + \textbf{B} \vec{e_{(i + 1)}}) - (\vec{x} + \textbf{B} \vec{e_{(i)}}) \\
&= \textbf{B} \vec{e_{(i + 1)}} - \textbf{B} \vec{e_{(i)}} \\
&=\vec{e_{(i + 2)}} - \vec{e_{(i + 1)}} \\
\therefore \quad \vec{x_{(i + 2)}} &= \vec{x_{(i + 1)}} - \vec{e_{(i + 1)}} + \vec{e_{(i + 2)}}
\end{split}
\tag{5 - 10 - 1}
$$
由上式可知,每当进行下一次迭代时,迭代后的解等于上一轮解与上一轮误差向量的反向向量之和,再加上本轮的误差向量。举例来说,第一轮迭代时,我们有:
$$
\vec{x_{(1)}} = \vec{x_{(0)}} - \vec{e_{(0)}} + \vec{e_{(1)}}
\tag{5 - 10 - 2}
$$
这也就是为什么上图中起始点处加了与该点处误差项向量相反的向量的原因,加上该误差向量后,就为点(2, -2),然后再加上一个下一轮的误差项向量,即为路径点上的第二个点了。由此递推,每一轮迭代时,都会加上一个该点处的误差向量的反向向量。即为图上的浅色实线箭头。
希望本节的内容能充分的给读者证实特征向量是非常有用的工具,而不是你的导师用来花式虐学渣的工具(笑)。
为了立即最速下降法的收敛性,我们先来考虑误差项$\vec{e_{(i)}}$正好是特征值为$\lambda_e$的特征向量,则由式(4-1)有残差向量$\vec{r_{(i)}} = -\textbf{A} \vec{e_{(i)}} = -\lambda_e \vec{e_{(i)}}$,即残差向量此时也是一个特征向量。由式(4-5)及误差向量的定义$\vec{e_{(i)}} = \vec{x_{(i)}} - \vec{x}$有:
$$
\begin{split}
\because \qquad \vec{x_{(i + 1)}} &= \vec{x_{(i)}} + \alpha_{(i)} \vec{r_{(i)}} \\
\\
\therefore \qquad \vec{e_{(i + 1)}} + \vec{x} &= \vec{e_{(i)}} + \vec{x} + \alpha_{(i)} \vec{r_{(i)}} \\
\vec{e_{(i + 1)}} &= \vec{e_{(i)}} + \alpha_{(i)} \vec{r_{(i)}} \\
\\
\because \qquad \alpha_{(i)} &= \frac{\vec{r_{(i)}}^T \cdot \vec{r_{(i)}}} {\vec{r_{(i)}}^T \textbf{A} \cdot \vec{r_{(i)}}} \\
&= \frac{\vec{r_{(i)}}^T \cdot \vec{r_{(i)}}} {(\textbf{A} \vec{r_{(i)}})^T \cdot \vec{r_{(i)}}} \\
&= \frac{\vec{r_{(i)}}^T \cdot \vec{r_{(i)}}} {-(\textbf{A} \lambda_e e_{(i)})^T \cdot \vec{r_{(i)}}} \\
&= \frac{\vec{r_{(i)}}^T \cdot \vec{r_{(i)}}} {\lambda_e (-\textbf{A} e_{(i)})^T \cdot \vec{r_{(i)}}} \\
&= \frac{\vec{r_{(i)}}^T \cdot \vec{r_{(i)}}} {\lambda_e \vec{r_{(i)}}^T \cdot \vec{r_{(i)}}} \\
&= \frac{1}{\lambda_e} \\
\\
\therefore \qquad \vec{e_{(i + 1)}} &= \vec{e_{(i)}} + \frac{1}{\lambda_e} \vec{r_{(i)}} \\
&= \vec{e_{(i)}} + \frac{1}{\lambda_e} (-\lambda_e \vec{e_{(i)}}) \\
&= 0
\end{split}
\label{error_iteration} \tag{6 - 1}
$$
下图展示了为何最速下降法只迭代了一次就收敛到了真实解$\vec{x}$:
如上图所示,若点$\vec{x_{(i)}}$位于椭圆等高线的一条轴上,则残差向量必然指向椭圆的中心点。取$\alpha_{(i)} = \lambda_e^{-1}$,则由上述推导可知,此时算法必然收敛。
译者注:因此时误差向量为$\vec{e_{(i)}} = \vec{x_{(i)}} - \vec{x}$,即误差向量此时由最内层椭圆的中心点指向$ \vec{x_{(i)}}$点。又由上述误差向量和残差向量的关系式$\vec{r_{(i)}} = -\lambda_e \vec{e_{(i)}}$可知,此时残差向量与误差向量共线。又由于$\textbf{A}$的两个特征值均大于0,所以此时残差向量方向与误差向量相反,由$ \vec{x_{(i)}}$点指向最内层椭圆的中心点,即为上图所示。
对于更一般的情况,我们则需要将误差项向量表示成特征向量的线性组合来分析,并且这些特征向量是标准正交化的。若矩阵$\textbf{A}$为对称阵,则其必然存在$n$个正交的特征向量,关于该结论的证明见附录C2。由于我们可以对特征向量进行随意的缩放,因此这里我们取每个特征向量的长度为单位长度1。该技巧可以给我们提供如下的有用特性:
$$
\vec{\upsilon_j^T} \vec{\upsilon_k} =
\begin{split}
\begin{cases}
&1, \qquad j &= k, \\
&0, \qquad j &\neq k,
\end{cases}
\end{split}
\tag{6 - 2}
$$
将误差项表示为特征向量的线性组合有:
$$
\vec{e_{(i)}} = \sum_{j = 1}^{n} \xi_j \vec{\upsilon_j}
\label{error_eigenvectors_relationship} \tag{6 - 3}
$$
式中,$\xi_j$表示第$j$个分量($\vec{\upsilon_j}$)的长度。由上式(6-2)、(6-3)有如下结论:
$$
\begin{eqnarray}
\vec{r_{(i)}} = -\textbf{A} \vec{e_{(i)}} = -\sum_{j} \xi_j \lambda_j \vec{\upsilon_j} \label{residual} \tag{6 - 4}\\
||\vec{e_{(i)}}||^2 = \vec{e_{(i)}}^T \vec{e_{(i)}} = \sum_{j}^{} \xi_j^2 \label{error_norm} \tag{6 - 5}\\
\vec{e_{(i)}}^T \textbf{A} \vec{e_{(i)}} = (\sum_{j}^{} \xi_j \vec{\upsilon_j}^T) (\sum_{j}^{} \xi_j \lambda_j \vec{\upsilon_j}) = \sum_{j}^{} \xi_j^2 \lambda_j \label{residual_norm} \tag{6 - 6}\\
||\vec{r_{(i)}}||^2 = \vec{r_{(i)}}^T \vec{r_{(i)}} = \sum_{j}^{} \xi_j^2 \lambda_j^2 \label{residual_by_eigenvector} \tag{6 - 7}\\
\vec{r_{(i)}}^T \textbf{A} \vec{r_{(i)}} = \sum_{j}^{} \xi_j^2 \lambda_j^3 \label{residual_plus_a_matrix} \tag{6 - 8}\\
\end{eqnarray}
$$
由上式($\ref{residual}$)可知,残差向量$\vec{r_{(i)}}$也可以表示为特征向量的线性组合,而其在每个特征向量分量上的长度为$-\xi_j \lambda_j$。而式($\ref{error_norm}$)、($\ref{residual_norm}$)为毕达哥拉斯定理(Pythagorean Theorem)的应用。
有了这些结论,现在我们就可以开始分析了。由式($\ref{error_iteration}$)及上述结论我们有:
$$
\begin{split}
\vec{e_{(i + 1)}} &= \vec{e_{(i)}} + \frac{\vec{r_{(i)}}^T \cdot \vec{r_{(i)}}} {\vec{r_{(i)}}^T \textbf{A} \cdot \vec{r_{(i)}}} \vec{r_{(i)}} \\
&= \vec{e_{(i)}} + \frac{ \sum_{j}^{} \xi_j^2 \lambda_j^2 } { \sum_{j}^{} \xi_j^2 \lambda_j^3 } \vec{r_{(i)}}
\end{split}
\label{error_residual_relation} \tag{6 - 9}
$$
上面我们已经讨论了误差向量$\vec{e_{(i)}}$仅由一个特征向量组成的情况,这种情况下我们只需要令$\alpha_{(i)} = \lambda_e^{-1}$即可实现一次迭代就收敛。现在我们来考虑当误差向量$\vec{e_{(i)}}$为任意向量的情况,同时我们假定所有特征向量的特征值均相同且为$\lambda$,则等式$(\ref{error_residual_relation})$变成:
$$
\begin{split}
\vec{e_{(i + 1)}} &= \vec{e_{(i)}} + \frac{\lambda^2 \sum_{j}^{} \xi_j^2 } { \lambda^3 \sum_{j}^{} \xi_j^2 } (-\lambda \vec{e_{(i)}}) \\
&= 0
\end{split}
\label{error_vector_equals_zero} \tag{6 - 11}
$$
下图再次展示了为何迭代过程最终仍然会收敛。
如上图所示,由于所有特征向量的特征值都相同,二次型函数的椭圆函数等高线此时变成圆形的。因此无论我们从哪个点开始,残差向量也必然指向球心。如前所述,此时应取$\alpha_{(i)} = \lambda^{-1}$。
译者注: 因此时函数等高线为圆形,取任意点都必然位于圆的轴线上,由等高线为椭圆的情况可知,此时必然也能一步收敛。由式($\ref{error_eigenvectors_relationship}$)及式($\ref{residual}$)可知:
$$
\begin{split}
\vec{r_{(i)}} &= -\sum_{j} \xi_j \lambda_j \vec{\upsilon_j}\\
&= -\lambda \sum_{j} \xi_j \vec{\upsilon_j} \\
&= -\lambda \vec{e_{(i)}}
\end{split}
\tag{6 - 11 - 1}
$$
由于误差向量是过圆心的,因此该情况下残差向量也必然过圆心。
然而,当误差向量为任意向量,而此时又存在多个不相等的非零特征值时,找不到能同时消除所有特征向量方向分量的$\alpha_{(i)}$,此时我们在取$\alpha_{(i)}$的值的时候就不得不做一些妥协。实际上,我们最好是把上式($\ref{error_residual_relation}$)中的分数看作对$\lambda_j^{-1}$的加权平均。权重系数$\xi_j^2$确保了误差向量$\vec{e_{(i)}}$的所有分量中,长度越长的分量,其所占的权重越高。
由此,在任意给定的迭代次数下,误差向量较短的特征向量上的分量实际上可能会变长(尽管不会特别长)。因此,最速下降法以及共轭梯度法通常被称作“粗糙算法”,相反雅可比法则是“平滑算法”,因为在每一轮迭代中每一个特征向量方向的分量都会收缩。尽管在数学文献中最速下降法以及共轭梯度法经常被错认,然而它们毕竟不是“平滑算法”。
译者注:由式($\ref{error_eigenvectors_relationship}$)、($\ref{residual}$)、($\ref{residual_by_eigenvector}$)、($\ref{residual_plus_a_matrix}$)、($\ref{error_residual_relation}$)有:
$$
\begin{split}
\vec{e_{(i + 1)}} &= \vec{e_{(i)}} + \frac{ \sum_{j}^{} \xi_j^2 \lambda_j^2 } { \sum_{j}^{} \xi_j^2 \lambda_j^3 } \vec{r_{(i)}} \\
&= \xi_1 \vec{\upsilon_1} + \cdots + \xi_n \vec{\upsilon_n} + \frac{ \xi_1^2 \lambda_1^2 + \cdots + \xi_n^2 \lambda_n^2 } { \xi_1^2 \lambda_1^3 + \cdots + \xi_n^2\lambda_n^3 } (-\xi_1 \lambda_1 \vec{\upsilon_1} - \cdots - \xi_n \lambda_n \vec{\upsilon_n}) \\
&= \xi_1 (1 - \lambda_1 \cdot \frac{ \xi_1^2 \lambda_1^2 + \cdots + \xi_n^2 \lambda_n^2 } { \xi_1^2 \lambda_1^3 + \cdots + \xi_n^2\lambda_n^3 }) \vec{\upsilon_1} + \cdots + \xi_n (1 - \lambda_n \cdot \frac{ \xi_1^2 \lambda_1^2 + \cdots + \xi_n^2 \lambda_n^2 } { \xi_1^2 \lambda_1^3 + \cdots + \xi_n^2\lambda_n^3 }) \vec{\upsilon_n} \\
&= \sum_{j}^{n} \xi_j (1 - \lambda_j \cdot \frac{ \xi_1^2 \lambda_1^2 + \cdots + \xi_n^2 \lambda_n^2 } { \xi_1^2 \lambda_1^3 + \cdots + \xi_n^2\lambda_n^3 }) \vec{\upsilon_j}
\end{split}
\label{next_iter_error_and_eigenvector_relationship} \tag{6 - 11 - 1}
$$
设$\xi_k、\xi_m$分别为第$i$轮迭代后误差向量在特征向量$\vec{\upsilon_k}、\vec{\upsilon_m}$(均不为0)方向上的分量大小,且有$|\xi_k| \ll |\xi_m| $(均不为0),即第$k$个分量长度远小于第$m$个分量。第$i + 1$轮迭代结束后,较大的特征向量分量被大大削弱(或消除),便有:
$$
\xi_m \cdot ( 1 - \lambda_m \cdot \frac{ \xi_1^2 \lambda_1^2 + \cdots + \xi_n^2 \lambda_n^2 } { \xi_1^2 \lambda_1^3 + \cdots + \xi_n^2\lambda_n^3 } ) = \varepsilon, \quad |\varepsilon| \rightarrow 0
\label{eror_mth_component} \tag{6 - 11 - 2}
$$
为了方便,我们用符号$\bigtriangleup$记上式中的分式,即:
$$
\bigtriangleup = \frac{ \xi_1^2 \lambda_1^2 + \cdots + \xi_n^2 \lambda_n^2 } { \xi_1^2 \lambda_1^3 + \cdots + \xi_n^2\lambda_n^3 }
\label{simple_express_of_frac} \tag{6 - 11 - 3}
$$
于是有:
$$
\bigtriangleup =\frac{1}{\lambda_m} \cdot (1 - \frac{\varepsilon} {\xi_m}), \quad \lambda_m \neq 0 ,\ \xi_m \neq 0
\label{frac_value_by_eigens} \tag{6 - 11 - 4}
$$
则第$i + 1$轮迭代结束后,较小的特征向量$\vec{\upsilon_k}$上的分量值变为:
$$
\begin{split}
\xi_k \cdot ( 1 - \lambda_k \cdot \frac{ \xi_1^2 \lambda_1^2 + \cdots + \xi_n^2 \lambda_n^2 } { \xi_1^2 \lambda_1^3 + \cdots + \xi_n^2\lambda_n^3 } ) &= \xi_k \cdot ( 1 - \lambda_k \cdot \bigtriangleup) \\
&= \xi_k \cdot [1 - \frac{\lambda_k}{\lambda_m} \cdot (1 - \frac{\varepsilon} {\xi_m})] \\
\end{split}
\label{error_kth_component_1} \tag{6 - 11 - 5}
$$
第$i + 1$轮迭代和第$i$轮迭代后较小特征向量方向上的分量大小对比有:
$$
\require{cancel}
\begin{split}
\frac{|\xi_k \cdot [1 - \frac{\lambda_k}{\lambda_m} \cdot (1 - \frac{\varepsilon} {\xi_m})]|} {|\xi_k|} &= |\frac{\bcancel{\xi_k} \cdot [1 - \frac{\lambda_k}{\lambda_m} \cdot (1 - \frac{\varepsilon} {\xi_m})]} {\bcancel{\xi_k}}| \\
&= |1 - \frac{\lambda_k}{\lambda_m} \cdot (1 - \frac{\varepsilon} {\xi_m})|
\end{split}
\label{error_kth_component_2} \tag{6 - 11 - 6}
$$
由于$|\varepsilon| \rightarrow 0$,故必有$|\frac{\varepsilon}{\xi_m} |\rightarrow 0$,因此有:
$$
1 - \frac{\varepsilon} {\xi_m} \approx 1
\label{error_kth_component} \tag{6 - 11 - 7}
$$
由此有:
$$
\xi_k \cdot ( 1 - \lambda_k \cdot \frac{ \xi_1^2 \lambda_1^2 + \cdots + \xi_n^2 \lambda_n^2 } { \xi_1^2 \lambda_1^3 + \cdots + \xi_n^2\lambda_n^3 } ) = |1 - \frac{\lambda_k}{\lambda_m}|
\label{error_kth_component_final} \tag{6 - 11 - 8}
$$
由上可知:
a). 当$\frac{\lambda_k}{\lambda_m} > 0$即小分量的特征向量的特征值与大分量的特征向量的特征值同号时,$|1 - \frac{\lambda_k}{\lambda_m}| < 1$,即此时小分量的特征向量长度(相比上一轮迭代后)也在同步缩短;
b). 当$\frac{\lambda_k}{\lambda_m} < 0$即小分量的特征向量的特征值与大分量的特征向量的特征值同号时,$|1 - \frac{\lambda_k}{\lambda_m}| > 1$,即此时小分量的特征向量长度(相比上一轮迭代后)在变长;
同时我们还知道,并非所有的小分量的特性向量长度都会增大(这也是上面为什么用粗体字标出可能
的原因),只有满足上述条件的部分小分量长度才会增大。另外,如果某个小分量长度增长后变成所有分量中较大的分量时,在下一轮迭代中会被削弱,因此这种增长不会持续太久,或者说这个长度不会增长的太大。
感悟:这个结论的证明真是不太容易,我反反复复思考了两周才推敲出来。若有更简单的方法,请务必留言告知。
为了讨论一般情况下最速下降法的收敛性,我们需要定义如下所示的误差向量的能量范式:
$$
||\vec{e}||_\textbf{A} = (\vec{e}^T \textbf{A} \vec{e})^\frac{1}{2}
\label{energe_norm} \tag{6 - 12}
$$
误差向量能量范式的示意图如下:
这种范式要比欧几里德范式更容易处理,并且从某种意义上来说,它是一种更为自然的表现形式。考察等式($\ref{function_of_point_p}$)可知,最小化$||\vec{e_{(i)}}||_\textbf{A}$即等价于最小化$f(\vec{x_{(i)}})$。结合能量范式我们有:
$$
\begin{split}
||\vec{e_{(i+1)}}||_\textbf{A}^2 &= \vec{e_{(i+1)}}^T \textbf{A} \vec{e_{(i+1)}} \\
&= (\vec{e_{(i)}}^T + \alpha_{(i)} \vec{r_{(i)}}^T) \textbf{A} (\vec{e_{(i)}} + \alpha_{(i)} \vec{r_{(i)}}) \quad \quad (据等式\ref{point_after_moved})\\
&= \vec{e_{(i)}}^T \textbf{A} \vec{e_{(i)}} + 2 \alpha_{(i)} \vec{r_{(i)}}^T \textbf{A} \vec{e_{(i)}} + \alpha_{(i)}^2 \vec{r_{(i)}}^T \textbf{A} \vec{r_{(i)}} \quad \quad (由\textbf{A}的对称性) \\
&= ||\vec{e_{(i)}}||_\textbf{A}^2 + 2 \frac{ \vec{r_{(i)}}^T \vec{r_{(i)}} }{ \vec{r_{(i)}}^T \textbf{A} \vec{r_{(i)}} }(-\vec{r_{(i)}}^T \vec{r_{(i)}}) + (\frac{ \vec{r_{(i)}}^T \vec{r_{(i)}} }{ \vec{r_{(i)}}^T \textbf{A} \vec{r_{(i)}} })^2 \vec{r_{(i)}}^T \textbf{A} \vec{r_{(i)}} \\
&= ||\vec{e_{(i)}}||_\textbf{A}^2 - \frac{ (\vec{r_{(i)}}^T \vec{r_{(i)}})^2 }{ \vec{r_{(i)}}^T \textbf{A} \vec{r_{(i)}} } \\
&= ||\vec{e_{(i)}}||_\textbf{A}^2 (1 - \frac{ (\vec{r_{(i)}}^T \vec{r_{(i)}})^2 }{ (\vec{r_{(i)}}^T \textbf{A} \vec{r_{(i)}}) (\underbrace{\vec{e_{(i)}}^T \textbf{A} \vec{e_{(i)}}}_{||\vec{e_{(i)}}||_\textbf{A}^2})}) \\
\\
&= ||\vec{e_{(i)}}||_\textbf{A}^2 (\underbrace{1 - \frac{ (\sum_{j} \xi_j^2 \lambda_j^2)^2 }{ (\sum_{j} \xi_j^2 \lambda_j^3) (\sum_{j} \xi_j^2 \lambda_j)}}_{\omega^2}) \quad \quad (据等式\ref{residual_norm}、\ref{residual_by_eigenvector}、\ref{residual_plus_a_matrix}) \\
&= ||\vec{e_{(i)}}||_\textbf{A}^2 \omega^2
\end{split}
\label{next_iter_energe_norm} \tag{6 - 13}
$$
上式中,$\omega^2 = 1 - \frac{ (\sum_{j} \xi_j^2 \lambda_j^2)^2 }{ (\sum_{j} \xi_j^2 \lambda_j^3) (\sum_{j} \xi_j^2 \lambda_j)}$。
由此可知,一般情况的收敛性分析关键在于找到$w$的一个上界。为了展示权重系数和特征值是如何影响收敛性的,我需要先推导出$n = 2$(即有两个特征向量)情况下的结果。假定$\lambda_1 \ge \lambda_2$,矩阵$\textbf{A}$的谱条件数定义为$\kappa = \frac{\lambda_1}{\lambda_2} \ge 1$(即特征值的比)。误差向量$\vec{e_{(i)}}$的斜率(相对于由特征向量组成的坐标系而言)取决于起点,记为$\mu = \frac{\xi_2}{\xi_1}$(即其沿特征向量上的分量也即权重系数的反比),由此我们有:
$$
\begin{split}
\omega^2 &= 1 - \frac{(\xi_1^2 \lambda_1^2 + \xi_2^2 \lambda_2^2)^2}{ (\xi_1^2 \lambda_1 + \xi_2^2 \lambda_2) (\xi_1^2 \lambda_1^3 + \xi_2^2 \lambda_2^3) } \\
&= 1 - \frac{(\kappa^2 + \mu^2)^2} {(\kappa + \mu^2) (\kappa^3 + \mu^2)} \quad \quad (分子分母同时除以\xi_1^4 \lambda_1^4) \\
\end{split}
\label{omega_expressed_by_weights_and_eigenvalues} \tag{6 - 13}
$$
上式中的$\omega$的值决定了最速下降法收敛的速度(其作用就相当于一个收敛因子),通过上述定义我们把$\omega$表示成了关于$\mu$和$\kappa$的函数,如下图所示:
上图也佐证了我前面列举的两个例子。若$\vec{e_{(0)}}$为一个特征向量,(由于此时误差向量与由特征向量组成的坐标系的坐标轴重合)则此时的斜率$\mu$为0(或者$\infty$),这种情况对应图(6-1)的例子。
由图可知,当$\omega$为0时,收敛几乎是瞬间完成的(即只需要一轮迭代)。若特征值均相等,则条件数$\kappa = 1$,查上图我们同样可得$\omega = 0$,即此时收敛也只需要一轮迭代,这种情况对应图(6-2)的例子。
下图展示了上图中四个角附近的例子。这些二次型函数的图像均绘制在其特征向量组成的坐标系中。其中图(a)和图(b)为大条件数的例子。这种情况下,如果初始点选的够好(图(a)所示,此时起始点位于山腰),那么最速下降法仍然能够快速的收敛。但通常情况下,大条件数时收敛性都是最糟的(图(b)所示)。图(b)给我们直观的展示了为何大条件数时收敛性如此之差:此时由于二次型函数$f(x)$的图像形状为山谷(而起始点又刚好位于谷底附近,斜率小),最速下降法在收敛过程中朝着波谷方向曲折而缓慢的前进,导致其前进速度非常慢(沿某坐标轴前进长度非常短)。
图(c)和图(d)则展示了小条件数的情况,此时由于其二次型函数图象为球形(山峰),因此不管初始点如何选择,由于斜率较大,因此其收敛速度都是非常迅速的。
固定$\kappa$的值(因矩阵$\textbf{A}$已给定),通过计算我们可得式($\ref{omega_expressed_by_weights_and_eigenvalues}$)在$\mu = \pm \kappa$时取得最大值。
译者注:要计算式($\ref{omega_expressed_by_weights_and_eigenvalues}$)的最大值,只需要找到其极值点。这个式子因为包含了高次幂,要求解其极值点需要一定的技巧,否则计算起来非常繁琐还容易出错,这里简单说一下计算过程。
上式展开后有:
$$
\begin{split}
\omega^2 &= 1 - \frac{(\kappa^2 + \mu^2)^2} {(\kappa + \mu^2) (\kappa^3 + \mu^2)} \\
&= \frac{ (\kappa^4 + \kappa \mu^2 + \kappa^3 \mu^2 + \mu^4) - (\mu^4 + 2 \kappa^2 \mu^2 + \kappa^4)} {\mu^4 + (\kappa + \kappa^3) \mu^2 + \kappa^4} \\
&= \underbrace{(\kappa + \kappa^3 - 2 \kappa^2)}_{常量,用c表示} \cdot \frac{ \mu^2 } {\mu^4 + (\kappa + \kappa^3) \mu^2 + \kappa^4} \\
&= c \cdot \frac{ \mu^2 } {\mu^4 + (\kappa + \kappa^3) \mu^2 + \kappa^4}
\end{split}
\label{omega_simply_expressed_by_weights_and_eigenvalues} \tag{6 - 13 - 1}
$$
因为$\kappa$为定值,因此$\kappa + \kappa^3 - 2 \kappa^2$为常量,对求偏导没有影响,因此上式中我们用符号$c$来表示这个系数,方便计算。上式对$\mu$求偏导有:
$$
\require{cancel}
\begin{split}
\frac{\partial \omega^2} {\partial \mu} &= \frac{\partial(c \cdot \frac{ \mu^2 } {\mu^4 + (\kappa + \kappa^3) \mu^2 + \kappa^4})} {\partial \mu} \\
&= -c \cdot \frac{2 \mu [\mu^4 + (\kappa + \kappa^3) \mu^2 + \kappa^4] - \mu^2 [4 \mu^3 + 2 (\kappa + \kappa^3) \mu]} {[\mu^4 + (\kappa + \kappa^3) \mu^2 + \kappa^4]} \\
&= -c \cdot \frac{2 \mu [\bcancel{\mu^4} + \bcancel{(\kappa + \kappa^3) \mu^2} + \kappa^4 - \bcancel{2} \mu^4 - \bcancel{ (\kappa + \kappa^3) \mu^2 }]} {[\mu^4 + (\kappa + \kappa^3) \mu^2 + \kappa^4]} \\
&= c \cdot \frac{2 \mu(\mu^4 - \kappa^4)} {[\mu^4 + (\kappa + \kappa^3) \mu^2 + \kappa^4]}
\end{split}
\label{omega_simply_expressed_by_weights_and_eigenvalues2} \tag{6 - 13 - 2}
$$
令上式等于0,即可求出所有的极值点。由于$\kappa > 0$恒成立,因此上式中的分母也恒为正值。因此我们只需考虑系数和分子为0的情况:
a). 若系数$c = 0$,此时可解出$\kappa = 0或1$,则$\omega^2 = 0$;
b). 若系数$c \neq 0$即$\kappa \neq 0, \ \kappa \neq 1$,此时可由$\mu(\mu^4 - \kappa^4) = 0$解出$\mu = \pm \kappa$($\mu = 0$舍去),则$\omega^2 = (\frac{\kappa - 1}{\kappa + 1})^2 > 0$;
因此可知,当且仅当$\mu = \pm \kappa$时,式($\ref{omega_expressed_by_weights_and_eigenvalues}$)取得最大值。
上图(6-4)中隐约可见由这两条直线($\mu = \pm \kappa$)所定义的山脊。下图绘制出了我们前面给定的矩阵$\textbf{A}$的最差的收敛性的起始点。这些点落在由$\frac{\xi_2}{\xi_1} = \pm \kappa$所定义的直线上(或者反过来想也成立,这些收敛性最差的点组成的直线即为$\frac{\xi_2}{\xi_1} = \pm \kappa$)。
通过将$\mu$的值置为$\mu^2 = \kappa^2$,我们即可得到$\omega$的上界(对应于收敛性最差时候的起点),如下所示:
$$
\require{cancel}
\begin{split}
\omega^2 &\le 1 - \frac{4 \kappa^4}{\kappa^5 + 2 \kappa^4 + \kappa^3} \\
&= \frac{\kappa^5 - 2 \kappa^4 + \kappa^3}{\kappa^5 + 2 \kappa^4 + \kappa^3} \\
&= \frac{\bcancel{\kappa^3} (\kappa - 1)^2}{\bcancel{\kappa^3} (\kappa + 1)^2} \\
&= (\frac{\kappa - 1}{\kappa + 1})^2
\end{split}
\label{omega_power_expressed_by_kappa} \tag{6 - 14}
$$
进而有:
$$
\omega \le \frac{\kappa - 1}{\kappa + 1}
\label{omega_expressed_by_kappa} \tag{6 - 15}
$$
我们将上述不等式绘制成图像如下所示:
给定的矩阵越病态(即其条件数$\kappa$越大),最速下降法收敛速度就越慢。在后面的第9.2节中我们还证明了,当$n \ge 2$的时候,等式($\ref{omega_expressed_by_kappa}$)依然成立。若我们将对称正定阵的条件数定义为其最大特征值与其最小特征值之比:
$$
\kappa = \frac{\lambda_{max}} {\lambda_{min}}
\label{condition_number_of_symmetric_positive_matrix} \tag{6 - 16}
$$
则最速下降法的收敛结果可表示为:
$$
||\vec{e_{(i)}}||_\textbf{A} \le (\frac{\kappa - 1}{\kappa + 1})^i ||\vec{e_{0}}||_\textbf{A}
\label{convergence_result} \tag{6 - 17}
$$
同时由等式($\ref{function_of_point_p}$)有:
$$
\require{cancel}
\begin{split}
\frac{f(\vec{x_{(i)}}) - f(\vec{x})}{f(\vec{x_{(0)}}) - f(\vec{x})} &= \frac{ \frac{1}{2} \vec{e_{(i)}}^T \textbf{A} \vec{e_{(i)}} } {\frac{1}{2} \vec{e_{(0)}}^T \textbf{A} \vec{e_{(0)}} } \\
&\le (\frac{\kappa - 1}{\kappa + 1})^{2i}
\end{split}
\label{quaratic_form_convergence_result} \tag{6 - 18}
$$
译者注:为了方便读者理解,这里多写一步,给出上式的来源。
$$
\require{cancel}
\begin{split}
f(\vec{x_{(i)}}) - f(\vec{x}) &= [\bcancel{ f(\vec{x}) } + \frac{1}{2} (\vec{ x_{(i)} } - \vec{x})^T \textbf{A} (\vec{ x_{(i)} } - \vec{x})] - [\bcancel{ f(\vec{x}) } + \frac{1}{2} (\vec{ x } - \vec{x})^T \textbf{A} (\vec{ x } - \vec{x})] \\
&= \frac{1}{2} (\vec{ x_{(i)}} - \vec{x})^T \textbf{A} (\vec{ x_{(i)} } - \vec{x}) \\
&= \frac{1}{2} \vec{e_{i}}^T \textbf{A} \vec{e_{i}}
\end{split}
\label{prove_of_convergence_result} \tag{6 - 18 - 1}
$$
上式中,$\vec{x}$为二次型函数的最小值点。
如前述图(4-6)所示,最速下降法在收敛中其早期的迭代步骤往往在沿着同一个方向。如果我们每次迭代前,都能选择正确的方向迈出去,效果不是会更好吗?一个可行的想法是:我们选择一系列(相互)正交的搜索方向$\vec{d_{(0)}}, \vec{d_{(1)}}, \cdots, \vec{d_{(n-1)}}$,在每一个搜索方向上我们都只移动一步,而这一步的长度又刚好能够均匀的对齐到最小值点$\vec{x}$(其实就是刚好能够消除误差向量在这个方向上的分量),$n$步之后,迭代结束,到达最小值点。
下图展示了该想法:
上图中,利用了两个(正交的)坐标轴作为搜索方向。第一步(水平向)指向正确的$x_1$坐标轴方向,第二步即(垂直向)直捣黄龙。注意第一轮迭代后的误差向量$\vec{e_{1}}$与$第一步的搜索方向\vec{d_{(0)}}$正交。一般的,每一步我们按照下式选择下一个前进的点:
$$
\vec{x_{(i+1)}} = \vec{x_{(i)}} + \alpha_{(i)} \cdot \vec{d_{(i)}}
\label{choose_next_point} \tag{7 - 1}
$$
上式中,$\vec{x_{(i+1)}}$为我们下一步要移动到的位置(点),$\vec{x_{(i)}}$为当前位置点。$\alpha_{(i)}$为下一步移动的步长,$\vec{d_{(i)}}$为下一步的移动方向(搜索方向)。有了这些说明,上式就很好理解了。
为了确定步长$\alpha_{(i)}$的值,我们需要利用误差向量$\vec{e_{(i+1)}}$与搜索方向向量$\vec{d_{(i)}}$正交的这一关系,同时我们还能避免后面的过程中再次往$\vec{d_{(i)}}$这个方向搜索。据此条件我们有:
$$
\begin{split}
\vec{d_{(i)}}^T \vec{e_{(i+1)}} &= 0 \\
\vec{d_{(i)}}^T ( \vec{e_{(i)}} + \alpha_{(i)} \vec{d_{(i)}}) &= 0 \qquad (据式\ref{choose_next_point})\\
\alpha_{(i)} &= -\frac{\vec{d_{(i)}}^T \vec{e_{(i)}}} {\vec{d_{(i)}}^T \vec{d_{(i)}}}
\end{split}
\label{step_expressed_by_direction_and_error} \tag{7 - 2}
$$
译者注:上面提到了第$i+1$轮迭代时的误差向量$\vec{e_{(i+1)}}$与第$i$轮的搜索方向向量$\vec{d_{(i)}}$正交,这个结论对某些读者来说由于没有足够的背景铺垫而显得有点突兀,导致很难理解。我们这里简单给个证明。
由于这个求解方法的核心思想是:给定一个起始点后,沿着一系列相互正交的方向$\vec{d_{(i)}}$前进,最终就能到达我们的目标值(最小值)点$\vec{x}$。也就是说,从起点指向目标值点的向量,等于这一系列相互正交的方向向量的和,假定有$n$个正交的方向向量,则有:
$$
\vec{e_{(0)}} = \vec{x} - \vec{x_{(0)}} = \alpha_{(0)} \cdot \vec{d_{(0)}} + \cdots + \alpha_{(n)} \cdot \vec{d_{(n)}}
\label{initial_error_composed_by_directions} \tag{7 - 2 - 1}
$$
上式也可以理解为:初始误差向量是由各个搜索方向向量复合而成。求解目标值的整个过程,也就是沿着这一系列搜索路径不断向终点靠拢的过程。第一轮迭代(迈第一步)后,从起始点$\vec{x_{(0)}}$到达整个搜索路径的第一个点$\vec{x_{(1)}}$,于是有:
$$
\begin{split}
\vec{x_{(1)}} &= \vec{x_{(0)}} + \alpha_{(0)} \cdot \vec{d_{(0)}} \\
\vec{x} - \vec{x_{(1)}} &= \vec{x} - (\vec{x_{(0)}} + \alpha_{(0)} \cdot \vec{d_{(0)}}) \\
&= (\vec{x} - \vec{x_{(0)}}) - \alpha_{(0)} \cdot \vec{d_{(0)}} \\
&= \vec{e_{(0)}} - \alpha_{(0)} \cdot \vec{d_{(0)}} \\
\vec{e_{(1)}} &= \vec{e_{(0)}} - \alpha_{(0)} \cdot \vec{d_{(0)}} = \alpha_{(1)} \cdot \vec{d_{(1)}} + \cdots + \alpha_{(n)} \cdot \vec{d_{(n)}}\\
\vec{e_{(0)}} - \vec{e_{(1)}} &= \alpha_{(0)} \cdot \vec{d_{(0)}}
\end{split}
\label{relationship_between_initial_1st_error} \tag{7 - 2 - 2}
$$
由上式可以看出,每一轮迭代的作用,就是刚好把该轮搜索方向上的误差分量消除。据此可知,在第$i+1$轮迭代时的误差向量:
$$
\begin{split}
\vec{e_{(i+1)}} &= \vec{e_{(0)}} - \alpha_{(0)} \cdot \vec{d_{(0)}} - \cdots - \alpha_{(i)} \cdot \vec{d_{(i)}} \\
&= \alpha_{(i+1)} \cdot \vec{d_{(i+1)}} + \cdots + \alpha_{(n)} \cdot \vec{d_{(n)}}
\end{split}
\label{ith_itered_components_of_directions} \tag{7 - 2 - 3}
$$
考察此时的误差向量和上一轮搜索方向的关系有:
$$
\begin{split}
\vec{d_{(i)}} \cdot \vec{e_{(i+1)}} &= \vec{d_{(i)}} \cdot [ \alpha_{(i+1)} \cdot \vec{d_{(i+1)}} + \cdots + \alpha_{(n)} \cdot \vec{d_{(n)}} ] \\
&= \alpha_{(i+1)} \cdot \underbrace{ \vec{d_{(i)}} \cdot \vec{d_{(i+1)}} }_{0} + \cdots + \alpha_{(n)} \cdot \underbrace{ \vec{d_{(i)}} \cdot \vec{d_{(n)}} }_{0} \\
&= 0 \qquad \vec{d_{(i)}} \cdot \vec{d_{(j)}} =
\begin{cases}
0, \ i \ne j \\
||\vec{d_{(i)}}||^2, \ i = j
\end{cases}
\end{split}
\label{relation_between_last_direction_and_current_error} \tag{7 - 2 - 4}
$$
即本轮的误差向量和上一轮的搜索方向必然正交。
上式中我们已经求得了步长$\alpha_{(i)}$的解析式,然而这还不是最终的解,因为不知道误差向量$\vec{e_{(i)}}$的值,我们仍然是无法求出$\alpha_{(i)}$的。
解决办法就是用矩阵·向量正交(A-orthogonal)替代向量正交(orthogonal)。所谓的矩阵·向量正交(或共轭),是指存在两个向量$\vec{d_{(i)}}、\vec{d_{(j)}}$,满足:
$$
\vec{d_{(i)}}^T \textbf{A} \vec{d_{(j)}} = 0 , \quad i \ne j
\label{a_orthogonal} \tag{7 - 3}
$$
译者注:上式中后面的限制条件$i \ne j$在作者原文中并没有直接给出,但是根据其上下文的叙述可以得出此结论。此结论在后面公式($\ref{mathematical_trick_to_solve_deltas}$)的推断中需要用到,这里先给出来好让读者心里有数。
下图展示了与矩阵正交的向量的样子。
如上图所示,可以明显看出这些向量对并非正交的。想象一下,如果我们把上图印在一块泡泡糖上,然后拉着泡泡糖的上下两边向两边轻轻的撕扯直到泡泡糖上椭圆形的图案变成圆形为止。这个时候图形中的向量对就变成正交的了,看起来也更为清爽,如下图所示:
译者注:这里作者之所以举一个拉扯泡泡糖的例子,其实是想说明,拉扯泡泡糖这个动作就对应于向量$\vec{d_{(i)}}$和矩阵$\textbf{A}$的乘积。这个乘积的本质是对向量$\vec{d_{(i)}}$进行变换(变换无非就是线性和非线性两种,变换的效果也无非就是平移、旋转、缩放等等这些效果的复合)。
由此我们的前提条件就不再是要求搜索方向两两正交,而是变成了本轮迭代后的误差向量和上轮的搜索方向向量两两矩阵正交即$\vec{d_{(i)}} \textbf{A} \vec{e_{(i+1)}} = 0$。真是无巧不成书,这种矩阵正交性条件刚好与最速下降法中沿着搜索方向找寻最小值点等价。为了证实这一结论,我们令第$i+1$轮迭代时二次型函数的方向导数为0,如下:
$$
\begin{split}
\frac{d}{d_\alpha} f(\vec{x_{(i+1)}}) &= f’(\vec{x_{(i+1)}})^T \frac{d}{d_\alpha} \vec{x_{(i+1)}} &\qquad (据式\ref{directional_derivative_of_quadratic_function})\\
&= -\vec{r_{(i+1)}}^T \vec{d_{(i)}} \\
&= -(\vec{d_{(i)}}^T \vec{r_{(i+1)}})^T \\
&= (\vec{d_{(i)}}^T \textbf{A} \vec{e_{(i+1)}})^T = 0 &\qquad (据式\ref{residual}) \\
\therefore \quad \vec{d_{(i)}}^T \textbf{A} \vec{e_{(i+1)}} &= 0
\end{split}
\label{set_directional_derivative_to_zero} \tag{7 - 4}
$$
由上式我们可以得到步长在矩阵·向量正交的条件下的表达式,如下所示:
$$
\begin{split}
\alpha_{(i)} &= -\frac{\vec{d_{(i)}}^T \textbf{A} \vec{e_{(i)}}} {\vec{d_{(i)}}^T \textbf{A} \vec{d_{(i)}}} \\
&= \frac{\vec{d_{(i)}}^T \vec{r_{(i)}}} {\vec{d_{(i)}}^T \textbf{A} \vec{d_{(i)}}}
\end{split}
\label{alpha_expressed_by_residual_and_direction} \tag{7 - 5}
$$
译者注:要得到上述结论,只需要把式($\ref{step_expressed_by_direction_and_error}$)中的正交条件换成矩阵·向量正交条件即能推导出上述结论。推导过程非常简单,在此不赘述。
与式($\ref{step_expressed_by_direction_and_error}$)所不同的是,上式我们是可以直接进行计算的。注意,当搜索方向向量$\vec{d_{(i)}}$即为残差向量时,上式与最速下降法的计算公式($\ref{formula_of_step_size}$)完全等价。
为了求证上述方法对最小值点的计算刚好在$n$步内完成,不多不少。我们现在用搜索方向向量的线性组合来表示误差向量,即:
$$
\vec{e_{(0)}} = \sum\limits_{j=0}^{n-1} \delta_{j} d_{(j)} \\
\label{error_expressed_by_directions} \tag{7 - 6}
$$
式中分量值$\delta_{j}$的确定可以通过使用一些小小的数学技巧完成。由于搜索方向为两两矩阵·向量正交的,因此将上式($\ref{error_expressed_by_directions}$)左右两边同时左乘$\vec{d_{(k)}}^T \textbf{A}$即可消除$\delta_{k}$以外的所有分量值。整个过程如下所示:
$$
\begin{split}
\vec{d_{(k)}}^T \textbf{A} \vec{e_{(0)}} &= \sum\limits_{j=0}^{n-1} \delta_{j} \vec{d_{(k)}}^T \textbf{A} \vec{d_{(j)}} \\
&= \delta_{0} \underbrace{ \vec{d_{(k)}}^T \textbf{A} \vec{d_{(0)}} }_{0} + \cdots + \delta_{k} \vec{d_{(k)}}^T \textbf{A} \vec{d_{(k)}} + \cdots + \delta_{n} \underbrace{ \vec{d_{(k)}}^T \textbf{A} \vec{d_{(n)}} }_{0} \\
&= \delta_{k} \vec{d_{(k)}}^T \textbf{A} \vec{d_{(k)}} \\
\therefore \qquad \delta_{k} &= \frac{\vec{d_{(k)}}^T \textbf{A} \vec{e_{(0)}}} {\vec{d_{(k)}}^T \textbf{A} \vec{d_{(k)}}} \\
&= \frac{ \vec{d_{(k)}}^T \textbf{A} \vec{e_{(0)}} + \underbrace{ \sum\limits_{i=0}^{k-1} \alpha_{(i)} \overbrace{ \vec{d_{(k)}}^T \textbf{A} \vec{d_{(i)}} }^{矩阵·向量正交性} }_{0} } {\vec{d_{(k)}}^T \textbf{A} \vec{d_{(k)}}} \\
&= \frac{ \vec{d_{(k)}}^T \textbf{A} ( \vec{e_{(0)}} + \sum\limits_{i=0}^{k-1} \alpha_{(i)} \vec{d_{(i)}} ) } {\vec{d_{(k)}}^T \textbf{A} \vec{d_{(k)}}} \\
&= \frac{\vec{d_{(k)}}^T \textbf{A} \vec{e_{(k)}}} {\vec{d_{(k)}}^T \textbf{A} \vec{d_{(k)}}}
\end{split}
\label{mathematical_trick_to_solve_deltas} \tag{7 - 7}
$$
据上式及式子($\ref{alpha_expressed_by_residual_and_direction}$)可得到步长$\alpha_{(i)}$的计算公式:$\alpha_{(i)} = -\delta_{(i)} $。这个结论让我们可以以一种全新的视角来审视误差向量:从给定的起始点开始,逐项重构目标值$\vec{x}$的分量去逼近目标值$\vec{x}$的整个过程也可以看作是逐项消除误差向量的分量的过程,如下式所示:
$$
\begin{split}
\vec{e_{(i)}} &= \vec{e_{(0)}} + \sum\limits_{j=0}^{i-1} \underbrace{\alpha_{(j)}}_{-\delta{(j)}} \vec{d_{(j)}} \\
&= \sum\limits_{j=0}^{n-1} \delta_{(j)} \vec{d_{(j)}} - \sum\limits_{j=0}^{i-1} \delta_{(j)} \vec{d_{(j)}} \\
&= \sum\limits_{j=i}^{n-1} \delta_{(j)} \vec{d_{(j)}}
\end{split}
\label{building_up_x_component_by_component} \tag{7 - 8}
$$
上述过程更为直观的图示展示如下:
由上图及公式可知,当且仅当$n$轮迭代完成后,误差向量的每一个分量都已经被消除,此时$\vec{e_{(n)}} = 0$,算法收敛。结论得证!
由上节可知,要求的目标值,我们现在唯一需要的就是一组矩阵·向量正交的搜索方向向量${ \vec{d_{(i)}} }$的集合。幸运的是,存在一种非常简单的方法计算这些向量,这种方法我们称之为共轭格莱姆-施密特过程。
假定我们现在有一组$n$个维线性无关(线性独立)的向量$\vec{u_{(0)}}, \vec{u_{(1)}}, \cdots, \vec{u_{(n-1)}}$。为了构建向量$\vec{d_{(i)}}$,我们先取一个向量$\vec{u_{(i)}}$,然后减掉该向量的所有分量中与之前的所有搜索方向向量$\vec{d_{(i-1)}}$都不成矩阵·向量正交关系的那些(即只保留与之前的所有搜索方向均矩阵·向量正交的分量)。换句话说,对第一步($i = 0$)的搜索方向向量我们取$\vec{d_{(i)}} = \vec{u_{(0)}}$。而对于之后的每一步($i > 0$),取:
$$
\vec{d_{(i)}} = \vec{u_{(i)}} + \sum\limits_{k=0}^{i-1} \beta_{(ik)} \vec{d_{(k)}} \\
\label{construct_value_of_d} \tag{7 - 9}
$$
译者注:这里的几句话读起来可能有点拗口,原文写的更简单,给充分理解这个方法的思想制造了不小的难度。其实这个共轭格莱姆-施密特过程的思想很简单。
Step:1 先找一组线性无关的向量。在这些向量里面任取一个,作为第一步的搜索方向向量(因为是第一步,不需要考虑正交性,因此可直接使用该向量作为搜索向量)。
Step:2 再在剩下的这些线性无关的向量中取一个出来进行向量分解。我们知道一个向量可以有很多种分解方式,那么怎么决定如何分解呢?首先找到与上一步的搜索向量成矩阵·向量正交的方向,该方向即作为这个要分解的向量的一个分量方向,这个方向的分量也将保留下来作为本轮迭代的搜索方向向量。其它方向的分量因为对我们没有用,自然就要消除掉。
Step:3 再在剩下的线性无关的向量中取一个出来进行向量分解。分解的方法就是,首先找一个与之前所有搜索向量均矩阵·向量正交的方向,留下这个方向的向量作为本轮的搜索方向向量。其余的分量同样消除掉。
Step:4 重复第3)步的过程,直到所有的线性无关的向量都抽完为止。
可以看出,通过上述步骤,每一步所保留下来的分量均与之前的所有搜索向量矩阵·向量正交。
式中,系数$\beta_{(ik)}$仅定义在$i > k$的情况。为了求得这些系数的值,我们可采用前面求解$\delta_{(j)}$的时候的小技巧,整个求解过程如下所示:
$$
\begin{split}
\vec{d_{(i)}}^T \textbf{A} \vec{d_{(j)}} &= \vec{u_{(i)}} \textbf{A} \vec{d_{(j)}} + \sum\limits_{k=0}^{i-1} \beta_{(ik)} d\vec{_{(k)}}^T \textbf{A} \vec{d_{(j)}} \\
&= \vec{u_{(i)}} \textbf{A} \vec{d_{(j)}} + \beta_{(ij)} d\vec{_{(j)}}^T \textbf{A} \vec{d_{(j)}}, \qquad i > j = 0 \\
\therefore \qquad \beta_{(ij)} &= -\frac{\vec{u_{(i)}} \textbf{A} \vec{d_{(j)}}} {d\vec{_{(j)}}^T \textbf{A} \vec{d_{(j)}}}
\end{split}
\label{trick_to_solve_beta} \tag{7 - 10}
$$
整个过程的示意图如下(图示为二维向量):
在共轭方向法中使用共轭格莱姆-施密特过程
(计算搜索向量)的一个不利的地方是,每一轮新的迭代之前的所有搜索向量都需要保存下来,以便用来计算本轮的搜索方向,这就导致要计算出所有的搜索向量,其操作的复杂度为$\mathcal{O}(n^3)$。实际上,如果搜索向量由轴向单位向量的共轭组成,那么共轭方向等价于进行高斯消元法,如下图所示:
正因如此,直到CG算法发明之前,共轭方向法都不太受人重视。CG算法弥补了共轭方向法的诸多缺点。
理解共轭方向法以及共轭梯度法的一个关键点就在于,要能够看出来上图实际上只是图(7-1)的更一般的情况(等高线为椭圆形)。总之,当我们在使用共轭方向法(包括CG算法)时,我们同时也是在一个缩放的空间里执行正交方向法。
共轭方向法有一个非常有趣的特性:在每一轮迭代的过程中,在其允许的搜索范围内,它总能找到最优解。那么我们不禁要问,允许的搜索范围又是什么?我们记$\mathcal{D_i}$为$i$维的张成空间$Span \lbrace \vec{d_{(0)}}, \vec{d_{(1)}}, \cdots, \vec{d_{(i-1)}} \rbrace$。误差项$\vec{e_{(i)}}$的值从$\vec{e_{(0)}} + \mathcal{D_i}$中选取。
那么我们上面提到的最优解是指什么呢?这里的最优解实际上是指共轭方向法从$\vec{e_{(0)}} + \mathcal{D_i}$中选取的能让其误差向量的能量范式$||\vec{e_{(i)}}||_\textbf{A}$最小(即能量最低)的误差向量,如下图所示:
实际上,某些作者喜欢通过在张成空间$\vec{e_{(0)}} + \mathcal{D_i}$上最小化$||\vec{e_{(i)}}||_\textbf{A}$的方法来推导出CG算法。
与误差项向量可以表示为搜索方向向量的线性组合类似(式$\ref{building_up_x_component_by_component}$),误差向量的能量范式也可以表示为累和的形式,如下所示:
$$
\begin{split}
||\vec{e_{(i)}}||_\textbf{A}^2 &= ||\vec{e_{(i)}}||^T \textbf{A} ||\vec{e_{(i)}}|| &\qquad (据式\ref{energe_norm})\\
&= (\sum\limits_{j=i}^{n-1} \delta_{(j)} \vec{d_{(j)}})^T \textbf{A} (\sum\limits_{k=i}^{n-1} \delta_{(k)} \vec{d_{(k)}}) &\quad (据式\ref{building_up_x_component_by_component})\\
&= (\sum\limits_{j=i}^{n-1} \delta_{(j)} \vec{d_{(j)}^T}) \textbf{A} (\sum\limits_{k=i}^{n-1} \delta_{(k)} \vec{d_{(k)}}) \\
&= \underbrace{ \underbrace{ (\delta_{(i)} \vec{d_{(i)}}^T \textbf{A} + \cdots + \delta_{(n-1)} \vec{d_{(n-1)}}^T) }_{n-i项} \cdot \underbrace{ (\delta_{(i)} \vec{d_{(i)}}^T + \cdots + \delta_{(n-1)} \vec{d_{(n-1)}}^T) }_{n-i项} }_{(n-i) \times (n-i) = (n-i)^2项} \\
&= \sum\limits_{j=i}^{n-1} \sum\limits_{k=i}^{n-1} \delta_{(j)} \delta_{(k)} \vec{d_{(j)}}^T \textbf{A} \vec{d_{(k)}} \\
&= \sum\limits_{j=i}^{n-1} \delta_{(j)}^2 \vec{d_{(j)}}^T \textbf{A} \vec{d_{(j)}} &\quad (据矩阵·向量正交性)\\
\end{split}
\label{summation_express_of_error_energe_norm} \tag{7 - 11}
$$
上述累和项中的每一项都与一项尚未使用过的搜索向量相关联。所有从$\vec{e_{(0)}} + \mathcal{D_i}$中选择的任意向量$\vec{e}$的展开式同样也是包含上述这些项,因此$\vec{e_{(i)}}$必然具有最小的能量范式(因任意两个方向向量的矩阵乘积全为0,原累和项变成各个方向向量的线性组合)。
通过方程证明了解的最优性之后,我们来点更直观的东西。也许最好的可视化展示共轭方向法原理的途径是通过对比我们正在使用的空间和上图(7-3)所示被拉伸过的空间。下图(a)和(c)演示了在二维($\mathbb{R}^2$)及三维($\mathbb{R}^3$)空间中的共轭方向法的样子,这些图中看起来垂直的线是正交的,如下所示:
下图(b)和(d)则分别对应上图(a)、(c)经过拉伸(沿着特征向量轴向)后等高线由椭圆变成圆的情况,这些图中看起来垂直的线是矩阵·向量正交的,如下图所示:
如上图(a)所示,共轭方向法起始于点$\vec{x_{(0)}}$,沿着方向$\vec{d_{(0)}}$前进了一步然后停留在点$\vec{x_{(1)}}$处,该点处的误差向量$\vec{e_{(1)}}$与向量$\vec{d_{(0)}}$矩阵·向量正交。那么我们何以肯定该点即为$\vec{x_{(0)}} + \mathcal{D_1}$上的最小值点呢?
答案就在于图(b)中:在图(b)所示的拉伸空间中,第一轮迭代后的误差向量$\vec{e_{(1)}}$由于与第一轮的方向向量$\vec{d_{(0)}}$矩阵·向量正交,因此他们是垂直的。此时误差向量$\vec{e_{(1)}}$为能量范式$||\vec{e}||_\textbf{A}$为定值的等高线所对应的一系列同心圆的半径。因此$\vec{x_{(0)}} + \mathcal{D_1}$必然与过点$\vec{x_{(1)}}$的圆相切与点$\vec{x_{(1)}}$处。因此$\vec{x_{(1)}}$为平面$\vec{x_{(0)}} + \mathcal{D_1}$上使得$||\vec{e_{(1)}}||_\textbf{A}$取的最小值的点。
译者注:这里原作者的意思其实是,在给定目标点$\vec{x}$和起始点$\vec{x_{(0)}}$、前进方向$\vec{x_{(0)}} + \mathcal{D}_1$的条件下,如何确定前进的步长才能使前进的路径是最短的。实际上就是等价于点到直线的最短距离(垂线)。
这里最开始我理解成给定目标点和起始点,如何确定最短路径,导致卡了很久。
这个似乎并没有什么惊奇之处。在前面的7.1节中我们已经讲了搜索方向向量和误差项的共轭性等价于沿着搜索方向最小化二次型函数$f$(也即$||\vec{e}||_{\textbf{A}}$)的值。然而,在共轭方向法迈出第二步时,当我们在第二搜索方向$\vec{d_1}$上最小化$||\vec{e}||_{\textbf{A}}$的时候,为何还有信心确保$||\vec{e}||_{\textbf{A}}$在上一轮的搜索方向$\vec{d_0}$上式最小的呢?同理,在我们迈出第$i$步的时候,$f(\vec{x_{(i)}})$为何仍然能在所有的$\vec{x_{(0)}} + \mathcal{D}_i$上保持最小值呢?
在图7-9(b)中,由于$\vec{d_{(0)}}$和$\vec{d_{(1)}}$是矩阵·向量正交的,因此他们看起来像是垂直的。毫无疑问,由于$\vec{d_{(0)}}$与圆心为$\vec{x}$的圆相切于点$\vec{x_{(1)}}$,所以$\vec{d_{(1)}}$必定指向解$\vec{x}$(因此即使迈出了第二步,在$\vec{d_0}$方向上$||\vec{e}||_{\textbf{A}}$依然是最小的)。在三维例子中可能更为直观,图7-8(c)和图7-9(d)均展示了两个同心圆。点$\vec{x_{(1)}}$位于外面的圆上,点$\vec{x_{(2)}}$则位于里面的圆上。仔细观察这两幅图我们可以发现:平面$\vec{x_{(0)}} + \mathcal{D}_2$穿过较大的椭圆(外面的圆),同时与较小(里面的圆)的圆相切于点$\vec{x_{(2)}}$。点$\vec{x}$为椭圆中心,位于割平面$\vec{x_{(0)}} + \mathcal{D}_2$下。
据图7-8(c),我们重新描述我们的问题:假定我们站在点$\vec{x_{(1)}}$,我们想到达平面$\vec{x_{(0)}} + \mathcal{D}_2$上使得$||\vec{e}||$最小的点,但同时我们移动的方向只能限制在$\vec{d_{(1)}}$上。如果$\vec{d_{(1)}}$本身就指向最小值点,沿着这个方向我们自然能到达最小值点。那$\vec{d_{(1)}}$凭什么就一定会指向最小值点的方向呢?
图7-9(d)给出了答案。由于$\vec{d_{(1)}}$与$\vec{d_{(0)}}$矩阵·向量正交,因此在此图中两个向量相互垂直。现在假定我们正在俯瞰平面$\vec{x_{(0)}} + \mathcal{D}_2$,就好像它是一张纸,那么我们所看到的与图7-9(b)是一样的。点$\vec{x_{(2)}}$为整张纸的中心点,而点$\vec{x}$则刚好位于$\vec{x_{(2)}}$的正下方。由于$\vec{d_{(0)}}$和$\vec{d_{(1)}}$相互垂直,因此$\vec{d_{(1)}}$必然指向点$\vec{x_{(2)}}$(过切点且垂直于切线的线必然经过圆心,这里点$\vec{x_{(2)}}$既然是整张纸的中心,自然也是圆心。),该点$\vec{x_{(2)}}$也是平面$\vec{x_{(0)}} + \mathcal{D}_2$上距离$\vec{x}$最近的点。平面$\vec{x_{(0)}} + \mathcal{D}_2$与点$\vec{x_{(2)}}$所在的球体(以点$\vec{x}$为圆心,点$\vec{x_{(2)}}$到$\vec{x}$的距离为半径的球)相切。这时候如果要迈出第三步,必然是从点$\vec{x_{(2)}}$处直接下降到点$\vec{x}$,这个方向与$\mathcal{D}_2$矩阵·向量正交。
译者注:要理解上面这段话关键在于以下几点:
- 1. 图7-8(c)、图7-9(d)都要看成三维而非二维图形;
- 2. 点$\vec{x_{(2)}}$位于点$\vec{x}$正上方而不是都在$\vec{x_{(0)}} + \mathcal{D}_2$平面上;
另外一种理解图7-9(d)的思路是,想象自己站在目标值点$\vec{x}$处,拽一根串到
$\vec{x_{(0)}} + \mathcal{D}_i$上的一颗珠子上的线。每次张成子空间$\mathcal{D}$维度增加1,珠子就能自由的移动到离你更近的地方(维度增加了,珠子移动的方向也就更多了,就能更靠近你)。最后你再把该图拉伸来变成图7-8(c)的样子,那就是我们说的共轭方向法了。
共轭方向的另一个非常重要的特性在上述图示中也是非常显见的。我们知道,每一步中的超平面$\vec{x_{(0)}} + \mathcal{D}_i$均与点$\vec{d_{(i)}}$所在的椭球体相切。回顾一下第四章中我们提到的,任意点的残差向量均与该点处的椭球面正交。于是有残差向量$\vec{r_{(i)}}$与$\mathcal{D}_i$也正交。为了证明这个特性,我们将等式$\ref{building_up_x_component_by_component}$左右两边同时左乘$-\vec{d_{(i)}}^T \textbf{A}$有:
$$
\begin{split}
-\vec{d_{(i)}^T} \textbf{A} \vec{e_{(j)}} &= -\sum\limits_{j=i}^{n-1} \delta_{(j)} \vec{d_{(i)}}^T \textbf{A} \vec{d_{(j)}} \\
\vec{d_{(i)}}^T \vec{r_{(j)}} &= 0 , \qquad i < j
\end{split}
\label{math_equation_of_residual_orthogonal_to_sllipsoidal_surface} \tag{7 - 12}
$$
我们还可以用另外的方法推导出这个结论。回顾之前我们讲的,每当我们沿着某个方向迈出一步之后,后续过程中就不再会往这个方向走了。则误差项与之前的所有搜索方向永远是矩阵·向量正交的。又因为$\vec{r_{(j)}} = - \textbf{A} \vec{e_{(j)}}$,故残差向量也必定与已经搜索过的方向正交。
由于搜索向量是由$\vec{u}$向量构建的,$\mathcal{D_i}$即为向量$\vec{u_0}, \cdots, \vec{u_{i-1}}$张成的子空间,由此残差向量$\vec{r_{(j)}}$与这些$\vec{u}$向量均正交(下图所示):
该结论的证明只需要将等式$\ref{construct_value_of_d}$左右两边分别与残差向量$\vec{r_{(j)}}$取内积即可,如下所示:
$$
\begin{split}
\vec{d_{(i)}}^T \vec{r_{(j)}} &= \vec{u_{(i)}}^T \vec{r_{(j)}} + \sum\limits_{k=0}^{i-1} \beta_{(ik)} \vec{d_{(k)}}^T \vec{r_{(j)}} \\
0 &= \vec{u_{(i)}}^T \vec{r_{(j)}}, \qquad i < j \quad(据式\ref{math_equation_of_residual_orthogonal_to_sllipsoidal_surface})
\end{split}
\label{residual_orthogonal_to_u_vectors} \tag{7 - 13}
$$
还有一个我们稍后会用到的公式,如下所示(由等式$\ref{residual_orthogonal_to_u_vectors}$及图7-10所得):
$$
\vec{d_{(i)}}^T \vec{r_{(i)}} = \vec{u_{(i)}}^T \vec{r_{(i)}}
\label{equation_from_figure28} \tag{7 - 14}
$$
作为本章的小节,我要特别注明的一点是:与最速下降法相比,共轭方向法在每一轮迭代中所需的矩阵·向量乘积计算数量可以通过递归求解残差向量直接减少到一次,如下所示:
$$
\begin{split}
\vec{r_{(i+1)}} &= -\textbf{A} \vec{e_{(i+1)}} \\
&= -\textbf{A}(\vec{e_{(i)}} + \alpha_{(i)} \vec{d_{(i)}})\\
&= \vec{r_{(i)}} - \alpha_{(i)} \textbf{A} \vec{d_{(i)}}
\end{split}
\label{recurrence_to_find_residual} \tag{7 - 15}
$$
一篇专门讲述CG算法的文章到第七章结束了都还没见到CG算法的影子,着实有点奇怪。刚好,到现在为止,要充分学习和理解CG算法所需要的背景知识都准备就绪了。实际上,CG算法就是搜索方向由残差的共轭(即令$\vec{u_i} = \vec{r_{(i)}}$)组成的共轭方向法。
之所以这样选择有诸多原因。首先,既然残差在最速下降法中工作的相当好,那共轭方向法里面也没理由不好啊对吧?其次,残差有一个非常好的特性,即它总是与之前的搜索方向正交(等式$\ref{math_equation_of_residual_orthogonal_to_sllipsoidal_surface}$)。因此,只要残差向量不为0,那么我们根据残差向量必然能够生成一个全新的、线性无关的搜索方向。而当残差向量为0的时候,目标解已经求出,无需再构造搜索向量。接下来我们将看到,之所以做出这样的选择,是因为还有一个更好的理由。
我们首先来考虑选择残差的共轭构建搜索方向的具体实现。由于搜索向量由残差向量构建,因此张成子空间$\mathcal{D}_i$即为$\lbrace \vec{r_{(0)}}, \vec{r_{(1)}}, \cdots, \vec{r_{(i-1)}} \rbrace$。由于每一个残差向量都与它之前迭代过程中的搜索方向向量正交,因此它也必然与之前的残差向量正交,如下图所示:
此时,等式$\ref{residual_orthogonal_to_u_vectors}$变成:
$$
\vec{r_{(i)}}^T \vec{r_{(j)}} = 0, \qquad i \neq j
\label{orthogonal_property_of_residuals} \tag{8 - 1}
$$
有趣的是,等式($\ref{recurrence_to_find_residual}$)表明每一个新的残差向量$\vec{r_{(i)}}$刚好是其上一轮迭代的残差向量$\vec{r_{(i-1)}}$和矩阵·搜索向量乘积$\textbf{A} \vec{d_{(i-1)}}$的线性组合。回顾下我们之前讲的$\vec{d_{(i-1)}} \in \mathcal{D}_i$,那么由此可知新的张成子空间$\mathcal{D}_{i+1}$等于上一轮迭代的张成子空间$\mathcal{D}_{i}$和子空间$\textbf{A} \mathcal{D}_{i}$的并集。因此我们有:
$$
\begin{split}
\mathcal{D}_i &= span \lbrace \vec{d_{(0)}}, \textbf{A} \vec{d_{(0)}}, \textbf{A}^2 \vec{d_{(0)}}, \cdots, \textbf{A}^{i-1} \vec{d_{(0)}} \rbrace \\
&= span \lbrace \vec{r_{(0)}}, \textbf{A} \vec{r_{(0)}}, \textbf{A}^2 \vec{r_{(0)}}, \cdots, \textbf{A}^{i-1} \vec{r_{(0)}} \rbrace
\end{split}
\label{Krylov_subspace} \tag{8 - 2}
$$
译者注:这里简单说一下上述结论的来源(推导过程),以便读者理解。由式($\ref{recurrence_to_find_residual}$)可知,第$i+1$轮迭代后的残差向量$\vec{r_{(i+1)}}$,实际上是由上一轮的残差向量$\vec{r_{(i)}}$和上一轮搜索向量经过矩阵变换后的向量$\textbf{A} \vec{d_{(i)}}$的线性组合(复合)。因此$\vec{r_{(i+1)}}$所在的张成子空间必然是由$\vec{r_{(i)}}$所在的子空间和$\textbf{A} \vec{d_{(i)}}$所在的子空间的并集。
由于$\vec{d_{(i)}}$所在的张成子空间为$\mathcal{D}_{i+1}$(即$\vec{d_{(i)}} \in \mathcal{D}_{i+1}$),所以$\textbf{A} \vec{d_{(i)}}$所在的张成子空间为$\textbf{A} \mathcal{D}_{i+1}$(即$\textbf{A} \vec{d_{(i)}} \in \textbf{A} \mathcal{D}_{i+1}$)。
又由于$\vec{r_{(i+1)}}$所在的子空间为$\mathcal{D}_{i+2}$(即$\vec{r_{(i+1)}} \in \mathcal{D}_{i+2}$),所以我们有:
$$
\mathcal{D}_{i+2} = \mathcal{D}_{i+1} \cup \textbf{A} \mathcal{D}_{i+1}
\label{relationship_of_current_subspace_and_last_subspace} \tag{8 - 2 - 1}
$$
由上式我们取$i=2,3$可得定值情况下的张成子空间:
$$
\begin{split}
\mathcal{D}_{2} &= \mathcal{D}_{1} \cup \textbf{A} \mathcal{D}_{1} \\
&= \lbrace \vec{d_{(0)}} \rbrace \cup \lbrace \textbf{A} \vec{d_{(0)}} \rbrace \\
&= span \lbrace \vec{d_{(0)}}, \textbf{A} \vec{d_{(0)}} \rbrace \\
\mathcal{D}_{3} &= \mathcal{D}_{2} \cup \textbf{A} \mathcal{D}_{2} \\
&= \lbrace \vec{d_{(0)}}, \textbf{A} \vec{d_{(0)}} \rbrace \cup \lbrace \textbf{A} \vec{d_{(0)}}, \textbf{A}^2 \vec{d_{(0)}} \rbrace \\
&= span \lbrace \vec{d_{(0)}}, \textbf{A} \vec{d_{(0)}}, \textbf{A}^2 \vec{d_{(0)}} \rbrace \\
\end{split}
\tag{8 - 2 - 2}
$$
更一般的,利用递归性,可以推导出:
$$
\begin{split}
\mathcal{D}_{i} &= \mathcal{D}_{i-1} \cup \textbf{A} \mathcal{D}_{i-1} \\
&= (\mathcal{D}_{i-2} \cup \textbf{A} \mathcal{D}_{i-2}) \cup \textbf{A} (\mathcal{D}_{i-2} \cup \textbf{A} \mathcal{D}_{i-2}) \\
&= \mathcal{D}_{i-2} \cup \textbf{A} \mathcal{D}_{i-2} \cup \textbf{A} \mathcal{D}_{i-2} \cup \textbf{A}^2 \mathcal{D}_{i-2} \\
&= \underbrace{ \mathcal{D}_{i-2} \cup \textbf{A} \mathcal{D}_{i-2} \cup \textbf{A}^2 \mathcal{D}_{i-2} }_{3项}\\
&= (\mathcal{D}_{i-3} \cup \textbf{A} \mathcal{D}_{i-3}) \cup \textbf{A} (\mathcal{D}_{i-3} \cup \textbf{A} \mathcal{D}_{i-3}) \cup \textbf{A}^2 (\mathcal{D}_{i-3} \cup \textbf{A} \mathcal{D}_{i-3}) \\
&= \mathcal{D}_{i-3} \cup \textbf{A} \mathcal{D}_{i-3} \cup \textbf{A} \mathcal{D}_{i-3} \cup \textbf{A}^2 \mathcal{D}_{i-3} \cup \textbf{A}^2 \mathcal{D}_{i-3} \cup \textbf{A}^3 \mathcal{D}_{i-3} \\
&= \underbrace{ \mathcal{D}_{i-3} \cup \textbf{A} \mathcal{D}_{i-3} \cup \textbf{A}^2 \mathcal{D}_{i-3} \cup \textbf{A}^3 \mathcal{D}_{i-3} }_{4项}\\
&\qquad \qquad \qquad \qquad \qquad \vdots \\
&= \mathcal{D}_{i-(i-1)} \cup \textbf{A} \mathcal{D}_{i-(i-1)} \cup \textbf{A}^2 \mathcal{D}_{i-(i-1)} \cup \textbf{A}^3 \mathcal{D}_{i-(i-1)}\\
&= \underbrace{ \mathcal{D}_{1} \cup \textbf{A} \mathcal{D}_{1} \cup \textbf{A}^2 \mathcal{D}_{1} \cup \cdots \cup \textbf{A}^{i-1} \mathcal{D}_{1} }_{i项} \\
&= span \lbrace \vec{d_{(0)}}, \textbf{A} \vec{d_{(0)}}, \textbf{A}^2 \vec{d_{(0)}}, \cdots, \textbf{A}^{i-1} \vec{d_{(0)}} \rbrace
\end{split}
\tag{8 - 2 - 3}
$$
又由于$ \vec{r_{(0)}} = \vec{d_{(0)}}$,代入上式即可得式子($\ref{Krylov_subspace}$)。要证明这个结论,需要利用本轮迭代和上一轮迭代张成子空间的关系及其递归性,如果从式子($\ref{recurrence_to_find_residual}$)本身来推导则是非常难甚至不可能的(笔者未推导处)。
上述子空间称为Krylov子空间,它是一个通过将一个矩阵反复左乘一个向量构建起来的子空间。它有一个令人愉悦的特性:
由于$\textbf{A} \mathcal{D}_{i} \subsetneqq \mathcal{D}_{i+1}$,因此下一轮迭代的残差向量$\vec{r_{(i+1)}}$与$\mathcal{D}_{i+1}$正交(等式$\ref{math_equation_of_residual_orthogonal_to_sllipsoidal_surface}$)实际上也就意味着$\vec{r_{(i+1)}}$与$\mathcal{D}_{i}$矩阵·向量正交($\vec{r_{(i+1)}} \mathcal{D}_{i+1} = 0 \ \Longrightarrow \ \vec{r_{(i+1)}} \textbf{A} \mathcal{D}_{i} = 0$)。
由于$\vec{r_{(i+1)}}$已经与之前的所有搜索方向矩阵·向量正交($\vec{d_{(i)}}$除外,因为$\vec{d_{(i)}} \notin \textbf{A} \mathcal{D}_{i}$),此时再运用格莱姆·施密特共轭方法自然就非常容易了。
回忆一下等式($\ref{trick_to_solve_beta}$)中的格莱姆·施密特常量:$\beta_{ij} = -\frac{\vec{u_{(i)}} \textbf{A} \vec{d_{(j)}}} {d\vec{_{(j)}}^T \textbf{A} \vec{d_{(j)}}} = -\frac{\vec{r_{(i)}} \textbf{A} \vec{d_{(j)}}} {d\vec{_{(j)}}^T \textbf{A} \vec{d_{(j)}}}$,让我们来化简一下这个式子,先将等式($\ref{recurrence_to_find_residual}$)左右两边与向量$\vec{r_{(i)}}$取内积有:
$$
\begin{split}
\vec{r_{(i)}}^T \vec{r_{(j+1)}} &= \vec{r_{(i)}}^T \vec{r_{(j)}} - \alpha_{(j)} \vec{r_{(i)}}^T \textbf{A} \vec{d_{(j)}} \\
\\
\therefore \qquad \alpha_{(j)} \vec{r_{(i)}}^T \textbf{A} \vec{d_{(j)}} &= \vec{r_{(i)}}^T \vec{r_{(j)}} - \vec{r_{(i)}}^T \vec{r_{(j+1)}} \\
\\
\therefore \qquad \vec{r_{(i)}}^T \textbf{A} \vec{d_{(j)}} &=
\begin{cases}
\frac{1}{\alpha_{(i)}} \vec{r_{(i)}}^T \vec{r_{(i)}}, \qquad &i = j\\
-\frac{1}{\alpha_{(i-1)}} \vec{r_{(i)}}^T \vec{r_{(i)}}, \qquad &i = j + 1 \qquad (据式\ref{orthogonal_property_of_residuals})\\
0, &其它 \qquad
\end{cases}
\end{split}
\label{inner_product_of_recurrence_to_find_residual} \tag{8 - 3}
$$
将上式回代入格莱姆·施密特常量的表达式中有:
$$
\begin{split}
\beta_{ij} &=
\begin{cases}
\frac{1}{\alpha_{(i-1)}} \frac{\vec{r_{(i)}}^T \vec{r_{(i)}}} {\vec{d_{(i-1)}}^T \textbf{A} \vec{d_{(i-1)}}}, \qquad &i = j + 1 \qquad\\
0, & i > j+1 \qquad
\end{cases}
\end{split}
\label{simplified_gram_schmidt_constants} \tag{8 - 4}
$$
从上式可以看出,大多数的常量系数项$\beta_{ij}$魔法般的被消失掉了(变成了0)。由此我们不再需要存储之前的搜索方向向量来确保新的搜索向量是严格矩阵·向量正交的了。这一优点也正是CG为何成为一个重要算法的原因。这个优点也让CG算法在每轮迭代中的空间和时间复杂度均从$\mathcal{O}(n^2)$降低到了$\mathcal{O}(m)$,其中$m$为矩阵$\textbf{A}$中的非零项的数量。后文中,我都将使用$\beta_{(i)}$这样的缩写方式来表示 $\beta_{i,i-1}$。对上式进行进一步化简有:
$$
\begin{split}
\beta_{ij} &=
\frac{\bcancel{ \vec{d_{(i-1)}}^T \textbf{A} \vec{d_{(i-1)}} }} {\vec{d_{(i-1)}}^T \vec{r_{(i-1)}}} \cdot \frac{\vec{r_{(i)}}^T \vec{r_{(i)}}} {\bcancel{ \vec{d_{(i-1)}}^T \textbf{A} \vec{d_{(i-1)}}} } \qquad (据式\ref{alpha_expressed_by_residual_and_direction})\\
&= \frac{\vec{r_{(i)}}^T \vec{r_{(i)}}} {\vec{d_{(i-1)}}^T \vec{r_{(i-1)}}} \\
&= \frac{\vec{r_{(i)}}^T \vec{r_{(i)}}} {\vec{r_{(i-1)}}^T \vec{r_{(i-1)}}} \qquad (据式\ref{math_equation_of_residual_orthogonal_to_sllipsoidal_surface})\\
\end{split}
\label{final_simplified_gram_schmidt_constants} \tag{8 - 5}
$$
我们把之前的这些结论、公式放到一起,则CG算法实际上就是以下公式:
$$
\begin{split}
\vec{d_{(0)}} &= \vec{r_{(0)}} = \vec{b_{(0)}} - \textbf{A} \vec{x_{(0)}} \\
\alpha_{(i)} &= \frac{\vec{r_{(i)}}^T \vec{r_{(i)}}} {\vec{d_{(i)}}^T \textbf{A} \vec{d_{(i)}}} \qquad (据式\ref{alpha_expressed_by_residual_and_direction}、\ref{math_equation_of_residual_orthogonal_to_sllipsoidal_surface}) \\
\vec{x_{(i+1)}} &= \vec{x_{(i)}} + \alpha_{(i)} \vec{d_{(i)}} \\
\vec{r_{(i+1)}} &= \vec{r_{(i)}} - \alpha_{(i)} \textbf{A} \vec{d_{(i)}} \\
\beta_{(i+1)} &= \frac{\vec{r_{(i+1)}}^T \vec{r_{(i+1)}}} {\vec{r_{(i)}}^T \vec{r_{(i)}}} \\
\vec{d_{(i+1)}} &= \vec{r_{(i+1)}} + \beta_{(i+1)} \vec{d_{(i)}}
\end{split}
\label{cg_algorithm_equations} \tag{8 - 6}
$$
CG算法在我们的例子上的执行过程如下图所示:
共轭梯度法(Conjugate Gradients)这个名字听起来多少有点措辞不当的感觉,因为梯度并非共轭的,共轭的(方向向量)也并非都是梯度。使用Conjugated Gradients这个名字会更加精准。
CG算法在$n$论迭代后就结束了,那我们为何还要关心它的收敛性呢?实际上,累积浮点舍入误差会导致残差逐渐的失准,同时删除误差(cancellation error)还会导致搜索向量逐渐丧失矩阵·向量正交性。前一个问题可以像最速下降法中那样处理(定期使用$\vec{x_i}$计算残差向量),可是后面这个问题就没那么容易处理了。正是由于这种共轭性的丧失,数学界在19世纪60年代时摒弃了CG算法。直到70年代有人发表了它作为迭代过程的有效性的证据时,人们才重新对它感兴趣起来。
正所谓此一时彼一时,现今对其进行收敛性分析是相当重要的,因为CG算法常常用来解决几乎不可能完整的执行$n$轮迭代的大型问题。而今进行收敛性分析更多的是提供一种证据来证明CG算法对一些精准算法无法处理的问题是行之有效的,而很少被看作是消除浮点舍入误差的手段。
CG算法的第一轮迭代与SD(Steepest Descent)的第一轮迭代完全一样。因此,无需做任何变动,6.1节同样描述了CG算法在第一轮迭代中收敛需要满足的条件。
从前面的内容我们已经知道了,在CG算法每一轮迭代过程中 ,误差向量$\vec{e_{(i)}}$是从$\vec{e_{(i)}} + \mathcal{D}_i$中选择的,$\mathcal{D}_i$的值如下:
$$
\begin{split}
\mathcal{D}_i &= span \lbrace \vec{r_{(0)}}, \textbf{A} \vec{r_{(0)}}, \textbf{A}^2 \vec{r_{(0)}}, \cdots, \textbf{A}^{i-1} \vec{r_{(0)}} \rbrace \\
&= span \lbrace \textbf{A} \vec{e_{(0)}}, \textbf{A} \cdot \textbf{A} \vec{e_{(0)}}, \textbf{A} \cdot \textbf{A}^2 \vec{e_{(0)}}, \cdots, \textbf{A} \cdot \textbf{A}^{i-1} \vec{e_{(0)}} \rbrace \qquad (据式\ref{residual})\\
&= span \lbrace \textbf{A} \vec{e_{(0)}}, \textbf{A}^2 \vec{e_{(0)}}, \textbf{A}^3 \vec{e_{(0)}}, \cdots, \textbf{A}^{i} \vec{e_{(0)}} \rbrace
\end{split}
\label{Krylov_subspace_expressed_by_error_vector} \tag{9 - 1}
$$
类似这样的Krylov张成子空间还有些其他令人非常愉悦的特性。比如,当给定$i$的值,误差项可以表示成如下形式(用上述张成子空间描述):
$$
\vec{e_{(i)}} = \Big ( I + \sum\limits_{j=1}^{i} \psi_j \textbf{A}^j \Big ) \vec{e_{(0)}}
\label{another_expression_of_error_term} \tag{9 - 2}
$$
上式中的系数$\psi_j$跟$\alpha_{(i)}、\beta_{(i)}$的值有关,至于他们之间确切的关系表达式在这里并不重要,重要的是在7.3节关于CG算法选择的系数$\psi_j$总能最小化$||\vec{e_{(i)}}||_{\textbf{A}}$的证据。
上述等式中圆括号的部分可以表示成一个多项式。记$P_i(\lambda)$为一个$i$阶多项式。$P_i$既可以以一个标量作为其参数,又可以用一个矩阵作为其参数,其效果是一样的。举例来说,如果$P_2(\lambda) = 2 \lambda^2 + 1$,则$P_2(\textbf{A}) = 2 \textbf{A}^2 + I$。这种灵活的记法对于特征向量非常方便,因为$P_i(\textbf{A}) \vec{v} = P_i(\lambda) \vec{v}$(因为$\textbf{A} \vec{v} = \lambda \vec{v}, \textbf{A}^2 \vec{v} = \lambda^2 \vec{v}, \cdots$)。
现在,我们可以把误差项表示为:
$$
\begin{split}
\vec{e_{(i)}} &= \Big ( \underbrace{ I + \sum\limits_{j=1}^{i} \psi_j \textbf{A}^j }_{P_i(\textbf{A})} \Big ) \vec{e_{(0)}} \\
&= P_i(\textbf{A}) \vec{e_{(0)}}
\end{split}
\label{error_term_expressed_by_polynomial} \tag{9 - 3}
$$
同时令$P_i(0) = 1$。当CG算法确定了系数$\psi_I$时,上述多项式也随之确定了。我们先来看看该多项式应用到$\vec{e_{(0)}}$的效果。正如我们在最速下降法中的分析一样,这里我们仍然将误差项$\vec{e_{(0)}}$表示为两两正交的单位特征向量的线性组合,如下:
$$
\begin{split}
\vec{e_{(0)}} = \sum\limits_{j=1}^{n} \xi_j \vec{v_j}
\end{split}
\label{error_term_expressed_by_eigenvectors} \tag{9 - 4}
$$
据此我们有:
$$
\begin{split}
\vec{e_{(i)}} &= \sum\limits_{j=1} \xi_j P_i(\lambda_j) \vec{v_j} \\
\therefore \qquad \textbf{A} \vec{e_{(i)}} &= \sum\limits_{j=1} \xi_j P_i(\lambda_j) \lambda_j \vec{v_j} \\
\therefore \qquad ||\vec{e_{(i)}}||_\textbf{A}^2 &= \vec{e_{(i)}}^T \textbf{A} \vec{e_{(i)}} \\
&= \sum\limits_{j=1} \xi_j^2 [P_i(\lambda_j)]^2 \lambda_j \qquad (单位特征向量的正交性)
\end{split}
\label{error_term_expressed_by_polynomial_and_eigenvectors} \tag{9 - 5}
$$
译者注:这里简单说一下第一个等式的推导过程,如下所示:
$$
\begin{split}
\vec{e_{(i)}} &= P_i(\textbf{A}) \vec{e_{(0)}} \\
&= \sum\limits_{j=1} \xi_j P_i(\textbf{A}) \vec{v_j} \\
&= \sum\limits_{j=1} \xi_j \underbrace{ (I + \psi_1 \textbf{A} + \psi_2 \textbf{A}^2 + \cdots + \psi_n \textbf{A}^n ) }_{P_i(\textbf{A})} \vec{v_j} \\
&= \sum\limits_{j=1} \xi_j (I \vec{v_j} + \psi_1 \textbf{A} \vec{v_j} + \cdots + \psi_n \textbf{A}^n \vec{v_j}) \\
&= \sum\limits_{j=1} \xi_j (I \vec{v_j} + \psi_1 \lambda_j \vec{v_j} + \cdots + \psi_n \lambda_j^n \vec{v_j}) \\
&= \sum\limits_{j=1} \xi_j \underbrace{ (I + \psi_1 \lambda_j + \cdots + \psi_n \lambda_j^n ) }_{P_i(\lambda_j)} \vec{v_j} \\
&= \sum\limits_{j} \xi_j P_i(\lambda_j) \vec{v_j}
\end{split}
\label{inferrence_of_first_equation} \tag{9 - 5 - 1}
$$
CG算法找到了使该表达式最小化的多项式,但是其收敛性和最坏的特征向量的收敛性一致。记$\Lambda(\textbf{A})$为矩阵$\textbf{A}$的特征值的集合,据此我们有:
译者注:下式中先求最大实际上体现的就是最坏的特征向量的情况,再取最小就是在最坏的特征向量情况下多项式最小,这样才能理解下式。
$$
\begin{split}
||\vec{e_{(i)}}||_\textbf{A}^2 &\le \min_{P_i} \max_{\lambda \in \Lambda(\textbf{A})} [P_i(\lambda)]^2 \sum\limits_{j} \xi_j^2 \lambda_j \\
&= \min_{P_i} \max_{\lambda \in \Lambda(\textbf{A})} [P_i(\lambda)]^2 \sum\limits_{j} \xi_j^2 [\underbrace{P_i(0)}_{1}]^2 \lambda_j \\
&= \min_{P_i} \max_{\lambda \in \Lambda(\textbf{A})} [P_i(\lambda)]^2 ||\vec{e_{(0)}}||_\textbf{A}^2
\end{split}
\label{boundary_of_error_term_norm} \tag{9 - 6}
$$
下图展示了不同阶数$i$(迭代次数)时,$P_i$对于我们之前给定的特征值为2、7的例子让上述表达式取最小值的过程示意图,如下图所示:
由上可知,0阶多项式中有且仅有一个满足$P_0(0) = 1$,即$P_0(\lambda) = 1$,如上图(a)所示。最优的1阶多项式则为$P_1(\lambda) = 1 - 2x/9$,如上图(b)所示。注意到$P_1(2) = 5/9, P_1(7) = -5/9$,因此CG算法在第一轮迭代之后,误差项的能量范式将小于其初始值5/9。图(c)展示了两轮迭代后,上式($\ref{boundary_of_error_term_norm}$)将能够取到0。这是因为,一个二阶(二次)多项式的图形可以拟合这三个点($P_2(0) = 1, \ P_2(2) = 0, \ P_2(7) = 0$)。一般的,$n$阶多项式可以拟合$n+1$个点,从而可以拟合$n$个不同的特征值。
译者注:要理解原作者的这段话确实还不太容易,写的有点绕。结合我们之前讲的,$P_i$要使得式子($\ref{boundary_of_error_term_norm}$)最小化,则其对应的所有特征向量情况下必然一致收敛(最坏的特征向量的情况下都已经收敛),此时对于任意$\lambda \in \Lambda(\textbf{A})$,必然有$P_i(\lambda) = 0$。由上面的图(b)可知,如果这一轮迭代时能够收敛,那么此时的一阶多项式(直线)必然需要同时拟合$P_2(0) = 1, \ P_2(2) = 0, \ P_2(7) = 0$这三个点,显然是无法做到的,因此第一轮的时候不可能收敛。
第二轮迭代时,如上图(c)所示,由于此时已经变成了二阶多项式,因此能够顺利的拟合上述三个关键点,对式($\ref{boundary_of_error_term_norm}$)求最小值也必然会得到上述三个点。
前面的论述巩固了我们对CG算法在$n$轮迭代后必然取得精确解的理解,此外还证明了CG算法在特征值存在重复的情况下收敛是非常快的。在给定足够浮点精度的情况下,迭代的次数将不超过不同特征值的数目(当然还有一种情况可能会导致提前结束:那就是$\vec{x_{(0)}}$已经与矩阵$\textbf{A}$的某个特征向量矩阵·向量正交了。如果$\vec{x_{(0)}}$的展开式中存在缺失的特征向量,则这些特征向量对应的特征值在式($\ref{boundary_of_error_term_norm}$)中可以直接忽略掉。然而对这些特征向量千万别掉以轻心,因为它们可能会因为浮点舍入误差而被重新引入)。
此外我们还发现,相较于特征值在$\lbrack \lambda_{min}, \ \lambda_{max} \rbrack$之间不规则分布的情况而言,当他们分布较为集中时(如上图(d)所示),CG算法收敛更快,因为此时CG算法更容易找到一个多项式来使得式($\ref{boundary_of_error_term_norm}$)最小化。
如果我们知道$\textbf{A}$的特征值的一些特征,我们甚至可以提出一个能够使得算法快速收敛的多项式。然而,在收敛性分析的剩下部分,我将针对一般性的情况:即特征值在$\lbrack \lambda_{min}, \ \lambda_{max} \rbrack$之间均匀分布,不同特征值的数量也非常大,并且还有浮点舍入误差。
一个有用的方法是在范围$\lbrack \lambda_{min}, \ \lambda_{max} \rbrack$内最小化等式($\ref{boundary_of_error_term_norm}$),而不是在有限的点上。能做到这点的多项式是基于切比雪夫多项式(Chebyshev polynomials)多项式。
$i$阶切比雪夫多项式如下所示:
$$
T_i(\omega) = \frac{1}{2} \Big [ (\omega + \sqrt{\omega^2 - 1})^i + (\omega - \sqrt{\omega^2 - 1})^i]
\label{Chebyshev_polynomials} \tag{9 - 7}
$$
如果你感觉上面这个式子压根儿就不像个多项式,你可以尝试着令$i=1, 2$,然后展开看看。下图展示了一些切比雪夫多项式的图形,如下所示:
切比雪夫多项式有如下特性:
附录C3证明了当选择如下多项式时,能够最小化等式($\ref{boundary_of_error_term_norm}$):
$$
P_i(\lambda) = \frac{T_i \Big( \frac{\lambda_{max} + \lambda_{min} - 2\lambda} {\lambda_{max} - \lambda_{min}} \Big )} {T_i \Big( \frac{\lambda_{max} + \lambda_{min}} {\lambda_{max} - \lambda_{min}} \Big)} \\
\label{polynomials_minimizes_energe_norm} \tag{9 - 8}
$$
上述多项式在定义域$\lambda_{min} \le \lambda \le \lambda_{max}$上具有同切比雪夫多项式相同的振荡特性,如下图所示:
上式中的分母要求$P_i(0) = 1$。分子在$\lbrack \lambda_{min}, \ \lambda_{max} \rbrack$区间内取得最大值,因此据式($\ref{boundary_of_error_term_norm}$)我们有:
$$
\begin{split}
||\vec{e_{(i)}}||_\textbf{A} &\le T_i \Big( \frac{\lambda_{max} + \lambda_{min}} {\lambda_{max} - \lambda_{min}} \Big)^{-1} ||\vec{e_{(0)}}||_\textbf{A} \\
&= T_i \Big( \frac{\kappa + 1} {\kappa - 1} \Big)^{-1} ||\vec{e_{(0)}}||_\textbf{A} \qquad (据式\ref{condition_number_of_symmetric_positive_matrix}) \\
&= 2 \Bigg[ \Big( \frac{\sqrt{\kappa} + 1} {\sqrt{\kappa} - 1} \Big )^i + \Big( \frac{\sqrt{\kappa} - 1} {\sqrt{\kappa} + 1} \Big )^i \Bigg]^{-1} ||\vec{e_{(0)}}||_\textbf{A}
\end{split}
\label{energe_norm_expressed_by_kappa} \tag{9 - 9}
$$
上式方括号中的第二项将随着$i$的增加而逐渐收敛到0,因此我们更多的时候会用下面的弱不等式来表述CG算法的收敛性:
$$
||\vec{e_{(i)}}||_\textbf{A} \le 2 \Big( \frac{\sqrt{\kappa} - 1} {\sqrt{\kappa} + 1} \Big )^i ||\vec{e_{(0)}}||_\textbf{A} \\
\label{weaker_inequality_of_cg_convergence} \tag{9 - 10}
$$
CG算法的第一步与SG算法的第一步相同。令式($\ref{energe_norm_expressed_by_kappa}$)中的$i = 1$,我们即得到等式($\ref{convergence_result}$),即SG算法的收敛结果。这对应如图9-1(b)所示的线性多项式的情况。
下图绘制了CG算法每一轮迭代的收敛情况,图中略掉了上式($\ref{weaker_inequality_of_cg_convergence}$)的系数项2
:
实际应用中,由于较好的特征值分布或者比较理想的起始点的存在,CG算法通常收敛的比式($\ref{weaker_inequality_of_cg_convergence}$)所描述的还要快。对比等式($\ref{weaker_inequality_of_cg_convergence}$)和等式($\ref{convergence_result}$)明显可以看出,CG算法远比SG算法收敛(如下图所示)的快:
然而,并非CG算法的每一轮迭代(相对于SG算法)都能具备更快的收敛性。比如说,CG算法的第一轮迭代即为SG算法的第一轮迭代。同时,等式($\ref{weaker_inequality_of_cg_convergence}$)中的系数2
允许CG算法在较差的迭代轮中放缓收敛速度。
无论是CG还是SG算法,在其迭代过程中的关键性操作均为矩阵·向量
乘积。一般来说,矩阵·向量
乘积需要$\mathcal{O}(m)$次操作,其中$m$为矩阵非零元素的个数。对于许多问题而言,包括引言部分所例举的这些,矩阵A都是稀疏的,因此有$m \in \mathcal{O}(n)$。
假设我们希望执行足够多的迭代次数来把误差的能量范式减少到其初始值的$\epsilon$倍,即$||\vec {e_{(i)}}|| = \epsilon ||\vec {e_{(0)}}||$。据等式($\ref{convergence_result}$)我们可推导出SG算法要实现这个边界条件所需要的最大迭代次数,如下所示:
$$
i \le \Big \lceil \frac{1}{2} \kappa ln \Big( \frac{1}{\epsilon} \Big) \Big \rceil
\label{maximum_iterations_of_steepest_descent} \tag{9 - 11}
$$
而根据等式($\ref{weaker_inequality_of_cg_convergence}$)则可以推导出CG算法所需要的最大迭代次数为:
$$
i \le \Big \lceil \frac{1}{2} \sqrt{\kappa} ln \Big( \frac{2}{\epsilon} \Big) \Big \rceil
\label{maximum_iterations_of_conjugate_gradient} \tag{9 - 12}
$$
译者注:此处结论的推导过程笔者暂时还没有想到。
据此我们做一个总结:
二阶椭圆边值问题在d维域上的有限差分和有限元逼近通常有:$\kappa \in \mathcal{O(n^{2/d})}$。因此,SG算法在二维问题上的时间复杂度为$\mathcal{O(n^2)}$,而CG算法则是$\mathcal{O(n^{3/2})}$。在三维问题上,两者的时间复杂度分别为:$\mathcal{O(n^{5/3})}、\mathcal{O(n^{4/3})}$。
前面演示的SG和CG算法我们略过了一些细节,具体来说就是如何选择起始点,以及何时应当停止。
关于起始点的选择真没太多要讲的。如果你对解$\vec{x}$的值有一个粗略的估计,那你就可以直接用它作为起始点$\vec{x}_{(0)}$。如果没有,那就直接取#$\vec{x}_{(0)} = 0$。不管是SG算法还是CG算法,用他们解决线性问题,最终都必然收敛。然而非线性最小化问题(第14章马上会讲)却是非常棘手的,因为通常这种情况下都会存在好几个局部最小值。此时,起始点的选择将决定收敛到这些局部最小值中的哪一个,或者压根儿就不收敛。
当SG或者CG算法到达最小值点时,残差必然为0,如果等式($\ref{formula_of_step_size}$)或者($\ref{cg_algorithm_equations}$)是在迭代之后再进行求值,那么必然会发生除0错误。因此,在残差向量等于0时,应立即停止迭代。然而,让事情复杂化的一件事是,等式($\ref{cg_algorithm_equations}$)中的递归求解方程由于舍入误差的影响可能会产生一个假的零残差。当然这个问题前面我们已经讲过了,可以通过使用式($\ref{cg_algorithm_equations}$)中的第一个等式来解决。
然而,通常我们都会希望在收敛之前结束迭代。因为误差项并不可靠,对此我们通常的做法是在残差向量的范式小于某个给定阈值的时候就停下来。通常这个阈值为初始残差的一小部分($||\vec{r_{(i)}}|| < \epsilon ||\vec{r_{(0)}}||$),具体例子可参见附录B的示例代码。
预处理是一项改善矩阵条件数的技术。假定矩阵M为一更方便求解逆矩阵的近似矩阵A的对称正定阵。我们可以通过间接的求解下式来求解$\textbf{A} \vec{x} = \vec{b}$:
$$
\textbf{M}^{-1} \textbf{A} \vec{x} = \textbf{M}^{-1} \vec{b} \\
\label{solve_a_by_solving_m} \tag{12 - 1}
$$
若$\kappa(\textbf{M}^{-1} \textbf{A}) \ll \kappa(\textbf{A})$,或者说$\textbf{M}^{-1} \textbf{A}$的特征值分布比$\textbf{A}$集中,我们递归求解上式要远比求解原始问题快。问题在于$\textbf{M}^{-1} \textbf{A}$通常并非对称或者正定的,即使$\textbf{M}$和$\textbf{A}$都是。
我们可以绕过这个棘手的问题,因为对于任意对称正定阵M,必然存在矩阵(并不一定唯一)E满足$\textbf{E} \textbf{E}^T = \textbf{M}$(像这样的矩阵E可以通过诸如Cholesky factorizationi/decomposition方法获得)。矩阵$\textbf{M}^{-1} \textbf{A}$和$\textbf{E}^{-1} \textbf{A} \textbf{E}^{-T}$具有相同的特征值。该结论之所以成立,是因为若$\vec{v}$为矩阵$\textbf{M}^{-1} \textbf{A}$的一个特征值为$\lambda$的特征向量,那么$\textbf{E}^{-T} \vec{v}$则为矩阵$\textbf{E}^{-1} \textbf{A} \textbf{E}^{-T}$特征值为$\lambda$的特征向量,推导如下所示:
$$
(\textbf{E}^{-1} \textbf{A} \textbf{E}^{-T}) (\textbf{E}^{T} \vec{v}) = (\textbf{E}^{T} \textbf{E}^{-T}) \textbf{E}^{-1} \textbf{A} \vec{v} = \textbf{E}^{T} \textbf{M}^{-1} \textbf{A} \vec{v} = \lambda \textbf{E}^{T} \vec{v}
\label{Cholesky_factorization_matrix_eigenvalues} \tag{12 - 2}
$$
由此,线性方程组$\textbf{A} \vec{x} = \vec{b}$可变换为如下问题:
$$
\textbf{E}^{-1} \textbf{A} \textbf{E}^{-T} \hat{\vec{x}} = \textbf{E}^{-1} \vec{b}, \qquad \hat{\vec{x}} = \textbf{E}^{T} \vec{x}
\label{linear_system_transformed} \tag{12 - 3}
$$
针对上述方程我们先求解$\vec{\hat{x}}$,再求解$\vec{x}$。由于$\textbf{E}^{-1} \textbf{A} \textbf{E}^{-T}$是对称正定阵,$\vec{\hat{x}}$即可通过SG或CG算法解出。利用CG算法求解上述方程的过程我们称作预处理变换共轭梯度法(Transformed Preconditioned Conjugate Gradient Method),根据式($\ref{cg_algorithm_equations}$)可得预处理变换后的求解式子如下所示:
$$
\begin{split}
\vec{\hat{d}_{(0)}} &= \vec{\hat{r}_{(0)}} = \textbf{E}^{-1} \vec{b} - \textbf{E}^{-1} \textbf{A} \textbf{E}^{-T} \vec{\hat{x}_{(0)}} \\
\alpha_{(i)} &= \frac{\vec{\hat{r}_{(i)}}^T \vec{\hat{r}_{(i)}}} {\vec{\hat{d}_{(i)}}^T \textbf{E}^{-1} \textbf{A} \textbf{E}^{-T} \vec{\hat{d}_{(i)}}} \\
\vec{\hat{x}_{(i+1)}} &= \vec{\hat{x}_{(i)}} + \alpha_{(i)} \vec{\hat{d}_{(i)}} \\
\vec{\hat{r}_{(i+1)}} &= \vec{\hat{r}_{(i)}} - \alpha_{(i)} \textbf{E}^{-1} \textbf{A} \textbf{E}^{-T} \vec{\hat{d}_{(i)}} \\
\beta_{(i+1)} &= \frac{\vec{\hat{r}_{(i+1)}}^T \vec{\hat{r}_{(i+1)}}} {\vec{\hat{r}_{(i)}}^T \vec{\hat{r}_{(i)}}} \\
\vec{\hat{d}_{(i+1)}} &= \vec{\hat{r}_{(i+1)}} + \beta_{(i+1)} \vec{\hat{d}_{(i)}}
\end{split}
\label{transformed_preconditioned_conjugate_gradient_method} \tag{12 - 4}
$$
该方法有一个不大让人喜欢的特点是,需要计算矩阵$\textbf{E}$。不过,我们可以通过审慎的替换一些变量来达到消除矩阵$\textbf{E}$的目的。令$\vec{\hat{r}_{(i)}} = \textbf{E}^{-1} \vec{r_{(i)}}$以及$\vec{\hat{d}_{(i)}} = \textbf{E}^{T} \vec{d_{(i)}}$,同时结合等式$\vec{\hat{x}_{(i)}} = \textbf{E}^{T} \vec{x_{(i)}}$以及$\textbf{E}^{-T} \textbf{E}^{-1} = \textbf{M} ^{-1}$,我们可以推导出预处理未变换共轭梯度法(Untransformed Preconditioned Conjugate Gradient Method)的计算公式如下:
$$
\begin{split}
\vec{r_{(0)}} &= \vec{b} - \textbf{A} \vec{x_{(0)}} \\
\vec{d_{(0)}} &= \textbf{M}^{-1} \vec{r_{(0)}} \\
\alpha_{(i)} &= \frac{\vec{r_{(i)}}^T \textbf{M}^{-1} \vec{r_{(i)}}} {\vec{d_{(i)}}^T \textbf{A} \vec{d_{(i)}}} \\
\vec{x_{(i+1)}} &= \vec{x_{(i)}} + \alpha_{(i)} \vec{d_{(i)}} \\
\vec{r_{(i+1)}} &= \vec{r_{(i)}} - \alpha_{(i)} \textbf{A} \vec{d_{(i)}} \\
\beta_{(i+1)} &= \frac{\vec{r_{(i+1)}}^T \textbf{M}^{-1} \vec{r_{(i+1)}}} {\vec{r_{(i)}}^T \textbf{M}^{-1} \vec{r_{(i)}}} \\
\vec{d_{(i+1)}} &= \textbf{M}^{-1} \vec{r_{(i+1)}} + \beta_{(i+1)} \vec{d_{(i)}}
\end{split}
\label{untransformed_preconditioned_conjugate_gradient_method} \tag{12 - 5}
$$
可以看出,矩阵$\textbf{E}$不再出现,取而代之的是$\textbf{M}^{-1}$。同理我们可以推导出不需要矩阵$\textbf{E}$即可计算的预处理最速下降法(Preconditioned Steepest Descent Method)。
预处理器$\textbf{M}$的效果取决于$\textbf{M}^{-1} \textbf{A}$的条件数,少数情况下则是由其特征值的分布情况决定。剩下的问题就是找到一个能足够近似$\textbf{A}$的预处理器来提高收敛性,以便弥补在每一轮迭代中因计算内积$\textbf{M}^{-1} \vec{r_{(i)}}$所造成的性能损失。(没有必要显式的去计算$\textbf{M}$或者$\textbf{M}^{-1}$,只需要能计算出$\textbf{M}^{-1}$作用于一个向量的效果即可。)在这些约束框架内,(这样的预处理器)就有许许多多的可能性了,我们这里也仅仅出击皮毛而已。
直观的来讲,预处理实际上是尝试着对二次型的图形进行拉伸,使其更接近于球形,以便它的特征值彼此之间更加靠近。最完美的预处理器当然是$\textbf{M} = \textbf{A}$,对于这个预处理器来说,$\textbf{M}^{-1} \textbf{A}$的条件数为1,其二次型是完美的球形,只需要一轮迭代即可获得其解。然而不幸的是,这个预处理器压根没什么用,因为经过预处理之后,我们还是要解$\textbf{M} \vec{x} = \vec{b}$(即与原始问题等价)。
最简单的预处理器莫过于与$\textbf{A}$对应的对角阵了(对角元素为矩阵$\textbf{A}$的对角元素),使用此预处理器的过程,即我们熟知的对角阵预处理(diagonal preconditioning)或者雅可比预处理(Jacobi preconditioning),实际上相当于把二次型沿着坐标轴进行缩放(作为对比,前面说的完美预处理器$\textbf{M} = \textbf{A}$则是沿着其特征向量方向对二次型进行缩放)。虽然对角阵的逆矩阵很好求,但它总归只是个中规中矩的预处理器。
下图展示了我们所举的示例的矩阵经过对角阵预处理之后的等高线的样子,如下所示:
对比图3-3可以明显看出已经有了较大的改善,条件数从原来的3.5减小到了大约2.8左右。当然,对于$n \gg 2$的高阶问题,改善的效果将更显著。一种更复杂、精细的预处理器是不完备Cholesky预处理器(incomplete Cholesky preconditioning)。Cholesky分解是一种可将矩阵$\textbf{A}$分解为形如$\textbf{L} \textbf{L}^T$的方法,其中$\textbf{L}$为下三角阵。不完备Cholesky分解则是该分解法的一种变体,它只允许少量填充或者不允许填充。我们通常通过乘积$\hat{\textbf{L}} \hat{\textbf{L}}^T$来近似$\textbf{A}$,式中$\hat{\textbf{L}}$可能会被限制为与$\textbf{A}$有相同非零元素的形式,此时$\textbf{L}$的其它元素则会被直接丢弃掉(因为$\hat{\textbf{L}} \hat{\textbf{L}}^T$只是近似$\textbf{A}$)。如果使用$\hat{\textbf{L}} \hat{\textbf{L}}^T$作为预处理器,则$\hat{\textbf{L}} \hat{\textbf{L}}^T w = z$的解通过反向替代来计算(不会显式的去计算$\hat{\textbf{L}} \hat{\textbf{L}}^T$的逆)。然而不幸的是,不完备Cholesky分解并非一直都是稳定可靠的。
因此,人们发明了许多预处理器,有些还相当复杂。不管你的选择如何,对于大型应用而言,将CG算法和预处理器搭配使用是一个普遍接受的事实。
CG算法还可以用来解决$\textbf{A}$非对称、非正定,甚至非方阵的问题。对给定的最小二乘问题,如下所示:
$$
\begin{split}
\min_{\vec{x}} ||\textbf{A} \vec{x} - \vec{b}||^2
\end{split}
\label{leat_squres_problem} \tag{13 - 1}
$$
其解可以通过令上式的导出式为0求得,如下所示:
$$
\begin{split}
\textbf{A}^T \textbf{A} \vec{x} = \textbf{A}^T \vec{b}
\end{split}
\label{derivative_of_leat_squres_problem} \tag{13 - 2}
$$
若$\textbf{A}$为非奇异的方阵,上式的解即为$\textbf{A} \vec{x} = \vec{b}$的解。若$\textbf{A}$为非方阵,则$\textbf{A} \vec{x} = \vec{b}$过约束(线性无关的方程数量超过了自变量数量),那么$\textbf{A} \vec{x} = \vec{b}$必定无解,但我们总有可能找到一个向量$\vec{x}$使得等式($\ref{leat_squres_problem}$)也即每个线性方程的误差的平方和最小化。
$\textbf{A}^T \textbf{A}$是对称并且正定的(对任意给定的$\vec{x}$,均有$\vec{x}^T \textbf{A}^T \textbf{A} \vec{x} = ||\textbf{A} \vec{x}||^2 \ge 0$)。若$\textbf{A} \vec{x} = \vec{b}$并非欠约束的,则$\textbf{A}^T \textbf{A}$就是非奇异的,我们就可以使用类似SG以及CG算法来求解式($\ref{derivative_of_leat_squres_problem}$)。唯一比较讨厌的一点就是,$\textbf{A}^T \textbf{A}$的条件数是$\textbf{A}$条件数的平方,因此其收敛速度会明显慢很多。
一个重要的技术点是,我们并不会显式的去构建矩阵$\textbf{A}^T \textbf{A}$,因为它远比$\textbf{A}$稀疏。取而代之的是,先用$\textbf{A}^T \textbf{A}$乘以$\vec{d}$求出乘积$\textbf{A} \vec{d}$,再求出$\textbf{A}^T \textbf{A} \vec{d}$。如果用$\textbf{A} \vec{d}$与其自身的乘积来计算$\vec{d}^T \textbf{A}^T \textbf{A} \vec{d}$的值,可以提高数值的稳定性。
CG算法不仅仅是用来找到二次型的最小值点,还可以用于任意梯度$f’(x)$值能计算的连续函数$f(x)$的最小化。其实际应用包括工程设计、神经网络训练、非线性回归等等。
为了推导非线性CG算法,线性算法有三点改变:
在非线性CG算法中,残差向量通常设为梯度的相反数:$\vec{r_{(i)}} = -f’(\vec{x_{(i)}})$。搜索方向仍然是采用与线性CG算法相同,由残差向量的格莱姆·施密特共轭计算。沿着这些搜索方向进行线性搜索要远比在线性CG算法中困难的多,而且有各种各样的手段可以使用。至于线性CG算法,能最小化$f(\vec{x_{(i)}} + \alpha_{(i)} \vec{d_{(i)}})$的值$\alpha_{(i)}$是通过确保梯度与搜索向量正交来找到的(我们可以使用任意能找到表达式$f’(\vec{x_{(i)}} + \alpha_{(i)} \vec{d_{(i)}})^T \vec{d_{(i)}}$等于0的算法)。
在线性CG算法中,$\beta$的值有几个等价的表达式。然而在非线性CG算法中,这几个表达式不再等价,学者们正致力于最佳选择的调研。目前有两种选择,一种是Fletcher-Reeves方程,由于其计算的便捷性,我们通常用在线性CG算法中。另一种是Polak-Ribière方程,分别如下所示:
$$
\begin{split}
\beta_{(i+1)}^{F·R} &= \frac{\vec{r_{(i+1)}^T} \vec{r_{(i+1)}}} {\vec{r_{(i)}^T} \vec{r_{(i)}} } \\
\beta_{(i+1)}^{P·R} &= \frac{\vec{r_{(i+1)}^T} (\vec{r_{(i+1)}} - \vec{r_{(i)}})} {\vec{r_{(i)}^T} \vec{r_{(i)}}} \\
\end{split}
\label{FR_and_PR_equation} \tag{14 - 1}
$$
在起始点足够接近我们想要求解的最小值点的情况下,F·R(Fletcher-Reeves)算法能够收敛,然而P·R(Polak-Ribière)算法在少数情况下却会无限循环而不收敛。但是P·R算法通常收敛的速度会快的多。
幸运的是,通过取$\beta = max \lbrace \beta^{P·R}, 0 \rbrace$,我们可以保证P·R算法的收敛性。使用这种取值方法就相当于在$\beta^{P·R} < 0$的时候重新开始CG算法。重启CG算法就是忘记之前的所有搜索方向,然后再在最陡的方向上重新开始CG算法。
如下即为非线性CG算法几乎没有非线性CG算法的一个大纲:
$$
\begin{split}
\vec{d_{(0)}} &= \vec{r_{(0)}} = -f’(\vec{x_{(0)}}) \\
\min_{\alpha_{(i)}} &f(\vec{x_{(i)}} + \alpha_{(i)} \vec{d_{(i)}}) \\
\vec{x_{(i+1)}} &= \vec{x_{(i)}} + \alpha_{(i)} \vec{d_{(i)}} \\
\vec{r_{(i+1)}} &= -f’( \vec{x_{(i+1)}} ) \\
\beta_{(i+1)} = \frac{ \vec{r_{(i+1)}^T} \vec{r_{(i+1)}} } { \vec{r_{(i)}^T} \vec{r_{(i)}} } \quad &or \quad \beta_{(i+1)} = max \Big \lbrace \frac{\vec{r_{(i+1)}^T} (\vec{r_{(i+1)}} - \vec{r_{(i)}})} {\vec{r_{(i)}^T} \vec{r_{(i)}}}, 0 \Big \rbrace \\
\vec{d_{(i+1)}} &= \vec{r_{(i+1)}} + \beta_{(i+1)} \vec{d_{(i)}} \\
\end{split}
\label{outline_of_nonlinear_cg_method} \tag{14 - 2}
$$
非线性CG算法几乎没有像线性CG算法的收敛性保证。函数$f$和二次型函数越不像,搜索方向失去共轭性的速度越快(很快我们就会明白,即使在非线性CG算法中,”共轭”这个词仍然有一定的意义)。另一个问题是,一般性的函数$f$可能会存在多个局部最小值点。CG算法并不保证能收敛到全局最小值点,若$f$没有下界的话,甚至都找不到局部最小值点。
下图展示了非线性CG算法:
如上图所示,图(a)是一个有多个最小值点的函数的图像。图(b)展示了使用F·R方程时的非线性CG算法的收敛性。在这个例子中,CG算法没有线性情况下那么有效,这个函数看起来很难最小化。图(c)展示了图(b)中第一次线性搜索过程中的曲面的横切面。注意图中有好几个最小值点,线性搜索找到了一个较近的最小点对应的$\alpha$的值。图(d)展示了使用P·G的CG算法的收敛过程的优越性。
由于在一个$n$维的空间中,CG算法仅仅能计算出$n$个共轭的向量,因此在$n$轮迭代后重启CG算法就很有必要了,尤其是当$n$较小的时候。下图展示了每辆轮迭代后就重启非线性CG算法的收敛过程(对这个特殊的例子,F·R方法和P·R方法表现一致)。
根据$f’$的值,我们可以选择一种快速的算法来找到$f’^T \vec{d}$等于0的解。举例来说,若$f’$是关于$\alpha$的多项式,那么这时候就可以使用一个高效的多项式零点求解(求根)算法。然而,我们这里仅仅会考虑通用的算法。
有两种求解零点的迭代算法,一种是牛顿法(Newton-Raphson),另一种则是割线法(Secant-method)。两种算法都要求$f$必须是二阶连续可微的。牛顿法还要求能够计算出$f(\vec{x} + \alpha \vec{d})$对$\alpha$的二阶导数。
牛顿法依赖于泰勒级数近似,如下所示:
$$
\begin{split}
f(\vec{x} + \alpha \vec{d}) &\approx f(\vec{x}) + \alpha \Big [ \frac{d} {d_{\alpha}} f(\vec{x} + \alpha \vec{d}) \Big]_{\alpha = 0} + \frac{\alpha^2}{2} \Big[ \frac{d^2} {d_{\alpha^2}} f(\vec{x} + \alpha \vec{d}) \Big]_{\alpha=0} \\
&= f(\vec{x}) + \alpha [f’(\vec{x})]^T \vec{d} + \frac{\alpha^2}{2} \vec{d}^T f’’(\vec{x}) \vec{d} \\
\end{split}
\label{Newton_method_expressed_by_Taylor_series} \tag{14 - 3}
$$
上述级数展开式对$\alpha$求导有:
$$
\begin{split}
\frac{d} {d_{\alpha}} f(\vec{x} + \alpha \vec{d}) &\approx [f’(\vec{x})]^T \vec{d} + \alpha \vec{d}^T f’’(\vec{x}) \vec{d}
\end{split}
\label{partial_of_Newton_method} \tag{14 - 4}
$$
上式中,$f’’(\vec{x})$为黑塞矩阵:
$$
f’’(\vec{x}) = \begin{bmatrix}
\frac{\partial^2 f}{\partial x_1 \partial x_1} & \frac{\partial^2 f}{\partial x_1 \partial x_2} & \cdots & \frac{\partial^2 f}{\partial x_1 \partial x_n}\\
\frac{\partial^2 f}{\partial x_2 \partial x_1} & \frac{\partial^2 f}{\partial x_2 \partial x_2} & & \frac{\partial^2 f}{\partial x_2 \partial x_n}\\
\vdots & & \ddots & \vdots \\
\frac{\partial^2 f}{\partial x_n \partial x_1} & \frac{\partial^2 f}{\partial x_n \partial x_2} & \cdots & \frac{\partial^2 f}{\partial x_n \partial x_n}\\
\end{bmatrix}
\label{Hessian_matrix} \tag{14 - 5}
$$
通过令上式($\ref{partial_of_Newton_method}$)等于0,可以求得函数$f(\vec{x} + \alpha \vec{d})$的近似最小值,于是可求出此时$\alpha$的值:
$$
\alpha = - \frac{f’^T \vec{d}} {\vec{d}^T f’’ \vec{d}}\\
\label{alpha_value_minimized_Newton_function} \tag{14 - 6}
$$
上述截断的泰勒级数以抛物线形式近似$f(\vec{x} + \alpha \vec{d})$,我们到达该抛物线的底部时,即到达了近似最小点,如下图所示:
实际上,如果$f$本身就是一个二次型,那么这个抛物线近似就是准确的的,因为$f’’$刚好就是我们所熟悉的矩阵$\textbf{A}$。一般来说,如果这些搜索向量是$f’’$-正交的,那么它们必然是共轭的。然而,共轭
的含义却在不断改变,因为$f’’$随着$\vec{x}$而不断在变化。$f’’$随着$\vec{x}$变化的越快,搜索向量失去共轭性的速度就越快。另一方面,$\vec{x_{(i)}}$越接近目标解,$f’’$在前后迭代中改变的越少。起始点越接近目标解,此时非线性CG算法和线性CG算法的收敛性也越接近。
为了对非二次型函数精准的进行线性搜索,我们需要在搜索过程沿着搜索方向不断重复,直到$f’^T \vec{d}$等于0为止。因此,CG算法的每一轮迭代可能会包含多轮牛顿法的迭代。每一步中我们都需要计算$f’^T \vec{d}$和$\vec{d}^T f’’ \vec{d}$的值。如果$\vec{d}^T f’’ \vec{d}$能被分析简化,那么这些值的计算开销就会小很多,但是如果整个矩阵$f’’$都需要重复计算的话,算法就会相当慢。对于某些应用,我们可以通过仅使用$f’’$的对角元素来进行近似的线性搜索,从而绕开这个问题。当然,有些函数根本就无法求解出$f’’$的值。
为了在不计算$f’’$的情况下进行精准的线性搜索,割线法通过求点$\alpha = 0$和$\alpha = \sigma$处的一阶导数来近似$f(\vec{x} + \alpha \vec{d})$的二阶导数,其中$\sigma$为任意足够小的非0值,如下所示:
$$
\begin{split}
\frac{d^2} {d_{\alpha^2}} f(\vec{x} + \alpha \vec{d}) &\approx \frac{ [ \frac{d} {d_{\alpha}} f(\vec{x} + \alpha \vec{d}) ]_{\alpha = \sigma} - [ \frac{d} {d_{\alpha}} f(\vec{x} + \alpha \vec{d}) ]_{\alpha = 0} } {\sigma} \qquad \sigma \ne 0 \\
&= \frac{ [ f’(\vec{x} + \sigma \vec{d}) ]^T \vec{d} - [ f’(\vec{x}) ]^T \vec{d} } {\sigma}
\end{split}
\label{Secamt_method_approximates_by_first_derivative} \tag{14 - 7}
$$
当$\alpha$和$\sigma$均趋近于0的时候,上式是对二阶导数更好的近似。如果我们用上式替换等式($\ref{Newton_method_expressed_by_Taylor_series}$)中的泰勒展开式的第三项有:
$$
\begin{split}
\frac{d} {d_{\alpha}} f(\vec{x} + \alpha \vec{d}) &\approx [f’(\vec{x})]^T \vec{d} + \frac{\alpha}{\sigma} \big \lbrace [f’(\vec{x} + \sigma \vec{d})]^T \vec{d} - [f’(\vec{x})]^T \vec{d} \big \rbrace
\end{split}
\label{Taylor_expansion_third_term_replaced} \tag{14 - 8}
$$
通过令上式等于0,即可最小化$f(\vec{x} + \alpha \vec{d})$,可求得此时的$\alpha$的解如下:
$$
\alpha = - \sigma \frac{[f’(\vec{x})]^T \vec{d}} {[f’(\vec{x} + \sigma \vec{d})]^T \vec{d} - [f’(\vec{x})]^T \vec{d}}
\label{alpha_value_of_function_minimized} \tag{14 - 9}
$$
与牛顿法类似,割线法也使用抛物线来近似$f(\vec{x} + \alpha \vec{d})$,但是与牛顿法通过计算一个点的一阶和二阶导数来选择抛物线不同的是,割线法选择在两个不同的点分别计算一阶导数来确定抛物线,如下图所示:
通常我们会在割线法的第一轮迭代中将$\sigma$取一个任意值,而在随后的迭代过程中,我们会从前一轮的迭代中选取$\vec{x} + \sigma \vec{d}$作为$\vec{x}$的值。换句话说,如果我们记$\alpha_{[i]}$为割线法第$i$轮迭代时$\alpha$的值,那么有:$\sigma_{[i+1]} = -\alpha_{[i]}$。
不管是牛顿法还是割线法,都应当在$\vec{x}$足够接近目标解的时候结束迭代。如果精度要求过低,可能会导致无法收敛。而如果精度要求过高,由于$f’’(\vec{x})$随着$\vec{x}$变化较大将导致共轭性很快丧失,那么计算速度则会不必要的变慢,最终还一无所获。因此,通常快速但并不特别准确的线性搜索会是较好的策略(举例来说,只进行固定迭代次数的牛顿法或割线法)。不幸的是,不特别准确的线性搜索可能会导致构建出来的搜索方向并非下降方向。一种通用的解决方案是测试这种可能性($\vec{r}^T \vec{d}$是否非正?),并且在必要的时候通过令$\vec{d} = \vec{r}$来重启CG算法。
两种方法还有个更大的问题,那就是他们无法辨别最小值和最大值。非线性CG算法的结果通常强烈依赖于初始点的选择,并且如果使用牛顿法或割线法的CG算法起始点接近于一个局部最大值点,那么它很可能就会收敛到这个点。
这些方法都各有千秋。牛顿法拥有更好的收敛速度,并且如果$\vec{d}^T f’’ \vec{d}$(或其近似)计算起来比较快(比如$\mathcal{O}(n)$的时间复杂度),那么更偏向于使用该算法。割线法则只需要计算函数$f$的一阶导数,但其最终是否能成功则取决于参数$\sigma$的取值是否够好。据此也很容易推导出其他算法。举例来说,通过在$f$上三个不同的点进行采样,甚至都不需要计算$f$的一阶导数我们也能获的$f(\vec{x} + \alpha \vec{d})$的近似抛物线。
通过选择一个近似$f’’(\textbf{A})$,同时$\textbf{M}^{-1} \vec{r}$比较容易计算的预处理器$\textbf{M}$,非线性CG算法同样可以做一些预处理。在线性CG算法中,预处理器尝试将整个二次型的图形变换为近似球形,而非线性CG算法预处理器则只在$\vec{x_{(i)}}$附近的区域内执行这种变换。
即使计算整个黑塞矩阵$f’’$的开销很大,我们通常也需要计算其对角阵来作为一个预处理器。然而,如果$\vec{x}$离任意一个局部最小值点足够远的话,你就要有心理准备了,因为此时黑塞矩阵的对角线上的元素可能并非全都是正的。一个预处理器应该是正定的,因此我们不允许对角元素存在非正值。一种保守的解决方案是,当我们无法确定黑塞矩阵的对角元素均为正定的时候,不进行预处理(即令$\textbf{M} = \textbf{I}$)。下图展示了对角矩阵预处理过得非线性CG算法的收敛过程,如下所示:
上图也可在图14-1中看到。这里,用$f’’$在目标解$\vec{x}$处的对角矩阵来对每一轮迭代进行预处理,连我自己都看不出原图的样子了。
共轭方向法(conjugate direction method)最早大约是在1908年由Schmidt[14]发表的,Fox,Huskey,Wilkinson[7]于1948年又再次独立的发明出来。在五十年代早期,CG算法由Hestenes[10]以及Stiefel[15]独立发现。用切比雪夫多项式来表示CG算法的收敛边界则是由Kaniel[12]提出的。对CG算法收敛性更为全面的分析则是由van der Sluis和van der Vorst[16]提供的。CG算法被Reid[13]推广为大型稀疏矩阵的一种迭代方法则是在1971年。
CG算法被泛化到非线性问题则是由Fletcher和Reeves[6]在1964年基于Davidon[4]和Fletcher、Powell[5]的工作完成的。非线性CG算法的非精准线性搜索的收敛性则是由Daniel[3]完成。Gilbert和Nocedal[8]两人则讨论了非线性CG算法的$\beta$的取值。
CG算法一直到70年代中期的历史性和广泛性的注释资料则是由Golub和O’Leary[9]提供的。自那时起,大多数的研究工作都聚焦在非对称系统上。Barrett et al[1]对线性系统求解的迭代方法进行了综述。
本章中分别针对前面讨论的算法给出了有效的伪代码实现。
给定输入$\textbf{A}$,起始点$\vec{x}$,最大迭代次数$i_{max}$以及可接受误差$\varepsilon < 1$,有:
$$
\begin{split}
&\textbf{Input:} \quad \textbf{A}, \ \vec{b}, \ \vec{x}, \ i_{max}, \ \varepsilon \\
&\textbf{Output:} \quad \vec{x} \\
&\textbf{Algorithm:} \\
&\qquad i \Leftarrow 0 \\
&\qquad \vec{r} \Leftarrow \vec{b} - \textbf{A} \vec{x} \\
&\qquad \delta \Leftarrow \vec{r}^T \vec{r} \\
&\qquad \delta_0 \Leftarrow \delta \\
&\qquad \textbf{while} \quad i < i_{max} \quad \textbf{and} \quad \delta > \varepsilon^2 \delta_{0} \quad \textbf{do} \\
&\qquad \qquad \qquad \vec{q} \Leftarrow \textbf{A} \vec{r} \\
&\qquad \qquad \qquad \alpha \Leftarrow \frac{\delta}{\vec{r}^T \vec{q} } \\
&\qquad \qquad \qquad \vec{x} \Leftarrow \vec{x} + \alpha \vec{r} \\
&\qquad \qquad \qquad \textbf{if} \quad (i \ \% \ 50) == 0 \\
&\qquad \qquad \qquad \qquad \vec{r} \Leftarrow \vec{b} - \textbf{A} \vec{x} \\
&\qquad \qquad \qquad \textbf{else} \\
&\qquad \qquad \qquad \qquad \vec{r} \Leftarrow \vec{r} - \alpha \vec{q} \\
&\qquad \qquad \qquad \delta \Leftarrow \vec{r}^T \vec{x} \\
&\qquad \qquad \qquad i \Leftarrow i + 1 \\
\end{split}
$$
上述算法在迭代次数达到$i_{max}$或者误差满足$||\vec{r_{(i)}}|| \le \varepsilon ||\vec{r_{(0)}}||$。
从上述伪代码我们可以看出,通常我们都会使用残差的快速递归计算公式,但是每50次迭代后,我们将重新计算精确的残差值以便消除浮点误差。当然,50这个值是任意的,可以根据需要自己指定。如果$n$较大,那么这个值指定为$\sqrt{n}$是较为合适的。如果误差的容忍度较大,那就不需要去校正残差了(在实际应用中,这种校正也很少使用)。如果误差容忍度接近浮点精度的极限,那么在计算出$\delta$的值后,需要检查$\delta \le \varepsilon^2 \delta_0$是否成立,如果成立,那么残差的精确值和$\delta$就应该重新进行计算。这样就能避免因浮点舍入误差导致的迭代过程提早结束问题。
输入同上面的最速下降法一样。
$$
\begin{split}
&\textbf{Input:} \quad \textbf{A}, \ \vec{b}, \ \vec{x}, \ i_{max}, \ \varepsilon \\
&\textbf{Output:} \quad \vec{x} \\
&\textbf{Algorithm:} \\
&\qquad i \Leftarrow 0 \\
&\qquad \vec{r} \Leftarrow \vec{b} - \textbf{A} \vec{x} \\
&\qquad \vec{d} \Leftarrow \vec{r} \\
&\qquad \delta_{new} \Leftarrow \vec{r}^T \vec{r} \\
&\qquad \delta_0 \Leftarrow \delta_{new} \\
&\qquad \textbf{while} \quad i < i_{max} \quad \textbf{and} \quad \delta_{new} > \varepsilon^2 \delta_{0} \quad \textbf{do} \\
&\qquad \qquad \qquad \vec{q} \Leftarrow \textbf{A} \vec{d} \\
&\qquad \qquad \qquad \alpha \Leftarrow \frac{\delta_{new}}{\vec{d}^T \vec{q} } \\
&\qquad \qquad \qquad \vec{x} \Leftarrow \vec{x} + \alpha \vec{d} \\
&\qquad \qquad \qquad \textbf{if} \quad (i \ \% \ 50) == 0 \\
&\qquad \qquad \qquad \qquad \vec{r} \Leftarrow \vec{b} - \textbf{A} \vec{x} \\
&\qquad \qquad \qquad \textbf{else} \\
&\qquad \qquad \qquad \qquad \vec{r} \Leftarrow \vec{r} - \alpha \vec{q} \\
&\qquad \qquad \qquad \delta_{old} \Leftarrow \delta_{new} \\
&\qquad \qquad \qquad \delta_{new} \Leftarrow \vec{r}^T \vec{r} \\
&\qquad \qquad \qquad \beta \Leftarrow \frac{\delta_{new}}{\delta_{old}} \\
&\qquad \qquad \qquad \vec{d} \Leftarrow \vec{r} + \beta \vec{d} \\
&\qquad \qquad \qquad i \Leftarrow i + 1 \\
\end{split}
$$
详细注释说明见上节B1的说明。
输入同上,另外多了预处理器$\textbf{M}$(可能是非显式定义的)。
$$
\begin{split}
&\textbf{Input:} \quad \textbf{A}, \ \textbf{M}, \ \vec{b}, \ \vec{x}, \ i_{max}, \ \varepsilon \\
&\textbf{Output:} \quad \vec{x} \\
&\textbf{Algorithm:} \\
&\qquad i \Leftarrow 0 \\
&\qquad \vec{r} \Leftarrow \vec{b} - \textbf{A} \vec{x} \\
&\qquad \vec{d} \Leftarrow \textbf{M}^{-1} \vec{r} \\
&\qquad \delta_{new} \Leftarrow \vec{r}^T \vec{d} \\
&\qquad \delta_0 \Leftarrow \delta_{new} \\
&\qquad \textbf{while} \quad i < i_{max} \quad \textbf{and} \quad \delta_{new} > \varepsilon^2 \delta_{0} \quad \textbf{do} \\
&\qquad \qquad \qquad \vec{q} \Leftarrow \textbf{A} \vec{d} \\
&\qquad \qquad \qquad \alpha \Leftarrow \frac{\delta_{new}}{\vec{d}^T \vec{q} } \\
&\qquad \qquad \qquad \vec{x} \Leftarrow \vec{x} + \alpha \vec{d} \\
&\qquad \qquad \qquad \textbf{if} \quad (i \ \% \ 50) == 0 \\
&\qquad \qquad \qquad \qquad \vec{r} \Leftarrow \vec{b} - \textbf{A} \vec{x} \\
&\qquad \qquad \qquad \textbf{else} \\
&\qquad \qquad \qquad \qquad \vec{r} \Leftarrow \vec{r} - \alpha \vec{q} \\
&\qquad \qquad \qquad \vec{s} \Leftarrow \textbf{M}^{-1} \vec{r} \\
&\qquad \qquad \qquad \delta_{old} \Leftarrow \delta_{new} \\
&\qquad \qquad \qquad \delta_{new} \Leftarrow \vec{r}^T \vec{s} \\
&\qquad \qquad \qquad \beta \Leftarrow \frac{\delta_{new}}{\delta_{old}} \\
&\qquad \qquad \qquad \vec{d} \Leftarrow \vec{s} + \beta \vec{d} \\
&\qquad \qquad \qquad i \Leftarrow i + 1 \\
\end{split}
$$
上述伪代码中的$\vec{s} \Leftarrow \textbf{M}^{-1} \vec{r}$表示在迭代过程中我们需要使用预处理器,当然,预处理器不一定非的是矩阵的形式。其它的注释见B1的说明。
给定输入$f$(函数),起始点$\vec{x}$,最大迭代次数$i_{max}$以及CG算法可接受误差$\varepsilon < 1$,牛顿法最大迭代次数$j_{max}$,牛顿法可接受误差$\epsilon < 1$有:
$$
\begin{split}
&\textbf{Input:} \quad f, \ \vec{x}, \ i_{max}, \ j_{max}, \ \varepsilon, \ \epsilon \\
&\textbf{Output:} \quad \vec{x} \\
&\textbf{Algorithm:} \\
&\qquad i \Leftarrow 0 \\
&\qquad k \Leftarrow 0 \\
&\qquad \vec{r} \Leftarrow -f’(\vec{x}) \\
&\qquad \vec{d} \Leftarrow \vec{r} \\
&\qquad \delta_{new} \Leftarrow \vec{r}^T \vec{r} \\
&\qquad \delta_0 \Leftarrow \delta_{new} \\
&\qquad \textbf{while} \quad i < i_{max} \quad \textbf{and} \quad \delta_{new} > \varepsilon^2 \delta_{0} \quad \textbf{do} \\
&\qquad \qquad \qquad j \Leftarrow 0 \\
&\qquad \qquad \qquad \delta_{\vec{d}} \Leftarrow \vec{d}^T \vec{d} \\
&\qquad \qquad \qquad \textbf{Do} \\
&\qquad \qquad \qquad \qquad \alpha \Leftarrow -\frac{[f’(\vec{x})]^T \vec{d}}{\vec{d}^T f’’(\vec{x}) \vec{d} } \\
&\qquad \qquad \qquad \qquad \vec{x} \Leftarrow \vec{x} + \alpha \vec{d} \\
&\qquad \qquad \qquad \qquad j \Leftarrow j + 1 \\
&\qquad \qquad \qquad \textbf{while} \quad j < j_{max} \quad \textbf{and} \quad \alpha^2 \delta_{\vec{d}} > \epsilon^2 \\
&\qquad \qquad \qquad \vec{r} \Leftarrow -f’(\vec{x}) \\
&\qquad \qquad \qquad \delta_{old} \Leftarrow \delta_{new} \\
&\qquad \qquad \qquad \delta_{new} \Leftarrow \vec{r}^T \vec{r} \\
&\qquad \qquad \qquad \beta \Leftarrow \frac{\delta_{new}}{\delta_{old}} \\
&\qquad \qquad \qquad \vec{d} \Leftarrow \vec{r} + \beta \vec{d} \\
&\qquad \qquad \qquad k \Leftarrow k + 1 \\
&\qquad \qquad \qquad \textbf{if} \quad k == n \quad \textbf{or} \quad \vec{r}^T \vec{d} \le 0 \\
&\qquad \qquad \qquad \qquad \vec{d} \Leftarrow \vec{r} \\
&\qquad \qquad \qquad \qquad k \Leftarrow 0 \\
&\qquad \qquad \qquad i \Leftarrow i + 1 \\
\end{split}
$$
上述算法在迭代次数达到$i_{max}$或者误差满足$||\vec{r_{(i)}}|| \le \varepsilon ||\vec{r_{(0)}}||$。
每一轮Newton-Raphson迭代都将$\vec{x}$的值增加$\alpha \vec{d}$。当每次更新的$\alpha \vec{d}$低于我们给定的可容忍误差(即$||\alpha \vec{d}|| \le \epsilon$)或者迭代次数达到$j_{max}$时,迭代终止。快速非精确线性搜索可以通过使用较小的$j_{max}$值和/或黑塞矩阵$f’’(\vec{x})$的近似来完成。
每当计算出来的新搜索方向不是下降方向时,非线性CG算法即重启(通过令$\vec{d} \Leftarrow \vec{r}$)。为了在$n$值较小的情况下改善收敛性,算法通常也会在每执行完$n$轮迭代后重启。
在计算$\alpha$的时候可能会发生除0错误。当起始点$\vec{x_{(0)}}$不够接近接近我们想要的最小值点或者$f$并非连续二阶可微的时候就会发生这种错误。在前一种情况下,解决的办法是选择一个较好的起始点或执行更为复杂的线性搜索。在后面的一种情况下,CG算法可能并非最合适的最小化算法。
给定输入$f$(函数),起始点$\vec{x}$,最大迭代次数$i_{max}$以及CG算法可接受误差$\varepsilon < 1$,割线法每步执行参数$\delta_{0}$,割线法最大迭代次数$j_{max}$,割线法可接受误差$\epsilon < 1$有:
$$
\begin{split}
&\textbf{Input:} \quad f, \ \vec{x}, \ \delta_0, \ i_{max}, \ j_{max}, \ \varepsilon, \ \epsilon \\
&\textbf{Output:} \quad \vec{x} \\
&\textbf{Algorithm:} \\
&\qquad i \Leftarrow 0 \\
&\qquad k \Leftarrow 0 \\
&\qquad \vec{r} \Leftarrow -f’(\vec{x}) \\
&\qquad \textbf{M} \approx f’’(\vec{x}) \\
&\qquad \vec{s} \Leftarrow \textbf{M}^{-1} \vec{r} \\
&\qquad \vec{d} \Leftarrow \vec{s} \\
&\qquad \delta_{new} \Leftarrow \vec{r}^T \vec{d} \\
&\qquad \delta_0 \Leftarrow \delta_{new} \\
&\qquad \textbf{while} \quad i < i_{max} \quad \textbf{and} \quad \delta_{new} > \varepsilon^2 \delta_{0} \quad \textbf{do} \\
&\qquad \qquad \qquad j \Leftarrow 0 \\
&\qquad \qquad \qquad \delta_{\vec{d}} \Leftarrow \vec{d}^T \vec{d} \\
&\qquad \qquad \qquad \alpha \Leftarrow -\sigma_{0} \\
&\qquad \qquad \qquad \eta_{prev} \Leftarrow [f’(\vec{x} + \sigma_{0} \vec{d})]^T \vec{d} \\
&\qquad \qquad \qquad \textbf{Do} \\
&\qquad \qquad \qquad \qquad \eta \Leftarrow [f’(\vec{x})]^T \vec{d} \\
&\qquad \qquad \qquad \qquad \alpha \Leftarrow \alpha \frac{\eta}{\eta_{prev} - \eta} \\
&\qquad \qquad \qquad \qquad \vec{x} \Leftarrow \vec{x} + \alpha \vec{d} \\
&\qquad \qquad \qquad \qquad \eta_{prev} \Leftarrow \eta \\
&\qquad \qquad \qquad \qquad j \Leftarrow j + 1 \\
&\qquad \qquad \qquad \textbf{while} \quad j < j_{max} \quad \textbf{and} \quad \alpha^2 \delta_{\vec{d}} > \epsilon^2 \\
&\qquad \qquad \qquad \vec{r} \Leftarrow -f’(\vec{x}) \\
&\qquad \qquad \qquad \delta_{old} \Leftarrow \delta_{new} \\
&\qquad \qquad \qquad \delta_{mid} \Leftarrow \vec{r}^T \vec{s} \\
&\qquad \qquad \qquad \textbf{M} \approx f’’(\vec{x}) \\
&\qquad \qquad \qquad \vec{s} \Leftarrow \textbf{M}^{-1} \vec{r} \\
&\qquad \qquad \qquad \delta_{new} \Leftarrow \vec{r}^T \vec{s} \\
&\qquad \qquad \qquad \beta \Leftarrow \frac{\delta_{new} - \delta_{mid}}{\delta_{old}} \\
&\qquad \qquad \qquad k \Leftarrow k + 1 \\
&\qquad \qquad \qquad \textbf{if} \quad k == n \quad \textbf{or} \quad \beta \le 0 \\
&\qquad \qquad \qquad \qquad \vec{d} \Leftarrow \vec{s} \\
&\qquad \qquad \qquad \qquad k \Leftarrow 0 \\
&\qquad \qquad \qquad \textbf{else} \\
&\qquad \qquad \qquad \qquad \vec{d} \Leftarrow \vec{s} + \beta \vec{d} \\
&\qquad \qquad \qquad i \Leftarrow i + 1 \\
\end{split}
$$
上述算法在迭代次数达到$i_{max}$或者误差满足$||\vec{r_{(i)}}|| \le \varepsilon ||\vec{r_{(0)}}||$。
每一轮割线法迭代都将$\vec{x}$的值增加$\alpha \vec{d}$。当每次更新的$\alpha \vec{d}$低于我们给定的可容忍误差(即$||\alpha \vec{d}|| \le \epsilon$)或者迭代次数达到$j_{max}$时,迭代终止。参数$\delta_{0}$决定了等式($\ref{alpha_value_of_function_minimized}$)中$\sigma$的值,它用于每种割线最小化方法的第一步。然而不幸的是,为了算法能够顺利收敛,我们有时候可能不得不调整该参数的值。
P·R方程的$\beta$参数的计算公式为:
$$
\beta = \frac{\delta_{new} - \delta_{mid}}{\delta_{old}} = \frac{\vec{r_{(i+1)}}^T \vec{s_{(i+1)}} - \vec{r_{(i+1)}}^T \vec{s_{(i)}} } {\vec{r_{(i)}}^T \vec{s_{(i)}}} = \frac{ \vec{r_{(i+1)}}^T \textbf{M}^{-1} (\vec{r_{(i+1)}} - \vec{r_{(i)}}) } { \vec{r_{(i)}}^T \textbf{M}^{-1} \vec{r_{(i)}} }
\label{beta_expression_of_appendix_b5} \tag{B5 - 1}
$$
应用上述公式需要注意,预处理器$\textbf{M}$必须要是正定的,并且其形式也并非一定要是矩阵。
当P·R方程的$\beta$参数为负的时候,我们就需要重启非线性CG算法(通过令$\vec{d} = \vec{r}$)。同样的,当$n$较小的时候,为了改善收敛性,每执行$n$轮我们就需要重启算法。
非线性CG算法提供了多种选择:是否进行预处理、牛顿法或割线法或其它方法、F·R方程或P·R方程。从上面的版本中应该可以衍生出其它变形算法。
假定矩阵$\textbf{A}$是对称阵。设点$\vec{x}$为满足$\textbf{A} \vec{x} = \vec{b}$并且使得二次型(等式$\ref{quadratic_form_expression}$)最小化的点,$\vec{e}$为误差项,则我们有:
$$
\begin{split}
f(\vec{x} + \vec{e}) &= \frac{1}{2} (\vec{x} + \vec{e})^T \textbf{A} (\vec{x} + \vec{e}) - \vec{b}^T (\vec{x} + \vec{e}) + c \quad (据等式\ref{quadratic_form_expression}) \\
&= \frac{1}{2} \vec{x}^T \textbf{A} \vec{x} + \vec{e}^T \textbf{A} \vec{x} + \frac{1}{2} \vec{e}^T \textbf{A} \vec{e} - \vec{b}^T \vec{x} - \vec{b}^T \vec{e} + c \quad (由\textbf{A}对称性) \\
&= \underbrace{ \frac{1}{2} \vec{x}^T \textbf{A} \vec{x} - \vec{b}^T \vec{x} + c }_{f(\vec{x})} + \bcancel{\vec{e}^T \vec{b}} + \frac{1}{2} \vec{e}^T \textbf{A} \vec{e} - \bcancel{\vec{b}^T \vec{e}} \\
&= f(\vec{x}) + \frac{1}{2} \vec{e}^T \textbf{A} \vec{e}
\end{split}
\label{equation_of_appendix_c1} \tag{C1 - 1}
$$
若矩阵$\textbf{A}$为正定阵,则上式最后一项$\frac{1}{2} \vec{e}^T \textbf{A} \vec{e}$对于任意的$\vec{e} \ne 0$都为正,因此$\vec{x}$使得$f$最小(即误差$\vec{e} = 0$的时候)。
任意矩阵至少有一个特征向量。为了证明这点,我们记$det(\textbf{A} - \lambda \textbf{I})$是关于$\lambda$的多项式,并且至少存在一个零点/根(可能是复数),记为$\lambda_{\textbf{A}}$。由此矩阵$\textbf{A} - \lambda_{\textbf{A}} \textbf{I}$的行列式为0,该矩阵为奇异矩阵。因此必然存在一个非零向量$\vec{v}$满足$(\textbf{A} - \lambda_{\textbf{A}} \textbf{I}) \vec{v} = 0$。则向量$\vec{v}$即为一个特征向量,因为有$\textbf{A} \vec{v} = \lambda_{\textbf{A}} \vec{v}$,结论得证!
任意一个($n$维)对称阵都有$n$个正交特征向量。为了证明这一点,我会用一个$4 \times 4$的矩阵$\textbf{A}$来演示,同时能让大家推广到任意尺寸的矩阵上去。上面我们已经证明了矩阵$\textbf{A}$至少一个特征向量$\vec{v}$,同时其对应的特征值为$\lambda_{\textbf{A}}$。令$\vec{x_1} = \frac{\vec{v}} {||\vec{v}||}$,其长度为单位长度。另选三个任意向量$\vec{x_2}、\vec{x_3}、\vec{x_4}$以便这些向量之间两两相互正交,同时向量长度均为单位长度(这些向量可以通过格莱姆·施密特正交化方法求得)。再令$\textbf{X} = [\vec{x_1} \ \vec{x_2} \ \vec{x_3} \ \vec{x_4}]$,因为这些向量两两正交,因此有$\textbf{X}^T \textbf{X} = \textbf{I}$以及$\textbf{X}^T = \textbf{X}^{-1}$。同时我们注意到对$i \ne 1$有$\vec{x_1}^T \textbf{A} \vec{x_i} = \vec{x_i}^T \textbf{A} \vec{x_1} = \vec{x_i}^T \lambda_{\textbf{A}} \vec{x_1} = 0$,由此我们有:
$$
\begin{split}
\textbf{X}^T \textbf{A} \textbf{X} &= \begin{bmatrix}
\vec{x_1}^T \\
\vec{x_2}^T \\
\vec{x_3}^T \\
\vec{x_4}^T \\
\end{bmatrix} \textbf{A}
\begin{bmatrix}
\vec{x_1} & \vec{x_2} & \vec{x_3} & \vec{x_4} \\
\end{bmatrix} \\
&= \begin{bmatrix}
\vec{x_1}^T \\
\vec{x_2}^T \\
\vec{x_3}^T \\
\vec{x_4}^T \\
\end{bmatrix}
\begin{bmatrix}
\lambda_{\textbf{A}} \vec{x_1} & \textbf{A} \vec{x_2} & \textbf{A} \vec{x_3} & \textbf{A} \vec{x_4} \\
\end{bmatrix} \\
&=
\begin{bmatrix}
\lambda_{\textbf{A}} & 0 & 0 & 0 \\
0 & & & \\
0 & & \textbf{B} & \\
0 & & & \\
\end{bmatrix}
\end{split}
\label{equation_of_appendix_c2} \tag{C2 - 1}
$$
上式中$\textbf{B}$为一个$3 \times 3$的对称阵。则$\textbf{B}$必然有特征值为$\lambda_{\textbf{B}}$的特征向量$\vec{w}$(前面我们证明了任意一个矩阵必然存在至少一个特征向量)。令$\vec{\hat{w}}$为一个首个元素为0其它三个元素为$w$的元素的4维向量,显然我们有:
$$
\textbf{X}^{-1} \textbf{A} \textbf{X} \vec{\hat{w}} = \textbf{X}^T \textbf{A} \textbf{X} \vec{\hat{w}} = \begin{bmatrix}
\lambda_{\textbf{A}} & 0 & 0 & 0 \\
0 & & & \\
0 & & \textbf{B} & \\
0 & & & \\
\end{bmatrix} \vec{\hat{w}} = \lambda_{\textbf{B}} \vec{\hat{w}}
\label{equation2_of_appendix_c2} \tag{C2 - 2}
$$
换句话说,$\textbf{A} \textbf{X} \vec{\hat{w}} = \lambda_{\textbf{B}} \textbf{X} \vec{\hat{w}}$,因此$ \textbf{X} \vec{\hat{w}}$为矩阵$ \textbf{A}$的一个特征向量。此外,由于$\vec{x_1}^T \textbf{X} \vec{\hat{w}} = [1 \ \ 0 \ \ 0 \ \ 0] \vec{\hat{w}} = 0$,因此$\vec{x_1}$与$ \textbf{X} \vec{\hat{w}}$正交。所以,(当矩阵$ \textbf{B}$至少有一个特征向量时)矩阵 \textbf{A}$至少有两个相互正交的向量。
关于($n$维)对称阵必定存在$n$个相互正交的特征向量的更一般的论述是通过归纳法证明的。比如上面的例子,假定任意一个$3 \times 3$的矩阵(比如$\textbf{B}$)有3个正交的特征向量,分别记作$\vec{\hat{w_1}}、\vec{\hat{w_2}}、\vec{\hat{w_3}}$,则$\textbf{X} \vec{\hat{w_1}}、\textbf{X} \vec{\hat{w_2}}、\textbf{X} \vec{\hat{w_3}}$均为矩阵$ \textbf{A}$的特征向量并且他们相互正交,因为$\textbf{X}$的列向量相互正交,这样就把原本正交的向量映射为了新的正交向量。因此,矩阵$ \textbf{A}$有4个相互正交的特征向量。
译者注:这里原文是eigenvalue w
,应该是作者笔误。结合上下文,这里$w$应该是特征向量才对。
对于像式($\ref{boundary_of_error_term_norm}$)这种表达式的最小化求解,切比雪夫不等式是最优的,因为相比其它在定义域$[-1, 1]$内增长速度不超过1的多项式,它在定义域$[-1, 1]$之外的范围内增长得更快。
$i$阶(次)切比雪夫多项式的公式如下:
$$
T_i(\omega) = \frac{1}{2} \Big [ (\omega + \sqrt{\omega^2 - 1})^i + (\omega - \sqrt{\omega^2 - 1})^i]
\label{expression1_of_appendix_c3} \tag{C3 - 1}
$$
上式在定义域$[-1, 1]$内还可以用如下式子来表示:
$$
T_i(\omega) = cos(i cos^{-1} \omega) \qquad -1 \le \omega \le 1
\label{expression2_of_appendix_c3} \tag{C3 - 2}
$$
由上式及图9-2,我们可以推断出切比雪夫多项式有如下特性:
$$
|T_i(\omega)| \le 1 \qquad -1 \le \omega \le 1
\label{expression3_of_appendix_c3} \tag{C3 - 3}
$$
并且其值在-1和1之间高频率的震荡:
$$
T_i \Big( cos \Big( \frac{k \pi}{i} \Big) \Big) = (-1)^k \qquad k=0,1,\cdots,i
\label{expression4_of_appendix_c3} \tag{C3 - 4}
$$
注意,$T_i$的$i$个零点必然落在定义域$[-1, 1]$内的$i + 1$个极值点之间。具体示例可以参见图9-2的$T_5(\omega)$的5个零点。
类似的,函数:
$$
P_i(\lambda) = \frac{T_i \Big( \frac{\lambda_{max} + \lambda_{min} - 2\lambda} {\lambda_{max} - \lambda_{min}} \Big )} {T_i \Big( \frac{\lambda_{max} + \lambda_{min}} {\lambda_{max} - \lambda_{min}} \Big)} \\
\label{expression5_of_appendix_c3} \tag{C3 - 5}
$$
在定义域$[\lambda_{min}, \lambda_{max}]$内其函数值则在$\pm T_i(\frac{\lambda_{max} + \lambda_{min}} {\lambda_{max} - \lambda_{min}})^{-1}$之间震荡。同时$P_i(\lambda)$还满足$P_i(0) = 1$的条件。
上述结论的证明需要一个小小的技巧:即在定义域$[\lambda_{min}, \lambda_{max}]$内没有比$P_i$更好的满足$Q_i(0) = 1$的$i$阶多项式$Q_i(\lambda)$。为了证明这点,我们假设存在这样的一个多项式,则在定义域$[\lambda_{min}, \lambda_{max}]$内必然有$Q_i(\lambda) < T_i(\frac{\lambda_{max} + \lambda_{min}} {\lambda_{max} - \lambda_{min}})^{-1}$。我们注意到由于多项式$P_i - Q_i$在$\lambda = 0$处存在一个零点(因为$P_i(0) = 1、Q_i(0) = 1$),同时在$[\lambda_{min}, \lambda_{max}]$内有$i$个零点。因此,多项式$P_i - Q_i$是一个至少有$i + 1$个零点的$i$阶多项式,这与事实违背,所以原假设不成立。由此可以推导出我们的结论:对于像式($\ref{boundary_of_error_term_norm}$)这种表达式的最小化求解,切比雪夫不等式是最优的。
对于下面这些问题,假定(除非另作说明)你使用的算法是精准的,没有浮点舍入误差:
$$
\begin{split}
&Choose \ an \ starting \ porint \ \vec{x_{(0)}}\\
&\textbf{for} \quad i \Leftarrow 0 \quad \textbf{to} \quad d - 1 \\
&\qquad \vec{r_{(i)}} \Leftarrow \vec{b} - \textbf{A} \vec{x_{(i)}} \\
&\qquad Remove \ an \ arbitrary \ eigenvalue \ from \ the \ list \ and \ call \ it \ \lambda_i \\
&\qquad \vec{x_{(i+1)}} \Leftarrow \vec{x_{(i)}} + \lambda_i^{-1} \vec{r_{(i)}} \\
\end{split}
$$
如上伪代码中,每个特征值均只使用1次,迭代结束后,特征值列表也取空了。
(a) 证明在上述算法迭代终止时,$\vec{x_{(d)}}$是$\textbf{A} \vec{x} = \vec{b}$的解。
提示:仔细阅读6.1节,画出二维例子的收敛过程。作出特征向量轴,并先尝试使用每个特征值(直观的展示算法干了些什么是至关重要的,如果你可以的话,也可以用等式来证明。)。
(b) 尽管在$d$轮迭代后,上述搜索路径精准的找到了目标解,但你仍然希望迭代过程中的每个中间解$\vec{x_{(i)}}$都能尽可能的靠近目标解。请总结出一条粗略的经验法则,如何从特征值列表中取特征才能达到这个目标(换句话说,如何确定特征值的使用顺序)。
(c) 如果发生了浮点舍入误差,会导致上述算法产生什么样的严重后果?
(d) 请总结出一条粗略的经验法则,如何从特征值列表中选择特征值才能有效的避免浮点舍入误差被逐步放大?
提示:这个问题的答案与(b)问题的答案并不一样。
[1] Richard Barrett, Michael Berry, Tony Chan, James Demmel, June Donato, Jack Dongarra, Victor Eijkhout, Roldan Pozo, Charles Romine, and Henk van der Vorst, Templates for the Solution of Linear Systems: Building Blocks for Iterative Methods, SIAM, Philadelphia, Pennsylvania, 1993.
[2] William L. Briggs, A Multigrid Tutorial, SIAM, Philadelphia, Pennsylvania, 1987.
[3] James W. Daniel, Convergence of the Conjugate Gradient Method with Computationally Convenient Modifications, Numerische Mathematik 10 (1967), 125–131.
[4] W. C. Davidon, Variable Metric Method for Minimization, Tech. Report ANL-5990, Argonne National Laboratory, Argonne, Illinois, 1959.
[5] R. Fletcher and M. J. D. Powell, A Rapidly Convergent Descent Method for Minimization, Computer Journal 6 (1963), 163–168.
[6] R. Fletcher and C. M. Reeves, Function Minimization by Conjugate Gradients, Computer Journal 7 (1964), 149–154.
[7] L. Fox, H. D. Huskey, and J. H. Wilkinson, Notes on the Solution of Algebraic Linear Simultaneous Equations, Quarterly Journal of Mechanics and Applied Mathematics 1 (1948), 149–173.
[8] Jean Charles Gilbert and Jorge Nocedal, Global Convergence Properties of Conjugate Gradient Methods for Optimization, SIAM Journal on Optimization 2 (1992), no. 1, 21–42.
[9] Gene H. Golub and Dianne P. O’Leary, Some History of the Conjugate Gradient and Lanczos Algorithms: 1948–1976, SIAM Review 31 (1989), no. 1, 50–102.
[10] Magnus R. Hestenes, Iterative Methods for Solving Linear Equations, Journal of Optimization Theory and Applications 11 (1973), no. 4, 323–334, Originally published in 1951 as NAML Report No. 52-9, National Bureau of Standards, Washington, D.C.
[11] Magnus R. Hestenes and Eduard Stiefel, Methods of Conjugate Gradients for Solving Linear Systems, Journal of Research of the National Bureau of Standards 49 (1952), 409–436.
[12] Shmuel Kaniel, Estimates for Some Computational Techniques in Linear Algebra, Mathematics of Computation 20 (1966), 369–378.
[13] John K. Reid, On the Method of Conjugate Gradients for the Solution of Large Sparse Systems of Linear Equations, Large Sparse Sets of Linear Equations (London and New York) (John K. Reid, ed.), Academic Press, London and New York, 1971, pp. 231–254.
[14] E. Schmidt, Title unknown, Rendiconti del Circolo Matematico di Palermo 25 (1908), 53–77.
[15] Eduard Stiefel, Uber ¨ einige Methoden der Relaxationsrechnung, Zeitschrift fur¨ Angewandte Mathematik und Physik 3 (1952), no. 1, 1-33.
[16] A. van der Sluis and H. A. van der Vorst, The Rate of Convergence of Conjugate Gradients, Numerische Mathematik 48 (1986), no. 5, 543–560.
相信很多读者读完之后会对本文的标题产生疑问,既然”通俗”,有些地方为何读起来还是不太容易理解。“通俗”$\ne$ “简单”,数学的东西,尤其是理论的东西从来就没有简单这一说。“通俗”是指尽量具象化、实例化,以便读者更为直观的去理解其中的奥妙。我认为“通俗”二字才是对原作者标题中painless
这个词最好的诠释。
翻译这篇英文文献最大的感触就是,要读懂一篇文章相对来说还算比较容易的,但要用自己的话、用专业的术语、用流畅的文风把它娓娓道来,孰非易事。由此也深深感到自己英语能力的匮乏。
翻译此文也给我留下很大的疑问,为何这种基础性理论的东西,只有国外的这些人愿意花时间去做,不仅做了,还做的非常好。
前前后后利用两个多月的空闲时间终于把这篇文章翻译完了。文中许多内容是几经删改,用词遣句也是反复推敲,生怕丢失了作者的原意。虽说如此,鉴于作者自身的能力有限,加之成文时间仓促,疏漏在所难免,若能及时指正,感激不尽!
两个多月的时间,同时也是对自己毅力的一种考验。最后,借乔帮主最喜欢的一句话送给诸位读者共勉:
Stay hungry, Stay foolish!
南箕北有斗,牵牛不负轭。——汉·《明月皎夜光》
在没有深挖这个词之前,只是一直觉得它听起来既优雅又神秘。直到最近看一些Paper,里面涉及到了共轭梯度,再想起之前学过的共轭曲线、共轭复数、共轭转置,忽然觉得这些花狸狐哨的名词背后可能隐藏者一个不被我所知的理论体系,出于好奇和知识回顾的需要,就有了这篇文章。
轭这个词汉语里面是有的,但是共轭(conjugate/conjugation)这个词(概念)却是从英语里面翻译过来的。与熵(entropy)这类词所不同的是,轭这个词是有真真切切的实物所对应的,是有非常具象的含义而非译者自己凭空创造出来的。下面这个图就是一副轭:
上图实际上是一副牛轭,分别将两只牛的牛头塞进那两个木套子,就能驭使它们犁地、耕田、拉货。如果只有一个套子的,就只能驭使一头牛,即为单轭。像上面这样有两个套子的,即为双轭(共轭)。
有了图就很好解释和理解了。所谓的共轭关系是什么关系?共轭关系就是这幅双轭里面两头牛之间的关系:既相互制衡,相互约束,相互对立,又相互支撑,相互依存,相互统一。由于科学技术的长足发展,许多原本统一的概念在各个领域有了自己专属的含义。共轭也一样,在不同的科学领域有着不同的定义,共轭这个术语出现的学科包括但不限于以下这些:
本文主要还是针对其数学领域而言,其他领域的请自行参考相关资料。
目前数学里面定义的共轭的类型包括但不限于以下内容:
上述定义还不是数学里面全部的概念,零零总总加起来也已经有十几个了。这也正好验证了我们最开始的猜想:共轭的背后其实有一个庞大的家族,分布在各个研究方向中。
要把这些概念都介绍一遍是非常耗时耗力的,也没有必要(其实是不了解),有许多高深的理论比如群论暂时我也用不上。因此只选择一些平时用的比较多的来讲,后面有需要的时候再来补充。
共轭复数(复共轭)是指实数部分相等,虚数部分互为相反数的一对复数,形如:
$$
z = a + bj \stackrel{conj}{\longleftrightarrow} \bar{z} = a -bj
\tag{2 - 1}
$$
或者用极坐标的形式表示为:
$$
z = r \cdot e^{\varphi \cdot j} \stackrel{conj}{\longleftrightarrow} \bar{z} = r \cdot e^{-\varphi \cdot j}
\tag{2 - 2 - 1}
$$
上述两种形式通过欧拉公式相互连接起来,如下图所示:
注:对于任意复数z,其共轭复数通常记作$\overline{z}$,也有的资料记作$z^*$。
对任意给定的两复数$z、w$,有:
$$
\begin{cases}
\begin{split}
\overline{z \pm w} &= \overline{z} \pm \overline{w} \\
\overline{\lgroup \frac{z}{w} \rgroup} &= \frac{\overline{z}}{\overline{w}}, w \neq 0 \\
\overline{z \ast w} &= \overline{z} \ast \overline{w} \\
\overline{z^n}&= (\overline{z})^n \\
|\overline{z}| &= |z| \\
e^\overline{z} &= \overline{e^z} \\
log(\overline{z}) &= \overline{log(z)}, z \neq 0
\end{split}
\end{cases}
\tag{2 - 2 - 3}
$$
此外,对任意给定的函数\varphi(x),若它为一个全纯函数(Holomorphic function),则恒有:
$$
\varphi(\overline{z}) = \overline{\varphi(z)}
\tag{2 - 2 - 4}
$$
共轭根式(Conjugate-squre-roots)可能是这里面最简单的了,它是指满足如下形式的一对含根号的式子:
$$
a + b \sqrt{d} \stackrel{conj}{\longleftrightarrow} a - b \sqrt{d}
\tag{2 - 3 - 1}
$$
实际上共轭复数是共轭根式的一个特例($a = 0, \ b=1, \ d=-1$)。
共轭根式的主要特性是:其和、积不包含平方根项,如下所示:
$$
\begin{cases}
\begin{split}
(a + b \sqrt{d}) \cdot (a - b \sqrt{d}) &= a^2 - db^2 \\
(a + b \sqrt{d}) + (a + b \sqrt{d}) &= 2a
\end{split}
\end{cases}
\tag{2 - 3 - 2}
$$
通常我们会用共轭根式的这个特性来消除分母中的平方根,如下示例:
$$
\frac{1}{a + b \sqrt{d}} = \frac{a - b \sqrt{d}}{a^2 - db^2}
\tag{2 - 3- 3}
$$
这个技巧相信大家平时几乎都用的炉火纯青了。这里需要提醒的是,对于$d$的取值,并没有特殊要求,它甚至可以是一个复数。
共轭转置(也称埃尔米特共轭、埃尔米特转置)定义为满足如下等式的复数矩阵:
$$
(\textbf{A}^*)_{ij} = \overline{\textbf{A}_{ji}}
\tag{2 - 4 - 1}
$$
式中矩阵$\textbf{A}^*$即表示矩阵$\textbf{A}$的共轭转置矩阵。不同的学科中有不同的记法,但都表示同一个意思。线性代数中通常用$\textbf{A}^*$或者$\textbf{A}^H$来表示。
注:按照Wikipedia的说法,某些情况下$\textbf{A}^*$仅仅表示对原矩阵$\textbf{A}$的元素取复共轭(而不做转置)后形成的矩阵,具体属于哪种情况请结合上下文判断。
上式等价的语言定义就是:共轭转置矩阵的每一个元素是原矩阵先做转置后对应元素的复共轭。共轭转置矩阵就是原矩阵先取转置矩阵,再将转置后的矩阵的每一个元素取复共轭后形成的矩阵。
也即上述定义可以等价的表示为如下等式:
$$
\textbf{A}^* = (\overline{\textbf{A}})^T = \overline{\textbf{A}^T}
\tag{2 - 4 - 2}
$$
从上式也可以看出,无论是先对原矩阵做转置,再取复共轭。还是先对原矩阵取复共轭,再做转置。最终的结果都相同。
共轭转置稍微复杂一些,因此我们举个例子来说明,给定如下复数矩阵:
$$
\textbf{A} =
\begin{bmatrix}
1+j & -2 & 3-2j \\
5 & 5-j & 2+j\\
-j & 4 & 3+j
\end{bmatrix}
\tag{2 - 4 - 3}
$$
则其对应的共轭转置为:
$$
\textbf{A}^* =
\begin{bmatrix}
1-j & 5 & j \\
-2 & 5+j & 4\\
3+2j & 2-j & 3-j
\end{bmatrix}
\tag{2 - 4 - 4}
$$
由上可知,对称矩阵实际上是共轭转置矩阵的一种特殊情况(原矩阵所有元素均为实数)。
共轭转置有如下运算特性:
$$
\begin{cases}
\begin{split}
(\textbf{A} + \textbf{B})^* &= \textbf{A}^* + \textbf{B}^* \\
(r\textbf{A})^* &= \overline{r} \textbf{A}^*, \quad r为任意复数 \\
(\textbf{A} \textbf{B})^* &= \textbf{B}^* \textbf{A}^* \\
(\textbf{A}^*)^* &= \textbf{A} \\
det(\textbf{A}^*) &= (det \ \textbf{A})^*, \quad \text{A}为方阵 \\
tr(\textbf{A}^*) &= (tr \ \textbf{A})^*, \quad \text{A}为方阵 \\
eig(\textbf{A}^*) &= \overline{(eig \ \textbf{A})} \\
\langle \textbf{A} \vec x, \vec y \rangle &= \langle x, \textbf{A}^* \vec y \rangle \\
\end{split}
\end{cases}
\tag{2 - 4 - 5}
$$
上式中涉及矩阵乘法、矩阵与向量乘法的地方默认满足维度关系,不赘述。
如果矩阵$\textbf{A}$是一个方阵,并且其共轭转置矩阵与其自身之间满足一些特定条件,我们就给A一些特殊的称谓。
如果矩阵$\textbf{A}$与其共轭转置满足如下关系:
$$
\textbf{A} = \textbf{A}^* \quad or \quad a_{ij} = \overline{a_{ji}}
\tag{2 - 4 - 6}
$$
我们就称矩阵$\textbf{A}$为埃尔米特(Hermitian)矩阵(或自伴随矩阵(self-adjoint))。
如果矩阵$\textbf{A}$与其共轭转置满足如下关系:
$$
\textbf{A} = -\textbf{A}^* \quad or \quad a_{ij} = -\overline{a_{ji}}
\tag{2 - 4 - 7}
$$
我们就称矩阵$\textbf{A}$为斜埃尔米特(skew Hermitian)矩阵(或反埃尔米特矩阵(antihermitian))。
如果矩阵$\textbf{A}$与其共轭转置满足如下关系:
$$
\textbf{A} \textbf{A}^* = \textbf{A}^* \textbf{A}
\tag{2 - 4 - 8}
$$
我们就称矩阵$\textbf{A}$为正规(normal)矩阵。
如果矩阵$\textbf{A}$与其共轭转置满足如下关系:
$$
\textbf{A}^* = \textbf{A}^{-1}
\tag{2 - 4 - 9}
$$
即矩阵的共轭转置为其逆矩阵,我们就称矩阵$\textbf{A}$为酉(unitary)矩阵。
根据Wikipedia的定义,在贝叶斯概率论中,如果后验概率(poeterior distributions)的概率分布$p(\theta | x)$与其对应的先验概率(prior probability)的概率分布$p(\theta)$属于相同类型的概率分布,则这对先验/后验概率分布被称为共轭分布。
此时,这个先验分布$p(\theta)$就称为似然函数(likelihood function)$p(x|\theta)$的共轭先验。共轭先验一定是相对于似然函数而言的。
这里再对先验概率、后验概率、似然函数做一个说明。
注:只有共轭分布、共轭先验的说法,并没有共轭后验这个术语。
按照似然函数分布的类型,我们有如下常见的共轭分布表:
表 2 - 1 常见离散共轭分布函数表
似然函数 | 共轭先验 | 模型参数 | 先验超参数 | 参数注释 |
---|---|---|---|---|
伯努力分布 | Beta分布 | $p$ | $\alpha, \ \beta$ | $\alpha$、$\beta$的详细解释见这里 |
二项分布 | Beta分布 | 同上 | 同上 | 同上 |
几何分布 | Beta分布 | $p_0$ | 同上 | 同上 |
超几何分布 | Beta-二项分布 | $M$ | $N, \ \alpha, \ \beta$ | $\alpha, \ \beta$同上 $M$——抽样数 $N$——样本总数 |
泊松分布 | Gamma分布 | $\lambda$ | $k, \ \theta $ $or$ $\alpha, \ \beta$ | $\frac{1}{\theta}(\beta)$——时间间隔 $k(\alpha)$——间隔内总次数 |
多项分布 | Dirichlet分布 | $p$——概率向量 $k$——概率向量维度 | $\alpha$ | $\alpha_i$——第i个元素发生次数 |
表 2 - 2 常见连续共轭分布函数表
似然函数 | 共轭先验 | 模型参数 | 先验超参数 | 参数注释 |
---|---|---|---|---|
正态分布 (已知$\tau$) | 正态分布 | $\mu$ | $\mu_0, \ \sigma^2_0$ | $\mu_0$——样本均值 $\sigma^2_0$——样本方差 |
正态分布 (已知$\tau$) | 同上 | 同上 | $\mu_0, \ \tau_0(\frac{1}{\sigma^2_0})$ | $\mu_0$——同上 $\tau_0$——准确率 |
正态分布 (已知$\mu$) | 逆Gamma分布 | $\sigma^2_0$ | $\alpha, \ \beta$ | $\sigma_0 = 2\beta$ |
正态分布 (已知$\mu$) | Gamma分布 | $\tau$ | $\alpha, \ \beta$ | 同上 |
多元正态分布 (已知$\sum$) | 多元正态分布 | $\vec \mu$ | $\vec \mu_0, \ \vec \sum_0$ | $\vec \mu_0$——样本均值向量 $\vec \sum_0$——样本协方差阵 |
指数分布 | Gamma分布 | $\lambda$ | $\alpha, \ \beta$ | $\alpha$、$\beta$的详细解释见这里 |
注:如果似然函数为指数函数,则其共轭先验必然存在,且通常也属于指数函数。
举个实际的例子可能更容易理解。我们想了解某个大学的男女生比例,于是随机在校园里面进行了抽样,在遇到的100个学生里面,发现有66个是男生。问:该学校男生的比例?
从频率学派的观点来看,有:
$$
\theta(p_{boy}) = \frac{66}{100} = 0.66
\tag{2 - 5 - 1}
$$
从频率学派的角度来看,这个(概率)值存在唯一真值,这个值是不会变化的。频率派的谬误之处在于,假设该校总共有1000人,我抽了其中800人,恰好800人全是男生,于是该校的男生比例就是100%?
从贝叶斯学派的角度出发,这个值$\theta$本身属于某个概率分布,还需要结合我们的先验知识(比如往年的统计数据、学校是文科还是理科多等等)来进行修正,确定一个合理的数据。
为了方便叙述,有如下定义:
根据贝叶斯公式:
$$
\underbrace{P(\theta|\textbf{X})}_{后验概率} = \frac{\underbrace{P(\textbf{X}|\theta)}_{似然函数} \cdot \underbrace{P(\theta)}_{先验概率}} {\underbrace{P(\textbf{X})}_{边缘概率}}
\tag{2 - 5 - 2}
$$
由上式可知,在获得了学生数据(观测样本)的情况下,我们要去推断该高校的男生的比例(即后验概率$P(\theta|\textbf{X})$),需要知道三个值:似然函数、先验概率(分布)、边缘概率。
边缘概率可以视作归一化因子,那么后验概率最终就只取决于似然函数和先验概率的形式。这个例子的似然函数实际上是二项分布(每次实验只有男生、女生两种结果,)。
由此有似然函数的表达式:
$$
P(\textbf{X}|\theta) = C^m_N \theta^m \ (1 - \theta)^{N - m}
\tag{2 - 5 - 3}
$$
查离散表2-1可知,当似然函数为二项分布时,其对应的共轭先验是Beta分布。即如果我们将共轭先验选择为Beta分布,最终可以推导出后验概率也为Beta分布。
因为$\theta \sim Beta(\alpha, \ \beta)$,由此有似然函数表达式:
$$
\begin{split}
P(\theta) = P(\theta;\alpha, \ \beta) &= Beta(\alpha, \ \beta) \\
&= \frac{\theta^{\alpha - 1}(1 - \theta)^{\beta - 1}}{\Large{\int_{0}^{1}} \normalsize{u^{\alpha - 1}(1 - u)^{\beta - 1}d_u}} \\
&= \frac{\theta^{\alpha - 1}(1 - \theta)^{\beta - 1}}{B(\alpha, \ \beta)}
\end{split}
\tag{2 - 5 - 4}
$$
式中:$B(\alpha, \ \beta) = \int_{0}^{1} u^{\alpha - 1}(1 - u)^{\beta - 1}d_u = \frac{\Gamma(\alpha) \cdot \Gamma(\beta)} {\Gamma(\alpha + \beta)}$,$\Gamma(x)$为伽玛函数。
由于先验分布为连续函数,因此有边缘概率的积分表达式(如果是离散函数则为累积求和表达式):
$$
\begin{split}
P(\textbf{X}) &= \int_{0}^{1} p(\textbf{X}|\theta) p(\theta) d_{\theta} \\
&= \int_{0}^{1} C^m_N \theta^m \ (1 - \theta)^{N - m} \cdot \frac{\theta^{\alpha - 1}(1 - \theta)^{\beta - 1}} {B(\alpha, \ \beta)} \\
&= \frac{C^m_N}{B(\alpha, \ \beta)} \int_{0}^{1} \theta^{\alpha + m - 1}(1 - \theta)^{\beta + N - m - 1} \\
&= \frac{C^m_N}{B(\alpha, \ \beta)} \cdot B(\alpha + m, \ \beta + N -m)
\end{split}
\tag{2 - 5 - 5}
$$
将上述式子2-5-3、2-5-4、2-5-5代入式2-5-2有:
$$
\require{cancel}
\begin{split}
P(\theta|\textbf{X}) &= \frac{C^m_N \theta^m \ (1 - \theta)^{N - m} \cdot \frac{\theta^{\alpha - 1}(1 - \theta)^{\beta - 1}}{B(\alpha, \ \beta)}}{\frac{C^m_N}{B(\alpha, \ \beta)} \cdot B(\alpha + m, \ \beta + N -m)} \\
&= \frac{\bcancel{C^m_N} \theta^m \ (1 - \theta)^{N - m} \cdot \theta^{\alpha - 1}(1 - \theta)^{\beta - 1} \cdot \bcancel{B(\alpha, \ \beta)}}{\bcancel{C^m_N} \cdot B(\alpha + m, \ \beta + N -m) \cdot \bcancel{B(\alpha, \ \beta)}} \\
&= \frac{\theta^{\alpha + m - 1}(1 - \theta)^{\beta + N - m - 1}} {B(\alpha + m, \ \beta + N -m)} \\
\\
&= Beta(\alpha + m, \ \beta + N -m)
\end{split}
\tag{2 - 5 - 6}
$$
我们算出来的后验概率$P(\theta|\textbf{X}) \sim Beta(\alpha + m, \ \beta + N -m)$,即后验概率仍然服从$Beta$分布!
以这个题为例,假设我们抽样的高校是一个综合性大学,男女比例比较接近,由如下的$Beta$分布的概率密度函数图:
由上图推出$\alpha=2, \ \beta=2$(图中紫色曲线)比较合理,即$\theta \sim Beta(2, \ 2)$,再结合$N=100, \ m=66$可知后验概率$P(\theta|\textbf{X}) \sim Beta(68, 46)$,如下图所示:
由上图可知,由于观测数据的加入,原本我们估计的男女比例均衡变成了男生比例偏高。这更符合我们的预期,模型参数会根据观测数据的变化而动态调整,同时结合了已有经验。
表中剩下的共轭分布感兴趣的可以自己验证。共轭分布最大的优点就是,后验概率会保持与先验概率相同的分布形式。
Nothing is perfect, nothing.
病态问题(ill-conditioned problem)是指输出结果相对于输入非常敏感的问题,输入数据中哪怕是极少(或者极微妙)的噪声也会导致输出的较大改变(该术语并没有严格的官方定义)。
相反的,对于输入不敏感的问题,我们就称为良态问题(well-conditioned problem)。
条件数(condition-number)是用来衡量输出相对于输入敏感度的指标,良态问题和病态问题就是靠这个指标来进行区分的。
注:以下两个概念(良态系统、病态系统)是我为方便叙述提出的,非官方术语。
我们把良态问题的内涵做一个延伸,对于任意一个系统,系统的输入和输出属于良态问题,我们称之为良态系统(或者这个系统是良态的)。
这里的系统的概念非常广泛,可以涵盖各种各样的类型。比如,以人体神经系统来说,一个良态的神经系统(身体健康的人),在外界环境温度(输入)发生微小改变时,它对人体温度的调节(输出)也应该是微小的。再比如,一个良态的汽车动力系统,当输入(油门大小)发生微小改变时,输出(动力的加减)也应该是微小的。
当然,我们主要还是围绕机器学习来讲。一个良态的分类/聚类/回归系统(模型),当输入数据中存在噪声时,其输出与没有噪声时相比,变化不应该太大,否则这个系统的鲁棒性就很差。存在过拟合的系统,因为泛化能力非常差,因此它必然是非良态(病态)的。
如上所述,一般情况下,非良态的系统就是病态的(除去那些本身就要求输出对输入敏感的系统)。
注意要把病态/良态系统和适定(well-posed problem)/不适定(ill-posed problem)问题区分开来。适定问题既可以是良态的,也可以是病态的,不适定问题同样如此,不要混为一谈。
这里贴一下适定问题(well-posed problem)的定义:
- 必然存在解;
- 解唯一;
- 解随着初始条件改变而连续改变。
因为接下来的内容涉及到矩阵,因此需要对相应的概念做个介绍。对于线性方程组A$\vec x = \vec b$,其中A为m*n矩阵,$\vec x、\vec b$均为n*1的列向量。我们定义方程组关于A的条件数为:
$$
k(\textbf{A})= ||\textbf{A}|| \cdot ||\textbf{A}^{-1}||
\tag{1 - 1}
$$
上式中的范数也可以取其他范数(比如0、$\infty$范数)。当然上式成立的条件是A的逆矩阵$\textbf{A}^{-1}$存在(即A为非奇异矩阵)。当$k(\textbf{A})$较小时,$\vec b$的微小改变不会造成方程组解$\vec x$的改变,我们称A为良态矩阵,反之则成为病态矩阵。
条件数的定义推导如下,假定$\vec b$的变化量为$\Delta \vec b$,对应的解的变化为$\Delta \vec b$,则有:
$$
\textbf{A} (\vec x + \Delta \vec x) = \vec b + \Delta \vec b
\tag{1 - 2}
$$
展开后有:
$$
\textbf{A} \vec x + \textbf{A} \cdot \Delta \vec x = \vec b + \Delta \vec b
\tag{1 - 3}
$$
因为$\textbf{A} \vec x = \vec b$,代入式(1-3)有:
$$
\textbf{A} \cdot \Delta \vec x = \Delta \vec b
\tag{1 - 4}
$$
同时我们假定了$\textbf{A}^{-1}$存在,所以有:
$$
\Delta \vec x = \textbf{A}^{-1} \cdot \Delta \vec b
\tag{1 - 5}
$$
对等式两边取范数并根据范数的特性)有:
$$
||\Delta \vec x|| = ||\textbf{A}^{-1} \cdot \Delta \vec b || \leq ||\textbf{A}^{-1}|| \cdot ||\Delta \vec b ||
\tag{1 - 6}
$$
同时对$\textbf{A} \vec x = \vec b$两边取二范数有:
$$
||\textbf{A}|| \cdot ||\vec x|| \ge ||\textbf{A} \vec x|| = ||\vec b||
\tag{1 - 7}
$$
结合上式(1-6)、(1-7)有:
$$
\frac{||\Delta \vec x||}{||\textbf{A}|| \cdot ||\vec x||} \leq \frac{||\textbf{A}^{-1}|| \cdot ||\Delta \vec b ||}{||\vec b||}
\tag{1- 8}
$$
根据上式进一步有:
$$
\underbrace{\frac{||\Delta \vec x||}{||\vec x||}}_{输入的改变率} \leq \underbrace{||\textbf{A}|| \cdot ||\textbf{A}^{-1}||}_{输入输出改变率比系数} \cdot \underbrace{\frac{||\Delta \vec b ||}{||\vec b||}}_{输出改变率}
\tag{1- 9}
$$
由上式,我们定义输入改变率和输出改变率的这个系数为条件数,即有:
$$
\begin{cases}
\begin{split}
k(\textbf{A}) &= ||\textbf{A}|| \cdot ||\textbf{A}^{-1}|| \geq ||\textbf{A} \cdot \textbf{A}^{-1}|| = 1 \\\\
\frac{||\Delta \vec x||}{||\vec x||} &\leq k(\textbf{A}) \cdot \frac{||\Delta \vec b ||}{||\vec b||}
\end{split}
\end{cases}
\tag{1 -10}
$$
由上可知,条件数实际上是输出相对于输入变化的灵敏度系数。
如果我们取的是二范数,即$||\cdot||_2$,则其条件数为:
$$
k(\textbf{A}) = \frac{\sigma_{max}(\textbf{A})}{\sigma_{min}(\textbf{A})}
\tag{1 - 11}
$$
上式中的$\sigma_{max}(\textbf{A})、\sigma_{min}(\textbf{A})$分别指矩阵A的最大、最小奇异值。
特别的,当A为正规阵时:
$$
k(\textbf{A}) = \frac{|\lambda_{max}(\textbf{A})|}{|\lambda_{min}(\textbf{A})|}
\tag{1 - 12}
$$
式中$\lambda_{max}(\textbf{A})、\lambda_{min}(\textbf{A})$分别为矩阵A的最大、最小特征值。
特别的,当A为酉矩阵时:
$$
k(\textbf{A}) = 1
\tag{1 - 13}
$$
即当且仅当A为酉矩阵时,条件数取得最小值1。
特别的,当A为奇异矩阵时,A的逆矩阵不存在:
$$
k(\textbf{A}) \rightarrow \infty
\tag{1 - 14}
$$
矩阵的条件数是针对一般方程组而言的,对于机器学习,还需要做一些说明。因为在线性方程组中,$\vec b$是输入,而$\vec x$才是我们的输出(要求解的),这点和机器学习模型有所差异,我们要在这两个问题之间做一个转换,以方便接下来的讨论。
对于机器学习模型而言,神经网络的参数矩阵通常用W表示,输入样本用$\vec x$表示,而输出用$\vec y$表示。三者的关系为:
$$
\textbf{W} \cdot \underbrace{\vec x}_{输入} = \underbrace{\vec y}_{输出} \\\\
\tag{1 - 15}
$$
线性方程组的关系为:
$$
\textbf{A}^{-1} \cdot \underbrace{\vec b}_{输入} = \underbrace{\vec x}_{输出}
\tag{1 - 16}
$$
模型训练好了之后,W是不会改变的,然后将样本$\vec x$作为输入(自变量),去生成输出$\vec y$(因变量)。因此有如下对应关系:
$$
\begin{cases}
\begin{split}
\textbf{W} & \iff \textbf{A}^{-1} \\\\
\vec x & \iff \vec b \\\\
\vec y & \iff \vec x
\end{split}
\end{cases}
\tag{1 -17}
$$
则有机器学习模型的条件数:
$$
\begin{split}
k(\textbf{W}) &= ||\textbf{W}|| \cdot ||(\textbf{W}^{-1})^{-1}||\\\\
&= ||\textbf{W}^{-1}|| \cdot ||\textbf{W}||
\end{split}
\tag{1 - 18}
$$
既然已经定义了条件数,定义了什么是病态,什么是良态,我们不禁会想,引起病态的根源是什么,或者说什么样的矩阵具备病态特征。
当然,你可能会说,前面不是定义了条件数较大的矩阵属于病态矩阵吗,这不是多此一问吗?
过大的条件数会导致矩阵病态只是我们的结论,并不是根本的原因。最根本的原因在于矩阵的列之间的相关性过大。
为了说明这个问题,我们举一个最简单的二维方阵来说明。
给定如下条件:
$$
\textbf{W} =
\begin{bmatrix}
1333 & -131 \\
331 & -31 \\
\end{bmatrix} \quad , \quad
\vec x =
\begin{bmatrix}
1 \\
11 \\
\end{bmatrix}
$$
可以解出:
$$
\vec y =
\begin{bmatrix}
-120 \\
-13 \\
\end{bmatrix}
$$
现在我们分别对输入做细微的调整,并计算输出结果,如下所示:
$$
\begin{cases}
\begin{split}
\vec x_1 &=
\begin{bmatrix}
1.0097 \\
11.001 \\
\end{bmatrix} \Longrightarrow
\vec y_1 &=
\begin{bmatrix}
-95.2 \\
-6.82 \\
\end{bmatrix} \Longrightarrow
\Delta \vec y_1 &=
\begin{bmatrix}
20.67\% \\
47.54\% \\
\end{bmatrix} \\\\
\vec x_2 &=
\begin{bmatrix}
1.0024 \\
11.010 \\
\end{bmatrix} \Longrightarrow
\vec y_2 &=
\begin{bmatrix}
-106.11 \\
-9.52 \\
\end{bmatrix} \Longrightarrow
\Delta \vec y_2 &=
\begin{bmatrix}
11.58\% \\
26.92\% \\
\end{bmatrix}
\end{split}
\end{cases}
\tag{2 - 1}
$$
由上可知,即使输入的微小改变,也会引起输出的大幅变动。比如上面的$\vec x_1$对应的输出$\vec y_1$分量最高变动率高达近50%。
我们来看看其列之间的相关性,有:
$$
\vec w_1/ \vec w_2 =
\begin{bmatrix}
1333/(-131) \\
331/(-31) \\
\end{bmatrix} = \begin{bmatrix}
10.1756 \\
10.6774 \\
\end{bmatrix}
\tag{2 - 2}
$$
可以看出,矩阵W的列之间的线性相关性非常高!
与此同时,虽然上面两个输入$\vec x_1、\vec x_2$与原始输入$\vec x$改变的绝对值不一样,但实际上在其两个特征向量方向上的改变率却是一样的,换句话说,不同特征向量方向上相同的改变率,其输出的改变率是不同的。
利用Numpy内置的函数numpy.linalg.eig()
我们可以非常方便的求解出$\textbf{W}$的特征值及其特征向量如下:
$$
\begin{cases}
\begin{split}
\vec \xi_1 &\approx
\begin{bmatrix}
0.97 \\
0.10 \\
\end{bmatrix} \quad , \quad \lambda_1 \approx 1300\\\\
\vec \xi_2 &\approx
\begin{bmatrix}
0.24 \\
1.00 \\
\end{bmatrix} \quad , \quad \lambda_2 \approx 1.57
\end{split}
\end{cases}
\tag{2 - 3}
$$
可以看出,输入$\vec x$分别沿着两个特征向量$\vec \xi_1、\vec \xi_2$方向增加1%后,即为式(2-1)中的$\vec x_1、\vec x_2$,而特征向量$\vec \xi_1$对应的特征值($\lambda_1 = 1300$)远大于特征向量$\vec \xi_2$对应的特征值($\lambda_1 = 1.57$)。
因此,当矩阵的特征值差异过大时,即使输入沿着较大特征值的方向有微小的改变,也会导致最终的输出结果的较大改变。原因很简单,较大的特征值意味着对应特征方向上较大的自由度。
现在我们换个思路,我们手里已经有了大量的样本,要训练一个机器学习的模型,即已知$\vec x、\vec y$,要求W,考虑SGD(随机梯度下降)算法,因为每次送进去的样本数量$N > 1$,所以上式(1-14)就变成了:
$$
\textbf{W} \cdot \textbf{X} = \textbf{Y} \\\\
\tag{2 - 4}
$$
我们对上式做一个变形:
$$
\underbrace{\textbf{X}}_{A} \cdot \underbrace{\textbf{Y}^{-1}}_{\vec x} = \underbrace{\textbf{W}^{-1}}_{\vec b} \\\\
\tag{2 - 5}
$$
这个时候我们把输入的样本X视作参数矩阵,把要求解的参数W矩阵视作输出。由之前的结论可知,如果X为病态矩阵,即样本之间的相关性过大,可能会导致训练的收敛过程非常慢,甚至不收敛。因为整个训练过程中,相似样本之间的标签即使存在微小的差异,也会导致W的较大波动,导致解的不稳定性。
正是由于解的这种不稳定性,训练过程结束后,我们很难保证求解出来的模型能够足够优秀。另一方面,病态矩阵从另一个角度揭示了过拟合现象产生的原因,即通过解的大幅波动,去拟合我们的训练数据。
无论是数据的生成还是采集过程中,我们都很难保证没有噪声,因此一般我们都会对样本进行一些预处理,比如异常点检测、离群点检测等等,或者将解集限定在一组正交基的空间内(正交意味着不相关,求解出的参数矩阵就是一个良态矩阵)。无论采取什么措施,最终的目的都只有一个,那就是获得一个良态参数矩阵。
病态矩阵为我们提供了很多理论上的指导,同时也启发我们去思考其它很多问题。
在深度学习中,我们将损失函数$L(x)$在点$\vec{x}^{(0)}$的进行二阶泰勒级数展开,如下:
$$
\begin{split}
\begin{cases}
\mathcal{L}(\vec{x}) &\approx \mathcal{L}(\vec{x}^{(0)}) + (\vec{x} - \vec{x}^{(0)})^T \ \vec{g} + \frac{1}{2}(\vec{x} - \vec{x}^{(0)})^T \ \textbf{H} \ (\vec{x} - \vec{x}^{(0)}) \\
\mathcal{L}(\vec{x}^{(0)} - \epsilon \vec{g})&\approx \mathcal{L}(\vec{x}^{(0)}) - \epsilon \vec{g}^T \vec{g} + \frac{1}{2} \epsilon^2 \vec{g}^T \ \textbf{H} \ \vec{g}
\end{cases}
\end{split}
\tag{2 - 6}
$$
式中:
——$\textbf{H}$:在$\vec{x}^{(0)}$点的梯度的Jacobian矩阵;
——$\vec{g}$:在$\vec{x}^{(0)}$点的梯度。
上式中当$\frac{1}{2} \epsilon^2 \vec{g}^T \ \textbf{H} \ \vec{g}$超过$\epsilon \vec{g}^T \vec{g}$时,$\textbf{H}$的病态会成为非常严重的问题,因为此时即使梯度$\vec{g}$的小幅波动也会导致损失的大幅增加。为了消除这个影响,学习率$\epsilon$就必须大幅收缩,此时虽然梯度仍然很大(通常平方梯度范数$\vec{g}^T \vec{g}$不会在训练过程中显著缩小),但是学习过程却变得非常缓慢。
所以在神经网络的训练过程中,我们需要检测平凡梯度范数$\vec{g}^T \vec{g}$和$\vec{g}^T \ \textbf{H} \ \vec{g}$,以便评估当前的病态是否不利于训练任务的执行并设法改善它。
Talk is cheap, explain me the Paper.
设计深度学习模型的时候,不管是自己从头搭建还是修改别人的,都离不开相关参数的计算,主要是输入图形先后经过卷积、池化层后输出尺寸的变化,尤其是涉及多个卷积或池化层时,如果对这两种操作的原理不清楚,就会对网络的各个参数产生困惑,不知道如何去修改以便适配自己的业务场景。
这里对CNN(卷积神经网络)中的主要参数的计算做一个归纳整理,方便参考。我们先用图示的方式给出这两种操作的运行过程,最后再归纳出数学公式。
注意:本文主要是结合Tensorflow来讲的,不同的平台会有所差异。
CNN网络的主要参数有下面这么几个:
顾名思义,卷积核是在进行卷积操作的时候使用的,在Tensorflow中,被称为filter,通过一个四元列表传递。文档中对该参数的说明如下:
1 | """ |
卷积核的示意图如下图中间部分(这里的卷积核大小为3*3)所示:
由上图可知,卷积这一步的操作,实际上是在输入数据上取与卷积核大小相同的矩阵,然后与卷积核矩阵进行哈达马达乘积),然后求这个乘积矩阵所有元素的和,作为卷积的结果。
需要说明的是,这里的卷积跟我们信号与系统里面的卷积有所差异。
上面提到的通道数(in_channels、out_channels)实际上就是我们的feature_map的数量。因为不同的卷积核卷积出来的feature_map是不一样的,因此有多少个out_channels,就有多少个不同的卷积核。关于这点,会在后边的通道参数里面解释。
对于直接与输入层连接的卷积层来说,输入通道数in_channels,图片通常为3(因为包含R、G、B三色矩阵),文本则通常为1。
填充在卷积和池化这两个操作里面都可能会用到,填充的作用主要是尽可能充分的保留和使用输入特征(如果不使用填充,网络层数越深,所丢失的边缘数据就越多,到最后可能无特征可用),同时也应注意填充的尺寸不宜过大,避免引入过多无用的数据。
关于填充作用的说明还可以参考知乎上的这篇文章。Tensorflow中的填充只有两种类型,说明如下:
1 | """ |
Theano中的填充则有不填充、半填充、全填充三种,它的半填充对应Tensorflow的SAME
。
SAME
类型以0
进行填充的示意图如下:
可以看到,这个操作实际上是在原始数据外面包裹
了一层填充数据。
滑动步长决定了在卷积或者池化的过程中,每次操作后移动的步数。在Tensorflow中,该参数也是一个四元列表,关于该参数的解释如下:
1 | """ |
它的第2、3个参数分别对应在数据垂直和水平方向上的滑动步长。第1个参数表示在样本上的跳跃幅度,一般都是置为1(表示不会跳过任何样本)。第4个参数表示在通道上的跳跃幅度,通常也是置为1。更详细的可参见Stackoverflow上的讨论以及该博客。
卷积过程中的滑动过程示意如下:
由上图可知,卷积核在输入数据上的操作顺序是先沿着水平方向,再沿着垂直方向。因为水平方向上步长为1,所以卷积核在水平方向上每操作一次,就往右移动一格,再进行下一次操作。当水平方向上操作完了以后,就回到最左边,在垂直方向向下移动一格,然后进行操作,如此往复循环,直到所有数据均处理完。
此外需要说明的是,由于卷积核中值为0的部分对最终的结果没有任何贡献,因此在动图中卷积的时候没有高亮(黄色)显示,并不代表它们没有参与卷积。另外输入和卷积核的元素可以是任意值(上图只是为方便举的简单例子)。如果你把卷积核看做单反的取景框,把输入数据当作我们的风景,卷积核在输入上的移动过程就类似于我们拍全景照的过程,从左往右,从上往下。
我们再来看看水平、垂直方向上滑动步长均为2并且使用了SAME
方式填充的情况,如下图所示:
由上图可知,其操作流程与步长为1时并没有区别,只不过每次沿水平/垂直方向挪动的步长为2。另外不太一样的地方是,对于有填充的数据,卷积核的取景
(取值)范围不会超出填充数据(第一行最后一次移动)。
池化核则是在进行池化操作时候的Kernal,池化的作用类似于PCA,可以有效的对数据降维同时保留关键特征。常用的池化核有如下几种类型:
取景
框取出来的所有数据)的最大值; 下图分别展示了步长分别为1、2时的最大池化过程:
关于池化的作用可参考该CSDN博客。
从上面的介绍我们可以看出,对于同一个输入矩阵,改变卷积核的元素值,将会产生不同的输出。因此,卷积核的数量决定了卷积操作之后生成的feature map(卷积核在输入上卷积后的输出矩阵我们称为feature map)数量。
对于CNN来说,其隐藏层部分,上一层的卷积核(即为该层的输出通道数out_channels)数量决定了下一层的输入通道数(in_channels)。输入层部分,输入的类型决定了第一个隐藏层的输入通道数。对于图片数据来说,会拆分成3个输入矩阵,分别对应RGB三原色,如下图所示:
当然图片如果是以CMYK格式存储的,那么输入通道数就会变成4个。而对于文本类的输入,其通道数则只有1个。当然,也可以通过reshape操作后把文本输入变成3通道的数据(将文本数据模拟成图像,360曾经把网络流量转换成黑白图片,并用来训练DNN模型,从而判别是否包含恶意数据,这个想法真的是非常有创意。详见这里)。
总之,数据是死的,人脑是活的,怎么玩,就靠你自己去发挥想象力了。下图是步长为2,卷积核数量为2上的卷积过程示意图:
主要有两个部分的参数需要计算,一个是卷积后的尺寸,另一个是池化后的尺寸。先看一些具体的比较简单的例子,方便去数。最后再给出通用的计算公式。
整个过程如下所示:
如上图所示,相关数据如下表所示:
输入通道数 (个) | 输入尺寸 (高*宽) | 卷积核数量 (个) | 卷积核尺寸 (高*宽) | 步长 (步) | 输出通道数 (个) | 输出尺寸 (高*宽) | 填充 |
---|---|---|---|---|---|---|---|
1 | 6 * 6 | 1 | 3 * 3 | 1 | 1 | 4 * 4 | VALID |
整个过程如下所示:
如上图所示,相关数据如下表所示:
输入通道数 (个) | 输入尺寸 (高*宽) | 卷积核数量 (个) | 卷积核尺寸 (高*宽) | 步长 (步) | 输出通道数 (个) | 输出尺寸 (高*宽) | 填充 |
---|---|---|---|---|---|---|---|
1 | 6 * 6 | 1 | 3 * 3 | 2 | 1 | 2 * 2 | VALID |
由上述两图、表可知,除卷积核大小外其它所有条件都不变的情况下,第二种情况相较于第一种情况,其输出的尺寸缩小了$\frac{2}{4} * \frac{2}{4} = \frac{1}{4}$,高、宽各自方向上则缩小了$\frac{2}{4} = \frac{1}{2}$。
整个过程如下所示:
如上图所示,相关数据如下表所示:
输入通道数 (个) | 输入尺寸 (高*宽) | 卷积核数量 (个) | 卷积核尺寸 (高*宽) | 步长 (步) | 输出通道数 (个) | 输出尺寸 (高*宽) | 填充 |
---|---|---|---|---|---|---|---|
1 | 6 * 6 | 1 | 3 * 3 | 2 | 1 | 3 * 3 | SAME(8*8) |
整个过程如下所示:
如上图所示,相关数据如下表所示:
输入通道数 (个) | 输入尺寸 (高*宽) | 池化核数量 (个) | 池化核尺寸 (高*宽) | 步长 (步) | 输出通道数 (个) | 输出尺寸 (高*宽) | 填充 |
---|---|---|---|---|---|---|---|
1 | 4 * 4 | 1 | 2 * 2 | 1 | 1 | 3 * 3 | VALID |
整个过程如下所示:
如上图所示,相关数据如下表所示:
输入通道数 (个) | 输入尺寸 (高*宽) | 池化核数量 (个) | 池化核尺寸 (高*宽) | 步长 (步) | 输出通道数 (个) | 输出尺寸 (高*宽) | 填充 |
---|---|---|---|---|---|---|---|
1 | 4 * 4 | 1 | 2 * 2 | 2 | 1 | 2 * 2 | VALID |
整个过程如下所示:
如上图所示,相关数据如下表所示:
输入通道数 (个) | 输入尺寸 (高*宽) | 池化核数量 (个) | 池化核尺寸 (高*宽) | 步长 (步) | 输出通道数 (个) | 输出尺寸 (高*宽) | 填充 |
---|---|---|---|---|---|---|---|
1 | 4 * 4 | 1 | 2 * 2 | 2 | 1 | 3 * 3 | SAME(8*8) |
卷积后的参数无非就三个,通道数、高、宽。其中通道数不用计算,就等于卷积核数量。为方便叙述,记:
给定相关参数后,设卷积核在水平、垂直方向分别可以有效移动(对有填充情况,超出填充部分则该次移动无效。无填充情况,超出输入数据则移动无效。)$m、n$次,则有:
移动情况如下所示:
由图有:
$$
m \cdot S + H_{ker} \leq H_{in} + 2 \cdot P \\\\
\tag{3 - 1}
$$
可求得:
$$
m \leq \frac{(H_{in} + 2 \cdot P - H_{ker})}{S} \\\\
\tag{3 - 2}
$$
而$H_{out}$就是我们最大的滑动次数,即:
$$
H_{out} = m_{max} + 1= \left \lfloor \frac{(H_{in} + 2 \cdot P - H_{ker})}{S} \right \rfloor + 1= \frac{(H_{in} + 2 \cdot P - H_{ker})}{S} + 1\\\\
\tag{3 - 3}
$$
注意:上式中$m_{max}$还要加1,因为第0次滑动的时候,也是做了卷积操作的。对应的文字版公式为:
$$
输出宽度 = \frac{(输入宽度 + 2 \cdot 填充宽度 - 卷积核宽度)}{步长} + 1\\\\
\tag{3 - 4}
$$
垂直方向的推导与水平方向一致,只不过换了个方向而已,在此不赘述,直接给出其计算公式如下:
$$
W_{out} = n_{max} + 1= \left \lfloor \frac{(W_{in} + 2 \cdot P - W_{ker})}{S} \right \rfloor + 1= \frac{(W_{in} + 2 \cdot P - W_{ker})}{S} + 1\\\\
\tag{3 - 5}
$$
对应的文字版公式为:
$$
输出高度 = \frac{(输入高度 + 2 \cdot 填充高度 - 卷积核高度)}{步长} + 1\\\\
\tag{3 - 6}
$$
综上,再加入输出通道数,有卷积操作后输出的尺寸的计算公式如下:
$$
\begin{cases}
\begin{split}
H_{out} &= \left \lfloor \frac{(H_{in} + 2 \cdot P - H_{ker})}{S} \right \rfloor + 1= \frac{(H_{in} + 2 \cdot P - H_{ker})}{S} + 1\\\\
W_{out} &= \left \lfloor \frac{(W_{in} + 2 \cdot P - W_{ker})}{S} \right \rfloor + 1= \frac{(W_{in} + 2 \cdot P - W_{ker})}{S} + 1 \\\\
CH_{out} &= K
\end{split}
\end{cases}
\tag{3 - 7}
$$
对应的文字版公式为:
$$
\begin{cases}
\begin{split}
输出宽度 &= \frac{(输入宽度 + 2 \cdot 填充宽度 - 卷积核宽度)}{步长} + 1\\\\
输出高度 &= \frac{(输入高度 + 2 \cdot 填充高度 - 卷积核高度)}{步长} + 1 \\\\
输出通道数 &= 卷积核数量
\end{split}
\end{cases}
\tag{3 - 8}
$$
如果有疑问,可以把前面的例子拿来验证。Tensorflow中关于这部分的说明参考官方文档。
池化部分因为工作原理和卷积部分基本上是一样的,所以其公式推导也并没有什么不同,有一点需要指出的是,池化部分仍然可以有填充。池化部分的推导这里就不再赘述。
综上,整理出卷积/池化参数的通用计算公式如下:
$$
\begin{cases}
\begin{split}
H_{out} &= \left \lfloor \frac{(H_{in} + 2 \cdot P - H_{ker})}{S} \right \rfloor + 1= \frac{(H_{in} + 2 \cdot P - H_{ker})}{S} + 1\\\\
W_{out} &= \left \lfloor \frac{(W_{in} + 2 \cdot P - W_{ker})}{S} \right \rfloor + 1= \frac{(W_{in} + 2 \cdot P - W_{ker})}{S} + 1 \\\\
CH_{out} &= K
\end{split}
\end{cases}
\tag{3 - 9}
$$
对应的文字版公式为:
$$
\begin{cases}
\begin{split}
输出宽度 &= \frac{(输入宽度 + 2 \cdot 填充宽度 - 卷积/池化核宽度)}{步长} + 1\\\\
输出高度 &= \frac{(输入高度 + 2 \cdot 填充高度 - 卷积/池化核高度)}{步长} + 1 \\\\
输出通道数 &= 卷积/池化核数量
\end{split}
\end{cases}
\tag{3 - 10}
$$
使用式(3-9)最右边的表达式计算时一定要注意,这种表达不是特别严谨,这样写是为了兼容网络上的一些博客的写法,分式结果要记得向下取整。
大部分参考的文献资料都在相应位置给出了,剩下还有些参考的资料罗列如下:
举几个例子来验证下。
该例子是牛客网上题库中的一个题,试题如下:
输入图片大小为200×200,依次经过一层卷积(kernel size 5×5,padding 1,stride 2),pooling(kernel size 3×3,padding 0,stride 1),又一层卷积(kernel size 3×3,padding 1,stride 1)之后,输出特征图大小为:
A. 95
B. 96
C. 97
D. 98
E. 99
F. 100
答案: C
可以看出,该网络隐藏层共有3层(2层卷积,1层池化),再加上输入、输出层,该网络是一个5层的NN。
对第一层卷积层,我们有$H_{in} = W_{in} = 200, \ H_{ker} = W_{ker} =5, \ S = 2, \ P = 1, \ K = ?$,则根据公式(3-9)有:
$$
\begin{cases}
\begin{split}
H_{out} &= \left \lfloor \frac{(H_{in} + 2 \cdot P - H_{ker})}{S} \right \rfloor + 1 = \left \lfloor \frac{(200 + 2 \times 1 - 5)}{2} \right \rfloor + 1 = 99\\\\
W_{out} &= \left \lfloor \frac{(H_{in} + 2 \cdot P - H_{ker})}{S} \right \rfloor + 1 = \left \lfloor \frac{(200 + 2 \times 1 - 5)}{2} \right \rfloor + 1 = 99
\end{split}
\end{cases}
\tag{3 - 11}
$$
对第二层池化层,同理我们有$H_{in} = W_{in} = 99, \ H_{ker} = W_{ker} =3, \ S = 1, \ P = 0, \ K = ?$,则有:
$$
\begin{cases}
\begin{split}
H_{out} &= \left \lfloor \frac{(99 + 2 \times 0 - 3)}{1} \right \rfloor + 1 = 97\\\\
W_{out} &= \left \lfloor \frac{(99 + 2 \times 0 - 3)}{1} \right \rfloor + 1 = 97
\end{split}
\end{cases}
\tag{3 - 12}
$$
对第三层池化层,同理我们有$H_{in} = W_{in} = 97, \ H_{ker} = W_{ker} =3, \ S = 1, \ P = 1, \ K = ?$,则有:
$$
\begin{cases}
\begin{split}
H_{out} &= \left \lfloor \frac{(97 + 2 \times 1 - 3)}{1} \right \rfloor + 1 = 97\\\\
W_{out} &= \left \lfloor \frac{(97 + 2 \times 1 - 3)}{1} \right \rfloor + 1 = 97
\end{split}
\end{cases}
\tag{3 - 13}
$$
该例子是经典的LeNet-5,网络结构如下图所示:
其网络参数参考下图(卷积核大小均为5*5,步长1。池化核大小均为2*2,步长为2,通道数不定。):
层数 | 层类型 | 对应名称 | 相关参数 |
---|---|---|---|
1 | 输入层 | INPUT | 输入尺寸 32 * 32 * 1 |
2 | 第1个卷积层 | C1 | 卷积核尺寸 5 * 5 * 6, 步长 1, 填充 VALID |
3 | 第1个池化层 | S2 | 池化核尺寸 2 * 2 * 6, 步长 2, 填充 VALID |
4 | 第2个卷积层 | C3 | 卷积核尺寸 5 * 5 * 16, 步长 1, 填充 VALID |
5 | 第2个池化层 | S4 | 池化核尺寸 2 * 2 * 16, 步长 2, 填充 VALID |
6 | 第3个卷积层 | C5 | 卷积核尺寸 5 * 5 * 120, 步长 1, 填充 VALID |
7 | 第1个全连接层 | F6 | 压扁为84个神经元 |
8 | 输出层 | OUTPUT | 10种分类 |
第1个卷积层C1,有:
$$
W_{out} = H_{out} = \left \lfloor \frac{(32 + 2 \times 0 - 5)}{1} \right \rfloor + 1 = 28
\tag{3 - 14}
$$
第1个池化层S2,有:
$$
W_{out} = H_{out} = \left \lfloor \frac{(28 + 2 \times 0 - 2)}{2} \right \rfloor + 1 = 14
\tag{3 - 15}
$$
第2个卷积层C3,有:
$$
W_{out} = H_{out} = \left \lfloor \frac{(14 + 2 \times 0 - 5)}{1} \right \rfloor + 1 = 10
\tag{3 - 16}
$$
第2个池化层S4,有:
$$
W_{out} = H_{out} = \left \lfloor \frac{(10 + 2 \times 0 - 2)}{2} \right \rfloor + 1 = 5
\tag{3 - 17}
$$
第2个池化层S4,有:
$$
W_{out} = H_{out} = \left \lfloor \frac{(10 + 2 \times 0 - 2)}{2} \right \rfloor + 1 = 5
\tag{3 - 18}
$$
第3个卷积层C5,有:
$$
W_{out} = H_{out} = \left \lfloor \frac{(5 + 2 \times 0 - 5)}{1} \right \rfloor + 1 = 1
\tag{3 - 19}
$$
第1个全连接层F6(全连接层没有卷积/池化操作),有:
$$
\begin{cases}
\begin{split}
H_{out} &= 1 \\\\
W_{out} &= 84
\end{split}
\end{cases}
\tag{3 - 20}
$$
所有结果计算出来都与原始Paper吻合,证明我们的公式是准确无误的。
]]>Wisdom in the mind is better than money in the hand.
今年年初的时候,组内接到个反馈,客户很早之前购买的咱们公司的一款产品系统无法正常启动。公司经初步排查后确定故障原因是由于咱们应用识别模块占用的内存偏高,而这款产品又属于比较老的型号,硬件配置较低,启动时内存不足引起的。
我们经过深入分析发现,导致应用识别模块内存占用率居高不下的一个主要原因是AC引擎的具体代码实现上有些问题,有很大的优化空间。经过重新设计和实现后,单class内存占用率直接降低了100多M,整个class(还包含其它很多模块)才占用内存约600M,效果可以说还是相当显著的。
深入排查的时候,少不了要去过N遍引擎代码,而AC多模匹配算法自然也成了我们的重点关注对象。经过我们不懈的努力,最终解决了这个问题,在此分享出来,以供大家参考。
其实最开始的时候并没有想写成系列的文章,因为后续的引擎演进会迁移到其它更高效的字符串匹配算法上,因此我们决定把一些常见/常用的字符串匹配算法都分享出来,方便对这方面感兴趣的同学学习了解,同时也作为我们组的一种技术积累,欢迎大家积极跟我们探讨交流。
本系列文章的目标是让没有任何字符串匹配/算法背景的同学也能轻松理解,在行文上可能会丧失一定的专业和简洁性,如有不妥,请多多指正。
字符串匹配算法自提出至今,可以说是硕果累累。无数业界大佬前赴后继的投入到了算法的研究中。时至今日,已经有大量成熟的匹配算法(及其变异体),对这些算法我们总结出一张概览表如下所示(仅仅覆盖了大部分算法):
算法名称 | 主要作者 | 提出年份 | 分类I | 分类II | 备注 |
---|---|---|---|---|---|
Naive Algorithm (Brute Force) | —— | —— | 单模匹配 | —— | —— |
KMP | DONALD E. KNUTHf JAMES H. MORRIS, JR. VAUGHAN R. PRATT | 1977 | 单模匹配 | 前缀搜索 | Paper下载地址 |
BM | Stanford Researc Xerox Palo Alto R | 1977 | 单模匹配 | 后缀搜索 | Paper下载地址 |
Horspool | R. NIGEL HORSPOOL | 1980 | 单模匹配 | 后缀搜索 | Paper下载地址 |
Rabin-Karp | Richard M. Karp Michael O. Rabin | 1987 | 单模匹配 | —— | Paper下载地址 |
Sunday | Daniel M.Sunday | 1990 | 单模匹配 | 前缀搜索 | Paper下载地址 |
Shift-And/Or | Ricardo A. Baeza-Yates | 1992 | 单模匹配 | 前缀搜索 | Paper下载地址 |
BDM | Maxime Crochemore | 1994 | 单模匹配 | 子串搜索 | Paper下载地址 |
BNDM | Gonzalo Navarro | 2000 | 单模匹配 | 子串搜索 | Paper下载地址 |
BLIM | M. Oguzhan Külekci | 2010 | 单模匹配 | 子串搜索 | Paper下载地址 |
Commentz-Walter | Beate Commentz-Walter | 1979 | 多模匹配 | 后缀搜索 | Paper下载地址 |
Wu-Manber | Sun Wu Udi Manber | 1994 | 多模匹配 | 后缀搜索 | Paper下载地址 |
SBDM | A. BLUMER | 1986 | 多模匹配 | 子串搜索 | Paper下载地址 |
SBOM | Maxime Crochemore | 1999 | 多模匹配 | 子串搜索 | Paper下载地址 |
AC | Alfred V.Aho Margaret J.Corasick | 1975 | 多模匹配 | 前缀搜索 | Paper下载地址 |
注:表格中仅仅列出了一些Paper能找到的算法,其它的则没有在此列出。
为了尽量保持语言的简洁,我们在此对一些术语及约定做一个介绍。
给定一串字符,比如”Sometimes, the everlasting hate have no unique period.”,我们要在这串字符里面寻找某个/某些单词/字母,我们称这串字符为目标串(Text),其长度记为$n$,指向目标串的索引变量我们用$r,\ r=1,2,3,…,n$表示。
即$t_r$表示目标串的第$r$个字符,目标串为:$Text = t_1 t_2 t_3 … t_r …t_n$。
接上面说的,我们要寻找的某个/某些单词/字母(比如我要找”unique”),我们称之为特征串(Pattern),其长度记为$m$,指向特征串的索引变量我们用$s, \ s=1,2,3,…,m$表示。
即$p_s$表示特征串的第$s$个字符,特征串为:$Pattern = p_1 p_2 p_3 … p_s … p_m$。
对于IT开发人员来说,字符串匹配/检索可以说是使用频率比较高的一种操作了。即使我们的400妹子,每天也会有大量的字符匹配操作,比如使用Windows自带的搜索文件功能。
对于字符匹配的这一系列算法,我们先来介绍最简单,最易懂,但也是效率最低的一种。由于这种算法跟我们大脑的思维模式比较贴近,因此在不需要任何算法、计算机等的相关知识的情况下,也能想出来。这就是我们要介绍的Naive Algorithm(朴素算法,名字果然很朴素)或者称为Brute Force(暴力算法,我想这个名字是缘于其实现思路简单粗暴)。
结合实际例子来讲,有一串字符aabcabcabcacabc
(即我们的目标串),要让你在里面找abcabcacab
(即我们的特征串),并且告诉你完全不用考虑效率,能找出来就算完成任务,目标串和特征串如下所示:
$$
\begin{split}
Text&: \qquad aabcabcabcacabc \\\\
Pattern&: \qquad abcabcacab \\\\
\end{split}
$$
该怎么做呢?聪明的你略加思索就找到了答案,我拿着特征串,比对着目标串,从前往后,挨个挨个去比较。虽然很简单,我们还是把这个过程画一下(为了方便对比,图中绿色表示匹配成功字符,红色表示匹配失败字符。),如下所示:
Index | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Text | $a$ | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ | $c$ |
Step1 | $\color{green}{a}$ | $\color{red}{b}$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ | |||||
Step2 | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{green}{c}$ | $\color{green}{a}$ | $ \color{red}{b}$ | |||||
Step3 | $\color{red}{a}$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ | |||||
Step4 | $\color{red}{a}$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ | |||||
Step5 | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{green}{c}$ | $\color{green}{a}$ | $ \color{green}{b}$ | |||||
Step6 | $\color{red}{a}$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ | |||||
Done! |
可以看出,直到第5次的时候,我们才找到了一个完全匹配。这个算法非常简单直白,就不多说,其时间复杂度(最坏情况下)为$\color{red}{O((n - m + 1)m)}$。
关于时间复杂度,考虑到有少数同学没有学过算法分析,在这里就多说几句。时间复杂度是用来定性(注意非定量)描述算法运行时间(效率)的一个参数,常用记号$O$(ooxx)来表示,我们常用的时间复杂度有:
$$
\underbrace{O(1)}_{常数} < \underbrace{O(logN)}_{对数} < \underbrace{O(N)}_{线性} < \underbrace{O(N \cdot logN)}_{线性对数} < \underbrace{O(N^{m=2,3,…})}_{m次方} < \underbrace{O(2^N)}_{指数级} < \underbrace{O(N!)}_{阶乘}
$$
从左往右,时间复杂度越来越高,而算法的效率越来越低。
KMP算法属于字符串匹配中比较经典的算法,它后面提出来的很多算法都借鉴它的一些想法。
前面的BF算法实在是太繁琐了,每次只能跳一格来开始新一轮的匹配,每一轮的匹配还需要逐个逐个字符去比较,简直累死个人,这也是它效率极低的原因所在。那么有没有什么方法可以改进呢?几个大牛琢磨一番之后,就找到了办法,并且这个办法还比较好使。
KMP算法的核心思想就是借助一个三方工具(Next数组,存放在内存中)去记录一些关键性的信息,实现匹配过程中的不断跳跃前进,从而有效的减少匹配次数。后面我们会看到还有许多算法都是这种套路,只不过玩法上有所差异。比如有个算法觉得这些关键信息放在内存中还是不够快,它干脆直接放到了寄存器里面,也是会玩。
只要充分理解了上面的两点核心思想,KMP算法咱也就掌握了一半了。理解了KMP算法的思想之后,一些疑问也随之而来,主要体现在以下两点:
这几个问题我们一个个来解答,首先我们来看看是如何做到定位的。
如下图所示,假定我们在第$i$轮匹配时,目标串和特征串前面的$s - 1$个字符已经匹配(图中黄色虚线框内部分),而在第$s$个字符处发生了失配(图中红色不等号对应的两个方框),我们需要确定在$i + 1$轮匹配时,跳到前面已经匹配的$s - 1$个字符中的哪一个开始。
上图中红色双向箭头对应的是我们目标串和特征串已匹配的字符,蓝色虚线部分对应的是预匹配部分字符。当然这种情况在实际生活中出现的概率是比较小的,示例如下;
index | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Text | $a$ | $a$ | $a$ | $a$ | $a$ | $a$ | $a$ | $a$ | $a$ | $a$ | $b$ | $c$ | $a$ | |
i’th iter | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{red}{c}$ | |||||
(i+1)’th iter | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{green}{a}$ | $\color{red}{c}$ |
上面说的特征串前$s - 1$个字符都要完全相同这个结论要证明是比较简单的,拿张纸随便画一画就明白了。这里我们还是给出理论证明过程,供有兴趣的参考。由于特征串与目标串已经匹配了前面$s - 1$个字符,所以有:
$$
\color{grey}{
\underbrace{p_1…p_{s-1}}_{(s - 1) - 1 + 1 = s - 1个字符} = \underbrace{t_{r-s+1}…t_{r-1}}_{(r - 1) - (r - s + 1) + 1 = s - 1个字符}
\tag{2 - 2}}
$$
即:
$$
\color{grey}{
\begin{cases}
p_1 = t_{r - s + 1} \\\\
p_2 = t_{r - s + 2} \\\\
\cdots \cdots \\\\
p_{s - 1} = t_{r - 1} \\\\
\end{cases}
\tag{2 - 3}}
$$
再由预匹配的条件我们有:
$$
\color{grey}{
\underbrace{p_1…p_{s-2}}_{(s - 2) - 1 + 1 = s - 2个字符} = \underbrace{t_{r-s+2}…t_{r-1}}_{(r - 1) - (r - s + 2) + 1 = s - 2个字符}
\tag{2 - 4}}
$$
即:
$$
\color{grey}{
\begin{cases}
p_1 = t_{r - s + 2} \\\\
p_2 = t_{r - s + 3} \\\\
\cdots \qquad \cdots \\\\
p_{s - 2} = t_{r - 1} \\\\
\end{cases}
\tag{2 - 5}}
$$
再由式(2-3)、(2-5)有:
$$
\color{grey}{
\underset{\triangle}{p_1} = t_{r - s + 1} = t_{r - s + 2} = \underset{\triangle}{p_2} = t_{r - s + 3} = \cdots = \underset{\triangle}{p_{s - 2}} = t_{r - 1} = \underset{\triangle}{p_{s - 1}}\\\}
$$
证毕!
我们给定的是最大前/后缀为$c = s - 2$,但实际上可以得出$c = s - 1$,与已知条件矛盾,因此这种情况不存在。实际上按照Paper中的定义,我们所谓的最大前缀必须是已匹配字符的子串。Paper中使用的是f值,因为利用f值可以非常容易的求解出Next值。结合本文,f值即为最大前/后缀大小 + 1,比如上面讲的第二种情况特征串最后一个字符处发生失配后,可以求出其最大前/后缀为$c = 1$,则该特征串字符对应的f值即为$c + 1 = 2$。
这里要特别强调下这个f值,它是我们理解整个KMP算法的关键点,务必充分理解并牢记其定义。
Paper中关于f值的定义原文如下:
Let f[j] be the largest i less than j such that pattern[1]…pattern[i - 1] = pattern[j - i + 1]…pattern[j- 1]
为了平滑的与Paper的概念衔接起来,我们对一些特殊情况做些约定。对于$s \geq 3$的情况,c值可以直接用$c = s - 3$公式计算,对于$s = 1$时,规定$c = -1$(这样对应的f值即为0)。对于$s = 2$时,规定$c = 0$(这样对应的f值即为1)。
注:Paper中是用$j$来指向失配的特征串字符,我们用的是$s$。
于是,对给定的前后缀,其大小(长度)为$c$,我们可求出,下一轮匹配时,特征串应该沿着目标串往前移动$(s - 1) - c = s - c - 1$个字符。这是我们目标串上的跳跃值计算公式,如下所示:
上面我们得到了目标串上的跳跃值计算公式。那么特征串上跳跃值又怎么计算公呢?
这个问题要比目标串上的跳跃简单的多,第$i + 1$轮匹配的时候,根据预匹配的定义,特征串和目标串前面的$c$个字符已经逐一匹配了,所以我们只需要比对目标串上的$t_r$(即为上一轮失配的字符)和特征串上的第$c + 1$个字符$p_{c + 1}$即可。也就是说,在新一轮的匹配中,我们跳过特征串的前面$c$个字符,如下图所说:
综上所述,Next数组正是存储了这些信息,使得我们可以精准的在目标串和特征串上跳跃匹配。
解开了Next数组的定位之谜,我们来看Next数组的大小是跟什么有关,是目标串还是特征串?是只需要计算一次,还是要在匹配过程中重复计算?只有确定了这两个问题,我们才能继续讨论下一步如何计算的问题。
我们知道Next数组保存的是当目标串和特征串发生失配的时候的跳转信息,也就是说,有多少种失配的情况,就需要保存多少条跳转记录。尽管目标串长度不定,内容也未知,但是特征串是固定和已知的。对长度为$m$的特征串,不管当前匹配到了目标串的什么位置,失配肯定是特征串的$p_0…p_m$中的某个字符与目标串不匹配,失配的情形也只有有限的$m$种,我们只需要把特征串每一个字符失配时对应的跳转信息保存起来就可以了。
即Next数组大小只跟特征串长度有关,它的大小即为特征串长度。
假定特征串的某一个字符在目标串不同位置上前后两次发生了失配,我们只需要考察这两种情况下的跳跃值是否有差异,就知道Next数组是否需要反复计算了,如下图所示:
由于两次失配时的特征串字符是相同的(已经匹配的字符数和字符内容均相同),即有$c_{s-1}^{i} = c_{s-1}^{i + k}$(因为相同特征串同一位置处其最大前缀/后缀必然是一样的),因此其在目标串上的跳跃值、在特征串上的跳跃值必然也是相同的。
即Next数组存放的跳跃值,只需要在开始进行匹配前计算一次,只要我们要匹配的特征串不变,这些值就不会变。
解开了Next数组的前两个问题,现在只剩下最后一个,即如何计算。前面我们提了下,只要求出f数组的值,就能轻松的求解出Next数组值,而f值又是跟我们的最大前/后缀紧密相关的。因此只要我们先找到最大前/后缀,就能求出相应的f值,进而求解Next值。
最大前后缀(包括f、Next数组)的计算,用到了递归的思想:除特征串前2个字符外的每一个字符,其最大前/后缀的计算都是根据其上一个字符的最大前/后缀推算出来的。
我们仍然以$s$指向当前要计算最大前缀的特征串字符,用$p_s$表示这个字符,再次强调,为了跟Paper结合起来,我们采用$f[t]$表示特征串索引号为t的字符的最大前/后缀长度值 + 1(也即下一个待匹配字符)。
假定我们现在已经计算好了$p_s$的最大前缀为$c_s$(即有$f[s] = c_{s + 1} + 1$),现在要计算其下一个字符$p_{s+1}$的最大前缀$c_{s+1}$(即有$f[s+1] = c_s + 1$),如下图所示:
$$
suffix’ = suffix + p_s
$$
从图上可以看出,如果$p_s$对应的前缀的后一个字符满足$p_{c_s + 1}(pattern[f[s]]) = p_s$,那么这个字符加到之前的前缀里面去就是我们要找的前缀,即有:
$$
\begin{cases}
f[s + 1] = f[s] + 1\\\\
\\\\
preffix’ = preffix + p_{c_s + 1}
\end{cases}
$$
此时的匹配情况如下图所示:
而这个时候我们取出这个最大前缀,把这个前缀的后一个字符(即为$pattern[f[c_s+1]]$)和$p_s$比较,如果相同,则开始的$f[c_s+1]$个字符即为我们最终的$preffix’$。如果不相同,那么再以特征串索引号为$f[c_s+1]$的字符为失配字符,找到其f值,再用这个f值对应的字符和$p_s$比较。如此重复,直到找到匹配的字符或者耗尽了所有字符(不存在匹配的前缀,此时置其f值为1),如下图所示:
$$
f[s + 1] = \begin{cases}
f[s] + 1, \quad if \ pattern[s] \ = \ pattern[f[s]] \\\\
\\\\
f[f[s]], \quad if \ pattern[s] \ \neq \ pattern[f[s]] \\\\
\end{cases}
\tag{2 - 6}
$$
求最大前/后缀的算法伪代码(相较于Paper略有改动)如下:
$$
\begin{split}
& \overline{\underline{\textbf{Algorithm 1 } \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad}} \\\\
& \textbf{Input: pattern} \\\\
& \textbf{Output: f[m]} \\\\
& \textbf{function} \quad preffix\_suffix\_algorithm(pattern): \\\\
&1 \qquad m \ = \ pattern.length \\\\
&2 \qquad f[1] \ = \ 0, \quad k= \ 1\\\\
&3 \qquad \textbf{while} \ \ k \ < \ m \\\\\
&4 \qquad \qquad t:= \ f[k]\\\\
&5 \qquad \qquad \textbf{while} \ \ t \ > \ 0 \ \ \textbf{and} \ \ pattern[t] \ \neq \ pattern[k] \\\\
&6 \qquad \qquad \qquad t:=\ f[t] \\\\
&7 \qquad \qquad f[k + 1]:=\ t \ + \ 1 \\\\
&8 \qquad \qquad k:=\ k \ + \ 1 \\\\
&9 \qquad \textbf{return} \ \ f \\\\
& \underline{\textbf{end function} \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad} \\\\
\end{split}
$$
注意:前面我们已经提到了,对于特征串的第1、2个字符,其f值比较特殊,分别置为0、1。
上式(2-6)以及其伪代码中的递归的思想其实可以用一句话来总结:既然长的后缀字符在已匹配的字符中找不到对应的前缀字符,那么我不断缩短后缀大小,就有可能在已匹配的字符中找到对应的前缀了,而缩短后缀字符最高效的办法就是利用失配(以特征串同时作为目标串和特征串)找到其对应的f值(当然你也可以一个字符一个字符的缩减,这样的话效率就会非常低了)。
我们仍以之前所述的特征串为例,其最终的f值计算结果如下表:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
Pattern[k] | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ |
f[k] | 0 | 1 | 1 | 1 | 2 | 3 | 4 | 5 | 1 | 2 |
表中索引号5、6、7、8的字符其f值计算都比较简单,因为它们都满足式(2 - 6)的第一个条件,所以直接在前一个字符的f值上递增1就可以了。这里主要讲一下索引号为9的字符串,它的f值没有延续之前的递增趋势。
如前所述,要计算$f[9] = f[8 + 1]$,先判断$pattern[8] = c$和$pattern[ \underbrace{f[8]}_{=5} ] = pattern[5] = b$是否相同,很明显两者不相同,也就是说以Pattern为目标串,以滑动后的Pattern为特征串进行匹配,在特征串索引号为5的字符$b$处发生了失配,所以$p_4…p_8 = abcac$不可能成为$p_9 = a$的最大后缀(表中蓝色字符),如下所示:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
Pattern(Text) | $a$ | $b$ | $c$ | $\color{blue}{a}$ | $\color{blue}{b}$ | $\color{blue}{c}$ | $\color{blue}{a}$ | $\color{blue}{c}$ | $a$ | $b$ | |
Pattern | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{red}{b}$ | $c$ | $a$ | ··· |
如前所述,既然找不到长度为5的前缀,我们就利用失配(红色字符$p_5 = b$处)找到其f值从而缩减要找寻的前缀的长度。据上面的f值表可知$f[5] = 2$,这个时候后缀已经缩减到了2个字符,再次比较$pattern[8] = c$和$pattern[ f[ \underbrace{f[8]}_{=5}] ] = pattern[ \underbrace{f[5]}_{=2}] = pattern[2] = b$是否相同,如下图所示:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
Pattern(Text) | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $\color{blue}{a}$ | $\color{blue}{c}$ | $a$ | $b$ | |
Pattern | $\color{green}{a}$ | $\color{red}{b}$ | $c$ | $a$ | ··· |
对比后可知,$p_7p_8 = ac$仍然找不到对应的前缀。我们只好重复刚才的操作,得到$f[[f5]] = f[2] = 1$,这个时候后缀已经缩减到了仅仅1个字符,再次比较$pattern[8] = c$和$pattern[f[f[\underbrace{f[8]}_{=5}]] = pattern[f[\underbrace{f[5]}_{=2}]] = pattern[\underbrace{f[2]}_{=1}] = pattern[1] = a$是否相同,如下图所示:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
Pattern(Text) | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $\color{red}{c}$ | $a$ | $b$ | |
Pattern | $\color{red}{a}$ | $b$ | $c$ | ··· |
对比后可知,$p_8 = c$仍然找不到对应的前缀,再次重复操作,得到$f[1] = 0$,这个时候表示前面已经匹配的字符耗尽了,因此f值计算结束,并以0 + 1 = 1作为该字符的f值,即有$f[9] = 0 + 1 = 1$。
求出f值数组后,只需要做一点小小的处理就能变成我们的Next数组了。我们先给出Next数组的求解表达式,如下所示:
$$
next[s] = \begin{cases}
f[s], \quad if \ pattern[s] \ \neq \ pattern[f[s]] \\\\
\\\\
next[f[s]], \quad if \ pattern[s] \ = \ pattern[f[s]] \\\\
\end{cases}
\tag{2 - 7}
$$
正如我们前面所述,Next数组的求解也用到了递归的思想,只是它对应的条件和f值的求解刚好相反,这点是最让人费解的。下面我们就结合例子来讲,先给出最终的f值及next值表如下:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
Pattern[k] | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ |
f[k] | 0 | 1 | 1 | 1 | 2 | 3 | 4 | 5 | 1 | 2 |
next[k] | 0 | 1 | 1 | $\color{red}{0}$ | $\color{red}{1}$ | $\color{red}{1}$ | $\color{red}{0}$ | 5 | $\color{red}{0}$ | $\color{red}{1}$ |
表中,next值与f值不同的部分均已标红。我们先来看看索引号为4的字符为什么next值变成了0,假定该特征串索引号为4这个字符处发生了失配,如下所示:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Text | $a$ | $b$ | $c$ | $b$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ | $a$ | $b$ |
Pattern | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{red}{a}$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ |
失配后,我们取得其f值$f[4] = 1$,该值对应的特征串字符为$p_1 = a$,这个字符和我们的失配字符是相同的,即有:$p_1 = p_4 = a$,既然$p_4$在这里发生了失配,我们根据求得的f值移动特征串($s - f[s] = 4 - 1 = 3$个字符)后再次进行匹配时,也必然会发生失配,如下所示:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Text | $a$ | $b$ | $c$ | $b$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ | $a$ | $b$ |
Pattern | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{red}{a}$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ | ||
Pattern(Shifted) | $\color{red}{a}$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ |
既然发生了失配,必然会有跳转操作,如果我们不对这个f值进一步处理,匹配的时候就避不开这一步不必要的操作,而处理后我们可以直接跳过4个字符,然后开始下一轮的匹配。这就是为什么我们算出了f值,还要进一步算next值的原因。如果你事先了解一些NFA和DFA的知识的话,f值的作用就有点类似我们的NFA,而next值则是DFA,想进一步了解可参考2.4.3.7节所述。
那这里跳过4个字符是怎么得出来的呢?首先特征串第一个字符处发生失配后,其f值为$f[1] = 0$,因此我们要跳过$s - f[s] = 1 - 0 = 1$个字符,再开始下一轮新的匹配。再结合之前跳过的3个字符,一共就是4个!
结合式(2-7)的第二条来看,$next[4] = next[f[4]] = next[1] = 0$。索引号7、9的字符其next值为0的原因也是一样的,大家可以自己推导一下。
我们再来看索引号为8的特征串字符$p_8 = b$,假定在该字符处发生了失配,如下所示:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Text | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $a$ | $a$ | $b$ | $a$ | $b$ |
Pattern | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{red}{c}$ | $a$ | $b$ |
失配后,我们取得其对应的f值$f[8] = 5$,该值对应的特征串字符为$pattern[f[8]] = pattern[5] = b$,这个字符和我们的失配字符是不相同的,即有:$p_5 \neq p_8$,既然这个字符与发生失配的字符不相同,那么跳转后下一轮匹配时,上一轮失配的地方就有可能不会再失配,因此我们就使用这个值作为next值,这就是式(2-7)的第一个条件的来源!
当然我们举得这个例子里面,跳转后在原来发生失配的地方还会再一次发生失配,如下所示:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Text | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $a$ | $a$ | $b$ | $a$ | $b$ | |
Pattern | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{red}{c}$ | $a$ | $b$ | |||
Pattern(Shifted) | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{red}{b}$ | $c$ | $a$ | $c$ | ··· |
其实这里还有一个问题,可能会让人有些困惑,既然跳转后的特征串我们知道了是$b$,发生失配的目标串字符是$a$,两者不相同,再次发生了失配,为什么不进一步处理以获得最终的next值呢?
原因就在于,我们要匹配的目标串是动态变化并且是不可预知的,这个例子发生失配后经过一次跳转再次发生了失配,但是在目标串的其它地方,跳转后可能就匹配上了!举个例子来讲,比如特征串第八个字符在目标串上某处再次发生失配,如下所示:
k | ··· | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | ··· |
---|---|---|---|---|---|---|---|---|---|---|---|
Text | ··· | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $b$ | $a$ | ··· |
Pattern | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{red}{c}$ | $a$ | ||
Pattern(Shifted) | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{green}{a}$ | $\color{green}{b}$ | $\color{red}{c}$ | ··· |
如上表所示,上一轮失配的字符,在下一轮匹配时却匹配上了。而后面还没有匹配的目标串中,可能还存在大量这样的例子,每一次你都无法预料跳转后是否能匹配上,还得老老实实做一次匹配。
至此,式(2-7)的两个条件我们就解答完了,这里我们再对比下next数组值和f数组值的异同:
在实际实现上,我们完全可以把next值和f值的求解融合在一起,而完全不需要单独存储f值,也不需要等所有f值求解完了之后再来转换成next值。算法(相较于Paper略有改动)伪代码如下:
$$
\begin{split}
& \overline{\underline{\textbf{Algorithm 2 } \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad}} \\\\
& \textbf{Input: pattern} \\\\
& \textbf{Output: next[m]} \\\\
& \textbf{function} \quad next\_precompile(pattern): \\\\
&1 \qquad m \ = \ pattern.length \\\\
&2 \qquad next[1] \ = \ 0 , \quad k = 1 , \quad t= \ 0\\\\
&3 \qquad \textbf{while} \ \ k \ < \ m \\\\\
&4 \qquad \qquad \textbf{while} \ \ t \ > \ 0 \ \ \textbf{and} \ \ pattern[t] \ \neq \ pattern[k] \\\\
&5 \qquad \qquad \qquad t:=\ next[t] \\\\
&6 \qquad \qquad t:=\ t \ + \ 1 , \quad k:=\ k \ + \ 1 \\\\
&7 \qquad \qquad \textbf{if} \ \ pattern[t] \ = \ pattern[k] \\\\
&8 \qquad \qquad \qquad next[k]:= \ next[t] \\\\
&9 \qquad \qquad \textbf{else} \\\\
1&0 \qquad \qquad \qquad \ next[k]:= \ t \\\\
1&1 \qquad \textbf{return} \ \ next \\\\
& \underline{\textbf{end function} \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad} \\\\
\end{split}
$$
分析可知,计算f数组以及Next数组的时间复杂度均为$O(m)$,这里不做展开,在Paper第6页有详细的分析说明,感兴趣请自行参阅。
前面说到了f值类似我们的NFA,next值类似我们的DFA,这里举个例子来予以说明,方便没有相关知识的同学去理解f值和next值的区别。仍以前面的例子来说,我们有f值及next值如下表:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
Pattern[k] | $a$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ |
f[k] | 0 | 1 | 1 | 1 | 2 | 3 | 4 | 5 | 1 | 2 |
next[k] | 0 | 1 | 1 | $\color{red}{0}$ | $\color{red}{1}$ | $\color{red}{1}$ | $\color{red}{0}$ | 5 | $\color{red}{0}$ | $\color{red}{1}$ |
我们来看索引号为4的特征串,假设在这里发生了失配,按照上述f值的移动(4-1=3)、next值移动(4 - 0 = 4)特征串后如下所示:
k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Text | $a$ | $b$ | $c$ | $b$ | $a$ | $c$ | $a$ | $c$ | $a$ | $b$ | $a$ | $b$ |
Pattern | $\color{green}{a}$ | $\color{green}{b}$ | $\color{green}{c}$ | $\color{red}{a}$ | $b$ | $c$ | $a$ | $c$ | $a$ | $b$ | ||
Pattern(f-Shifted) | $\color{red}{a}$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | $c$ | $a$ | |||
Pattern(next-Shifted) | $\color{green}{a}$ | $b$ | $c$ | $a$ | $b$ | $c$ | $a$ | ··· |
可以看出,失配后,根据f值我们只能往前移动$4 - f[4] = 4 - 1 = 3$个字符,然后比对特征串第1个字符$pattern[f[4]] = pattern[1] = a$和目标串第4个字符$b$,发现再次失配,然后特征串再往前挪动一格,到达我们按照next值所移动到的位置。很明显这一步是完全没有必要的,我们可以直接跳到目标串索引号为5的字符$a$处开始。
上一轮匹配时特征串字符为$a$,发生了失配,根据f值跳转后,我们要匹配的第一个特征串字符仍然是$a$,这就必然会导致再次失配。但由于f值仅仅存储的是失配处字符前面的最大前/后缀,没有关于前/后缀的下一个字符的信息,所以使用f值是无法避免大量重复匹配的。
而next值保存的是,让你在不漏匹配的情况下,最大移动的字符数。
在本节的最后,我们给出完整的KMP算法伪代码,如下所示:
$$
\begin{split}
& \overline{\underline{\textbf{Algorithm 3 KMP} \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad}} \\\\
& \textbf{Input: text, pattern} \\\\
& \textbf{Output: index(1st match) or -1(no match)} \\\\
& \textbf{function} \quad kmp\_algorithm(text, \ pattern): \\\\
&1 \qquad m \ = \ pattern.length, \quad s \ = \ 1(ptr \ of \ pattern)\\\\
&2 \qquad n \ = \ text.length, \quad r \ = \ 1(ptr \ of \ text)\\\\
&3 \qquad next \ = \ next\_precompile(pattern)\\\\
&4 \qquad \textbf{while} \ \ s \leq \ m \ \ \textbf{and} \ \ r \leq n\\\\\
&5 \qquad \qquad \textbf{while} \ \ s \ > \ 0 \ \ \textbf{and} \ \ pattern[s] \ \neq \ text[r] \\\\
&6 \qquad \qquad \qquad s:=\ next[s] \\\\
&7 \qquad \qquad s:=\ s \ + \ 1 , \quad r:=\ r \ + \ 1 \\\\\
&8 \qquad \textbf{if} \ \ s \ > \ m\\\\
&9 \qquad \qquad \textbf{return} \ \ r - 1\\\\
1&0 \qquad \textbf{else}\\\\
1&1 \qquad \qquad \textbf{return} \ \ -1\\\\
& \textbf{end function} \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \\\\
\\\\
& \textbf{function} \quad next\_precompile(pattern): \\\\
1&2 \qquad m \ = \ pattern.length \\\\
1&3 \qquad next[1] \ = \ 0 , \quad k = 1 , \quad t= \ 0\\\\
1&4 \qquad \textbf{while} \ \ k \ < \ m \\\\\
1&5 \qquad \qquad \textbf{while} \ \ t \ > \ 0 \ \ \textbf{and} \ \ pattern[t] \ \neq \ pattern[k] \\\\
1&6 \qquad \qquad \qquad t:=\ next[t] \\\\
1&7 \qquad \qquad t:=\ t \ + \ 1 , \quad k:=\ k \ + \ 1 \\\\
1&8 \qquad \qquad \textbf{if} \ \ pattern[t] \ = \ pattern[k] \\\\
1&9 \qquad \qquad \qquad next[k]:= \ next[t] \\\\
2&0 \qquad \qquad \textbf{else} \\\\
2&1 \qquad \qquad \qquad \ next[k]:= \ t \\\\
2&2 \qquad \textbf{return} \ \ next \\\\
& \underline{\textbf{end function} \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad} \\\\
\end{split}
$$
KMP算法总的时间复杂度(包括预计算Next数组)为$O(m + n)$,如果只考虑匹配/搜索部分的话,为$O(n)$。这些无论是《算法导论》还是原始Paper中都有详细的分析,限于篇幅,这里就不再展开。
KMP算法的实现不可谓不精妙,尤其是前/后缀的引进,犹如神来之笔,然而即使这样,上面的算法匹配性能也还有许多可优化之处。
此外,上面的代码检测到第一个特征串之后就停止了,也即只能检测出目标串中最左边的特征串,稍做处理之后,KMP可以把整个目标串中的特征串都检索出来。
KMP算法在其Paper中提到,在实际的应用场景中,上述没有优化过的KMP算法实际的表现性能可能还不如暴力匹配算法,原话是这么说的:
In fact, the algorithm as presented above would probably not be competitive with the naive algorithm on realistic data, even though the naive algorithm has a worst-case time of order m times n instead of m plus n, because the chance of this worst case is rather slim.
作者的意思很明确,虽然KMP理论上的时间复杂度为线性$O(m + n)$,而BF算法最坏情况下为$O(m \cdot n)$,但毕竟这种情况在实际应用中是极难遇到的。针对上述算法存在的问题,作者从以下几个方面入手,分别介绍了问题的根源,以及对应的解决办法。
当我们字符串中的字母很多的时候,我们很难得到一次部分匹配(partial match),几乎都是在特征串第一个字符处就发生了失配,比如有目标串$bc…xyz$,特征串$ac…$,特征串每次在首字符就发生失配,然后查Next数组有$next[1] = 0$,于是用这个值更新特征串指针$s:= next[s]$,然后判断这个指针是否越界$s \leq m$。我们每次都只能往右移动$1 - 0 = 1$个字符,然后去进行各种各样的判断,导致效率异常底下,这种情况下KMP的效率与我们的BF算法其实是一样的。
解决的办法是,将$s = 1$这种情况作为特殊例子来处理。具体做法是,当在特征串的首字符处失配时,不去查Next表,也不用去更新$s$以及判断其是否越界,直接将目标串指针$r$自增1再判断(即指向下一个目标串字符)即可,部分伪代码如下:
$$
\begin{split}
& \underline{ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad} \\\\
&1 \qquad \textbf{while} \ \ s = 1 \ \ \textbf{and}\ \ text[r] \neq \ pattern[1] \\\\
&2 \qquad \qquad r:= \ r \ + \ 1 \\\\
&3 \qquad \qquad \textbf{if} \ \ r > n \\\\
&4 \qquad \qquad \qquad break \\\\
& \underline{ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad} \\\\
\end{split}
$$
每一轮新的匹配开始前,我们都要对目标串以及特征串的指针做越界检测(即检测$s \leq m, \ r \leq n$是否满足),以便判断是否已经找到了满足要求的特征串(全匹配),从而浪费了大量不必要的时间。
处理办法是特征串的最后增加一个永远不会被匹配的字符,比如“@”,以此判断指针已经到达了字符串的尾部边界处,同时置该字符的next值为-1,即$next[m + 1] = -1$。于是就可以把对$s > 0$的判断放在执行不那么频繁的代码段中。
当然,我们还可以对目标串我们也采用类似的方法,比如在其尾部添加一个字符“$\perp$”作为边界标志,同时添加特征串首字符$pattern[1]$从而避免频繁的去做越界检测,即$text[n + 1] = \perp, \ text[n+2] = pattern[1]$。
上面给出的例子是一种通用的解决方案,但是在某些应用场景下,效率还不是最高的。比如在文本编辑器中使用查找功能时,我们输入的特征串都是比较短的,因此我们预先把特征串(隐式的包含了Next数组)编译成机器码,就可以大大的提高效率。
比如可以编译成如下的机器码:
1 | L0: k:= k + 1 |
KMP算法没有扩展前只能匹配目标串最左边的第一个特征串,并且只是单模匹配。要实现更强大的功能就需要做一些改进。
要想把所有特征串都检索出来,作者提到了一种方法,那就是新增一个resume变量同时结合上面提到的在特征串尾部添加越界标识字符的方法。添加越界字符后,计算这个越界字符的Next值$next[m + 1]$,然后将这个值赋给resume,最后再将$next[m + 1]$置为-1。当我们检测到一次完全的匹配之后,使用resume的值更新特征串指针$s$的值,从而恢复执行。
但需要注意的是resume的值一定不能为0(这样匹配算法中内存循环的$s > 0$就无法满足)。
KMP要实现多模匹配,需要做大量的工作,都可以再单独写一篇分析的文章了,感兴趣可自行参考Paper,在此不赘述。
能够坚持看到这里,我相信你一定会有所收获。其实KMP算法是众多匹配算法中最难理解的之一,我没在开篇的时候提出来,就是不希望给读者带来太大压力。要搞懂KMP的精髓,真的是需要花大量时间反复的去揣摩、推导。本文为了兼顾一些没有相关背景的同事,所以有点偏长,疏漏也在所难免,希望及时指正。
如果你没看懂,不要怀疑自己,一定是我没有讲清楚,请务必在评论中指出(目前查看、回复评论均需要代理)。
我们一直坚信,授人以鱼远不如授人渔。
参考资料如下:
]]>Nothing is perfect, nothing.
在本系列的前面我们已经讨论了线性可分和线性非可分问题的求解,虽然线性非可分的问题复杂了点,但终究还是在线性框架内。然而生活中有很多问题并不是咱们简单画一条或者几条直线就能解决的,比如本文开头的那张图片,你能简单的画几条直线(非折线)就把这两种类型的点区分开吗?
显然不能!也就是说这个问题在线性框架内并没有解,这就是本文要讲的非线性问题。
非线性分类问题是指通过利用非线性模型才能很好的进行分类的问题。
——《统计学习方法》$P_{115}$
对于非线性问题,其解决思路很简单(然而实现起来却不是那么容易),通过将原始的非线性数据转换成线性数据$\vec x_i \xrightarrow{f(\vec x_i)} \vec x_i’$,从而实现线性分类。这种转换可以看做是一种映射,是一种非线性变换。然而这种转换却存在几个问题:
鉴于上述这些原因,我们通常不去显式的定义映射函数(将映射函数记为$\phi(x)$),也就是说我们通常都不会直接去找这个映射函数。转而采用一种“投机取巧”的办法,这种方法我们通常称之为核技巧(Kernel Trick),它的思路是:
利用一个容易获取的函数,来代替不容易获得的映射函数的作用效果,从而在没有映射函数的情况下,我们也能获得映射后的运算结果。
具体点来说,对任意两个样本向量,在没有映射函数的情况下,我们也能获得其映射后的向量的内积,而获得的途径就是我们大名鼎鼎的核函数(Kernel Function),记作$K(x,y)$。核函数的数学表达式如下:
$$
\underbrace{K(\vec x_i, \vec x_j)}_{核函数} = \underbrace{\underbrace{\phi(\vec x_i)}_{映射后的\vec x_i} \cdot \underbrace{\phi(x_j)}_{映射后的\vec x_j}}_{映射后的\vec x_i、\vec x_j的内积}
\tag{1 - 1}
$$
上式(1-1)的意思就是,我将任意两个样本代入核函数进行计算,其结果与先将这两个样本映射到新空间再点积的结果等价。
为了方便理解,我画了如下的图示:
如上图所示,核函数的作用就是替代我们要找寻的映射函数的映射 + 内积效果。
原始的空间(输入空间,记为$\chi$为欧式空间或离散集合)中,数据是非线性可分的。原始数据映射后,在映射后的新空间(特征空间/希尔伯特空间,记为$H$)中,数据是线性可分的。因为输入空间中数据非线性可分,所以输入空间中的分割面必然是超曲面。相反,特征空间中的分割面则是超平面。
以上这些概念暂时不懂没有关系,有个印象就可以了,我们会在后面做详细的介绍。
那核函数为什么要定义成上述的形式呢?答案其实很简单!但是不管是网上的博客,还是出版的相关书籍,都没有对这点做出说明,Duang!直接就扔出一个东西,然后告诉你,这就是核函数。虽然这只是个很小的问题,对我们理解SVM也不会有太大影响,但就是这么小小的一个问题,对我们的逻辑思维和数学素养影响却非常大。我相信有一部分人甚至都不会想到这个问题。
为了方便叙述,我们分别记映射前后的样本为$\vec x_i、\vec x_i’$,我们有:
$$
\vec x_i’ = \phi(\vec x_i)
\tag{1 - 2}
$$
因为映射后的数据$\vec x_i \in T’ = \lbrace (x_1’, y_1’), \ (x_2’, y_2’), \ …, \ (x_N’, y_N’) \rbrace$已经是线性可分的,根据机器学习算法系列之三:SVM(4)中的分析,对于线性可分的数据,我们由其中的式(1-21)可以求出映射后的数据其对偶优化问题为:
$$
\min \limits_{\vec \alpha} \lbrack \frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i’ · \vec x_j’) - \sum_{i=1}^N \alpha_i \rbrack \\\\
\Updownarrow \\\\
\min \limits_{\vec \alpha} \lbrack \frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j [\color{red}{\phi(\vec x_i)} · \color{blue}{\phi(\vec x_j)}] - \sum_{i=1}^N \alpha_i \rbrack\\\\
\tag{1- 3}
$$
再由其中的式(1- 28)可以求出映射后的数据其分类决策函数为:
$$
\begin{split}
f(x) &= sign \lgroup \sum_{i=1}^N \alpha_i^* · y_i · (\vec x_i’ \cdot \vec x’) + b^* \rgroup \\\\
&= sign \lgroup \sum_{i=1}^N \alpha_i^* · y_i · [\color{red}{\phi(\vec x_i)} \cdot \color{blue}{\phi(\vec x)}] + b^* \rgroup
\end{split}
\tag{1 - 4}
$$
由上式(1-3)以及(1-4)可以看出,不管是在解最优化问题的时候,还是在求解最后的决策函数的时,我们都要先使用映射函数计算出任意样本在新空间的映射值。而我们在前面1.2小节中已经说了我们不会去找这个映射函数,更不用说去计算映射值了,而核函数的作用就是为了让我们避开这个坑。至此一切就都明朗了,核函数之所以要定义成式(1-1)的形式,就是为了用来替代$(\vec x_i’ \cdot \vec x’) = \color{red}{\phi(\vec x_i)} \cdot \color{blue}{\phi(\vec x)}$这一坨复杂的东西。
那么使用核函数替代后,上式(1-3)、(1-4)变成如下形式:
$$
\begin{cases}
\min \limits_{\vec \alpha} \lbrack \frac{1}{2} \sum\limits_{i=1}^N \sum\limits_{j=1}^N \alpha_i \alpha_j y_i y_j \color{red}{K(\vec x_i, \vec x_j)} - \sum\limits_{i=1}^N \alpha_i \rbrack \\
\\
sign \lgroup \sum\limits_{i=1}^N \alpha_i^* · y_i · \color{red}{K(\vec x_i, \vec x)} + b^* \rgroup
\tag{1 - 5}
\end{cases}
$$
核函数的本质是,通过在输入空间中计算函数$K(\vec x_i, \vec x_j)$的值,我们就能获得在特征空间中的向量的内积值$<\phi(\vec x_i), \ \phi(\vec x_j)>$,并且完全不需要映射函数。
尽管已经对核函数解释了这么多,很多读者可能还是无法直观的感受到:我们为什么要用核函数?下面我们通过一个例子来说明这一点。
给定两个样本向量$\vec{x}=(2, 3, 1, 3)^T 、\vec{z}=(3, 2, 2, 4)^T$,映射函数:
$$
\begin{split}
\phi(\vec{x})=(&x_1^2,\quad x_1 \cdot x_2, \quad x_1 \cdot x_3, \quad x_1 \cdot x_4, \\
&x_2 \cdot x_1, \quad x_2^2, \quad x_2 \cdot x_3, \quad x_2 \cdot x_4, \\
&x_3 \cdot x_1, \quad x_3\cdot x_2, \quad x_3^2, \quad x_3 \cdot x_4, \\
&x_4 \cdot x_1, \quad x_4 \cdot x_2, \quad x_4 \cdot x_3, \quad x_4^2)^T \end{split}
$$
注:上述映射函数$\phi$是一个$\mathbb{R}^4$到$\mathbb{R}^{16}$即4维空间到16维空间的映射函数。
核函数:
$$
K(\vec{x}, \vec{z}) = (\vec{x} \cdot \vec{z})^2
$$
常规做法(映射 + 内积),则有:
$$
\begin{split}
\phi(\vec{x}) &= (4, 6, 2, 6, 6, 9, 3, 9, 2, 3, 1, 3, 6, 9, 3, 9)^T \\
\phi(\vec{z}) &= (9, 6, 6, 12, 6, 4, 4, 8, 6, 4, 4, 8, 12, 8, 8, 16)^T \\
\phi(\vec{x}) \cdot \phi(\vec{z}) &= 4 \cdot 9 + 6 \cdot 6 + 2 \cdot 6 + 6 \cdot 12 + 6 \cdot 6 + 9 \cdot 4 \\
&\quad + 3 \cdot 4 + 9 \cdot 8 + 2 \cdot 6 + 3 \cdot 4 + 1 \cdot 4 + 3 \cdot 8 \\
&\quad + 6 \cdot 12 + 9 \cdot 8 + 3 \cdot 8 + 9 \cdot 16 \\
&= 676
\end{split}
$$
核函数计算,可得:
$$
\begin{split}
K(\vec{x}, \vec{z}) &= ((2, 3, 1, 3)^T \cdot (3, 2, 2, 4)^T)^2 \\
&= (2 \cdot 3 + 3 \cdot 2 + 1 \cdot 2 + 3 \cdot 4)^2 \\
&= 26^2 \\
&= 676
\end{split}
$$
对比上述两个求解过程可以看出,在最终计算结果完全一致的情况下,核函数所需的计算量大幅减少。
若样本特征取值变成浮点型,并且其绝对值较大,同时映射后的维度也较大的情况下,这种计算量的节省更为可观。
在某些场景下,若映射后的维度$n \rightarrow \infty$(即映射到无限维特征空间),这时即使我们能找到合适的映射函数$\phi(\vec{x})$,也会因为维度爆炸而无法计算映射后的值。而核函数却能轻而易举的解决这个难题。
综上,使用核函数的原因有如下几点:
虽然有了核函数让我们不必再去苦苦寻觅映射函数,那么如果没有一些较为通用的核函数供我们选择和使用,我们的问题也只是从寻找映射函数变成了寻找核函数,问题的复杂性可能并不会降低。
由此,我们提供一些比较常用的核函数以供调试、选择(关于如何选择核函数,目前学术界还没有一个具体的公式能给出答案,大多数情况下还是依赖于我们的经验同时结合实际业务来选择。)。下面就列出一些常用的核函数表达式:
名称 | 表达式 | 参数 | 备注 |
---|---|---|---|
线性核函数 | $K(\vec x_i, \vec x_j) = a \cdot {\vec x_i}^T \cdot \vec x_j + c$ | $a、c$ | —— |
多项式核函数 | $K(\vec x_i, \vec x_j) = (a \cdot {\vec x_i}^T \cdot \vec x_j + c)^d$ | $a、c、d(d \geq 1)$ $d = 1$时即为线性核函数 | 特别适合正交归一化后的数据 |
高斯核函数 (RBF) | $K(\vec x_i, \vec x_j) = exp\lgroup - \frac{\Arrowvert \vec x_i - \vec x_j \Arrowvert^2}{2 \sigma^2} \ \rgroup$ | 高斯核带宽$\sigma > 0$ | 抗噪能力强,对参数敏感 |
指数核函数 | $K(\vec x_i, \vec x_j) = exp\lgroup - \frac{\Arrowvert \vec x_i - \vec x_j \Arrowvert}{2 \sigma^2} \ \rgroup$ | $\sigma > 0$ | 属于高斯核函数变种,对参数敏感性降低,适用范围相对较窄 |
拉普拉斯核函数 | $K(\vec x_i, \vec x_j) = exp\lgroup - \frac{\Arrowvert \vec x_i - \vec x_j \Arrowvert}{\sigma} \ \ \rgroup$ | $\sigma > 0$ | 属于高斯核函数变种,对参数敏感性降低 |
Sigmoid核函数 | $K(\vec x_i, \vec x_j) = tanh\lgroup a \cdot {\vec x_i}^T \cdot \vec x_j + c)$ | $a > 0, c < 0$ | —— |
二次有理核函数 | $K(\vec x_i, \vec x_j) = 1 - \frac{\Arrowvert \vec x_i - \vec x_j \Arrowvert^2}{\Arrowvert \vec x_i - \vec x_j \Arrowvert^2 + \ c}$ | $c$ | 高斯核函数的替代品,作用域广,对参数特别敏感 |
ANOVA核函数 | $K(\vec x_i, \vec x_j) = exp \lgroup -\sigma (\vec x_i^k - \vec x_j^k)^2 \rgroup ^d$ | $\sigma > 0, d \geq 1$ | 也属于RBF核函数,适用于多维回归问题 |
多元二次核函数 | $K(\vec x_i, \vec x_j) = (\Arrowvert \vec x_i - \vec x_j \Arrowvert^2 + \ c^2 )^{0.5}$ | $c$ | 可替代二次有理核,非正定核函数 |
逆多元二次核函数 | $K(\vec x_i, \vec x_j) = (\Arrowvert \vec x_i - \vec x_j \Arrowvert^2 + \ c^2 )^{-0.5}$ | $c$ | 不会导致核相关矩阵奇异 |
对数核函数 | $K(\vec x_i, \vec x_j) = -log(1 \ + \ \Arrowvert \vec x_i - \vec x_j \Arrowvert^d)$ | $d$ | 图像分割上经常使用 |
字符串核函数 | $K(s, t) = \sum\limits_{u \in \Sigma^n} \sum\limits_{(i,j):s(i)=t(j)=u} \lambda^{l(i)} \lambda^{l(j)}$ | $n、\lambda$ | 文本分类,信息检索广泛使用 |
圆形核函数 | $K(\vec x_i, \vec x_j) = \frac{2}{\pi} arccos(- \frac{\parallel \vec x_i - \vec x_j \parallel}{\sigma}) - \frac{2}{\pi} \frac{\parallel \vec x_i - \vec x_j \parallel}{\sigma} (1 - \frac{\parallel \vec x_i - \vec x_j \parallel ^2} {\sigma})^{0.5}$ | $\sigma$ | —— |
球形核函数 | $K(\vec x_i, \vec x_j) = 1 - \frac{3}{2} \frac{\parallel \vec x_i - \vec x_j \parallel}{\sigma} + \frac{1}{2} (\frac{\parallel \vec x_i - \vec x_j \parallel ^2} {\sigma})^{3}$ | $\sigma$ | 圆形核函数的简化版 |
波动核函数 | $K(\vec x_i, \vec x_j) =\frac{\theta} {\parallel \vec x_i - \vec x_j \parallel} sin(\frac{\parallel \vec x_i - \vec x_j \parallel} {\theta})$ | $\theta$ | 适用于语音处理 |
曲线核函数 | $\begin{split} K(\vec x_i, \vec x_j) = 1 + &\vec x_i^t \cdot \vec x_j + \vec x_i^t \cdot \vec x_j \min(\vec x_i, \vec x_j) \\ - &\frac{\vec x_i + \vec x_j}{2} \min(\vec x_i, \vec x_j)^2 + \frac{1}{3} \min(\vec x_i, \vec x_j)^3 \end{split}$ | $t$ | —— |
Bessel核函数 | $K(\vec x_i, \vec x_j) = \frac{J_{v+1} (\sigma \parallel \vec x_i - \vec x_j \parallel)} {\parallel \vec x_i - \vec x_j \parallel ^{-n(v+1)}}$ | $\sigma、n$ | —— |
泛化T-Student核函数 | $K(\vec x_i, \vec x_j) = \frac{1}{1 + \parallel \vec x_i - \vec x_j \parallel ^ d}$ | $d$ | mercer核 |
参考链接:
针对非线性数据,利用核函数学习出来的分类决策模型即为非线性支持向量机。训练样本中,参与了分割超平面决策的这些我们称为非线性支持向量。
非线性支持向量机的分类决策函数表达式如下:
$$
\begin{split}
f(x) &= sign \lgroup \sum_{i=1}^N \alpha_i^* · y_i · K(\vec x_i, \vec x) + b^* \rgroup \\\\
&= sign \lgroup \underbrace{\sum_{i=1}^N \alpha_i^* · y_i · K(\vec x_i, \vec x)}_{\vec w \cdot \vec x} + \underbrace{\lgroup y_j - \sum_{i=1}^N \alpha_i^* \cdot y_i \cdot K(\vec x_i, \vec x_j)\rgroup}_{b^*} \rgroup
\end{split}
\tag{1 - 6}
$$
由上式可以看出,对于$b^*$的求解,我们也换成了核函数。同线性可分情况一样,要求解$b^*$,只需要选择一个由上式(1-5)第一个式子求解出来的满足$0 < \alpha_j^* < C$的拉格朗日乘子计算即可。
非线性分类决策函数的求解过程与线性可分情况下的求解是一样的,只不过目标函数里面相应的内积替换成了核函数。另外有一点需要特别说明的是,对于非线性支持向量机的求解,我们通常都不会去求$\vec w^*$的表达式,因为其求解需要利用映射后的样本,如下所示:
$$
\begin{split}
\vec w^* &= \sum_{i=1}^N \alpha_i^* \cdot y_i \cdot \vec x_i’ \\\\
&= \sum_{i=1}^N \alpha_i^* \cdot y_i \cdot \phi(\vec x_i)
\end{split}
\tag{1 - 7}
$$
因此我们通常都是直接利用核函数求解出式(1-6)的分类决策函数。
]]>N % M = P
模运算想必大家都不陌生了,在看《算法导论》中字符串匹配的Rabin-Karp算法的时候,里面提到了对要匹配的字符串计算出一个整型值之后,使用一个较大的素数进行模运算,并以模运算的结果作为匹配值。这里让我产生了两个疑问,第一是:取余为何非要用一个素数进行模运算呢?第二是:为何还要把这个素数取得非常大?
下面就这两点分别说明。
首先我们来看,取余实际上相当于一种映射。比如我们给定一个整数的集合$A = \lbrace a_0, a_1, a_2, …, a_i \rbrace$,我们要对该集合里面的所有元素对数字$j$取余,那么A中所有元素取余后的值的集合$B=\lbrace 0, 1, 2, …, j-1 \rbrace$,取余实际上就是将集合A映射到集合B,即$A \stackrel{f(x)}{\longrightarrow} B$,这么说比较抽象,我们举例来讲。
现取$i=12,\ j=8$,则有:
$$
A_0 = \lbrace 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 \rbrace \rbrace \\\\
{\downarrow}{f(a_i)}=a_i \ \% \ j \\\\
f(A_0)=\lbrace 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4 \rbrace \\\\
\Downarrow \\\\
\color{\red}{\lbrace 0, 1, 2, 3, 4, 5, 6, 7 \rbrace} \\\\
\Large{vs} \\\\
B_0^{j=8}=\lbrace 0, 1, 2, 3, 4, 5, 6, 7\rbrace
$$
再举个栗子,仍然取$i=12, \ j=8$,有:
$$
A_1 = \lbrace 10, 8, 12, 4, 16, 32, 20, 24, 26, 32, 34, 28 \rbrace \\\\
{\downarrow}{f(a_i)}=a_i \ \% \ j \\\\
f(A_1)=\lbrace 2, 0, 4, 4, 0, 0, 4, 0, 2, 0, 2, 4\rbrace \\\\
\Downarrow \\\\
\color{\purple}{\lbrace 0, 2, 4 \rbrace} \\\\
\Large{vs} \\\\
B_0^{j=8}=\lbrace 0, 1, 2, 3, 4, 5, 6, 7\rbrace
$$
这两个例子差别在哪呢?后面这个例子虽然映射前集合A的元素数量仍然是12个,但是映射后的元素只出现了0、2、4,也就是说集合B中的元素,有$8 - 3 = 5 > \frac{1}{2} \cdot 8 = 4$即超过一半多的元素根本就没有用上!
集合A不变,我们把模换成素数试试,取$i=12, \ j=7$,有:
$$
A_1 = \lbrace 10, 8, 12, 4, 16, 32, 20, 24, 26, 32, 34, 28 \rbrace \\\\
{\downarrow}{f(a_i)}=a_i \ \% \ j \\\\
f(A_1)=\lbrace 3, 1, 5, 4, 2, 4, 6, 3, 5, 4, 6, 0\rbrace \\\\
\Downarrow \\\\
\color{\green}{\lbrace 0, 1, 2, 3, 4, 5, 6 \rbrace} \\\\
\Large{vs} \\\\
B_1^{j=7}=\lbrace 0, 1, 2, 3, 4, 5, 6, 7\rbrace
$$
这里为了避免因模数改变过大而造成取余后的值分布范围增大,我们取了跟8最接近并且比它小的一个素数7。可以看出,模数换成素数后,原来的集合A并没有改变,但是最终映射的结果却100%的利用了新的余数空间(而新的余数空间和旧余数空间大小只相差1)。
也就是说,使用素数作为模数,可以提高余数空间的利用率!
当然,如果原集合中的元素属于均匀分布或者接近均匀分布的话(元素间的值差均匀且较小),模数用素数和非素数的差距并不大(第一个例子)。
我知道几个精心构造的例子是说服不了睿智的你的,下面上数学证明!
设有原集合$A = \lbrace a_0, a_1, a_2, …, a_i \rbrace, \ a_i \in N^0$,取$p \in A$以及模数$q(q \in N^+)$,$p、q$的最大公约数为$k(k \in N^+)$,分别记:
$$
\begin{cases}
\begin{split}
&p = k \cdot m , \quad m \in N^0\\\\
&q = k \cdot n , \quad n \in N^+
\end{split}
\end{cases}
\tag{1 - 1}
$$
再设模运算后的商为$d$,余数为$r$(即$p \ \% \ q = r$),则有:
$$
\begin{split}
p = q \cdot \color{blue}d + \color{red}r, \quad r \in N^0, \ d \in N^0
\end{split}
\tag{1 - 2}
$$
将上式(1-1)入式(1-2)有:
$$
\begin{split}
k \cdot m = k \cdot n \cdot d + r
\end{split}
\tag{1 - 3}
$$
化简后可得到余数$r$的表达式为:
$$
r = k(m - nd), \quad k \in N^+, \ m \in N^0, \ n \in N^+, \ d \in N^0
\tag{1 - 4}
$$
由$r \in N^0$可知$(m - nd) \in N^0$,即$m - nd$也是整数,我们令$(m - nd) = c$,则有:
$$
\begin{cases}
\begin{split}
&r = k \cdot c, \quad c=0,\ 1, \ 2,… \\\\
&c = m - nd
\end{split}
\end{cases}
\tag{1 - 5}
$$
当模数$q$为非素数时,若此时集合中的元素$p$为素数,则必有$k = 1$(素数和非素数的最大公约数为1),也就是说此时余数可以遍取余数空间的所有值:
$$
r = 0, \ 1, \ 2, …
$$
当模数$q$为非素数,且集合中的元素$p$也为非素数(且$q \neq q)$时,则必有$k = 2, \ 3, …$(两个非素数的最大公约数必然大于1, $p = 0$除外。),也就是说此时余数只能取余数空间中的部分值:
$$
r = 0, \ 2, \ 4, … \quad k = 2 \\\\
r = 0, \ 3, \ 6, … \quad k = 3 \\\\
…
$$
总结上述两点来说,当模数$q$取非素数时,如果原集合A中的非素数较多时,会导致大量的余数空间被浪费,这种情况的另一个不利影响是加剧了余数之间的碰撞(重叠)。
当模数$q$为素数时,不管原集合A中的元素$p$取什么值,只要有$p \neq q$,则必有$k = 1$(素数的性质),也就是说此时余数可以遍取余数空间的所有值:
$$
r = 0, \ 1, \ 2, …
$$
也就是说,只要我们的模数为素数,不管原集合A中的素数和非素数的组成情况如何,都能充分利用余数空间。这就解释了为什么我们做模运算的时候,模数要选择素数。
这个原因就比较简单了,使用大的素数,可以尽量减小余数之间的碰撞。仍以前面的例3来说,原集合大小为12,余数空间大小为7,也就是说原集合中有$12 - 7 = 5$个元素因为余数空间的限制,跟其他元素的余数发生了碰撞(重叠)。
前面我们提到了,当原集合中元素的分布属于或者接近于均匀分布的话,模数取素数和非素数差距不大,但是在我们的实际应用中均匀分布这种情况很少见,因此使用大素数作为模运算的模数往往会取得较好的效果。
最后,本文主要参考了以下博客,在此附上以供查阅,感谢他们细致的讲解:
水至清则无鱼!
前面章节中我们讨论的分割面的求解均是在数据线性可分的条件下进行的,与该条件对应的数学表达式为:
$$
y_i \lgroup {\vec w} · \vec x_i + {b} \rgroup - 1 \geq 0, \ \ i = 1,2,···,N
\tag{1 - 1}
$$
这个限制条件对应的几何含义就是:所有数据都能被准确的进行分类。这个条件对我们简化问题,抽象出数学模型非常有用,但是在面对实际业务的时候却显得非常乏力,因为现实生活中我们所处理的数据几乎都是线性不可分甚至非线性的。这个时候如果不对式(1-1)做调整(改动)的话,会造成两方面的影响:
以上问题,用一句古话总结就是:
水至清则无鱼 人至察则无徒
线性可分和线性不可分二者建立起来的模型均是线性分类模型。
数学模型、理论基础是一致的,解决的技术方案(均转换为拉格朗日对偶问题解决)也相同。
线性可分模型只能解决线性可分的问题,而线性非可分模型则可以解决所有线性问题(包含了线性可分、线性非可分)。
线性可分是基础,线性非可分是扩展。
线性可分使用硬间隔作为优化目标,线性非可分使用软间隔(引入了松弛变量、惩罚系数)作为优化目标。
为了让我们学习出的模型能够处理线性非可分数据,同时增强模型的泛化能力,我们就需要适当的放松间隔的限制条件。允许少部分数据(噪点、离群点)到分割超平面的几何间隔不等于$\frac{1}{||\vec w||}$(不在间隔边界上),甚至为负(不在本类缓冲区中)。
因此,我们针对训练集中的每一个样本点都引入一个调控参数,根据需要来调节数据点到分割超平面的距离,这个参数称为松弛变量$\xi_i \geq 0$。于是原来的不等式约束变为下式:
$$
y_i(\vec w \cdot \vec x_i + b) \geq 1 - \xi_i
\tag{1 - 2}
$$
引入松弛变量之后,线性非可分问题倒是解决了,却又引入了新的问题。松弛变量因为放宽了限制条件(允许某些点到分割超平面的距离小于1),其中一个类就很容易把另一个类的离群点(更靠近相反类)纳入自己的版图(因为这样会使得函数间隔最大,同时也能满足不等式约束条件),这样就会导致误分类。
为了解决这个问题,我们就需要对松弛变量做一些约束,具体办法是对引入的每一个松弛变量,我们都让它支付一个等值的代价(我姑且把它称作松弛代价,注意是一个非官方术语。)即$\xi _i$,同时引入一个称为惩罚参数$C(C > 0)$的约束参数,来调节对于误分类的容忍程度。
把惩罚参数、松弛代价整合到目标函数后有:
$$
\min \limits_{w,b,\xi}{\frac{1}{2} {||\vec w||}}^2 + C\sum_{i=1}^N \xi_i\\\\
\tag{1 - 3}
$$
上式中第一项的优化目标是使得间隔尽量最大,而第二项的求和式优化目标则是使得误分类的点数目尽量最少(松弛代价最小)。
很明显,对于线性非可分数据来说,因为存在数据交叉,所以间隔越大,误分类的点越多。误分类点越少,必然要求间隔越小。两者处于一种鱼和熊掌不可兼得的矛盾状态。
C作为一个调和参数,平衡着这两者的矛盾关系。C越大,误分类的惩罚越强(代价越高,越在意误分类),表示我们更偏向于学习出一个分类准确率更高的模型;C越小,误分类的惩罚越弱(代价越低,越在意),表示我们更偏向于学习出一个泛化能力更强的模型。
另外需要补充的一点是,惩罚系数C是作为超参数存在的,因此没有加到目标函数中。
最后,我们的线性非可分问题要求解的即是如下凸二次规划问题:
$$
\begin{cases}
\min \limits_{w,b,\xi}{\frac{1}{2} {||\vec w||}}^2 + C\sum_{i=1}^N \xi_i, \ C > 0\\\\
\\\\
y_i \lgroup {\vec w} · \vec x_i + {b} \rgroup - (1 - \xi_i) \geq 0, \ \ i = 1,2,···,N \\\\
\\\\
\xi_i \geq 0, \ i=1,2,\cdots,N \\\\
\end{cases}
\tag{1 - 4}
$$
我们把引入了松弛变量、惩罚参数之后的间隔,称为软间隔,它是相对于之前线性可分问题的硬间隔而言的,意思就是该间隔相对来说限制条件没那么严格。
上式(1-4)对应的问题也称为软间隔最大化问题,由软件隔最大化学习出来的SVM我们称为线性支持向量机。
需要补充说明的一点是,由硬间隔最大化学习出来的SVM我们称为线性可分支持向量机,显然,线性支持向量机包含了线性可分支持向量机,是一个更综合性的称谓。
关于线性非可分问题的存在性证明与线性可分问题类似,这里就不赘述了。
关于线性可分问题的唯一性证明,《统计学习方法》一书中一笔带过,并没有给出详细的证明过程,网上也没有找到相应的资料。目前我还没有完全证明出来,后续证明了再来补齐,这里只贴出结论:
与线性可分问题的求解思路一致,我们仍然要先利用KKT条件将原始问题转换为等价的对偶问题,然后再对转换后的对偶问题进行求解,即可得到分割超平面 。
先根据式(1-4)构造出对应的广义拉格朗日函数如下:
$$
L(\vec w, b, \vec \xi, \vec \alpha, \vec \beta) = {\frac{1}{2} {||w||}}^2 + C\sum_{i=1}^N \xi_i - { \sum_{i=1}^N \alpha_i [y_i (\vec w · \vec x_i + b) - (1 - \xi_i)]} - { \sum_{i=1}^N \beta_i \xi_i }
\tag{1 - 5}
$$
式中,$\alpha_i \geq 0, \ \beta_i \geq 0$。另外需要补充的是,构造此广义拉个朗日函数后的附加等式条件如下:
$$
\begin{split}
\begin{cases}
& y_i \lgroup {\vec w} · \vec x_i + {b} \rgroup - (1 - \xi_i) = 0 \\\\
\\\\
& \beta_i \cdot\xi_i = 0
\end{cases}
\end{split}
$$
由前面章节的论述可知,原始问题的对偶问题是其广义拉格朗日函数的极大极小问题,即$\max \limits_{\alpha} \min \limits_{\vec w, \vec b,\vec \xi} L(\vec w, \vec b,\vec \xi, \vec \alpha, \vec \beta)$。
先求极小值问题。将广义拉格朗日函数对$\vec w、b、\vec \xi$分别求偏导有:
$$
\begin{cases}
\begin{split}
&\nabla _{\vec w} L(\vec w, \vec b,\vec \xi, \vec \alpha, \vec \beta) = \vec w - \sum_{i=1}^N \alpha_i y_i \vec x_i \\\\
&\nabla _b L(\vec w, \vec b,\vec \xi, \vec \alpha, \vec \beta) = - \sum_{i=1}^N \alpha_i y_i \\\\
&\nabla _{\vec \xi} L(\vec w, \vec b,\vec \xi, \vec \alpha, \vec \beta) = \vec C - \vec \alpha - \vec \beta
\end{split}
\end{cases}
\tag{1 - 6}
$$
注意,上式中$\vec C = \underbrace{(C, C, \cdots, C)^T}_{N}$,是以惩罚系数C为基本元素的N维常数向量。
然后分别另上式(1-6)的三个偏导数等于零,有:
$$
\begin{cases}
\begin{split}
& \vec w - \sum_{i=1}^N \alpha_i y_i \vec x_i = \vec 0\\\\
& \sum_{i=1}^N \alpha_i y_i = 0\\\\
& \vec C - \vec \alpha - \vec \beta = \vec 0
\end{split}
\end{cases}
\tag{1 - 7}
$$
然后我们将上式(1-7)回代到式(1-5)的广义拉格朗日函数中,并对其进行化简,有:
$$
\begin{split}
& L(\vec w, \vec b,\vec \xi, \vec \alpha, \vec \beta)\\\\
&= \underbrace{ {\frac{1}{2} {||w||}}^2 - { \sum_{i=1}^N \alpha_i y_i (\vec w · \vec x_i + b) } + { \sum_{i=1}^N \alpha_i }}_{线性可分情况下的广义拉格朗日函数} + C\sum_{i=1}^N \xi_i - \sum_{i=1}^N \alpha_i \xi_i - \sum_{i=1}^N \beta_i \xi_i \\\\
&= \underbrace{ {\frac{1}{2} {||w||}}^2 - { \sum_{i=1}^N \alpha_i y_i (\vec w · \vec x_i + b) } + { \sum_{i=1}^N \alpha_i }}_{线性可分情况下的广义拉格朗日函数} + \sum_{i=1}^N (C - \alpha_i - \beta_i) \xi_i\\\\
&= \underbrace{ {\frac{1}{2} {||w||}}^2 - { \sum_{i=1}^N \alpha_i y_i (\vec w · \vec x_i + b) } + { \sum_{i=1}^N \alpha_i }}_{线性可分情况下的广义拉格朗日函数} + \underbrace{(\vec C - \vec \alpha - \vec \beta)}_{\vec 0} \cdot \vec \xi\\\\
&= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) + \sum_{i=1}^N \alpha_i
\end{split}
\tag{1 - 8}
$$
由上式可以看出,线性非可分情况下的广义拉个朗日函数和线性可分情况下的广义拉格朗日函数的极小值问题求解形式是等价的,差别在于两者的支持向量有所不同,后者的支持向量还包括了一些并不严格满足间隔边界要求的样本点,即有如下结果:
$$
\underbrace{\min \limits_{\vec w, \vec b} L(\vec w, \vec b, \vec \alpha)}_{线性可分极小值}= \underbrace{\min \limits_{\vec w, \vec b,\vec \xi} L(\vec w, \vec b,\vec \xi, \vec \alpha, \vec \beta)}_{线性非可分极小值}= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) + \sum_{i=1}^N \alpha_i
\tag{1 - 9}
$$
求出极小值问题后,我们再来求解线性非可分情况下的极大值,即求解下列问题:
$$
\max \limits_{\vec \alpha} [-\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) + \sum_{i=1}^N \alpha_i] \\\\
\tag{1 - 10}
$$
当然,上式的$\vec \alpha$还必须要满足如下的约束条件:
$$
\begin{split}
\begin{cases}
\sum_{i=1}^N \alpha_i y_i = 0\\\\
\\\\
\vec C - \vec \alpha - \vec \beta = \vec 0, \ C > 0 \\\\
\\\\
\alpha_i \geq 0, \ \ \beta_i \geq 0, \ i=1,2, \cdots, N
\end{cases}
\end{split}
\tag{1 - 11}
$$
因为要求对$\vec \alpha$的极大值问题,所以我们要消去上式中的$\vec \beta$,$C$是一个常数,不用理会。根据上式(1-11)的最后两个式子可求得:
$$
\begin{split}
\begin{cases}
\sum_{i=1}^N \alpha_i y_i = 0\\\\
\\\\
0 \leq \alpha_i \leq C, \ i=1,2, \cdots, N
\end{cases}
\end{split}
\tag{1 - 12}
$$
当然,我们如果保留$\beta_i$而消掉$\alpha_i$的话,可以得到关于$\beta_i$的取值范围为:$\beta_i \in [0, \ C]$。
从上式可以看出,相比于线性可分SVM($\alpha_i \in [0, \ \infty)$),线性非可分SVM的拉格朗日乘子$\alpha_i$的取值区间多了一个上限约束($\alpha_i \in [0, \ C]$),这个上限约束正好是我们的惩罚参数。
从上式(1-12)可以看出,线性非可分情况下的极大值求解与线性可分情况下的极大值求解思路是一样的,仅仅是其拉格朗日乘子多了一个上限约束而已。利用式(1-12)的约束等式(第一个式子),我们可以用$N$个朗格朗日乘子中的$N-1$个去表示剩下的一个,回代到式(1-10)中,然后分别对$N-1$个拉格朗日乘子求偏导并另其等于零,从而得到$N-1$个等式。于是我们就可以解出所有的拉格朗日乘子,即我们的$\alpha_i^*,\ i=1,2, \cdots, N$。
求出$\vec \alpha^*$后,根据式(1-7)的第一个等式,可求得我们的权重系数向量,如下:
$$
\begin{split}
\vec w^* &= \sum_{i=1}^N \alpha_i^* · y_i · \vec x_i \\\\
&= \alpha_1^* y_1 \vec x_1 + \ \alpha_2^* y_2 \vec x_2 + \ \dots + \ \alpha_N^* y_N \vec x_N
\end{split}
\tag{1 - 13}
$$
与线性可分情况类似,对于$\vec \alpha^* = 0$的点,不影响分割超平面的参数$\vec w^*$。
求出$\vec \alpha^*$后,我们再根据条件$\vec C - \vec \alpha - \vec \beta = \vec 0$可以求得$\beta_i^*$如下:
$$
\beta_i^* = C - \alpha_i^*, \ i=1,2, \cdots, N
\tag{1 - 14}
$$
求出$\beta_i^*$后,我们再根据构造广义拉个朗日函数时的条件$\beta_i^* \cdot \xi_i^* = 0$对$\xi_i^*$的解讨论如下:
1. 当$\beta_i^* = 0$时,$\xi_i^*$可以取任意值(当然还要满足其它约束条件)。
2. 当$\beta_i^* \neq 0$时,$\xi_i^* \equiv 0$,这时候只能取0。
综上,$\xi_i^*$的取值如下:
$$
\xi_i^* =
\begin{cases}
any, \ \ \beta_i^* = 0 \\\\
\\\\
0, \ \ \beta_i^* \neq 0 \\\\
\end{cases}
\tag{1 - 15}
$$
1. 与线性可分情况下的求解类似,当$\alpha_i^* = 0$时,即对应的样本点不起约束作用,此时$b^*$取任意值都能满足,即有无穷个解。对应的几何含义是:当前点属于非支持向量,不参与分割超平面参数的决策,此时无法利用拉格朗日乘子求解$b^*$。
2. 当$\alpha_i^* \neq 0 且 \alpha_i^* = C $时,有$\beta_i^* = 0$,此时$\xi_i^*$可取任意值,对应的几何含义是:当前点属于支持向量,并且位于缓冲区内,其松弛代价可根据模型需要选择任意值。此时我们仍然无法解出 $b^*$,这样的点不受惩罚参数和松弛代价的影响。
3. 当$\alpha_i^* \neq 0 且 \alpha_i^* \neq C $时(即$0 < \alpha_i^* < C)$,有$\beta_i^* \neq 0$,可知$\xi_i^* = 0$,对应的几何含义是:当前样本点在间隔边界上,严格满足最大间隔要求,因此其松弛代价为0。此时我们再根据线性非可分情况下的KKT条件得到:$y_i \lgroup {\vec w} · \vec x_i + {b} \rgroup - (1 - \xi_i) = 0$,利用该条件可求得 $b^*$:
$$
\begin{split}
b^* &= y_i(1 - \xi_i^*) - \vec w^* · \vec x_i \\\\
&= y_i(1 - \xi_i^*) - \sum_{j=1}^N \alpha_j^* · y_j · \vec x_j · \vec x_i \\\\
&= y_i - \sum_{j=1}^N \alpha_j^* · y_j · \vec x_j · \vec x_i
\end{split}
\tag{1 - 16}
$$
由上式可知,这种情况下的偏置参数$b^*$的求解方法与线性可分的时候相同。不同的地方在于,线性可分情况下求解$b^*$时,利用的是拉格朗日系数$\alpha_i \neq 0$的点,而线性非可分情况下的求解,其要求更严格,用来求解的点必须同时满足$\alpha_i \neq 0 且 \alpha_i^* \neq C $。
另外还需要注意的地方是,在1.3.2小节中我们提到了,$b^*$的值可能并不唯一,所以最终计算出来的$b^*$可能有多个值,然而根据李航老师的说法,在实际应用中,我们很难遇到这种情况,一般都只会遇到上面所说的唯一解这种。。
至此,线性非可分SVM的分割超平面参数$\vec w、b$均求解出来了,分割超平面的方程自然就可以得出了。
1.3.3.5小节中我们提到了,对求解出的拉格朗日乘子$\vec \alpha_i^* = 0$的点,它们不参与分割超平面参数$\vec w^*$的决策。同时我们观察到,$\vec w^*$的表达式中,并没有包含与松弛代价/松弛变量、惩罚参数有关的项,再结合1.3.3.8小节中$b^*$的表达式可以看出,数据集中存在的不满足最大间隔要求的这些点,并不会影响分割超平面的分布走向和形状,只影响分割超平面在整个空间中的分布位置!
线性非可分情况下的支持向量与线性可分情况下的略有不同,其支持向量不仅仅位于间隔边界上,还会存在于间隔之中,即我们本系列第三篇文章中就提到过的缓冲区中,这种情况下的支持向量如下示意图所示:
我们记支持向量到分割超平面、间隔边界的距离(绝对值)、几何间隔分别为(下同)$d_{h}、d_{b}、m_{\gamma}$,可知几个参数的计算表达式为:
$$
\begin{cases}
\begin{split}
& d_{h} = |1 - \xi_i|\\\\
\\\\
& d_{b} = \xi_i \\\\
\\\\
& m_{\gamma} = \frac{1}{||\vec w||} - \frac{\xi_i}{||\vec w||} = \frac{1 - \xi_i}{||\vec w||}
\end{split}
\end{cases}
\tag{1 - 17}
$$
备注:以上结果均由不等式(1-2)中的函数间隔$1 - \xi_i$及函数间隔与几何间隔的关系推导而来。
如上图所示,线性非可分情况下的支持向量可以分成四种情况:位于间隔边界上、位于本类的缓冲区中、位于另一个类的缓冲区中,位于另一个类的族群中(另一间隔边界之后),下面我们针对这几种情况分类讨论。
这种情况下的支持向量与我们之前讲的线性可分情况下的支持向量相同,此时的松弛代价$\xi_i \equiv 0$,其到分割超平面的距离为$\frac{1}{||\vec w||}$,上图中虚线L1、L2上的两个点就属于这种。为了方便叙述,我们给这种向量取个名字叫$强约束/标准支持向量^*$。
这些右上角带*
的名字是我们为了方便叙述所命名,仅在本系列文章通用,非官方/权威术语,后文不再说明,请知悉。
这种情况下的支持向量,不能严格满足最大间隔的约束但偏差不大,此时的松弛代价$ 0 < \xi_i \leq 1$,它到分割超平面的距离小于1,同时≥0。我们称这种支持向量为$弱偏差支持向量^*$,该类型支持向量如上图中$\xi_1、\xi_2$对应的点。
这种情况下的支持向量,不仅不能严格满足最大间隔的约束,并且其偏差较大(但仍然处于缓冲区中,没有越过对面的间隔边界),此时的松弛代价$\xi_i > 1$,这时候它到分割超平面的距离为负。我们称这种支持向量为$强偏差支持向量^*$,该类型支持向量如上图中$\xi_4$对应的点。
这种情况下的支持向量,已经越过了对面的间隔边界,跑到另一个分类的族群中去了,此时的松弛代价$\xi_i > 2$,这种样本非常容易让模型产生误分类,也是我们训练模型的时候需要重点关注的对象。我们称这种支持向量为$全偏差支持向量^*$,该类型支持向量如上图中$\xi_5、\xi_3$对应的点。
我们再来看看上述问题中提到的线性非可分支持向量机的求解的原始最优化问题,将其第一、第二项交换顺序后如下式:
$$
\min \limits_{\vec w, b, \vec \xi} C\sum_{i=1}^N \xi_i + {\frac{1}{2}{||\vec w||}}^2
\tag{2 - 1}
$$
只看其形式,很容易让我们联想到正则化(参见《统计学习方法》$P_{13} - P_{14}$),参数向量的$L_2$范数对应的正则化如下:
$$
\min \limits_{\vec w, b, \lambda} \frac{1}{N} \sum_{i=1}^N L[f(\vec x_i;\vec w), \ y_i] + \frac{\lambda}{2} ||\vec w||^2
\tag{2 - 2}
$$
这种相似并非巧合,实际上式(2-1)的第一项就是我们的经验风险,第二项就是我们的的$L_2$范数正则化项(或罚项),原始优化问题实际上就是结构风险最小化策略的实现。从而,$\xi_i$即为我们的损失函数,即有:
$$
\xi_i \sim L(Y, \ f(X))
\tag{2 - 3}
$$
然而在前面的内容中我们仅仅提到了$\xi_i$是为了解决线性非可分问题而引入的松弛变量,仅仅讨论了标准支持向量、强偏差支持向量、全偏差支持向量所对应的松弛变量得取值情况,但并没有给出计算该参数的具体公式。
我们都知道,当损失函数具有凸、连续、可导这些优秀的数学性质时,其最优化问题才必然有解,并且容易求解。于是我们自然会问,当$\xi_i$应该具备什么样的数学表达式才是一个优秀的损失函数。
注意:以下讨论均是以$L_2$范数为正则化项进行的。
在《统计学习方法》读书手札第一章中我们谈到了几种比较常见的损失函数,包括了0-1损失、平方损失、绝对损失、对数损失,但这几种损失函数除对数损失外都不属于优秀的损失函数,0-1损失非凸、非连续,平方损失非凸,绝对损失非凸、非处处可导。
除了以上提到的损失函数,另外还有几个比较优秀的,这种具有良好数学性质的损失函数我们通常称为替代损失(surrogate loss)。一个是指数损失、另一个就是SVM里面用的比较多的Hinge损失(多译作合页损失),三种损失(另附对率损失)函数的公式如下:
$$
\begin{cases}
\begin{split}
&L_{hinge}(Y, \ f(X)) = max \lbrace 0, \ 1 - [y_i - f(\vec x_i)] \rbrace \\\\
\\\\
&L_{exp}(Y, \ f(X)) = exp[-(y_i - f(\vec x_i))] = \frac{1}{e^{y_i - f(\vec x_i)}} \\\\
\\\\
&L_{log}(Y, \ f(X)) = log \lbrace 1 + exp[-(y_i - f(\vec x_i)] \rbrace = log [1 + \frac{1}{e^{y_i - f(\vec x_i)}}]
\end{split}
\end{cases}
\tag{2 - 4}
$$
注意:对数损失(logarithmic loss)与对率损失(logistic loss)实际上是同一个概念的不同叫法,网上讨论,只是具体用法上略有不同,比如采用不同的底。
以上损失函数的图像(含0-1损失)如下图所示:
单纯从数学上来讲,上面的三个损失函数都可以作为线性非可分SVM优化问题的损失函数,但合页损失更适合SVM的具体业务,考虑如下三种情况。
当样本被正确分类且该样本到分割超平面的间隔不满足最大间隔要求时,我们有$0 < y_i(\vec w \cdot \vec x_i + b) < 1$。对于该类样本,如我们前面所述,虽然正确分类了,但是仍然需要支付一个松弛代价,也即当$1 - y_i(\vec w \cdot \vec x_i + b) > 0$时,$L(Y, \ f(X)) > 0$。
当样本被正确分类且该样本到分割超平面的间隔完全满足最大间隔要求(≥1)时,我们有$1 \leq y_i(\vec w \cdot \vec x_i + b) $。对于该类样本,如我们前面所述,因为天然满足最大间隔要求,因此无需支付任何代价,也即当$1 - y_i(\vec w \cdot \vec x_i + b) \leq 0$时,$L(Y, \ f(X)) = 0$。
当样本被误分类且该样本到分割超平面的间隔为任意条件时,我们有$0 < |y_i(\vec w \cdot \vec x_i + b)| 及 y_i(\vec w \cdot \vec x_i + b) < 0$。对于该类样本,毫无疑问需要支付一个松弛代价,并且通过惩罚参数C对该类行为的惩罚力度进行调整,也即当$1 - y_i(\vec w \cdot \vec x_i + b) > 1$时,$L(Y, \ f(X)) > 0$。
综合上述三种情况,再由图(2-1)可知,几种损失函数中,只有Hinge函数更符合我们的要求。我们可以单独绘制出Hinge-Loss函数的图像如下所示:
即我们理想的SVM损失函数是Hinge-Loss函数。
综合上述三种情况可得线性非可分SVM的合页损失函数表达式为:
$$
L_{hinge}[Y, \ f(X)] =
\begin{split}
\begin{cases}
1 - y_i(\vec w \cdot \vec x_i + b), \ &y_i(\vec w \cdot \vec x_i + b) < 1\\\\
0, \ &y_i(\vec w \cdot \vec x_i + b) \geq 1
\end{cases}
\end{split}
\tag{2 - 5}
$$
为了叙述方便,我们记上述Hinge函数为$[1 - y_i(\vec w \cdot \vec x_i + b)]_{hinge}$。
从上述函数的性质我们可以看出,合页损失函数不仅仅要求样本进行正确分类,还必须要确保有足够的间隔,其损失才不会为0。相比于0-1损失函数,合页损失函数对学习更为严格。
对比式(2-1)、(2-2),再结合式(2-5)可知两个最优化问题要等价必须满足如下条件:
$$
\begin{split}
\begin{cases}
C = \frac{1}{N} \\\\
\lambda = 1 \\\\
\xi_i = [1 - y_i(\vec w \cdot \vec x_i + b)]_{hinge}
\end{cases}
\end{split}
\tag{2 - 6}
$$
则线性非可分SVM的最优化问题可表示为如下的等价最优化问题:
$$
\min \limits_{\vec w, b} \frac{1}{N} \sum_{i=1}^N [1 - y_i(\vec w \cdot \vec x_i + b)]_{hinge} + \frac{\lambda}{2} ||\vec w||^2
\tag{2 - 7}
$$
上式相较于式(2-1),少了松弛代价$\xi_i$,变成了更具体的、定量的合页损失函数,也为最优问题的求解提供了数学基础。
合页损失函数这个名字听起来怪怪的,虽然树上说明了其名称来源是因为其函数图像神似我们平常门、窗、衣柜等等上面用到的合页,但是恕我眼拙,真没看出来哪里像^–^。
]]>Deeper is better?
目标检测属于计算机视觉的范畴,它在深度学习这一概念还没有提出来之前就存在了。在没有深度神经网络之前,目标检测实现的主要方式还是基于统计或知识的方法。深度学习流行起来之后,鉴于深度神经网络的惊人表现,使得业界对目标检测的研究基本都转移到了以深度神经网络为基础的方向上。
目标检测按照使用的技术来分,大体范围上可以分成两个时代:
传统目标检测多基于统计或知识,其特征属性多依靠人工设计,检测对象也相对比较局限,以人脸、车牌为主。算法以Cascade + Harr / SVM + HOG / DPM 及其改进、优化算法为主。
传统目标检测算法的大致流程如下:
- Step1:确定滑动窗口;
- Step2:利用滑动窗口提取出候选区域;
- Step3:对候选区域进行特征提取;
- Step4:使用分类器(事先已经训练好)进行分类,判断候选区域是否包含有效目标;
- Step5:对所有包含有效目标的候选区域进行合并;
- Step6:作图,绘制出检测目标轮廓框。
上述步骤对应的流程图如下:
1 | st=>start: 确定滑动窗口 |
本系列文章主要集中于基于深度学习的目标检测
,因此关于传统目标检测的详细信息请参考相关资料,这里列出几篇我阅读过的。
对于基于知识的检测方法,其核心思想是模块化。以人脸检测为例,它将人脸划分成眼、鼻、嘴等各个模块的组合。利用先验知识来识别各个模块,同时基于各个模块之间的距离来进行人脸的识别。
基于统计的检测方法则扎根于统计学习。它将整个待检测图片看做一个像素矩阵,利用其统计特性结合一些机器学习的算法进行识别。之前虽然没有去学习过这方面的知识,但是那会儿有实验室的同学是做这个方向的,所以或多或少也了解了一些,只不过现在还能记得的就只有Fisher线性判别
这么个算法了。
鉴于深度神经网络强大的逻辑抽象能力、特征提取能力,目前的目标检测几乎都是基于深度学习框架的。基于深度学习的目标检测其检测对象更为广泛,涵盖了生活中各种各样的物体,如自行车、动物、人、车等等。
基于深度学习的目标检测又可以分为两大类:
该类算法首先对待检测的图片进行Region Proposal(候选区)提取,与滑动窗口所不同的是,Region Proposal
利用了图像中的纹理、颜色等等信息,因此其产生的候选窗口数量更少,质量更高。相对于传统的目标检测算法,这类算法并没有完全抛弃Region Proposal
这个设计理念,只不过使用相应的算法大大减少了Region Proposal
的数量。
Region Proposal
提取出候选窗口后,剩下的就是利用深度神经网络对这些候选窗口自动提取特征并进行分类。然后合并包含相同目标的区域,最后输出我们要检测的目标区域。
这类算法以RCNN | Fast-RCNN | Faster-RCNN | SPP-net | R-FCN
为代表。
我们对以上算法的特性做一个简单点的汇总,如下表所示:
算法 | 特性 | 缺点 |
---|---|---|
R-CNN | 1.每张图生成1K~2K个候选区域; 2.使用CNN自动提取特征; 3.使用SVM作为分类器; 4.使用回归器精修候选框的位置。 | 1.保留了候选区域 这一理念;2.步骤繁琐(候选+分类+回归); 3.时间及空间复杂度都较高; |
Fast-RCNN | 1.使用多任务损失函数; 2.边框回归放到CNN中训练; 3.引入 ROI pooling layer ; | 1. selective search 算法是瓶颈;2.非 end-to-end 模型;3. Region Proposal使用CPU完成; 4.不能满足实际应用需求; |
Faster-RCNN | 1.更少、质量更高的候选框; 2.Region Proposal由卷积层产生; 3.真正的end-to-end模型; 4.速度和精度均有所提升; 5.多尺度anchor; | 1. 仍然不能满足实际应用需求; 2.Region Proposal分类计算量偏大; |
SPP-Net | 1.每张图只提一次卷积层特征; 2.输入图像尺寸可变(弹性尺度); | 1.步骤繁琐; 2.只能tune全连接层; |
R-FCN | 1.使用共享卷积层; 2. Regional Proposal后面的卷积层移至共享卷积层; 3.不再对每一个ROI镜像卷积计算; | 待补充 |
相较于R-CNN
等基于Regional Proposal
的分类算法而言,以SOLO、SSD
等为代表的目标检测算法抛弃了生成Regional Proposal
的过程,直接在输出层回归出Bounding Box
的位置和类别,极大提高了检测速度。
我们对以上算法的特性做一个简单点的汇总,如下表所示:
算法名称 | 特性 | 缺点 |
---|---|---|
YOLO | 1.输入图片转换成7*7网格; 2.每个网格预测2个边框; 3.使用NMS去除冗余窗口; 4. False Positive 大幅降低。 | 1.7*7网格进行回归较为粗糙; 2.定位不太准确; 3.检测精度不高; |
SSD | 1.整合了YOLO及FAST-RCNN思想; 2.使用局部特征做位置预测; | 待补充 |
建议读者在阅读本系列之前先了解一些关于DNN
、DL
的相关知识,本文只对目标检测
这个方向的一些术语做介绍。
基于深度学习的目标检测的常用术语罗列如下:
Regional Proposal(候选区域),提取出所有可能包含识别目标的那些候选区域,相比于传统的滑动窗口而言,Regional Proposal
数量上会更少(通常1K~2K个),质量更高。
NMS(Non-maximum Suppression即非极大值抑制),是图像处理中用来消除多余(有交叉或重复)的框,找到最佳的物体检测位置的一个算法。
备注:
Anchor Box(候选框),从原始或卷积后的图片中提取出来,然后用来判断是否存在要识别的目标的小的图片块。
mAP(mean average precision即平均准度),用来度量目标检测算法效果的一个指标。
备注:
Selective Search(选择性搜索),利用颜色、纹理、尺寸、空间交叠来生成候选区域的算法,相比于传统的滑动窗口算法,Selective Search
避免了穷举所产生的海量候选区域及其所造成的低效。
备注:
IoU(Intersection-over-Union即交并比或重叠率),是用来度量候选框的有效性的一个指标,目标检测与我们单纯的回归/分类/聚类任务不同,它提取出来的候选框可能没办法与我们事先标记的完全重叠(这是最最理想的情况),实际应用中也没有必要这样苛求。
于是,我们将两个框相交部分的面积比上两个框并集面积(各自面积之和)作为评价指标,如下图所示:
计算公式为:
$$
\begin{split}
IoU &= \frac{两框交集面积} {两框并集面积} \\\\
&= \frac{S_{area(T) \bigcap area(C)}} {S_{area(T) \bigcup area(C)}}\\\\
\end{split}
\tag{ 2 - 1}
$$
ground-truth(暂无中文翻译)是指标定的真实数据。
grid-search(网格搜索)通常用来寻找最优超参数,在R-CNN
的论文中提到,其IoU阈值(这个值非常关键,直接影响mAP)的确定就是通过grid-search
完成的。
grid-search
实际上是一种暴力搜索算法,给定一组需要调参的参数的候选值,grid-search
会穷举各个参数的各种组合,然后根据预设的评分机制找到最优的组合。
scikit-learn
包中提供了这个算法,点此了解详情。
hard negative mining method(难分样本挖掘方法),根据我们已有的经验可以知道,要训练处一个非常好的模型,你必须要有适量的正样本和负样本,缺一不可。但是有些情况下我们的正样本数量远少于负样本数量(比如目标检测中包含背景的候选框数量远多于包含识别目标的数量),这个时候就需要用到hard negative mining method
,即挑选出负样本中比较有代表性的那些(而不是使用全部的负样本集)参与训练,这些代表性的负样本就称作hard negative
。
备注:
同样作为深度学习目标检测方向的新手,以我自身的情况做如下建议:
建议先学习了解一些深度学习的基础知识(比如卷积、池化等等。),能对深度神经网络的框架有个大致的了解。
通读一些关于目标检测的经典论文(比如R-CNN、Fast-RCNN、YOLO等等),这个阶段没必要去咬文嚼字,大致了解一下作者的思路,同时懂一些术语(包括但不限于上小节提到的那些)。
精读相关论文,把之前读过的论文翻出来认真阅读,仔细推敲,做到对论文的每个细节都了然于心。
找一些开源的(论文作者提供的最好)代码来读,看看别人的具体实现。这个阶段不仅可以加深理解,还可能发现一些之前读论文没有注意到的问题。
读别人千行代码不如自己写十行,这个阶段就自己多练习练习了,如果有实际项目或者大腿抱是最好不过的了。
最后,附上相关论文链接地址(成稿时均为最新,论文后续不排除会继续更新,可移步至arxiv.org搜索最新版),方便查看:
]]>Make Linux easy to use!
Ubuntu is an open source operating system for computers. It is a Linux distribution based on the Debian architecture。
版本号 | 发行时间 | 代号 | 是否LTS | 停止日期 | 备注 |
---|---|---|---|---|---|
4.10 | 2004.10.20 | Warty Warthog(多疣的疣猪) | N | 2006.04.30 | —— |
5.04 | 2005.04.08 | Hoary Hedgehog(灰白的刺猬) | N | 2006.10.31 | USB安装支持 UTF8为默认编码 |
5.10 | 2005.10.12 | Breezy Badger(活波的獾) | N | 2007.04.13 | 左上角新图标 |
*6.06 | 2006.06.01 | Dapper Drake(帅气的公鸭) | Y | 2009.07.14 | 第一个LTS版本 公司原因导致推迟发布 |
6.10 | 2006.10.26 | Edgy Eft(敏捷的蜥蜴) | N | 2008.04.25 | —— |
7.04 | 2007.04.19 | Feisty Fawn(活跃的小花鹿) | N | 2008.10.19 | 不再支持PPC架构 |
7.10 | 2007.10.18 | Gutsy Gibbon(勇敢的长臂猿) | N | 2009.04.18 | —— |
8.04 | 2008.04.24 | Hardy Heron(坚韧的苍鹭) | Y | 2011.05.12 | 第二个LTS版本 |
8.10 | 2008.10.30 | Intrepid Ibex(无畏的塔尔羊) | N | 2010.04.30 | —— |
9.04 | 2009.04.23 | Jaunty Jackalope(灵巧的鹿角兔) | N | 2010.10.23 | 首个支持ARM架构版本 |
9.10 | 2009.10.29 | Intrepid Ibex(幸运的无尾熊) | N | 2011.04.? | —— |
10.04 | 2010.04.29 | Lucid Lynx(雪地的猞猁) | N | 2015.04.30(Server) 2013.05.09(Desktop) | 启动子版本10.04.X |
10.10 | 2010.04.02 | Maverick Meerkat(特立独行的猫鼬) | N | 2012.04.10 | —— |
11.04 | 2011.04.28 | Natty Narwhal(敏捷的独角鲸) | N | 2012.10.28 | Netbook版合并到Desktop版 |
11.10 | 2011.10.13 | Oneiric Ocelot(梦幻的虎猫) | N | 2013.05.9 | Thunderbird成为默认邮件客户端 |
12.04 | 2012.04.26 | Precise Pangolin(精准的穿山甲) | Y | 2017.04.28 | Rtythmbox成为默认音乐播放器 |
12.10 | 2012.10.18 | Quantal Quetzal(量子的格查尔鸟) | N | 2014.05.16 | Webapp整合 |
13.04 | 2013.04.25 | Raring Ringtail(铆足了劲的环尾猫熊) | N | 2014.01.27 | 全新的关闭系统对话框 |
13.10 | 2013.10.17 | Saucy Salamander(活泼的蝾螈) | N | 2014.07.17 | 引入Ubuntu Touch |
14.04 | 2014.04.17 | Trusty Tahr(可靠的塔尔羊) | Y | 2019.04 | 新的锁屏风格 |
14.10 | 2014.10.23 | Utopic Unicorn(乌托邦的独角兽) | N | 2015.07.23 | Ubuntu Developer Tools Centre |
15.04 | 2015.04.23 | Vivid Vervet(活泼的长尾黑颚猴) | N | 2016.02.04 | 服务管理器切换为Systemd |
15.10 | 2015.10.22 | Wily Werewolf(老谋深算的狼人) | N | 2016.07.28 | 使用新的4.2内核 |
16.04 | 2016.04.21 | Xenial Xerus(好客的非洲地松鼠) | Y | 2021.04 | GNOME Software替代了软件中心 |
16.10 | 2016.10.13 | Yakkety Yak(喋喋不休的牦牛) | N | 2017.07.20 | Unity 8, scap格式应用 |
17.04 | 2017.04.13 | Zesty Zapus(热情的美洲林跳鼠) | N | 2018.01 | 安装不再需要swap分区 |
17.10 | 2017.10.19 | Artful Aardvark(巧妙的土豚) | N | 2018.07 | Wayland作为默认显示服务器 |
18.04 | 2018.04.26 | Bionic Beaver(仿生的海狸) | Y | 2021.04 | 待定 |
I wish life is just like a docker app, when U screwed it up, U can just start it over and nothing happened.
什么是Docker?用一句话来表述就是:Docker is the world’s leading software container platform,即Docker是一种软件容器平台。对于个人开发者来说,它允许你把你的应用程序和所需的环境一起打包,然后在其它任意一个同样支持Docker的环境下重新部署。比如你辛辛苦苦在某台电脑上开发的程序,拿到另一台上却跑不起来(缺少库、依赖等等之类的),使用Docker就不会有这样的问题了。Docker的作用可以简单粗暴的理解为ghost装机。来自Docker官方的这个图或许能给你些提示。
当然,对于运营商(Operator)以及企业(Enterprise)用户来说,其功能远超想象,作为普通使用者的我们并不关心,在此也不过多介绍,感兴趣的可以查阅官方网站。
Docker的作用有点类似虚拟机软件如VMWare、Virtualbox等,这些产品功能已经十分强大了,那为何还要重复造轮子弄个虚拟化的软件?答案就在下面的传统虚拟机和Docker的架构对比图中:
从上面的架构可以看出,相比于传统的虚拟机软件,Docker底层复用程度更高,所有的App共用一套OS,应用之间则通过所谓的Container实现隔离(解耦),而传统的VM要实现App的隔离,只能通过新增一套OS(也即重新创建一个虚拟机)实现。
Docker的优点就在于:
1. 轻量,即使在一台配置一般的笔记本上,也可以轻轻松松创建和启动许多container;
2. 高效,其创建、启动、停止等操作速度都较快,毫不逊色于VMware等软件;
3. 免费,其社区版完全免费,并且对于个人使用来说,功能完全够用;
4. 便捷,使用Docker你可以非常方便的打包、分发、部署你的应用,保证跨平台的一致性,实现持续部署和测试;
5. 云化,支持部署到云端,包括Amazon的AWS、Microsoft的Azure等等;
截止到本文成稿时(2017-08-12)为止,已支持平台如下:
桌面 | 服务器 | 云 | |
---|---|---|---|
社区版 | MacOS、Windows、 Linux(Desktop Distribution) | Windows Server、CentOS、Debian、Fedora、SLES、Ubuntu | AWS、Azure |
企业版 | 同社区版 | Windows Server、CentOS、Debian、Fedora、 Oracle Linux、RHEL、SLES、Ubuntu | 同社区版 |
从上面可以看出,就支持的平台而言,社区版相对于企业版仅仅少了两个商业化Linux操作系统RHEL、Oracle Linux的支持。
要理解Docker的东西,可能需要掌握一些基本术语:
中文 | 英文 | 解释 | 备注 |
---|---|---|---|
镜像 | Image | 可以理解为ISO文件,根据镜像我们可以创建一个实体的container | 也可以理解为面向对象编程中的类 |
容器 | Container | 可以理解为ISO文件安装好的系统 | 也可以理解为面向对象编程中的对象 |
仓库 | Repository | 存放镜像的地方,分私有和公有仓库, 里面的镜像又分为官方和非官方 | 我们可以从仓库获取(pull)或向仓库上传(push)想要的镜像 |
鉴于Docker的优良特性,它目前已被个人开发者和企业广泛使用。最初接触Docker是在公司,当时我们需要在服务器上新增一些新功能,由于服务器太过重要,不敢轻易去动,新功能的开发又需要依赖于服务器上的一些应用和环境,然后就尝试把服务器上需要的应用和环境打包,用Docker在另一台机器上构建了一个开发环境,等开发、测试都OK后,再部署到生产服务器上。
用了之后发现这个功能蛮不错的,特别对于一些搭建和配置非常繁琐的app或环境(比如分布式计算平台),可以大大节省你的工作量。像分布式计算平台这种,不仅仅配置繁琐容易出错,对硬件要求也不低,比如你想构建一个5个节点的集群,要么在一台配置非常好的服务器上建五个独立的虚拟机实现,要么买五台配置一般的普通商用PC来搭建。这两种方案,不仅仅耗资较多,而且使用上不太方便,比如我想在公司也能用这个集群,你总不能把集群背到公司去吧。当然你可以给集群买个域名、弄个公网IP啥的,到哪都可以访问,但这也意味着把你的集群直接暴露在互联网上,其安全性不言而喻,况且公网IP也不是你想弄就能弄得,什么移动、爱普的渣网就别想了,花生壳免费版只支持电信,域名也是要花钱的。
有了Docker,所有的问题都不是问题了,一台配置稍好(内存4G+,推荐8G,CPU双核+,推荐酷睿2以后的)的笔记本或者台式机就能满足你的要求,黑威够,Cluster-In-Pocket!
按本文部署所需软件列表及其依赖关系如下:
版本号 | 软件包全名 | 依赖软件或环境 | 作用 | |
---|---|---|---|---|
Win7 | X86_64 | – | 一台电脑 | 安装VMWare |
*VMware | 12 | VMware12 | Win7 X86_64 | 创建一个Ubuntu虚拟机 |
*Ubuntu | 16.04 X86_64 | ubuntu-16.04-desktop-amd64.iso | VMware12 | 给Docker提供寄生环境 |
Docker | 1.30 | docker (使用apt-get install docker安装) | Ubuntu16.04_X86_64 Kernel-4.4.0-21-generic | 分布式计算平台的基础 |
latest | – | ubuntu:latest | Docker (通过Docker pull 命令从镜像站点拉取) | 所有后续Docker镜像之母 |
*django | v9 | flat2010:djangov9 | ubuntu:latest | hadoop-base镜像的基础镜像,相比于latest镜像,安装了django、mysql等一些软件 |
hadoop-base | – | flat2010:hadoop-base | flat2010:djangov9 jdk-8u111-linux-x64.tar.gz hadoop-2.6.5.tar.gz | 集群master和slave的共同基础镜像 |
hadoop-master | – | flat2010:hadoop-master | @hadoop-base @zookeeper-3.4.10.tar.gz hbase-1.2.6-bin.tar.gz @scala-2.10.6.tgz @spark-1.6.2-bin-hadoop2.6.tgz | 创建Hadoop集群master节点,同时也是Spark Master的基础镜像 |
hadoop-slave | – | flat2010:hadoop-slave | 同上 | 创建Hadoop集群slave节点,同时也是Spark Slave的基础镜像 |
*docker-compose | 1.15.0 | docker-compose (通过pip install安装) | hadoop-slave hadoop-master python2.7(with pip installed) | 创建Spark集群master、slave节点 |
注:表格中第一列带“*”号的属于非必须的,你可以根据自己需求增删,依赖软件或环境中以@开头的软件包属于构建Spark平台要用到的或者属于非Hadoop必须的软件,也请根据自己需求定制。
表格可能不太直观,可以结合下面两个图来理解:
如果想跟着本教程一步一步安装部署,请务必先阅读下面的安装部署说明。如果觉得麻烦,可以直接拷贝相关文件,自己build镜像。当然最最省心的办法是,拷贝我已经构建好的镜像,直接运行即可,下载链接见文章末尾。
Docker的设计初衷就是为了便于隔离不同的App,实现松耦合,因此Docker镜像的构建原则基本上是一个App对应一个镜像,这样每一个Container仅仅包含一个独立的App,任何一个Container中的App出了问题都不会影响其他Container中的App。但是本教程并没有严格按照这个约束来做,而是把Hadoop、Spark、Hbase、Zookeeper整合到了一起,之所以这么做是考虑到一来本身这些软件耦合度就较高,分开部署反而不太方便(实际上Spark、Hbase的运行都要依赖Hadoop)。二来,我的主要需求是All-in-one,而并非应用之间的独立性,因此耦合性并非考虑重点。
本教程中有些安装包并非必须的,请根据自己需求进行增删。比如Zookeeper可以用Hbase自带的,而如果不需要Spark平台的话,Scala和Spark的安装包都可以不要,还能减小构建出来的镜像的大小。
集群大小根据自己机器配置来,但是有一点务必要注意,规划集群的时候,集群数量(含master)最好是奇数,而不是偶数(因为Zookeeper的leader选举算法需要根据得票数来确定leader,偶数个节点很容易导致选票持平问题)。本教程集群规划如下表:
容器名 | 主机名 | IP地址 | 节点类型 | 所属网络 |
---|---|---|---|---|
spark-master | spark-master | 192.168.3.110 | Namenode | cluster/bridge |
spark-slave1 | spark-slave1 | 192.168.3.111 | Datanode | cluster/bridge |
spark-slave2 | spark-slave2 | 192.168.3.112 | Datanode | cluster/bridge |
spark-slave3 | spark-slave3 | 192.168.3.113 | Datanode | cluster/bridge |
spark-slave4 | spark-slave4 | 192.168.3.114 | Datanode | cluster/bridge |
集群大小:5,所属网段:192.168.3.0/24。
要用集群的话,肯定需要静态IP,并且需要指明hostname,需要在启动Container时指定Container的IP地址,以及hostanme。如果使用docker-compose的话,在yml文件中使用如下参数配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14# 指定容器IP,隶属于名为cluster的网络
networks:
cluster:
ipv4_address: 192.168.3.114
# 定义名为cluster的网络
networks:
cluster:
driver: bridge //网络类型为桥接
ipam: //如果网络类型是bridge的话,需要同时定义ipam参数
driver: default
config:
-
subnet: 192.168.3.0/24 //网段是根据2.2.3节集群规划确定的
注意:如果是直接拷贝镜像或者配置文件去使用而不需要做修改的可以直接跳到安装部分。部署过程中所需要的文件,其存放目录及作用如下:1
2
3
4
5
6
7
8# 目录树图
../docker_hadoop
├── addfiles //yml配置文件中所有以ADD命令添加的文件,主要是一些软件的配置文件
├── autoscripts //一些自动化脚本,方便操作使用,包括集群环境配置,启动、停止等
├── copyfiles //yml配置文件中所有以COPY命令添加的文件,包括软件配置文件,环境初始化脚本等
├── spark_compose //spark集群启动的docker-compose配置文件
├── tgzfiles //所有后缀为.tgz、.tar.gz的安装包
└── ymlfiles //构建集群所需镜像的yml格式的配置文件
文件的树形列表会比较长,其中大部分文件都是安装Hadoop、Hbase、Zookeeper、Spark所必须配置的文件,本文不会对这些软件的安装做说明,需要的请自行百度相关资料。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61../docker_hadoop
├── addfiles
│ ├── core-site.xml //Hadoop集群的HDFS、数据目录等参数配置文件
│ ├── hadoop-env.sh //Hadoop集群的系统环境配置文件
│ ├── hdfs-site.xml //HDFS的相关参数配置文件,相对hdfs-site.xml.std增加了一些参数
│ ├── hdfs-site.xml.std //HDFS最开始配置文件
│ ├── mapred-env.sh //Map-Reduce系统环境配置文件
│ ├── mapred-site.xml //Map-Reduce端口等配置文件
│ ├── slaves //slave节点配置文件
│ ├── spark-defaults.conf //Spark启用history的配置文件
│ ├── spark-env.sh //Spark系统环境配置文件
│ ├── yarn-env.sh //Yarn系统环境配置文件
│ └── yarn-site.xml //Yarn端口等参数配置文件
├── autoscripts
│ ├── assign_static_ip.sh //使用pipwork给容器分配静态IP的脚本
│ ├── create_bridge.sh //创建桥接网络的脚本(仅在用pipwork分配网络时需要)
│ ├── initial_hadoop_all.sh //初次启动Hadoop前需要初始化的所有相关环境,包括格式化Namenode、设置Hadoop、Zookeeper的环境变量
│ ├── initial_spark_all.sh //初次启动Spark前需要初始化的所有相关环境,包括格式化Namenode、设置Spark、Hadoop、Zookeeper的环境变量
│ ├── remove_all_containers.sh //自动分析并删除与集群相关的Container脚本
│ ├── remove_cluster_images.sh //自动分析并删除与集群相关的Images脚本(使用前务必看下代码,防止误删)
│ ├── start_all_in_order.sh //按顺序启动集群(Hadoop -> Zookeeper -> HBase -> Spark)
│ ├── start_cluster_container.sh //启动所有与集群有关的Contaner
│ ├── start_hadoop.sh //仅仅启动Hadoop
│ ├── start_hbase.sh //仅仅执行启动HBase命令(不会先启动Hadoop,不推荐手动执行,请封装在start_all_in_order.sh中来执行)
│ ├── start_spark.sh //仅仅执行启动Spark命令(不会先启动Hadoop等,不推荐手动执行,请封装在start_all_in_order.sh中来执行)
│ ├── start_zookeeper.sh //仅仅执行启动Zookeeper命令(不会先启动Hadoop等,不推荐手动执行,请封装在start_all_in_order.sh中来执行)
│ ├── stop_all_container.sh //停止所有与集群有关的Contaner
│ ├── stop_all_in_order.sh //按顺序停止集群(Spark -> HBase -> Zookeeper -> Hadoop)
│ ├── stop_hadoop.sh //仅仅执行停止Hadoop命令(不会先停止HBase等,不推荐手动执行,请封装在stop_all_in_order.sh中来执行)
│ ├── stop_hbase.sh //仅仅执行停止HBase命令(不会停止Hadoop等,不推荐手动执行,请封装在stop_all_in_order.sh中来执行)
│ ├── stop_spark.sh //仅仅执行停止Spark命令(不会去停止Hadoop等,不推荐手动执行,请封装在stop_all_in_order.sh中来执行)
│ └── stop_zookeeper.sh //仅仅执行停止Zookeeper命令(不会去停止Hadoop等,不推荐手动执行,请封装在stop_all_in_order.sh中来执行)
├── build_base.sh //自动构建master和slave基础镜像脚本
├── build_master_and_server.sh //自动构建master和slave镜像脚本
├── copyfiles
│ ├── format_hadoop.sh //格式化Namenode配置文件
│ ├── hbase-env.sh //HBase系统环境配置文件
│ ├── hbase-site.xml //HBase其它参数配置文件
│ ├── hosts.bak //集群主机列表文件
│ ├──regionservers //HBase的regionservers列表
│ ├── initial_spark.sh //初始化Spark环境脚本,封装在initial_spark_all.sh、initial_hadoop_all.sh中使用
│ ├── initial_zookeeper.sh //初始化Zookeeper环境脚本,封装在initial_spark_all.sh、initial_hadoop_all.sh中使用
│ ├── master_bootstrap.sh //Master容器启动后自动执行操作,完成集群主机列表设置、开启ssh守护进程
│ ├── setenv_hadoop.sh //初始化Hadoop环境变量,封装在start_all_in_order.sh中使用
│ ├── slave_bootstrap.sh //Slave容器启动后自动执行操作,完成集群主机列表设置、开启ssh守护进程
│ ├── ssh_config //ssh的配置文件(关闭添加远程主题host需要确认功能)
│ └── zoo.cfg //Zookeeper的配置文件
├── Dockerfile
├── spark_compose
│ └── docker-compose.yml //使用docker-compose up命令启动Spark或者Hadoop集群的配置文件
├── tgzfiles
│ ├── hadoop-2.6.5.tar.gz //Hadoop2.6.5安装包
│ ├── hbase-1.2.6-bin.tar.gz //HBase1.2.6安装包
│ ├── jdk-8u111-linux-x64.tar.gz //JDK1.8.111安装包
│ ├── scala-2.10.6.tgz //Scala2.10.6安装包
│ ├── spark-1.6.2-bin-hadoop2.6.tgz //Spark1.6.2安装包
│ └── zookeeper-3.4.10.tar.gz //Zookeeper3.4.10安装包
└── ymlfiles
├── base.yml //构建基础镜像yml配置文件
├── master.yml //构建Master节点镜像配置文件
└── slave.yml //构建Slave节点镜像配置文件
在Docker上部署Hadoop完全分布式和在虚拟机或者实体机上部署其实大同小异,配置好相关文件,分发到相应的主机上,然后在Master节点上初始化和启动即可。只不过在Docker上部署时,这些工作都定义在Dockerfile
中,通过构建所谓的镜像
来实现。
先确定自己需要哪些组件,配置好2.2.5节中对应的参数文件,放到2.2.5节所述的目录下。比如你只想安装Hadoop,只需要把hdfs-site.xml、mapred-env.sh等文件拷贝进去就是。
注意:如无特别说明,本节的命令、脚本执行均是在Docker宿主机上而非容器内完成的。
先修改ymlfiles
目录下的base.yml
,增删组件,再切换到build_base.sh
脚本所在目录,并在当前目录下执行:1
2
3./build_base.sh
或者
bash build_base.sh
如果提示权限不足,请加sudo
执行。build_base.sh
代码如下:1
2
3
4
# build the base images for master and slave
cp ymlfiles/base.yml Dockerfile
sudo docker build -t="flat2010:hadoop-base" .
代码很简单,先拷贝ymlfiles
目录下的base.yml
文件到当前目录下覆盖已有的Dockerfile
,然后根据这份拷贝出来的Dockerfile
生成一个REPOSITORY:TAG
为“flat2010:hadoop-base”的基础Docker镜像。base.yml
的内容这里就不贴了,它的主要任务就是解压、拷贝相关文件,创建hadoop账户和群组(hadoop账户的初始密码设置为123456),启动ssh守护进程,设置ssh免密登录等等,网上随便搜下Docker的知识就能看懂了。注意最后一行代码的最后还有个“.”
,这可不是手误。
这里需要强调的是,该基础镜像是基于我自己修改保存的“djangov9”镜像构建的,因为加了Mysql、Django等软件,所以构建出来的基础镜像会有些偏大,如果不需要的话可以直接基于官方最新镜像“ubuntu:latest”构建。
构建结束并且提示成功的话,可以通过sudo docker images
看到刚刚构建好的基础镜像:1
2
3
4
5
6REPOSITORY TAG IMAGE ID CREATED SIZE
flat2010 hadoop-base 6b3d949f2d6a 4 days ago 1.99GB
flat2010 djangov9 9e21705da454 5 days ago 999MB
··· ··· ··· ··· ··· ···
flat2010/ubuntu v1 666037795a72 8 days ago 187MB
ubuntu latest 14f60031763d 3 weeks ago 120MB
同上,先修改ymlfiles
目录下的master.yml
、slave.yml
文件,根据自己需求增删组件,再在build_master_and_slave.sh
脚本所在目录下执行:1
2
3./build_master_and_slave.sh
或者
bash build_master_and_slave.sh
脚本会自动构建好集群所需的Master节点和Slave节点镜像。build_master_and_slave.sh
脚本代码如下:1
2
3
4
5
6
7
cp ymlfiles/slave.yml Dockerfile
sudo docker build -t="flat2010:hadoop-slave" .
cp ymlfiles/master.yml Dockerfile
sudo docker build -t="flat2010:hadoop-master" .
这部分代码也不复杂,先拷贝ymlfiles
目录下的slave.yml
为Dockerfile
,构建一个REPOSITORY:TAG
为“flat2010:hadoop-slave”的Slave节点镜像,然后拷贝ymlfiles
目录下的master.yml
为Dockerfile
,构建一个REPOSITORY:TAG
为“flat2010:hadoop-master”的Master节点镜像。master.yml
以及slave.yml
的内容这里也不贴了,其作用也无非就是解压、拷贝相关文件,更改目录权限,设置环境变量等等。唯一需要说明的一点是,文件最后一行:1
2
3
4# 文件输出截断
ADD addfiles/spark-defaults.conf /usr/local/spark-1.6.2-bin-hadoop2.6/conf/
COPY copyfiles/initial_spark.sh /home/hadoop
ENTRYPOINT ["/home/hadoop/master_bootstrap.sh"]
该行代码会在利用Image实例化的Container启动时,去执行这个脚本。这个脚本的作用在前面也备注了,无非就是设置主机名、启动ssh守护进程,其代码如下:1
2
3
sudo cp /tmp/hosts.bak /etc/hosts
sudo /usr/sbin/sshd -D
之所以单独使用一个脚本去执行这些工作,是因为最初我在Dockerfile中操作时死活不生效,主要是/etc/hosts文件无论如何不允许修改(加sudo依然),只有走曲线。
master.yml
相对于slave.yml
只是多了些修改环境变量和启动集群的脚本文件,同时slave.yml
暴露了一些Zookeeper通信需要的端口(2181、2888、3888)出来。
构建结束并且提示成功的话,同样通过sudo docker images
查看刚刚构建好的基础镜像:1
2
3
4
5
6REPOSITORY TAG IMAGE ID CREATED SIZE
flat2010 hadoop-master 9dd58e6f1180 2 days ago 3.49GB
flat2010 hadoop-slave 71d2a20b8a3e 2 days ago 3.49GB
flat2010 hadoop-base 6b3d949f2d6a 4 days ago 1.99GB
··· ··· ··· ···
ubuntu latest 14f60031763d 3 weeks ago 120MB
可以看到,由于加了较多软件包,编译生成的集群镜像非常大(约3.5G),所以强烈建议剔除一些不需要的软件。
先修改spark_compose
目录下的docker-compose.yml
,然后在该目录下执行:1
sudo docker-compose up
成功执行后,使用命令sudo docker ps
查看创建和启动起来的所有Container:1
2
3
4
5
6CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3ad515acbab3 flat2010:hadoop-master "/home/hadoop/mast..." 2 days ago Up 10 seconds 0.0.0.0:2181->2181/tcp, 0.0.0.0:4040->4040/tcp, 22/tcp, 0.0.0.0:7077->7077/tcp, 0.0.0.0:8042->8042/tcp, 0.0.0.0:8080->8080/tcp, 0.0.0.0:8088->8088/tcp, 0.0.0.0:9000-9001->9000-9001/tcp, 0.0.0.0:16010->16010/tcp, 0.0.0.0:16201->16201/tcp, 0.0.0.0:16301->16301/tcp, 0.0.0.0:18080->18080/tcp, 0.0.0.0:19888->19888/tcp, 8000/tcp, 0.0.0.0:50070->50070/tcp spark-master
7e58661fa37f flat2010:hadoop-slave "/home/hadoop/slav..." 2 days ago Up 12 seconds 22/tcp, 2181/tcp, 2888/tcp, 3888/tcp, 8000/tcp, 8080/tcp, 0.0.0.0:8071->8081/tcp spark-slave2
98a68bab400d flat2010:hadoop-slave "/home/hadoop/slav..." 2 days ago Up 12 seconds 22/tcp, 2181/tcp, 2888/tcp, 3888/tcp, 8000/tcp, 8080/tcp, 0.0.0.0:8081->8081/tcp spark-slave1
31a94a5d40f8 flat2010:hadoop-slave "/home/hadoop/slav..." 2 days ago Up 12 seconds 22/tcp, 2181/tcp, 2888/tcp, 3888/tcp, 8000/tcp, 8080/tcp, 0.0.0.0:8061->8081/tcp spark-slave3
281ada44cd48 flat2010:hadoop-slave "/home/hadoop/slav..." 2 days ago Up 12 seconds 22/tcp, 2181/tcp, 2888/tcp, 3888/tcp, 8000/tcp, 8080/tcp, 0.0.0.0:8051->8081/tcp
上面的输出结果可以看出来,Master节点映射了许多端口出来,让我们即使在宿主机外面也能访问,关于端口映射(PORTS)和端口暴露(EXPOSE)的详细说明,参见后面章节。
对于docker-compose.yml
,需要说明的有几点,首先是在启动时用参数ports
指定要映射出来的端口:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15ports:
- "8042:8042"
- "19888:19888"
- "50070:50070" //Hadoop集群的webui地址
- "8088:8088" //Spark集群的webui地址
- "9000:9000"
- "9001:9001"
- "2181:2181"
- "16010:16010" //HBase的webui地址
- "16301:16301"
- "16201:16201"
- "4040:4040"
- "8080:8080"
- "7077:7077" //提交作业需要
- "18080:18080"
其次,考虑灾备,我把一些关键数据(比如HDFS数据、HBase数据)存放在宿主机上而非Container中,这样即使包含集群的Container删除或者损毁了,集群数据并不会丢失,重新创建好集群后,还能使用之前的集群数据。通过volumes
参数,可以在容器中指定要挂载的外部目录:1
2
3volumes:
- /home/develop/hadoop_datas:/home/hadoop/hadoop_datas
- /home/develop/spark_datas:/home/hadoop/spark_datas
另外,Slave节点也映射了一个端口8081,这是为了方便查看Spark集群各个节点的信息,这里又存在一个问题,每个Container的8081都需要映射出来,但是宿主机只有一个8081端口,所以这些Container中只能有一个可以映射到原生的8081端口,剩下的只能改用其它端口。这里采取的是把8081留给slave1,剩下的slave2、slave3、slave4分别对应8071、8061、8051端口。即访问 http://宿主机:8071 就相当于访问slave1的8081端口。
进入autoscripts
目录,执行脚本initial_spark_all.sh(安装了Spark集群)
或者initial_hadoop_all.sh(安装了Hadoop集群)
初始化Container的环境,这些脚本会先登录容器,然后执行容器里面的相应脚本,以initial_hadoop_all.sh
代码来说,如下:
1 |
|
代码先是查找镜像名为flat2010:hadoop-master
的Container,获取Container ID,然后使用exec
命令进入该容器,执行容器中的format_hadoop.sh
、setenv_hadoop.sh
、initial_zookeeper.sh
脚本,执行完成后,会自动退出容器(但不会停止或销毁),完成环境初始化配置。
注意执行format_hadoop.sh脚本会格式化Namenode,所以一般是在第一次安装好Hadoop后尚未启动前调用上面两个initial_xxx脚本。
此外,第一行进入容器的代码之所以要加入参数-ti
,是因为如果不以这种交互方式执行,格式化Namenode提示输入YES/NO?
时,容器将无法获取你的输入,从而一直卡在那个提示界面。
当然如果你不嫌麻烦的话,这些初始化工作也可以先在宿主机上进入Master容器,然后手动敲代码,一条一条去执行,我比较懒,就整合到脚本里面了。
进入autoscripts
目录,执行脚本start_all_in_order.sh
来启动集群,它会按照特定顺序(Hadoop ->Zookeeper -> HBase -> Spark)来确保集群正确的被启动。其代码如下:1
2
3
4
5
6
7
8
9
10
11
12
# start hadoop first
./start_hadoop.sh
# start zookeeper
./start_zookeeper.sh
# start hbase
./start_hbase.sh
# start spark
./start_spark.sh
从代码来看,实际上是按先后顺序分别去调用了几个启动脚本。一般情况下,不推荐单独去执行这几个脚本中的某个。如果没有安装相应的软件的话,注掉就可以了。
执行脚本时,会有一些提示信息,比如Zookeeper选中了哪个节点作为leader
,Worker的启动情况等等,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 输出截断
···
==========================================================================
==========================================================================
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper-3.4.10/bin/../conf/zoo.cfg
inetaddr:192.168.3.112
inetaddr:127.0.0.1
Mode: leader
==========================================================================
==========================================================================
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper-3.4.10/bin/../conf/zoo.cfg
inetaddr:192.168.3.111
inetaddr:127.0.0.1
Mode: follower
==========================================================================
==========================================================================
···
# 输出截断
启动完成后,可以在Docker宿主机或者VMWare宿主机上查看相应的集群信息,比如在访问 http://hadoop-master:50070 即可查看Hadoop集群信息,如下图2 -1 、2 - 2所示:
访问 http://hadoop-master:8080 即可查看Spark集群信息,如下图2 -3 、2 - 4所示:
访问 http://hadoop-master:16010 即可查看HBase信息,如下图2 -5 、2 - 6所示:
至此,集群部署完成!
为了方便集群、Docker容器的管理,编写了一些批量自动化操作脚本,这些脚本都位于autoscripts
目录下。脚本功能参见前面的2.2节部署说明。
进入autoscripts
目录,执行脚本stop_all_in_order.sh
来启动集群,它会按照特定顺序(HBase ->Zookeeper -> Spark -> Hadoop)来确保集群正确的被关闭。其代码如下:
1 |
|
同样的,一般情况下,不推荐单独去执行这几个脚本中的某个。执行脚本时,也会有相应的提示信息输出。
要停止容器,有几种方式:
1. 进入autoscripts
目录,执行脚本stop_all_container.sh
,该脚本会停止Hadoop和Spark集群相关的所有容器;
2. 进入spark_compose
目录,执行命令sudo docker-compose stop
,推荐使用该命令;
3. 进入spark_compose
目录,执行命令sudo docker-compose down
,该命令会停止和删除所有用yml文件编排启动的容器,谨慎使用!
要重启容器,有几种方式:
1. 进入autoscripts
目录,执行脚本start_cluster_container.sh
,该脚本会重启Hadoop和Spark集群相关的所有容器;
2. 进入spark_compose
目录,执行命令sudo docker-compose start
,推荐使用该命令,但该命令只能启动已存在的容器,无法新建;
3. 进入spark_compose
目录,执行命令sudo docker-compose up
,创建(不存在、镜像有更新、被依赖容器重建)或启动(已有)容器,在启动容器前,会检查之前的容器,确保容器的唯一性(CONTAINER ID
不变),并且会把旧容器的数据拷贝出来;
要删除容器,有几种方式:
1. 进入spark_compose
目录,执行命令sudo docker-compose down
,该命令会停止和删除所有用yml文件编排启动的容器,谨慎使用!
2. 进入autoscripts
目录,执行脚本remove_all_containers.sh
,该脚本会删除所有容器(不管是与集群有关还是无关),谨慎使用!
3. 执行sudo docker rm Name/ID
命令,该命令会删除指定名字或ID的容器,推荐!
进入spark_compose
目录,执行脚本remove_cluster_images.sh
,会删除TAG
为hadoop-master和hadoop-slave的这两个Docker镜像。
关于如何在Docker上部署Hadoop,网上有一些资料,但是都比较零散,并且我的需求跟大多数都不太一样,所以在折腾过程中还是填了不少坑,特此记录。
按照Hadoop安装教程指导,配置SSH远程免密登陆后,在启动集群时发现虽然不需要输入密码,但是每次启动集群、每登录一个节点都会提示有新的主机,是否添加公钥到信任列表,如下:1
2
3The authenticity of host 'spark-slave (192.168.3.111)' can't be established.
ECDSA key fingerprint is SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
Are you sure you want to continue connecting (yes/no)?
每次都要手动确认,比较烦,所以就修改了ssh的相关配置文件,关闭了这个确认交互,改动的文件为ssh_config(该文件位于/etc/ssh目录下
,在该文件中加入了两行代码:1
2StrictHostKeyChecking no
UserKnownHostsFile /dev/null
这样每次登陆远程主机就不会要求输入yes
来确认了。但是请务必注意,因为不做公钥检查,所以存在较大安全隐患,比如中间人攻击等等。
最开始我并没有使用docker-compose来设定集群IP,就用到了brctl、pipwork
工具来创建和管理桥接网络,后来使用docker-compose构建spark集群时,定义的静态网络与用brctl工具创建的网络因为网段相同而引发冲突,导致集群无法正常访问。解决办法是更改网段,或者删除brctl
创建的网络即可。
创建hadoop账户时,之前没有指定默认shell类型,远程登陆仅容器后只有一个提示符$,执行命令:1
$ echo $SHELL
可以看到当前的shell类型是/bin/sh
而不是/bin/bash
,使用chsh
改变shell类型提示不能更改。解决办法是在构建基础镜像hadoop-base时,指定shell类型,原来的base.yml
中创建用户代码为:1
RUN useradd -m hadoop -g hadoop -p 123456
更改后为:1
RUN useradd -m hadoop -g hadoop -p 123456 -s /bin/bash
Docker是没有自带Docker-compose命令的,需要先安装这个软件。Docker-compose的安装有多种方法,可以使用curl
,也可以使用pip
,我是用的pip
安装的,安装好后版本号为docker-compose version 1.15.0, build e12f3b9
,在Docker的宿主机上安装Docker-compose需要安装python及pip,执行命令如下:1
2
3
4$ sudo apt-get install python-pip python-dev build-essential
$ sudo pip install --upgrade pip
$ sudo pip install --upgrade virtualenv
$ sudo pip install docker-compose
Spark的4040网址只有在任务执行期间才能查看,要在任务结束后仍然可以查看,需要配置spark-history。
shell中按照./xxx.sh 或 bash xx.sh
方式运行脚本配置系统环境变量时(特别是一些命令别名alias)可能不生效,改用命令source xxx.sh
即可。
最开始用Docker-compose配置静态IP时,找了好多资料,都没有讲如何给容器创建指定网络并分配指定网段,有也是语焉不详,后来还是去官网Docs
查到的,所以当百度或谷歌搜不到你要的资料时,不妨耐下性子啃啃官方英文文档,或许会有意想不到的收获。
白天工作,晚上深夜加班赶稿,修修改改,缝缝补补,用了一周多才写完。也只有这样才不会有时间去想一些事吧!
由于时间仓促,所以一些自动化脚本写的比较粗糙,镜像规划也不是特别好,实际上Master和Slave节点可以共用一套Image,完全不需要为了几个小文件搞成单独的两套。后期优化和完善的地方有:
通过参数EXPOSE
指定的端口称为暴露端口,这样的端口只能在容器之间相互访问时使用,在宿主机上式无法访问的。而通过参数PORT
指定的端口称为映射端口,是允许在任意地方(比如宿主机)访问的。
http://stark-summer.iteye.com/blog/2218995 Spark History安装配置
https://docs.docker.com/ Docker官方文档网站
Lagrange multiplier.
生活中总是存在着各种各样的最优化问题,比如出行的时候,不坐地铁的情况下(地铁贵啊!)如何坐车时间最省。工作上时间一定的情况下,如何安排任务能最快的完成。这些问题就是数学里面的约束最优化问题,按照约束类型和约束条件的不同,可以分成以下三大类:
1、零约束(无约束)最优化;
$$
\min \limits_{} { f(x) } \ \ or \ \ \max \limits_{} { f(x) }
$$
2、一个及以上等式约束最优化(其中$h_i(x)$表示第i个约束条件);
$$
\min \limits_{} { f(x) } \ \ or \ \ \max \limits_{} { f(x) } \\\\
s.t \ \ h_i(x) = 0,i = 1, 2, ···, n
$$
3、一个及以上不等式约束最优化(同时还可以有等式约束);
$$
\min \limits_{} { f(x) } \ \ or \ \ \max \limits_{} { f(x) } \\\\
\begin{split}
\textbf{s.t}\ \ h_i(x) & \geq 0(或h_i(x) \leq 0),i = 1,2,···,n \\\\
g_j(x) &= 0,j = 0,1,···,m
\end{split}
$$
为了叙述方便,我们仅对两种最优化问题(最大或最小)中的一种进行探讨,另一种其证明过程和原理是完全相同的,如无特殊说明,下文中的最优化问题都是指对目标函数求最小值,即:
$$
\min \limits_{} { f(x) }
$$
针对以上三种优化问题,对应的求解方法如下:
对于第一种情况(零约束优化),高数里面我们就学过,通过求解函数的偏导数(partial derivative)并令其为零,即对函数$f(x_1, x_2, …, x_n)$,分别另$\frac{\partial f}{\partial x_i} = 0$即可求得函数的所有极值点,再结合Fermat引理:
即可求出最值。
对于第二种情况(带等式约束优化),实际上在高中的时候我们就学过,通过增加拉格朗日系数,构造出拉格朗日函数,并对各个变量求偏导后令其为零并结合约束方程联解方程组,从而求解出各个变量的候选解集,最后回代并验证这些解集,最后求出最优值。这种方法就是广为人知的拉格朗日乘子法(Lagrange Multiplier)!
举例来说,对含$m$个等式约束$h_i(x) = 0,i = 1, 2, ···, m$的$n$元目标函数$f(x_1, x_2, …, x_n)$,其拉格朗日函数为:
$$
L(x,\alpha) = f(x) + { \sum_{i=1}^m \alpha_i h_i(x) }
$$
对$n$个优化变量$x_i$分别求偏导后等于零的等式以及原约束条件等式组成的方程组为:
$$
\begin{cases}
\begin{split}
\frac {\partial L} {\partial x_1} = 0 \\\\
··· \ ··· \\\\
\frac {\partial L} {\partial x_n} = 0 \\\\
h_1(x) = 0 \\\\
··· \ ···\\\\
h_m(x) = 0 \\\\
\end{split}
\end{cases}
$$
上述方程组一共有$m + n$个待求解变量($m$个$\alpha_i$,$n$个$x_i$),也有$m + n$个方程,于是便能解出$\alpha_i、x_i$,也就求出了所有可能的极值点。
实际上,含等式约束的最优化问题可以转换为不含等式约束的最优化问题。因为约束条件$h_i(x)=0$等于拉格朗日函数$L(x,\alpha)$对第$i$个拉格朗日乘子$\alpha_i$的偏导数!即有:
$$
h_i(x) = \frac {\partial L(x, \alpha)} {\partial \alpha_i}
$$
我们把拉格朗日乘子扩展为目标函数的优化变量:
$$
f(x_1, x_2, …, x_n) \ ==> \ f(x_1, x_2, …, x_n, \alpha_1, …, \alpha_m)
$$
这样等式约束的优化问题的求解就和1.3.1
节中不带等式约束的优化问题一模一样了!带不等式约束的优化问题同样可以转换为不带约束的优化问题,这个下面会讲。
有时候只需要稍稍改变一下视角,你就能看到一个全新的世界。
对于第三种情况(带不等式约束优化),可能大多数非数学专业的同学都没有接触过,需要结合KKT(Karush–Kuhn–Tucker conditions)条件(KKT Conditions)来求解,这里就涉及到一个新的概念——KKT条件。为了更方便排版叙述,下面新开一个小节来说说这个KKT条件。
KKT条件的全称是Karush–Kuhn–Tucker conditions(也称Kuhn–Tucker conditions),多应用于数学中的优化问题(非线性最优化)。
KKT条件的作用就在于:它给出了判断优化问题中某个解$x^*$是否为最优解的必要条件!
考虑如下的非线性(带不等式约束)最优化问题:
$$
\begin{split}
目标&函数:f(x) \\\\
约束&条件: \\\\
\ \ &g_i(x) \leq 0 \\\\
\ \ &h_j(x) = 0
\end{split}
$$
其最优解$x^*$,其必然满足如下的必要条件:
KKT条件的Stationarity(即平稳性)从最大化和最小化两个优化方向来说,对应如下的式子:
$$
\begin{cases}
最大化f(x):\nabla f(x^*) = { \sum_{i=1}^m \mu_i \nabla g_i(x^*) + \sum_{j=1}^l \lambda_j \nabla h_j(x^*) } \\\\
\\\\
\ 最小化f(x):-\nabla f(x^*) = { \sum_{i=1}^m \mu_i \nabla g_i(x^*) + \sum_{j=1}^l \lambda_j \nabla h_j(x^*) } \\\\
\tag{1 - 1}
\end{cases}
$$
KKT条件的Primal feasibility(原始可行性)是指原始问题的约束条件:
$$
\begin{cases}
\begin{split}
g_i(x^*) & \leq 0,i = 1,···,m\\\\
h_j(x^*) &= 0,j = 1,···,l
\end{split}
\tag{1 - 2}
\end{cases}
$$
KKT条件的Dual feasibility(对偶可行性)是指不等式约束条件的拉格朗日乘数应满足条件:
$$
\mu_i \geq 0 ,i = 1,···,m
\tag{1 - 3}
$$
KKT条件的Complementary slackness(互补松弛)是指如下条件:
$$
\mu_i · g_i(x^*) = 0 ,i = 1,···,m
\tag{1 - 4}
$$
上面的$\mu_i、\lambda_j$均称为拉格朗日乘子,当KKT条件中的$m=0$时,即退化为拉格朗日条件。
将上述条件整合起来(求最小化)就有KKT条件的定义:
$$
\begin{cases}
\begin{split}
-\nabla f(x) &= { \sum_{i=1}^m \mu_i \nabla g_i(x) + \sum_{j=1}^l \lambda_j \nabla h_j(x) } \\\\
\mu_i · g_i(x) &= 0 ,i = 1,···,m \\\\
g_i(x) & \leq 0,i = 1, ···, m\\\\
h_j(x) &= 0,j = 1, ···, l \\\\
\mu_i & \geq 0 ,i = 1, ···, m
\end{split}
\end{cases}
$$
注意:KKT条件中对等式约束对应的拉格朗日乘子$\lambda _j$并没有非负性要求!
为了在KKT条件下利用拉格朗日乘子求出最优值,我们需要构造出广义拉格朗日函数(generalized Lagrange function),以上述优化问题为例,其对应的广义拉格朗日函数为:
$$
L(x,\alpha,\beta) = f(x) + { \sum_{i=1}^k \alpha_i g_i(x) } + { \sum_{j=1}^l \beta_j h_j(x) }
\tag{1 - 5}
$$
式中:
$$
\begin{cases}
\begin{split}
&& \alpha_i,\beta_j是拉格朗日乘子; \\\\
&& \alpha_i \geq 0;
\end{split}
\end{cases}
$$
可以证明(见后文相关部分),若原始问题和对偶问题均存在解,则它们的解是等价的(即有相同的解),上述最优化问题(称为原始最优化问题或原始问题等价于如下的广义拉格朗日函数的极小极大问题:
$$
\min \limits_{x} {\theta_P(x)} = \min \limits_{x} {\max \limits_{\alpha , \beta : \alpha_i \geq 0} { L(x , \alpha , \beta) } }
\tag{ 1 - 6}
$$
这样就把原始最优化问题转换为了广义拉格朗日函数的极小极大问题。同样为了方便叙述,定义原始最优化问题的解为$p^*$,即有:
$$
p^* = \min \limits_{x} {\theta_P(x)} = \min \limits_{x} {\max \limits_{\alpha , \beta : \alpha_i \geq 0} { L(x , \alpha , \beta) } }
\tag{ 1 - 7}
$$
我们记如下广义拉格朗日函数的极大极大小问题(注意极大、极小两个词语的顺序和1.4节的不同)为上述最优化问题的对偶问题:
$$
\max \limits_{\alpha , \beta , \alpha_i \geq 0} {\theta_D(\alpha , \beta)} = \max \limits_{\alpha , \beta , \alpha_i \geq 0} {\min \limits_{x} { L(x , \alpha , \beta) } }
\tag{ 1 - 8}
$$
同时,记上述对偶问题的解为$d^*$,即有:
$$
d^* = \max \limits_{\alpha , \beta , \alpha_i \geq 0} {\theta_D(\alpha , \beta)} = \max \limits_{\alpha , \beta , \alpha_i \geq 0} {\min \limits_{x} { L(x , \alpha , \beta) } }
\tag{ 1 - 9}
$$
可以证明(见后文相关部分),若原始问题和对偶问题均存在解,则它们的解是等价的,即有:
$$
p^* = \min \limits_{x} {\theta_P(x)} = \max \limits_{\alpha , \beta , \alpha_i \geq 0} {\theta_D(\alpha , \beta)} = d^*
\tag{ 1- 10}
$$
第1节中我们提到了,KKT条件是用来求解带不等式约束的最优化问题的。那不等式约束的优化问题是如何演变出KKT条件的呢?还是先给出原始问题:
$$
\begin{split}
目标&函数:f(x) \\\\
约束&条件: \\\\
\ \ &g_i(x) \leq 0 \\\\
\ \ &h_j(x) = 0
\end{split}
$$
因为拉格朗日乘子法只适用于无约束或等式约束的情况,所以对带不等式约束的优化问题如果要应用拉格朗日乘子法,我们就得想办法把不等式转换为等式,转换的方法是引入额外的变量,平衡不等式。
因为$g_i(x) \leq 0$,所以我们只需要引入一个恒非负的变量$s_i$(称之为松弛变量),这里还有个小技巧,为了避免引入额外的约束,引入一个具有天然非负特性的项是比较理想的选择,这里我们使用$s^2_i,s_i \in R$来实现。
引入新变量后,我们有:
$$
g’_i(x, s_i) = g_i(x) + s^2_i= 0
$$
由于对任意的$g_i(x) \leq 0$,必能找到对应的$s_i$使上式成立,因此原不等式约束和新的等式约束的作用是完全等价的,即有:
$$
g_i(x) \leq 0 \ \ <==> \ \ g’_i(x, s_i) = 0
$$
于是原始不等式约束优化问题可以转化为如下的等式约束问题:
$$
\begin{split}
目标&函数:f(x) \\\\
约束&条件: \\\\
\ \ &g’_i(x, s_i) = 0 \\\\
\ \ &h_j(x) = 0
\end{split}
$$
这个时候就可以使用拉格朗日乘子法构造拉格朗日函数了:
$$
L(x,s,\alpha,\beta) = f(x) + { \sum_{i=1}^k \alpha_i g’_i(x, s_i) } + { \sum_{j=1}^l \beta_j h_j(x) }
\tag{2 - 1}
$$
再按照求约束问题的极值的思路,我们有如下方程组:
$$
\begin{cases}
\begin{split}
\frac {\partial L} {\partial x_i} &= \frac {\partial f} {\partial x_i} + { \sum_{i=1}^k \alpha_i \frac {\partial g’_i(x, s_i)} {\partial x_i} } + { \sum_{j=1}^l \beta_j \frac {\partial h_j(x)} {\partial x_i} } \\\\
\
&= \frac {\partial f} {\partial x_i} + { \sum_{i=1}^k \alpha_i \frac {\partial g_i(x)} {\partial x_i} } + { \sum_{j=1}^l \beta_j \frac {\partial h_j(x)} {\partial x_i} } = 0\\\\
\
\frac {\partial L} {\partial \alpha_i} &= g’_i(x, s_i) = g_i(x) + s^2_i = 0\\\\
\
\frac {\partial L} {\partial \beta_i} &= h_i(x) = 0 \\\\\
\
\frac {\partial L} {\partial s_i} &= \alpha_i \frac {\partial g’_i(x, s_i)} {\partial s_i} = 2 \alpha_i · s_i = 0\\\\\
\end{split}
\tag{2 - 2}
\end{cases}
$$
利用最后一行的等式来简化上述方程组:
因为:
$$
2 \alpha_i · s_i = 0 \\\\
$$
所以我们有:
1.$\alpha_i =0, \ s_i \neq 0 \ ==> \ g_i(x) < 0(约束条件不作用于目标函数)$;
2.$\alpha_i \neq 0, \ s_i = 0 \ ==> \ g_i(x) = 0(约束条件作用于目标函数)$;
3.$\alpha_i =0, \ s_i = 0 \ ==> \ g_i(x) = 0(约束条件不作用于目标函数)$;
整合三种情况可得到:
$$
\alpha_i · g_i(x) = 0
$$
$\alpha_i \geq 0$的本质是将移动方向的点的梯度向量限制在由约束条件的梯度向量形成的锥角范围内。这部分的证明稍长,也比较难,下面专门用一小节来证明。
要证明$\alpha_i \geq 0$,需要结合图形来说明。要理解这部分,需要熟悉函数等高线、等值面的含义,这些知识都比较基础,就不多说了,详情可参考相数学教材和资料。
梯度是微积分里面多元函数对各个变量求偏导后的表达形式,比如二元函数$f(x,y)的梯度为:grad(x,y)或者\nabla(x,y) = (\frac {\partial f}{\partial x} , \frac {\partial f} {\partial y})$,梯度的几何意义是:函数增长最快的地方。
梯度向量就是由函数对各个变量求偏导后的值组成的向量,沿着梯度向量方向$\nabla f(x_1, x_2, … , x_n)$因为函数增加的最快(山势更陡峭),所以更容易到达函数的最大值处;相反,沿着梯度向量的反方向$-\nabla f(x_1, x_2, … , x_n)$函数增加的最慢(山势更平缓),则更容易到达函数的极小值处。
梯度向量的方向由高值等高线指向低值等高线(即由地势低的地方指向地势高的地方)。
对任意函数$f(x)$,其梯度向量可表示为:
$$
\nabla f(x) = (\frac {\partial f}{\partial x_1} , \frac {\partial f} {\partial x_2}, ···, \frac {\partial f}{\partial x_k}) ,k \in N^+
\tag{2 - 2}
$$
以三维坐标系来说,将函数$f(x)$投影到$XOY$平面上(为方便叙述,我们称之为目标区域),并作出其等高线、等高线上某点的梯度如下图所示:
对约束交叉区域包含全局最优的情况(如图2-2所示),这个时候约束相当于不起作用,直接按优化问题的第一种(无约束)求解目标函数$f(x)$的最小值即可。这个时候$\alpha_i = 0$,求出的最优值属于全局最优!
这种情况目标函数和约束函数形状如下图所示(纯手绘,凑合看):
约束函数映射到$XOY$平面后的图形如下图所示:
通常情况下,我们的约束区域就像上图2-5一样,是不规则的,但为了方便绘图和讨论,我们假设约束区域都是规则的圆形区域。
约束区域内任意点处的约束函数梯度向量、目标函数梯度向量方向如下图所示:
可以看到,这种情况下约束区域任意点的梯度向量是由内向外,因为内部的点对应的目标函数值比外部的点小。由图可知,此时的最优解(图中红色大圆点)其所在目标函数梯度向量反方向($-\nabla f(x)$)与约束函数梯度向量正方向同向。沿着约束函数梯度向量正方向($\alpha · \nabla g_i(x) \ , \ \alpha \in R^+$)移动的话,会越来越接近最小值处,也即有:
$$
-\nabla f(x) = \alpha_i · \nabla g_i(x) \ , \ \alpha_i > 0
$$
上面只画了含一个不等式约束的情况,多个不等式约束的情况同理。综合上述两种情况即有$\alpha_i \geq 0$,这就是KKT条件中不等式约束对应的拉格朗日乘子大于零的来源。
这里再重新贴一下结论:
$$
d^* = \max \limits_{\alpha , \beta: \alpha_i \geq 0} \min \limits_{x} { L(x , \alpha , \beta) } \leq \min \limits_{x} {\max \limits_{\alpha , \beta : \alpha_i \geq 0} { L(x , \alpha , \beta) } } = p^*
\tag{ 2 - 3}
$$
对任意的$\alpha、\beta、x$,有:
$$
{\theta_D(\alpha , \beta)} = \min \limits_{x} { L(x , \alpha , \beta) } \leq L(x , \alpha , \beta) \leq {\max \limits_{\alpha , \beta : \alpha_i \geq 0} { L(x , \alpha , \beta) } } = {\theta_p(x)}
\tag{2 - 4}
$$
上式很简单,一个函数的最小值肯定是小于或等于其最大值的,不需要太多说明。因此我们有:
$$
{\theta_D(\alpha , \beta)} \leq {\theta_p(x)}
\tag{2 - 5}
$$
又因为原始问题${\theta_p(x)}$和对偶问题${\theta_D(\alpha , \beta)}$均有解,即它们对应的函数均存在最优值,所以:
$$
{\max \limits_{\alpha , \beta : \alpha_i \geq 0} } {\theta_D(\alpha , \beta)} \leq \min \limits_{x} {\theta_p(x)}
\tag{2 - 6}
$$
即有欲证的结论:
$$
d^* = \max \limits_{\alpha , \beta: \alpha_i \geq 0} \min \limits_{x} { L(x , \alpha , \beta) } \leq \min \limits_{x} {\max \limits_{\alpha , \beta : \alpha_i \geq 0} { L(x , \alpha , \beta) } } = p^*
\tag{ 2 - 7}
$$
证毕!
当原始问题中的函数$f(x)、g_i(x)$为凸函数,$h_j(x)$为仿射函数,且原始问题的约束$g_i(x)$是严格可行(即存在$x$,使得对所有$i$均有$g_i(x) < 0$),那么必然存在原始问题解$x^*$、对偶问题解$\alpha^*、\beta^*$使得上式(2-7)取等号,即:
$$
p^* = d^* = { L(x , \alpha , \beta) }
\tag{ 2 - 8}
$$
由上述论证可知,当满足一些特定的条件时,带不等式约束的最优化问题,可以通过转化为其拉格朗日对偶问题来求解。优化问题本身已经演变成了一门独立的学科,它涉及非常多的知识点,仅凭只言片语是没办法道出其精髓的,更多的公式证明请参阅相关资料。
]]>“你听过的最有哲理,也是你最喜欢的一句话是什么?”,他问。
“我也不知道…”,她淡淡的说道。
在本系列的第(3)篇中,我们证明了分割超平面的存在性和唯一性,这章我们来看看如何求解这个分割超平面。我们先来看看要求解的最优化问题,如下式:
$$
\begin{cases}
\min \limits_{w,b}{\frac{1}{2} {||w||}}^2\\\\
y_i \lgroup {\vec w} · \vec x_i + {b} \rgroup - 1 \geq 0, \ \ i = 1,2,···,N
\end{cases}
\tag{*2 - 14}
$$
为了求解该凸优化问题,先构造出其广义拉格朗日函数(见本博客“拉格朗日对偶问题”专栏),如下式1-1:
$$
L(\vec w, \vec \alpha, b) = {\frac{1}{2} {||w||}}^2 - { \sum_{i=1}^N \alpha_i y_i (\vec w · \vec x_i + b) } + { \sum_{i=1}^N \alpha_i }
\tag{1 - 1}
$$
这里有一点要特别说明:上式中的第二项表明,每一个输入样本都可以成为一个不等式(或等式)约束,每一个样本对应一个唯一的拉格朗日乘子$\alpha_i$,请务必牢记这一点,它将有助于你理解下文。
由拉格朗日对偶问题(见本博客“拉格朗日对偶问题”专栏)可知,原始问题的解转化为如下问题的解:
$$
\max \limits_{\vec \alpha} \min \limits_{w,b}{L(\vec w, \vec \alpha, b)}
\tag{1 - 2}
$$
欲求解该问题,要先求解$L(\vec w, \vec \alpha, b)$对$\vec w, b$的极小值,再求对$\vec \alpha$的极大值。
要求$\min \limits_{w,b}{L(\vec w, \vec \alpha, b)}$,利用高数的知识,只需要分别对$\vec w, b$求偏导并另其等于零即可求出极小值情况下$\vec w, b, \vec \alpha$这些未知变量需要满足的约束条件,即要求解如下两个方程:
$$
\nabla _ \vec w L(\vec w, \vec \alpha, b) = 0
\tag{1 - 3}
$$
$$
\nabla _ b L(\vec w, \vec \alpha, b) = 0
\tag{1 - 4}
$$
网上几乎所有资料在讲解上式(1 - 3)、(1 - 4)的时候,都是直接给出一个结果,并没有给出详细的证明和求解过程,大多数人在看的时候也是一眼带过,似乎认为得出这种结果是理所当然的,然而数学中从来就没有理所当然。
我们来看看式(1 - 3),式中要求的偏导是对一个向量$\vec w$,注意,$\vec w$是一个向量,是我们的权重系数向量$\vec w = (w_1, \ w_2, \ \dots)$,不是一个标量(Scalar)!我们在高数里面学的函数的极值问题求解,是针对某个标量而言的,非数学专业的同学可以回想下,你们大学学过函数对向量的偏导吗,没有吧?所以我们不能简单的把$\frac{1}{2} ||\vec w||^2$对向量$\vec w$的偏导按照对标量$w$的偏导的求解方法来做。
设输入的维度为$M$,则我们有权重向量$\vec w = (w_1, \ w_2, \ \dots, \ w_M)$,则:
$$
\frac{1}{2} ||\vec w||^2 = \vec w · \vec w = w_1^2 + w_2^2 + \dots + w_M^2
$$
于是有广义拉格朗日函数(1 - 1)第一项$\frac{1}{2} ||\vec w||^2$对向量$\vec w$的偏导如下(标量对向量的偏导的求解参见本博客的“概念定义杂记”专栏):
$$
\begin{split}
\frac {\partial(\frac{1}{2} ||\vec w||^2)} {\partial \vec w} &= (\frac {\partial(\frac{1}{2} ||\vec w||^2)} {\partial w_1}, \ \frac {\partial(\frac{1}{2} ||\vec w||^2)} {\partial w_2}, \ \dots, \ \frac {\partial(\frac{1}{2} ||\vec w||^2)} {\partial w_M}) \\\\
&= (\frac{1}{2} · 2w_1, \ \frac{1}{2} · 2w_2, \ \dots, \ \frac{1}{2} · 2w_M) \\\\
&= (w_1, \ w_2, \ \dots, \ w_M) \\\\
&= \vec w
\end{split}
\tag{1 - 5}
$$
我们再来看式(1 - 1)的第二项,${ \sum_{i=1}^N \alpha_i y_i (\vec w · \vec x_i + b) }$,该项又可以拆分成两项${ \sum_{i=1}^N \alpha_i · y_i · \vec w · \vec x_i}$及${ \sum_{i=1}^N \alpha_i · y_i · b}$,后一项因为不包含与$\vec w$相关的内容,因此根据标量对向量的求导法则,该项对向量$\vec w$的偏导恒为零向量,因此我们直接忽略这项,直接看第一项的结果。
$$
\begin{split}
{\sum_{i=1}^N \alpha_i · y_i · \vec w · \vec x_i} &= \alpha_1 · y_1 · \vec w · \vec x_1 + \alpha_2 · y_2 · \vec w · \vec x_2 + \dots + \alpha_N · y_N · \vec w · \vec x_N \\\\
\end{split}
\tag{1 - 6}
$$
为了方便书写,我们把上式记作$g(\vec w, \ \vec \alpha)$,于是根据标量对向量的偏导法则有:
$$
\begin{split}
\frac{\partial [g(\vec w, \ \vec \alpha)]}{\partial \vec w} &= \alpha_1 · y_1 \frac{\partial (\vec w · \vec x_1)}{\partial \vec w} + \alpha_2 · y_2 \frac{\partial (\vec w · \vec x_2)}{\partial \vec w} + \dots + \alpha_N · y_N \frac{\partial (\vec w · \vec x_N)}{\partial \vec w} \\\\
&= \sum_{i=1}^N \alpha_i · y_i \frac{\partial (\vec w · \vec x_i)}{\partial \vec w} \\\\
&= \sum_{i=1}^N \alpha_i · y_i \frac{\partial (w_1 · x_i^1 + w_2 · x_i^2 + \dots + w_M · x_i^M)}{\partial \vec w} \\\\
&= \sum_{i=1}^N \alpha_i · y_i (x_i^1 , \ x_i^2 , \ \dots , \ x_i^M) \\\\
&= \sum_{i=1}^N \alpha_i · y_i · \vec x_i
\end{split}
\tag{1 - 7}
$$
注意:上式中$x_i^k$表示的是第$i$个样本的第$k$个特征(或属性)的取值,而非$x_i$的$k$次方。
我们再来看式(1 - 1)第三项${\sum_{i=1}^N \alpha_i }$,该项是由拉格朗日乘子组成的,不含与向量$\vec w$相关的项,同样由标量对向量的偏导法则,该项对向量$\vec w$求偏导后恒为零向量,即有$\partial ({\sum_{i=1}^N \alpha_i }) / \partial \vec w \equiv 0$,我们同样忽略该项。
综上,我们有:
$$
\nabla _ \vec w L(\vec w, \vec \alpha, b) = \vec w - \sum_{i=1}^N \alpha_i · y_i · \vec x_i
\tag{1 - 8}
$$
对于式(1 - 4),因为求的是对标量b的偏导,与我们在大学时候学的内容契合,这里就不赘述,可得到:
$$
\nabla _ b L(\vec w, \vec \alpha, b) = \sum_{i=1}^N \alpha_i · y_i
\tag{1 - 9}
$$
分别另式(1 - 8)、(1 - 9)等于零,即可求解出极值情况未知变量需要满足的约束条件,可得:
$$
\begin{cases}
\vec w = \sum_{i=1}^N \alpha_i · y_i · \vec x_i \\\\
\sum_{i=1}^N \alpha_i · y_i = 0
\tag{1 - 10}
\end{cases}
$$
将上式回代入式(1 - 1),可以消掉未知变量$\vec w、b$。
同样的,几乎所有对这部分的讲解都是一笔带过,让有些数学基础不怎么好的同学有点犯晕,不知道是怎么得出来的。这里我给出详细的回代化简过程,我们先来看$\frac{1}{2} ||\vec w||^2$这一项:
$$
\begin{split}
& \frac{1}{2} ||\vec w||^2 \\\\
&= \frac{1}{2} \vec w · \vec w \\\\
&= \frac{1}{2} (\sum_{i=1}^N \alpha_i · y_i · \vec x_i) · (\sum_{i=1}^N \alpha_i · y_i · \vec x_i) \\\\
&= \frac{1}{2} (\alpha_1 · y_1 · \vec x_1 + \dots + \alpha_N · y_N · \vec x_N) · (\alpha_1 · y_1 · \vec x_1 + \dots + \alpha_N · y_N · \vec x_N)
\end{split}
\tag{1 - 11}
$$
上式展开出来后不进行同类项合并的话,一共有$N·N = N^2$项,为了方便书写和分析,我们把这些展开项按顺序放在一个$N·N$的方阵$A$中,矩阵的元素$A_{ij}$表示上式中第一个括号中的第$i$项与后面括号的第$j$项相乘的结果,可得:
$$
\begin{split}
A &= \frac{1}{2}
\begin{bmatrix}
\alpha_1 y_1 \vec x_1 · \alpha_1 y_1 \vec x_1 & \alpha_1 y_1 \vec x_1 · \alpha_2 y_2 \vec x_2 & \dots & \alpha_1 y_1 \vec x_1 · \alpha_N y_N \vec x_N \\\\
\alpha_2 y_2 \vec x_2 · \alpha_1 y_1 \vec x_1 & \alpha_2 y_2 \vec x_2 · \alpha_2 y_2 \vec x_2 & \dots & \alpha_2 y_2 \vec x_2 · \alpha_N y_N \vec x_N \\\\
\vdots & \vdots & \vdots & \vdots \\\\
\alpha_N y_N \vec x_N · \alpha_1 y_1 \vec x_1 & \alpha_N y_N \vec x_N · \alpha_2 y_2 \vec x_2 & \dots & \alpha_N y_N \vec x_N · \alpha_N y_N \vec x_N \\\\
\end{bmatrix} \\\\
\\\\
&= \frac{1}{2}
\begin{bmatrix}
\alpha_1^2 y_1^2 \vec x_1 · \vec x_1 & \alpha_1 \alpha_2 y_1 y_2 \vec x_1 · \vec x_2 & \dots & \alpha_1 \alpha_N y_1 y_N \vec x_1 · \vec x_N \\\\
\alpha_1 \alpha_2 y_1 y_2 \vec x_1 · \vec x_2 & \alpha_2^2 y_2^2 \vec x_2 · \vec x_2 & \dots & \alpha_2 \alpha_N y_2 y_N \vec x_2 · \vec x_N \\\\
\vdots & \vdots & \vdots & \vdots \\\\
\alpha_1 \alpha_N y_1 y_N \vec x_1 · \vec x_2 & \alpha_2 \alpha_N y_2 y_N \vec x_1 · \vec x_N & \dots & \alpha_N^2 y_N^2 \vec x_N · \vec x_N \\\\
\end{bmatrix}
\end{split}
\tag{1 - 12}
$$
可以看出,这是一个对称阵,矩阵的任意元素为$\alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j), 其中\ i,j \in [1,N]$,因此有:
$$
\frac{1}{2} ||\vec w||^2 = \frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j)
\tag{1 - 13}
$$
再来看第二项的化简,结合式(1 - 10),如下:
$$
\begin{split}
& {\sum_{i=1}^N \alpha_i y_i (\vec w · \vec x_i + b) } \\\\
&= {\sum_{i=1}^N \alpha_i y_i (\vec w · \vec x_i) } + b{ \sum_{i=1}^N \alpha_i y_i} \\\\
&= {\sum_{i=1}^N \alpha_i y_i ((\sum_{j=1}^N \alpha_j · y_j · \vec x_j) · \vec x_i) } + b · 0 \\\\
&= \sum_{j=1}^N \alpha_i y_i(\alpha_1 y_1 \vec w · \vec x_1 + \alpha_2 y_2 \vec w · \vec x_2 + \dots + \alpha_N y_N \vec w · \vec x_N) · \vec x_i \\\\
&= \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j)
\end{split}
\tag{1 - 14}
$$
最后来看第三项${\sum_{i=1}^N \alpha_i }$,该项已是最简形式,保持不变。
综上,我们有:
$$
\begin{split}
& \min \limits_{\vec w, b} L(\vec w, \vec \alpha, b) \\\\
&= \frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) - \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) + \sum_{i=1}^N \alpha_i \\\\
&= -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) + \sum_{i=1}^N \alpha_i
\end{split}
\tag{1 - 15}
$$
更进一步,式(1 - 2)的对偶问题就是上式再对$\vec \alpha$极大化,如下:
$$
\max \limits_{\vec \alpha} \lbrack -\frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) + \sum_{i=1}^N \alpha_i \rbrack
\tag{1 - 16}
$$
当然,上式的$\vec \alpha$还需要满足约束条件:
$$
\begin{cases}
\sum_{i=1}^N \alpha_i y_i = 0 \\\\
\\\\
\alpha_i \geq 0, \ i=1,2, \dots, N
\end{cases}
\tag{1 - 17}
$$
上式的求解,也是利用构造拉格朗日函数,然后分别对各个拉格朗日乘子求偏导并令偏导式等于零,即可求解出各个拉格朗日乘子的值,即可以求出$\vec \alpha^* = (\alpha_1^*, \ \alpha_2^*, \ \dots, \ \alpha_N^*)$,利用$\vec \alpha^*$以及式(1 - 10),我们就可以求出分割超平面的参数$\vec w^*$。根据式(1 - 10)有:
$$
\begin{split}
\vec w^* &= \sum_{i=1}^N \alpha_i^* · y_i · \vec x_i \\\\
&= \alpha_1^* y_1 \vec x_1 + \ \alpha_2^* y_2 \vec x_2 + \ \dots + \ \alpha_N^* y_N \vec x_N
\end{split}
\tag{1 - 18}
$$
由上式可知,对于$\alpha_i^* = 0$的拉格朗日乘子(约束不起作用),它对整个式子的贡献值$\equiv 0$,因此无论这些乘子取什么值,都不会影响分割超平面,因此我们再计算的时候也只需要代入$\alpha_i^* > 0$的拉格朗日乘子。这里要多说一句,该系列的前一篇机器学习算法系列之三:SVM3中我们已经证明了$\vec w^*$的存在性,并且$\vec w^* = 0$并非问题的解,因此必然存在$\alpha_j^* > 0, \ j \in [1, N]$,即至少有一项不等式(或等式)约束是起作用的。
求出$\vec w^*$后,结合$\vec \alpha^*$的值,再根据KKT条件的互补松弛$\alpha_i^* [y_i(\vec w^* · x_i + b^*) - 1] = 0$,可以求解出分割超平面参数$b^*$,这样整个分割超平面$H(\vec w, b)$就确定了。对$b^*$的求解,由互补松弛条件可知:
当$\alpha_i = 0$即对应的第$i$个样本约束不起作用时,则$\vec b^*$可取任意值而互不松弛条件均能满足,这个时候$\vec b^*$有任意多个解,因此无法由拉格朗日乘子为零的样本来计算$\vec b^*$。
当$\alpha_i > 0$即对应的第$i$个样本约束起作用时,要满足互不松弛条件,则必然有:
$$
y_i(\vec w^* · x_i + b^*) - 1 = 0
\tag{1 - 19}
$$
此时可以利用不为零的拉格朗日乘子求出$\vec b^*$,如下:
$$
\begin{split}
y_i[y_i(\vec w^* · x_i + b^*) - 1] &= y_i · 0 \\\\
y_i^2(\vec w^* · x_i + b^*) - y_i &= 0
\end{split}
$$
$\because$
$y_i^2 \equiv 1, \ \vec w^* = \sum_{j=1}^N \alpha_j^* · y_j · \vec x_j$
$\therefore$
$$
\begin{split}
& (\vec w^* · x_i + b^*) = y_i \\\\
& b^* = y_i - \vec w^* · \vec x_i \\\\
& b^* = y_i - \sum_{j=1}^N \alpha_j^* · y_j · \vec x_j · \vec x_i
\end{split}
\tag{1 - 20}
$$
到此,整个超平面的参数$\vec w^*、b^*$就解出来了,并且从上式可以看出,$b^*$的求解也不依赖于那些拉格朗日乘子为0的样本。
SVM的分割超平面的求解就讲完了(严格来说是线性可分SVM的分割超平面),因为这部分内容实在是太重要了,它是我们深入理解SVM精髓的基石,所以我们重新把分割超平面的求解算法梳理一下,方便大家回顾之前的内容和平滑的切入到下一章的内容。
顺便提一句,上述极大化问题还可以转换为如下的极小化问题,本质没有发生任何变化:
$$
\begin{cases}
\min \limits_{\vec \alpha} \lbrack \frac{1}{2} \sum_{i=1}^N \sum_{j=1}^N \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) - \sum_{i=1}^N \alpha_i \rbrack \\\\
\\\\
\sum_{i=1}^N \alpha_i y_i = 0 \\\\
\\\\
\alpha_i \geq 0, \ i=1,2, \dots, N
\end{cases}
\tag{1 - 21}
$$
理论比较抽象,我们用一个例子来说明上述求解过程,帮助理解消化。注:以下例子选自李航老师的《统计学习方法》P107的例2,略有改动和补充说明。
三个样本点$x_1 = (3,3)^T, \ x_2 = (4,3)^T, \ x_3 = (1,1)^T $,两个正样本$y_1 = +1, \ y_2 = +1$,一个负样本$y_3 = -1$。
利用1.4节的求解算法求出分割超平面。
特征向量维度$M = 2$,样本数量$N = 3$;
设拉格朗日乘子向量$\vec \alpha = (\alpha_1, \ \alpha_2, \ \alpha_3)$;
求$\max \limits_{\vec \alpha} \lbrack -\frac{1}{2} \sum_{i=1}^3 \sum_{j=1}^3 \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) + \sum_{i=1}^3 \alpha_i \rbrack$在限制条件$\alpha_1, \ \alpha_2, \ \alpha_3 \in [0, \ +\infty) $及$\alpha_1 · (+1) + \alpha_2 · (+1) + \alpha_3 · (-1) = \alpha_1 + \alpha_2 - \alpha_3 = 0$下的极值:
$$
\begin{split}
& \max \limits_{\vec \alpha} \lbrack -\frac{1}{2} \sum_{i=1}^3 \sum_{j=1}^3 \alpha_i \alpha_j y_i y_j (\vec x_i · \vec x_j) + \sum_{i=1}^3 \alpha_i \\\\
&= -\frac{1}{2} (18 \alpha_1^2 + 25 \alpha_2^2 + 2 \alpha_3^2 + 42 \alpha_1 \alpha_2 - 12 \alpha_1 \alpha_3 - 14 \alpha_2 \alpha_3) \\\\
& \ \ - \alpha_1 - \alpha_2 - \alpha_3
\end{split}
\tag{1 - 22}
$$
记上式为$f(\alpha_1 , \alpha_2 , \alpha_3)$,将$\alpha_3 = \alpha_1 + \alpha_2$代入上式有:
$$
f(\alpha_1 , \alpha_2 , \alpha_3) = -4 \alpha_1^2 - \frac{13}{2} \alpha_2^2 - 10 \alpha_1 \alpha_2 + 2\alpha_1 + 2 \alpha_2
\tag{1 - 23}
$$
分别对$\alpha_1、\alpha_2$求偏导有:
$$
\begin{split}
\begin{cases}
\frac{\partial f(\alpha_1 , \alpha_2 , \alpha_3)}{\partial \alpha_1} \ = \ -8 \alpha_1 - 10 \alpha_2 + 2= 0 \\\\
\frac{\partial f(\alpha_1 , \alpha_2 , \alpha_3)}{\partial \alpha_2} \ = \ -13 \alpha_2 - 10 \alpha_1 + 2= 0
\end{cases}
\end{split}
\tag{1 - 24}
$$
联立上式得唯一解:
$$
\begin{cases}
\alpha_1^* = \frac{3}{2} \\\\
\alpha_2^* = -1
\end{cases}
\tag{1 - 25}
$$
虽然求出了解,但是该解对应的$\alpha_2^*$不满足约束$\alpha_2^* \geq 0$,因此极大值必然在边界处。因为拉格朗日乘子的取值范围都是$[0 , \ +\infty)$,显然拉格朗日乘子不可能取右边界$+\infty$,因此必然在左边界即0处取极值。
若$\alpha_1 = 0$,则$f(\alpha_1 , \alpha_2 , \alpha_3) = - \frac{13}{2} \alpha_2^2 + 2 \alpha_2$,再用该式对$\alpha_2$求一阶偏导可求出极值点为$\alpha_2^* = -\frac{2}{13}$,不符合拉格朗日乘子恒为非负的条件,因此最大值不在此处。
根据$\alpha_1^* = \alpha_3^* = \frac{1}{4}$可求得$\vec w^*$:
$$
\begin{split}
\vec w^* &= \alpha_1^* y_1 \vec x_1 + \alpha_3^* y_3 \vec x_3 \\\\
&= \frac{1}{4} · (+1) · (3,3)^T + \frac{1}{4} · (-1) · (1,1)^T \\\\
&= (\frac{1}{2} , \frac{1}{2})^T
\end{split}
\tag{1 - 26}
$$
再根据$\vec w^* = (\frac{1}{2}, \frac{1}{2})^T$或选择拉格朗日乘子$\alpha_1^* = \frac{1}{4}(或 \alpha_3^* = \frac{1}{4})$来计算$b^*$:
$$
\begin{split}
b^* &= y_1 - [\alpha_1^* y_1 (\vec x_1 · \vec x_1) + \alpha_3^* y_3 (\vec x_3 · \vec x_1)]\\\\
&= +1 - [\frac{1}{4} · (+1) · (3,3)^T·(3,3)^T + \frac{1}{4} · (-1) · (1,1)^T·(3,3)^T] \\\\
&= -2 \\\\
\\\\
或 \\\\
\\\\
b^* &= y_1 - \vec w^* · \vec x_1 \\\\
&= +1 - (\frac{1}{2}, \frac{1}{2})^T · (3,3)^T \\\\
&= -2
\end{split}
$$
所以分割超平面为:
$$
\frac{1}{2} x^{(1)} + \frac{1}{2} x^{(2)} - 2 = 0
\tag{1 - 27}
$$
从上述过程可以看出,SVM分割超平面的求解是先把不易求解的原始极小极大最优化问题转换为易于求的极大极小最优化问题。
转换后,通过先求解极小化问题,求解出超平面参数$\vec w, b$与拉格朗日乘子$\vec \alpha$的关系,从而消去原最优化问题中的未知变量$\vec w, b$,只剩下$\vec \alpha$。
消元后,再求解极大化(也可以转换为求极小化)问题的解,从而求出$\vec \alpha$。
求出$\vec \alpha$后即可求出分割超平面,其公式为:
$$
(\sum_{i=1}^N \alpha_i^* · y_i · \vec x_i) \cdot \vec x + b^* = \sum_{i=1}^N \alpha_i^* · y_i · (\vec x_i \cdot \vec x) + b^* = 0
\tag{1 - 28}
$$
此外,其分类决策函数则为:
$$
f(x) = sign \lgroup \sum_{i=1}^N \alpha_i^* · y_i · (\vec x_i \cdot \vec x) + b^* \rgroup
\tag{1 - 29}
$$
在上一节中我们提到了分割超平面参数$\vec w, \ b$的求解不需要那些拉格朗日乘子为0的样本参与,因为这些样本对分割超平面的位置不产生任何有效约束力。从几何上来理解,这些样本因为远离交界处,因此不会影响分割面。
而对于那些拉格朗日乘子不为0的样本(更准确的说,是训练数据集中对应于$\alpha_i > 0$的样本),它们直接决定了分割超平面,我们称这些样本点为支持向量,如下图2-1所示:
由上可知,支持向量一定在边界处,它的文字性定义为:
在线性可分情况下,训练数据集的样本点中与分离超平面距离最近的样本点的实例成为支持向量。
——摘自李航《统计学习方法》$P_{102}$
其数学定义式如下:
$$
\begin{split}
& y_i (\vec w^* · \vec x_i + b^*) - 1 = 0 , \ \alpha_i^* > 0\\\\
\\\\
& 或 \\\\
\\\\
& (\vec w^* · \vec x_i + b^*) = \pm1 , \ \alpha_i^* > 0\\\\
\end{split}
$$
支持向量机的名字就是由支持向量而来的,从这一点出发就可以理解支持向量的重要性。一旦决定分割超平面的支持向量保持不变,其它的点无论怎么改变,都不会影响分割超平面,也就不会影响我们的分类决策模型。但是线性SVM由于其线性特征,导致其对噪声数据比较敏感,它只适用于线性可分的数据(即能通过画一条直线区分开)。一旦不同类别数据之间互有交错(即线性不可分),就无法求出分割超平面,线性可分/线性非可分数据的示例如下图所示:
对于线性非可分数据的问题,我们将在本系列的下一篇文章里讲怎么解决。
实际上,就我们生活中的数据而言,根据最后学习出来的SVM的类型,可以划分成以下三种情况:
注意:刚接触SVM的同学很容易把非线性和线性非可分两个概念混淆在一起。关于非线性支持向量机,会在本系列的后面讲。
]]> numpy数组中的轴不太容易理解,但是却非常重要。官方定义为:轴即维度(In Numpy dimensions are called axes.)。
对于二维数组,0轴即代表数组的行,1轴代表数组的列,对二维数组:1
2
3
4
5arr1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr1
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
其轴0、1如下图所示:
1 | 0) arr1.sum(axis= |
1 | 0, 1, 2, 3], [4, 5, 6, 7]], [[8, 9, 10, 11], [12, 13, 14, 15]]]) arr = np.array([[[ |
arr
有3条轴,编号分别为0、1、2,要直接看出来这三条轴分别对应什么方向有点困难。最好的办法就是先将三维数组降维成一个二维数组,这样就可以获得原数组的0轴、1轴。怎么降呢?把最内层数组作为一个整体来看待,即有:1 | A = [0, 1, 2, 3] |
1 | # arr.sum(axis=0) = [A + C, B + D] |
所以对轴2方向进行求和,实际上就是分别将A、B、C、D的元素求和(对一维向量应用sum函数,计算的是该向量所有元素之和),代码及结果如下:1
2
3
4# sum(A) = [0 + 1 + 2 + 3] = [6]
2) arr.sum(axis=
array([[ 6, 22],
[38, 54]])
由此可知,对于多维数组,numpy对轴的编号是先行后列,由外向内!实际中三维数组算是维度比较高的了,至于四维及以上的不太常见,因此没必要讲,但是为了验证我们刚才提到的这个结论,我们再举一个四维数组来证明。
我们先生成一个4*2*2*2数组,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
310, 32) arr2 = np.arange(
arr2
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31])
4,2,2,2) arr2.reshape(
array([[[[ 0, 1],
[ 2, 3]],
[[ 4, 5],
[ 6, 7]]],
[[[ 8, 9],
[10, 11]],
[[12, 13],
[14, 15]]],
[[[16, 17],
[18, 19]],
[[20, 21],
[22, 23]]],
[[[24, 25],
[26, 27]],
[[28, 29],
[30, 31]]]])
为了手算出结果,同样的,我们需要对原数组进行降维,降维方法是将内部的二维数组分别用字母表示,即有:1
2
3
4
5
6
7
8
9
10
11
12A = [[ 0, 1], [ 2, 3]]
B = [[ 4, 5], [ 6, 7]]
C = [[ 8, 9], [10, 11]]
D = [[12, 13], [14, 15]]
E = [[16, 17], [18, 19]]
F = [[20, 21], [22, 23]]
G = [[24, 25], [26, 27]]
H = [[28, 29], [30, 31]]
arr2 = [[A, B],
[C, D],
[E, F],
[G, H]]
降维后可知,对0、1轴求和的结果为:
$$
\begin{split}
arr.sum(axis=0) &= \lbrack A + C + E + G \ , \ B + D + F + H \rbrack \\\\
arr.sum(axis=1) &= \lbrack A + B \ , \ C + D \ , \ E + F \ , \ G + H \rbrack
\end{split}
$$
因为A~H均为二维数组,因此其求和受向量运算法则约束,即有:
$$
\begin{split}
A + C + E + G &= \lbrack A_0 + C_0 + E_0 + G_0 \ , \ A_1 + C_1 + E_1 + G_1 \rbrack \\\\
&=
\lbrack
{\lbrack 0, 1 \rbrack + \lbrack 8, 9 \rbrack + \lbrack 16, 17 \rbrack + \lbrack 24, 25 \rbrack }
\ , \
{\lbrack 2, 3 \rbrack + \lbrack 10, 11 \rbrack + \lbrack 18, 19 \rbrack + \lbrack 26, 27 \rbrack}
\rbrack \\\\
&=
\lbrack
{\lbrack 0 + 8 + 16 + 24 \ , \ 1 + 9 + 17 + 25 \rbrack}
\ , \ \\\\
&\ \ \ \ \ \ \ {\lbrack 2 + 10 + 18 + 26 \ , \ 3 + 11 + 19 + 27 \rbrack}
\rbrack \\\\
&=
\lbrack
{\lbrack 48 \ , \ 52 \rbrack}
\ , \
{\lbrack 56 \ , \ 60 \rbrack}
\rbrack
\\\\同理可求得:\\\\ \\\\
B + D + F + H &=
\lbrack
{\lbrack 64 \ , \ 68 \rbrack}
\ , \
{\lbrack 72 \ , \ 76 \rbrack}
\rbrack
\end{split}
$$
这与代码运行的结果完全一致,如下图所示:
1 | 1) arr2.sum(axis= |
四维数组一共有4个轴,至此我们已经把最外层的两个轴(0、1)计算完了,还剩下4-2=2个轴,这两个轴(2,、3)按照我们上面的结论,分别对应内层数组的行(轴0)、列(轴1)。对轴2、3进行求和计算实际上就是对这些二维数组的行、列分别进行求和。
以A = [[0,1], [2, 3]]来说,对其0、1轴求和分别等于[2, 4]、[1, 5],同理可求出剩余的二维数组的相关值,因此对原四维数组轴2、3求和的结果为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
242) arr2.sum(axis=
array([[[ 2, 4],
[10, 12]],
[[18, 20],
[26, 28]],
[[34, 36],
[42, 44]],
[[50, 52],
[58, 60]]])
3) arr2.sum(axis=
array([[[ 1, 5],
[ 9, 13]],
[[17, 21],
[25, 29]],
[[33, 37],
[41, 45]],
[[49, 53],
[57, 61]]])
这就证明了我们上面的结论是完全正确的,当维度$N \geq 5$时,原理是一样的,只是稍微繁琐一些。需要注意的是,如果我们要手算,应该进行降维,降维后的维度最好是2,因为这是我们能直观理解的最佳维度,外层计算完后,计算内层时,内层元素进行维度还原时,也最好是二维数组。
numpy数组中的维度(dimension)官方定义说是指轴的个数,通俗点将,就是你要取得这个数组里面的某个元素必须使用的索引的个数,比如有如下数组:1
arr1 = np.array([[1,2], [7,5]])
我们要使用arr1[1][0]
来取得数组中的元素7
,即用了两个索引来获得数组元素,因此数组arr1
的维度即为2。
官方定义中,秩即为轴的个数。
有了上面多维数组轴的概念,要理解数组的转置就容易多了。对于数组的转置,当维度$N \leq 2$时即表示二维数组的转置,其含义非常明确(行列互换),也很容易理解,但是当维度$N \geq 3$时,就不太直观了。书中P97-98关于三维数组的转置(transpose)和轴对换(swapaxes)的描述过于简单,也比较抽象,导致新手有点雾里看花的感觉,对这个问题我认真思考了三天,经过大量的手工推演及编码验证,才搞清楚了它的原理。因为五维及以上的转置,手工推演已经失去了价值和意义并且工作量浩大,所以这里我们仅对三维及四维数组转置做推导,更高维度的原理相同。
生成一个三维数组(2*2*4),代码如下:1
2
3
4
5
6
7
8
9
10
11import numpy as np
0,16) arr = np.arange(
arr
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
2,2,4) arr = arr.reshape(
arr
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
现在对其按任意轴序进行转置,假定轴序为(2, 0, 1),则转置代码为:1
arr.transpose((2, 0, 1))
transpose()函数接受的是一个由轴编号
组成的有序元组,表示变换后的新数组的轴编号顺序。上述代码的含义就是:将原数组的轴2变换为新数组的轴0,原数组的轴0变换为新数组的轴1,原数组的轴0变换为新数组的轴1。
以上述数组来说,变换后的结果是怎样的呢?按照网上资料的做法,可以分别计算出每个元素变换后的索引,然后就可以得到变换后的数组,比如对元素6
,变换前其索引为[0][1][2]
,变换后的索引则变成了[2][0][1]
,数组小的时候,这样做还可以,当数组非常大的时候,一个个去计算就非常不明智了,并且容易算错。我们需要一种更为高效、准确的方法——轴推导法。
轴推导法的思想主要有以下三步:
1. 定维度。根据变换前后各个轴轴向维度不变原理,可以确定变换后的数组形式;
2. 定内层。先确定最内层元素的形式;
3. 递归。确定内层元素后,由内向外,逐层确定元素形式及内容。
具体推导过程为:
1. 定维度。变换前各个轴的维度(注意:这里的维度是指各个轴方向元素的个数,与数组的维度有所区别。)如下:
轴号 | 0 | 1 | 2 |
---|---|---|---|
维度 | 2 | 2 | 4 |
矩阵形式:2 * 2 * 4 |
则变换后矩阵的形式为:4(轴2) * 2(轴0) * 2(轴1),矩阵写出来的形式如下:
$$
\begin{split}
\huge
\lbrack \
\LARGE
&\lbrack \
\lbrack \ \square \ , \ \square \rbrack \ , \ \lbrack \ \square \ , \ \square \rbrack \
\rbrack , \\\\
&\lbrack \
\lbrack \ \square \ , \ \square \rbrack \ , \ \lbrack \ \square \ , \ \square \rbrack \
\rbrack , \\\\
&\lbrack \
\lbrack \ \square \ , \ \square \rbrack \ , \ \lbrack \ \square \ , \ \square \rbrack \
\rbrack , \\\\
&\lbrack \
\lbrack \ \square \ , \ \square \rbrack \ , \ \lbrack \ \square \ , \ \square \rbrack \
\rbrack
\huge
\ \rbrack
\end{split}
$$
其中的$\square$代表原数组中的任意一个数字。
2. 定内层。对于内层数组而言,是一个1 × 2的矩阵$\lbrack \ \square \ , \ \square \ \rbrack$,变换后的新数组的轴2(即最内层数组的行方向)是原来的数组的轴1(即最外层数组的列方向),原数组的列方向数字依次为$\stackrel{\longrightarrow}{0, 1, 2, 3}$,所以变换后最内层数组的行方向元素依次为:
$$
\lbrack \ \boxed{\color{red}0} \ , \ \boxed{\times} \ \rbrack \ , \ \lbrack \ \boxed{\times} \ , \ \boxed{\times} \ \rbrack \ \\\\
\lbrack \ \\boxed{\color{red}1} \ , \ \boxed{\times} \ \rbrack \ , \ \lbrack \ \boxed{\times} \ , \ \boxed{\times} \ \rbrack \ \\\\
\lbrack \ \boxed{\color{red}2} \ , \ \boxed{\times} \ \rbrack \ , \ \lbrack \ \boxed{\times} \ , \ \boxed{\times} \ \rbrack \ \\\\
\lbrack \ \boxed{\color{red}3} \ , \ \boxed{\times} \ \rbrack \ , \ \lbrack \ \boxed{\times} \ , \ \boxed{\times} \ \rbrack \ \\\\
$$
3. 递归。内层数组首元素确定后,我们还需要根据外层数组的行列才能完全确定变换后的数组。上可知,变换后的数组的列方向是原数组的行方向,于是得到列首元素:
$$
\begin{split}
& \lbrack \ \boxed{\color{red}0} \ , \ \boxed{\color{red}4} \ \rbrack \ , \ \lbrack \ \boxed{\color{red}8} \ , \ \boxed{\color{red}12} \ \rbrack \ \\\\
& \lbrack \ \boxed{1} \ , \ \boxed{\times} \ \rbrack \ , \ \lbrack \ \boxed{\times} \ , \ \boxed{\times} \ \rbrack \ \\\\
& \lbrack \ \boxed{2} \ , \ \boxed{\times} \ \rbrack \ , \ \lbrack \ \boxed{\times} \ , \ \boxed{\times} \ \rbrack \ \\\\
& \lbrack \ \boxed{3} \ , \ \boxed{\times} \ \rbrack \ , \ \lbrack \ \boxed{\times} \ , \ \boxed{\times} \ \rbrack \ \\\\
\end{split}
$$
注意:这里首行的列元素顺序为$\stackrel{\longrightarrow}{0, 4, 8, 12}$而不是$\stackrel{\longrightarrow}{0, 8, 4, 12}$,因为0、4才是属于不同行同列的元素。
再根据变换后数组的行方向是原数组的列方向,可以分别得到4、8、12下面的元素,最后结果为:
$$
\begin{split}
& \lbrack \ \boxed{0} \ , \ \boxed{\color{red}4} \ \rbrack \ , \ \lbrack \ \boxed{\color{blue}8} \ , \ \boxed{\color{purple}{12}} \ \rbrack \ \\\\
& \lbrack \ \boxed{1} \ , \ \boxed{\color{red}5} \ \rbrack \ , \ \lbrack \ \boxed{\color{blue}9} \ , \ \boxed{\color{purple}{13}} \ \rbrack \ \\\\
& \lbrack \ \boxed{2} \ , \ \boxed{\color{red}6} \ \rbrack \ , \ \lbrack \ \boxed{\color{blue}{10}} \ , \ \boxed{\color{purple}{14}} \ \rbrack \ \\\\
& \lbrack \ \boxed{3} \ , \ \boxed{\color{red}7} \ \rbrack \ , \ \lbrack \ \boxed{\color{blue}{11}} \ , \ \boxed{\color{purple}{15}} \ \rbrack \ \\\\
\end{split}
$$
代码运行结果与我们的推导完全一致,如下:1
2
3
4
5
6
7
8
9
10
11
122,0,1) arr.transpose(
array([[[ 0, 4],
[ 8, 12]],
[[ 1, 5],
[ 9, 13]],
[[ 2, 6],
[10, 14]],
[[ 3, 7],
[11, 15]]])
四维数组的变换与三维数组类似,只是需要先确定最内层数组的行、列方向,在变换中一定要注意的是:保持元素的对应关系(异行同列,异列同行)!如果不确定,可以使用元素索引来辅助分析。比如,对三维数组中索引为$\lbrack 0 \rbrack \lbrack 0 \rbrack \lbrack x \rbrack$的元素,按轴序(1,2,0)变换后,索引变为$\lbrack 0 \rbrack \lbrack x \rbrack \lbrack 0 \rbrack$,也就是说原数组第一项的第一项中的元素变换后,是新数组的第一项的所有项的首元素。
ndarray还提供了轴对换方法,名为swapaxes,它接受一对轴编号,然后将给定的两个轴的数据进行对换,它的作用于数组转置相同,只不过它每一次只能完成两个轴的交换,而transpose方法则可以是3个及以上,swapaxes用法如下:1
2
3
4
5
6
7
8
9
101,2) arr.swapaxes(
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])
上述代码完成了原三维数组的轴2和轴1的交换。
]]>Thinking deeply!
好记性不如烂笔头,专门开一篇来记录学习中遇到的一些重要概念。
看周志华的《机器学习》,里面有个名词函数空间,它的中文维基百科给出的定义是:
在数学中,函数空间是从集合X到集合Y的给定类型的一组函数。
通俗点来讲,这个“空间”中的所有元素都是函数(负责自变量到因变量的转换),并且这些函数都满足一定的条件。
函数空间的概念在许多数学领域中均有自己的意义,这里我们只关心集合论、线代中的相关问题。
在集合论中,从集合$X$到$Y$的函数空间记做:$X \rightarrow Y$或者$Y^X$,其中的特列就是,$X$的所有子集(power set或powerset)可以用从$X$到二项集合$\lbrace 0, 1 \rbrace$的所有函数的集合来标识,记做:$2^X$。
在线性代数中,函数空间(也是向量空间)则是同一个域上的向量空间$V$到另一个向量空间$W$的所有线性变换的集合。
在手写SVM证明过程的时候,发现偏导数的知识点远远不只我们平时高数里面学的那些。按照变量类型划分,有Scalar(标量)、Vector(矢量)、Matrix(矩阵),两两结合就有3 * 3 = 9
中形式,如下表:
变量类型 | 标量 | 向量 | 矩阵 |
---|---|---|---|
标量 | $\frac {\large \partial y} {\large \partial x}$ | $\large \frac {\partial \vec y} {\partial x}$ | $\frac {\large \partial \textbf{Y}} {\large \partial x}$ |
向量 | $\frac {\large \partial y} {\large \partial \vec x }$ | $\frac {\large \partial \vec y} {\large \partial \vec x }$ | $\color {fuchsia} {\frac {\large \partial \textbf{Y}} {\large \partial \vec x}}$ |
矩阵 | $\frac {\large \partial y} {\large \partial \textbf{X}}$ | $\color {fuchsia} {\frac {\large \partial \vec y} {\large \partial \textbf{X}}}$ | $\color {fuchsia} {\frac {\large \partial \textbf{Y}} {\large \partial \textbf{X}}}$ |
上述表中的偏导数有不同的记法,wikipedia上讲了两种,一种是Numerator-layout notation(分子记法)
,另一种是Denominator-layout notation(分母记法)
,当然也可以两种方式混排,为了减少节省篇幅(并且有些偏导仅能用分子记法表示),这里采用第一种分子记法。
注意,上表中带颜色部分的偏导,因为属于更高阶(秩≥2)的张量,无法直接写在一个矩阵中,所以在此不给出这部分表达式,wikipedia上的原话是:
However, these derivatives are most naturally organized in a tensor of rank higher than 2, so that they do not fit neatly into a matrix.
标量对标量的偏导(Scalar-by-scalar)的求法正是我们日常中见得最多也是用的最多的,如下:
$$
\frac {\partial \large y} {\partial x}
\tag{1 - 2 - 1}
$$
向量对标量偏导(Vector-by-scalar)求法如下:
$$
\frac {\partial \bf{\vec y}} {\partial x} =
\begin{bmatrix}
\frac{\partial y_1}{\partial x} \\\\
\frac{\partial y_2}{\partial x} \\\\
\vdots \\\\
\frac{\partial y_m}{\partial x}
\end{bmatrix}
\tag{1 - 2 - 2}
$$
矩阵对标量偏导(Matrix-by-scalar)求法如下,其中矩阵$\bf{X}$为m * n
矩阵:
$$
\frac {\partial \large \textbf{Y}} {\partial x} =
\begin{bmatrix}
\frac{\partial y_{11}}{\partial x} & \frac{\partial y_{12}}{\partial x} & \cdots & \frac{\partial y_{1n}}{\partial x} \\\\
\frac{\partial y_{21}}{\partial x} & \frac{\partial y_{22}}{\partial x} & \cdots & \frac{\partial y_{2n}}{\partial x} \\\\
\vdots & \vdots & \ddots & \vdots \\\\
\frac{\partial y_{m1}}{\partial x} & \frac{\partial y_{m2}}{\partial x} & \cdots & \frac{\partial y_{mn}}{\partial x}
\end{bmatrix}
\tag{1 - 2 - 3}
$$
标量对向量偏导(Scalar-by-vector)求法如下:
$$
\frac {\partial \large y} {\partial \bf{\vec x}} =
\begin{bmatrix}
\frac {\partial \large y} {\partial x_1} \ , \ \frac {\partial \large y} {\partial x_2} \ , \ \cdots \ , \ \frac {\partial \large y} {\partial x_n}
\end{bmatrix}
\tag{1 - 2 - 4}
$$
向对向量偏导(Vector-by-vector)求法如下:
$$
\frac {\partial \large \bf{\vec y}} {\partial \large \bf{\vec x}} =
\begin{bmatrix}
\frac{\partial y_1}{\partial x_1} & \frac{\partial y_1}{\partial x_2} & \cdots & \frac{\partial y_1}{\partial x_n} \\\\
\frac{\partial y_2}{\partial x_1} & \frac{\partial y_2}{\partial x_2} & \cdots & \frac{\partial y_2}{\partial x_n} \\\\
\vdots & \vdots & \ddots & \vdots \\\\
\frac{\partial y_m}{\partial x_1} & \frac{\partial y_m}{\partial x_2} & \cdots & \frac{\partial y_m}{\partial x_n}
\end{bmatrix}
\tag{1 - 2 - 5}
$$
矩阵对向量偏导(Matrix-by-vector)在此不予列出,原因前文已述及
标量对矩阵偏导(Scalar-by-Matrix)求法如下,其中矩阵$\bf{X}$为m * n
矩阵:
$$
\frac {\partial \large y} {\partial \large \bf{Y}} =
\begin{bmatrix}
\frac{\partial y}{\partial x_{11}} & \frac{\partial y}{\partial x_{21}} & \cdots & \frac{\partial y}{\partial x_{m1}} \\\\
\frac{\partial y}{\partial x_{12}} & \frac{\partial y}{\partial x_{22}} & \cdots & \frac{\partial y}{\partial x_{m2}} \\\\
\vdots & \vdots & \ddots & \vdots \\\\
\frac{\partial y}{\partial x_{1n}} & \frac{\partial y}{\partial x_{2n}} & \cdots & \frac{\partial y}{\partial x_{mn}} \\\\
\end{bmatrix}
\tag{1 - 2 - 6}
$$
向量对矩阵偏导(Vector-by-Matrix)在此也不予列出。
矩阵对矩阵偏导(Matrix-by-Matrix)在此也不予列出。
设$X \sim N(\mu, \sigma^2)$,则其概率密度函数为:
$$
f(x) = \frac{1}{\sqrt{2 \pi} \sigma} exp \lgroup -\frac{(x - \mu)^2}{2 \sigma^2} \rgroup
\tag{1 - 3 - 1}
$$
式中$\mu$为期望,$\sigma$为方差。
设$X_1 \sim N(\mu_1, \sigma^2_1), \ X_2 \sim N(\mu_2, \sigma^2_2)$,且$X_1、X_2$并不相互独立,其相关系数为$\rho$,即有$(X_1, \ X_2) \sim (\mu_1, \ \mu_2, \ \sigma^2_1, \ \sigma^2_2, \ \rho)$,则其概率密度函数为:
$$
\begin{split}
f(x_1, \ x_2) = \frac{1}{2 \pi \sigma_1 \sigma_2 \sqrt{1 - \rho^2}} exp \lbrack &-\frac{1}{2(1 - \rho^2)} \lgroup \frac{(x_1 - \mu_1)^2}{\sigma^2_1} \ \\\\
&- \ \frac{2\rho (x_1 - \mu_1)(x_2 - \mu_2)}{\sigma_1 \sigma_2} \ \\\\
&+ \ \frac{(x_2 - \mu_2)^2}{\sigma^2_2} \rgroup \rbrack
\end{split}
\tag{1 - 3 - 2}
$$
改写成向量形式,现记:
$$
\begin{split}
\vec x &= (x_1, \ x_2)^T \\\\
\vec \mu &= (\mu_1, \ \mu_2)^T \\\\
C &=
\begin{bmatrix}
\sigma^2_1 & \rho \sigma_1 \sigma_2 \\\\
\rho \sigma_1 \sigma_2 & \sigma^2_2
\end{bmatrix}
=
\begin{bmatrix}
c_{11} & c_{12} \\\\
c_{21} & c_{22}
\end{bmatrix}
\end{split}
$$
则二元/维高斯分布概率密度的向量形式为:
$$
f(\vec x) = f(x_1, \ x_2) = \frac{1}{2\pi \sqrt{det (C) }} exp \lbrack -\frac{1}{2} (\vec x - \vec \mu)^T C^{-1} (\vec x - \vec \mu) \rbrack
\tag{1 - 3 - 3}
$$
式中,$det(C) = |C| = \sigma^2_1 \sigma^2_2 - \rho^2 \sigma^2_1 \sigma^2_2 = \sigma^2_1 \sigma^2_2(1 - \rho^2)$,为协方差矩阵$C$对应的行列式之值。如果对上式(3 - 3)的指数中的幂有疑问,自行展开对比即可,这里不赘述。
从二元/维高斯分布出发,可以推导出(过程略)$n$元/维高斯分布的向量形式为:
$$
f(\vec x; \mu \ , C) = \frac{1}{(2\pi)^{\frac{n}{2}} (det (C))^\frac{n}{2}} exp \lbrack -\frac{1}{2} (\vec x - \vec \mu)^T C^{-1} (\vec x - \vec \mu) \rbrack
\tag{1 - 3 - 4}
$$
式中$C$为一$n*n$的协方差阵,$det(C)$仍然表示其对应的行列式的值。
根据奈奎斯特(香农采样定理)采样定理:
$$
f_s > 2· f_{max}
\tag{2 - 1 - 1}
$$
用语言描述则是:为了准确还原原始信号,采样频率要大于信号中最高频率的2倍。公式非常简单,但是其所蕴含的哲理却并不那么显见,特别是信号中的最高频率这几个字非常值得体会。
通信理论中,采样分过采样(oversampling) $VS$ 欠采样(undersampling)、上采样(upsampling) $VS$ 下采样(downsampling)。
过采样,是指采样频率大于信号频率2倍及以上的采样,只有过采样才能重建原始信号。过采样针对模拟信号到数字信号的过程。
欠采样与过采样是一对,顾名思义,它是指采样频率小于信号频率2倍的采样,欠采样无法完全重建原始信号。欠采样也是针对模拟信号到数字信号的过程。
欠采样的一个比较著名的现象是告诉旋转的车轮看起来更像是在反着转,这种现象称为混叠。
上采样,是通过插值的方式对原始信号进行处理(比如图像的放大)。
下采样,它与上采样是一对,它通过采样的方式来减少原始数据(比如图像的缩小)。比如对一副分辨率为$M * N$的图片,进行$k$倍下采样后,得到的图像分辨率为$(M/k) * (N/k)$。
关于范数,Wikipedia)上式这么定义的:
In linear algebra, functional analysis, and related areas of mathematics, a norm is a function that assigns a strictly positive length or size to each vector in a vector space—save for the zero vector, which is assigned a length of zero. A seminorm, on the other hand, is allowed to assign zero length to some non-zero vectors (in addition to the zero vector).
范数满足三个特性:
其中$p(\cdot)$表示对$\cdot$取范数。范数分为向量范数(vector norm)和矩阵范数(matrix norm)。
向量的p范数定义为向量中所有元素绝对值的$p$次方之和再开$p$次方。
$$
||\vec x||_{p} = (\sum_{i=1}^N |x_i|^p)^{\frac{1}{p}}, \quad p \in [1, \ +\infty] \ 或 \ p = 0
\tag{3 - 1 - 1}
$$
根据$p$最常用的取值,我们有以下几种范数(注意当$p \in (0, 1)$的时候并不满足范数的三角不等式特性)。
向量的0范数定义为向量中所有非零元素的个数。
向量的1范数定义为向量中所有元素的绝对值之和。
$$
||\vec x||_1 = \sum_{i=1}^N |x_i|
\tag{3 - 1 - 2}
$$
向量的2范数定义为向量中所有元素平方之和再开方,向量的二范数实际上与欧式距离是一样的。
$$
||\vec x||_2 = \sqrt{\sum_{i=1}^N x_i^2}
\tag{3 - 1 - 3}
$$
向量的2范数定义为向量中所有元素绝对值的最大值。
$$
||\vec x||_{\infty} = \max_{i=1} |x_i|
\tag{3 - 1 - 4}
$$
矩阵的$p$范数定义为:
$$
||\textbf{A}||_{p} = (\max_{||x||_p = 1} ||\textbf{A}x||_p)^{\frac{1}{p}}
\tag{3 - 1 - 5}
$$
矩阵的1范数定义为矩阵每一行的所有列元素绝对值之和的最大值,故又称为列和范数:
$$
||\textbf{A}||_{1} = \max_{j} \sum_{i} |\textbf{A}_{ij}|
\tag{3 - 1 - 6}
$$
矩阵的2范数定义为矩阵最大特征值的开方,又称普范数:
$$
||\textbf{A}||_{2} = \sqrt{\max_{j} \lambda_j} = \sqrt{\max eig(A^H A)}
\tag{3 - 1 - 7}
$$
矩阵的$\infty$范数定义为矩阵每一列所有行元素绝对值之和的最大值,故又称为行和范数:
$$
||\textbf{A}||_{infty} = \max_{i} \sum_{j} |\textbf{A}_{ij}|
\tag{3 - 1 - 7}
$$
矩阵的$F$(Frobenius)范数定义为矩阵所有元素绝对值平方之和再开方:
$$
||\textbf{A}||_{F} = \sqrt{\sum_{ij} |\textbf{A}_{ij}|^2} = \sqrt{Tr(A A^H)}
\tag{3 - 1 - 8}
$$
矩阵的max范数定义为矩阵所有元素绝对值的最大值:
$$
||\textbf{A}||_{max} = \max_{ij} |\textbf{A}_{ij}|
\tag{3 - 1 - 9}
$$
矩阵的KF范数定义为矩阵奇异值的1范数:
$$
||\textbf{A}||_{KF} = ||sing(\textbf{A})||_1
\tag{3 - 1 - 10}
$$
上式中$||sing(\textbf{A})||$表示矩阵A的奇异值向量。
一些比较有意思的矩阵。
托普利兹矩阵(Toeplitz),简称为T矩阵,又称常对角矩阵,定义为如下形式:
$$
\textbf{A} =
\begin{bmatrix}
a_0 & a_{-1} & a_{-2} &\cdots & \cdots & a_{-(n-1)}\\
a_1 & a_0 & a_{-1} &\ddots & \ddots & \vdots \\
a_2 & a_1 & a_{0} &\ddots & \ddots & \vdots \\
\vdots & \ddots & \ddots &\ddots & a_{-1} & a_{-2} \\
\vdots & \ddots & \ddots &a_1 & a_0 & a_{-1} \\
a_{n-1} & \cdots & \cdots &a_2 & a_1 & a_0 \\
\end{bmatrix}
\tag{3 - 2 - 1}
$$
Toeplitz矩阵的特点是其第$i$行$j$列的元素,等于其左上角即$i-1$行$j - 1$列的元素,数学表达式为:
$$
\textbf{A}_{i,j} = \textbf{A}_{i-1,j-1}
\tag{3 - 2 - 2}
$$
Toeplitz矩阵其元素完全由第1行和第1列的$2n-1$个元素就能确定。它之所以叫常对角矩阵,是因为其左上至右下的每一条对角线上的所有元素都是相同的,T矩阵没有要求必须是方阵。
下面是一个数字信号处理领域经常用到的特殊T型矩阵,它不但具备T型阵的特点,还是一个对称阵,如下所示:
$$
\textbf{T} =
\begin{bmatrix}
t_0 & t_1 & t_2 &\cdots & t_n\\
t_1 & t_0 & t_1 &\cdots & t_{n-1}\\
t_2 & t_1 & t_0 &\cdots & t_{n-2}\\
\vdots & \vdots & \vdots &\ddots & \vdots\\
t_n & t_{n-1} & t_{n-2} &\cdots & t_0\\
\end{bmatrix}
\tag{3 - 2 - 3}
$$
再来,下面是一个前向移位矩阵:
$$
\textbf{T} =
\begin{bmatrix}
0 & 1 &\cdots & 0\\
0 & \ddots & \ddots &\vdots\\
\vdots & \ddots & \ddots &1\\
0 & \cdots & 0 &0\\
\end{bmatrix}
\tag{3 - 2 - 4}
$$
Toeplitz矩阵的另一个重要特性是可以用来做卷积!
$$
y = h*x =
\begin{bmatrix}
h_1 & 0 & \cdots & 0 & 0\\
h_2 & h_1 & \cdots & \vdots & \vdots\\
h_3 & h_2 & h_1 & \cdots & 0\\
\vdots & \vdots & \vdots &\ddots & \vdots\\
0 & 0 & \cdots & h_{m-1} & h_{m-2}\\
\vdots & \vdots & \vdots & h_{m} & h_{m-1}\\
0 & 0 & 0 & \cdots & h_{m}\\
\end{bmatrix}
\begin{bmatrix}
x_1\\
x_2\\
x_3\\
\vdots\\
x_n\\
\end{bmatrix}
\tag{3 - 2 - 5}
$$
上式的另一种表达形式为:
$$
y^T =
\begin{bmatrix}
h_1 & h_2 &h_3 & \cdots &h_n\\
\end{bmatrix}
\begin{bmatrix}
x_1 & x_2 & x_3 & \cdots & x_n & 0 & 0 & 0 & \cdots & 0\\
0 & x_1 & x_2 & x_3 & \cdots & x_n & 0 & 0 & \cdots & 0\\
0 & 0 & x_1 & x_2 & x_3 & \cdots & x_n & 0 & \cdots & 0\\
\vdots & \vdots & \vdots &\vdots & \vdots & \cdots & \vdots & \vdots & \cdots & 0\\
0 & \cdots & 0 & 0 & x_1 & \cdots & x_{n-2} & x_{n-1} & x_n & \vdots\\
0 & \cdots & 0 & 0 & 0 & x_1 & \cdots & x_{n-2} & x_{n-1} & x_n\\
\end{bmatrix}
\tag{3 - 2 - 6}
$$
一些比较有意思的矩阵。
汉克尔矩阵(Hankle),是方阵,其每一条副对角线上的元素均相等,定义为如下形式:
$$
\textbf{A} =
\begin{bmatrix}
a_0 & a_1 & a_2 &\cdots & \cdots & a_{(n-1)}\\
a_1 & a_2 & a_3 &\ddots & \ddots & \vdots \\
a_2 & \vdots & \vdots &\ddots & \ddots & \vdots \\
\vdots & \ddots & \ddots &\ddots & \ddots & a_{2n-4} \\
\vdots & \ddots & \ddots &\ddots & a_{2n-4} & a_{2n-3} \\
a_{n-1} & \cdots & \cdots &a_{2n-4} & a_{2n-3} & a_{2n-2} \\
\end{bmatrix}
\tag{3 - 2 - 7}
$$
Hankle矩阵的特点是其第$i$行$j$列的元素,等于对称位置即$j$行$i$列的元素,数学表达式为:
$$
\textbf{A}_{i,j} = \textbf{A}_{j,i}
\tag{3 - 2 - 8}
$$
Hankle矩阵其是一个对称阵,它与上面所说来的Toeplitz矩阵有着紧密联系,仔细观察可以发现,Hankle矩阵自右上至左下的每一条对角线的所有元素均是相同的,刚好与T矩阵相反。
若$H \in C^{n \times n},T \in C^{n \times n}$即H、T分别表示$n \times n$的Hankle、Toeplit方阵,那么它们之间的相互转换关系为:
$$
\begin{cases}
\begin{split}
\textbf{T} &= \textbf{J}_n \cdot \textbf{H} = \textbf{H} \cdot \textbf{J}_n \\
\\
\textbf{H} &= \textbf{J}_n \cdot \textbf{T} = \textbf{T} \cdot \textbf{J}_n \\
\end{split}
\end{cases}
\tag{3 - 2 - 9}
$$
对于非方阵,则有:
$$
\textbf{H}_{m \times n} = \textbf{T}_{m \times n} \cdot \textbf{J}_n \\
\tag{3 - 2 - 10}
$$
式中$\textbf{J}_n$表示n阶反向方阵,即:
$$
\textbf{J}_n =
\begin{bmatrix}
0 & 0 & 0 &\cdots & 0 & 1\\
0 & 0 & 0 &\ddots & 1 & 0 \\
\vdots & \vdots & \vdots &\vdots & \vdots & \vdots \\
0 & 1 & 0 &\cdots & 0 & 0 \\
1 & 0 & 0 & \cdots & 0 & 0 \\
\end{bmatrix}
\tag{3 - 2 - 11}
$$
给定Hankel、Toeplitz矩阵如下:
$$
\textbf{T}_{5 \times 5} =
\begin{bmatrix}
a & b & c &d & e\\
f & a & b & c &d\\
g & f & a & b & c\\
h & g & f & a & b\\
i & h & g & f & a\\
\end{bmatrix} \quad , \quad
\textbf{H}_{5 \times 5} =
\begin{bmatrix}
a & b & c &d & e\\
b & c & d & e &f\\
c & d & e &f & g\\
d & e &f & g & h\\
e &f & g & h & i\\
\end{bmatrix}
\tag{3 - 2 - 12}
$$
则$\textbf{T}_{5 \times 5} \cdot \textbf{J}_{5 \times 5}$结果如下:
$$
\textbf{T} \cdot \textbf{J} =
\begin{bmatrix}
a & b & c &d & e\\
f & a & b & c &d\\
g & f & a & b & c\\
h & g & f & a & b\\
i & h & g & f & a\\
\end{bmatrix} \cdot
\begin{bmatrix}
0 & 0 & 0 &0 & 1\\
0 & 0 & 0 & 1 &0\\
0 & 0 & 1 & 0 & 0\\
0 & 1 & 0 & 0 & 0\\
1 & 0 & 0 & 0 & 0\\
\end{bmatrix} =
\begin{bmatrix}
e & d & c &b & a\\
d & c & b & a &f\\
c & b & a & f & g\\
b & a & f & g & h\\
a & f & g & h & i\\
\end{bmatrix}
\tag{3 - 2 - 13}
$$
把上式最终结果和$\textbf{H}_{5 \times 5}$对比,虽然元素上没有严格对应(比如前者副对角线上元素是$a$,而后者是$e$),但是前者形式上已经完全符合Hankle矩阵的定义。Wiki上提到的两者的关系,指的就是这个意思,而并不是说元素要严格一致,我之前因为这个原因纠结了很久。
附上Wiki关于两个矩阵关系的原话:
The Hankel matrix is closely related to the Toeplitz matrix (a Hankel matrix is an upside-down Toeplitz matrix).
海森矩阵(Hessian),又称黑塞、海瑟、海塞矩阵,定义为如下形式:
$$
\textbf{H(f)} =
\begin{bmatrix}
\frac{\partial^2f}{\partial x^2_1} & \frac{\partial^2f}{\partial x_1 \partial x_2} &\cdots & \frac{\partial^2f}{\partial x_1 \partial x_n}\\
\frac{\partial^2f}{\partial x_2 \partial x_1} & \frac{\partial^2f}{\partial x^2_2} &\cdots & \frac{\partial^2f}{\partial x_2 \partial x_n}\\
\vdots & \vdots & \ddots &\vdots\\
\frac{\partial^2f}{\partial x_n \partial x_1} & \frac{\partial^2f}{\partial x_n \partial x_2} &\cdots & \frac{\partial^2f}{\partial x^2_n}\\
\end{bmatrix}
\tag{3 - 2 - 14}
$$
Hessian矩阵是由多变量函数的二阶偏导数组成的,数学表达式为:
$$
\textbf{H(f)}_{i,j}(x) = D_i D_j f(x)
\tag{3 - 2 - 15}
$$
如果f函数在区域D内的每个二阶导数都是连续的,即有:
$$
\frac{\partial}{\partial x} (\frac{\partial f}{\partial y}) = \frac{\partial}{\partial y} (\frac{\partial f}{\partial x})
\tag{3 - 2 - 16}
$$
此时海森矩阵在D区域内为对称矩阵。海森矩阵对应的行列式可以用来判定$f$的临界点属于鞍点还是极值点。判别依据如下:
或者按如下的条件来判断:
关于Hessian矩阵的其它参考资料罗列如下:
埃尔米特矩阵(Hermitian),又称为自共轭矩阵,定义为如下形式:
$$
\textbf{A} =
\begin{bmatrix}
a_{11} & a_{12} &\cdots & a_{1n}\\
\overline{a_{12}} & a_{22} &\cdots & a_{2n}\\
\vdots & \vdots & \ddots &\vdots\\
\overline{a_{1n}} & \overline{a_{2n}} &\cdots & a_{nn}\\
\end{bmatrix}
\tag{3 - 2 - 17}
$$
式中,$\overline{(\cdot)}$表示共轭算子。其数学表达式为:
$$
a_{ij} = \overline{a_{ji}} \quad 或 \quad \textbf{A} = \overline{\textbf{A}^T} \quad 或 \quad \textbf{A} = \textbf{A}^H
\tag{3 - 2 - 18}
$$
Hermitian矩阵的主对角线上的元素必须是实数,如果非对角线不包含虚数的对称矩阵也是Hermitian矩阵(特例)。对于满足如下条件的矩阵我们则称为反Hermitian矩阵(对称位置元素互为共轭相反数)。
$$
a_{ij} = -\overline{a_{ji}} \quad 或 \quad \textbf{A} = -\overline{\textbf{A}^T} \quad 或 \quad \textbf{A} = -\textbf{A}^H
\tag{3 - 2 - 19}
$$
Hermitian矩阵的主要性质如下:
关于Hermitian矩阵的其他参考资料:
范德蒙矩阵(Vandermonde),定义为如下形式:
$$
\textbf{V} =
\begin{bmatrix}
1 & \alpha_1 & \alpha^2_1 &\cdots & \alpha^{n-1}_1\\
1 & \alpha_2 & \alpha^2_2 &\cdots & \alpha^{n-1}_2\\
\vdots & \vdots & \vdots & \ddots &\vdots\\
1 & \alpha_m & \alpha^2_m &\cdots & \alpha^{n-1}_m\\
\end{bmatrix}
\tag{3 - 2 - 20}
$$
该矩阵的特点是,每一行都是一个等比级数。其数学表达式为:
$$
\textbf{V}_{i,j} = \alpha^{j-1}_i
\tag{3 - 2 - 21}
$$
Vandermonde的性质不多,比较明显的是,其对应的行列式的值。
$$
\begin{split}
det(\textbf{V}) &= \prod_{1 \leq i \leq j \leq n} (\alpha_j - \alpha_i) \\
&= (\alpha_2 - \alpha_1) \cdot \\
& \quad \ (\alpha_3 - \alpha_2) \cdot (\alpha_3 - \alpha_1) \\
& \quad \ (\alpha_4 - \alpha_3) \cdot (\alpha_4 - \alpha_2) \cdot (\alpha_4 - \alpha_1) \\
& \quad \qquad \ \vdots \qquad \qquad \ \vdots \qquad \qquad \ \vdots \\
& \quad \ (\alpha_n - \alpha_{n-1}) \cdot (\alpha_n - \alpha_{n-2}) \cdots (\alpha_n - \alpha_1) \\
\end{split}
\tag{3 - 2 - 22}
$$
汉密尔顿矩阵(Hamiltonian),定义为满足如下性质的矩阵:
$$
\textbf{JA}^T = \textbf{JA}
\tag{3 - 2 - 23}
$$
式中的J为2n阶斜对称矩阵(反对称矩阵)
$$
\textbf{J} =
\begin{bmatrix}
0 & \textbf{I}_n\\
-\textbf{I}_n & 0\\
\end{bmatrix}
\tag{3 - 2 - 24}
$$
如果把Hamiltonian矩阵写成如下的分块阵:
$$
\textbf{A} =
\begin{bmatrix}
a & b\\
c & d\\
\end{bmatrix}
\tag{3 - 2 - 25}
$$
则有如下特性:
Just make it easier, easier to understand and beautiful!
Python绘图包括了2D和3D,不同的类型使用的库不同,本系列主要讲3D图形的绘制。包括3D曲面图、3D散点图以及在3D曲面图上绘制有向、无向线。
本文中软件运行环境如下:
项 | 参考值 | 备注 |
---|---|---|
OS | Win7_X64 | 旗舰版 |
Python | 3.4.3 | 32bit版 |
IDE | WinPython-32bit | 3.4.3.7Qt5 |
3D曲面和散点的绘制用的都是mpl_toolkits库的mplot3d模块中的Axes3D来完成。曲面的绘制调用的是函数plot_surface()。下述代码将绘制一个3D锥面。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29# -*- coding: utf-8 -*-
"""
Created on Mon May 8 12:20:29 2017
@author: flat2010
"""
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 创建3D对象
fig = plt.figure()
ax = Axes3D(fig)
# 分别生成x、y坐标数据
xcord = np.arange(-5, 5, 0.2)
ycord = np.arange(-5, 5, 0.2)
# 将坐标向量转换成坐标矩阵(vectors -> matrices)
xcord, ycord = np.meshgrid(xcord, ycord)
# 生成z坐标数据
r = np.sqrt(xcord**2 + ycord**2)
zcord = r
# 绘制曲面图
ax.plot_surface(xcord, ycord, zcord, rstride=1, cstride=1, cmap='rainbow')
plt.show()
绘制成的图形如下图1-1所示:
Axes3D.plot_surface(X, Y, Z, *args, **kwargs)函数的常用参数官方解释如下:
参数名 | 英文说明 | 中文解释 |
---|---|---|
X, Y, Z | Data values as 2D arrays | x、y、z三个坐标轴的数据,均为二维数组 |
rstride | Array row stride (step size) | 二维数组行增量,如该值取2时,二维数组中只有序号为0、2、4···的数据才会被绘制 |
lstride | Array row ltride (step size) | 二维数组列增量,作用同rstride |
color | Color of the surface patches | 曲面图中像素块的颜色,如果同时设置了cmap参数该值会被cmap覆盖 |
cmap | A colormap for the surface patches | 曲面图中像素块的颜色图,可提供多个值 |
cmap | A colormap for the surface patches | 曲面图中像素块的颜色图,可提供多个值 |
facecolors | Face colors for the individual patches | 指定像素块的颜色,二维数组[rs][cs] |
norm | An instance of Normalize to map values to colors | Normalize(标准化)对象,负责将不同的值映射为不同的颜色 |
vmin | Minimum value to map | 规范化的最小值 |
vmax | Maximum value to map | 规范化的最小值 |
shade | Whether to shade the facecolors | 是否屏蔽facecolors |
3D散点图和3D曲面图有所不同的是,创建绘图对象时,使用的是add_subplot()函数(实测使用Axes3D也可以),代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30# -*- coding: utf-8 -*-
"""
Created on Mon May 8 12:20:29 2017
@author: flat2010
"""
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 创建3D对象
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# 分别生成x、y坐标数据
xcord = np.arange(-5, 5, 0.5)
ycord = np.arange(-5, 5, 0.5)
# 将坐标向量转换成坐标矩阵(vectors -> matrices)
xcord, ycord = np.meshgrid(xcord, ycord)
# 生成z坐标数据
r = np.sqrt(xcord**2 + ycord**2)
zcord = r
# 绘制散点图,将图形分成两部分,用不同颜色绘制
ax.scatter(xcord[:10], ycord[:10], zcord[:10], c='r')
ax.scatter(xcord[10:], ycord[10:], zcord[10:], c='g')
plt.show()
坐标点的数据仍然是上面的3D圆锥面。绘制形成的图形如下所示:
Axes3D.scatter(xs, ys, zs=0, zdir=’z’, s=20, c=’b’, *args, **kwargs)函数的常用参数官方解释如下:
参数名 | 英文说明 | 中文解释 |
---|---|---|
xs, ys | Positions of data points | x、y两个个坐标轴的数据 |
zs | Either an array of the same length as xs and ys or a single value to place all points in the same plane. Default is 0 | z轴数据,即可以是数组(与x、y等长),也可以是一个数字(即为二维图形) |
zdir | Which direction to use as z (‘x’, ‘y’ or ‘z’) when plotting a 2D set | 若提供的是一个二维数据集时,使用哪个方向的数据作为z轴数据 |
s | size in points^2. It is a scalar or an array of the same length as x and y | 数据点在图中的显示大小(标量或与x、y等维度的向量) |
c | a color. c can be a single color format string, or a sequence of color specifications of length N, or a sequence of N numbers to be mapped to colors using the cmap and norm specified via kwargs (see below)… | 数据点颜色,即可以是字符串形式的标量,也可以是长度N的颜色序列,也可以是通过cmap或norm映射的长度为N的颜色序列,但不能是单个RGB值或序列,可以是二维数组。 |
该模块还提供了其它的一些常见图形,如:
详细用法及说明可参见matplot官方文档。
]]>The road to dreams is not only lonely, but also rugged!
在本系列的前两章主要是对SVM的一些相关概念做了介绍,对于只想简单了解SVM原理的来说,已经足够了。但对于想进一步深入理解SVM的来说,收拾好你的行囊,整理好你的思绪,踏上新的征程吧,前面的路途不但遥远,而且崎岖。
本章内容主要是SVM的理论证明部分,前方各种公式定理高能预警!以下内容可能会引起不适,主要表现为犯困、头晕、有暴力冲动,请自行选择是否继续。
所谓支持向量(Support Vector),是指训练数据集的样本点(线性可分)中与分离超平面距离最近的样本点。在机器学习算法系列之三:SVM(2)中我们讲到了分割超平面求解的等价问题,以归一化(函数间隔$\hat{\gamma} = 1$)后的问题来说,求解出的超平面必然使得所有点都满足:
$$
y_i(\vec w · x_i + b) \geq 1
\tag{1 - 1}
$$
即所有点到分割超平面的距离都不小于1,那么支持向量就是指这些到超平面的距离刚好等于1的点。其实决定参数$\vec w、b$的,正是这些点。这也是支持向量机这个称谓的由来,即由一组支持向量控制决定的分类模型。
请注意,缓冲带这个词并非术语,是我根据它的作用起的一个名字,方便大家理解它的功能。它的专业术语称作margin,是由过支持向量并与分割超平面平行的两个平面所围起来的部分,由于支持向量到分割超平面的距离均为1,所以分割超平面位与这两个平面的中间位置,如下图所示:
如图上所示,L1、L2上的点就是支持向量,黄色粗直线为分割超平面(由$\vec w、b$决定),L1、L2之间的距离就是margin = $\frac{2}{||w||}$,L1、L2之间的区域即为缓冲带(缓冲区),L1、L2到分割超平面的距离均为几何距离 = $\frac{1}{||w||}$。
之所以把L1和L2之间的区域称为缓冲带,是因为任意一边的样本点发生抖动时,都不会立即越过分割超平面被误判为另一种类型的样本,这个安全距离就是$\frac{1}{||w||}$,缓冲带的存在确保了SVM的泛化能力和抗噪能力。
同时由上图可知,分割超平面只与支持向量有关,去掉或者移动其它非支持向量(不越过支持向量),并不会影响最终的解。SVM分类模型训练的本质就是找到这些支持向量。
给定数据集T(线性可分),则有且仅有一个如上所述的分割超平面存在,使得几何间隔$\gamma$最大。即机器学习算法系列之三:SVM(2)中的式(2-14)有且仅有一个解,现在分别从存在性和唯一性方面进行证明。为了方便阅读,我把前面提到的公式(2-14)贴在这里。
$$
\begin{cases}
\min \limits_{w,b}{\frac{1}{2} {||w||}}^2\\\\
y_i \lgroup {\vec w} · \vec x_i + {b} \rgroup - 1 \geq 0, \ \ i = 1,2,···,N
\end{cases}
\tag{*2 - 14}
$$
存在性的证明相对比较简单。因为给定的数据集$T$线性可分(非线性可分其实也有解,后面会讲),所以式(2-14)一定有解。对式子中的最小化问题有:
$$
\min \limits_{w,b}{\frac{1}{2} {||w||}}^2 =
\min \limits_{w,b}{\frac{1}{2} \sqrt{w_1^2 + w_2^2 + ··· + w_n^2}^2}
$$
设$w_i^2 \geq w_k^2,i \neq k,i = 1,2,···,n$,则有:
$$
\min \limits_{w,b}{\frac{1}{2} {||w||}}^2 \geq
{\frac{1}{2} \sqrt{n·w_k^2}^2} \geq
{\frac{n}{2}} w_k^2
$$
又因为$n、w_k$都是有界实数,所以必然有:
$$
\min \limits_{w,b}{\frac{1}{2} {||w||}}^2 \geq
K,K \in R^+且K \nrightarrow \infty
\tag{1 - 2}
$$
即$\min \limits_{w,b}{\frac{1}{2} {||w||}}^2$必然有解。记最优解为$(w^*, b^*)$,可知$w^* \neq 0$,注意这点很重要,因为$w^* = 0$就会导致$||w|| = 0$,也即几何间隔不存在。
对唯一性的证明,我们用反证法。设(2-14)的解存在且不唯一,记$(\vec w_1^*,b_1^*)、(\vec w_2^*,b_2^*)$分别为式(2-14)的两个最优解。
因为$(\vec w_1^*,b_1^*)、(\vec w_2^*,b_2^*)$均为最优解,则$(\vec w_1^*,b_1^*)、(\vec w_2^*,b_2^*)$必然均满足目标函数:
$$
\min \limits_{w,b}{\frac{1}{2} {||w||}}^2
\tag{1 - 3}
$$
因此必有:
$$
\min \limits_{w,b}{\frac{1}{2} {||w||}}^2 =
{\frac{1}{2} {||\vec w_1^*||}}^2 =
{\frac{1}{2} {||\vec w_2^*||}}^2 =
m,m \in R^+,常数
$$
所以必有:
$$
||\vec w_1^*|| = ||\vec w_2^*|| = c,c \in R^+,常数
\tag{1 - 4}
$$
取$\vec w^* = \frac{\vec w_1^* + \vec w_2^*}{2},b = \frac{b_1^* + b_2^*}{2}$,则:
$$
\begin{split}
\min \limits_{\vec w^*,b^*}{\frac{1}{2} {||\vec w^*||^2 } }
& =
\min \limits_{\vec w^*,b^*}{\frac{1}{2} {|| \frac{\vec w_1^* + \vec w_2^*}{2} ||^2} }\\\\
& =
\min \limits_{\vec w^*,b^*}{\frac{1}{8} {|| {\vec w_1^* + \vec w_2^*} ||^2} }\\\\
\end{split}
$$
由矢量合成法则可知:
$$
||\vec w_1^* + \vec w_2^*|| = ||\vec w^*|| \leq ||\vec w_1^*|| + ||\vec w_2^*||
\tag{1 - 5}
$$
当且仅当$\vec w_1^*、\vec w_2^*$同向即$\vec w_1^* = \lambda · \vec w_2^*,\lambda \in R^+$时上式取等号。
由此有:
$$
\begin{split}
\min \limits_{\vec w^*,b^*}{\frac{1}{2} {||\vec w^*||^2 } }
& =
\min \limits_{\vec w^*,b^*}{\frac{1}{8} {|| {\vec w_1^* + \vec w_2^*} ||^2} }\\\\
& \leq
\min \limits_{\vec w^*,b^*}{\frac{1}{8} {(|| {\vec w_1^*|| + ||\vec w_2^*} ||)^2} } \\\\
&=
\min \limits_{\vec w^*,b^*}{\frac{1}{8} { ( ||\vec w_1^*||^2 + ||\vec w_2^* ||^2+ 2||\vec w_1^*||·||\vec w_2^*|| )} }
\end{split}
$$
$\because$
$$
||\vec w_1^*|| = ||\vec w_2^*||
$$
$\therefore$
$$
\begin{split}
\min \limits_{\vec w^*,b^*}{\frac{1}{2} {||\vec w^*||^2 } }
& \leq
\min \limits_{\vec w^*,b^*}{\frac{1}{8} (||\vec w_1^*||^2 + ||\vec w_1^*||^2 + 2||\vec w_1^*||·||\vec w_1^*||)} \\\\
&=
\min \limits_{\vec w^*,b^*}{\frac{1}{8} · 4 · ||\vec w_1^*||^2} \\\\
& =
\min \limits_{\vec w^*,b^*}{ \frac{1}{2} {||\vec w_1^*||^2 } } \\\\
& =
m
\end{split}
$$
即$\vec w^* = \frac {\vec w_1^* + \vec w_2^*}{2}$也是原始问题的权重系数向量解。
对不等式约束条件,有:
$$
\begin{split}
y_i \lgroup {\vec w^*} · \vec x_i + {b^*} \rgroup - 1 &=
y_i \lgroup { \frac{\vec w_1^* + \vec w_2^*}{2}} · \vec x_i + \frac{b_1^* + b_2^*}{2} \rgroup - 1 \\\\
&=
\frac{1}{2} y_i \lgroup { \vec w_1^*} · \vec x_i + b_1^* + {\vec w_2^*} + b_2^* \rgroup - 1 \\\\
&=
\frac{1}{2} y_i \lgroup { \vec w_1^*} · \vec x_i + b_1^* \rgroup +
\frac{1}{2} y_i \lgroup { \vec w_2^*} · \vec x_i + b_2^* \rgroup - 1
\end{split}
$$
又因为$\vec w_1^*、\vec w_2^*$满足:
$$
\begin{cases}
y_i \lgroup {\vec w_1^*} · \vec x_i + {b^*} \rgroup - 1 \geq 0, \ \ i = 1,2,···,N \\\\
y_i \lgroup {\vec w_2^*} · \vec x_i + {b^*} \rgroup - 1 \geq 0, \ \ i = 1,2,···,N
\end{cases}
$$
所以有:
$$
\begin{split}
y_i \lgroup {\vec w^*} · \vec x_i + {b^*} \rgroup - 1 &\geq
\frac{1}{2} · 1 +
\frac{1}{2} · 1 - 1 =
1 - 1 =
0
\end{split}
$$
即$\vec w^* = \frac {\vec w_1^* + \vec w_2^*}{2}、b^* = \frac{b_1^* + b_2^*}{2}$同时还满足不等式的约束条件。
综上两点可知$\vec w^* = \frac {\vec w_1^* + \vec w_2^*}{2}、b^* = \frac{b_1^* + b_2^*}{2}$也是最优解!
由上可知$\vec w^*$必然也满足式(1-3)的目标函数的约束,即有:
$$
\min \limits_{\vec w,b}{\frac{1}{2} {||\vec w^*||}}^2 =
{\frac{1}{2} {||\vec w_1^*||}}^2 =
{\frac{1}{2} {||\vec w_2^*||}}^2 =
m,m \in R^+,常数
$$
因此有式(1-4)的扩展:
$$
||\vec w^*|| = ||\vec w_1^*|| = ||\vec w_2^*|| = c,c \in R^+,常数
\tag{1 - 6}
$$
所以有:
$$
||\vec w^*|| = || \frac {\vec w_1^* + \vec w_2^*}{2} || = \frac {|| \vec w_1^* + \vec w_2^* ||}{2} = ||\vec w_1^*|| = ||\vec w_2^*||
\tag{1 - 7}
$$
所以必有:
$$
\vec w_1^* = \vec w_2^*
\tag{ 1- 8}
$$
注意,上式(1-8)是两个向量相等!相当于之前的限制条件$\vec w_1^* = \lambda · \vec w_2^*,\lambda \in R^+$中的$\lambda = 1$时的情况,这种情况下$\vec w_1^*、 \vec w_2^*$的关系的限定更为严格和明确。对于式(1-8)的证明如下:
$$
\begin{split}
\frac {|| \vec w_1^* + \vec w_2^* ||}{2} &= \frac {1}{2} · \sqrt { \sum_{i=1}^n (w_{1i} + w_{2i})^2} \\\\
&= \frac {1}{2} · \sqrt { w_{11}^2 + w_{21}^2 + 2·w_{11}w_{21} + ··· +w_{1n}^2 + w_{2n}^2 + 2·w_{1n} w_{2n} } \\\\
&= \frac {1}{2} · \sqrt { (w_{11}^2 + ··· + w_{1n}^2) + (w_{21}^2 + ··· + w_{2n}^2) + 2·(w_{11} w_{21} + ··· +w_{1n} w_{2n}) } \\\\
||\vec w_1^*|| &= \sqrt { w_{11}^2 + ··· + w_{1n}^2 }
\end{split}
$$
由式(1-7)利用已知条件$||\vec w^*|| = ||\vec w_1^*||$有:
$$
\sqrt { w_{11}^2 + ··· + w_{1n}^2 } = \frac {1}{2} · \sqrt { (w_{11}^2 + ··· + w_{1n}^2) + (w_{21}^2 + ··· + w_{2n}^2) + 2·(w_{11} w_{21} + ··· +w_{1n} w_{2n}) }
$$
再对等式两边取平方有:
$$
w_{11}^2 + ··· + w_{1n}^2 = \frac {1}{4} [ (w_{11}^2 + ··· + w_{1n}^2) + (w_{21}^2 + ··· + w_{2n}^2) + 2·(w_{11} w_{21} + ··· +w_{1n} w_{2n}) ]
$$
两边同时乘以4,并化简有:
$$
3w_{11}^2 + ··· + 3w_{1n}^2 = w_{21}^2 + ··· + w_{2n}^2 + 2·(w_{11} w_{21} + ··· +w_{1n} w_{2n})
\tag{1 - 9}
$$
同时我们有$||\vec w_1^*|| = ||\vec w_2^* ||$即:
$$
w_{11}^2 + ··· + w_{1n}^2 = w_{21}^2 + ··· + w_{2n}^2
$$
回代入式(1-9)并化简有:
$$
w_{11}(w_{11} - w_{21}) + ··· + w_{1n}(w_{1n} - w_{2n}) = 0
\tag{1 - 10}
$$
同理,再利用式(1-7)已知条件$||\vec w^*|| = ||\vec w_2^*||$可得到:
$$
w_{21}(w_{21} - w_{11}) + ··· + w_{2n}(w_{2n} - w_{1n}) = 0
\tag{1 - 11}
$$
将式(1-10)和式(1-11)相加,并提取公因式有:
$$
(w_{11} - w_{21})^2 + ··· + (w_{1n} - w_{2n})^2 = 0
\tag{1 - 12}
$$
于是可解得:
$$
\begin{cases}
w_{11} = w_{21} \\\\
w_{12} = w_{22} \\\\
\cdots \ \ \ \cdots \\\\
w_{1n} = w_{2n}
\end{cases}
$$
即向量$\vec w_1^* = \vec w_2^*$,为同一向量。
证毕!!!
上一节我们已经证明了$\vec w_1^* = \vec w_2^*$,为了方便下面的证明,我们记$\vec w_1^* = \vec w_2^* = \vec w^*$。同时我们设$(\vec x_1’, 1)、(\vec x_2’, 1)$是正样本($x_i|y_i = +1$)中分别对应于($\vec w^*, b_1^*$)、($\vec w^*, b_2^*$)的支持向量,而$(\vec x_1’’, -1)、(\vec x_2’’, -1)$则是负样本($x_i|y_i = -1$)中分别对应于($\vec w^*, b_1^*$)、($\vec w^*, b_2^*$)的支持向量,则$(\vec x_1’, 1)、(\vec x_2’, 1)、(\vec x_1’’, -1)、(\vec x_2’’, -1)$也必然是使得不等式(1-1)取等号的点,即有:
$$
\begin{cases}
+1·(\vec w^* · \vec x_1’ + b_1^*) - 1 = 0 \\\\
-1·(\vec w^* · \vec x_1’’ + b_1^*) - 1 = 0 \\\\
\tag{1 - 13}
\end{cases}
$$
$$
\begin{cases}
+1·(\vec w^* · \vec x_2’ + b_2^*) - 1 = 0 \\\\
-1·(\vec w^* · \vec x_2’’ + b_2^*) - 1 = 0 \\\\
\tag{1 - 14}
\end{cases}
$$
分别将式(1-13)、(1-14)的两个等式相减可求出$b_1^*、b_2^*$的表达式如下:
$$
\begin{cases}
b_1^* = -\frac {1}{2} ( \vec w^* · \vec x_1’ + \vec w^* · \vec x_1’’ ) \\\\
b_2^* = -\frac {1}{2} ( \vec w^* · \vec x_2’ + \vec w^* · \vec x_2’’ )
\tag{1 - 15}
\end{cases}
$$
再将式(1-15)的两个等式相减可得:
$$
b_1^* - b_2^* = -\frac {1}{2} [ \vec w^* · (\vec x_1’ - \vec x_2’) + \vec w^* · (\vec x_1’’ - \vec x_2’’)]
\tag{1 - 16}
$$
由最开始的假设可知,$(\vec x_1’, 1)$是超平面($\vec w^*, b_1^*$)的支持向量,但$(\vec x_2’, 1)$并不一定是该超平面的的支持向量,所以必有:
$$
\vec w^* · \vec x_2’ + b_1^* \geq 1 = \vec w^* · \vec x_1’ + b_1^*
$$
上式的几何意义就是:非支持向量到超平面的距离必然不小于支持向量到超平面的距离。上式化简后可得到:
$$
\vec w^* · (\vec x_2’ - \vec x_1’) \geq 0
\tag{1 - 17}
$$
同理还可得到:
$$
\vec w^* · \vec x_1’ + b_2^* \geq 1 = \vec w^* · \vec x_2’ + b_2^*
$$
即:
$$
\vec w^* · (\vec x_2’ - \vec x_1’) \leq 0
\tag{1 - 18}
$$
结合式(1-17)和(1-18)则有$\vec w^* · (\vec x_1’ - \vec x_2’) = 0$,这两个是正样本,再对两个负样本应用相应的公式则可得$\vec w^* · (\vec x_1’’ - \vec x_2’’) = 0$,将这两个等式回代入式(1-16)可得$b_1^* - b_2^* = 0$,即有:
$$
b_1^* = b_2^*
$$
结合上一节证明的$\vec w_1^* = \vec w_2^*$,可知我们之前假设的两个最优解$(w_1^*, b_1^*)$和$(w_2^*, b_2^*)$实际上是同一个解,至此,解的唯一性亦得证!
本章主要对分割超平面的存在性和唯一性进行了证明,而这也是我们接下来研究内容的基石,纵观这两点的证明,可以说是非常的巧妙和严谨,这也是我在开篇时提到过的,对于SVM,我一直把它当做一件精湛的艺术品。
]]>