简介:本资源是一套基于Python实现的机器学习网络入侵检测系统源码及配套文档,专为人工智能、通信工程、电子信息等专业本科生课程设计、毕业设计与实训项目打造,聚焦网络安全实战场景下的异常流量识别与模型部署能力训练。压缩包共22个文件,含7个核心Python脚本(如Sniffer.py、SVM.py、metrics.py)、9个XML配置与数据定义文件、2个.md和.pt模型文件,以及.idea工程配置与.gitignore等开发支持文件,整体12.99MB,结构完整、开箱即用。已有75人下载学习,适合具备基础Python与机器学习知识的学习者快速复现端到端流程——从网络数据包捕获、特征工程处理、多算法(SVM等)建模评估,到模型权重加载与检测结果可视化。项目附带详细说明文档与模块化代码组织,便于理解各组件职责并开展二次开发与性能调优。
1. 为什么用机器学习做网络入侵检测,不是“加个模型就完事”:毕业设计能跑通、能讲清、能答辩的最小闭环在这里
很多同学拿到“机器学习网络入侵检测系统”这个毕设题目时,第一反应是去 GitHub 搜关键词,下个带intrusion-detection和ML的仓库,改改config.py就交差——结果答辩被问“你用的特征工程逻辑是什么?”“为什么选随机森林而不是 XGBoost?”“测试集里混入了未见过的攻击类型,你的模型还可靠吗?”当场卡壳。这不是模型不行,是整个技术链路没闭环:数据怎么来、特征怎么提、模型怎么训、结果怎么验、边界怎么划,缺一不可。本项目(机器学习网络入侵检测系统Python源码+项目说明-满分毕业设计.zip)不是玩具 demo,它是一套可复现、可解释、可答辩的完整流程:从真实流量抓包(CICIDS2017 数据集)、到协议层特征提取(TCP/UDP/ICMP 流统计 + 时间窗口聚合)、再到五类攻击(DoS、PortScan、Botnet、WebAttack、Infiltration)的多分类建模,最后输出带置信度的实时告警日志。适合计算机/网络安全方向本科生,要求 Python 基础(会 pip install、写函数、读 CSV)、了解监督学习基本概念(不需要推公式),目标不是发论文,而是让老师点头说:“这个工作量和逻辑链,确实够得上‘优秀’。”
2. 从原始 PCAP 到结构化特征表:为什么不能直接喂 raw packet 给 sklearn?
2.1 网络流量数据的“三难”困境:高维、稀疏、非结构化
原始网络包(PCAP 文件)本质是二进制字节流,包含以太网帧头、IP 头、传输层头(TCP/UDP)、应用层载荷。直接把每个包转成向量喂给sklearn.ensemble.RandomForestClassifier?会翻车。原因有三:
- 维度爆炸:一个 HTTP GET 包可能含 1500 字节,若按字节 one-hot 编码 → 256 维 × 1500 = 384,000 维,内存撑爆;
- 语义丢失:字节序列不反映“连接持续时间”“重传次数”“SYN-FIN 比例”等安全关键指标;
- 时序割裂:单个包无法判断是否属于某次端口扫描(需连续多个 SYN 包 + 目标 IP 分散性)。
所以必须做流级抽象(Flow-based Feature Extraction):把同一五元组(src_ip, dst_ip, src_port, dst_port, protocol)在固定时间窗口(如 2 秒)内的所有包聚合成一条记录,计算统计量。这是 CICIDS2017 数据集的标准做法,也是工业界 IDS 的通用前置。
2.2 用 CICFlowMeter 提取特征:比手写解析器快 10 倍,且结果可复现
别自己写scapy解包循环——效率低、易出错、特征定义不统一。CICFlowMeter 是加拿大网络安全实验室开源的专用工具,支持 Windows/Linux/macOS,能直接从 PCAP 输出 CSV 特征表(共 80+ 列,含Flow Duration,Total Fwd Packets,Fwd Packet Length Mean,Packet Length Std,Fwd IAT Mean,Fwd Header Size Min等)。我们用它处理CICIDS2017/Monday-WorkingHours.pcap(正常流量)和Friday-WorkingHours-Morning-WebAttacks.pcap(Web 攻击):
# 下载 CICFlowMeter v4.0(注意:必须用 v4.0,v3.x 不兼容 CICIDS2017 标签) wget https://github.com/ISCX/CICFlowMeter/releases/download/v4.0/CICFlowMeter-v4.0.zip unzip CICFlowMeter-v4.0.zip cd CICFlowMeter-v4.0/bin/ # 提取正常流量特征(输出到 ./output/normal/) ./CICFlowMeter.sh /path/to/Monday-WorkingHours.pcap ./output/normal/ 1 # 提取 Web 攻击特征(输出到 ./output/webattack/) ./CICFlowMeter.sh /path/to/Friday-WorkingHours-Morning-WebAttacks.pcap ./output/webattack/ 1提示:
1表示启用标签自动识别(CICFlowMeter 内置规则匹配已知攻击模式),但仅作初筛。最终标签必须人工校验——因为它的 Botnet 检测漏报率高达 23%(见 CICIDS2017 官方报告 Table 7),我们后续要用pandas合并并清洗。
2.3 合并与清洗:把 15 个子文件拼成一张大表,删掉“幽灵特征”
CICFlowMeter 对每个 PCAP 生成多个 CSV(按分钟切分),需合并并去重:
import pandas as pd import glob import os # 合并所有 normal 子文件 normal_files = glob.glob("./output/normal/*.csv") normal_df = pd.concat([pd.read_csv(f) for f in normal_files], ignore_index=True) # 合并所有 webattack 子文件 web_files = glob.glob("./output/webattack/*.csv") web_df = pd.concat([pd.read_csv(f) for f in web_files], ignore_index=True) # 关键清洗:删除含 NaN 或 Inf 的行(CICFlowMeter 有时计算失败) normal_df = normal_df.replace([np.inf, -np.inf], np.nan).dropna() web_df = web_df.replace([np.inf, -np.inf], np.nan).dropna() # 删除重复 flow(相同五元组+时间戳) normal_df = normal_df.drop_duplicates(subset=['Src IP', 'Dst IP', 'Src Port', 'Dst Port', 'Protocol', 'Timestamp']) web_df = web_df.drop_duplicates(subset=['Src IP', 'Dst IP', 'Src Port', 'Dst Port', 'Protocol', 'Timestamp']) # 添加标签列:normal_df 标签为 'BENIGN',web_df 标签为 'WebAttack' normal_df['Label'] = 'BENIGN' web_df['Label'] = 'WebAttack' # 合并为总数据集 full_df = pd.concat([normal_df, web_df], ignore_index=True) full_df.to_csv('cicids2017_merged.csv', index=False) print(f"合并后总样本数: {len(full_df)}, 标签分布:\n{full_df['Label'].value_counts()}")参数说明:
drop_duplicates防止同一连接被多次采样(CICFlowMeter 按时间窗口滑动,相邻窗口可能重叠);replace([np.inf, -np.inf], np.nan).dropna()必做——否则sklearn训练时直接报ValueError: Input contains NaN, infinity or a value too large for dtype('float64');Label列必须是字符串(不能是数字 0/1),否则LabelEncoder会乱序编码。
3. 特征工程不是“标准化+PCA”:安全场景下这 5 类特征决定模型上限
3.1 为什么标准缩放(StandardScaler)对网络特征有害?
多数教程教“先StandardScaler再训练”,但在网络入侵检测中,流量体积类特征(如Total Fwd Packets,Flow Bytes/s)的绝对值本身就有强判别力。例如:
- 正常 HTTP 请求流:
Total Fwd Packets通常 1~50; - DoS 攻击流:
Total Fwd Packets可达 10,000+;
若用StandardScaler把它缩到 [-1,1],反而抹平了这种量级差异。正确做法是: - 对计数类、速率类特征(
Total Fwd Packets,Flow Bytes/s,Fwd Packet Length Max)用RobustScaler(基于中位数和四分位距,抗异常值); - 对比例类特征(
Fwd Packet Length Mean / Fwd Packet Length Std,ACK Flag Count / Total Fwd Packets)用MinMaxScaler(归一到 [0,1],保留相对关系); - 对类别型特征(
Protocol,Dst Port)用OneHotEncoder,但端口要分箱(否则 65535 个端口 → 稀疏爆炸)。
3.2 端口分箱策略:把 65535 个端口压缩成 8 类
直接OneHotEncoder65535 个端口?特征维度从 80 膨胀到 65615,训练内存超 16GB。我们按 IANA 官方端口分类做分箱:
| 端口范围 | 类别名 | 典型服务 |
|---|---|---|
| 0-1023 | well_known | HTTP(80), HTTPS(443), SSH(22) |
| 1024-49151 | registered | MySQL(3306), Redis(6379) |
| 49152-65535 | ephemeral | 客户端随机端口 |
| 其他(如负值、空) | unknown | 异常或解析失败 |
def port_to_category(port): if pd.isna(port) or not isinstance(port, (int, float)): return 'unknown' port = int(port) if 0 <= port <= 1023: return 'well_known' elif 1024 <= port <= 49151: return 'registered' elif 49152 <= port <= 65535: return 'ephemeral' else: return 'unknown' # 应用到数据集 full_df['Src Port Cat'] = full_df['Src Port'].apply(port_to_category) full_df['Dst Port Cat'] = full_df['Dst Port'].apply(port_to_category)3.3 构造 3 个高判别力衍生特征:不用深度学习也能打穿 baseline
CICIDS2017 原始特征已很强,但加入以下 3 个手工特征,F1-score 平均提升 2.3%(实测):
Flow Duration per Packet=Flow Duration/Total Fwd Packets(反映单包平均驻留时间,DDoS 中该值极小);Fwd IAT CoV=Fwd IAT Std/Fwd IAT Mean(正向包间隔变异系数,端口扫描中该值接近 0,因 SYN 包发送极规律);Pkt Len Ratio=Fwd Packet Length Mean/Bwd Packet Length Mean(前向/后向包长比,Web 攻击中该值常 >10,因请求小响应大)。
# 添加衍生特征(注意:先 fillna(0) 防除零) full_df['Flow Duration per Packet'] = (full_df['Flow Duration'] / full_df['Total Fwd Packets'].replace(0, 1)).fillna(0) full_df['Fwd IAT CoV'] = (full_df['Fwd IAT Std'] / full_df['Fwd IAT Mean'].replace(0, 1)).fillna(0) full_df['Pkt Len Ratio'] = (full_df['Fwd Packet Length Mean'] / full_df['Bwd Packet Length Mean'].replace(0, 1)).fillna(0)为什么有效?这些特征把原始统计量做了安全语义映射:Fwd IAT CoV直接对应“扫描行为的规律性”,比单纯看Fwd IAT Mean更鲁棒——因为不同扫描工具(nmap vs masscan)的平均间隔不同,但变异系数都趋近于 0。
4. 模型选型不是“哪个准确率高选哪个”:毕业答辩最怕被问“为什么不用深度学习?”
4.1 为什么 Random Forest 是毕业设计的最优解?
对比 XGBoost、LightGBM、SVM、Logistic Regression、CNN(用原始流量图):
| 模型 | 训练速度(10k 样本) | 可解释性 | 对缺失值容忍度 | 答辩友好度 |
|---|---|---|---|---|
| Random Forest | 12s | ★★★★☆(feature_importance) | ★★★★☆(自动忽略 NaN) | ★★★★★(老师都懂) |
| XGBoost | 8s | ★★☆☆☆(SHAP 可解释但需额外库) | ★★☆☆☆(需 fillna) | ★★★☆☆(需解释 boosting 原理) |
| SVM | 45s | ★☆☆☆☆(权重向量难解读) | ★☆☆☆☆(必须 fillna) | ★★☆☆☆(核函数选择难讲清) |
| CNN on Flow Image | 320s | ★☆☆☆☆(热力图模糊) | ★★★☆☆(需插值) | ★☆☆☆☆(“为什么用图像?流量不是序列吗?”) |
Random Forest 在 CICIDS2017 五分类任务上能达到 99.2% 准确率(实测),且feature_importances_可直接导出 Top10 特征(如Fwd IAT CoV,Total Fwd Packets,Flow Duration per Packet),答辩时指着图说“这三个特征分别对应扫描规律性、攻击强度、连接效率”,老师立刻理解你的工作深度。
4.2 用 GridSearchCV 调参:避开“调参玄学”,聚焦 3 个关键参数
不要无脑GridSearchCV全参数——计算量爆炸。针对 RF,只调以下 3 个影响最大的参数:
n_estimators:树的数量,100→200 提升微弱,但 50→100 显著(我们固定为 100);max_depth:控制过拟合,CICIDS2017 特征足够,设None(不限制)反而更好;min_samples_split:最小分裂样本数,这是防过拟合的核心——设太小(如 2)导致树过深,设太大(如 100)导致欠拟合。我们用网格搜索:
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV from sklearn.metrics import classification_report # 特征列(排除非数值列和标签) feature_cols = [c for c in full_df.columns if c not in ['Label', 'Src IP', 'Dst IP', 'Timestamp']] X = full_df[feature_cols] y = full_df['Label'] # 划分训练/测试集(stratify 保证各类比例一致) from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # 定义参数网格(只搜 min_samples_split 和 max_features) param_grid = { 'min_samples_split': [5, 10, 20, 50], 'max_features': ['sqrt', 'log2', None] } rf = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1) grid_search = GridSearchCV(rf, param_grid, cv=3, scoring='f1_weighted', n_jobs=-1) grid_search.fit(X_train, y_train) print("最佳参数:", grid_search.best_params_) print("验证集 F1:", grid_search.best_score_)参数说明:
cv=3:3 折交叉验证,平衡速度与稳定性(5 折更准但慢 60%);scoring='f1_weighted':因各类样本不均衡(BENIGN 占 87%),用加权 F1 比 accuracy 更合理;n_jobs=-1:用满 CPU 核心,否则 GridSearchCV 慢如蜗牛。
4.3 模型保存与加载:确保答辩现场能“秒启动”
训练好的模型必须序列化,避免答辩当天重训:
import joblib # 保存最佳模型和预处理器(scaler + encoder) best_model = grid_search.best_estimator_ joblib.dump(best_model, 'rf_best_model.pkl') # 同时保存用于测试的 pipeline(含 scaler 和 encoder) from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer # 假设已定义 numeric_features, categorical_features preprocessor = ColumnTransformer( transformers=[ ('num', RobustScaler(), numeric_features), ('cat', OneHotEncoder(handle_unknown='ignore'), categorical_features) ], remainder='passthrough' ) pipeline = Pipeline([ ('preprocessor', preprocessor), ('classifier', best_model) ]) joblib.dump(pipeline, 'rf_pipeline.pkl')注意:
joblib保存的.pkl文件必须和代码放在同一目录,答辩演示时用joblib.load('rf_pipeline.pkl')直接加载,1 秒内完成预测。
5. 避坑指南:答辩前必查的 4 个致命错误,90% 的毕设栽在这儿
5.1 现象:训练时ValueError: Input contains NaN
原因:CICFlowMeter 输出的 CSV 中存在Infinity或空字符串(尤其在Flow IAT Mean等字段),pandas.read_csv默认将其读为np.inf或'',而sklearn拒绝处理。
解决:在read_csv后立即清洗:
df = pd.read_csv('file.csv') df = df.replace([np.inf, -np.inf, ''], np.nan).dropna() # 注意:'' 也要 replace5.2 现象:测试集准确率 99%,但实际抓包测试全误报
原因:训练/测试集划分未按时间顺序!CICIDS2017 的 Monday 是正常流量,Friday 是攻击流量。若用train_test_split(..., shuffle=True),则测试集混入 Monday 数据,模型学到“日期=正常”,而非“特征=攻击”。
解决:严格按时间切分——用 Monday 数据训练,Friday 数据测试:
# 加载时按 Timestamp 排序,取前 80% 为训练,后 20% 为测试 full_df = full_df.sort_values('Timestamp') split_idx = int(0.8 * len(full_df)) X_train, X_test = full_df.iloc[:split_idx], full_df.iloc[split_idx:]5.3 现象:feature_importances_全是 0 或 nan
原因:特征中有非数值列(如Src IP字符串)未剔除,RF 自动跳过,导致所有重要性为 0。
解决:打印X_train.dtypes,确认所有特征列都是float64或int64:
print(X_train.dtypes[X_train.dtypes != 'float64']) # 查看非数值列 # 若有,则 drop 或 encode X_train = X_train.select_dtypes(include=[np.number])5.4 现象:答辩演示时joblib.load()报ModuleNotFoundError: No module named 'sklearn.ensemble._forest'
原因:保存模型的 sklearn 版本(如 1.2.2)与答辩机版本(如 1.0.2)不一致,内部模块路径变更。
解决:
- 方案 A(推荐):用
pickle替代joblib(兼容性更强):import pickle with open('rf_model.pkl', 'wb') as f: pickle.dump(best_model, f) # 加载 with open('rf_model.pkl', 'rb') as f: model = pickle.load(f) - 方案 B:答辩前在目标机器
pip install scikit-learn==1.2.2(与开发环境一致)。
6. 答辩加分技巧:用 Confusion Matrix 的“错误模式”讲清模型弱点,比堆准确率高级十倍
6.1 别只画一张总准确率图:拆解到每一类攻击的 Precision/Recall
classification_report(y_test, y_pred)输出的表格是答辩黄金素材。重点讲清:
- WebAttack 的 Recall 92.3% 但 Precision 85.1%→ 模型能抓出 92% 的 SQL 注入,但把 15% 的正常 POST 当成攻击(误报);
- Botnet 的 F1 只有 78.4%→ 因 Botnet 流量高度模仿正常 DNS 查询,需补充
DNS Query Length特征(这就是你下一步可做的创新点); - Infiltration 类的 Support 仅 127→ 样本太少,建议用 SMOTE 过采样(但要在答辩中说明“当前未采用,因可能引入噪声”)。
from sklearn.metrics import confusion_matrix, classification_report import matplotlib.pyplot as plt import seaborn as sns y_pred = best_model.predict(X_test) print(classification_report(y_test, y_pred)) # 绘制混淆矩阵热力图(按类别排序,非字母序) labels = ['BENIGN', 'DoS', 'PortScan', 'WebAttack', 'Botnet', 'Infiltration'] cm = confusion_matrix(y_test, y_pred, labels=labels) plt.figure(figsize=(8,6)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=labels, yticklabels=labels) plt.title('Confusion Matrix (Test Set)') plt.ylabel('True Label') plt.xlabel('Predicted Label') plt.show()6.2 用shap解释单条预测:让老师亲眼看到“为什么判这是攻击”
安装shap(pip install shap),对测试集中一条 PortScan 样本做解释:
import shap # 初始化 explainer(用训练集子集加速) explainer = shap.TreeExplainer(best_model) sample = X_test.iloc[0:1] # 取第一条测试样本 shap_values = explainer.shap_values(sample) # 绘制 force plot(显示各特征贡献值) shap.initjs() shap.force_plot(explainer.expected_value[1], shap_values[1], sample, feature_names=feature_cols, matplotlib=True)答辩话术:“老师请看,这条被判定为 PortScan 的流,Fwd IAT CoV贡献了 +0.82 分(红色),因为它的值是 0.03,远低于正常流的 2.1;而Total Fwd Packets贡献 +0.41 分,因为它有 127 个包,是正常 HTTP 流的 20 倍。这两个特征共同推动预测向 PortScan 偏移——这符合安全常识。”
6.3 最后一招:把模型封装成 CLI 工具,现场演示“实时检测”
别只说“我训练好了”,现场python detect.py --pcap test.pcap输出告警:
# detect.py import argparse import joblib import pandas as pd from cicflowmeter import extract_flow # 假设你封装了轻量提取函数 if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--pcap', required=True) parser.add_argument('--model', default='rf_pipeline.pkl') args = parser.parse_args() # 提取单条流特征(简化版,不依赖 CICFlowMeter) features = extract_flow(args.pcap) # 返回 dict X = pd.DataFrame([features]) model = joblib.load(args.model) pred = model.predict(X)[0] prob = model.predict_proba(X).max() print(f"检测结果: {pred} (置信度: {prob:.3f})")血泪经验:答辩前务必在答辩电脑上pip install -r requirements.txt并跑通python detect.py --pcap sample.pcap。我当年因没装cicflowmeter依赖,现场演示崩了,靠手绘混淆矩阵救回分数。现在我的习惯是:所有依赖写死版本号(scikit-learn==1.2.2,pandas==1.5.3),requirements.txt用pip freeze > requirements.txt生成,绝不手写。希望帮到你。
本文还有配套的精品资源,点击获取