
簡介這是一份面向計算機專業(yè)畢業(yè)設計及課程作業(yè)的股票智能預測系統(tǒng)完整源碼包以人工智能技術為核心結合金融場景實現(xiàn)數(shù)據(jù)采集、清洗、建模、預測與可視化全鏈路。全包共2000個文件壓縮后約23.82MB其中包含809個HTML頁面與426個JavaScript腳本構成前端展示層后端則涵蓋Java、Python、PHP等多種語言實現(xiàn)的接口與業(yè)務邏輯并配套SQL數(shù)據(jù)庫、YAML配置及若干訓練好的模型文件方便直接運行或二次開發(fā)。項目內(nèi)含大量可復用的算法模塊與接口示例如基于LSTM、GRU的時間序列預測模型以及線性回歸、隨機森林等經(jīng)典算法既可用于快速搭建完整系統(tǒng)也能深入拆解數(shù)據(jù)處理邏輯與模型調(diào)參細節(jié)。同時源碼對數(shù)據(jù)采集、標準化、特征工程、訓練評估等模塊進行了清晰劃分并帶有可視化圖表與前端交互代碼幫助讀者理解從原始行情到最終預測結果的完整工程流程。已有187人學習下載適合正在籌備相關課題的高校學生、需要參考完整項目架構的開發(fā)者以及想了解AI在量化投資中落地的初學者。1. 股票智能預測系統(tǒng)畢設里的“全棧數(shù)據(jù)項目”到底怎么落地如果你拿到的是一個名為“畢設課程作業(yè)_股票智能預測系統(tǒng).zip”的項目包里面大概率跑的是這樣一條流水線把歷史行情數(shù)據(jù)下載到本地加工成技術面特征喂給分類或回歸模型預測后面幾個交易日的漲跌再拿到回測里看有沒有超額收益。作為畢業(yè)設計這套系統(tǒng)的真實難點不在模型有多先進而在工程鏈路能不能閉合——數(shù)據(jù)、特征、訓練、回測、可視化成一條線評委和老師隨時能復現(xiàn)。適合拿來當課題的是學過 Python 和機器學習基礎、又想快速攢出一個完整項目的同學。這個方向坑很典型不是網(wǎng)絡請求報錯就是回測曲線漂亮得讓人心虛。2. 數(shù)據(jù)鏈路與特征工程先讓“喂給模型的東西”保持干凈2.1 數(shù)據(jù)源選型Akshare、Tushare 還是本地 CSV做畢設最怕數(shù)據(jù)源卡脖子。我最常用的做法是優(yōu)先用 Akshare 的免費接口拉日線數(shù)據(jù)代碼短、不用注冊適合課程作業(yè)Tushare 需要 token但歷史數(shù)據(jù)完整性和字段規(guī)范更好適合時間跨度較大的實驗。Akshare 的日線接口返回的是 DataFrame第一版原型直接用to_csv存本地后面再做成 SQLite 或 Parquet。這里有一個樸素的標準哪個數(shù)據(jù)源能在十分鐘內(nèi)讓你跑出一張head()表就先用它。數(shù)據(jù)源是否需要注冊歷史深度主要風險適合場景Akshare否部分接口支持多年接口偶爾調(diào)整字段課程作業(yè)、快速原型Tushare需要 token較完整積分限制頻率時間跨度大、需要規(guī)范字段本地 CSV無取決于自采范圍覆蓋不全離線實驗、演示回放import akshare as ak import pandas as pd # 拉取貴州茅臺的日線行情 df ak.stock_zh_a_hist( symbol600519, perioddaily, start_date20190101, end_date20231231, adjustqfq ) # 統(tǒng)一列名akshare 返回中文列名改成英文方便后面特征工程 df df.rename(columns{ 日期: date, 開盤: open, 收盤: close, 最高: high, 最低: low, 成交量: volume, }) df[date] pd.to_datetime(df[date]) df df[[date, open, high, low, close, volume]].sort_values(date) df.to_csv(data/600519_daily.csv, indexFalse) print(df.head())這段代碼邏輯很簡單先用 Akshare 取貴州茅臺 2019 到 2023 的前復權日線再把中文字段改成統(tǒng)一英文名保證后面所有模型代碼只用 open、close 這類標準字段。adjustqfq一定要保留前復權能消除除權除息造成的價格跳空否則模型會把分紅當成暴跌。sort_values(date)是容易被忽略的一步很多接口返回順序不定按時間升序是時序建模的基本前提。如果在校園網(wǎng)環(huán)境里接口超時正確做法是退回 Tushare 或者直接用本地行情 CSV。課程作業(yè)階段不必追求在線實時數(shù)據(jù)把某只股票五年日線存成 CSV整個訓練流程完全可以離線跑通。不推薦去爬網(wǎng)頁數(shù)據(jù)反爬和解析成本足夠拖垮一個畢設周。數(shù)據(jù)量上單只股票五年日線不到 1500 行做 LSTM 訓練其實偏少所以很多畢設會選 3 到 5 只股票做橫向對比或者把窗口縮短到 15 天。2.2 特征工程從 OHLCV 到技術指標與標簽模型吃進去的不是原始行情而是特征。常見做法是計算過去 N 日的漲跌幅、均線、RSI、MACD、成交量變化率。特征不是越多越好而是每個特征都有一個明確含義這樣答辯時被問“為什么選這個特征”能講得清。我一般會在特征工程里留一份手工計算的中間結果方便和第三方庫交叉驗證。第三方庫計算結果是一個黑匣子手工算一遍反而能幫你回答評委的細節(jié)問題。import numpy as np def make_features(df, window10): tmp df.copy() # 每日收益 tmp[ret] tmp[close].pct_change() # N 日均線用于捕捉價格趨勢 tmp[ma] tmp[close].rolling(window).mean() # 相對強弱指標 RSI簡化為 N 日內(nèi)漲跌幅均值之比 delta tmp[close].diff() up delta.clip(lower0).rolling(window).mean() down (-delta.clip(upper0)).rolling(window).mean() tmp[rsi] 100 * up / (up down) # 當前股價距離 N 日最高點的回撤幅度 tmp[drawdown] tmp[close] / tmp[close].rolling(window).max() - 1 # 成交量變化率衡量資金活躍程度 tmp[vol_ratio] tmp[volume] / tmp[volume].rolling(window).mean() # MACD 差離值12 日 EMA 與 26 日 EMA 之差 ema12 tmp[close].ewm(span12, adjustFalse).mean() ema26 tmp[close].ewm(span26, adjustFalse).mean() tmp[macd] ema12 - ema26 # 標簽未來 5 個交易日收益是否為正作為二分類目標 tmp[future_ret] tmp[close].shift(-5) / tmp[close] - 1 tmp[label] (tmp[future_ret] 0).astype(int) # 去掉計算產(chǎn)生的空值 return tmp.dropna().reset_index(dropTrue) df pd.read_csv(data/600519_daily.csv, parse_dates[date]) data make_features(df, window10) print(data[[close, ret, ma, rsi, drawdown, macd, label]].tail())這一段里有兩個關鍵選擇一是用shift(-5)構造未來 5 日收益的標簽而不是預測明天。原因是 5 日窗口能過濾掉大量日內(nèi)噪聲模型學起來比預測單日漲跌更容易收斂答辯時也更好解釋。二是pct_change和rolling天然產(chǎn)生前幾個空值dropna()以后樣本量會少一個窗口長度這是正常的。rsi公式是簡化版沒有用 EWMA 平滑但對模型來說已經(jīng)夠用答辯時如實說明即可。另外要特別強調(diào)特征計算里像rolling(window).mean()這種操作第 t 行用的只是 t 時刻以前的數(shù)據(jù)邏輯上沒有問題。但如果你在后面又對特征做了一次全局shift(-1)那就是讓第 t 天看到了 t1 天屬于典型的前視偏差。我習慣在make_features函數(shù)末尾把所有特征列單獨檢查一遍確保沒有任何一列是依賴未來數(shù)據(jù)的。所有特征必須只依賴過去數(shù)據(jù)這是避免前視偏差的第一道關卡。特征數(shù)量控制在 8 到 15 個之間超過 20 個特征在數(shù)據(jù)量只有一千多行時很容易過擬合而且評委問起來也很難逐個解釋。2.3 數(shù)據(jù)切分與歸一化時序拆分是硬要求結構化數(shù)據(jù)做機器學習很多人第一反應是train_test_split隨機切分——這在股票預測里是嚴重錯誤。隨機打亂等于把未來信息混進訓練集回測自然漂亮實盤必翻車。時序任務必須按時間順序切訓練集占總數(shù)的 70%驗證集 15%測試集 15%。驗證集用來調(diào)參測試集只在最后評分用一次用多了會讓測試集變成隱式訓練集。from sklearn.preprocessing import StandardScaler train_ratio, val_ratio 0.7, 0.15 n len(data) train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) feature_cols [ret, ma, rsi, drawdown, vol_ratio, macd] X_train, y_train data.iloc[:train_end][feature_cols], data.iloc[:train_end][label] X_val, X_test data.iloc[train_end:val_end][feature_cols], data.iloc[val_end:][feature_cols] y_val, y_test data.iloc[train_end:val_end][label], data.iloc[val_end:][label] scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 只在訓練集上 fit X_val_scaled scaler.transform(X_val) X_test_scaled scaler.transform(X_test)scaler.fit_transform和transform分離是數(shù)據(jù)泄露問題上最容易踩的雷。標準化用的均值和方差只能由訓練集計算驗證集、測試集都必須復用同一組參數(shù)。如果對全量數(shù)據(jù)先做標準化再切分測試集的信息已經(jīng)滲入訓練過程評測結果會虛高。這組切分后的數(shù)據(jù)可以立刻喂給邏輯回歸或隨機森林做基線后續(xù) LSTM 需要重新生成滑動窗口序列。切分完之后還可以順手檢查標簽分布是否均衡直接y_train.mean()。如果均值是 0.35說明訓練集中下跌樣本占 65%模型什么都不做一直猜跌也能有 65% 準確率——后面要針對這個做處理。數(shù)據(jù)行數(shù)較少時建議把訓練、驗證、測試的比例從 7:1.5:1.5 改成 8:1:1確保測試集至少有 120 個交易日否則回測曲線只有幾個點沒有說服力。3. 模型選型與訓練LSTM 不是唯一答案但最容易講清楚3.1 基線模型先跑邏輯回歸確認數(shù)據(jù)里確實有信號不要一上來就搭 LSTM。先用邏輯回歸或隨機森林在 2.3 節(jié)的特征表上跑一遍得到準確率、F1 這些指標。如果基線準確率接近 50%往往不是模型問題而是特征工程或標簽構造的問題只有基線明顯超過隨機猜測再上 LSTM 才有對比意義。這一步還能讓答辯時的評委看到你有建模思維而不是一把梭。from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, f1_score model LogisticRegression(max_iter500) model.fit(X_train_scaled, y_train) for name, X, y in [(train, X_train_scaled, y_train), (val, X_val_scaled, y_val), (test, X_test_scaled, y_test)]: pred model.predict(X) print(f{name}: acc{accuracy_score(y, pred):.3f}, f1{f1_score(y, pred):.3f}) # 打印特征系數(shù)答辯時可以用來解釋模型行為 coef pd.Series(model.coef_[0], indexfeature_cols).sort_values(keyabs, ascendingFalse) print(coef)邏輯回歸的優(yōu)勢在于可解釋——每個特征對應的系數(shù)可以直接打印出來答辯時可以指著某個系數(shù)說“當前價格離 10 日最高點越遠模型越傾向于預測反彈?!比绻€分數(shù)和隨機猜測相差不到 1 個百分點通常說明特征里沒有足夠的信息先回頭檢查特征和標簽再考慮換模型?;€模型還有一個容易被忽略的作用它幫你估算數(shù)據(jù)本身的天花板。假如邏輯回歸在測試集上準確率只有 51%你后面的 LSTM 就算調(diào)得再好也很難超過 55%因為你用的是同一批特征。這時應該考慮的不是把 LSTM 層數(shù)加到 5 層而是重新構造標簽——比如把二分類改成三分類漲、平、跌或者把預測周期從 5 日改成 10 日。3.2 LSTM 輸入構造與 PyTorch 實現(xiàn)窗口長度與特征維度決定了一半效果LSTM 和表格模型的區(qū)別在于輸入形狀表格模型一行樣本是一根 K 線而 LSTM 一個樣本是連續(xù) N 根 K 線組成的時間窗口。窗口長度一般取 20 到 60我常用 20因為 20 個交易日大約是一個月剛好覆蓋一輪短期趨勢。輸入維度是特征工程里的特征數(shù)。PyTorch 的Dataset作用就是把二維特征表切成三維窗口樣本。import torch from torch.utils.data import Dataset, DataLoader class StockDataset(Dataset): def __init__(self, features, labels, window20): self.features torch.tensor(features, dtypetorch.float32) self.labels torch.tensor(labels, dtypetorch.float32) self.window window def __len__(self): return len(self.features) - self.window def __getitem__(self, idx): x self.features[idx: idx self.window] # (window, n_features) y self.labels[idx self.window] # 用窗口之后的一天做標簽 return x, y這里的核心邏輯在__getitem__取連續(xù)window行特征作為輸入取窗口結束后的下一天的label作為輸出。這個錯位關系一旦搞錯模型就會拿著未來數(shù)據(jù)作弊。配合上一節(jié)的StandardScaler注意標準化必須在構造窗口之前完成因為窗口中的每個特征都要使用同一套均值方差如果先切窗口再標準化不同窗口之間的尺度無法對齊。接下來是 LSTM 模型主體import torch.nn as nn class LSTMPrediction(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, dropout0.3): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout, ) self.fc nn.Sequential( nn.Linear(hidden_size, 16), nn.ReLU(), nn.Linear(16, 1), ) def forward(self, x): out, _ self.lstm(x) # out: (batch, window, hidden_size) last out[:, -1, :] # 取最后一個時間步的輸出 return self.fc(last).squeeze()模型參數(shù)說明參數(shù)常見取值影響input_size特征數(shù)本文是 6每個時間步看到的維度hidden_size64128越大擬合能力越強也越容易過擬合num_layers122 層是畢設兼顧效果和穩(wěn)定性的選擇dropout0.20.5層間隨機丟棄防止過擬合window2060太短抓不到中期趨勢太長樣本量驟減batch_firstTrue表示輸入形狀是 (batch, window, features)初始化時與數(shù)據(jù)形狀對齊。forward 里取out[:, -1, :]是 LSTM 預測的標準寫法——用窗口最后一個時間步的隱狀態(tài)輸出而不是把全部時間步都送入全連接層。輸出層只有一個神經(jīng)元配合BCEWithLogitsLoss做二分類。3.3 訓練循環(huán)與超參數(shù)學習率、早停、隨機種子訓練代碼里最容易出亂子的不是模型結構而是三個細節(jié)二分類損失函數(shù)要用BCEWithLogitsLoss而不能直接用CrossEntropyLoss每次迭代固定 shuffle 順序驗證集 loss 連續(xù)多個 epoch 不降就早停防止過擬合。def train_model(model, train_loader, val_loader, lr1e-3, epochs30, patience5): criterion nn.BCEWithLogitsLoss() optimizer torch.optim.Adam(model.parameters(), lrlr) best_val float(inf) wait 0 for epoch in range(epochs): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() logits model(x_batch) loss criterion(logits, y_batch) loss.backward() optimizer.step() model.eval() val_loss 0.0 with torch.no_grad(): for x_batch, y_batch in val_loader: logits model(x_batch) val_loss criterion(logits, y_batch).item() val_loss / len(val_loader) print(fepoch{epoch1}, val_loss{val_loss:.4f}) if val_loss best_val: best_val val_loss wait 0 torch.save(model.state_dict(), best_lstm.pt) else: wait 1 if wait patience: print(early stop) breakpatience5表示驗證集 loss 連續(xù) 5 個 epoch 沒有下降就終止訓練torch.save只保存最優(yōu)權重而不是最后一次迭代的權重這兩點能讓模型穩(wěn)定很多。學習率從1e-3開始如果訓練 loss 震蕩明顯降到1e-4。訓練前記得組裝 DataLoader 并固定隨機種子這一步我在第 5 章會展開講。有一個容易忽略的參數(shù)是batch_size股票數(shù)據(jù)窗口樣本通常只有幾百到一千個batch_size32或64比較合適如果設成 128 以上每個 batch 的梯度方向會過于平均模型收斂慢。4. 回測與可視化讓模型的“成績單”能拿去答辯4.1 手寫回測引擎年化收益、最大回撤、夏普比率訓練完模型不算完評委最常問的問題就是“預測準確率這么高那實盤能不能賺錢”。這時候需要一個回測引擎回答這個問題?;販y的本質是按時間順序遍歷測試集模型給出每個交易日的預測方向按這個方向模擬買賣統(tǒng)計收益、回撤和風險調(diào)整后收益。我推薦自己寫而不是用第三方框架因為幾十行代碼能讓評委看清每一個細節(jié)而且不會有“調(diào)庫黑匣子”的質疑。def backtest(close, pred_prob, threshold0.5, fee0.0003): # close: 收盤價序列, pred_prob: 模型預測的上漲概率 position 0 # 0 空倉1 持倉 equity 1.0 # 初始資金歸一化 equity_curve [1.0] for i in range(1, len(close)): # 按前一交易日的預測信號決定當前交易日持倉 signal 1 if pred_prob[i-1] threshold else 0 if signal 1 and position 0: position 1 equity * (1 - fee) # 買入扣手續(xù)費 elif signal 0 and position 1: position 0 equity * (1 - fee) # 賣出扣手續(xù)費 equity * (1 (close[i] / close[i-1] - 1) * position) equity_curve.append(equity) equity_curve pd.Series(equity_curve) ret equity_curve.iloc[-1] - 1 annual (equity_curve.iloc[-1]) ** (252 / max(len(close), 1)) - 1 max_drawdown (equity_curve.cummax() - equity_curve).max() sharpe equity_curve.pct_change().mean() / equity_curve.pct_change().std() * (252 ** 0.5) return {總收益率: ret, 年化收益: annual, 最大回撤: max_drawdown, 夏普比率: sharpe}這段回測代碼的邏輯是用前一交易日的預測概率作為當天的持倉信號次日開盤后才真正生效繞過前視偏差。fee0.0003近似單邊萬三手續(xù)費別忽略否則回測收益會整體虛高。輸出的四個指標是答辯必備指標含義合格線參考總收益率測試期內(nèi)資金增長倍數(shù)能跑贏同期基準指數(shù)即可年化收益把收益率折算到一年5% 以上說明模型不是純噪聲最大回撤從峰值跌到谷底的最大幅度越小越好超過 20% 要警惕夏普比率每單位波動換來的超額收益大于 1 說明風險調(diào)整后表現(xiàn)合理回測的時候要單獨保存一份測試集的預測概率不要在整個數(shù)據(jù)集上回測。測試集必須是模型從未見過的尾部數(shù)據(jù)否則回測曲線會包含訓練階段的記憶效應。4.2 預測結果可視化從 matplotlib 到交互式界面課程作業(yè)階段一段簡單清晰的 matplotlib 代碼比花哨的交互界面更實用。把預測概率和真實收益畫在同一張圖上能直觀看出模型在哪些時段失靈。若時間充裕再用 Streamlit 包一層界面讓評委輸入股票代碼后看到最新預測結果。我一般會先畫靜態(tài)圖跑通整個流程后再加交互層。import matplotlib.pyplot as plt def plot_prediction(data, pred_prob, test_start_idx): fig, ax plt.subplots(figsize(14, 5)) ax.plot(data[date], data[close], labelclose, linewidth1) ax2 ax.twinx() ax2.plot(data[date][test_start_idx:], pred_prob, colororange, labelpred prob, alpha0.7) ax.set_title(Stock Prediction) fig.legend() plt.show()先畫價格、再疊加預測概率這種雙軸圖能一眼看出預測概率是否在關鍵轉折點發(fā)生漂移。如果預測概率長期貼在 0.5 附近說明模型對這段行情沒有把握回測里頻繁開平倉反而會增加手續(xù)費。答辯 PPT 里可以放三張圖原始價格曲線、預測概率曲線、凈值曲線評委看完這三張圖基本就能理解整個系統(tǒng)。交互式界面方面Streamlit 是最省事的方案一個腳本就能把回測結果、概率圖、最新信號全放到網(wǎng)頁上。建議把模型權重和 scaler 保存成文件頁面加載時直接讀入避免每次打開都重訓。界面選型上Streamlit 適合演示但如果需要復雜的前端交互還是直接導出 HTML 報告更穩(wěn)省去部署環(huán)境依賴。4.3 模型輸出解釋概率、置信度與漲跌幅我一般會在模型輸出層用 sigmoid 把 logits 轉成概率再按 0.5 閾值判定漲跌。答辯時如果被問“0.6 和 0.51 有什么區(qū)別”回答是 0.6 表示模型置信度更高而不是預測漲幅更大——明確這個邊界能避免被評委挑戰(zhàn)。置信度概念也可以延伸到回測策略里只有當預測概率超過 0.55 才開倉低于 0.45 才平倉。def sigmoid(x): return 1 / (1 float(np.exp(-x))) logits model(X_batch) prob torch.sigmoid(logits).detach().numpy()閾值的選擇可以通過驗證集搜索完成在 0.4 到 0.6 區(qū)間每 0.05 步長試一次選擇 F1 分數(shù)最高的閾值。不要直接用測試集調(diào)閾值這又會引入信息泄露。如果你用的是二分類最終匯報時除了準確率還要報 AUC因為樣本不均衡時準確率會騙人。5. 畢設避坑清單股票預測系統(tǒng)里翻車頻率最高的 5 個問題5.1 未來函數(shù)污染標簽回測曲線過度樂觀現(xiàn)象訓練集和測試集指標都超過 90%回測資金曲線一路向上實盤完全不對。原因在計算特征時用了rolling(window).mean()卻沒有按時平移或者標準化時用了全量數(shù)據(jù)導致第 t 天的特征已經(jīng)包含 t1 甚至更遠的信息。解決所有特征計算后統(tǒng)一做一次shift(1)并嚴格按 2.3 節(jié)把fit_transform和transform分開。在回測中養(yǎng)成“信號當天收盤后生成、次日開盤執(zhí)行”的習慣。我見過最隱蔽的寫法是df[label] (df[future_ret] 0).astype(int)恰好future_ret是用shift(-5)構造的如果后面有人不經(jīng)意地在特征列表里加上了future_ret整個模型就是在預測自己。5.2 停牌和漲跌停導致收益計算錯位現(xiàn)象回測里某天收益突然變成 10% 或 -10%導致回撤曲線出現(xiàn)斷崖。原因A 股有漲跌停和停牌機制某天停牌時close[i]和close[i-1]間隔了好幾個交易日直接計算收益率會把停牌期間的漲跌算到一天里。解決回測前先過濾掉停牌日或把收益率改成close[i] / close[last_trade_day] - 1。最簡單的做法是在數(shù)據(jù)清洗階段刪掉volume 0的行。對漲跌停日還要注意漲停時買不進、跌停時賣不出回測里不應該允許這些交易發(fā)生。5.3 歸一化信息泄露現(xiàn)象驗證集指標高于訓練集怎么看都不合理。原因對全量數(shù)據(jù)先統(tǒng)一標準化再切分訓練集測試集驗證集和測試集的均值方差混入了訓練集統(tǒng)計量。解決必須分步執(zhí)行scaler.fit(X_train)和scaler.transform(X_test)。推薦把擬合好的 scaler 也用 joblib 存成文件在推理階段和模型權重一起加載這樣線上預測和線下回測使用同一套預處理參數(shù)不會出現(xiàn)“回測挺好部署到新數(shù)據(jù)上結果對不上”的問題。5.4 樣本不均衡模型只會“看跌”現(xiàn)象訓練集跌的樣本占 80%模型干脆全都預測跌準確率還挺高。原因標簽按未來 5 日收益構造震蕩市里未來 5 日下跌的概率天然偏高且分類閾值 0.5 對概率型模型不公平。解決記錄訓練集正負樣本比例若失衡超過 1.5 倍用class_weight或對少數(shù)類過采樣。更實用的是把評估指標從 accuracy 換成 F1 或 AUC。邏輯回歸里直接加class_weightbalancedPyTorch 里在BCEWithLogitsLoss中傳入pos_weight這個參數(shù)能顯著改善少數(shù)類的召回率。5.5 隨機種子不固定兩次結果對不上現(xiàn)象完全相同的代碼跑兩次準確率不一樣答辯回放翻車。原因PyTorch 初始化、DataLoader 打亂順序都有隨機性。解決在訓練腳本開頭固定所有隨機源。def set_seed(seed42): import random random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)固定隨機種子后還要把 DataLoader 的shuffleTrue放在torch.Generator上設置同樣的種子否則第一個 epoch 的數(shù)據(jù)順序仍然隨機。最后把訓練完的模型權重、參數(shù)、回測結果打包成一個固定版本目錄答辯時直接從目錄里讀取結果而不是現(xiàn)場重新訓練這是最穩(wěn)妥的演示方式。6. 提升模型說服力的三個進階做法調(diào)參、集成與演示課程作業(yè)能跑到這一步已經(jīng)比大部分同題作品完整。但想拿高分有三個方向的改進性價比最高。第一是超參數(shù)調(diào)優(yōu)把 window、hidden_size、學習率、dropout 四個參數(shù)放進網(wǎng)格搜索里跑一遍每組參數(shù)只訓練 10 個 epoch用驗證集 AUC 排序。每組訓練前要固定隨機種子否則參數(shù)對比會被隨機噪聲淹沒。網(wǎng)格搜索產(chǎn)出的成績單可以直接作為答辯附錄證明你理解模型對超參數(shù)敏感的特性而不只是會調(diào)庫。第二用心做模型集成。LSTM、XGBoost、邏輯回歸三個模型各出一個概率然后取加權平均作為最終信號。XGBoost 在表格特征上的表現(xiàn)通常不錯和 LSTM 的結構差異也夠大兩個模型同時犯錯的概率比較低。權重不要手動調(diào)太極端我一般固定 0.4、0.4、0.2再根據(jù)驗證集微調(diào)。集成模型單獨跑一套回測和單一模型對比用這個對比圖當 PPT 的高潮直觀證明“組合優(yōu)于單模型”。第三是演示形態(tài)。答辯時與其現(xiàn)場訓練不如把最佳權重和 scaler 序列化保存寫一個幾行的推理腳本讓評委輸入股票代碼后直接看到當前信號。同時把回測曲線和最大回撤標注畫在同一張圖上并準備一個“如果當天買入跌了怎么辦”的防守話術模型給出的是概率而非常勝預測閾值 0.55 以上的信號才執(zhí)行交易實際回測中信號占比約三成這是模型對不確定性的誠實反饋。這個邊界能讓評委認可你而不是覺得你在吹噓。經(jīng)驗上固定隨機種子加多模型投票是我最后反復驗證的穩(wěn)妥組合最終成績單和回放結果完全一致不會在演示現(xiàn)場玩心跳。為保險起見我也會把閾值從 0.5 調(diào)整到 0.55 再跑一遍防止決策邊界過于敏感。這條路走到這里已經(jīng)能從數(shù)據(jù)采集到模型訓練、回測、答辯展示一條線全部打通。希望幫到你。本文還有配套的精品資源點擊獲取