測軟件實踐)
1. 為什么要做一個基于Python的大氣污染預(yù)測軟件我先交代一下背景。去年接了個空氣質(zhì)量監(jiān)測的課題需要根據(jù)歷史污染物濃度數(shù)據(jù)預(yù)測未來幾天的PM2.5、PM10、NO?、O?等指標(biāo)。最開始用Excel硬算滑動平均、線性回歸都試過結(jié)果一到污染過程轉(zhuǎn)折點(diǎn)就嚴(yán)重滯后。后來決定自己動手用Python寫一套完整的時間序列分析預(yù)測軟件把數(shù)據(jù)清洗、特征構(gòu)造、模型訓(xùn)練、預(yù)測、可視化、報告輸出全部串起來。這套東西做完以后不僅課題交付了還整理出了一份配套文檔和源碼工程方便后面任何人接手維護(hù)。文章標(biāo)題里的“基于Python的時間序列分析的大氣污染預(yù)測軟件”說白了就是干這件事的。它能做的事情很明確給定一個監(jiān)測站點(diǎn)的歷史濃度數(shù)據(jù)自動完成缺失值處理、異常值識別、趨勢和周期性分解然后訓(xùn)練時間序列模型輸出未來24小時到72小時的濃度預(yù)測并生成圖表和評估指標(biāo)。適合給環(huán)境監(jiān)測站、高校實驗室、環(huán)保方向的學(xué)生和科研人員做參考也可以作為課程設(shè)計或畢業(yè)設(shè)計的完整項目模板。我見過太多類似項目最終卡死在兩個地方一是數(shù)據(jù)質(zhì)量太差二是模型選型過于隨意。很多教程上來就教你跑ARIMA但壓根不告訴你數(shù)據(jù)需要滿足什么前提也不告訴你如果數(shù)據(jù)有多個季節(jié)周期該怎么辦。所以這篇博文我不想只貼一堆代碼而是把完整的設(shè)計思路和踩坑過程都講清楚讓你拿到源碼之后不僅能跑通還能根據(jù)實際數(shù)據(jù)調(diào)整出更好的效果。2. 時間序列分析在空氣質(zhì)量預(yù)測里的核心思路2.1 為什么用時間序列而不是普通回歸大氣污染數(shù)據(jù)天然是帶時間戳的序列數(shù)據(jù)每小時的監(jiān)測值之間存在強(qiáng)烈的自相關(guān)性。舉個很直觀的例子今天下午3點(diǎn)的PM2.5濃度和昨天下午3點(diǎn)、前幾小時的數(shù)據(jù)密切相關(guān)而不是和某個不相關(guān)的特征比如“今天星期幾”線性相關(guān)。普通回歸模型把每個時刻當(dāng)作獨(dú)立樣本處理會徹底丟掉這種時間依賴關(guān)系。時間序列分析的核心思路是把“時間順序”作為模型的一等公民。我們會先處理三件事趨勢trend、季節(jié)性seasonality、殘差residual??諝赓|(zhì)量數(shù)據(jù)里有明顯的日周期交通早晚高峰導(dǎo)致污染物濃度升高、周周期周末工業(yè)活動減少和年周期冬季采暖導(dǎo)致濃度上升。如果不用時間序列方法這些周期性規(guī)律很難被普通回歸模型捕捉。另一個原因是預(yù)測任務(wù)本身的性質(zhì)。我們要做的是“未來幾小時/幾天的濃度”不是“給定一堆特征預(yù)測一個值”。時間序列模型天生支持滾動預(yù)測比如用過去72小時預(yù)測未來1小時再逐步滾動預(yù)測未來24小時。這對環(huán)境監(jiān)測、預(yù)警場景極其重要。2.2 數(shù)據(jù)分解看懂序列里藏著的規(guī)律拿到數(shù)據(jù)后第一件事不是建模而是分解。我常用statsmodels的seasonal_decompose把序列拆成趨勢項、季節(jié)項和殘差項。import pandas as pd import statsmodels.api as sm df pd.read_csv(pm25_hourly.csv, parse_dates[time], index_coltime) decomp sm.tsa.seasonal_decompose(df[pm25], modeladditive, period24) decomp.plot()這里要特別注意period參數(shù)。如果不指定seasonal_decompose默認(rèn)認(rèn)為沒有季節(jié)周期很多新手會漏掉這一步??諝赓|(zhì)量數(shù)據(jù)的日周期是24小時周周期是168小時如果做月級別的預(yù)測可能還需要考慮年周期8760小時。分解結(jié)果能直觀告訴你序列是否有明顯趨勢是否有周期性殘差是否平穩(wěn)這是后續(xù)選擇模型的基礎(chǔ)。2.3 平穩(wěn)性檢驗為什么ARIMA要求數(shù)據(jù)平穩(wěn)經(jīng)典ARIMA模型的前提是序列平穩(wěn)也就是均值、方差不隨時間變化??諝馕廴疚餄舛让黠@不滿足——冬季高、夏季低早晚高峰高、午后低。所以直接對原始數(shù)據(jù)跑ARIMA結(jié)果肯定一塌糊涂。正確做法是先做ADF檢驗Augmented Dickey-Fuller test判斷是否平穩(wěn)如果不平穩(wěn)就差分。我常用的代碼from statsmodels.tsa.stattools import adfuller result adfuller(df[pm25].dropna()) print(fADF統(tǒng)計量: {result[0]:.4f}) print(fp值: {result[1]:.4f}) # p值小于0.05通常認(rèn)為平穩(wěn)但這里有個陷阱空氣質(zhì)量數(shù)據(jù)單純差分一次往往還不夠因為還有季節(jié)性。如果只做一階差分季節(jié)性依舊存在ADF檢驗可能仍然不平穩(wěn)。這時候需要考慮兩種路一是使用SARIMA對季節(jié)性部分做季節(jié)差分二是對原始序列做STL分解后對殘差項建模。我在項目里通常先做STL分解再對殘差做ARIMA效果比直接差分好很多。3. 數(shù)據(jù)準(zhǔn)備從爬取到清洗這一步?jīng)Q定了預(yù)測上限3.1 數(shù)據(jù)來源選擇與接口對接大氣污染數(shù)據(jù)來源一般是兩類一類是環(huán)境監(jiān)測站提供的CSV或Excel導(dǎo)出另一類是通過API實時抓取。常見的API比如和風(fēng)天氣、AirVisual以及國內(nèi)的青悅開放數(shù)據(jù)平臺。當(dāng)時我做的軟件需要支持兩種方式讀本地文件以及定時從API拉取。以AirVisual API為例請求很簡單import requests api_key 你的key url fhttps://api.airvisual.com/v2/city?cityBeijingstateBeijingcountryChinakey{api_key} resp requests.get(url).json()注意不同API返回字段差異很大。有的返回的是“當(dāng)前實時濃度”有的返回的是“過去24小時歷史濃度”。做時間序列預(yù)測必須要有足夠長的歷史數(shù)據(jù)至少需要過去一個月以上的小時級數(shù)據(jù)否則模型根本學(xué)不到周期性。3.2 缺失值處理不能隨便刪也不能只填均值污染物監(jiān)測設(shè)備經(jīng)常斷線凌晨數(shù)據(jù)也容易異常。缺失值處理是數(shù)據(jù)處理環(huán)節(jié)最耗時的部分。簡單粗暴地刪除缺失行會打斷時間的連續(xù)性后續(xù)做滯后特征、傅里葉變換時都會出問題。簡單填充均值會抹掉日周期特征。我實際使用的策略分三層場景處理方式理由單點(diǎn)缺失1小時線性插值相鄰時刻相關(guān)性最接近線性插值足夠連續(xù)缺失3~6小時時間加權(quán)插值或前后同周期均值考慮日周期用前后兩天的同一時刻插值連續(xù)缺失超過24小時刪除該段或分段訓(xùn)練長缺失會引入大量估計誤差不如放棄具體代碼df[pm25] df[pm25].interpolate(methodlinear)后來我改進(jìn)了一下用scipy.interpolate的PchipInterpolator做保形狀插值避免線性插值導(dǎo)致的“尖角”突變。插值之后還要對所有填充點(diǎn)做標(biāo)記方便后面評估模型時區(qū)分真實值和填充值否則驗證集的誤差會被虛假值拉低。3.3 異常值識別用滾動標(biāo)準(zhǔn)差比閾值更靠譜空氣監(jiān)測數(shù)據(jù)經(jīng)常出現(xiàn)瞬時高值比如0點(diǎn)出現(xiàn)一個10000的值這明顯是設(shè)備故障。如果不去除模型會被這個點(diǎn)帶跑偏。我之前試過固定閾值法比如500就算異常但這個在污染嚴(yán)重的城市冬季根本不適用——正常濃度就能到400。后來改用滾動窗口的中位數(shù)和MADMedian Absolute Deviation來檢測異常。MAD法對離群值更穩(wěn)健適合非正態(tài)分布的污染物數(shù)據(jù)。import numpy as np def detect_outliers(series, window24, n_sigma5): rolling_median series.rolling(windowwindow, centerTrue).median() mad (series - rolling_median).abs().rolling(windowwindow, centerTrue).median() z_score 0.6745 * (series - rolling_median) / mad return z_score.abs() n_sigma這個方法的物理意義是如果一個點(diǎn)的濃度水平顯著偏離周圍24小時的“正常波動程度”就判定為設(shè)備異常用插值替換。注意n_sigma別設(shè)太小空氣質(zhì)量波動本身就大設(shè)成3會誤殺很多真實的高濃度過程。3.4 特征工程讓模型看到時間節(jié)律雖然時間序列模型本身能捕捉時間依賴但適當(dāng)構(gòu)造外生特征能顯著提升預(yù)測精度。我加了幾組特征時間sin/cos編碼用正弦余弦編碼小時、星期、月份避免模型誤以為“23”和“0”兩個小時內(nèi)差別非常大。滯后值過去1、3、6、12、24小時的濃度。氣象因子溫度、濕度、風(fēng)速、氣壓如果API能取到。周邊站點(diǎn)數(shù)據(jù)相鄰站點(diǎn)的濃度對流場有指示作用。氣象因子的重要性經(jīng)常被忽視。有一次模型預(yù)測霧霾消散時間總是滯后2小時后來發(fā)現(xiàn)是沒加入風(fēng)速變化特征。風(fēng)速突然增大后污染物濃度會在下一小時迅速下降這個信號純靠歷史濃度序列反映不出來。4. 模型實現(xiàn)從ARIMA到LSTM怎么選怎么調(diào)4.1 基準(zhǔn)模型SARIMA是效率首選一開始我直接上ARIMA結(jié)果被數(shù)據(jù)的光照季節(jié)性和周期性教做人。后來改用SARIMA即季節(jié)性ARIMA才算是真正入了門。SARIMA的全稱是Seasonal Autoregressive Integrated Moving Average它額外增加了季節(jié)項。模型參數(shù)寫成SARIMA(p,d,q)(P,D,Q,S)其中S是季節(jié)周期PM2.5小時數(shù)據(jù)的S24。確定p、d、q最常用的方法是我用過的pmdarima庫的auto_arima它會自動搜索最佳參數(shù)組合。直接用示例from pmdarima import auto_arima model auto_arima( df[pm25].dropna(), seasonalTrue, m24, start_p0, max_p5, start_q0, max_q5, d1, D1, traceTrue, stepwiseTrue )這里要說明兩點(diǎn)。第一d1, D1是我先做ADF檢驗、再對季節(jié)差分后的序列做ADF檢驗得到的結(jié)果不是隨便設(shè)的。第二m24意味著模型會把“24小時前”的誤差和滯后項納入計算訓(xùn)練時間會成倍增加。數(shù)據(jù)量超過1萬條時auto_arima可能要跑十幾分鐘正常現(xiàn)象。4.2 升級方案Prophet處理多季節(jié)周期SARIMA最多處理一個季節(jié)周期。但空氣質(zhì)量同時有日、周、年度周期。Facebook開源的Prophet可以同時擬合多個周期而且對缺失值、異常值相當(dāng)魯棒。我第二版軟件把Prophet用作主力模型之一。Prophet的使用特別簡單from prophet import Prophet model Prophet( yearly_seasonalityTrue, weekly_seasonalityTrue, daily_seasonalityTrue, changepoint_prior_scale0.05 ) model.add_regressor(wind_speed) model.fit(train) future model.make_future_dataframe(periods72, freqH) future[wind_speed] forecasted_weather forecast model.predict(future)雖然Prophet不算嚴(yán)格的“時間序列分析”工具它本質(zhì)上是可加回歸模型但拆分趨勢、周期、節(jié)假日的能力非常適合空氣質(zhì)量這種強(qiáng)季節(jié)性數(shù)據(jù)。唯一的問題是它對突變點(diǎn)比如沙塵暴事件的擬合能力偏弱因為變點(diǎn)默認(rèn)是稀疏先驗。我調(diào)高了changepoint_prior_scale到0.1之后能捕捉到一些突發(fā)性污染過程但代價是過擬合風(fēng)險也增加。4.3 進(jìn)階方向LSTM和它們的現(xiàn)實局限網(wǎng)上很多教程把LSTM吹得神乎其神實際用下來并沒有想象中那么美。LSTM的優(yōu)勢是能自動學(xué)習(xí)非線性關(guān)系和長時間依賴?yán)碚撋虾苓m合空氣質(zhì)量預(yù)測但需要大量數(shù)據(jù)。我實測發(fā)現(xiàn)當(dāng)訓(xùn)練數(shù)據(jù)少于3萬小時記錄時LSTM的效果經(jīng)常不如調(diào)好參的Prophet而且訓(xùn)練時間長了兩個數(shù)量級。如果一定要用LSTM我的建議是控制輸入窗口長度并加上注意力機(jī)制或者做多步滾動預(yù)測。下面是項目里用TensorFlow/Keras實現(xiàn)的簡單LSTM結(jié)構(gòu)from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(24, n_features))) model.add(LSTM(32)) model.add(Dense(1)) model.compile(optimizeradam, lossmse)這里input_shape的24表示用過去24小時數(shù)據(jù)作為特征。最開始我窗口設(shè)為168一周結(jié)果模型參數(shù)量暴增訓(xùn)練很慢精度也沒提升。后來用特征重要性分析發(fā)現(xiàn)過去24小時已經(jīng)包含了大部分有效信息。LSTM還有一個坑尺度敏感。污染物濃度分布右偏直接用原始值訓(xùn)練模型會對高濃度區(qū)間擬合不足。我做了Box-Cox變換后再訓(xùn)練預(yù)測值再逆變換回來RMSE降低了大約15%。4.4 多模型加權(quán)集成穩(wěn)健性的秘密單一模型總有各自的盲區(qū)。SARIMA擅長捕捉線性趨勢和周期Prophet擅長多季節(jié)分解LSTM擅長非線性模式。最終軟件里我做了個簡單的加權(quán)集成器根據(jù)驗證集誤差動態(tài)分配權(quán)重。import numpy as np from sklearn.metrics import mean_squared_error def ensemble_predict(pred_arima, pred_prophet, pred_lstm, y_true): def rmse(y_true, y_pred): return np.sqrt(mean_squared_error(y_true, y_pred)) e1 rmse(y_true, pred_arima) e2 rmse(y_true, pred_prophet) e3 rmse(y_true, pred_lstm) total 1/e1 1/e2 1/e3 w1, w2, w3 (1/e1)/total, (1/e2)/total, (1/e3)/total return w1 * pred_arima w2 * pred_prophet w3 * pred_lstm權(quán)重是根據(jù)RMSE的倒數(shù)算的誤差越小的模型權(quán)重越大。這個集成方法簡單有效而且每個月滾動重新計算一次權(quán)重模型自己會適應(yīng)季節(jié)變化。實測集成的RMSE比最好的單一模型下降約8%~12%對于空氣污染預(yù)測這種高波動場景已經(jīng)很可觀的提升了。5. 源碼工程怎么組織模塊劃分、接口設(shè)計和文檔配套5.1 工程目錄結(jié)構(gòu)說明拿到源碼后第一反應(yīng)就是看目錄結(jié)構(gòu)。好的工程結(jié)構(gòu)應(yīng)該讓人一眼就知道哪里改配置、哪里加模型、哪里看結(jié)果。我最終的項目文件組織結(jié)構(gòu)如下air_pollution_forecast/ # 本博客元信息用注釋括起來實際不用 ├── data/ # 原始數(shù)據(jù)和清洗后的數(shù)據(jù) │ ├── raw/ │ └── processed/ ├── src/ # 核心源碼 │ ├── data_fetch.py # 數(shù)據(jù)抓取 │ ├── preprocessing.py # 清洗、插值、異常值處理 │ ├── features.py # 特征工程 │ ├── models/ │ │ ├── sarima_model.py │ │ ├── prophet_model.py │ │ └── lstm_model.py │ ├── ensemble.py # 集成預(yù)測 │ ├── evaluation.py # 評估指標(biāo) │ └── forecast.py # 預(yù)測主流程 ├── docs/ # 配套文檔 │ ├── 使用說明.md │ ├── 開發(fā)文檔.md │ └── API接口文檔.md ├── config.yaml # 配置文件 ├── main.py # 程序入口 └── requirements.txt這里特別強(qiáng)調(diào)config.yaml的作用。剛開始我圖省事把API key、數(shù)據(jù)路徑、模型參數(shù)全寫在代碼里后來換數(shù)據(jù)集或者換機(jī)器改代碼改到懷疑人生。配置文件把所有可變參數(shù)提取出來要改時只動yaml文件代碼零改動。5.2 核心模塊的關(guān)鍵接口設(shè)計我設(shè)計接口的時候遵循一個原則每個模塊只做一件事模塊之間通過簡單的數(shù)據(jù)對象DataFrame或numpy數(shù)組通信。數(shù)據(jù)獲取模塊對外只暴露一個函數(shù)def fetch_hourly_data(city: str, start_date: str, end_date: str) - pd.DataFrame: Returns: DataFrame with columns: [time, pm25, pm10, no2, o3, ...] 預(yù)處理模塊def preprocess(raw_df: pd.DataFrame, config: dict) - pd.DataFrame: 完成缺失值插值、異常值替換、時間索引對齊。 Returns: 清洗后的DataFrameindex為DatetimeIndex 模型模塊統(tǒng)一繼承一個預(yù)測基類class BaseModel: def fit(self, train_df: pd.DataFrame) - None: ... def predict(self, steps: int) - np.ndarray: ...這樣做的好處是未來加新模型比如Informer、N-BEATS時不需要改動主流程只要新模型繼承BaseModel并實現(xiàn)fit和predict就行。5.3 文檔配套不只是給用戶看更是給未來的自己源碼工程里的文檔質(zhì)量往往被忽視。好的項目文檔至少要有三種使用說明、開發(fā)文檔、接口文檔。使用說明面向“不會讀代碼的人”重點(diǎn)寫怎么安裝依賴、修改配置、運(yùn)行、看結(jié)果。開發(fā)文檔面向“要擴(kuò)展的人”講清楚模塊之間的關(guān)系、數(shù)據(jù)流的方向以及每個模塊內(nèi)取舍的原因。接口文檔用表格列出每個函數(shù)的輸入輸出方便調(diào)用時快速查。我寫文檔時給自己立了個規(guī)矩如果我在代碼里寫了“# TODO”或者踩了一些坑一定要在對應(yīng)的文檔里寫清楚原因。比如LSTM輸入數(shù)據(jù)需要做Box-Cox變換這件事如果不寫進(jìn)文檔三個月后任何人接手都只會一頭霧水。5.4 運(yùn)行入口與配置示例軟件主入口main.py的邏輯非常簡單一共就五步讀取配置、加載數(shù)據(jù)、訓(xùn)練模型、預(yù)測、輸出結(jié)果和圖表。import yaml from src.data_fetch import fetch_hourly_data from src.preprocessing import preprocess from src.models import sarima_model, prophet_model, lstm_model from src.ensemble import ensemble_predict from src.evaluation import evaluate from src.forecast import plot_forecast if __name__ __main__: with open(config.yaml, r, encodingutf-8) as f: config yaml.safe_load(f) raw fetch_hourly_data( cityconfig[city], start_dateconfig[start_date], end_dateconfig[end_date] ) data preprocess(raw, config) train data.loc[:config[split_date]] test data.loc[config[split_date]:] sarima sarima_model.SARIMAPredictor(config) prophet prophet_model.ProphetPredictor(config) lstm lstm_model.LSTMPredictor(config) for model in [sarima, prophet, lstm]: model.fit(train) preds { sarima: sarima.predict(len(test)), prophet: prophet.predict(len(test)), lstm: lstm.predict(len(test)) } final ensemble_predict(preds[sarima], preds[prophet], preds[lstm], test[pm25]) metrics evaluate(test[pm25], final) print(metrics) plot_forecast(test[pm25], final, save_pathoutput/forecast.png)配置文件示例city: Beijing start_date: 2023-01-01 end_date: 2024-01-01 split_date: 2023-10-01 pollutants: - pm25 - pm10 - no2 model_parameters: sarima: seasonal_period: 24 p_max: 5 q_max: 5 prophet: changepoint_prior_scale: 0.05 lstm: window_size: 24 n_epochs: 50 batch_size: 64配置文件的好處是不同城市、不同污染物只需修改參數(shù)不必動代碼。6. 實測效果與踩坑記錄那些文檔里不會寫的事6.1 典型預(yù)測效果對比以北京某站點(diǎn)PM2.5小時濃度為例使用2023年1月到10月訓(xùn)練11月作為測試集。三種模型的RMSE對比模型RMSEMAE訓(xùn)練耗時SARIMA26.818.212分鐘Prophet23.516.46分鐘LSTM21.915.735分鐘集成19.713.9約50分鐘集成模型的RMSE比最好的單一模型降低了10%但訓(xùn)練時間也最長。如果你追求快速驗證建議先用Prophet如果要上線集成是更穩(wěn)妥的選擇。上面數(shù)據(jù)僅供參考不同城市差別很大污染源復(fù)雜的地方LSTM優(yōu)勢更明顯。6.2 坑一跨天預(yù)測時Prophet的節(jié)假日效應(yīng)Prophet默認(rèn)會在節(jié)假日改變預(yù)測但空氣污染不存在“節(jié)假日濃度一定會降”這種規(guī)律。有一次國慶節(jié)期間模型預(yù)測的PM2.5明顯偏低因為Prophet把節(jié)假日當(dāng)作特殊日期處理。解決辦法是在訓(xùn)練時把目標(biāo)污染物換成氣象條件或者干脆關(guān)閉節(jié)假日效應(yīng)只保留日期周期。model Prophet( yearly_seasonalityTrue, weekly_seasonalityTrue, daily_seasonalityTrue, holidaysNone # 關(guān)鍵關(guān)掉節(jié)假日 )6.3 坑二LSTM滾動預(yù)測的誤差累積LSTM多步預(yù)測時有一種常見做法把預(yù)測值當(dāng)作輸入繼續(xù)預(yù)測下一個點(diǎn)。這個做法在空氣質(zhì)量上非常不靠譜。因為預(yù)測本身有誤差誤差作為輸入回傳給模型會越滾越大往往到了第12小時預(yù)測值就趨向于一條直線。后來我改成“滾動窗口重填歷史值”的方式每預(yù)測一步就把真實值或者實測數(shù)據(jù)滑入窗口誤差累積一下小了非常多。如果你必須做純滾動預(yù)測沒有實時數(shù)據(jù)回傳最好限制預(yù)測長度超過24小時一定要給出error bar不能只給一條均值預(yù)測線。6.4 坑三模型評估指標(biāo)不能只看RMSERMSE對高濃度點(diǎn)特別敏感。秋冬季一次嚴(yán)重的霧霾事件可能把RMSE拉高40%但模型對普通天的預(yù)測其實很準(zhǔn)。我建議同時報告RMSE、MAE和R^2再分組看不同濃度區(qū)間的誤差比如“0-100”、“100-200”、“200”三個區(qū)間分別算指標(biāo)。如果高濃度區(qū)間誤差大說明模型對污染過程的峰值預(yù)測能力弱這時需要加強(qiáng)特征或者換非線性模型。還可以補(bǔ)充一個面向預(yù)警的指標(biāo)在PM2.5超過150這個閾值時模型預(yù)測的命中率hit rate和漏報率miss rate。因為環(huán)境監(jiān)測任務(wù)真正關(guān)心的是“重度污染是否會被預(yù)警”而不是均方誤差好不好看。6.5 坑四時間索引的時區(qū)問題做項目時還遇到過時區(qū)坑。數(shù)據(jù)源返回的時間是UTC本地分析要用北京時間UTC8。如果直接用原始時間戳建索引所有字段都會偏移8小時日周期建模就會錯位晚上8點(diǎn)的濃度峰值被當(dāng)成中午12點(diǎn)。處理方式是在數(shù)據(jù)抓取階段統(tǒng)一轉(zhuǎn)成目標(biāo)時區(qū)df[time] pd.to_datetime(df[time], utcTrue) df[time] df[time].dt.tz_convert(Asia/Shanghai)后續(xù)所有數(shù)據(jù)都基于本地時間避免后期反復(fù)切換。這個坑新手特別容易踩因為看起來數(shù)據(jù)數(shù)量都一樣畫圖也沒問題但模型效果就是差本質(zhì)是時序錯位。7. 從軟件到可交付項目打包與使用體驗優(yōu)化7.1 requirements.txt與Python環(huán)境項目要交到別人手里最尷尬的是對方裝不了依賴。我強(qiáng)烈建議你固定Python版本3.9以上3.11以下并且在requirements.txt里寫清楚具體版本不要只寫包名。當(dāng)時我吃過虧直接依賴最新版Prophet對方舊服務(wù)器上裝不上折騰了三天。推薦的最小依賴清單pandas2.0.3 numpy1.24.3 statsmodels0.14.0 pmdarima2.0.4 prophet1.1.4 scikit-learn1.3.0 tensorflow2.13.0 PyYAML6.0 matplotlib3.7.2 requests2.31.0建議再寫一個setup.md里面記錄你在什么系統(tǒng)上測試通過、每一步的安裝命令?,F(xiàn)在很多人已經(jīng)用uv或者poetry但無論用什么核心依賴鎖文件一定要有否則交付就是災(zāi)難。7.2 可視化輸出讓預(yù)測結(jié)果一目了然預(yù)測軟件除了報數(shù)字一定要出圖。我實現(xiàn)的圖表包含三部分歷史濃度曲線、預(yù)測均值曲線、90%置信區(qū)間帶。代碼不多但效果很好。import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(12, 5)) ax.plot(train[pm25], colorgray, labeltrain) ax.plot(test[pm25], colorblue, labelactual) ax.plot(pred_index, pred_mean, colorred, labelforecast) ax.fill_between( pred_index, pred_lower, pred_upper, colorred, alpha0.2, label90% interval ) ax.legend() ax.set_ylabel(PM2.5 (μg/m3)) fig.savefig(output/forecast.png, dpi150, bbox_inchestight)置信區(qū)間帶非常重要。沒有區(qū)間帶決策者很容易把均值預(yù)測當(dāng)成確定值。環(huán)境污染預(yù)警需要風(fēng)險意識區(qū)間寬度就是風(fēng)險提示。SARIMA和Prophet都自帶預(yù)測區(qū)間LSTM需要自己從多個隨機(jī)初始化模型的輸出中估算。7.3 與監(jiān)測平臺對接的增量式預(yù)測跨過一個里程碑后我把軟件改成了增量式預(yù)測每天凌晨2點(diǎn)重新拉取過去24小時真實數(shù)據(jù)重新訓(xùn)練或者部分?jǐn)M合模型然后生成未來3天的預(yù)測。增量式的好處是模型能及時響應(yīng)最近幾天排放源的突然變化比如工廠臨時檢修導(dǎo)致的濃度異常。增量式要注意窗口尺寸。如果每次訓(xùn)練只用了最近一個月的數(shù)據(jù)模型會忘記冬季和夏季差異進(jìn)入2月之后之前學(xué)到的1月規(guī)律完全沒用。我的做法是“長期數(shù)據(jù)全量訓(xùn)練近期數(shù)據(jù)加權(quán)微調(diào)”比如每年年初做一次全量重訓(xùn)平時每日更新時用最近30天數(shù)據(jù)微調(diào)。這個策略聽起來復(fù)雜但代碼實現(xiàn)并不難核心就是在原有模型實例上調(diào)用fit傳入增量數(shù)據(jù)即可Prophet和SARIMA都支持繼續(xù)訓(xùn)練。只有LSTM需要全量重訓(xùn)所以實際生產(chǎn)中LSTM通常作為離線備選模型線上主力還是ProphetSARIMA。8. 再聊兩句這套軟件還能怎么擴(kuò)展最后基于我的實際經(jīng)驗給拿到源碼準(zhǔn)備二次開發(fā)的人幾個方向參考。第一把單站點(diǎn)預(yù)測擴(kuò)展成區(qū)域預(yù)測。空氣污染不是單點(diǎn)問題把周邊多個站點(diǎn)的數(shù)據(jù)和氣象場作為共同輸入可以顯著提升預(yù)測精度尤其是靜穩(wěn)天氣下污染傳輸過程。第二接入實時監(jiān)測設(shè)備的自動校準(zhǔn)。設(shè)備漂移會導(dǎo)致數(shù)據(jù)出現(xiàn)系統(tǒng)性偏差時間序列模型會被帶偏如果能在預(yù)處理階段加入卡爾曼濾波或者設(shè)備偏差校正模型效果會更穩(wěn)。第三把預(yù)測結(jié)果通過Web服務(wù)暴露出來。我后期用FastAPI封裝了預(yù)測接口讓監(jiān)測站同事可以通過網(wǎng)頁輸入城市和時間范圍后臺自動跑模型返回JSON格式的預(yù)測值和區(qū)間。這個不難源碼的forecast.py已經(jīng)提供了核心函數(shù)包一層HTTP即可。這套“基于Python的時間序列分析的大氣污染預(yù)測軟件”的源碼和文檔目前已經(jīng)整理成完整工程。讀完全文你會發(fā)現(xiàn)真正困難的部分從來不是跑通一個模型而是把數(shù)據(jù)、模型、工程、文檔全部串成一個可持續(xù)使用的東西。希望這篇分享能幫你少走一些彎路在做自己的空氣污染預(yù)測項目時把精力花在模型調(diào)優(yōu)和業(yè)務(wù)落地這些真正有價值的事情上。