Skip to main content
 首页 » 编程设计

Python使用卡尔曼滤波器来改进模拟但结果更差

2025年05月04日9落叶无声

我对将卡尔曼滤波器 (KF) 应用于以下预测问题时所看到的行为有疑问。我已经包含了一个简单的代码示例。

目标: 我想知道 KF 是否适合使用现在(t 时)获得的测量结果来改进 future 一天(t+24 小时)的预测/模拟结果。目标是让预测尽可能接近测量

假设: 我们假设测量是完美的(即,如果我们能得到与测量完美匹配的预测,我们就会很高兴)。

我们有一个测量变量(z,实际风速)和一个模拟变量(x,预测风速)。

模拟风速 x 是由 NWP(数值天气预报)软件使用各种气象数据(对我来说是黑匣子)产生的。仿真文件每天生成,每半小时包含一次数据。

我尝试使用标量卡尔曼滤波器使用我现在获得的测量值和现在的预测数据(t-24 小时前生成)来更正 t+24 小时的预测。作为引用,我使用了: http://www.swarthmore.edu/NatSci/echeeve1/Ref/Kalman/ScalarKalman.html

代码:

#! /usr/bin/python 
 
import numpy as np 
import pylab 
 
import os 
 
 
def main(): 
 
    # x = 336 data points of simulated wind speed for 7 days * 24 hour * 2 (every half an hour) 
    # Imagine at time t, we will get a x_t fvalue or t+48 or a 24 hours later. 
    x = load_x() 
 
    # this is a list that will contain 336 data points of our corrected data 
    x_sample_predict_list = [] 
 
    # z = 336 data points for 7 days * 24 hour * 2 of actual measured wind speed (every half an hour) 
    z = load_z() 
 
    # Here is the setup of the scalar kalman filter 
    # reference: http://www.swarthmore.edu/NatSci/echeeve1/Ref/Kalman/ScalarKalman.html 
    # state transition matrix (we simply have a scalar) 
    # what you need to multiply the last time's state to get the newest state 
    # we get the x_t+1 = A * x_t, since we get the x_t+1 directly for simulation 
    # we will have a = 1 
    a = 1.0 
 
    # observation matrix 
    # what you need to multiply to the state, convert it to the same form as incoming measurement  
    # both state and measurements are wind speed, so set h = 1 
    h = 1.0 
 
    Q = 16.0    # expected process variance of predicted Wind Speed 
    R = 9.0 # expected measurement variance of Wind Speed 
 
    p_j = Q # process covariance is equal to the initial process covariance estimate 
 
    # Kalman gain is equal to k = hp-_j / (hp-_j + R).  With perfect measurement 
    # R = 0, k reduces to k=1/h which is 1 
    k = 1.0 
 
    # one week data 
    # original R2 = 0.183 
    # with delay = 6, R2 = 0.295 
    # with delay = 12, R2 = 0.147    
    # with delay = 48, R2 = 0.075 
    delay = 6  
 
    # Kalman loop 
    for t, x_sample in enumerate(x): 
 
        if t <= delay:           
            # for the first day of the forecast, 
            # we don't have forecast data and measurement  
            # from a day before to do correction 
            x_sample_predict = x_sample              
        else: # t > 48 
            # for a priori estimate we take x_sample as is 
            # x_sample = x^-_j = a x^-_j_1 + b u_j 
            # Inside the NWP (numerical weather prediction,  
            # the x_sample should be on x_sample_j-1 (assumption) 
 
            x_sample_predict_prior = a * x_sample 
 
            # we use the measurement from t-delay (ie. could be a day ago) 
            # and forecast data from t-delay, to produce a leading residual that can be used to 
            # correct the forecast. 
            residual = z[t-delay] - h * x_sample_predict_list[t-delay] 
 
 
            p_j_prior = a**2 * p_j + Q 
 
            k = h * p_j_prior / (h**2 * p_j_prior + R) 
 
            # we update our prediction based on the residual 
            x_sample_predict = x_sample_predict_prior + k * residual 
 
            p_j = p_j_prior * (1 - h * k) 
 
            #print k 
            #print p_j_prior 
            #print p_j 
            #raw_input() 
 
        x_sample_predict_list.append(x_sample_predict) 
 
    # initial goodness of fit 
    R2_val_initial = calculate_regression(x,z) 
    R2_string_initial = "R2 initial: {0:10.3f}, ".format(R2_val_initial)     
    print R2_string_initial     # R2_val_initial = 0.183 
 
    # final goodness of fit 
    R2_val_final = calculate_regression(x_sample_predict_list,z) 
    R2_string_final = "R2 final: {0:10.3f}, ".format(R2_val_final)   
    print R2_string_final       # R2_val_final = 0.117, which is worse 
 
 
    timesteps = xrange(len(x))       
    pylab.plot(timesteps,x,'r-', timesteps,z,'b:', timesteps,x_sample_predict_list,'g--') 
    pylab.xlabel('Time') 
    pylab.ylabel('Wind Speed') 
    pylab.title('Simulated Wind Speed vs Actual Wind Speed') 
    pylab.legend(('predicted','measured','kalman')) 
    pylab.show() 
 
 
def calculate_regression(x, y):          
    R2 = 0   
    A = np.array( [x, np.ones(len(x))] ) 
    model, resid = np.linalg.lstsq(A.T, y)[:2]   
    R2_val = 1 - resid[0] / (y.size * y.var())           
    return R2_val 
 
def load_x(): 
    return np.array([2, 3, 3, 5, 4, 4, 4, 5, 5, 6, 5, 7, 7, 7, 8, 8, 8, 9, 9, 10, 10, 10, 11, 11, 
     11, 10, 8, 8, 8, 8, 6, 3, 4, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 6, 6, 7, 6, 8, 9, 10, 
     12, 11, 10, 10, 10, 11, 11, 10, 8, 8, 9, 8, 9, 9, 9, 9, 8, 9, 8, 11, 11, 11, 12, 
     12, 13, 13, 13, 13, 13, 13, 13, 14, 13, 13, 12, 13, 13, 12, 12, 13, 13, 12, 12,  
     11, 12, 12, 19, 18, 17, 15, 13, 14, 14, 14, 13, 12, 12, 12, 12, 11, 10, 10, 10,  
     10, 9, 9, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 7, 7, 8, 8, 8, 6, 5, 5,  
     5, 5, 5, 5, 6, 4, 4, 4, 6, 7, 8, 7, 7, 9, 10, 10, 9, 9, 8, 7, 5, 5, 5, 5, 5, 5,  
     5, 5, 6, 5, 5, 5, 4, 4, 6, 6, 7, 7, 7, 7, 6, 6, 5, 5, 4, 2, 2, 2, 1, 1, 1, 2, 3, 
     13, 13, 12, 11, 10, 9, 10, 10, 8, 9, 8, 7, 5, 3, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 
     7, 7, 7, 6, 6, 6, 7, 6, 6, 5, 4, 4, 3, 3, 3, 2, 2, 1, 5, 5, 3, 2, 1, 2, 6, 7,  
     7, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7,  
     7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 11, 11, 11, 11, 10, 10, 9, 10, 10, 10, 2, 2, 
     2, 3, 1, 1, 3, 4, 5, 8, 9, 9, 9, 9, 8, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 
     7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 5, 5, 5, 5, 6, 5]) 
 
def load_z(): 
    return np.array([3, 2, 1, 1, 1, 1, 3, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 2, 2, 2, 
     2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 5, 4, 4, 5, 5, 5, 6, 6, 
     6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 9, 10, 9, 9, 10, 10, 9, 
     9, 10, 9, 9, 10, 9, 8, 9, 9, 7, 7, 6, 7, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 7, 6, 7, 
     8, 8, 7, 8, 9, 9, 9, 9, 10, 9, 9, 9, 8, 8, 10, 9, 10, 10, 9, 9, 9, 10, 9, 8, 7,  
     7, 7, 7, 8, 7, 6, 5, 4, 3, 5, 3, 5, 4, 4, 4, 2, 4, 3, 2, 1, 1, 2, 1, 2, 1, 4, 4, 
     4, 4, 4, 3, 3, 3, 1, 1, 1, 1, 2, 3, 3, 2, 3, 3, 3, 2, 2, 5, 4, 2, 5, 4, 1, 1, 1,  
     1, 1, 1, 1, 2, 2, 1, 1, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 
     4, 4, 5, 5, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 3, 3, 1, 2, 1, 1, 2, 4, 3, 1, 
     1, 2, 0, 0, 0, 2, 1, 0, 0, 2, 3, 2, 4, 4, 3, 3, 4, 5, 5, 5, 4, 5, 4, 4, 4, 5, 5,  
     4, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4, 5, 5, 5, 4, 5, 5, 5, 5, 6, 5, 5, 8, 9, 8, 9, 
     9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 9, 10, 9, 8, 8, 9, 8, 9, 9, 10, 9, 9, 9, 
     7, 7, 9, 8, 7, 6, 6, 5, 5, 5, 5, 3, 3, 3, 4, 6, 5, 5, 6, 5]) 
 
if __name__ == '__main__': main()  # this avoids executing main on import your_module 

------------------------

观察:

1) 如果昨天的预测是高估(正偏差),那么今天,我会通过减去偏差来进行修正。在实践中,如果今天我恰好预测不足,那么减去正偏差会导致更糟糕的预测。实际上,我观察到整体拟合度较差的数据波动范围更大。我的示例有什么问题?

2) 大多数卡尔曼滤波器资源表明卡尔曼滤波器最小化后验协方差 p_j = E{(x_j – x^_j)},并有选择 K 来最小化 p_j 的证明。但是有人可以解释如何最小化后验协方差实际上最小化过程白噪声 w 的影响吗?在实时情况下,假设实际风速和测量风速为 5 m/s。预测风速为6 m/s,即。有 w = 1 m/s 的噪音。残差为 5 – 6 = -1 m/s。您通过从预测中取 1 m/s 得到 5 m/s 来纠正。这就是过程噪声影响最小化的方式吗?

3) 这里有一篇论文提到将 KF 应用于平滑天气预报。 http://hal.archives-ouvertes.fr/docs/00/50/59/93/PDF/Louka_etal_jweia2008.pdf .有趣的一点是 pg 9 eq (7),“一旦知道新的观测值 y_t,x 在时间 t 的估计值就变成 x_t = x_t/t-1 = K(y_t – H_t * x_t/t- 1)”。如果我根据实际时间来解释它,那么“一旦现在知道新的观察值,估计就会变成 x_t ......。 ” 我了解 KF 如何使您的数据实时接近于测量。但是,如果您使用 t=now 的测量数据来校正 t=now 的预测数据,那么预测又如何呢?

谢谢!

更新 1:

4) 我在代码中添加了一个延迟,以调查预测可以比根据当前测量计算的当前偏差晚多少,如果我们希望卡尔曼处理数据与测量数据时间序列之间的 R2 从未处理的数据与测量数据。在此示例中,如果测量用于改进预测 6 时间步长(从现在起 3 小时),它仍然有用(R2 从 0.183 变为 0.295)。但是,如果测量用于改进从现在起 1 天的预测,那么它会破坏相关性(R2 下降到 0.075)。

请您参考如下方法:

我更新了我的测试标量实现,没有假设完美测量 R 为 1,这就是将卡尔曼增益降低到恒定值 1 的原因。现在我看到时间序列有了改进,RMSE 误差减少了。

#! /usr/bin/python 
 
import numpy as np 
import pylab 
 
import os 
 
# RMSE improved 
def main(): 
 
    # x = 336 data points of simulated wind speed for 7 days * 24 hour * 2 (every half an hour) 
    # Imagine at time t, we will get a x_t fvalue or t+48 or a 24 hours later. 
    x = load_x() 
 
    # this is a list that will contain 336 data points of our corrected data 
    x_sample_predict_list = [] 
 
    # z = 336 data points for 7 days * 24 hour * 2 of actual measured wind speed (every half an hour) 
    z = load_z() 
 
    # Here is the setup of the scalar kalman filter 
    # reference: http://www.swarthmore.edu/NatSci/echeeve1/Ref/Kalman/ScalarKalman.html 
    # state transition matrix (we simply have a scalar) 
    # what you need to multiply the last time's state to get the newest state 
    # we get the x_t+1 = A * x_t, since we get the x_t+1 directly for simulation 
    # we will have a = 1 
    a = 1.0 
 
    # observation matrix 
    # what you need to multiply to the state, convert it to the same form as incoming measurement  
    # both state and measurements are wind speed, so set h = 1 
    h = 1.0 
 
    Q = 1.0     # expected process noise of predicted Wind Speed     
    R = 1.0     # expected measurement noise of Wind Speed 
 
    p_j = Q # process covariance is equal to the initial process covariance estimate 
 
    # Kalman gain is equal to k = hp-_j / (hp-_j + R).  With perfect measurement 
    # R = 0, k reduces to k=1/h which is 1 
    k = 1.0 
 
    # one week data 
    # original R2 = 0.183 
    # with delay = 6, R2 = 0.295 
    # with delay = 12, R2 = 0.147    
    # with delay = 48, R2 = 0.075 
    delay = 6  
 
    # Kalman loop 
    for t, x_sample in enumerate(x): 
 
        if t <= delay:           
            # for the first day of the forecast, 
            # we don't have forecast data and measurement  
            # from a day before to do correction 
            x_sample_predict = x_sample              
        else: # t > 48 
            # for a priori estimate we take x_sample as is 
            # x_sample = x^-_j = a x^-_j_1 + b u_j 
            # Inside the NWP (numerical weather prediction,  
            # the x_sample should be on x_sample_j-1 (assumption) 
 
            x_sample_predict_prior = a * x_sample 
 
            # we use the measurement from t-delay (ie. could be a day ago) 
            # and forecast data from t-delay, to produce a leading residual that can be used to 
            # correct the forecast. 
            residual = z[t-delay] - h * x_sample_predict_list[t-delay] 
 
            p_j_prior = a**2 * p_j + Q 
 
            k = h * p_j_prior / (h**2 * p_j_prior + R) 
 
            # we update our prediction based on the residual 
            x_sample_predict = x_sample_predict_prior + k * residual 
 
            p_j = p_j_prior * (1 - h * k) 
 
            #print k 
            #print p_j_prior 
            #print p_j 
            #raw_input() 
 
        x_sample_predict_list.append(x_sample_predict) 
 
    # initial goodness of fit 
    R2_val_initial = calculate_regression(x,z) 
    R2_string_initial = "R2 original: {0:10.3f}, ".format(R2_val_initial)    
    print R2_string_initial     # R2_val_original = 0.183 
 
    original_RMSE = (((x-z)**2).mean())**0.5 
    print "original_RMSE" 
    print original_RMSE  
    print "\n" 
 
    # final goodness of fit 
    R2_val_final = calculate_regression(x_sample_predict_list,z) 
    R2_string_final = "R2 final: {0:10.3f}, ".format(R2_val_final)   
    print R2_string_final       # R2_val_final = 0.267, which is better 
 
    final_RMSE = (((x_sample_predict-z)**2).mean())**0.5 
    print "final_RMSE" 
    print final_RMSE     
    print "\n" 
 
 
    timesteps = xrange(len(x))       
    pylab.plot(timesteps,x,'r-', timesteps,z,'b:', timesteps,x_sample_predict_list,'g--') 
    pylab.xlabel('Time') 
    pylab.ylabel('Wind Speed') 
    pylab.title('Simulated Wind Speed vs Actual Wind Speed') 
    pylab.legend(('predicted','measured','kalman')) 
    pylab.show() 
 
 
def calculate_regression(x, y):          
    R2 = 0   
    A = np.array( [x, np.ones(len(x))] ) 
    model, resid = np.linalg.lstsq(A.T, y)[:2]   
    R2_val = 1 - resid[0] / (y.size * y.var())           
    return R2_val 
 
def load_x(): 
    return np.array([2, 3, 3, 5, 4, 4, 4, 5, 5, 6, 5, 7, 7, 7, 8, 8, 8, 9, 9, 10, 10, 10, 11, 11, 
     11, 10, 8, 8, 8, 8, 6, 3, 4, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 6, 6, 7, 6, 8, 9, 10, 
     12, 11, 10, 10, 10, 11, 11, 10, 8, 8, 9, 8, 9, 9, 9, 9, 8, 9, 8, 11, 11, 11, 12, 
     12, 13, 13, 13, 13, 13, 13, 13, 14, 13, 13, 12, 13, 13, 12, 12, 13, 13, 12, 12,  
     11, 12, 12, 19, 18, 17, 15, 13, 14, 14, 14, 13, 12, 12, 12, 12, 11, 10, 10, 10,  
     10, 9, 9, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 7, 7, 8, 8, 8, 6, 5, 5,  
     5, 5, 5, 5, 6, 4, 4, 4, 6, 7, 8, 7, 7, 9, 10, 10, 9, 9, 8, 7, 5, 5, 5, 5, 5, 5,  
     5, 5, 6, 5, 5, 5, 4, 4, 6, 6, 7, 7, 7, 7, 6, 6, 5, 5, 4, 2, 2, 2, 1, 1, 1, 2, 3, 
     13, 13, 12, 11, 10, 9, 10, 10, 8, 9, 8, 7, 5, 3, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 
     7, 7, 7, 6, 6, 6, 7, 6, 6, 5, 4, 4, 3, 3, 3, 2, 2, 1, 5, 5, 3, 2, 1, 2, 6, 7,  
     7, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7,  
     7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 11, 11, 11, 11, 10, 10, 9, 10, 10, 10, 2, 2, 
     2, 3, 1, 1, 3, 4, 5, 8, 9, 9, 9, 9, 8, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 
     7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 5, 5, 5, 5, 6, 5]) 
 
def load_z(): 
    return np.array([3, 2, 1, 1, 1, 1, 3, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 2, 2, 2, 
     2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 5, 4, 4, 5, 5, 5, 6, 6, 
     6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 9, 10, 9, 9, 10, 10, 9, 
     9, 10, 9, 9, 10, 9, 8, 9, 9, 7, 7, 6, 7, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 7, 6, 7, 
     8, 8, 7, 8, 9, 9, 9, 9, 10, 9, 9, 9, 8, 8, 10, 9, 10, 10, 9, 9, 9, 10, 9, 8, 7,  
     7, 7, 7, 8, 7, 6, 5, 4, 3, 5, 3, 5, 4, 4, 4, 2, 4, 3, 2, 1, 1, 2, 1, 2, 1, 4, 4, 
     4, 4, 4, 3, 3, 3, 1, 1, 1, 1, 2, 3, 3, 2, 3, 3, 3, 2, 2, 5, 4, 2, 5, 4, 1, 1, 1,  
     1, 1, 1, 1, 2, 2, 1, 1, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 
     4, 4, 5, 5, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 3, 3, 1, 2, 1, 1, 2, 4, 3, 1, 
     1, 2, 0, 0, 0, 2, 1, 0, 0, 2, 3, 2, 4, 4, 3, 3, 4, 5, 5, 5, 4, 5, 4, 4, 4, 5, 5,  
     4, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4, 5, 5, 5, 4, 5, 5, 5, 5, 6, 5, 5, 8, 9, 8, 9, 
     9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 9, 10, 9, 8, 8, 9, 8, 9, 9, 10, 9, 9, 9, 
     7, 7, 9, 8, 7, 6, 6, 5, 5, 5, 5, 3, 3, 3, 4, 6, 5, 5, 6, 5]) 
 
if __name__ == '__main__': main()  # this avoids executing main on import your_module