10-09:课程小结-迭代收敛理论、自先关和功率谱估计

连着三天8:00上课,我真的是痩不了了呀。

一、迭代法收敛理论

1.结论:对角占优(对角线元素大)似乎比较有利于收敛,且GS法收敛的更快!

代码验证一下,用一个已知可以求解的例子,分别用雅克比和高斯赛德尔方法迭代求解。令其对角元素逐渐增大,绘制其迭代次数随增大数值的变化曲线。

def Jocobi(A,b,initial,delta,isPrint=False):
    '''
    @author:zengwei
    输入:A是系数矩阵,N阶方阵
          b是N*1列向量
          initial是解的初始值,N*1大小
          delta是我给定的迭代值与真实值之间的误差
    输出:迭代后的求解结果
    '''
    D = np.diag(np.diag(A))             # 获得D矩阵
    L = -np.tril(A,-1)                  # 获得L矩阵
    U = -np.triu(A,1)                   # 获得U矩阵
    try:
        d = np.linalg.inv(D)            # 对D矩阵求逆
    
        BJ = np.dot(d,L+U)              # 迭代矩阵BJ
        lamda,_ = np.linalg.eig(BJ)
        if np.max(np.abs(lamda))<1:     # 谱半径小于1
            f = np.dot(d,b)
            X = np.dot(BJ,initial)+f    # 初次的解
            k = -np.log(delta)/-np.log(np.max(np.abs(lamda)))
            
            BJnorm = np.linalg.norm(BJ)
            times = 1                   # 因为前面有了一次迭代
            while (BJnorm**k/(1-BJnorm))*np.linalg.norm(X - initial)> delta:
                initial = X
                X = np.dot(BJ,initial) + f
                times = times +1
            if isPrint==True:
                print('谱半径为:',np.max(np.abs(lamda)))
                print('理论上的最大迭代次数为:%d次' %(int(k)+1))
                print("实际上的最大迭代次数为:%d次" %times)
            return X,times
        else:
            print('Sorry,不可收敛。')
            print('谱半径为:',np.max(np.abs(lamda)))
    except:
        print('对角矩阵D没有逆矩阵!')
def Seidel(A,b,initial,delta,isPrint=False):
    '''
    @author:zengwei
    输入:A是系数矩阵,N阶方阵
          b是N*1列向量
          initial是解的初始值,N*1大小
          delta是我给定的迭代值与真实值之间的误差
    输出:迭代后的求解结果
    '''
    D = np.diag(np.diag(A)) 
    L = -np.tril(A,-1)
    U = -np.triu(A,1)
    try:
        d = np.linalg.inv( D - L )           # 这里是和雅克比不同的地方

        BG = np.dot(d,U)                     # 迭代矩阵BG
        lamda,_ = np.linalg.eig(BG)
        if np.max(np.abs(lamda))<1:          # 谱半径小于1
            f = np.dot(d,b)
            X = np.dot( BG ,initial ) + f
            k = -np.log(delta)/-np.log(np.max(np.abs(lamda)))
            
            BGnorm = np.linalg.norm(BG)
            times = 1                        # 因为前面有了一次迭代
            while (BGnorm**k/(1-BGnorm))*np.linalg.norm( X - initial ) > delta:
                initial = X
                X = np.dot( BG,initial )+f  
                times = times +1
            if isPrint==True:
                print('谱半径为:',np.max(np.abs(lamda)))
                print('理论上的最大迭代次数为:%d次' %(int(k)+1))
                print("实际上的最大迭代次数为:%d次" %times)
            return X,times
        else:
            print('Sorry,不可收敛。')
            print('谱半径为:',np.max(np.abs(lamda)))
    except:
        print('矩阵[D-L]没有逆矩阵!')
A = np.array([[8,-3,2],[4,11,-1],[6,3,12]],dtype=float)
b = np.array([[20],[33],[36]],dtype=float)
initial = np.zeros((3,1))
delta = 10**(-5)
Jocobi(A,b,initial,delta,isPrint=True)
'''
谱半径为: 0.35924985028455664
理论上的最大迭代次数为:12次
实际上的最大迭代次数为:13次
array([[2.99999933],
       [2.00000577],
       [1.00000419]])
'''
Seidel(A,b,initial,delta,isPrint=True)
'''
谱半径为: 0.13055824196677338
理论上的最大迭代次数为:6次
实际上的最大迭代次数为:7次
array([[3.00000201],
       [1.9999987 ],
       [0.99999932]])
'''
def addII(A,s):
    A = A.copy()
    for i in np.arange(len(A)):
        A[i,i] = A[i,i] + s
    return A

def getTimeslist(N,S,JorGs):
    '''
    增加N次,每次增加s大小,JorGs做算法选择
    '''
    timeslist = []
    n = np.arange(S,N*S+S,S)
    for s in n:
        newA = addII(A,s)
        if JorGs=='Jocobi':
            _,times = Jocobi(newA,b,initial,delta)
        if JorGs=='Seidel':
            _,times = Seidel(newA,b,initial,delta)
        timeslist.append(times)
    timeslist = np.array(timeslist)
    return n,timeslist

N = 50
S = 5
n1,Jtimeslist = getTimeslist(N,S,'Jocobi')
n2,GStimeslist = getTimeslist(N,S,'Seidel')
plt.figure(figsize=(12,5))
plt.scatter(n1,Jtimeslist)
plt.plot(n1,Jtimeslist)
plt.scatter(n2,GStimeslist)
plt.plot(n2,GStimeslist)
plt.title('对角元素加%d的迭代次数变化图'%S)
plt.legend(['Jocobi','Seidel'])
plt.xlabel('对角元素每次加%d'%S)
plt.ylabel('迭代次数')
plt.savefig('result.png')
plt.show()
迭代速度对比图.png

从测试来看,结论得以验证。

2.用迭代法计算希尔伯特矩阵

生成希尔伯特矩阵:

def Hilbert(N):
   return 1. / (np.arange(1, N+1) + np.arange(0, N)[:, np.newaxis])

生成一个6阶的希尔伯特矩阵,并且解均为1。
当我用雅克比迭代法进行计算时,会显示不可收敛,因为谱半径大于1了。当用高斯赛德尔迭代法时,会出现第一次迭代的结果。我注意到这时这个值会变得很大,且理论上的最大迭代次数也变得很大。因为,写SOR函数时先不用这些收敛条件,先粗糙地设置最大迭代次数,同时参考书后发现SOR对对称正定的三对角矩阵能取到最优的松弛因子。一个不太成熟的SOR代码:

def SOR(A,b,initial,delta,w,maxTimes,isw=False):
    '''
    @author:zengwei
    输入:A是系数矩阵,N阶方阵
          b是N*1列向量
          initial是解的初始值,N*1大小
          delta是我给定的迭代值与真实值之间的误差
          w:松弛因子
          isw:对于对称正定的三对角矩阵可以求最优的w
    输出:迭代后的求解结果
    '''
    D = np.diag(np.diag(A)) 
    L = -np.tril(A,-1)
    U = -np.triu(A,1)
    
    if isw==True:
        BJ = np.dot(np.linalg.inv(D),L+U)
        lamdaBJ,_ = np.linalg.eig(BJ)
        rouBJ = np.max(np.abs(lamdaBJ))
        w = 2./(1+np.sqrt(1-rouBJ**2))
    try:
        d = np.linalg.inv(D - w*L)
        Bw = np.dot(d,(1-w)*D+w*U)          # 迭代矩阵Bw
        lamda,_ = np.linalg.eig(Bw)
        print('谱半径为:',np.max(np.abs(lamda)))
        if np.max(np.abs(lamda))<1: 
            f = np.dot(d,w*b)
            X = np.dot( Bw ,initial ) + f
            for i in np.arange(maxTimes):
                initial = X
                X = np.dot( Bw,initial )+f  
            return X
        else:
            print('Sorry,不可收敛。')
            print('谱半径为:',np.max(np.abs(lamda)))
    except:
        print('矩阵[D-wL]没有逆矩阵!')

我用SOR迭代法计算上述6阶希尔伯特矩阵时可以得到结果,并分别试了松弛因子为1.0,1.25,1.5时得到的结果。我没法用肉眼分辨哪个结果更好,因为我使用迭代值与真实值的均方误差来进行比较。

N = 6
A = Hilbert(N)
x = np.ones((N,1))
b = np.dot(A,x)
initial = np.zeros((N,1))
delta = 10**(-5)

def MES(y,yTrue):
    return np.sum([i**2 for i in (y-yTrue)])/len(y)

yTrue = 1
print('w=1时,数值解与真实值之间的均方误差为:',MES(SOR(A,b,initial,delta,1,100),yTrue))
print('w=1.25时,数值解与真实值之间的均方误差为:',MES(SOR(A,b,initial,delta,1.25,100),yTrue))
print('w=1.5时,数值解与真实值之间的均方误差为:',MES(SOR(A,b,initial,delta,1.5,100),yTrue))
'''
w=1时,数值解与真实值之间的均方误差为: 0.012836171242905989
w=1.25时,数值解与真实值之间的均方误差为: 0.02127700958940544
w=1.5时,数值解与真实值之间的均方误差为: 0.03818250349102156
'''

二、自相关

自相关在工程上有非常多的应用,典型应用是从随机信号中找出周期信号。噪声的自相关会在0时刻取到最大值,随后衰减到0。
参考《数字信号处理》(胡广书)编写自相关函数有两种思路:一种是按照定义计算,可以转换为矩阵运算;另一种是先补N个0,再做FFT,然后对N分之一幅度平方做IFFT。代码如下:

def myAutocorrelation(SigA):
    '''
    @author:zengwei
    自相关函数,属于有偏估计,对应matlab的xcorr(SigA,'biased')
    https://ww2.mathworks.cn/help/matlab/ref/xcorr.html
    '''
    N = len(SigA)
    SigB = np.zeros(2*N-1)
    SigB[0:N] = SigA
    L = len(SigB)
    
    SigC = np.array(SigB.tolist()*(L+1))
    Matrix = np.zeros((L,L))
    start = N-1
    for i in np.arange(L):
        end = start + L               # 还可以用np.roll()函数
        Matrix[i,:] = SigC[start:end]
        start = end - 1
    return np.dot(Matrix,SigB)/N
def selfAutocorrelation(signal):
    N = len(signal)
    signal2N = np.hstack((signal,np.zeros(N)))
    X = np.fft.fft(signal2N)
    x = (np.abs(X)**2)/(N)
    rm = np.fft.ifft(x)
    return np.roll(rm,N-1)[0:-1]

得到的结果与matlab中的xcorr(SigA,'biased')结果一致,但看到更多情况下用xcorr(SigA,'unbiased')。matlab说明文档详细说明了其中的区别:https://ww2.mathworks.cn/help/matlab/ref/xcorr.html 。

Fs = 500
n = np.arange(0,1,1/Fs)
noise = np.random.randn(len(n))     
x =  2*np.cos(2*np.pi*5*n)+noise
cor = myAutocorrelation(x)

plt.figure(figsize=(12,5))
plt.subplot(211)
plt.title('输入信号')
plt.plot(n,x)
plt.subplot(212)
plt.plot(cor)
plt.title('自相关结果')
plt.savefig('自相关.png')
plt.show()

自相关示例.png

对于如何利用自相关结果找周期信号,可以参见matlab说明文档进一步了解:https://ww2.mathworks.cn/help/signal/ug/find-periodicity-using-autocorrelation.html

三、自相关法求功率谱估计

用Python做功率谱估计感觉怪怪的,因为matlab有现成的函数做各种功率谱估计。我这里用信号自相关函数的FFT变换做为功率谱估计。如下所示:

# 生成原始输入信号
fs = 1000
t = np.arange(0, 1, 1/fs)
f = 100
x = 2*np.cos(2*np.pi*f*t) + np.random.randn(t.size)

# 自相关法功率谱估计
num_fft = 1024
cor_x = myAutocorrelation(x)
cor_X = np.fft.fft(cor_x, num_fft)
P_BT = np.abs(cor_X)

plt.figure(figsize=(12, 8))
ax=plt.subplot(311)
ax.set_title('输入信号')
plt.tight_layout()
plt.plot(x)

ax=plt.subplot(312)
ax.set_title('自相关法功率谱估计P_BT')
plt.plot(P_BT[:num_fft//2])
plt.tight_layout()

ax=plt.subplot(313)
ax.set_title('自相关法功率谱估计20*log10(P_BT)')
plt.plot(20*np.log10(P_BT[:num_fft//2]))
plt.tight_layout()
plt.savefig('P_BT.png')
plt.show()
自相关功率谱估计.png

接下来试试对自相关函数加窗函数,其余操作不变,并且窗函数加在自相关函数整个上面。然后看看有啥变化。

def Hamming(N):
    return np.array([0.54 - 0.46 * np.cos(2 * np.pi * n / (N - 1)) for n in range(N)])
def Hanning(N):
    return np.array([0.5 - 0.5 * np.cos(2 * np.pi * n / (N - 1)) for n in range(N)])
def Rect(N):
    return np.ones(N)
自相关功率谱估计加窗.png

放在一副图上比较.png

不加窗,加矩形窗,加汉明窗,加汉宁窗,其方差分别为:579.6754,579.6754,422.8173,413.1546。感官上,有一丢丢改善。应该比不上分段加窗强。

四、直接法功率谱估计的平滑改进

【10月12日批注】我这里分段时没有考虑到频率分辨率的问题,可以移步下一篇

依旧对上述相同的信号进行处理。

1.Bartlett法

def Bartlett(signal,L,num_fft):
    M = len(signal)//L
    signal = signal.reshape(L,M)
    P_BT = []
    for i in signal:
        cor_I = np.fft.fft(i,num_fft)
        P_BTi = np.abs(cor_I)**2/M
        P_BT.append(P_BTi.tolist())
    P_BT = np.sum(np.array(P_BT),axis=0)/L
    return np.abs(P_BT)
BP_BT = Bartlett(x,100,1024)
plt.figure(figsize=(15,5))
plt.title('Bartlett平滑')
plt.plot(np.log10(BP_BT[:num_fft//2]))
plt.savefig('Bartlett.png')
plt.show()
Bartlett.png

2.Welch法

这里用的是汉宁窗。

def Welch(sig,M,num_fft):
    L = int((len(sig)-M/2)//(M/2))
    signal = np.zeros((L,M))
    for m in np.arange(L):
        signal[m,:] = x[int(m/2):int(m/2)+M]
    
    d2 = Hanning(M)
    U = np.sum(d2**2)/M
    
    P_BT = []
    for i in signal:
        cor_I = np.fft.fft(i*d2,num_fft)
        P_BTi = np.abs(cor_I)**2/(M*U)
        P_BT.append(P_BTi.tolist())
    P_BT = np.sum(np.array(P_BT),axis=0)/L
    return np.abs(P_BT)
WP_BT = Welch(x,10,1024)
plt.figure(figsize=(15,5))
plt.title('Welch平滑')
plt.plot(np.log10(WP_BT[:num_fft//2]))
plt.savefig('Welch.png')
plt.show()
Welch.png

感觉可能有错误的地方。明天继续......

你可能感兴趣的:(10-09:课程小结-迭代收敛理论、自先关和功率谱估计)