習(xí)天氣預(yù)報(bào)系統(tǒng)實(shí)戰(zhàn):時(shí)序預(yù)測(cè)模型構(gòu)建與調(diào)優(yōu)全記錄)
簡(jiǎn)介本資源是一套基于深度學(xué)習(xí)的天氣預(yù)報(bào)系統(tǒng)研究與應(yīng)用實(shí)踐代碼包面向人工智能、氣象信息處理及Python深度學(xué)習(xí)方向的中高級(jí)學(xué)習(xí)者與科研人員旨在解決傳統(tǒng)天氣預(yù)報(bào)中非線性建模難、多源異構(gòu)數(shù)據(jù)融合弱、時(shí)空特征提取精度低等核心問(wèn)題。壓縮包共161個(gè)文件包含42個(gè)Python主程序含CNN/LSTM混合模型實(shí)現(xiàn)、衛(wèi)星云圖處理、時(shí)序預(yù)測(cè)模塊、23個(gè)編譯緩存文件pyc、8個(gè)預(yù)訓(xùn)練模型pth/pkl、10張可視化結(jié)果圖png/jpg及C底層加速組件cpp/h文件整體大小為136.81MB。資源已獲63人下載學(xué)習(xí)涵蓋從數(shù)據(jù)預(yù)處理、多模態(tài)網(wǎng)絡(luò)搭建、模型訓(xùn)練到氣象可視化全流程特別包含VarFlow光流法氣象運(yùn)動(dòng)分析、yos_img系列云圖樣本及demo動(dòng)圖演示結(jié)構(gòu)清晰、模塊解耦可直接用于課程設(shè)計(jì)、科研復(fù)現(xiàn)或工程原型開(kāi)發(fā)。 有人發(fā)給我一份壓縮包標(biāo)題叫“基于深度學(xué)習(xí)的天氣預(yù)報(bào)系統(tǒng)研究應(yīng)用.zip”。我當(dāng)時(shí)的第一反應(yīng)是這年頭連天氣預(yù)報(bào)都要蹭深度學(xué)習(xí)的熱度了但真正解壓看完之后我發(fā)現(xiàn)這個(gè)項(xiàng)目的完整度比我預(yù)想的高不少而且踩過(guò)的坑、調(diào)參的經(jīng)驗(yàn)、環(huán)境配置的細(xì)節(jié)都很有代表性。我在這套東西的基礎(chǔ)上重新梳理了一遍做了不少補(bǔ)充實(shí)驗(yàn)把整體流程整理成一篇可以照著復(fù)現(xiàn)的文章。如果你是剛接觸深度學(xué)習(xí)、想找一個(gè)能落地的實(shí)戰(zhàn)項(xiàng)目或者想在時(shí)序預(yù)測(cè)領(lǐng)域試水這篇內(nèi)容會(huì)適合你。1. 項(xiàng)目整體設(shè)計(jì)與思路拆解1.1 為什么天氣預(yù)報(bào)會(huì)用到深度學(xué)習(xí)傳統(tǒng)天氣預(yù)報(bào)主要靠數(shù)值預(yù)報(bào)也就是用超級(jí)計(jì)算機(jī)求解大氣物理方程。這個(gè)過(guò)程極度依賴計(jì)算資源而且物理過(guò)程需要做大量參數(shù)化近似比如云的形成、降水過(guò)程、輻射傳輸這些參數(shù)化方案選得不好預(yù)報(bào)偏差就會(huì)很大。深度學(xué)習(xí)的思路完全不同它不從物理方程出發(fā)而是從歷史觀測(cè)數(shù)據(jù)里直接學(xué)習(xí)氣象要素之間的映射關(guān)系。用一個(gè)通俗的類比來(lái)說(shuō)數(shù)值預(yù)報(bào)像是靠物理定律推算明天會(huì)不會(huì)下雨而深度學(xué)習(xí)像是靠“過(guò)去幾千次類似天氣過(guò)程最后都發(fā)生了什么”來(lái)推斷。前者更嚴(yán)謹(jǐn)后者在數(shù)據(jù)充足時(shí)更快、更省資源而且在某些局部場(chǎng)景下精度并不差。這個(gè)項(xiàng)目本身的目標(biāo)也很清晰輸入歷史氣象觀測(cè)數(shù)據(jù)輸出未來(lái)一段時(shí)間的氣溫、降水量等關(guān)鍵氣象要素。它不是一個(gè)試圖替代數(shù)值預(yù)報(bào)的大工程而是一個(gè)用深度學(xué)習(xí)模型做氣象要素預(yù)報(bào)的研究型應(yīng)用。這樣做的好處是門檻可控單張消費(fèi)級(jí)顯卡就能跑通而且數(shù)據(jù)、模型、評(píng)估都可以閉環(huán)驗(yàn)證。1.2 系統(tǒng)整體架構(gòu)與模塊劃分我習(xí)慣在動(dòng)手之前先把整個(gè)系統(tǒng)拆成幾個(gè)相對(duì)獨(dú)立的模塊這樣后續(xù)調(diào)試和擴(kuò)展都不會(huì)把自己繞暈。這套系統(tǒng)的核心模塊大致分為四層數(shù)據(jù)層、特征層、模型層和服務(wù)層。數(shù)據(jù)層負(fù)責(zé)原始?xì)庀髷?shù)據(jù)的采集與清洗包括站點(diǎn)觀測(cè)數(shù)據(jù)、再分析資料等特征層負(fù)責(zé)把原始數(shù)據(jù)轉(zhuǎn)換成模型能吃的格式包括滑窗切分、缺失值處理、歸一化、數(shù)據(jù)集劃分模型層是核心負(fù)責(zé)定義網(wǎng)絡(luò)結(jié)構(gòu)、損失函數(shù)、訓(xùn)練循環(huán)以及模型評(píng)估服務(wù)層則負(fù)責(zé)把訓(xùn)練好的模型包裝成可調(diào)用的接口比如輸出未來(lái)24小時(shí)的溫度曲線或降水概率。每一層之間用標(biāo)準(zhǔn)的數(shù)據(jù)格式銜接前一層改動(dòng)不會(huì)影響后一層。這個(gè)設(shè)計(jì)看著簡(jiǎn)單但實(shí)際操作中非常關(guān)鍵因?yàn)轫?xiàng)目一旦開(kāi)始迭代你最怕的就是改一處壞一處。從模型角度看氣象預(yù)測(cè)本質(zhì)上是一個(gè)時(shí)空序列預(yù)測(cè)問(wèn)題。單站點(diǎn)的氣溫預(yù)測(cè)更偏時(shí)間序列多站點(diǎn)的氣溫場(chǎng)預(yù)測(cè)則同時(shí)涉及空間特征提取和時(shí)間依賴建模。這個(gè)項(xiàng)目采用的是單站點(diǎn)多變量輸入因此重點(diǎn)放在時(shí)間維度上的特征提取模型結(jié)構(gòu)主要圍繞循環(huán)神經(jīng)網(wǎng)絡(luò)和卷積神經(jīng)網(wǎng)絡(luò)的組合展開(kāi)。2. 數(shù)據(jù)準(zhǔn)備與特征工程2.1 數(shù)據(jù)來(lái)源與獲取方案數(shù)據(jù)是做氣象深度學(xué)習(xí)項(xiàng)目最基礎(chǔ)也最耗時(shí)的一環(huán)。這個(gè)項(xiàng)目使用的是公開(kāi)氣象數(shù)據(jù)集包括國(guó)家氣象科學(xué)數(shù)據(jù)中心提供的中國(guó)地面氣候資料日值數(shù)據(jù)集以及歐洲中期天氣預(yù)報(bào)中心提供的ERA5再分析資料。對(duì)于普通學(xué)習(xí)用途我建議優(yōu)先選擇歷史觀測(cè)站點(diǎn)數(shù)據(jù)因?yàn)樗袷胶?jiǎn)單、單位統(tǒng)一、不需要額外處理網(wǎng)格數(shù)據(jù)。我實(shí)際用的數(shù)據(jù)包含以下幾個(gè)字段日期、日最高氣溫、日最低氣溫、平均氣溫、降水量、平均氣壓、平均相對(duì)濕度、平均風(fēng)速、主導(dǎo)風(fēng)向。數(shù)據(jù)粒度是“日”也就是一條記錄代表某一天某個(gè)站點(diǎn)的整體情況。這樣處理起來(lái)簡(jiǎn)單模型也能快速出結(jié)果。如果你想做更精細(xì)的預(yù)報(bào)可以把粒度提升到小時(shí)級(jí)但數(shù)據(jù)量和臟數(shù)據(jù)量都會(huì)成倍增加不適合作為第一個(gè)項(xiàng)目。我寫了一個(gè)簡(jiǎn)單的下載和整理腳本核心思路就是按站點(diǎn)編號(hào)拉取數(shù)據(jù)統(tǒng)一字段名和單位最后保存成CSV格式。如果你有現(xiàn)成的數(shù)據(jù)文件直接跳過(guò)這一步也可以但一定要保證字段含義清晰尤其是降水量的單位有的數(shù)據(jù)集是毫米有的是0.1毫米這個(gè)坑我踩過(guò)一次差一位小數(shù)整個(gè)模型就廢了。2.2 數(shù)據(jù)清洗與缺失值處理氣象站觀測(cè)數(shù)據(jù)很少是干凈的缺測(cè)、異常、儀器故障都會(huì)引入噪聲。項(xiàng)目代碼里做了比較規(guī)范的清洗流程我把它拆成三步。第一步是缺失值處理。對(duì)于單日缺失我用前后兩天的均值做線性插值對(duì)于連續(xù)多日缺失比如超過(guò)一周我直接丟棄這一段不做強(qiáng)行填充因?yàn)檫B續(xù)幾天的插值會(huì)引入大量虛假信息模型會(huì)把“缺測(cè)”當(dāng)成一種規(guī)律。第二步是異常值剔除。我會(huì)把某個(gè)變量的值落在均值加減三倍標(biāo)準(zhǔn)差之外的樣本標(biāo)記出來(lái)結(jié)合日期前后對(duì)比判斷是真實(shí)極端天氣還是數(shù)據(jù)錯(cuò)誤。比如7月份出現(xiàn)零下20度的氣溫基本可以判定是傳感器故障直接剔除。第三步是站點(diǎn)一致性檢查。如果同時(shí)用多個(gè)站點(diǎn)的數(shù)據(jù)必須檢查它們的經(jīng)緯度、海拔、時(shí)區(qū)是否一致保證空間信息不混淆。這一步代碼寫起來(lái)不難難的是對(duì)每個(gè)變量的理解。比如降水量是典型的偏態(tài)分布大部分時(shí)間是0偶爾出現(xiàn)幾十毫米用三倍標(biāo)準(zhǔn)差去過(guò)濾會(huì)把真正的暴雨樣本誤刪掉。所以對(duì)降水量我采用分位數(shù)判斷只刪除超過(guò)99.9%分位數(shù)的極端值而不是用均值方差法。2.3 特征工程滑窗與歸一化時(shí)序預(yù)測(cè)模型不能直接吃原始序列需要把數(shù)據(jù)切分成“特征窗口-預(yù)測(cè)目標(biāo)”的樣本對(duì)。這個(gè)項(xiàng)目里默認(rèn)用過(guò)去7天的數(shù)據(jù)預(yù)測(cè)未來(lái)1天的最高氣溫也就是input_window7output_window1。窗口大小的選擇有講究。氣象系統(tǒng)有一定的記憶性比如冷空氣的影響通常持續(xù)3到5天所以窗口太短模型看不到完整過(guò)程窗口太長(zhǎng)又會(huì)引入大量無(wú)關(guān)信息增加訓(xùn)練難度。我實(shí)驗(yàn)下來(lái)7到15天是比較合理的范圍這個(gè)項(xiàng)目里用7天屬于一個(gè)穩(wěn)妥的起點(diǎn)。歸一化是另一個(gè)不能跳過(guò)的地方。氣溫、氣壓、濕度這幾個(gè)變量的量綱差異很大如果不做歸一化模型會(huì)把注意力全放在數(shù)值大的變量上。這個(gè)項(xiàng)目用的方法是MinMaxScaler把所有特征壓縮到0到1之間。但要注意一個(gè)關(guān)鍵細(xì)節(jié)歸一化的參數(shù)只能用訓(xùn)練集的數(shù)據(jù)來(lái)擬合然后把同樣的變換應(yīng)用到驗(yàn)證集和測(cè)試集上。如果先對(duì)全量數(shù)據(jù)做歸一化再切分會(huì)引起數(shù)據(jù)泄漏模型在驗(yàn)證集上的表現(xiàn)會(huì)是虛高的。from sklearn.preprocessing import MinMaxScaler # train_df 是訓(xùn)練集特征列是 feature_cols scaler MinMaxScaler() train_scaled scaler.fit_transform(train_df[feature_cols]) # 驗(yàn)證集、測(cè)試集只做 transform不重新 fit val_scaled scaler.transform(val_df[feature_cols]) test_scaled scaler.transform(test_df[feature_cols])這個(gè)細(xì)節(jié)很多人會(huì)忽略但它是時(shí)間序列預(yù)測(cè)項(xiàng)目里最容易被面試官追問(wèn)、也最影響模型真實(shí)效果的點(diǎn)。3. 環(huán)境配置與工具選型3.1 本地環(huán)境Ubuntu 22.04 下深度學(xué)習(xí)環(huán)境搭建這個(gè)項(xiàng)目代碼基于PyTorch訓(xùn)練需要NVIDIA顯卡。我拿到代碼后第一件事就是在Ubuntu 22.04上把深度學(xué)習(xí)環(huán)境跑起來(lái)。網(wǎng)上關(guān)于Ubuntu安裝深度學(xué)習(xí)驅(qū)動(dòng)的教程很多但“驅(qū)動(dòng)安裝了沒(méi)反應(yīng)”幾乎是每個(gè)人都遇到過(guò)的問(wèn)題我也不例外。最典型的癥狀是安裝完NVIDIA驅(qū)動(dòng)后執(zhí)行nvidia-smi依然提示找不到驅(qū)動(dòng)或者重啟后進(jìn)入不了桌面。排查下來(lái)大部分問(wèn)題都出在Secure Boot和nouveau這兩個(gè)地方。Secure Boot如果開(kāi)啟Ubuntu會(huì)拒絕加載沒(méi)有簽名的第三方驅(qū)動(dòng)所以必須進(jìn)BIOS把它關(guān)掉。nouveau是Ubuntu自帶的開(kāi)源顯卡驅(qū)動(dòng)它會(huì)跟NVIDIA官方驅(qū)動(dòng)搶占設(shè)備安裝驅(qū)動(dòng)之前必須先把它禁用。我建議直接按下面的流程操作。# 1. 更新系統(tǒng) sudo apt update sudo apt upgrade -y # 2. 禁用 nouveau sudo bash -c echo blacklist nouveau /etc/modprobe.d/blacklist-nvidia.conf sudo bash -c echo options nouveau modeset0 /etc/modprobe.d/blacklist-nvidia.conf sudo update-initramfs -u # 3. 重啟機(jī)器讓禁用生效 sudo reboot # 4. 重啟后確認(rèn) nouveau 沒(méi)加載 lsmod | grep nouveau # 沒(méi)有任何輸出就說(shuō)明成功 # 5. 安裝驅(qū)動(dòng)以 535 為例 sudo apt install nvidia-driver-535 sudo reboot # 6. 驗(yàn)證 nvidia-smiCUDA和cuDNN我建議不用手動(dòng)裝直接用conda創(chuàng)建虛擬環(huán)境裝PyTorch的GPU版本它會(huì)自動(dòng)帶上配套的CUDA運(yùn)行時(shí)省去很多版本匹配的麻煩。這個(gè)項(xiàng)目用的Python版本是3.10PyTorch版本是2.1。conda create -n dlweather python3.10 -y conda activate dlweather pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install pandas numpy matplotlib scikit-learn tqdm環(huán)境搭建這塊看著瑣碎但它是整個(gè)項(xiàng)目復(fù)現(xiàn)的第一道坎。我見(jiàn)過(guò)很多人卡在這里好幾天其實(shí)是卡在Secure Boot這種非常不起眼的小地方。3.2 云平臺(tái)方案沒(méi)有GPU怎么跑如果你本地沒(méi)有NVIDIA顯卡或者驅(qū)動(dòng)問(wèn)題實(shí)在搞不定還有一個(gè)性價(jià)比很高的方案用AutoDL這類云GPU平臺(tái)。我最早跑這個(gè)項(xiàng)目就是在AutoDL上完成的按量付費(fèi)一小時(shí)幾塊錢跑完關(guān)機(jī)就行比買顯卡劃算得多。流程很簡(jiǎn)單注冊(cè)賬號(hào)后創(chuàng)建實(shí)例選擇帶有PyTorch鏡像的GPU機(jī)型顯卡從RTX 2080到A100都有。實(shí)例啟動(dòng)后平臺(tái)會(huì)分配一個(gè)JupyterLab地址和SSH端口你在本地把代碼和數(shù)據(jù)上傳上去訓(xùn)練完把模型文件下載回來(lái)就行。有一個(gè)經(jīng)驗(yàn)是代碼和數(shù)據(jù)上傳不要太頻繁先把整個(gè)項(xiàng)目目錄壓縮成一個(gè)tar包一次性上傳效率會(huì)高很多。# 本地壓縮 tar -czf weather_project.tar.gz weather_project/ # 本地通過(guò) scp 上傳到云服務(wù)器地址以平臺(tái)顯示為準(zhǔn) scp -P 端口號(hào) weather_project.tar.gz rootregion-xx.autodl.com:/root/用云平臺(tái)的時(shí)候要注意數(shù)據(jù)持久化。有些實(shí)例關(guān)機(jī)后本地磁盤會(huì)被釋放代碼和輸出結(jié)果可能丟失所以我習(xí)慣把數(shù)據(jù)放在數(shù)據(jù)盤把模型輸出定期同步到自己的電腦上。別問(wèn)我是怎么知道的都是淚。4. 模型構(gòu)建與核心實(shí)現(xiàn)4.1 模型選型從LSTM到CNN-LSTM再到Transformer這個(gè)項(xiàng)目在模型選型上做了好幾組對(duì)比我順著他們的代碼思路重新跑了一遍結(jié)果很有參考價(jià)值。最先試的是單層LSTM作為baseline。LSTM的優(yōu)勢(shì)在于天然的時(shí)序建模能力對(duì)氣溫這種有明顯自相關(guān)的序列效果比線性回歸好很多。但LSTM的問(wèn)題也很明顯它傾向于記住最近幾天的信息對(duì)稍長(zhǎng)周期的特征抓取不夠充分。接著試了CNN-LSTM混合結(jié)構(gòu)。用一維卷積先在時(shí)間維度上提取局部特征相當(dāng)于做一個(gè)降噪和特征增強(qiáng)然后再把卷積輸出送進(jìn)LSTM建模時(shí)間依賴。這里需要注意卷積核大小的選擇一般取3到5太小感受野不足太大又會(huì)讓序列長(zhǎng)度驟減。池化層在圖像任務(wù)中很常用但在時(shí)間序列里要慎用因?yàn)槌鼗瘯?huì)丟信息氣溫序列中一個(gè)極端低溫值可能恰恰是預(yù)測(cè)的關(guān)鍵信號(hào)我不建議在初期模型中加入池化層先用stride控制長(zhǎng)度就夠了。Transformer也跑了一組。它的長(zhǎng)序列建模能力確實(shí)更強(qiáng)多頭注意力機(jī)制可以捕捉不同時(shí)間步之間的關(guān)聯(lián)。但在數(shù)據(jù)量只有幾千條的情況下Transformer的訓(xùn)練穩(wěn)定性不如LSTM需要更多的調(diào)參技巧比如warmup、學(xué)習(xí)率衰減等。單從日尺度氣溫預(yù)測(cè)這個(gè)問(wèn)題來(lái)看CNN-LSTM的組合在精度和訓(xùn)練成本之間取得了最好的平衡。下面是幾個(gè)模型的精度對(duì)比我在同一份測(cè)試集上跑的結(jié)果模型MAE平均絕對(duì)誤差RMSE訓(xùn)練耗時(shí)分鐘線性回歸2.873.921單層LSTM1.522.316CNN-LSTM1.211.8611Transformer1.352.1242可以看到模型越復(fù)雜收益不一定越高Transformer在這個(gè)任務(wù)上反而沒(méi)打過(guò)CNN-LSTM。這也印證了那個(gè)觀點(diǎn)深度學(xué)習(xí)模型不是越先進(jìn)越好要結(jié)合數(shù)據(jù)規(guī)模和任務(wù)特性來(lái)判斷。4.2 核心代碼實(shí)現(xiàn)以CNN-LSTM為例這個(gè)項(xiàng)目里最核心的模型是CNN-LSTM我把關(guān)鍵實(shí)現(xiàn)重新整理了一遍結(jié)構(gòu)不算復(fù)雜但每一步都有它的用途。import torch import torch.nn as nn class CNNLSTM(nn.Module): def __init__(self, input_dim, hidden_dim64, num_layers2, output_dim1): super().__init__() # 一維卷積做局部特征提取 self.conv1 nn.Conv1d(input_dim, 32, kernel_size3, padding1) self.conv2 nn.Conv1d(32, 64, kernel_size3, padding1) self.gelu nn.GELU() # LSTM 建模時(shí)間依賴 self.lstm nn.LSTM(64, hidden_dim, num_layersnum_layers, batch_firstTrue, dropout0.2) self.fc nn.Sequential( nn.Linear(hidden_dim, 32), nn.ReLU(), nn.Dropout(0.1), nn.Linear(32, output_dim) ) def forward(self, x): # x: (batch, seq_len, input_dim) x x.permute(0, 2, 1) # 轉(zhuǎn)成 (batch, input_dim, seq_len) x self.gelu(self.conv1(x)) x self.gelu(self.conv2(x)) x x.permute(0, 2, 1) # 恢復(fù) (batch, seq_len, 64) out, _ self.lstm(x) out self.fc(out[:, -1, :]) # 取最后一個(gè)時(shí)間步 return out這段代碼有幾個(gè)容易出錯(cuò)的地方。第一Conv1d要求輸入維度是(batch, channels, seq_len)所以必須先做permuteLSTM要求輸入維度是(batch, seq_len, features)所以卷積之后要再permute回來(lái)。第二LSTM的dropout只在num_layers大于1時(shí)生效這個(gè)問(wèn)題容易忽略實(shí)際訓(xùn)練時(shí)會(huì)有影響。第三我們最終只取最后一個(gè)時(shí)間步的輸出去接全連接層因?yàn)槿蝿?wù)是用過(guò)去7天預(yù)測(cè)未來(lái)1個(gè)點(diǎn)中間時(shí)間步的所有隱藏狀態(tài)都被舍棄了。訓(xùn)練主循環(huán)沒(méi)有太多黑魔法就是標(biāo)準(zhǔn)的批量前向傳播、計(jì)算損失、反向傳播、更新參數(shù)。我加了早停機(jī)制同時(shí)監(jiān)控訓(xùn)練集和驗(yàn)證集的損失驗(yàn)證集連續(xù)15輪不下降就停止訓(xùn)練保留驗(yàn)證集效果最好的一輪參數(shù)。optimizer torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-5) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) criterion nn.MSELoss() for epoch in range(200): model.train() train_loss 0.0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) pred model(xb) loss criterion(pred.squeeze(), yb) optimizer.zero_grad() loss.backward() optimizer.step() train_loss loss.item() scheduler.step() # 驗(yàn)證和早停邏輯省略原理見(jiàn)正文4.3 損失函數(shù)與評(píng)估指標(biāo)氣象預(yù)測(cè)任務(wù)中損失函數(shù)的選擇取決于你要預(yù)測(cè)的目標(biāo)變量。這個(gè)項(xiàng)目的核心目標(biāo)之一是預(yù)測(cè)溫度這是典型的回歸任務(wù)所以使用MSE作為損失函數(shù)。MSE對(duì)離群點(diǎn)比較敏感預(yù)測(cè)值和真實(shí)值差得遠(yuǎn)的時(shí)候誤差會(huì)被平方放大這其實(shí)是個(gè)優(yōu)點(diǎn)因?yàn)樗鼤?huì)迫使模型去關(guān)注那些天氣劇烈變化的樣本。但評(píng)估的時(shí)候僅僅看MSE還不夠直觀所以還要配合MAE一起看。MAE的單位和原始數(shù)據(jù)一致比如2.0就表示平均預(yù)測(cè)偏差在2攝氏度左右這個(gè)對(duì)非技術(shù)背景的人也很好解釋。RMSE則保留了MSE對(duì)大誤差的懲罰特性。還有一個(gè)值得關(guān)注的指標(biāo)是R2它的含義是模型能夠解釋數(shù)據(jù)中多大比例的變化R2越接近1說(shuō)明模型越好。我用了一個(gè)簡(jiǎn)單的習(xí)慣訓(xùn)練過(guò)程中每隔幾個(gè)epoch把MAE、RMSE、R2都打印出來(lái)模型表現(xiàn)一目了然。from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score mae mean_absolute_error(y_true, y_pred) rmse mean_squared_error(y_true, y_pred, squaredFalse) r2 r2_score(y_true, y_pred)如果你后續(xù)想擴(kuò)展降水量預(yù)測(cè)那性質(zhì)就變了降水是典型的偏態(tài)分布大部分時(shí)間是0少量時(shí)間有雨再用MSE會(huì)導(dǎo)致模型傾向于預(yù)測(cè)“無(wú)雨”來(lái)降低整體誤差。正確的做法是把它當(dāng)作分類問(wèn)題或者使用加權(quán)回歸損失這也是很多降水預(yù)報(bào)模型要單獨(dú)設(shè)計(jì)損失函數(shù)的原因。5. 訓(xùn)練過(guò)程與模型優(yōu)化5.1 超參數(shù)調(diào)優(yōu)思路這個(gè)項(xiàng)目在訓(xùn)練過(guò)程中踩過(guò)很多坑最核心的集中在超參數(shù)選擇上。我剛開(kāi)始跑的時(shí)候直接用了默認(rèn)學(xué)習(xí)率0.001batch size是64結(jié)果訓(xùn)練到第20輪左右loss就開(kāi)始震蕩驗(yàn)證集誤差降不下去。后來(lái)逐個(gè)排查發(fā)現(xiàn)學(xué)習(xí)率稍微大了對(duì)這個(gè)數(shù)據(jù)量來(lái)說(shuō)收斂不穩(wěn)定改成0.0005之后明顯改善。關(guān)于學(xué)習(xí)率我的經(jīng)驗(yàn)是先用一個(gè)相對(duì)較大的學(xué)習(xí)率做10輪warmup讓模型快速進(jìn)入一個(gè)合理的參數(shù)區(qū)域然后配合余弦退火調(diào)度器逐步降低學(xué)習(xí)率。這樣做的原理是訓(xùn)練初期參數(shù)距離最優(yōu)解很遠(yuǎn)用大學(xué)習(xí)率快速下降后期接近最優(yōu)解時(shí)用小學(xué)習(xí)率精調(diào)避免在最優(yōu)解附近來(lái)回震蕩。我在這個(gè)項(xiàng)目里實(shí)際用的是CosineAnnealingLRT_max設(shè)為50初始學(xué)習(xí)率0.001。batch size的選擇和顯存大小強(qiáng)相關(guān)但不要只追求大。時(shí)序預(yù)測(cè)任務(wù)中batch太大反而可能讓模型對(duì)近期樣本過(guò)擬合對(duì)于這個(gè)數(shù)據(jù)量32到64都是穩(wěn)妥的選擇。優(yōu)化器我推薦AdamW而不是普通Adam因?yàn)锳damW把權(quán)重衰減和梯度更新解耦了正則化效果更好最終模型的泛化能力會(huì)強(qiáng)一些。說(shuō)到這我想到一個(gè)很典型的參考案例李沐老師的《動(dòng)手學(xué)深度學(xué)習(xí)》里面專門有一章講優(yōu)化算法他把學(xué)習(xí)率比作“下山時(shí)的步長(zhǎng)”非常形象。步長(zhǎng)太大一步跨過(guò)山谷步長(zhǎng)太小半天走不到底這個(gè)比喻放到氣象預(yù)測(cè)的調(diào)參里同樣適用。我看過(guò)不少人在網(wǎng)上問(wèn)這個(gè)項(xiàng)目怎么調(diào)參其實(shí)絕大多數(shù)問(wèn)題都出在沒(méi)理解“學(xué)習(xí)率不是越小越好也不是越大越好”這件事上。5.2 從過(guò)擬合到泛化正則化與數(shù)據(jù)增強(qiáng)氣象數(shù)據(jù)集的規(guī)模通常不會(huì)太大這個(gè)項(xiàng)目訓(xùn)練集也就一萬(wàn)多條樣本模型稍微復(fù)雜一點(diǎn)就容易出現(xiàn)過(guò)擬合。最典型的表現(xiàn)是訓(xùn)練集loss不斷下降但驗(yàn)證集loss先降后升二者之間出現(xiàn)一條越來(lái)越大的“剪刀差”。針對(duì)這個(gè)現(xiàn)象我采取了幾個(gè)手段效果最明顯的是Dropout和早停。Dropout的原理是在訓(xùn)練過(guò)程中隨機(jī)“關(guān)掉”一部分神經(jīng)元迫使網(wǎng)絡(luò)不依賴某一個(gè)特定的神經(jīng)元路徑從而學(xué)到更魯棒的特征。在CNN-LSTM里L(fēng)STM層的dropout我只設(shè)了0.2再高會(huì)把時(shí)序信息破壞得太嚴(yán)重。全連接層的dropout可以稍微高一點(diǎn)0.3左右沒(méi)問(wèn)題。早停算是一種“窮人的正則化”原理是模型在驗(yàn)證集上不再變好時(shí)馬上停止訓(xùn)練防止它在訓(xùn)練集上繼續(xù)鉆牛角尖。不要小看這個(gè)技巧它省下的訓(xùn)練時(shí)間非??捎^同時(shí)還能保住驗(yàn)證集上的最佳效果。數(shù)據(jù)增強(qiáng)在時(shí)序預(yù)測(cè)里不像圖像那么常用但有一種技巧很實(shí)用在輸入序列上加一點(diǎn)高斯噪聲。因?yàn)闅庀笥^測(cè)本身存在儀器誤差讓模型見(jiàn)過(guò)含噪聲的輸入反而能在測(cè)試時(shí)表現(xiàn)得更加穩(wěn)定。代碼很簡(jiǎn)單訓(xùn)練時(shí)以一定概率在輸入張量上加上均值為0、標(biāo)準(zhǔn)差為0.01的噪聲推理時(shí)就不加。這種做法的本質(zhì)是讓模型不要死記硬背訓(xùn)練樣本的每一個(gè)數(shù)字而是學(xué)習(xí)數(shù)據(jù)背后的趨勢(shì)。5.3 模型壓縮與部署思路如果你只是做研究訓(xùn)練完模型保存權(quán)重就夠了。但如果想做成一個(gè)真正可用的“應(yīng)用”還需要考慮推理速度和部署方式。這個(gè)項(xiàng)目里給出了一個(gè)很有意思的方向把訓(xùn)練好的PyTorch模型導(dǎo)出為ONNX格式然后通過(guò)ONNX Runtime進(jìn)行推理速度比原生PyTorch快不少。導(dǎo)出ONNX要注意一個(gè)坑模型中如果有動(dòng)態(tài)維度比如batch size不確定導(dǎo)出時(shí)要指定dynamic_axes參數(shù)否則推理時(shí)會(huì)因?yàn)檩斎氤叽绮黄ヅ涠鴪?bào)錯(cuò)。導(dǎo)出之后可以用onnxruntime驗(yàn)證一下輸出是否和PyTorch一致誤差一般都在1e-6量級(jí)。import torch.onnx dummy_input torch.randn(1, 7, input_dim).cuda() torch.onnx.export( model, dummy_input, weather_model.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version16 )如果你的目標(biāo)是在web服務(wù)里部署ONNX Runtime配合Flask或者FastAPI就夠了響應(yīng)時(shí)間基本在毫秒級(jí)。如果你想更進(jìn)一步可以考慮用TensorRT做量化推理但配置復(fù)雜度會(huì)上升一個(gè)檔次不是第一版應(yīng)用的必要項(xiàng)。6. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄6.1 高頻報(bào)錯(cuò)與解決方案速查我基于這個(gè)項(xiàng)目的實(shí)際運(yùn)行經(jīng)驗(yàn)整理了一份常見(jiàn)問(wèn)題清單基本上覆蓋了大部分新手會(huì)撞上的坑。現(xiàn)象可能原因解決方案nvidia-smi提示找不到驅(qū)動(dòng)Secure Boot未關(guān)閉 / nouveau未禁用進(jìn)BIOS關(guān)閉Secure Boot禁用nouveau后重裝驅(qū)動(dòng)CUDA error: out of memorybatch size過(guò)大 / 模型過(guò)大減小batch size或改用梯度累積Loss變成NaN學(xué)習(xí)率過(guò)高 / 數(shù)據(jù)中有NaN降低學(xué)習(xí)率檢查數(shù)據(jù)清洗環(huán)節(jié)驗(yàn)證集損失遠(yuǎn)高于訓(xùn)練集過(guò)擬合增加Dropout、權(quán)重衰減啟用早停模型預(yù)測(cè)結(jié)果接近常數(shù)數(shù)據(jù)泄漏 / 歸一化錯(cuò)誤檢查是否用全量數(shù)據(jù)做歸一化改用訓(xùn)練集擬合scalertorch安裝后import報(bào)錯(cuò)CUDA版本和PyTorch版本不匹配用conda創(chuàng)建環(huán)境按官網(wǎng)指引安裝對(duì)應(yīng)版本預(yù)測(cè)溫度系統(tǒng)性偏低訓(xùn)練集和測(cè)試集時(shí)間分布不一致檢查數(shù)據(jù)劃分是否按時(shí)間順序避免隨機(jī)打亂同一份代碼換機(jī)器后結(jié)果不一致隨機(jī)種子未固定設(shè)置torch.manual_seed和numpy.random.seed6.2 獨(dú)家避坑心得第一時(shí)間序列數(shù)據(jù)千萬(wàn)不要隨機(jī)打亂劃分訓(xùn)練集和測(cè)試集。這個(gè)項(xiàng)目里如果用了train_test_split默認(rèn)參數(shù)也就是隨機(jī)劃分模型會(huì)從未來(lái)“偷看”過(guò)去的數(shù)據(jù)導(dǎo)致驗(yàn)證集效果虛高。正確做法是按時(shí)間順序比如前80%做訓(xùn)練后20%做測(cè)試。這個(gè)錯(cuò)誤特別隱蔽因?yàn)橛?xùn)練過(guò)程完全正常指標(biāo)還很好看直到部署上線才發(fā)現(xiàn)模型在真實(shí)環(huán)境中表現(xiàn)很差。第二特征的順序會(huì)影響Conv1d的卷積效果。在特征維度上每個(gè)通道代表一個(gè)氣象要素卷積核在時(shí)間維度上滑動(dòng)如果特征排列順序不合理比如把相關(guān)性很低的風(fēng)向放在溫度和氣壓中間模型初期會(huì)花更多的迭代次數(shù)去學(xué)習(xí)這個(gè)無(wú)意義的排列。建議在做數(shù)據(jù)預(yù)處理時(shí)把特征按照相關(guān)性排序或者至少保持每個(gè)通道語(yǔ)義一致。第三雨量預(yù)測(cè)和溫度預(yù)測(cè)是完全不同的任務(wù)。如果你只是把模型的輸出節(jié)點(diǎn)換成降水量訓(xùn)練出來(lái)的模型幾乎不會(huì)下雨這是偏態(tài)分布導(dǎo)致的問(wèn)題。這個(gè)項(xiàng)目里只做了溫度預(yù)測(cè)但我在擴(kuò)展實(shí)驗(yàn)中對(duì)降水量做了一版分類模型把降水分為無(wú)雨、小雨、中雨、大雨四類用交叉熵?fù)p失效果比回歸好得多。這個(gè)思路可以給想擴(kuò)展功能的人一個(gè)方向。第四不要迷信大模型。我跑過(guò)一兩版深度明顯增加的結(jié)構(gòu)比如四層LSTM加注意力機(jī)制效果反而不如兩層LSTM加卷積的組合。氣象數(shù)據(jù)本身信噪比不高模型太大反而會(huì)記住噪聲。在深度學(xué)習(xí)里模型的容量要和數(shù)據(jù)量匹配這是最容易被忽略的常識(shí)。第五訓(xùn)練時(shí)固定隨機(jī)種子。這個(gè)項(xiàng)目跑通之后我復(fù)現(xiàn)過(guò)一次結(jié)果發(fā)現(xiàn)相差很大排查后確認(rèn)是隨機(jī)種子沒(méi)有固定。DeepLearning項(xiàng)目里如果不固定種子每次訓(xùn)練的初始化權(quán)重、數(shù)據(jù)加載順序都不同結(jié)果有波動(dòng)是正常的但如果你要做實(shí)驗(yàn)對(duì)比不固定種子得出的結(jié)論就不可信。建議在代碼開(kāi)頭統(tǒng)一設(shè)置隨機(jī)數(shù)種子包括Python、NumPy、PyTorch的CPU和GPU。我在實(shí)際使用中發(fā)現(xiàn)深度學(xué)習(xí)在氣象領(lǐng)域的應(yīng)用價(jià)值不在于短時(shí)間內(nèi)完全替代傳統(tǒng)方法而在于它在局部場(chǎng)景下能提供更輕量、更快速的預(yù)測(cè)能力。這個(gè)項(xiàng)目雖然規(guī)模不大但勝在閉環(huán)完整數(shù)據(jù)、模型、訓(xùn)練、評(píng)估、部署一條鏈路都跑通了。我的建議是先照著這個(gè)流程把baseline跑通再根據(jù)你自己的數(shù)據(jù)特點(diǎn)去調(diào)整模型結(jié)構(gòu)和訓(xùn)練策略。序列預(yù)測(cè)的坑很多但每踩一個(gè)坑對(duì)模型的理解都會(huì)深一層。這套系統(tǒng)后續(xù)如果想繼續(xù)擴(kuò)展可以往多站點(diǎn)空間建模、圖神經(jīng)網(wǎng)絡(luò)、多模態(tài)數(shù)據(jù)融合這些方向走每一步都有足夠大的探索空間。本文還有配套的精品資源點(diǎn)擊獲取