连着三天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()
从测试来看,结论得以验证。
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()
对于如何利用自相关结果找周期信号,可以参见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()
接下来试试对自相关函数加窗函数,其余操作不变,并且窗函数加在自相关函数整个上面。然后看看有啥变化。
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)
不加窗,加矩形窗,加汉明窗,加汉宁窗,其方差分别为: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()
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()
感觉可能有错误的地方。明天继续......