
简介基于卷积神经网络实现网络入侵检测的完整项目代码包面向希望掌握深度学习与网络安全结合应用的小白和进阶学习者适合用于毕业设计、课程设计或工程实训。包内提供数据预处理脚本、一层全连接层对照代码和CNN主程序配套KDD Cup数据集压缩包及TensorBoard训练日志可复现99.5%的检测正确率。资源共16个文件以Python脚本、gzip压缩数据集、项目配置XML和说明文档为主整体大小约17.52MB目录结构清晰。目前已有105人学习下载。通过阅读源码和训练日志读者能理解CNN处理网络流量的思路对比全连接层与卷积神经网络的性能差异并根据准确率与损失曲线进行参数调整是快速上手入侵检测项目的实用资料包内还附工程配置与使用说明便于直接导入项目并按需调整。1. 网络入侵检测搭上卷积神经网络为什么流量识别也要“看图”网络入侵检测最麻烦的不是抓不到流量而是不知道“正常”长什么样。规则引擎靠专家逐条写特征遇到慢速扫描、低频爆破这类不刷屏的攻击经常漏传统机器学习方案又极度依赖特征工程的质量。基于卷积神经网络的网络入侵检测走的是另一条路不逐条定义攻击模式而是把网络连接的特征向量重排成类似图像的二维矩阵让卷积神经网络CNN自动抽取局部规律。在NSL-KDD这类公开基准集上二分类正确率达到99.5%并不算夸张真正的问题在于怎么复现它、怎么看这个数字、以及上线时怎么让它不翻车。这篇笔记围绕数据编码、模型搭建、参数设置和部署踩坑展开适合流量分析、安全网关和IDS产品验证方向的人照着自己跑一遍。2. 流量数据先变成张量NSL-KDD选型、特征编码与二维重构CNN吃进去的是矩阵不是pcap文件所以在谈模型之前得先把“网络连接”这种结构化记录变成能放进卷积层的数字张量。做入侵检测的团队经常忽略这一步结果模型结构再漂亮也救不回错误的数据表示。2.1 数据集怎么选NSL-KDD、UNSW-NB15和CICIDS 2017数据集决定了你的实验能跑多快、结论能信多少。我一般先看NSL-KDD训练集约12.5万条、测试集约2.2万条每条样本41维特征标签分Normal和4类攻击DoS、Probe、R2L、U2R。它去掉了KDD Cup 99里大量冗余重复记录类别分布相对合理整轮训练在普通GPU上几分钟完成适合先验证网络结构和参数。UNSW-NB15是比NSL-KDD更新一点的选项49维特征、9类攻击攻击面更贴近现代网络但样本量达到百万级预处理和训练都更慢。CICIDS 2017则是从原始PCAP重放出来的数据集特征超过80维最接近真实会话流但需要自己决定怎么从抓包中抽特征工程量明显更大。做第一版模型验证NSL-KDD性价比最高。数据集样本规模特征数攻击类别适合阶段NSL-KDD训练约12.5万测试约2.2万414类加正常模型选型、参数迭代UNSW-NB15百万级499类加正常跨数据集泛化验证CICIDS 2017百万级80以上14类加正常真实场景模拟、端到端验证选好数据集后下一个问题是这些数据集里的特征列CNN不能直接读。卷积神经网络原理上是在二维网格里找局部模式把协议类型、连接时长、窗口统计这些字段按某种顺序排成矩阵卷积核才能学到“相邻位置特征之间的组合关系”这正是CNN相比全连接网络的一个优势——特征的空间排布天然带有抽象语义。2.2 41维特征怎么编码成11x11矩阵NSL-KDD的41维特征里有3个是离散的protocol_type协议类型、service目标服务、flag连接状态。剩下38个是连续数值比如duration、src_bytes、dst_bytes以及大量基于时间窗口的统计特征像是count、serror_rate、dst_host_srv_count。离散字段不能直接当数值用——把tcp编码成1、udp编码成2模型会莫名其妙学到“udp大于tcp”这种不存在的序关系。常见做法是独热编码展开。protocol_type有3种取值、service约70种、flag约11种三个离散字段展开后新增81个维度加上38个连续特征总维度变成122。图像格式一般选单通道灰度图所以需要把122维向量重排成二维矩阵。12x10等于120维度还缺2个11x11等于121维度缺1个补零是最省事的方案也有做法直接截断到120维但我倾向补零保留完整信息量卷积核自己会学到边缘补零位置不重要。2.3 reshape策略特征排列顺序影响感受野特征重排成矩阵后排列顺序不能随便来。卷积神经网络结构图里最直观的一点是3x3卷积核每次只看见相邻的9个格子。如果相邻位置放的是语义上完全无关的特征卷积核学到的“局部模式”就没有物理含义模型准确率会有波动而且这种波动在调参阶段让你分不清是结构问题还是排列问题。经验做法是把同一来源或同一时段的统计特征放在相邻位置。比如count、srv_count、serror_rate、srv_serror_rate这四个都描述“最近一段时间内连接行为”把它们排在一起卷积核更容易捕捉到“同一时段的连接数异常且错误率高”这种攻击模式。离散特征展开后的独热位放到矩阵边缘连续数值特征集中在中间区域。下面这个预处理脚本可以直接套到NSL-KDD上import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler # NSL-KDD 的41列特征名按原始顺序 FEATURES [ 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 ] def load_and_encode(train_df, test_df): # 合并train/test做独热展开保证两边列集合完全一致 all_x pd.concat([train_df.drop(label, axis1), test_df.drop(label, axis1)], axis0) encoded pd.get_dummies(all_x, columns[protocol_type, service, flag]) train_enc encoded.iloc[:len(train_df)].reset_index(dropTrue) test_enc encoded.iloc[len(train_df):].reset_index(dropTrue) # 重点scaler只fit训练集测试集只做transform避免信息泄漏 scaler StandardScaler().fit(train_enc) X_train scaler.transform(train_enc).astype(float32) X_test scaler.transform(test_enc).astype(float32) # 122维补1个零到121维再reshape成11x11x1 def to_image(X): n X.shape[0] X_pad np.pad(X, ((0, 0), (0, 1)), modeconstant) return X_pad.reshape(n, 11, 11, 1) return to_image(X_train), to_image(X_test)逻辑说明先合并train/test做独热展开是为了避免service字段在训练集和测试集中取值集合不一致导致两边的列数对不上StandardScaler用fit_transform和transform分开调用是防止测试集的均值方差信息提前混进特征最后np.pad在第122个位置补0凑成121维再reshape成11x11的单通道灰度图。参数说明缩放方式选了StandardScaler而不是MinMaxScaler是因为网络流量统计特征有长尾分布标准化后梯度更稳定。如果你用的是UNSW-NB15这类49维数据集需要重新算独热展开后的维度再调整reshape尺寸比如补零到9x9或12x12都可行原则是让语义相关的特征落在相邻格子里。3. CNN入侵检测模型怎么搭卷积核、池化与三个关键参数数据变成张量后模型部分反而简单。入侵检测是数据量相对小的分类任务不需要套ResNet那种上百层的深度网络两到三个卷积块加一个全连接层就能跑出很高的基准。真正拉开差距的是卷积核数量的设置、Dropout的位置、以及训练时的早停策略。3.1 基础结构两层卷积块加全连接为什么不用深网络我常用的CNN入侵检测结构长这样输入一个11x11x1的灰度图第一个卷积块包含32个3x3卷积核卷积后接BatchNorm和ReLU再做一次2x2最大池化第二个卷积块换成64个3x3卷积核同样接BatchNorm、ReLU和池化然后Flatten压平接一个128节点的全连接层Dropout比例0.5最后输出层按二分类或五分类接softmax。第一层32个卷积核学习的是基础流量模式比如连接数窗口统计、协议类型组合、错误率局部异常第二层64个卷积核在更高层把前面学到的模式组合起来对应“某个目标主机在时间窗口内出现大量不同源端口连接”这类攻击语义。MaxPooling把11x11降到5x5再降到2x2既降了计算量又保留了激活最强的特征。为什么不用更深的网络NSL-KDD只有十几万训练样本深层模型参数量上去了过拟合很快而且加深一两层带来的准确率提升通常不到0.5%不值得引入额外的调参和训练成本。import tensorflow as tf from tensorflow.keras import layers, models def build_cnn(num_classes2): model models.Sequential([ layers.Input(shape(11, 11, 1)), # 卷积块1学基础流量局部模式 layers.Conv2D(32, 3, paddingsame, activationrelu), layers.BatchNormalization(), layers.MaxPooling2D(pool_size2), # 卷积块2在基础模式之上组合攻击语义 layers.Conv2D(64, 3, paddingsame, activationrelu), layers.BatchNormalization(), layers.MaxPooling2D(pool_size2), layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(num_classes, activationsoftmax) ]) return model逻辑说明Conv2D用3x3卷积核、paddingsame保证特征图边缘信息不提前丢失BatchNorm放在卷积层后面、激活函数前面对网络流量这种分布变化大的输入能显著稳定训练过程Dropout放在全连接层前是CNN里对抗过拟合最有效的常规位置卷积层本身有参数共享和池化做正则不需要额外加Dropout。参数说明第一层卷积核32个、第二层64个是稳妥起步。如果验证集准确率上不去可以试着提到64/128但参数量会翻倍在小数据集上收益有限。激活函数统一用ReLU二分类输出层用softmax而不是sigmoid这样后面分析概率阈值时更方便。3.2 三个必调参数卷积核数量、Dropout比例、学习率CNN入侵检测模型跑起来后真正影响落地效果的参数就三个。第一个是卷积核数量32/64起步模型欠拟合时加一倍但不要反复加——加到128以上在NSL-KDD上几乎看不到提升只增加了训练时间。第二个是Dropout比例全连接层前我固定0.5如果你发现验证集损失和训练集损失差得很大把Dropout提到0.6试试。第三个是学习率Adam优化器默认0.001基本不用改收敛太慢时降到0.0003比直接改动网络结构更有效。这三个参数调到合理范围后别急着继续调先看数据有没有泄漏、类别是不是不平衡那才是让准确率虚高的主因。3.3 早停与学习率衰减让训练自己停下来early_stop tf.keras.callbacks.EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) reduce_lr tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-6 )逻辑说明EarlyStopping监控验证集损失连续10轮不下降就停止训练restore_best_weights会回滚到验证集损失最低时的权重这个回滚很重要否则保存下来的可能是过拟合后的模型。ReduceLROnPlateau在验证损失连续5轮不下降时把学习率减半用来跨过局部平稳区域min_lr1e-6防止学习率衰减到完全学不动。参数说明epoch设置50就够配合上面两个回调实际训练通常20到30轮就停了。patience设太小容易在准确率还没爬上去时就早停设太大浪费时间10和5是我在NSL-KDD上比较稳的组合。4. 复现99.5%的完整训练流程数据加载、训练配置与指标评估结构定了参数定了接下来就是端到端训练。这一章要解决一个核心问题在什么时候、用什么配置才能稳定复现出99.5%这个数字以及这个数字到底该怎么读。4.1 训练配置固定随机种子和分层切分复现实验结果最怕的是每次跑出来的准确率忽高忽低。差异主要来自两个地方模型初始化时卷积核的随机权重、以及训练/验证切分时的样本分布。解决办法是固定随机种子并且按类别比例分层切分。import numpy as np import tensorflow as tf from sklearn.model_selection import train_test_split from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau np.random.seed(42) tf.random.set_seed(42) # 二分类标签normal为0所有攻击合并为1 y_train_bin (train_df[label] ! normal).astype(int) y_test_bin (test_df[label] ! normal).astype(int) # 如果不用官方测试集按分层留10%做验证集 X_tr, X_val, y_tr, y_val train_test_split( X_train_data, y_train_bin, test_size0.1, stratifyy_train_bin, random_state42 ) model build_cnn(num_classes2) model.compile( optimizertf.keras.optimizers.Adam(0.001), losssparse_categorical_crossentropy, metrics[accuracy] ) history model.fit( X_tr, y_tr, validation_data(X_val, y_val), batch_size64, epochs50, callbacks[ EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-6) ], verbose1 ) test_loss, test_acc model.evaluate(X_test_data, y_test_bin, verbose0) print(ftest acc: {test_acc:.4f})逻辑说明random_state42和tf.random.set_seed(42)要在数据切分和模型构建之前完成否则模型初始化顺序不同结果仍会有细微差异。stratifyy_train_bin保证切出来的验证集里normal和attack比例和原始训练集一致避免验证集里全是normal导致评估失真。参数说明batch_size64在12万条样本上迭代速度合适想更稳可以降到32损失函数用sparse_categorical_crossentropy配合整数标签不需要手动做one-hot。评估时直接拿官方测试集跑这样和别人论文里的结果有可比性。4.2 正确率99.5%是怎么来的混淆矩阵和分类报告比一个数字诚实正确率本身是个很有迷惑性的指标。如果测试集里正常流量占大多数把样本全部判成正常正确率也会有九成以上。所以在宣称“正确率99.5%”之前一定要把混淆矩阵和分类报告打出来看一眼。from sklearn.metrics import confusion_matrix, classification_report y_pred np.argmax(model.predict(X_test_data), axis1) print(confusion_matrix(y_test_bin, y_pred)) # 输出格式[[TN, FP], [FN, TP]] print(classification_report(y_test_bin, y_pred, target_names[normal, attack], digits4))逻辑说明混淆矩阵的四个格子能直接看出模型是偏向把正常流量误判成攻击FP高还是把攻击漏成正常FN高。分类报告里的precision和recall分别反映“报出来的攻击里有多少是真的”和“真正的攻击里有多少被抓住”这两个数是上线前最该盯住的指标。参数说明digits4让指标保留四位小数方便对比实验间的细微差异。在NSL-KDD二分类任务里模型通常能到0.99以上的precision和recall这也是99.5%这个数字的主要来源。4.3 二分类和五分类的路径差异attention与class_weight二分类是“攻击还是正常”五分类则需要区分DoS、Probe、R2L、U2R这些攻击类型。五分类难度高不少因为R2L和U2R在NSL-KDD里的样本量非常少U2R整个训练集才几十条模型很容易把它们学成噪声。遇到这种情况class_weight比改网络结构更直接from sklearn.utils.class_weight import compute_class_weight classes np.unique(y_train) weights compute_class_weight(balanced, classesclasses, yy_train) class_weight dict(zip(classes, weights)) model.fit( X_tr, y_tr, validation_data(X_val, y_val), class_weightclass_weight, batch_size64, epochs50, callbacks[...], verbose1 )逻辑说明compute_class_weight会按样本量反比给每个类别分配权重稀有类权重高在损失函数里相当于多给几条样本让模型不直接把它们忽略掉。加了class_weight后R2L和U2R的recall能改善不少但整体正确率通常会微降这是正常现象——你是在拿一点点整体准确率换稀有类的检出率。5. 高正确率背后的避坑清单数据泄漏、类别不平衡与指标幻觉99.5%的正确率看起来漂亮实际踩坑时会发现这个数字有一大半是数据分布给的不是模型多聪明。这一章写清我在复现过程中遇到的5个典型问题每个都按现象、原因、解决来拆。5.1 归一化泄漏为什么你复现出来的结果比论文还好现象训练集准确率99.9%测试集准确率99.5%怎么看都漂亮但你隐约觉得哪里不对——换了一组真实流量数据后准确率直接掉到85%。原因做特征缩放时用StandardScaler对train和test合并后的全部数据做了fit_transform。测试集的均值和方差提前混进了特征缩放统计量模型在测试时已经“见过”了测试集的分布信息这不是真实的泛化能力。解决严格按本文2.2的写法scaler只fit在训练集上测试集单独调用transform。更严格一点连独热编码的列集合都只从训练集导出。可以用一个简单方式自查在训练脚本里打印scaler.mean_的长度和训练集特征数如果对不上说明编码阶段的数据范围就不对。5.2 类别不平衡U2R和R2L的recall为什么可能是0现象整体正确率95%但打开分类报告U2R那一行的recall是0.00R2L只有0.12。所有U2R样本全部被预测成normal或DoS。原因NSL-KDD里U2R样本太少模型在训练时几乎没见过这个类别梯度更新被majority class主导。正确率是高了但模型实际上是个偏科生。解决先看分类报告不要只看acc。然后按4.3加class_weight或者对稀有类做SMOTE过采样。如果业务必须检出U2R还可以把问题重新组织成“正常vs四类攻击”的单分类器把稀有类检出的优先级提到准确率之前。5.3 切分数据时泄漏了同源会话现象随机切分一个训练/测试集后准确率极高换成按时间切分同一个模型掉3到5个点。原因NSL-KDD里存在来自同一个网络会话的多个流量记录随机切分会把同源记录同时分到训练集和测试集相当于测试集里出现了训练样本的近亲副本。解决尽量使用官方预设的KDDTrain和KDDTest划分不要自己随机切分。如果要自己切先按源IP或会话ID分组再对组做切分避免同源记录跨集合。5.4 固定了seed但换机器结果还是变现象同一份代码在同一台机器上跑结果稳定。换一台机器或换一个TensorFlow版本准确率掉了1到2个百分点。原因GPU算子浮点累加顺序、TF底层算子调度、cuDNN算法选择都会引入非确定性。seed只能控制Python层级的随机数控制不了底层并行计算的微小差异。解决记录训练环境的TensorFlow版本、CUDA版本和GPU型号。如果项目对复现要求高用CPU训练或开启TF的确定性算子开关代价是训练慢一些。反正NSL-KDD数据集小CPU跑也就几分钟完全能接受。5.5 指标幻觉准确率99.5%上线后误报刷屏现象基准集上一切正常接入真实网关后正常业务流量被频繁判为攻击安全运营一天收到上千条告警。原因真实网络流量的特征分布和NSL-KDD差别很大。基准集里的攻击流量是结构化样本真实流量里大量P2P传输、视频流、长连接握手和内部扫描工具的特征在模型眼里都和“异常”很像。正确率在两个分布下含义完全不同。解决上线前用真实业务流量回放计算误报率而不是正确率。误报率的定义是“正常流量被判为攻击的比例”按这个指标调阈值或加规则。另外线上模型最好从二分类降级到多分类只对明确攻击类型告警模糊预测不告警。6. 从实验台挪到网关部署CNN入侵检测器的延迟与误报处理模型在基准集上跑通只是第一步。把CNN入侵检测器接入真实链路时要处理模型导出、在线推理延迟、误报压制三个问题。这一章讲我最常用的部署做法。6.1 模型导出与推理管线的最小形态# 保存Keras模型供后续加载推理 model.save(cnn_nids_model.keras)在线推理管线一般长这样实时抓包后按会话聚合连接记录提取NSL-KDD同款41维特征加载训练时保存的scaler做transform补零后reshape成11x11x1送入模型拿到softmax概率。单条样本推理在CPU上是毫秒级瓶颈通常在会话聚合的等待时间。如果每秒要处理上千条连接把推理改成batch模式跑吞吐会高很多。6.2 误报压制滑动窗口多数投票和阈值调整单条流量判异常并不等于告警。我上线时加了两层保护第一层取最近5个会话的预测概率做滑动窗口平均超过告警阈值才触发第二层对新告警做同源IP聚合短时间内同一源IP的多次告警合并成一条事件避免告警风暴。阈值一般不用默认的0.5可以按验证集P/R曲线选一个偏保守的值比如0.7让告警更精确。6.3 上线前的一个验证习惯我现在的习惯是无论在基准集上跑出多高的正确率正式上线前都会拿真实流量pcap回放一遍统计误报率和单条延迟。98%以上的准确率只是入场券能把误报率摁在业务能接受的范围内才算真的落地。如果你正准备把这个方向带到生产环境建议从NSL-KDD小步验证再逐步换成UNSW-NB15和真实流量一路把阈值、窗口和告警策略都磨一遍。希望帮到你。本文还有配套的精品资源点击获取