測(cè)實(shí)戰(zhàn):LSTM時(shí)序建模+Flask動(dòng)態(tài)可視化全鏈路)
簡(jiǎn)介這是一份面向Python初學(xué)者與高校課程設(shè)計(jì)學(xué)生的天氣預(yù)測(cè)與可視化綜合實(shí)踐項(xiàng)目聚焦氣象數(shù)據(jù)爬取、時(shí)序建模預(yù)測(cè)及多維度圖表呈現(xiàn)適用于期末大作業(yè)、課程實(shí)訓(xùn)或數(shù)據(jù)分析入門實(shí)戰(zhàn)。資源共24個(gè)文件包含4個(gè)核心Python腳本main.py、GetData.py、ProcessData.py、GetModel.py、4個(gè)CSV數(shù)據(jù)集訓(xùn)練/驗(yàn)證/測(cè)試/今日天氣、12張效果截圖展示預(yù)測(cè)曲線、熱力圖、地圖疊加等可視化成果、1份Markdown使用文檔及1個(gè)預(yù)訓(xùn)練模型pkl文件包體僅1.42MB輕量易部署。已有203人學(xué)習(xí)下載項(xiàng)目經(jīng)助教審定、本地實(shí)測(cè)可運(yùn)行評(píng)審分達(dá)95分以上配套文檔詳述環(huán)境配置、數(shù)據(jù)獲取路徑、模型調(diào)參邏輯與繪圖定制方法特別適合理解從原始數(shù)據(jù)采集到結(jié)果可視化的完整閉環(huán)流程。1. 這不是「調(diào)個(gè) API 顯示天氣」的玩具項(xiàng)目它用真實(shí)氣象數(shù)據(jù)跑通了從爬蟲(chóng)→特征工程→LSTM 預(yù)測(cè)→動(dòng)態(tài)可視化全鏈路期末答辯被助教當(dāng)場(chǎng)截圖存檔你見(jiàn)過(guò)多少“Python 天氣預(yù)測(cè)”項(xiàng)目點(diǎn)開(kāi)全是 requests.get(https://api.xxx.com/weather) print(f今天{data[temp]}℃) —— 這類代碼連數(shù)據(jù)清洗都沒(méi)有更別說(shuō)模型驗(yàn)證和時(shí)序建模。而這個(gè)資源包是真正跑在本地、用中國(guó) 31 個(gè)省會(huì)城市近 3 年實(shí)測(cè)氣象數(shù)據(jù)date_train.csv / date_valid.csv / date_test.csv訓(xùn)練出可復(fù)現(xiàn) LSTM 模型Model.pkl并用 PlotlyFlask 渲染出帶時(shí)間滑塊、多變量聯(lián)動(dòng)、支持導(dǎo)出 PNG 的交互式天氣熱力圖wps23.jpg ~ wps28.jpg 全是運(yùn)行截圖。它不是 demo是完整閉環(huán)GetData.py 爬取天氣網(wǎng)歷史數(shù)據(jù) → ProcessData.py 做缺失值插補(bǔ)、滑動(dòng)窗口構(gòu)造時(shí)序樣本 → GetModel.py 定義 LSTM 結(jié)構(gòu)并保存權(quán)重 → main.py 一鍵啟動(dòng)預(yù)測(cè)可視化服務(wù)。適合 Python 初學(xué)者練手有詳細(xì) readme.md 和 step-by-step 使用文檔也經(jīng)得起課程設(shè)計(jì)答辯拷問(wèn)——95 分高分背后是每個(gè)模塊都留了 debug 日志開(kāi)關(guān)、每個(gè) CSV 都標(biāo)注了字段含義、每個(gè)繪圖參數(shù)都可調(diào)。如果你正卡在「學(xué)完 Pandas/Numpy/Scikit-learn 卻不知道怎么串成項(xiàng)目」或者需要一份能直接交作業(yè)、還能講清楚技術(shù)選型理由的期末大作業(yè)它就是那個(gè)不翻車的底牌。2. 數(shù)據(jù)獲取與預(yù)處理為什么不用公開(kāi) API 而堅(jiān)持爬取「天氣網(wǎng)」三個(gè)硬約束決定了必須自己動(dòng)手2.1 爬蟲(chóng)邏輯拆解GetData.py 不是簡(jiǎn)單抓 HTML而是對(duì)抗反爬結(jié)構(gòu)化清洗雙線程這個(gè)項(xiàng)目沒(méi)用任何第三方天氣 API如和風(fēng)、心知原因很實(shí)際期末作業(yè)要求「數(shù)據(jù)來(lái)源可追溯、過(guò)程可復(fù)現(xiàn)」。公開(kāi) API 的 key 有效期短、調(diào)用頻次受限、返回字段不穩(wěn)定比如某天突然把「濕度」字段名從humidity改成rh答辯時(shí)老師一句「你這數(shù)據(jù)哪來(lái)的」就可能扣分。所以 GetData.py 直接定位「天氣網(wǎng)tianqi.com」的歷史天氣頁(yè)用 requests BeautifulSoup 抓取關(guān)鍵在于它繞過(guò)了兩個(gè)典型障礙動(dòng)態(tài) UA 輪換天氣網(wǎng)對(duì)固定 User-Agent 會(huì)返回 403代碼里內(nèi)置了 5 個(gè)主流瀏覽器 UA 字符串每次請(qǐng)求隨機(jī)切換日期參數(shù)防錯(cuò)機(jī)制URL 中的年月日必須是合法日期如不能傳 2023-02-30GetData.py 內(nèi)置了 datetime 校驗(yàn)自動(dòng)跳過(guò)非法日期并記錄 warning# GetData.py 片段UA 輪換與日期校驗(yàn)核心邏輯 import requests from datetime import datetime, timedelta import random USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ] def is_valid_date(year, month, day): try: datetime(year, month, day) return True except ValueError: return False def fetch_weather_data(city_code, year, month, day): if not is_valid_date(year, month, day): print(f[WARN] Invalid date: {year}-{month}-{day}, skipped) return None url fhttps://www.tianqi.com/{city_code}/{year}{month:02d}{day:02d}/ headers {User-Agent: random.choice(USER_AGENTS)} try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() # 后續(xù)解析 HTML 表格... return parse_weather_table(resp.text) except Exception as e: print(f[ERROR] Failed to fetch {url}: {e}) return None提示city_code是天氣網(wǎng)為每個(gè)城市分配的英文縮寫如北京是beijing廣州是guangzhou項(xiàng)目已內(nèi)置china_today.csv提供全國(guó)主要城市 code 映射表無(wú)需手動(dòng)查。2.2 數(shù)據(jù)清洗關(guān)鍵ProcessData.py 如何把「臟 HTML 表格」變成 LSTM 可吃的時(shí)序張量爬下來(lái)的數(shù)據(jù)是 HTML 表格字段混雜如「氣溫」列含「12℃~23℃」、「多云轉(zhuǎn)晴」等非數(shù)值ProcessData.py 的核心任務(wù)是統(tǒng)一單位、填充缺失、構(gòu)造滑動(dòng)窗口、標(biāo)準(zhǔn)化。它不做簡(jiǎn)單 dropna而是用「前向填充 線性插值」組合策略對(duì)連續(xù)型字段溫度、濕度、氣壓先用前向填充ffill補(bǔ)短期斷點(diǎn)再對(duì)長(zhǎng)段缺失用線性插值interpolate(methodlinear)對(duì)離散型字段天氣狀況、風(fēng)向轉(zhuǎn)換為 one-hot 編碼如「晴」「多云」「小雨」→ [1,0,0], [0,1,0], [0,0,1]避免模型誤判數(shù)值大小關(guān)系# ProcessData.py 片段溫度字段清洗與滑動(dòng)窗口構(gòu)造 import pandas as pd import numpy as np def clean_temperature_series(df): # 提取最高溫字符串中第一個(gè)數(shù)字 df[high_temp] df[weather_text].str.extract(r(\d)℃).astype(float) # 提取最低溫字符串中第二個(gè)數(shù)字 df[low_temp] df[weather_text].str.extract(r℃~(\d)℃).astype(float) # 前向填充 線性插值 df[high_temp] df[high_temp].ffill().interpolate(methodlinear) df[low_temp] df[low_temp].ffill().interpolate(methodlinear) return df def create_sliding_window(data, window_size7, pred_horizon1): 構(gòu)造滑動(dòng)窗口輸入過(guò)去7天數(shù)據(jù)預(yù)測(cè)第8天最高溫 data: shape (n_samples, n_features) returns: X (n_samples-window_size, window_size, n_features), y (n_samples-window_size, 1) X, y [], [] for i in range(len(data) - window_size - pred_horizon 1): X.append(data[i:(i window_size)]) y.append(data[i window_size pred_horizon - 1, 0]) # 預(yù)測(cè) high_temp return np.array(X), np.array(y).reshape(-1, 1) # 實(shí)際調(diào)用 df_clean clean_temperature_series(raw_df) features [high_temp, low_temp, humidity, pressure, wind_speed] X_train, y_train create_sliding_window( df_clean[features].values, window_size7, pred_horizon1 )這段代碼輸出的X_train是三維數(shù)組(n_samples, 7, 5)正好喂給 LSTM 層y_train是一維目標(biāo)向量。注意window_size7是項(xiàng)目默認(rèn)值你完全可以改成 14 或 30 —— 但改之前得確認(rèn)數(shù)據(jù)量是否足夠len(data) window_size pred_horizon否則create_sliding_window會(huì)返回空數(shù)組后續(xù)訓(xùn)練直接報(bào)錯(cuò)。2.3 數(shù)據(jù)集文件清單與字段說(shuō)明別急著 run先看懂 date_train.csv 里每一列在說(shuō)什么項(xiàng)目提供的三個(gè) CSV 文件不是隨便命名的它們對(duì)應(yīng)機(jī)器學(xué)習(xí)標(biāo)準(zhǔn)流程文件名用途行數(shù)范圍關(guān)鍵字段共 12 列字段說(shuō)明date_train.csv訓(xùn)練集模型學(xué)習(xí)規(guī)律~2000 行date,city,high_temp,low_temp,weather,humidity,pressure,wind_direction,wind_speed,air_quality,uv_index,precipitationweather是原始文本如「多云」air_quality是數(shù)值0-500precipitation單位 mmdate_valid.csv驗(yàn)證集調(diào)參時(shí)監(jiān)控過(guò)擬合~500 行同上與 train 同分布但時(shí)間上連續(xù)train 是 2021-01~2022-06valid 是 2022-07~2022-12date_test.csv測(cè)試集最終評(píng)估模型泛化能力~300 行同上時(shí)間最晚2023-01~2023-03確保無(wú)時(shí)間穿越注意所有 CSV 的date列格式為YYYY-MM-DDcity列是中文城市名如「北京」不是拼音或 code。ProcessData.py 會(huì)自動(dòng)做 city → one-hot 映射所以你新增城市只需往 CSV 里加行不用改代碼。3. 模型構(gòu)建與訓(xùn)練LSTM 不是玄學(xué)它的輸入維度、層數(shù)、Dropout 值全由數(shù)據(jù)長(zhǎng)度和預(yù)測(cè)目標(biāo)決定3.1 GetModel.py 的 LSTM 結(jié)構(gòu)為什么用 2 層 LSTM Dropout0.3參數(shù)選擇有據(jù)可依很多初學(xué)者以為 LSTM 層數(shù)越多越好其實(shí)不然。本項(xiàng)目GetModel.py定義的模型結(jié)構(gòu)是經(jīng)過(guò)驗(yàn)證的平衡點(diǎn)輸入層(batch_size, 7, 12)→ 7 天 × 12 個(gè)特征注意date_train.csv有 12 列ProcessData.py 未刪減LSTM 層1return_sequencesTrue輸出(batch_size, 7, 64)保留時(shí)間步以便下一層繼續(xù)處理LSTM 層2return_sequencesFalse輸出(batch_size, 64)壓縮為單個(gè)向量Dense 層units32Dropout(0.3)→ 防止過(guò)擬合0.3 是經(jīng)驗(yàn)值小于 0.2 泛化差大于 0.5 收斂慢輸出層units1回歸預(yù)測(cè)單個(gè)數(shù)值如最高溫# GetModel.py 片段LSTM 模型定義Keras 2.x 風(fēng)格 from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam def build_lstm_model(input_shape(7, 12)): model Sequential([ LSTM(64, return_sequencesTrue, input_shapeinput_shape), Dropout(0.3), LSTM(64, return_sequencesFalse), Dropout(0.3), Dense(32, activationrelu), Dropout(0.3), Dense(1) # 輸出單個(gè)預(yù)測(cè)值 ]) model.compile( optimizerAdam(learning_rate0.001), lossmse, metrics[mae] ) return model # 實(shí)際調(diào)用main.py 中 model build_lstm_model(input_shape(7, 12)) history model.fit( X_train, y_train, validation_data(X_valid, y_valid), epochs100, batch_size32, verbose1 ) model.save(Model.pkl) # 注意這里用的是 keras.models.save_model不是 pickle.dump關(guān)鍵細(xì)節(jié)input_shape(7, 12)必須與create_sliding_window輸出的X_train.shape[1:]嚴(yán)格一致。如果改了window_size這里必須同步改否則model.fit()會(huì)報(bào)ValueError: Input 0 of layer sequential is incompatible with layer。3.2 模型評(píng)價(jià)指標(biāo)為什么 MAE 比 RMSE 更適合作為天氣預(yù)測(cè)的核心指標(biāo)項(xiàng)目在main.py中同時(shí)計(jì)算了MAE平均絕對(duì)誤差和RMSE均方根誤差但答辯時(shí)重點(diǎn)展示MAE原因很務(wù)實(shí)MAE 單位與原始數(shù)據(jù)一致比如最高溫預(yù)測(cè) MAE2.1℃老師一眼看懂「平均誤差 2 度」RMSE 對(duì)異常值敏感某天模型把 35℃ 預(yù)測(cè)成 15℃誤差 20℃RMSE 會(huì)被平方放大導(dǎo)致整體分?jǐn)?shù)虛高掩蓋日常預(yù)測(cè)穩(wěn)定性業(yè)務(wù)場(chǎng)景更看重「日常誤差」氣象服務(wù)用戶關(guān)心「明天大概幾度」而不是「極端天氣能否精準(zhǔn)捕捉」# main.py 片段模型評(píng)價(jià)邏輯 from sklearn.metrics import mean_absolute_error, mean_squared_error y_pred model.predict(X_test) mae mean_absolute_error(y_test, y_pred) rmse np.sqrt(mean_squared_error(y_test, y_pred)) print(fTest MAE: {mae:.2f}℃) print(fTest RMSE: {rmse:.2f}℃) # 附加繪制預(yù)測(cè) vs 真實(shí)值散點(diǎn)圖用于答辯 PPT import matplotlib.pyplot as plt plt.scatter(y_test, y_pred, alpha0.6) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--, lw2) plt.xlabel(True Temperature (℃)) plt.ylabel(Predicted Temperature (℃)) plt.title(Prediction Accuracy) plt.savefig(prediction_scatter.png, dpi300, bbox_inchestight)這段代碼生成的prediction_scatter.png就是wps25.jpg的來(lái)源——它比單純報(bào)數(shù)字更有說(shuō)服力。3.3 避坑LSTM 訓(xùn)練失敗的四個(gè)高頻現(xiàn)象、原因與秒級(jí)修復(fù)方案現(xiàn)象 1ValueError: Input 0 of layer lstm is incompatible with layer原因X_train.shape與input_shape不匹配。常見(jiàn)于修改window_size后忘記同步改build_lstm_model的input_shape。解決打印X_train.shape確認(rèn)第二維時(shí)間步和第三維特征數(shù)與input_shape一致。例如X_train.shape(1950, 7, 12)→input_shape(7,12)。現(xiàn)象 2訓(xùn)練 loss 不下降始終在 100 波動(dòng)原因數(shù)據(jù)未歸一化。LSTM 對(duì)輸入尺度極度敏感原始溫度0~40、濕度0~100、氣壓950~1050量綱差異太大。解決在ProcessData.py中加入MinMaxScaler或StandardScaler。項(xiàng)目已預(yù)留接口取消# scaler StandardScaler()注釋并對(duì)X_train/X_valid/X_test統(tǒng)一 fit-transform。現(xiàn)象 3CUDA out of memoryGPU 顯存不足原因默認(rèn)用 GPU 訓(xùn)練但學(xué)生筆記本顯存僅 2GBbatch_size32 超載。解決在main.py開(kāi)頭加兩行import os os.environ[CUDA_VISIBLE_DEVICES] -1 # 強(qiáng)制 CPU 模式CPU 訓(xùn)練慢 3~5 倍但 100 epoch 仍可在 20 分鐘內(nèi)完成且結(jié)果一致?,F(xiàn)象 4Model.pkl加載后預(yù)測(cè)結(jié)果全為 nan原因模型保存/加載方式錯(cuò)誤。項(xiàng)目用model.save(Model.pkl)是 Keras 原生保存HDF5 格式但有人誤用pickle.dump(model, open(Model.pkl,wb))導(dǎo)致結(jié)構(gòu)丟失。解決嚴(yán)格按項(xiàng)目 README 執(zhí)行model tf.keras.models.load_model(Model.pkl)不要用 pickle。4. 可視化系統(tǒng)搭建Flask Plotly 不只是「畫個(gè)圖」而是實(shí)現(xiàn)「城市切換變量聯(lián)動(dòng)時(shí)間回溯」三合一交互4.1main.py的 Flask 服務(wù)架構(gòu)為什么用/predict接口而非直接渲染 HTML項(xiàng)目沒(méi)有用 Jupyter Notebook 或 Matplotlibplt.show()而是啟動(dòng)一個(gè)本地 Web 服務(wù)http://127.0.0.1:5000原因在于答辯演示剛需老師用手機(jī)掃二維碼就能看效果不用裝 Python 環(huán)境交互邏輯解耦前端HTML/CSS/JS只負(fù)責(zé)展示后端Flask只負(fù)責(zé)計(jì)算符合工程規(guī)范動(dòng)態(tài)更新友好點(diǎn)擊「切換城市」按鈕前端發(fā) AJAX 請(qǐng)求到/predict?city上海后端實(shí)時(shí)加載對(duì)應(yīng)數(shù)據(jù)并返回 JSON頁(yè)面局部刷新# main.py 片段Flask 路由定義 from flask import Flask, render_template, request, jsonify import pandas as pd import numpy as np from tensorflow.keras.models import load_model app Flask(__name__) model load_model(Model.pkl) app.route(/) def index(): return render_template(天氣網(wǎng).html) # 靜態(tài)首頁(yè) app.route(/predict) def predict(): city request.args.get(city, 北京) # 加載該城市的 test 數(shù)據(jù)date_test.csv 中過(guò)濾 df_test pd.read_csv(date_test.csv) df_city df_test[df_test[city] city].copy() # 構(gòu)造輸入同 training 邏輯 X_input df_city[FEATURES].values[-7:].reshape(1, 7, len(FEATURES)) pred model.predict(X_input)[0, 0] # 返回 JSON前端用 Plotly 動(dòng)態(tài)渲染 return jsonify({ city: city, predicted_high_temp: float(pred), actual_high_temp: float(df_city[high_temp].iloc[-1]) }) if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)注意FEATURES [high_temp,low_temp,humidity,pressure,wind_speed]是硬編碼在main.py頂部的列表如果你在ProcessData.py中增刪了特征這里必須同步更新否則X_input維度錯(cuò)亂。4.2天氣網(wǎng).html的 Plotly 動(dòng)態(tài)圖表如何用 30 行 JS 實(shí)現(xiàn)「時(shí)間滑塊拖拽即重繪」天氣網(wǎng).html不是靜態(tài)圖片而是嵌入了 Plotly.js 的交互式圖表。核心邏輯在script標(biāo)簽里初始化圖表加載時(shí)默認(rèn)顯示北京未來(lái) 7 天預(yù)測(cè)調(diào)用/predict?city北京時(shí)間滑塊綁定input typerange拖動(dòng)時(shí)觸發(fā)updateChart()函數(shù)動(dòng)態(tài)數(shù)據(jù)源滑塊值映射到date_test.csv的行索引前端直接讀取 CSV通過(guò)fetch(date_test.csv)并提取對(duì)應(yīng)日期數(shù)據(jù)!-- 天氣網(wǎng).html 片段Plotly 圖表初始化 -- div idchart stylewidth:100%;height:400px;/div input typerange min0 max299 value0 idtimeSlider script srchttps://cdn.plot.ly/plotly-latest.min.js/script script let chartData []; async function loadData() { const response await fetch(date_test.csv); const csvText await response.text(); const rows csvText.split(\n).slice(1); // skip header chartData rows.map(row { const cols row.split(,); return { date: cols[0], city: cols[1], high_temp: parseFloat(cols[2]), low_temp: parseFloat(cols[3]) }; }); } function updateChart(index) { const data chartData.slice(index, index7); const dates data.map(d d.date); const highs data.map(d d.high_temp); const lows data.map(d d.low_temp); Plotly.react(chart, [{ x: dates, y: highs, name: 預(yù)測(cè)最高溫, mode: linesmarkers }, { x: dates, y: lows, name: 預(yù)測(cè)最低溫, mode: linesmarkers }], { title: 未來(lái)7天天氣預(yù)測(cè)${data[0].date} ~ ${data[6].date}, xaxis: {title: 日期}, yaxis: {title: 溫度 (℃)} }); } document.getElementById(timeSlider).oninput function() { updateChart(parseInt(this.value)); }; loadData().then(() updateChart(0)); /script這段 JS 直接操作 CSV 文本不依賴后端所以即使 Flask 服務(wù)關(guān)閉滑塊依然能本地運(yùn)行——這是答辯時(shí)的「后悔藥」萬(wàn)一現(xiàn)場(chǎng)網(wǎng)絡(luò)故障你還能指著屏幕說(shuō)「看純前端也能跑」。4.3 可視化參數(shù)自定義如何修改wps23.jpg那種熱力圖的顏色映射和分辨率項(xiàng)目截圖wps23.jpg是用plotly.express.imshow()生成的「城市-時(shí)間-溫度」熱力圖其核心參數(shù)在main.py的generate_heatmap()函數(shù)里參數(shù)當(dāng)前值修改建議效果zmin/zmaxNone自動(dòng)設(shè)為zmin0, zmax40固定色標(biāo)范圍避免單日極端值拉伸整個(gè)顏色條color_continuous_scaleViridis改為RdBu或Plasma更符合氣象習(xí)慣紅熱藍(lán)冷width/height800/600改為1200/800適配答辯 PPT 寬屏導(dǎo)出 PNG 更清晰# main.py 片段熱力圖生成函數(shù)可直接復(fù)制修改 import plotly.express as px import pandas as pd def generate_heatmap(df, output_pathheatmap.png): # df 格式index城市, columns日期, values最高溫 fig px.imshow( df.T, # 轉(zhuǎn)置使城市為 y 軸 labels{x: 城市, y: 日期, color: 最高溫 (℃)}, color_continuous_scaleRdBu, zmin0, zmax40, width1200, height800 ) fig.write_image(output_path, enginekaleido) # 需 pip install kaleido print(fHeatmap saved to {output_path}) # 調(diào)用示例 # df_heat load_city_temp_matrix() # 你自己的數(shù)據(jù)構(gòu)造邏輯 # generate_heatmap(df_heat)提示kaleido是 Plotly 官方推薦的高質(zhì)量導(dǎo)出引擎比orca更穩(wěn)定。安裝命令pip install kaleido若報(bào)錯(cuò)kaleido not found請(qǐng)先升級(jí) pippython -m pip install --upgrade pip。5. 從零運(yùn)行全流程五步走通「下載→環(huán)境配置→數(shù)據(jù)準(zhǔn)備→模型訓(xùn)練→可視化部署」附每步耗時(shí)與驗(yàn)證點(diǎn)5.1 第一步環(huán)境配置耗時(shí) ≤ 3 分鐘驗(yàn)證點(diǎn)python --version和pip list這不是「裝 Python」教程而是針對(duì)本項(xiàng)目最小化依賴清單# 創(chuàng)建虛擬環(huán)境推薦避免污染全局 python -m venv weather_env weather_env\Scripts\activate # Windows # source weather_env/bin/activate # macOS/Linux # 安裝核心依賴版本鎖定避免兼容問(wèn)題 pip install tensorflow2.13.0 pip install pandas1.5.3 pip install numpy1.23.5 pip install scikit-learn1.2.2 pip install flask2.2.5 pip install plotly5.18.0 pip install kaleido0.2.1 # 導(dǎo)出高清圖必需驗(yàn)證點(diǎn)運(yùn)行python -c import tensorflow as tf; print(tf.__version__)輸出2.13.0且無(wú)ImportError。若報(bào)No module named tensorflow檢查是否激活了weather_env。5.2 第二步數(shù)據(jù)準(zhǔn)備耗時(shí) ≤ 1 分鐘驗(yàn)證點(diǎn)date_train.csv行數(shù) ≥ 1800項(xiàng)目 ZIP 包里已含全部 CSV無(wú)需重新爬取GetData.py是備用方案。只需確認(rèn)解壓后目錄結(jié)構(gòu)正確weather/ ├── main.py ├── ProcessData.py ├── GetModel.py ├── GetData.py ├── date_train.csv ├── date_valid.csv ├── date_test.csv └── 天氣網(wǎng).html用 Excel 或head -n 5 date_train.csv查看前 5 行確認(rèn)有date,city,high_temp,...12 列且high_temp列數(shù)值合理非空、非負(fù)。驗(yàn)證點(diǎn)python -c import pandas as pd; print(len(pd.read_csv(date_train.csv)))輸出1950或接近值。若為0說(shuō)明 CSV 路徑錯(cuò)誤或文件損壞。5.3 第三步模型訓(xùn)練耗時(shí) 15~25 分鐘驗(yàn)證點(diǎn)Model.pkl生成 history.history[loss]末尾 5直接運(yùn)行main.py即可啟動(dòng)全流程python main.py它會(huì)自動(dòng)執(zhí)行加載date_train.csv→ProcessData.py清洗 → 構(gòu)造X_train/y_train調(diào)用GetModel.py創(chuàng)建模型 →model.fit()訓(xùn)練 100 epoch用date_valid.csv驗(yàn)證 → 保存Model.pkl啟動(dòng) Flask 服務(wù) → 打開(kāi)瀏覽器訪問(wèn)http://127.0.0.1:5000。驗(yàn)證點(diǎn)訓(xùn)練日志末尾出現(xiàn)100/100 [] - loss: 3.2145 - mae: 1.8923loss 5 即合格當(dāng)前目錄生成Model.pkl文件大小 ≥ 2MB瀏覽器打開(kāi)后顯示天氣網(wǎng).html頁(yè)面。5.4 第四步可視化驗(yàn)證耗時(shí) ≤ 30 秒驗(yàn)證點(diǎn)wps23.jpg類似熱力圖成功渲染在瀏覽器中點(diǎn)擊「城市選擇」下拉框選「上?!埂?頁(yè)面右上角顯示「上海28.3℃」拖動(dòng)底部時(shí)間滑塊 → 圖表日期自動(dòng)更新線條平滑無(wú)斷裂點(diǎn)擊「生成熱力圖」按鈕 → 彈出heatmap.png下載提示打開(kāi)后確認(rèn)是 1200×800 像素、紅藍(lán)漸變的城市溫度矩陣。驗(yàn)證點(diǎn)若點(diǎn)擊按鈕無(wú)反應(yīng)檢查瀏覽器控制臺(tái)F12 → Console是否有Failed to load resource: date_test.csv錯(cuò)誤——說(shuō)明date_test.csv未與天氣網(wǎng).html放在同一目錄。5.5 第五步答辯交付物打包耗時(shí) ≤ 2 分鐘驗(yàn)證點(diǎn)壓縮包內(nèi)含 4 個(gè)必需文件答辯前務(wù)必打包這 4 個(gè)文件其他可刪Model.pkl訓(xùn)練好的模型date_test.csv測(cè)試數(shù)據(jù)用于現(xiàn)場(chǎng)演示天氣網(wǎng).html前端頁(yè)面readme.md使用說(shuō)明老師必看驗(yàn)證點(diǎn)將這 4 個(gè)文件放入新文件夾雙擊天氣網(wǎng).html在無(wú) Flask 服務(wù)情況下時(shí)間滑塊仍能本地運(yùn)行證明前端獨(dú)立可用。6. 我踩過(guò)的最深的坑date_test.csv的日期順序必須嚴(yán)格升序否則時(shí)間滑塊會(huì)倒放——從那以后我每次生成測(cè)試集都強(qiáng)制加一行df.sort_values(date).to_csv(...)這個(gè)坑讓我在答辯前夜調(diào)試到凌晨?jī)牲c(diǎn)?,F(xiàn)象是拖動(dòng)時(shí)間滑塊圖表顯示的日期從2023-03-01→2023-02-28→2023-02-27… 完全倒序。排查了 JS、Flask、甚至懷疑瀏覽器緩存最后發(fā)現(xiàn)date_test.csv里date列是亂序的——因?yàn)榕老x(chóng)抓取時(shí)按網(wǎng)頁(yè)發(fā)布時(shí)間排序而非自然日期。根本原因天氣網(wǎng).html的updateChart()函數(shù)假設(shè)date_test.csv按date升序排列slice(index, index7)才能取到連續(xù) 7 天。如果 CSV 本身亂序slice取出的就是隨機(jī)日期組合。血淚解決方案在ProcessData.py末尾加強(qiáng)制排序# ProcessData.py 末尾追加確保測(cè)試集有序 def ensure_date_order(csv_path): df pd.read_csv(csv_path) df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) df[date] df[date].dt.strftime(%Y-%m-%d) # 恢復(fù)字符串格式 df.to_csv(csv_path, indexFalse) print(f[INFO] {csv_path} sorted by date) # 在 main.py 訓(xùn)練前調(diào)用 ensure_date_order(date_test.csv)但這只是補(bǔ)救。更徹底的做法是在GetData.py爬取完成后就對(duì)每批數(shù)據(jù)按date排序再寫入 CSV。我現(xiàn)在的習(xí)慣是只要涉及時(shí)間序列的 CSV生成后第一件事就是pandas.read_csv().sort_values().to_csv()哪怕多花 0.1 秒也比答辯時(shí)面對(duì)老師「你這圖怎么倒著走」的沉默強(qiáng)。另一個(gè)隱形坑是china_today.csv的城市編碼。項(xiàng)目用它做城市篩選但文件里「呼和浩特」寫成了「呼市」導(dǎo)致GetData.py爬取失敗。我的做法是打開(kāi)china_today.csvCtrlF 搜索所有「市」字把「哈市」「沈市」等簡(jiǎn)稱全替換成「哈爾濱」「沈陽(yáng)」——城市名必須與date_train.csv中的city列完全一致一個(gè)字都不能差。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取