絡(luò)攻擊檢測(cè)實(shí)戰(zhàn):從特征工程到隨機(jī)森林與Web部署)
簡(jiǎn)介面向數(shù)據(jù)挖掘與網(wǎng)絡(luò)安全相關(guān)的課程設(shè)計(jì)與期末大作業(yè)場(chǎng)景這份基于KDD-CUP99數(shù)據(jù)集的網(wǎng)絡(luò)攻擊檢測(cè)項(xiàng)目提供了完整的Python實(shí)現(xiàn)和配套資料。項(xiàng)目源碼經(jīng)過(guò)本地編譯驗(yàn)證難度適中內(nèi)容由助教老師審定可直接用于學(xué)習(xí)與驗(yàn)收展示。壓縮包體積約6.93MB共75個(gè)文件核心包括14個(gè)Python腳本與10個(gè)pyc編譯文件同時(shí)含有7個(gè)Vue前端組件、4個(gè)CSS樣式、4個(gè)JSON配置、HTML入口頁(yè)面、Markdown說(shuō)明文檔以及SQLite數(shù)據(jù)庫(kù)等覆蓋數(shù)據(jù)預(yù)處理、特征提取、模型訓(xùn)練與可視化展示的完整鏈路。前后端分離的目錄結(jié)構(gòu)清晰便于按模塊理解入侵檢測(cè)流程。已有349人瀏覽學(xué)習(xí)適合需要快速搭建網(wǎng)絡(luò)攻擊檢測(cè)系統(tǒng)原型、完成期末匯報(bào)或入門(mén)機(jī)器學(xué)習(xí)安全應(yīng)用的開(kāi)發(fā)者參考。1. KDD-CUP99 網(wǎng)絡(luò)攻擊檢測(cè)一門(mén)課設(shè)如何把 IDS 從理論拉到工程大四做網(wǎng)絡(luò)攻擊檢測(cè)的項(xiàng)目老師遞過(guò)來(lái)一份基于 KDD-CUP99 數(shù)據(jù)集的源碼包。當(dāng)時(shí)我第一反應(yīng)是這東西太老——1999 年 DARPA 采集的 9 周網(wǎng)絡(luò)流量和今天動(dòng)輒百 G 的骨干網(wǎng)不在同一個(gè)量級(jí)??烧姘阉鹜晡野l(fā)現(xiàn)這門(mén)課設(shè)考的不是算法多新而是三件事懂特征、會(huì)評(píng)估、能把模型包成系統(tǒng)。每個(gè)連接 41 維特征、四類(lèi)攻擊DoS、Probe、R2L、U2R加一條正常標(biāo)簽這個(gè)分類(lèi)框架二十多年后仍然是 IDS 行業(yè)的標(biāo)準(zhǔn)姿勢(shì)。適合期末大作業(yè)、畢設(shè)起步、以及想弄清入侵檢測(cè)數(shù)據(jù)鏈路怎么走的人。更難得的是這份資料把數(shù)據(jù)預(yù)處理、模型訓(xùn)練、Django 后端和 Vue 前端串成了一條完整鏈路不是那種只丟給你一個(gè) notebook 的半成品。2. 數(shù)據(jù)先行的第一關(guān)讀懂 41 維特征再談分類(lèi)怎么落地2.1 數(shù)據(jù)集結(jié)構(gòu)KDD-CUP99 的四類(lèi)攻擊到底在檢測(cè)什么KDD-CUP99 每條記錄對(duì)應(yīng)一次 TCP 連接是一個(gè) 41 維特征加一條標(biāo)簽的 tabular 數(shù)據(jù)。41 維內(nèi)部可以分成四組第一組是基礎(chǔ)特征duration、protocol_type、service、flag、src_bytes、dst_bytes 等 9 個(gè)描述一條連接的時(shí)長(zhǎng)、協(xié)議、上下行字節(jié)數(shù)第二組是內(nèi)容特征hot、failed_logins、num_compromised、root_shell 等 13 個(gè)主要設(shè)計(jì)用來(lái)捕捉 U2R 和 R2L 攻擊在會(huì)話里留下的痕跡第三組是基于時(shí)間的流量特征以 2 秒窗口統(tǒng)計(jì)相同目標(biāo)主機(jī)的連接次數(shù)、錯(cuò)誤比例第四組是基于主機(jī)的流量特征窗口擴(kuò)大成 100 條連接。四組特征測(cè)的是不同粒度的行為畫(huà)像。你如果只用前 9 個(gè)特征拿到 40% 準(zhǔn)確率都屬于正?!魴z驗(yàn)大量分布在流量統(tǒng)計(jì)特征上。標(biāo)簽側(cè)項(xiàng)目把攻擊歸成四類(lèi)加正常共五類(lèi)。DoS 是最常見(jiàn)的拒絕服務(wù)樣本量最大Probe 是端口掃描與漏洞探測(cè)屬于偵查行為R2L 是遠(yuǎn)程未授權(quán)訪問(wèn)U2R 是本地提權(quán)這兩類(lèi)樣本占比極小但恰恰是一般分類(lèi)器最容易漏掉的部分。做這項(xiàng)網(wǎng)絡(luò)攻擊檢測(cè)我建議先把標(biāo)簽分布打印出來(lái)再往下走這一步?jīng)Q定你后面要不要做重采樣也決定答辯時(shí)你能不能講清楚為什么某些攻擊類(lèi)型檢測(cè)不出來(lái)。2.2 預(yù)處理代碼字符串編碼、數(shù)值標(biāo)準(zhǔn)化與標(biāo)簽映射把 CSV 讀成矩陣并不難麻煩的是類(lèi)別特征。protocol_typetcp/udp/icmp、servicehttp/ftp/…幾十種取值、flagSF/REJ/S0/…都是字符串?dāng)?shù)值列里 src_bytes 能從 0 到上億不標(biāo)準(zhǔn)化會(huì)讓樹(shù)模型之外的算法直接崩。下面是資源的預(yù)處理核心邏輯我用 sklearn Pipeline 重構(gòu)過(guò)一版import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import OneHotEncoder, StandardScaler, LabelEncoder from sklearn.compose import ColumnTransformer # 假設(shè) train_df 是讀入的原始 DataFrame列包含 41 個(gè)特征 label num_cols [duration, src_bytes, dst_bytes, wrong_fragment, urgent, hot, num_failed_logins, num_compromised, root_shell, su_attempted, num_root, num_file_creations, num_shells, num_access_files, num_outbound_cmds, count, srv_count, serror_rate, srv_serror_rate, rerror_rate, srv_rerror_rate, same_srv_rate, diff_srv_rate, srv_diff_host_rate, dst_host_count, dst_host_srv_count, dst_host_same_srv_rate, dst_host_diff_srv_rate, dst_host_same_src_port_rate, dst_host_srv_diff_host_rate, dst_host_serror_rate, dst_host_srv_serror_rate, dst_host_rerror_rate, dst_host_srv_rerror_rate] cat_cols [protocol_type, service, flag] X train_df.drop(label, axis1) y LabelEncoder().fit_transform(train_df[label]) preprocessor ColumnTransformer([ (num, StandardScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols) ]) X_processed preprocessor.fit_transform(X) # 嚴(yán)格流程先 fit_transform 訓(xùn)練集保存 preprocessor # 之后對(duì)測(cè)試集只調(diào)用 preprocessor.transform(X_test)這段代碼里最容易踩的坑是 pipeline 的 fit 邊界。ColumnTransformer 里 StandardScaler 和 OneHotEncoder 必須只在訓(xùn)練集上 fit之后測(cè)試集復(fù)用同一個(gè) preprocessor。如果你把全量數(shù)據(jù)丟進(jìn)去然后一次性劃分均值、方差和類(lèi)別字典都包含了未來(lái)信息交叉驗(yàn)證分?jǐn)?shù)會(huì)虛高換到真實(shí)流量上立刻現(xiàn)形。handle_unknownignore 是為了測(cè)試集里出現(xiàn)訓(xùn)練集沒(méi)見(jiàn)過(guò)的 service 值時(shí)能安全跳過(guò)編碼矩陣?yán)镞@一列會(huì)全部為 0。標(biāo)簽映射上原始文件里是字符串normal、neptune、satan…LabelEncoder 會(huì)把它們壓成 0-4 的整數(shù)配合后面可視化時(shí)你需要在代碼里維護(hù)一張真實(shí)名字的對(duì)照表不要靠數(shù)字猜攻擊類(lèi)型。我做網(wǎng)絡(luò)攻擊檢測(cè)時(shí)習(xí)慣把 normal 定為 0后面分別對(duì)應(yīng) DoS、Probe、R2L、U2R。源碼里自帶的清洗腳本已經(jīng)處理過(guò)行尾點(diǎn)號(hào)這些臟數(shù)據(jù)我復(fù)現(xiàn)時(shí)基本只改了下文件路徑。2.3 看一眼類(lèi)別分布再動(dòng)手不平衡比模型更重要訓(xùn)練集包含了幾十萬(wàn)條記錄但分布極其傾斜DoS 占了大頭U2R 只有幾十到幾百條。這意味著即使什么都不學(xué)把所有樣本預(yù)測(cè)成 DoS整體準(zhǔn)確率也能到 60% 以上。所以預(yù)處理階段我會(huì)額外做一件事——分層抽樣stratify把各類(lèi)比例在訓(xùn)練集/驗(yàn)證集里保持一致X_train, X_val, y_train, y_val train_test_split( X_processed, y, test_size0.2, stratifyy, random_state42 )stratify 參數(shù)在樣本不均衡時(shí)屬于必選否則運(yùn)氣差一點(diǎn)驗(yàn)證集里可能一條 U2R 都沒(méi)有模型報(bào)告直接失去參考意義。分完層之后我還會(huì)用 value_counts() 核對(duì)每類(lèi)樣本數(shù)U2R 如果少于 10 條說(shuō)明這份數(shù)據(jù)切得太狠考慮換成 k 折分層交叉驗(yàn)證或者把 10% 訓(xùn)練集和 corrected 測(cè)試集合并起來(lái)重新劃分。重采樣方案先放后面先用類(lèi)權(quán)重class_weightbalanced跑通基線U2R/R2L 實(shí)在拉不起來(lái)再上 SMOTE別一上來(lái)就生成合成樣本不然邊界容易更亂。3. 模型主線隨機(jī)森林跑五分類(lèi)準(zhǔn)確率不是唯一指標(biāo)3.1 為什么選樹(shù)模型而不是一上來(lái)就上神經(jīng)網(wǎng)絡(luò)KDD-CUP99 這類(lèi)表格型數(shù)據(jù)里樹(shù)模型有天然優(yōu)勢(shì)對(duì)特征尺度和單調(diào)變換不敏感特征交互靠規(guī)則分裂就能表達(dá)訓(xùn)練速度快到可以在筆記本上反復(fù)試參數(shù)。相比之下深度學(xué)習(xí)最出效果的是高維稀疏或序列數(shù)據(jù)在這里往往需要大量調(diào)參才能追平隨機(jī)森林的基線。資源源碼里用的方案正是 sklearn 的 RandomForestClassifier配合少量特征篩選效果足夠支撐一場(chǎng)答辯。我在復(fù)現(xiàn)時(shí)保留了數(shù)據(jù)集原始特征組合沒(méi)有做 PCA。原因是 PCA 會(huì)把單個(gè)特征的可解釋性抹掉后面答辯講「為什么 count 和 srv_count 組合能識(shí)別端口掃描」時(shí)根本說(shuō)不出人話。模型的 joblib 文件從源碼里直接導(dǎo)出來(lái)后是十幾 MB 級(jí)別Django 后端加載完全可行這也是我當(dāng)時(shí)沒(méi)用 XGBoost 的一個(gè)考量。如果你后面想換 LightGBM數(shù)據(jù)鏈路不用動(dòng)只換訓(xùn)練腳本和模型文件即可這也是這套工程結(jié)構(gòu)比較舒服的地方。3.2 訓(xùn)練主代碼參數(shù)邊界與 OOB 驗(yàn)證隨機(jī)森林有兩個(gè)參數(shù)最容易糾結(jié)n_estimators 和 max_depth。n_estimators 太少會(huì)抖太多訓(xùn)練慢且收益遞減max_depth 限制過(guò)深會(huì)過(guò)擬合但樹(shù)模型在 41 維特征上即使不限制也通常不會(huì)太離譜。我在源碼基礎(chǔ)上跑過(guò)的配置如下from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix import joblib model RandomForestClassifier( n_estimators200, # 我習(xí)慣先固定 200看 OOB 是否收斂 max_depth20, # 限制深度防止單棵樹(shù)記住噪聲 min_samples_split5, # 節(jié)點(diǎn)再劃分所需最少樣本數(shù)控制分裂風(fēng)險(xiǎn) class_weightbalanced, # 緩解 U2R/R2L 的傾斜 n_jobs-1, # 全核并行 random_state42, oob_scoreTrue # 用袋外樣本評(píng)估泛化不用額外切驗(yàn)證集 ) model.fit(X_train, y_train) print(OOB score:, model.oob_score_) joblib.dump(model, ids_model.joblib) joblib.dump(preprocessor, preprocessor.joblib)這里的 oob_scoreTrue 是白撿的泛化參考每棵樹(shù)只用約三分之二樣本訓(xùn)練剩下三分之一袋外樣本做預(yù)測(cè)對(duì)隨機(jī)森林來(lái)說(shuō)等價(jià)于內(nèi)置了一次交叉驗(yàn)證。我一般拿它和驗(yàn)證集分?jǐn)?shù)對(duì)照兩者差超過(guò)兩個(gè)百分點(diǎn)就說(shuō)明有泄漏或過(guò)擬合。class_weightbalanced 會(huì)按類(lèi)別頻率反比放大少數(shù)類(lèi)權(quán)重代價(jià)是 DoS 的精確率可能輕微下降但從「攻擊檢測(cè)」的目標(biāo)看值得。model 和 preprocessor 分開(kāi)保存有個(gè)好處接口層換模型不影響預(yù)處理改模型不改代碼。3.3 評(píng)估指標(biāo)該怎么看混淆矩陣?yán)锊刂鴨?wèn)題一個(gè)典型翻車(chē)現(xiàn)場(chǎng)是整體準(zhǔn)確率 99%但 U2R 的 recall 是 0。原因很簡(jiǎn)單U2R 樣本太少分類(lèi)器把所有樣本都判成 Normal 或 DoS全局 accuracy 幾乎不受影響。應(yīng)對(duì)方案是打印 per-class 報(bào)告再談優(yōu)化y_pred model.predict(X_val) report classification_report( y_val, y_pred, target_names[Normal, DoS, Probe, R2L, U2R], digits3 ) print(report) print(confusion_matrix(y_val, y_pred))對(duì)于本項(xiàng)目的四類(lèi)攻擊場(chǎng)景重點(diǎn)看兩個(gè)數(shù)R2L 和 U2R 的 recall。只要這兩個(gè)不過(guò)半系統(tǒng)在真實(shí)場(chǎng)景就約等于沒(méi)裝。Probe 和 DoS 通常不難難的是少數(shù)類(lèi)。如果 val 集上 U2R recall 實(shí)在拉不起來(lái)先確認(rèn)是不是分層抽樣抽到了極少數(shù)樣本再考慮在 pipeline 里給少數(shù)類(lèi)擴(kuò)樣。模型訓(xùn)練完我會(huì)順手把當(dāng)時(shí)的參數(shù)和 per-class 指標(biāo)寫(xiě)進(jìn)一個(gè) JSON 文件跟模型放同目錄以后換數(shù)據(jù)集再跑至少知道這個(gè)基線是怎么來(lái)的這個(gè)習(xí)慣幫我避免了好幾次「這個(gè)分?jǐn)?shù)怎么復(fù)現(xiàn)不出來(lái)」的尷尬。4. Django Vue 雙端聯(lián)動(dòng)把模型包成能演示的檢測(cè)系統(tǒng)4.1 Django 端模型加載與預(yù)測(cè)接口設(shè)計(jì)資源工程里 Django 目錄承擔(dān)后端服務(wù)manage.py 在根目錄配置里注冊(cè)了數(shù)據(jù)庫(kù)sqlite3static 和 templates 負(fù)責(zé)傳統(tǒng)服務(wù)端渲染頁(yè)面。既然前端用 Vue 寫(xiě)了 net-analyze 這個(gè)工程實(shí)際聯(lián)調(diào)時(shí)我會(huì)讓 Django 只暴露 API頁(yè)面交給 Vue 去拉。預(yù)測(cè)接口的核心是避免重復(fù)加載模型——sklearn 的 joblib.load 在機(jī)械硬盤(pán)上動(dòng)不動(dòng)幾百毫秒放進(jìn) view 函數(shù)里每次請(qǐng)求都執(zhí)行接口延遲會(huì)高到難以接受。項(xiàng)目源碼里我看到的是把模型加載放在模塊層這個(gè)習(xí)慣建議保留# views.py import json import joblib import numpy as np from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt _model None _pre None def _load_model(): global _model, _pre if _model is None: _model joblib.load(ids_model.joblib) _pre joblib.load(preprocessor.joblib) csrf_exempt def predict_api(request): if request.method ! POST: return JsonResponse({error: need POST}, status405) data json.loads(request.body) features data.get(features) if len(features) ! 41: return JsonResponse({error: feature length must be 41}, status422) _load_model() x _pre.transform([features]) y _model.predict(x)[0] proba _model.predict_proba(x)[0].tolist() return JsonResponse({ prediction: int(y), probabilities: proba })注意這里我把 _load_model() 的調(diào)用放在拿到請(qǐng)求并校驗(yàn)完字段之后才執(zhí)行是懶加載寫(xiě)法。第一次請(qǐng)求慢一點(diǎn)后續(xù)請(qǐng)求全部復(fù)用內(nèi)存中的模型對(duì)象。URL 配置很簡(jiǎn)單path(api/predict/, views.predict_api)。校驗(yàn) features 長(zhǎng)度是一個(gè)低成本高收益的約定前端傳錯(cuò)維度時(shí)接口直接返回 422而不是讓模型去拋一個(gè)讓人看不懂的維度錯(cuò)誤。另外sqlite3 數(shù)據(jù)庫(kù)我用來(lái)記錄每次預(yù)測(cè)的日志包括時(shí)間、原始特征、預(yù)測(cè)結(jié)果后面畫(huà)趨勢(shì)圖不用重新跑模型。4.2 Vue 端儀表盤(pán)、攻擊分布圖與跨域聯(lián)調(diào)net-analyze 的 Vue 工程包含 babel.config.js、vue.config.js、jsconfig.json 和 package.json是標(biāo)準(zhǔn) Vue CLI 結(jié)構(gòu)。前端頁(yè)面要做的事有三件上傳或粘貼連接特征、調(diào)用 Django 接口拿預(yù)測(cè)結(jié)果、把結(jié)果渲染成攻擊分布圖。核心調(diào)用代碼并不復(fù)雜// src/api/detect.js import axios from axios export function detectAttack(features) { return axios.post(/api/predict/, { features }) .then(res res.data) .catch(err { console.error(predict failed:, err.message) throw err }) }這里的關(guān)鍵是開(kāi)發(fā)環(huán)境不能直接寫(xiě)死http://127.0.0.1:8000否則會(huì)撞上跨域。項(xiàng)目在 vue.config.js 里配了 devServer 代理把 /api 前綴轉(zhuǎn)發(fā)給 Django// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } }配好 proxy 之后前端 axios 請(qǐng)求 /api/predict/ 時(shí)瀏覽器看到的請(qǐng)求是同源的由開(kāi)發(fā)服務(wù)器轉(zhuǎn)發(fā)給后端跨域被化解在 dev server 這一層。jsconfig.json 里通常還有 開(kāi)頭的路徑別名import 時(shí)可以寫(xiě)/api/detect而不是一串相對(duì)路徑。babel.config.js 保持默認(rèn)即可如果你在項(xiàng)目里用了比較新的 JavaScript 語(yǔ)法preset-env 會(huì)自動(dòng)做降級(jí)處理。4.3 前后端數(shù)據(jù)的字段約定與圖表渲染圖表部分我建議前端自己做聚合后端不要塞一堆 label 映射給前端。后端只返回整數(shù)預(yù)測(cè)結(jié)果和概率數(shù)組前端維護(hù)一張攻擊類(lèi)型對(duì)照表。用 ECharts 的話餅圖和柱狀圖可以直接消費(fèi) probabilities 數(shù)組。做網(wǎng)絡(luò)攻擊檢測(cè)演示時(shí)把 DoS 和 Probe 的概率畫(huà)成懸浮條是答辯時(shí)的加分項(xiàng)——評(píng)審一眼就能看出系統(tǒng)不是黑匣子。這套字段約定里最容易出問(wèn)題的是「前端和后端各維護(hù)一份攻擊類(lèi)型映射表」時(shí)間一長(zhǎng)兩邊必然對(duì)不上。我習(xí)慣以 Django 端的一個(gè)常量類(lèi)為唯一事實(shí)源前端從接口拿類(lèi)型名稱(chēng)而不是自己寫(xiě)死。整套源碼包從預(yù)處理腳本到 Vue 頁(yè)面是一次完整閉環(huán)換掉模型文件就能復(fù)用到別的數(shù)據(jù)集上這也是我愿意把它完整跑一遍的原因。5. 避坑實(shí)錄從數(shù)據(jù)泄漏到跨域預(yù)檢五個(gè)翻車(chē)點(diǎn)逐一拆解5.1 one-hot 編碼導(dǎo)致特征數(shù)量對(duì)不上現(xiàn)象訓(xùn)練時(shí)準(zhǔn)確率正常模型保存后新數(shù)據(jù)進(jìn)來(lái)transform 報(bào)出特征數(shù)量不符或者預(yù)測(cè)結(jié)果全亂。原因預(yù)處理用 pd.get_dummies 直接對(duì)全量數(shù)據(jù)編碼如果新數(shù)據(jù)里某個(gè) service 值沒(méi)有出現(xiàn)在訓(xùn)練集那么新數(shù)據(jù)編碼后的列數(shù)和訓(xùn)練時(shí)不一致模型無(wú)法對(duì)齊特征矩陣。解決統(tǒng)一走 sklearn 的 OneHotEncoder在 preprocessor 里 fit 訓(xùn)練數(shù)據(jù)之后所有新數(shù)據(jù)都用同一個(gè) transform。參數(shù) handle_unknownignore 會(huì)跳過(guò)新類(lèi)別而不是報(bào)錯(cuò)代價(jià)是這一列的編碼全部為 0但至少接口不會(huì)崩。5.2 StandardScaler 在全量數(shù)據(jù)上 fit造成信息泄漏現(xiàn)象交叉驗(yàn)證分?jǐn)?shù) 99%但把模型放到 corrected 測(cè)試集或新流量上檢測(cè)率掉到 80% 以下。原因StandardScaler 的均值和方差在全量數(shù)據(jù)上計(jì)算測(cè)試數(shù)據(jù)的信息早已進(jìn)入訓(xùn)練流程。這在課設(shè)里最隱蔽因?yàn)榉謹(jǐn)?shù)很好看很難察覺(jué)。解決把預(yù)處理器和模型綁在同一個(gè) Pipeline 里對(duì)訓(xùn)練集 fit對(duì)驗(yàn)證集/測(cè)試集只 transform。我復(fù)現(xiàn)時(shí)把 preprocessor 和 model 分開(kāi)保存只是為了接口方便但 fit 邊界必須嚴(yán)格這是一個(gè)原則問(wèn)題。5.3 整體準(zhǔn)確率 99%查 R2L/U2R 的召回率卻是 0現(xiàn)象classification_report 里前三行都漂亮后兩行全是 0看起來(lái)像模型壞了。原因類(lèi)別嚴(yán)重不平衡max_depth 又設(shè)得比較淺樹(shù)的分裂完全被 Normal 和 DoS 主導(dǎo)少數(shù)類(lèi)樣本一片葉子都分不到。單獨(dú)優(yōu)化整體 accuracy 會(huì)掩蓋這個(gè)問(wèn)題。解決先用 class_weightbalanced 跑一遍看 U2R/R2L 的 recall 有沒(méi)有變化不行再對(duì)訓(xùn)練集做 SMOTE 過(guò)采樣。注意 SMOTE 必須在 train_test_split 之后做只對(duì)訓(xùn)練集生成合成樣本否則又是一輪泄漏。如果項(xiàng)目方只要整體準(zhǔn)確率好看那另說(shuō)但做入侵檢測(cè)的人應(yīng)該知道這個(gè)坑。5.4 Django 接口每次請(qǐng)求都重新加載模型響應(yīng)要 3 秒現(xiàn)象第一次請(qǐng)求慢還能忍之后每次都慢看日志發(fā)現(xiàn) joblib.load 被反復(fù)執(zhí)行。原因模型加載寫(xiě)在 view 函數(shù)體內(nèi)Django 每個(gè)請(qǐng)求都會(huì)重新走一遍機(jī)械硬盤(pán)上加載一個(gè)十幾 MB 的模型當(dāng)然慢。解決把模型加載提升到模塊頂層或者用 lru_cache 包裹加載函數(shù)進(jìn)程啟動(dòng)時(shí)加載一次之后只讀內(nèi)存。另外如果開(kāi)著 debug 模式代碼變更會(huì)自動(dòng)重啟進(jìn)程也會(huì)讓人覺(jué)得模型加載慢部署時(shí)記得關(guān)掉 autoreload。5.5 Vue 聯(lián)調(diào)報(bào)跨域OPTIONS 預(yù)檢請(qǐng)求失敗現(xiàn)象前端 axios 請(qǐng)求 Django 接口控制臺(tái)報(bào) CORS errorNetwork 面板里有一個(gè)失敗的 OPTIONS 請(qǐng)求。原因Django 默認(rèn)不允許跨域前端直連 127.0.0.1:8000 時(shí)瀏覽器先發(fā)預(yù)檢請(qǐng)求被后端拒絕。有人會(huì)在 settings.py 里悶頭加 django-cors-headers其實(shí)開(kāi)發(fā)期更穩(wěn)妥的是用 vue.config.js 的 proxy。解決開(kāi)發(fā)環(huán)境配 devServer.proxy把 /api 轉(zhuǎn)發(fā)到后端如果一定要跨域直連再裝 django-cors-headers在 CORS_ALLOWED_ORIGINS 里明確寫(xiě) http://localhost:8080。寫(xiě) * 在課設(shè)演示里能跑通但答辯被問(wèn)安全策略時(shí)很難圓。6. 進(jìn)階把單次檢測(cè)變成一張能交代的攻擊態(tài)勢(shì)圖6.1 做一個(gè)輕量級(jí)的檢測(cè)回放腳本單次 POST 只能驗(yàn)證一條記錄但答辯和實(shí)際使用需要看到整體效果。我會(huì)額外寫(xiě)一個(gè)回放腳本讀取測(cè)試集的一條條記錄把預(yù)測(cè)結(jié)果按攻擊類(lèi)型和時(shí)間窗口聚合生成 JSON 給前端畫(huà)趨勢(shì)圖# replay.py import json, joblib import pandas as pd from collections import Counter model joblib.load(ids_model.joblib) pre joblib.load(preprocessor.joblib) df pd.read_csv(corrected.csv) result [] for _, row in df.head(5000).iterrows(): x pre.transform([row[:41].tolist()])[0] result.append(int(model.predict([x])[0])) # 按窗口聚合出攻擊類(lèi)型占比 print(Counter(result))這個(gè)腳本的價(jià)值不在效果而在于把「檢測(cè)系統(tǒng)」從單接口演示延伸成批量回放工具之后想畫(huà)準(zhǔn)確率曲線、誤報(bào)隨時(shí)間變化都從這個(gè)結(jié)果文件出發(fā)。6.2 閾值自整定讓概率輸出可調(diào)同樣一條記錄把判斷閾值從 0.7 調(diào)到 0.3結(jié)果會(huì)完全不同。接口里我還保留了一個(gè) threshold 參數(shù)前端滑塊改閾值后端只把概率數(shù)組返回由前端決定是否判定為攻擊。這是模型 demo 里最容易被忽略的一個(gè)交互點(diǎn)加了它演示時(shí)就能現(xiàn)場(chǎng)展示「閾值調(diào)低 → 檢出率上升、誤報(bào)也上升」的關(guān)系比空口講概念有用得多。從那以后我每次做這類(lèi)課設(shè)都會(huì)強(qiáng)制自己走一遍「先看標(biāo)簽分布 → 再定評(píng)估口徑 → 最后寫(xiě)接口」的順序。數(shù)據(jù)沒(méi)看清楚之前模型調(diào)得再花哨都是白搭。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取