图解格拉姆-施密特正交化和改进的格拉姆-施密特正交化

Gram-Schmidt正交化

格拉姆-施密特(Gram-Schmidt)正交化常用于求解向量空间的标准正交基,同时也是一种天然的求解矩阵的QR分解的方法,即将一个矩阵A分解为一个正交矩阵Q和一个上三角矩阵R的乘积,即A=QR。这里我们假设A是一个方阵,当然A不是方阵的时候也是可以进行QR分解的。QR分解可用于线性方程组的求解,并且使得求解的过程更加高效、稳定,这里不细说,我们重点关注Gram-Schmidt正交化的过程和原理。

Classical Gram-Schmidt

我们普遍在课本中主要接触到Gram-Schmidt正交化属于传统的正交化方法,其相对容易理解,但是在计算机中,由于数值计算的精度问题,其计算的误差累积较快,当矩阵维数较高的时候具有较差的稳定性。其误差影响两个方面,一是矩阵Q的正交性,矩阵的正交性使得其逆矩阵可以直接通过矩阵的转置得到,如果正交性被破坏,那么会造成计算的错误;二是矩阵R元素的精度问题,越往后面计算所得的元素误差越大。具体可以看一下下面的网页中的demo。总的来说传统方法的误差水平大约为计算机精度的平方根。

https://nbviewer.jupyter.org/github/mitmath/18335/blob/master/notes/Gram-Schmidt.ipynb

传统的Gram-Schmidt正交化(CGS)方法类似于拼积木,如下图的例子。其先拿出第一个向量作为基准,接着拿出第二个向量,并通过对照第一个基准找到垂直于第一个基准的第二个基准,并依次类推。因此,在构建第n个基准时,n+1及以后的向量是不可见的。因为在计算的过程中不可避免地引入误差,而后面的向量只有在其被计算的时候才可见,这时误差可能已经积累到一定程度了,所以总的来说其稳定性较差。
在这里插入图片描述
对于上图,我们可以将其表示为以下的计算过程,最终我们可以直接得到一个正交矩阵Q和一个上三角矩阵R,这也就是为什么Gram-Schmidt天然就是一种QR分解的方法。
在这里插入图片描述
在这里插入图片描述

Modified Gram-Schmidt

前面已经说了,传统的Gram-Schmidt正交化方法误差累积速度较快,原因在于在当前计算的向量之后的向量是不可见的,使得后面的计算使用的是已经积累了相当误差的结果。因此,一种改进的Gram-Schmidt正交化(MGS)方法被提了出来,其实际上与CGS是等价的,但其对计算的过程进行了重排,使得从一开始所有的向量都是可见的,这样大部分的计算都在误差尚未积累到较大程度的时候就已经被执行,如下图所示。实际上,MGS的计算量越往后是逐步减少的,因此前面计算积累的误差的影响就不会剧烈地扩散开,所以MGS可以使求得的矩阵Q的正交性更好,同时矩阵R的误差也被控制在计算机精度的水平。

与CGS不同,MGS的主要改进思路是,首先选定一个向量作为第一个基准,然后将其与所有向量都投影到该基准的正交空间中,这时第一个基准与正交空间中所有的向量都是垂直的。因此,在此之后,我们只需在该正交空间中,对剩下的向量重复前面的工作,那么最后所有的向量都是相互正交的了。同时,我们也可以得到相应的正交矩阵Q和上三角矩阵R
在这里插入图片描述
对于上图,我们可表示成以下的计算过程。最终我们得到的矩阵和上面的CGS所得到的其实是一样的。
在这里插入图片描述
改进的Gram-Schmidt正交化方法虽然相比于传统方法在矩阵Q的正交性方面虽然有了不少改善,但实际上还是有点不太理想的,具体可以看一下上面的那个链接。相比之下,Householder变换方法在正交性方法能够达到非常高的精度,当然对于求解R的精度与MGS也是相同的水平的,而且其计算复杂度通常会比MGS更低,因此目前在QR分解中更多使用的是Householder变换方法。除此之外,还有一种Givens变换方法。具体关于QR分解的知识可参考这个文档,这里不细说。

https://wenku.baidu.com/view/6872ac728e9951e79b892700.html

Python实现

# -*- coding: utf-8 -*-
import numpy as np

def cgs(A):
    m, n = A.shape
    Q = np.copy(A)
    R = np.zeros([n, n], dtype='float64')
    
    for i in range(n):
        R[:i, i] = A[:, i].T.dot(Q[:, :i])
        Q[:, [i]] = A[:, [i]] - Q[:, :i].dot(R[:i, [i]])
        
        R[i, i] = np.linalg.norm(Q[:, i])
        if np.abs(R[i, i]) < 1e-15:
            Q[:, i] = np.zeros(m, dtype='float64')
            R[i, i] = 0.
        else:
            Q[:, i] /= R[i, i]
    return Q, R

def mgs(A):
    m, n = A.shape
    Q = np.copy(A)
    R = np.zeros([n, n], dtype='float64')
    
    for i in range(n):
        R[i, i] = np.linalg.norm(Q[:, i])
        if np.abs(R[i, i]) < 1e-15:
            Q[:, i] = np.zeros(m, dtype='float64')
            R[i, i] = 0.
        else:
            Q[:, i] /= R[i, i]
        
        R[[i], i+1:] = Q[:, [i]].T.dot(Q[:, i+1:])
        Q[:, i+1:] -= Q[:, [i]].dot(R[[i], i+1:])
    return Q, R
发布了5 篇原创文章 · 获赞 3 · 访问量 553

猜你喜欢

转载自blog.csdn.net/qq_33552519/article/details/103325018