ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python+神经网络流量异常检测:从毕业设计到可复现源码的完整方案

Python+神经网络流量异常检测:从毕业设计到可复现源码的完整方案 简介本资源面向计算机、网络安全相关专业的毕业设计、课程设计与项目开发学习者提供一套基于Python与深度神经网络的流量异常检测完整实现方案重点解决传统入侵检测误报率偏高的问题。压缩包共9个文件约10.58MB以csv数据集、py源码、txt说明与md文档为主涵盖二分类数据、DNN与LSTM两种检测模型代码及项目说明结构清晰便于按模块查阅。资源已通过严格测试可直接运行参考并在此基础上延伸扩展。目前已有184人学习下载。读者可获得从数据处理、模型搭建到异常识别评估的完整项目链路理解基于网络的IDS如何学习正常与异常流量行为同时借助文档快速掌握目录组织与代码逻辑适合作为毕业设计选题、课程实践或安全方向入门项目也可用于对比DNN与LSTM在流量检测中的表现差异。1. 从一份毕业设计说起Python神经网络做流量异常检测到底在做什么很多同学第一次接触「流量异常检测」是在毕业设计选题表上看到「基于 Python 神经网络实现」几个字觉得既像安全方向又像 AI 方向听起来很唬人真动手却发现连数据长什么样都不知道。我带的几个做课程设计的学生最初都卡在同一个地方把网络流量当成普通表格数据直接丢进模型结果准确率 99% 却完全不能用。原因很简单流量数据是时序的、带协议语义的、类别极度不平衡的不是鸢尾花数据集。这个标题真正要解决的问题是给定一段网络流量的统计特征或原始报文序列判断它是否异常并尽量说清异常属于哪一类扫描、DDoS、暴力破解、Web 攻击等。它适合三类人一是做毕业设计、课程设计需要一套能跑通、能写进论文的完整方案二是刚入门安全数据分析想找一个比「调包跑 MNIST」更贴近真实场景的练手项目三是需要一份可复现源码和项目文档模板用来对照自己实现的人。核心链路其实就四步抓流量或读公开数据集、做特征工程、搭神经网络、评估并解释结果。后面几章我会按这条链路把每一步的参数、坑和可抄的代码都摊开讲。2. 数据从哪来NSL-KDD、CICIDS 与自采流量的取舍2.1 为什么不能随便找个 CSV 就开跑流量异常检测的公开数据集里最常被毕业设计引用的是 NSL-KDD 和 CICIDS2017/2018。NSL-KDD 是 KDD99 的去重版本每条记录 41 维特征加一个标签优点是体积小、格式规整、论文引用多缺点是年代久远很多攻击模式在现代网络里已经很少见模型容易学到过时的分布。CICIDS 系列用 CICFlowMeter 提取流特征包含 80 多维覆盖 DDoS、PortScan、Brute Force、Web Attack 等较新的攻击类型更贴近真实但数据量大、类别极不平衡清洗成本高。我一般的建议是如果目标是快速跑通并写进论文用 NSL-KDD 打底保证流程完整如果想让结果更有说服力用 CICIDS2017 的某一天子集比如 Wednesday 的 DDoS 和 PortScan做二分类或少数几类多分类。自采流量听起来最酷但涉及抓包环境、标注成本和隐私问题除非实验室有现成的镜像流量否则不建议毕业设计阶段自己从零采。2.2 用 pandas 读 NSL-KDD 并做最小清洗NSL-KDD 的原始文件没有表头列名需要自己按官方特征说明补上。下面这段代码是我常用的读取和清洗模板直接可抄。import pandas as pd import numpy as np # NSL-KDD 官方 41 维特征名 label difficulty col_names [ duration,protocol_type,service,flag,src_bytes,dst_bytes, land,wrong_fragment,urgent,hot,num_failed_logins,logged_in, num_compromised,root_shell,su_attempted,num_root,num_file_creations, num_shells,num_access_files,num_outbound_cmds,is_host_login, is_guest_login,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,label,difficulty ] train pd.read_csv(KDDTrain.txt, namescol_names) test pd.read_csv(KDDTest.txt, namescol_names) # 二分类normal 为 0其余攻击为 1 for df in (train, test): df[binary_label] (df[label] ! normal).astype(int) # 类别型特征做 one-hot数值型保留 cat_cols [protocol_type, service, flag] train pd.get_dummies(train, columnscat_cols) test pd.get_dummies(test, columnscat_cols) # 对齐列测试集可能缺某些 one-hot 列 train, test train.align(test, joinleft, axis1, fill_value0) print(train.shape, test.shape) print(train[binary_label].value_counts())逻辑说明先补列名再把多分类标签压成二分类这是毕业设计里最稳的起点。pd.get_dummies对三个类别特征做独热编码注意训练集和测试集必须用align对齐否则测试集少一列会导致模型输入维度不匹配这是新手最常见的翻车点之一。参数上joinleft表示以训练集列为准测试集缺失的列补 0符合「训练集定义特征空间」的原则。2.3 类别不平衡怎么处理才不虚高NSL-KDD 训练集里正常流量约占 53%攻击占 47%还算均衡但 CICIDS 里正常流量可能占 80% 以上直接训练会让模型偏向多数类准确率虚高、召回率惨淡。常见做法有三种一是用class_weightbalanced让损失函数加权二是对少数类做 SMOTE 过采样但要注意只能在训练集上做测试集保持原始分布三是用 focal loss 替代交叉熵。我一般先用 class_weight简单且不容易引入合成样本的偏差。评估时不要只看 accuracy重点看 macro-F1 和少数类的 recall否则答辩时老师一问「DDoS 召回多少」就露馅。3. 特征工程把原始流量变成神经网络能吃的张量3.1 数值归一化和独热编码的边界神经网络对输入尺度敏感src_bytes可能上万serror_rate却在 0 到 1 之间不归一化会导致梯度被大特征主导。标准做法是对连续特征做 Min-Max 或 StandardScaler。Min-Max 把值压到 [0,1]适合有明确边界的特征StandardScaler 做零均值单位方差适合近似正态的特征。流量特征里很多是计数和比率我倾向用 Min-Max因为比率本身有界计数经过压缩后也不会太离谱。注意一个坑归一化参数必须只在训练集上 fit然后 transform 测试集。如果先把训练集和测试集拼起来再 fit就造成了数据泄漏论文里被查到会很尴尬。from sklearn.preprocessing import MinMaxScaler # 去掉标签列和 difficulty drop_cols [label, difficulty, binary_label] feature_cols [c for c in train.columns if c not in drop_cols] scaler MinMaxScaler() X_train scaler.fit_transform(train[feature_cols]) X_test scaler.transform(test[feature_cols]) y_train train[binary_label].values y_test test[binary_label].values print(X_train.shape, X_test.shape)逻辑说明fit_transform只在训练集调用测试集只调用transform这是防止数据泄漏的铁律。参数上MinMaxScaler 默认把每列压到 [0,1]如果某列方差为 0比如全 0 的num_outbound_cmds它会保留 0不会报错但这类无用列建议提前删掉减少输入维度。3.2 用滑动窗口构造时序样本如果只做单条记录分类前馈网络就够了。但流量异常检测的本质是时序问题很多攻击比如慢速扫描、低频 DDoS单看一条流看不出异常需要上下文。常见做法是用滑动窗口把连续 N 条记录拼成一个样本窗口内可以取统计量均值、方差、计数或直接堆叠成二维张量喂给 CNN/LSTM。def make_windows(X, y, window10, stride1): xs, ys [], [] for i in range(0, len(X) - window 1, stride): xs.append(X[i:iwindow]) # 窗口标签取窗口内是否含异常只要有一个异常就标 1 ys.append(int(y[i:iwindow].max())) return np.array(xs), np.array(ys) Xw_train, yw_train make_windows(X_train, y_train, window10, stride5) Xw_test, yw_test make_windows(X_test, y_test, window10, stride5) print(Xw_train.shape, yw_train.mean())逻辑说明window10表示用连续 10 条记录预测一个标签stride5是步长用来减少样本重叠、控制数据量。标签取窗口内最大值意味着只要窗口里有一条攻击就标为异常偏向高召回适合安全场景。参数怎么调窗口太小捕捉不到慢速攻击太大又会让正常窗口里混入异常导致误报我一般从 10 开始试配合验证集看 F1。3.3 特征选择别把 122 维全塞进去独热编码后 NSL-KDD 会膨胀到 120 多维其中很多列是稀疏甚至全零的。全塞进去不仅训练慢还容易过拟合。常见做法是用随机森林或互信息做特征重要性排序保留前 30 到 50 维。下面用随机森林快速筛一遍。from sklearn.ensemble import RandomForestClassifier import pandas as pd rf RandomForestClassifier(n_estimators100, random_state42, n_jobs-1) rf.fit(X_train, y_train) imp pd.Series(rf.feature_importances_, indexfeature_cols).sort_values(ascendingFalse) top_k imp.head(40).index.tolist() print(imp.head(15))逻辑说明随机森林的特征重要性基于不纯度下降速度快、对非线性关系敏感适合做初筛。n_estimators100是精度和速度的平衡点n_jobs-1用满 CPU。拿到 top_k 后后续模型只在这 40 维上训练。注意特征选择也要只在训练集上做测试集用同样的列索引切。4. 神经网络建模从 MLP 到 LSTM 的选型与训练4.1 前馈网络 MLP 作为基线别一上来就上 Transformer毕业设计里最稳的基线是三层 MLP。输入维度等于特征数两个隐藏层用 ReLU输出层二分类用 sigmoid。下面用 PyTorch 写一个可复现的训练脚本。import torch import torch.nn as nn from torch.utils.data import TensorDataset, DataLoader device torch.device(cuda if torch.cuda.is_available() else cpu) class MLP(nn.Module): def __init__(self, in_dim): super().__init__() self.net nn.Sequential( nn.Linear(in_dim, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, 1) ) def forward(self, x): return self.net(x).squeeze(-1) # 用特征选择后的 top_k 列 Xtr torch.tensor(X_train[:, [feature_cols.index(c) for c in top_k]], dtypetorch.float32) Xte torch.tensor(X_test[:, [feature_cols.index(c) for c in top_k]], dtypetorch.float32) ytr torch.tensor(y_train, dtypetorch.float32) yte torch.tensor(y_test, dtypetorch.float32) train_loader DataLoader(TensorDataset(Xtr, ytr), batch_size256, shuffleTrue) test_loader DataLoader(TensorDataset(Xte, yte), batch_size512) model MLP(Xtr.shape[1]).to(device) # 类别加权缓解不平衡 pos_weight torch.tensor([(y_train 0).sum() / (y_train 1).sum()], dtypetorch.float32).to(device) criterion nn.BCEWithLogitsLoss(pos_weightpos_weight) optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) for epoch in range(20): model.train() for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() optimizer.step() print(fepoch {epoch} done)逻辑说明BCEWithLogitsLoss把 sigmoid 和交叉熵合在一起数值更稳定。pos_weight设为负样本数除以正样本数让少数类攻击的损失被放大提升召回。Dropout(0.3)防过拟合weight_decay1e-5是 L2 正则。参数怎么改学习率 1e-3 是 Adam 的常用起点如果 loss 震荡就降到 1e-4batch_size 256 适合几千到几万条样本太大收敛慢太小梯度噪声大。4.2 LSTM 处理时序窗口如果用了第 3 章的滑动窗口输入是(batch, window, feat)这时用 LSTM 更自然。LSTM 能记住窗口内的顺序依赖对慢速扫描这类攻击比 MLP 敏感。class LSTMDetector(nn.Module): def __init__(self, in_dim, hidden64): super().__init__() self.lstm nn.LSTM(in_dim, hidden, batch_firstTrue, num_layers1) self.fc nn.Linear(hidden, 1) def forward(self, x): out, _ self.lstm(x) # out: (B, T, H) last out[:, -1, :] # 取最后一个时间步 return self.fc(last).squeeze(-1) # Xw_train 形状 (N, window, feat) Xtr_w torch.tensor(Xw_train, dtypetorch.float32) Xte_w torch.tensor(Xw_test, dtypetorch.float32) ytr_w torch.tensor(yw_train, dtypetorch.float32) yte_w torch.tensor(yw_test, dtypetorch.float32) model_lstm LSTMDetector(Xtr_w.shape[2]).to(device)逻辑说明batch_firstTrue让输入维度是(batch, seq, feature)符合 PyTorch 习惯。取最后一个时间步的输出做分类是因为 LSTM 的末状态聚合了整个窗口的信息。参数上hidden64对 40 维特征足够再大容易过拟合num_layers1先跑通效果不够再加到 2但要注意梯度问题。4.3 训练完怎么评估才不虚评估必须同时看 precision、recall、F1 和混淆矩阵二分类还要看 AUC。安全场景里 recall 通常比 precision 重要漏报一个攻击比误报几个正常流量代价大。下面这段评估代码直接可用。from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score model.eval() with torch.no_grad(): logits model(Xte.to(device)) probs torch.sigmoid(logits).cpu().numpy() preds (probs 0.5).astype(int) print(confusion_matrix(y_test, preds)) print(classification_report(y_test, preds, digits4)) print(AUC:, roc_auc_score(y_test, probs))逻辑说明阈值 0.5 是默认值但安全场景可以调低到 0.3 提高召回代价是误报上升具体看业务容忍度。roc_auc_score衡量排序能力不受阈值影响是论文里比 accuracy 更值得写的指标。如果 AUC 高但 F1 低说明阈值没调好如果 AUC 也低说明模型没学到东西回去查特征和标签。5. 避坑与排查那些让准确率虚高、模型翻车的细节5.1 现象测试集准确率 99%实际部署全是误报原因训练集和测试集做了全局归一化或全局独热编码造成数据泄漏或者测试集里正常样本占绝大多数模型全预测正常也能拿高准确率。解决归一化和编码只在训练集 fit评估必须看少数类 recall 和混淆矩阵不能只看 accuracy。5.2 现象loss 不下降一直卡在 0.69 左右原因二分类里 0.69 约等于 ln2说明模型输出接近 0.5什么都没学到。常见原因是输入特征没归一化、学习率太大导致梯度爆炸或者标签列不小心混进了特征。解决检查feature_cols是否包含 label确认归一化已做把学习率降到 1e-4 再试。5.3 现象训练集 F1 0.99测试集 F1 0.6原因过拟合。流量数据特征维度高、样本有限时特别容易发生。解决加 Dropout、weight_decay减少隐藏层维度或者做特征选择降维。如果用了 LSTM检查窗口是否太大导致参数量爆炸。5.4 现象LSTM 训练极慢一个 epoch 要十几分钟原因窗口重叠太多导致样本量翻倍或者 batch_size 太小。解决增大 stride 减少重叠batch_size 提到 256 或 512确认用了 GPU。如果还是慢先用 MLP 跑通再换 LSTM。5.5 现象换了 CICIDS 数据集后代码全报错原因CICIDS 列名带空格、无穷值、NaN且标签列名和 NSL-KDD 不同。解决读入后先df.replace([np.inf, -np.inf], np.nan).dropna()列名用strip()去空格标签列单独映射成 0/1。不同数据集的预处理脚本建议分开写别硬套。6. 把项目文档和源码组织成能交付的样子毕业设计和课程设计最后要交的不只是代码还有项目文档。我一般按这个结构组织data/放原始数据和预处理脚本models/放网络定义train.py和eval.py分开config.yaml存超参数README.md写清环境依赖和运行顺序。文档部分重点写三块数据集来源和字段说明、模型结构和参数选择理由、实验结果表格。参数选择理由要写「为什么 window 取 10 而不是 5」而不是只列数字答辩时老师最常追问的就是这个。验证方法上除了在测试集上跑指标我习惯再做一次「时间切分」验证按时间顺序把数据切成前 70% 训练、后 30% 测试而不是随机切分。随机切分会让相邻的相似流量同时出现在训练和测试集里指标虚高。时间切分更接近真实部署指标会低一些但更可信。这个技巧写进论文的「实验设置」里是加分项。最后说个我自己的习惯每次改完预处理或模型结构先跑一个 1000 条样本的小子集确认 loss 能下降、指标不是随机水平再上全量数据。这个「小样本冒烟测试」帮我省过无数次等半小时才发现标签写反的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表