天天看點

新冠疫情預測模組化記錄

持續到現在的新冠疫情,已經改變了千萬人的命運,其中也包括我

面對這麼殘暴的天災,我做了一個預測模型,希望能貢獻自己一點微薄的力量,讓天災早點過去

有首歌唱得好:

讓我悲也好,讓我悔也好,讓疫情過去沒煩惱;讓我苦也好,讓我累也好,讓我看見大家的笑……

好了,廢話結束,進入正題:

在這之前,github上已經有一個預測項目了:

https://github.com/YiranJing/Coronavirus-Epidemic-2019-nCov

于是我将他的源碼down下來仔細看了看

該項目作者實作了自己的SIR和SEIR模型

(兩種經典的傳染病模型:

https://baike.baidu.com/item/%E4%BC%A0%E6%9F%93%E7%97%85%E6%A8%A1%E5%9E%8B/5130035?fr=aladdin

而模型的關鍵部分是定義了一個分布函數:

def Binomial_log_like(n: int, p: float, k: int):
    loglik = lgamma(n+k+1) - lgamma(k+1) - lgamma(n+1) + k*np.log(p) + n*np.log(1-p)           

上述公式定義了對疫情傳染人數趨勢的一個假設公式

顧名思義,他将疫情的傳染人數曲線看作二進制對數分布

使用SIR和SEIR構模組化型是否恰當呢?

這兩種傳染病動力學模型,其實都未考慮隔離措施的因素

都是建立在自然傳播和自然康複的基礎上,是以如果要做更客觀的預測,需要考慮人類隔離活動的影響

而疫情資料實際的分布是否滿足這種分布呢?我們取出曆史資料看看:

新冠疫情預測模組化記錄

國内每天新增病例趨勢

從上圖可以發現,新增病例曲線總體上更有點類似于伽馬分布

而曲線中殘缺的部分,猜測則是由于政府相關部門采取了強有力的隔離措施,導緻了病毒傳播的中斷

是以,從宏觀上看,新增病例曲線就是兩種力量不斷對抗的結果:

新增感染數量 = 病毒自然傳播力 - 人類阻斷摩擦力           

如果非要用一個實體現象做類比,就好像給往下滾動的雪球踩刹車

假設人類阻斷力在23号封城以後是一個常量(已達到人類能實作的最進階别阻斷)

那麼其實我們就可以用伽馬曲線近似的逼近23号之後的曲線

是以,這裡我自定義了一個控制函數:

def control_curve(x, i0, missing, latency=7, infection_num=2):
    """
    傳染曲線,假設過了潛伏期即被隔離
    今天傳染人數 = (昨天實際傳染人數 - 昨天隔離人數) * 平均傳染人數
    昨天隔離人數 = 七天前實際傳染人數
    今天傳染人數 = (昨天實際傳染人數 - 七天前實際傳染人數) * 平均傳染人數
    
    :param x: 傳染時間
    :param i0: 初始傳染人數
    :param missing: 遺漏率
    :param latency: 潛伏期(确切的說,這裡代表平均發病時間,假設發病時間内每天都具有傳染性)
    :param infection_num: 平均傳染人數
    """
    if x >= latency:
        isolated = i0 * (infection_num ** (x - latency))
    else:
        isolated = 0
    day_num = i0 * missing * (infection_num ** x) - isolated
    if day_num < 0:
        day_num = 0
    return day_num           

該函數中存在4個未知參數,我們可以利用scipy的優化器自動求解最優值:

from scipy.optimize import curve_fit

def control_curve_list(x, i0, missing, latency, infection_num):
    y = list(map(control_curve, x, [i0] * len(x), [missing] * len(x), [latency] * len(x), [infection_num] * len(x)))
    return y

popt,pcov = curve_fit(f=control_curve_list, xdata=x_data[:-1], ydata=y_data[:-1])           

通過 curve_fit 曲線拟合函數,我們就可以快速的得到一組近似最優解

拟合出來後,看看效果:

新冠疫情預測模組化記錄

最優參數得到的模型預測120天結果

看起來很光滑呀,實際資料恐怕會比這個粗糙很多

那這個預測值,與實際是否比對呢?

看起來,方差還是比較大的,有沒有辦法得到一個更精确的預測呢?

我們可以大膽假設,人類的隔離力度,在每個階段、每個區域可能都不盡相同

每個時期的參數值可能會略有不同

是以接下來就用貝葉斯優化器對最優參數繼續做更深入的探索:

這裡先引入JS散度,友善貝葉斯優化器對結果進行比較

from bayes_opt import BayesianOptimization

def JS_divergence(p,q):
    M=(p+q)/2
    return 0.5*scipy.stats.entropy(p, M)+0.5*scipy.stats.entropy(q, M)           

再建構一個目标函數,傳回可比較的分值

def result_function(nu, close_contacts, k, I0, E0, T, death_rate):
    N = 14 * (10 ** 8)
    econ = len(t)
    R0 = I0 * (death_rate) * 2
    try:
        Est = Estimate_parameter(nu=nu, k=close_contacts, t=t, I=I)
        eso = Estimate_Wuhan_Outbreak(Est, k=k, N=N, E0=E0, I0=I0, R0=R0, T=T, econ=econ)
        result = eso._run_SIER('Forecat', 'China', 'Days after 2019-12-1', death_rate=death_rate, show_Plot=False)
        score = JS_divergence(I,result['Infected'])
    except Exception as e:
        score = 999
        print(e)
    try:
        score = float(score)
    except:
        score = 999
    if score > 999:
        score = 999
    return -score           

然後,根據前面的最優參數,手工設定一些搜尋區間:

rf_bo = BayesianOptimization(
        result_function,
        {'nu': (1/20, 1/10),
        'close_contacts': (5, 15),
        'k': (1, 3),
        'I0': (1, 10000),
        'E0': (0,10000),
         'T':(3,14),
         'death_rate':(0.2,0.3)
        }
    )           

最後,把我們的優化器跑起來,讓它自己找找

rf_bo.maximize(init_points=5,
    n_iter=1000,)           

最終,我們就得到了一個相對靠譜的結果:

新冠疫情預測模組化記錄

使用這個參數進行預測,與實際結果的差距就不大了

整套邏輯寫好後,也可以把流程寫成一個pipline,每天自動拟合

這樣,就能不斷的逼近最客觀的結果!

以上就是我分享的思路,有不正确的地方還請大家多多批評!