ARTICLE DETAIL

资讯详情

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

Python+CNN网络入侵检测实战:从NSL-KDD源码到真实流量迁移

Python+CNN网络入侵检测实战:从NSL-KDD源码到真实流量迁移 简介这是一套基于Python与CNN实现的网络入侵检测算法项目源码面向计算机相关专业正在做毕业设计、课程设计或期末大作业的学生以及需要项目实战练习的学习者。项目经导师指导并认可评审得分98分可作为高分毕设参考方案。资源包共33个文件约21.59MB包含4个Python脚本训练、预测、预处理与模型定义、12个csv数据集文件、7个xml配置、1个pth模型权重、1个md说明文档以及准确率、精确率等png/jpg结果图覆盖从数据预处理、模型训练到效果评估的完整流程。项目以NSL-KDD数据集为基础配套minMax归一化、Change与DataSet等模块便于读者理解CNN在网络流量特征提取与分类中的应用。目前已有191人学习下载适合希望快速掌握入侵检测实战思路、复用代码框架并完成论文或答辩材料整理的学习者参考。1. 从一份 PythonCNN 入侵检测源码说起它到底能解决什么问题很多人第一次拿到「基于pythonCNN的网络入侵检测算法源码项目说明」这类压缩包反应是两极化要么觉得深度学习做安全检测太玄学要么直接解压跑一遍发现准确率 99% 就以为捡到宝。我当年也踩过这个坑——在 KDD Cup 99 上跑出 99.2% 的准确率换到真实流量上直接崩到 60% 出头。问题不在 CNN而在数据分布和特征工程。这个标题背后其实是一条完整的落地链路用 Python 把原始网络流量pcap 或 CSV 格式的流量特征清洗成模型能吃的张量用一维卷积网络1D-CNN学习流量特征中的局部模式最后输出「正常 / 各类攻击」的分类结果。它适合两类人一是做课程设计或毕设、需要一份能跑通、结构清晰的高分代码的同学二是安全方向刚转深度学习、想找一个完整闭环练手的工程师。核心价值不是那个准确率数字而是让你理解「流量特征 → 张量 → 卷积 → 分类」这条链路里每一步的参数怎么定、坑在哪。需要先泼一盆冷水这份源码大概率是基于 NSL-KDD 或 KDD Cup 99 这类经典数据集写的这类数据集有 41 维手工特征如 duration、protocol_type、src_bytes、flag 等不是原始 pcap 字节流。所以它本质是「表格数据的 1D-CNN 分类」不是端到端的原始流量深度学习。搞清楚这一点你才不会拿着它去套真实场景然后翻车。下面我按「数据怎么来 → 模型怎么搭 → 怎么训 → 怎么避坑 → 怎么进阶」的顺序把这条链路拆开讲透。2. 数据管道从 NSL-KDD 的 41 维特征到 CNN 能吃的张量2.1 为什么选 NSL-KDD 而不是直接上原始 pcap先说选型理由。网络入侵检测的公开数据集里KDD Cup 99 是最老的NSL-KDD 是它的去冗余版本去掉了大量重复记录训练集 125973 条、测试集 22544 条攻击类型归为 4 大类DoS、Probe、R2L、U2R。它的优势是特征已经手工提取好41 维数值型和类别型混合拿来就能训模型适合验证算法本身。缺点是年代久远很多攻击手法已经过时真实场景泛化差。如果你要的是「端到端原始流量」那得用 CIC-IDS2017、UNSW-NB15 或者自己抓 pcap 用 CICFlowMeter 提特征。但那些数据集的清洗工作量是 NSL-KDD 的好几倍对新手不友好。我的建议是先用 NSL-KDD 把 CNN 这条链路跑通、理解每个参数的作用再迁移到 CIC-IDS2017 上做泛化验证。这份源码大概率走的就是第一条路。2.2 类别特征编码one-hot 还是 label encodingNSL-KDD 里有 3 个类别特征protocol_typetcp/udp/icmp、service70 种、flag11 种。处理方式直接决定模型效果。常见做法有两种Label Encoding把 tcp/udp/icmp 映射成 0/1/2。问题是引入了不存在的序关系模型可能误以为 icmp udp。One-Hot Encodingprotocol_type 变成 3 维service 变成 70 维flag 变成 11 维。特征维度从 41 涨到 41 - 3 3 70 11 122 维。我一般用 one-hot因为 CNN 对输入维度的序关系敏感度虽然比树模型低但没必要引入噪声。下面是完整的预处理代码import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler, OneHotEncoder # 列名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) # 攻击类型映射到 5 类normal 4 大类攻击 attack_map { normal: normal, back:dos,land:dos,neptune:dos,pod:dos,smurf:dos, teardrop:dos,apache2:dos,udpstorm:dos,processtable:dos, mailbomb:dos,worm:dos, satan:probe,ipsweep:probe,nmap:probe,portsweep:probe, mscan:probe,saint:probe, guess_passwd:r2l,ftp_write:r2l,imap:r2l,phf:r2l, multihop:r2l,warezmaster:r2l,warezclient:r2l,spy:r2l, xlock:r2l,xsnoop:r2l,snmpguess:r2l,snmpgetattack:r2l, httptunnel:r2l,sendmail:r2l,named:r2l, buffer_overflow:u2r,loadmodule:u2r,rootkit:u2r, perl:u2r,sqlattack:u2r,xterm:u2r,ps:u2r } train[label] train[label].map(attack_map) test[label] test[label].map(attack_map) # 类别特征 one-hot cat_cols [protocol_type, service, flag] enc OneHotEncoder(sparse_outputFalse, handle_unknownignore) enc.fit(pd.concat([train[cat_cols], test[cat_cols]])) train_cat enc.transform(train[cat_cols]) test_cat enc.transform(test[cat_cols]) # 数值特征归一化 num_cols [c for c in col_names if c not in cat_cols [label,difficulty]] scaler MinMaxScaler() train_num scaler.fit_transform(train[num_cols]) test_num scaler.transform(test[num_cols]) # 拼接 X_train np.hstack([train_num, train_cat]) X_test np.hstack([test_num, test_cat]) # 标签编码 label_map {normal:0, dos:1, probe:2, r2l:3, u2r:4} y_train train[label].map(label_map).values y_test test[label].map(label_map).values print(训练集形状:, X_train.shape) # (125973, 122) print(测试集形状:, X_test.shape) # (22544, 122)这段代码的关键点有三个。第一OneHotEncoder必须在训练集和测试集合并后的数据上 fit否则测试集出现训练集没见过的 service 类别时会报错或全零。第二MinMaxScaler只能在训练集上 fit测试集用 transform这是防数据泄漏的基本功。第三handle_unknownignore保证测试集出现新类别时输出全零向量而不是崩溃。2.3 把 122 维向量 reshape 成 CNN 输入1D-CNN 的输入形状是(batch, timesteps, channels)。对于表格特征常见做法是把 122 维特征当成 122 个时间步、1 个通道即 reshape 成(122, 1)。也有做法是 reshape 成(11, 11, 1)当图像处理但那样会破坏特征间的语义关系我不推荐。# reshape 成 (样本数, 122, 1) X_train_cnn X_train.reshape(-1, X_train.shape[1], 1) X_test_cnn X_test.reshape(-1, X_test.shape[1], 1) # 标签 one-hot多分类用 categorical_crossentropy from tensorflow.keras.utils import to_categorical y_train_cat to_categorical(y_train, num_classes5) y_test_cat to_categorical(y_test, num_classes5) print(CNN 输入:, X_train_cnn.shape) # (125973, 122, 1)提示如果你的显存不够别一次性把 12 万条全塞进去用tf.data.Dataset做 batch 和 prefetch后面训练部分会讲。3. 1D-CNN 模型搭建卷积核、池化、Dropout 的参数怎么定3.1 为什么用 1D-CNN 而不是 LSTM 或全连接先回答选型。全连接网络MLP在 122 维表格数据上其实表现不差但参数量大、容易过拟合。LSTM 适合序列建模但 NSL-KDD 的特征没有严格的时间顺序122 维是手工特征拼接不是时间序列用 LSTM 属于杀鸡用牛刀训练还慢。1D-CNN 的优势是卷积核在特征维度上滑动能捕捉相邻特征间的局部模式比如 src_bytes 和 dst_bytes 的联合模式参数量比 MLP 少训练快适合这种「特征有局部相关性但无严格时序」的场景。常见做法是堆 2~3 层 Conv1D MaxPooling1D每层后面加 Dropout最后 GlobalAveragePooling 或 Flatten 接全连接分类头。下面是我常用的结构import tensorflow as tf from tensorflow.keras import layers, models def build_cnn(input_dim, num_classes5): model models.Sequential([ # 第一层卷积64 个卷积核核大小 3 layers.Conv1D(filters64, kernel_size3, paddingsame, activationrelu, input_shape(input_dim, 1)), layers.BatchNormalization(), layers.MaxPooling1D(pool_size2), layers.Dropout(0.3), # 第二层卷积128 个卷积核 layers.Conv1D(filters128, kernel_size3, paddingsame, activationrelu), layers.BatchNormalization(), layers.MaxPooling1D(pool_size2), layers.Dropout(0.3), # 第三层卷积64 个卷积核 layers.Conv1D(filters64, kernel_size3, paddingsame, activationrelu), layers.BatchNormalization(), layers.GlobalAveragePooling1D(), # 分类头 layers.Dense(64, activationrelu), layers.Dropout(0.4), layers.Dense(num_classes, activationsoftmax) ]) return model model build_cnn(input_dim122, num_classes5) model.summary()参数说明filters从 64 到 128 再到 64 是常见的「先升后降」结构升维是为了捕捉更多模式降维是为了压缩特征。kernel_size3是 1D 卷积的默认选择覆盖相邻 3 个特征再大容易过拟合。paddingsame保证卷积后长度不变方便后面池化对齐。BatchNormalization放在卷积后、激活前这里激活在卷积里所以放激活后能加速收敛。Dropout0.3~0.4 是经验值太高欠拟合太低过拟合。3.2 编译参数优化器、学习率、损失函数model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), losscategorical_crossentropy, metrics[accuracy] )Adam 1e-3 是起点。如果训练 loss 震荡降到 5e-4 或 1e-4。损失函数用 categorical_crossentropy 因为标签是 one-hot 且多分类。如果标签是整数用 sparse_categorical_crossentropy。评估指标别只看 accuracy后面会讲为什么。3.3 训练循环与回调EarlyStopping 和 ModelCheckpointcallbacks [ tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue), tf.keras.callbacks.ModelCheckpoint( best_cnn.h5, monitorval_loss, save_best_onlyTrue), tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-6) ] history model.fit( X_train_cnn, y_train_cat, validation_split0.2, batch_size256, epochs50, callbackscallbacks, verbose1 )batch_size256是显存和收敛速度的折中12 万条数据大概 500 个 batch 一轮。EarlyStopping的patience5表示验证 loss 连续 5 轮不降就停restore_best_weightsTrue保证拿到最优权重而不是最后一轮。ReduceLROnPlateau在 loss 停滞时自动降学习率比手动调省心。注意validation_split0.2是从训练集里切 20% 做验证不是测试集。测试集只在最后评估用一次别提前偷看。4. 评估与调优准确率 99% 为什么可能是假象4.1 类别不平衡下的评估陷阱NSL-KDD 训练集里 normal 约 6.7 万条dos 约 4.6 万条probe 约 1.2 万条r2l 约 1000 条u2r 只有 52 条。如果模型把所有样本都预测成 normal 或 dos准确率也能到 70% 以上。所以 accuracy 在这个数据集上参考价值有限必须看每个类别的 precision、recall、F1。from sklearn.metrics import classification_report, confusion_matrix y_pred model.predict(X_test_cnn) y_pred_label np.argmax(y_pred, axis1) print(classification_report(y_test, y_pred_label, target_names[normal,dos,probe,r2l,u2r])) print(confusion_matrix(y_test, y_pred_label))跑完你会看到 u2r 和 r2l 的 recall 通常很低因为样本太少。这是这类数据集的通病不是模型的问题。解决办法有对少数类过采样SMOTE、在 loss 里加 class_weight、或者用 focal loss。4.2 用 class_weight 缓解不平衡from sklearn.utils.class_weight import compute_class_weight class_weights compute_class_weight( class_weightbalanced, classesnp.unique(y_train), yy_train ) class_weight_dict dict(enumerate(class_weights)) print(class_weight_dict) # 训练时传入 history model.fit( X_train_cnn, y_train_cat, validation_split0.2, batch_size256, epochs50, callbackscallbacks, class_weightclass_weight_dict, verbose1 )compute_class_weight的balanced模式会按类别频率反比给权重u2r 的权重会远大于 normal。这样模型在训练时对少数类的错误惩罚更重recall 会提升但 normal 的 precision 可能略降。这是权衡不是 bug。4.3 超参数搜索网格搜索还是手动调我一般手动调因为训练一轮几分钟网格搜索太慢。重点调三个学习率1e-3 / 5e-4 / 1e-4、Dropout0.2 / 0.3 / 0.4、卷积核数量32/64/128。用 TensorBoard 看 loss 曲线最直观tensorboard_cb tf.keras.callbacks.TensorBoard(log_dir./logs, histogram_freq1) # 加进 callbacks 列表然后 tensorboard --logdir ./logs提示如果 val_loss 一直降但 val_accuracy 不升可能是过拟合加 Dropout 或减层数。如果两者都不降可能是学习率太大或数据有问题。5. 避坑与排查这份源码跑不起来时先看这 5 条5.1 报错 Unknown label type 或标签映射后出现 NaN现象train[label].map(attack_map)之后出现 NaN后续to_categorical报错。原因NSL-KDD 的 label 列里有些攻击类型名不在你的 attack_map 里比如snmpgetattack、httptunnel这些在 KDDTest 里才出现训练集里没有。解决先print(train[label].unique())和print(test[label].unique())把两边所有类型都列出来补全 attack_map。或者用train[label] train[label].map(attack_map).fillna(normal)兜底但这样会把未知攻击当正常不推荐。5.2 测试集准确率远低于训练集比如 99% vs 75%现象训练集 accuracy 99%测试集只有 75%。原因NSL-KDD 的训练集和测试集攻击类型分布不同测试集里有训练集没见过的攻击。这是数据集设计如此不是代码 bug。解决接受这个差距重点看测试集上各类的 recall。如果要做真实场景得用 CIC-IDS2017 这种时间切分的数据集重新验证。别为了刷测试集准确率去调参那是自欺欺人。5.3 OneHotEncoder 在测试集上维度对不上现象enc.transform(test[cat_cols])报错或输出维度与训练集不一致。原因测试集出现了训练集没有的 service 类别且handle_unknown没设成ignore。解决OneHotEncoder(sparse_outputFalse, handle_unknownignore)并且 fit 的时候用pd.concat([train[cat_cols], test[cat_cols]])。注意 sklearn 版本老版本是sparseFalse新版本是sparse_outputFalse。5.4 显存不足或训练极慢现象ResourceExhaustedError或一个 epoch 跑十几分钟。原因一次性把 12 万条数据读进内存并传给model.fit或者 batch_size 太小。解决用tf.data.Dataset做管道train_ds tf.data.Dataset.from_tensor_slices((X_train_cnn, y_train_cat)) train_ds train_ds.shuffle(10000).batch(256).prefetch(tf.data.AUTOTUNE) val_ds tf.data.Dataset.from_tensor_slices((X_val_cnn, y_val_cat)).batch(256) model.fit(train_ds, validation_dataval_ds, epochs50, callbackscallbacks)prefetch(tf.data.AUTOTUNE)让数据加载和 GPU 计算重叠速度能快一倍以上。5.5 模型保存后加载预测结果不一致现象model.save(best_cnn.h5)后重新加载预测结果和保存前不一样。原因Dropout 和 BatchNormalization 在训练和推理时行为不同如果加载后没设trainingFalse或者保存的是权重不是完整模型。解决用model.save(best_cnn.keras)保存完整模型Keras 新格式加载用tf.keras.models.load_model(best_cnn.keras)。预测时model.predict()自动切推理模式不用手动设。如果保存的是.h5确保save_format一致。6. 从 NSL-KDD 迁移到真实流量的进阶技巧把这份源码跑通只是起点。真正有价值的是把它迁移到 CIC-IDS2017 或你自己抓的流量上。CIC-IDS2017 有 78 维特征用 CICFlowMeter 从 pcap 提取包含 Flow Duration、Total Fwd Packets、Fwd Packet Length Mean 等统计量。迁移时要做三件事特征对齐把 78 维映射到模型输入维度、重新归一化用新数据集的 min/max、增量训练冻结卷积层只训分类头。# 迁移学习冻结前两层卷积只训后面 base_model tf.keras.models.load_model(best_cnn.keras) for layer in base_model.layers[:4]: layer.trainable False # 替换分类头适配新类别数 x base_model.layers[-3].output x layers.Dense(64, activationrelu)(x) x layers.Dropout(0.4)(x) new_output layers.Dense(7, activationsoftmax)(x) # CIC-IDS2017 有 7 类 new_model tf.keras.Model(inputsbase_model.input, outputsnew_output) new_model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-4), # 微调用小学习率 losscategorical_crossentropy, metrics[accuracy] )这里学习率降到 1e-4因为预训练权重已经不错大学习率会破坏。冻结层数看数据量数据少就多冻几层数据多就少冻几层。验证方法上我习惯用「时间切分」而不是随机切分用前 70% 时间的数据训练后 30% 测试。这样能暴露模型对新型攻击的泛化能力。随机切分会让同一攻击的样本同时出现在训练和测试集准确率虚高。最后一个技巧把模型输出概率做阈值调整。默认argmax取最大概率但在安全场景里漏报把攻击判成正常比误报代价高。可以把 normal 类的判定阈值从 0.5 提到 0.7即只有 normal 概率超过 0.7 才判正常否则判为可疑。这会增加误报但降低漏报。具体阈值用验证集上的 ROC 曲线定。我自己踩过最深的坑是拿着 NSL-KDD 上 99% 的模型去跑真实流量结果把大量正常流量判成攻击排查了两天才发现是归一化参数不匹配——训练集用 MinMaxScaler 的 min/max真实流量的特征范围完全不同导致输入分布偏移。后来养成习惯每次换数据集先画特征分布直方图对比再重新 fit scaler。这个习惯帮我省了无数后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表