简介这是一份基于Python的神经网络流量异常检测项目资源面向信息安全与机器学习方向的学习者适合毕业设计、课程设计或工程实训。项目基于CICIDS2017数据集使用Pandas完成预处理与标准化并通过TensorFlow内置Keras搭建DNN或LSTM模型实现流量分类与异常识别帮助读者掌握从数据清洗到模型优化的完整流程。压缩包共8个文件包含5个CSV数据文件、2个Python脚本及1个说明文档整体大小约10.58MB数据、代码与说明分层清晰便于对照学习。目前已有297人学习资源可直接运行调试也可作为算法对比与论文撰写的参考基础。1. 从 CICIDS2017 到神经网络流量异常检测到底在解决什么问题公司 IDS 每天产生海量告警其中大部分是误报。用 python 做神经网络流量异常检测本质上是把每条网络流看成一行特征向量让模型去回答「正常还是异常」。如果你手里刚拿到 CICIDS2017 数据集和两个基于 Keras 的神经网络脚本——DNN 和 LSTM——那么整个项目可以拆成清晰的四步Pandas 预处理、建模、调参、评估。经历过完整流程的人会把重心放在三个地方数据清洗、类别不平衡、评估指标的选择。这三个环节做好之后无论写毕设做课程设计还是搭初期的工程原型都能直接复用。本文按一条可复现的路线拆到底所有代码在 TensorFlow 2.x 下可以直接跑。2. 数据预处理Pandas 清洗 CICIDS2017 的正确顺序流量异常检测项目里模型结构反而不是最耗时间的部分数据预处理才是。CICIDS2017 是加拿大网络安全研究所 2017 年发布的基准数据集其原始 pcap 经过 CICFlowMeter 特征提取后得到 78 维流量统计特征加一个 Label 字段。2.1 数据集结构与各 CSV 文件的对应关系这个项目压缩包里的几个 CSV分别对应不同的流量类别。BENIGN.csv 是正常流量DDoS.csv 是分布式拒绝服务攻击DoS Hulk.csv 是 Hulk 变种的拒绝服务攻击PostScan-real.csv 是端口扫描流量binary_classification.csv 则是把上述类别合并成「正常/异常」二分类之后的版本。文件名和原始 CICIDS2017 官方命名略有出入这是因为不同渠道下载后被人重新整理过以你本地的实际文件名为准。处理这类多文件数据集的第一步是先看清楚每个文件的列结构是否一致。CICIDS2017 每个 CSV 的表头都包含 Flow ID、Src IP、Dst Port、Protocol、Flow Duration、Packet Length Mean 等字段但不同攻击流量的某些列取值范围差异很大比如 DDoS 的 Flow Duration 可能集中在极短时间区间而 PortScan 的连接数和标志位分布又不一样。2.2 多文件合并与缺失值清洗项目里 DNN_IDS.py 和 LSTM_IDS.py 都要复用同一份清洗逻辑所以推荐把多文件合并和缺失值处理写在数据准备阶段。下面这段是常用的合并方式import pandas as pd import glob file_map { benign: BENIGN.csv, ddos: DDoS.csv, hulk: DoS Hulk.csv, portscan: PostScan-real.csv, } frames [] for label, path in file_map.items(): df pd.read_csv(path) df[source] label # 记录来源方便后续回溯 frames.append(df) df pd.concat(frames, ignore_indexTrue) print(df.shape) print(df[source].value_counts())pd.concat的ignore_indexTrue会把多个 CSV 的行索引重排避免后续 train_test_split 时索引重叠。source字段用来追踪每行来自哪个文件调试阶段很有用。合并之后先不要急着删列先检查缺失值比例再决定策略。CICIDS2017 里存在两种典型的脏数据单元格里的NaN和Infinity。前者来自某些协议字段无法提取时留下的空位后者来自除零计算产生的无穷值。处理它们时可以分两步走import numpy as np # 把无穷值替换为 NaN方便统一处理 df df.replace([np.inf, -np.inf], np.nan) # 统计每列的缺失比例 missing_ratio df.isnull().mean() print(missing_ratio[missing_ratio 0.01])注意这里用replace而不是先用dropna。如果直接把含Infinity的行删除你删掉的行数大概率比预期多很多。实际项目里我通常会先用isnull().mean()看缺失比例分布对缺失比例超过 50% 的特征列直接丢弃对缺失比例很低的列才执行dropna()。这样既保留样本量又不会让模型拟合到太多空值。2.3 特征标准化StandardScaler 与 MinMaxScaler 的选型神经网络对输入特征的尺度很敏感。CICIDS2017 的特征里Flow Duration 可能上百万微秒而 Packet Length Min 可能只有几十字节量纲差距达到四个数量级。如果不做标准化梯度更新会被大数值特征主导小数值特征几乎学不到东西。对于流量统计特征我一般默认选StandardScaler它对均值方差归一化后特征分布接近标准正态配合 ReLU 激活函数收敛更稳定。MinMaxScaler更适合特征本身有明确上下界、且不要求分布形状的场景比如已经归一化过的协议标志位。实际编码如下from sklearn.preprocessing import StandardScaler, LabelEncoder from sklearn.model_selection import train_test_split feature_cols df.select_dtypes(include[float64, int64]).columns.tolist() feature_cols.remove(source) # source 是字符串列select_dtypes 不会选中 X df[feature_cols].fillna(0).values y_raw df[Label].values # 统一编码BENIGN - 0攻击类型 - 1或保留多分类 le LabelEncoder() y_binary (y_raw ! BENIGN).astype(int) scaler StandardScaler() X_scaled scaler.fit_transform(X) X_train, X_test, y_train, y_test train_test_split( X_scaled, y_binary, test_size0.3, random_state42, stratifyy_binary )fit_transform只能用在训练集上测试集必须用scaler.transform(X_test)否则训练集和测试集的均值方差就不一致相当于数据泄漏。stratifyy_binary保证切分后训练集和测试集中正常流量与攻击流量的比例与原始数据一致这一点在类别不平衡的场景里尤其重要CICIDS2017 的正常流量样本量远大于攻击流量不 stratify 的话测试集里可能几乎全是正常样本。2.4 标签处理二分类还是多分类压缩包里存在binary_classification.csv说明项目作者已经把多类别标签合并成了二分类。实际做的时候要看你的目标是「检测是否有异常」还是「识别具体攻击类型」。后者需要把Label字段做多分类编码y_multi le.fit_transform(y_raw) # 0benign, 1ddos, 2hulk, 3portscan多分类时输出层改成 softmax损失函数改成categorical_crossentropy其余流程不变。二分类是简化版适合先跑通全流程多分类是完整版适合写论文时展示各类攻击的检测效果。从工程实践角度看先用二分类拿到 baseline再逐步扩展到多分类踩坑会少很多。3. DNN-IDS用 Keras 搭建前馈神经网络做异常分类流量异常检测里最基础的深度模型就是全连接神经网络也就是项目里的 DNN_IDS.py。它结构简单、训练快适合作为所有对比实验的基线模型。3.1 为什么选 DNN 而不是 CNNCICIDS2017 里的每条记录是 CICFlowMeter 聚合后的统计特征向量而不是原始报文。特征之间没有天然的局部空域相关性所以 CNN 卷积核的平移不变性在这里并没有明显优势。DNN 的每个神经元连接前一层的所有输出能直接建模特征与特征之间的非线性组合关系这是表格型数据建模最常见的选择。另外不少人会拿 BP 神经网络来对比BP 和这里说的 DNN 在结构上等价只是 BP 强调误差反向传播的更新过程DNN 强调多层堆叠的表达能力。在 Keras 里搭建时你不需要手工实现反向传播框架已经帮你处理了自动求导你需要关注的是层数、神经元数量、激活函数和正则化策略。3.2 DNN_IDS.py 的网络结构与 Dropout 作用一个可以稳定跑通的结构是 128 → 64 → 32 的三层全连接。输入维度是数据清洗后的特征数约 77 维去掉标签列后。第二层开始配合 BatchNormalization 和 Dropout防止参数过多导致过拟合。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, BatchNormalization from tensorflow.keras.optimizers import Adam model Sequential([ Dense(128, activationrelu, input_shape(X_train.shape[1],)), BatchNormalization(), Dropout(0.3), Dense(64, activationrelu), Dropout(0.2), Dense(32, activationrelu), Dense(1, activationsigmoid) ])Dropout(0.3)表示训练时每一轮随机丢弃 30% 的神经元输出这样模型不会过度依赖某一个特征组合。BatchNormalization放在卷积和全连接层之后激活函数之前能缓解梯度消失问题也让学习率可以适当调大。最后一层只有一个神经元激活函数用sigmoid输出值表示该样本属于攻击流量的概率。之后判断时以 0.5 作为默认阈值但如果正常流量和攻击流量比例悬殊0.5 往往不是最优阈值后面第 5 章会讲怎么调。3.3 编译与训练学习率、batch size 与早停model.compile(optimizerAdam(learning_rate0.001), lossbinary_crossentropy, metrics[accuracy]) history model.fit( X_train, y_train, validation_split0.2, batch_size256, epochs50, callbacks[EarlyStopping(patience5, restore_best_weightsTrue)] )Adam是工程里默认选择它对学习率的适应能力比 SGD 强很多learning_rate0.001是 Keras 的默认值也是大多数表格建模的一个可靠起点。batch_size256在 CICIDS2017 这种几十万行的数据集上比较合适显存占用小且梯度估计足够稳定。validation_split0.2表示在训练数据中再划出 20% 作为验证集用于监控每一轮的泛化表现不影响模型参数更新。配合EarlyStopping(patience5)当验证集损失连续 5 轮没有下降时训练自动终止restore_best_weightsTrue会把模型参数回滚到验证集效果最好的那一轮。这一点比手动保存权重再 reload 更省事。3.4 多分类改动点与实践建议如果要把二分类模型改成多分类改动集中在三处标签编码、损失函数、输出层。多分类输出层用Dense(num_classes, activationsoftmax)损失函数改为categorical_crossentropy同时 y 标签要做 one-hot 编码Keras 也支持直接用整数标签配合sparse_categorical_crossentropy。初始化不同攻击类型数量过多时需要特别注意某些攻击类型在数据集中本身样本量就很低多分类下模型很容易把它忽略掉全部预测成样本量最大的那一类。遇到这种情况先把低频攻击类型合并成「other_attack」或者改用第 5 章讲的加权损失会更实际。4. LSTM-IDS把流量当成时间序列来建模项目里另一个模型是 LSTM_IDS.py。LSTM 引入了一个 DNN 没有的假设流量样本之间可能有一定的时间顺序关系。4.1 LSTM 用于流量异常检测的合理性DDoS 攻击的过程往往有逐渐加剧的趋势端口扫描更是天然具有时间序列特征先探测端口再尝试漏洞利用。如果只用单条流量的统计特征模型看不到这个时间上下文。LSTM 通过门控机制保留历史信息理论上能捕捉这种跨样本的时序模式。但 CICIDS2017 的 CSV 数据没有显式的时间戳列每条记录已经聚合成了独立特征。常见做法是把连续的多条流量记录拼成一个滑动窗口用窗口内所有样本的特征来预测最后一条样本的标签。这样把表格数据转成了弱时序数据保留了数据在文件中的原始排列顺序。4.2 数据 reshape 成三维张量LSTM 输入要求三维形状(样本数, 时间步长, 特征维度)。下面用滑动窗口把上一章的X_scaled转换import numpy as np seq_len 10 # 每个窗口包含 10 条连续流量记录 X_seq, y_seq [], [] for i in range(len(X_scaled) - seq_len 1): X_seq.append(X_scaled[i:i seq_len]) y_seq.append(y_binary[i seq_len - 1]) X_seq np.array(X_seq) y_seq np.array(y_seq)窗口生成后第 i 个样本的特征是第 i 到 i9 行流量记录标签取窗口最后一行的标签。seq_len是超参数取的太短学不到时序上下文取太长会显著增加样本数并让相邻样本高度重叠训练成本翻倍。项目里先用 10 跑通是合理的后续可以调 5 和 20 做对比。4.3 LSTM_IDS.py 主体结构与训练from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential([ LSTM(64, return_sequencesTrue, input_shape(X_seq.shape[1], X_seq.shape[2])), LSTM(32, return_sequencesFalse), Dropout(0.3), Dense(32, activationrelu), Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy])第一层LSTM(64, return_sequencesTrue)返回所有时间步的隐状态让第二层LSTM(32)继续处理时间维度的信息。最后一个 LSTM 不返回序列只输出最终时序特征再交给全连接层分类。input_shape必须显式指定时间步和特征数Keras 会自动推断样本数。训练时把X_seq和对应标签丢进model.fit代码和上一章 DNN 的 fit 完全一样。注意 LSTM 参数数量远少于同层数的 DNN但由于时序计算引入了循环依赖实际训练耗时通常是 DNN 的 3 到 5 倍。数据量大时建议先用 5000 条样本验证流程能跑通再全量训练。4.4 DNN 与 LSTM 的适用边界维度DNNLSTM输入要求二维特征向量三维时间窗口时序建模能力无有训练速度快慢适合场景快速基线、特征独立攻击过程有先后依赖过拟合风险较高需 Dropout较低但仍需正则化大部分实际数据集里DNN 和 LSTM 的最终准确率差距并不大因为 CICIDS2017 的单条记录里已经包含了很多强区分特征比如 Packet Length 的统计值、标志位组合等。LSTM 的价值在于展示另一种建模思路以及在攻击流量确实存在时间模式时提供更好的召回表现。如果你发现 LSTM 结果比 DNN 差不要急着调结构先检查seq_len和数据是否乱序排列。5. 超参数优化与模型评估为什么准确率可能骗你很多人在跑流量异常检测时看到测试集准确率 98% 就以为完事了。但在 CICIDS2017 这类高度不平衡的数据上准确率是很容易出现误导性的指标需要结合更多工具综合判断。5.1 准确率的陷阱如果整个数据集里 85% 是正常流量15% 是攻击流量那么一个把所有样本全部预测为「正常」的模型准确率也有 85%。模型可能什么都没学到只是学会了复制数据集的分布。所以在训练完成后评估阶段必须看混淆矩阵、精确率、召回率和 F1 分数并额外关注攻击类别的召回率。5.2 用 EarlyStopping 和 ModelCheckpoint 控制最优模型from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks [ EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue), ModelCheckpoint(best_dnn.h5, monitorval_auc, save_best_onlyTrue, modemax) ]EarlyStopping按验证损失决定何时停ModelCheckpoint按验证集 AUC 保存最优权重两者可以同时工作。注意回调函数监控指标要看模型编译时是否加入对应 metricfrom tensorflow.keras.metrics import AUC model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy, AUC(nameval_auc)])加了AUC之后ModelCheckpoint的monitorval_auc才有效。modemax告诉回调函数 AUC 越大越好如果不写这个参数框架不知道该朝哪个方向保存权重。5.3 评估代码混淆矩阵与 ROC-AUCfrom sklearn.metrics import classification_report, confusion_matrix, roc_auc_score y_proba model.predict(X_test) y_pred (y_proba 0.5).astype(int) print(classification_report(y_test, y_pred, target_names[benign, attack])) print(confusion_matrix(y_test, y_pred)) print(AUC , roc_auc_score(y_test, y_proba))classification_report会给出精确率、召回率、F1 值和各类别样本数先看攻击类别的召回率。confusion_matrix返回[[TN, FP], [FN, TP]]重点关注左下角的FN即「把攻击流量误判为正常」的数量漏报在安全场景里比误报更危险。最后一行AUC的好处是不依赖分类阈值AUC 在 0.95 以上说明模型对正常和异常的概率分布区分度良好但 AUC 高不代表 0.5 阈值下效果好所以前面两个指标仍然要看。5.4 超参数调优的实操顺序超参数调优不要一上来就网格搜索CICIDS2017 全量训练一轮可能就要几分钟穷举组合很难受。我的习惯是三步走固定batch_size256把learning_rate按 0.01、0.001、0.0001 各训练 10 轮看验证集 loss 下降速度选下降最快且不震荡的那一档。固定学习率把网络宽度和深度分别调整比如隐藏层神经元从 128 变 256或从三层加到四层观察验证集与训练集 loss 的差距是否在扩大。差距扩大说明欠拟合需要加容量差距很小且两者都高说明过拟合需要加 Dropout。最后调batch_size。32 和 128 的收敛轨迹差异明显小 batch 收敛更慢但泛化更好大 batch 速度更快但容易收敛到平坦的局部极值。在流量数据上256 到 512 通常是在训练时间和效果之间的性价比区间。6. 把模型部署到检测流程模型导出、特征对齐与类别不平衡处理训练的最终目的是把模型用起来哪怕只是课程设计也要让模型能对一条新产生的流量记录给出预测结果。这一章给出三条最常遇到的操作路径每一条都直接影响线上效果。6.1 模型导出与加载训练完成后把模型和标准化器同时导出。Keras 的 H5 格式单文件就能包含结构和权重而StandardScaler也需要单独保存from tensorflow.keras.models import load_model import joblib model.save(dnn_ids.h5) joblib.dump(scaler, scaler.pkl)加载时注意一条容易踩的坑load_model(dnn_ids.h5)默认会编译模型并保留训练时配置的优化器状态但如果你用了自定义的AUC等指标加载时可能会报错需要加上compileFalse参数再用model.compile手动设置新的优化器策略。6.2 新流量的特征对齐与标准化线上预测时新流量必须经过与训练时完全一致的特征提取和标准化步骤。CICIDS2017 原始特征是通过 CICFlowMeter 从 pcap 提取的如果新流量数据的特征顺序和训练时不一致神经网络的预测结果会完全失真。所以实操中建议提前把特征列名列表导出预测前做一次列对齐expected_cols feature_cols # 训练时保存的特征列名列表 def predict_flow(raw_row_df): missing set(expected_cols) - set(raw_row_df.columns) if missing: raise ValueError(f缺少特征列: {missing}) row raw_row_df[expected_cols].fillna(0).values row_scaled scaler.transform(row.reshape(1, -1)) prob loaded_model.predict(row_scaled) return prob[0][0]这里的scaler必须是训练时保存的那份不能线上重新fit否则破坏了训练时的特征分布假设。6.3 类别不平衡的两种处理方式CICIDS2017 里正常流量和各类攻击样本比例悬殊如果不做处理模型对低频攻击类型的召回率往往偏低。最简单的方法是给 Keras 传入class_weightclass_weight {0: 1.0, 1: 10.0} model.fit(X_train, y_train, validation_split0.2, class_weightclass_weight, epochs30)此时模型在计算binary_crossentropy损失时会把攻击类样本的损失乘以 10相当于人为提高了攻击类的错分代价梯度更新会更倾向于把攻击样本分对。class_weight 和 SMOTE 过采样可以叠加使用但每次修改类别权重或采样策略之后都要重新评估验证集上的混淆矩阵防止模型从一个极端走到另一个极端把所有流量都判成异常。本文还有配套的精品资源点击获取