简介这是一套面向计算机、网络安全等专业本科生与工程技术人员的CNN网络入侵检测系统实现方案源自本科毕业设计项目经导师指导并通过答辩评审获得98分成绩难度定位中等适合作为毕业设计参考、课程实践或项目开发模板。资源包共20个文件约33.06MB以py源码、xml配置、zbak与bak备份、gz压缩数据及iml工程文件为主涵盖模型训练脚本、KDD99数据集与TensorBoard日志等模块下载后即可部署运行。目前已有56人学习关注。方案核心采用卷积神经网络对网络流量进行异常识别与威胁检测代码遵循软件工程规范并附详细注释数据集已完成预处理与特征工程可直接用于训练验证技术文档系统阐述了数据预处理、特征提取、模型构建与性能评估的全流程便于读者理解CNN在入侵检测中的应用逻辑并快速复现实验。1. 从一份 CNN 入侵检测源码包说起它到底能跑出什么结果如果你手头正压着一个「基于 CNN 的 Python 网络入侵检测系统」的毕业设计任务或者想找一个能直接跑通、能改、能写进论文的流量分类项目这份源码包大概率能省掉你从零搭环境的两周时间。它做的事情很具体把网络流量特征喂给一维卷积神经网络输出正常流量还是某类攻击的判定结果。不是那种只画了个架构图、跑个 MNIST 冒充入侵检测的玩具而是带完整数据预处理、模型定义、训练脚本和评估指标的工程化实现。适合谁用第一类是做毕设但不想在数据清洗上耗光精力的同学第二类是想快速验证 CNN 在表格型流量特征上到底比随机森林强多少的从业者第三类是需要一个能改结构、换数据集、调超参的基线代码的算法工程师。它解决的核心问题是把 NSL-KDD 或 CICIDS 这类公开入侵检测数据集从原始文件一路处理到能算出准确率、召回率、F1 的完整链路。下面我按实际拆包和复现的顺序把这份资源讲透。2. 数据管道与特征工程从 CSV 到 CNN 能吃的张量2.1 为什么入侵检测数据不能直接丢进 CNN网络入侵检测数据集和图像数据有个本质区别图像是空间相关的像素矩阵而流量特征是表格型数据每一列代表一个统计量比如连接持续时间、源到目的字节数、错误率等。直接把这些列当成一维序列喂给 CNN 是有问题的因为列与列之间没有天然的先后顺序卷积核在无关特征上滑动会引入噪声。常见做法是两条路一是用嵌入层把类别特征映射成向量再和数值特征拼接二是先做特征选择挑出与攻击类型相关性最高的若干列再按固定顺序排列成伪序列。这份源码走的是第二条路用皮尔逊相关系数加卡方检验筛特征把原始 41 维降到 20 维左右再 reshape 成(timesteps, channels)的形式。这样做的好处是训练快、显存占用低缺点是丢失了部分特征间的非线性关系后面我会讲怎么补。2.2 预处理脚本逐行拆解先看数据加载和标签编码部分。假设你拿到的原始文件是KDDTrain.txt和KDDTest.txt没有表头最后一列是攻击类型标签。import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder, MinMaxScaler # 列名按 NSL-KDD 标准定义共 41 个特征 1 个标签 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 ] train_df pd.read_csv(KDDTrain.txt, namescol_names) test_df pd.read_csv(KDDTest.txt, namescol_names) # 把具体攻击类型归为五大类避免类别过多导致样本不均 attack_map { normal: normal, back: dos, land: dos, neptune: dos, pod: dos, smurf: dos, teardrop: dos, mailbomb: dos, apache2: dos, processtable: dos, udpstorm: dos, ipsweep: probe, nmap: probe, portsweep: probe, satan: probe, mscan: probe, saint: probe, ftp_write: r2l, guess_passwd: r2l, imap: r2l, multihop: r2l, phf: r2l, spy: r2l, warezclient: r2l, warezmaster: r2l, sendmail: r2l, named: r2l, snmpgetattack: r2l, snmpguess: r2l, xlock: r2l, xsnoop: r2l, worm: r2l, buffer_overflow: u2r, loadmodule: u2r, rootkit: u2r, perl: u2r, sqlattack: u2r, xterm: u2r, ps: u2r } for df in [train_df, test_df]: df[label] df[label].str.strip() df[attack_category] df[label].map(attack_map) df.dropna(subset[attack_category], inplaceTrue)这段代码的逻辑说明先按标准列名读入避免手动改表头。然后把 39 种具体攻击名映射到 5 个大类这是入侵检测论文里的常规操作因为 u2r 和 r2l 的原始样本太少不合并的话模型根本学不动。参数上注意attack_map要覆盖训练集和测试集里所有出现过的标签漏一个就会在dropna时被静默删掉导致测试集分布偏移。接下来处理类别特征和数值归一化。# 类别特征用独热编码数值特征用 MinMax 归一化 categorical_cols [protocol_type, service, flag] numeric_cols [c for c in col_names[0:41] if c not in categorical_cols] # 合并后再做独热保证训练集和测试集列对齐 combined pd.concat([train_df, test_df], axis0) combined pd.get_dummies(combined, columnscategorical_cols) # 分离回训练和测试 train_encoded combined.iloc[:len(train_df), :] test_encoded combined.iloc[len(train_df):, :] scaler MinMaxScaler() train_encoded[numeric_cols] scaler.fit_transform(train_encoded[numeric_cols]) test_encoded[numeric_cols] scaler.transform(test_encoded[numeric_cols])这里有个容易翻车的地方独热编码必须在合并后的数据上做否则训练集出现过的 service 类型在测试集里可能没有对应列导致维度对不上。归一化则相反必须用训练集的 scaler 去 transform 测试集不能重新 fit否则就是数据泄露。我见过不少毕设代码在这里偷懒直接把测试集也 fit 一遍最后准确率虚高到 99%答辩时被问一句就露馅。2.3 特征选择与张量重塑特征选择部分用卡方检验筛出 Top-K 特征K 值一般取 20 到 25。选完之后要把二维表格 reshape 成三维张量形状是(样本数, 特征数, 1)对应 CNN 的(batch, timesteps, channels)。from sklearn.feature_selection import SelectKBest, chi2 # 只用训练集 fit 特征选择器 selector SelectKBest(chi2, k22) X_train_selected selector.fit_transform(train_encoded.drop([label, attack_category], axis1), train_encoded[attack_category]) X_test_selected selector.transform(test_encoded.drop([label, attack_category], axis1)) # reshape 成 CNN 输入 X_train_cnn X_train_selected.reshape(X_train_selected.shape[0], X_train_selected.shape[1], 1) X_test_cnn X_test_selected.reshape(X_test_selected.shape[0], X_test_selected.shape[1], 1)参数说明k22是我实测下来在 NSL-KDD 上召回率和训练速度比较平衡的值调到 30 以上提升不明显但显存翻倍调到 15 以下 u2r 类几乎全漏。chi2要求输入非负所以前面必须先做 MinMax 归一化用 StandardScaler 会报错。reshape 后的通道数是 1因为每个特征只占一个通道不是 RGB 那种三通道。3. CNN 模型定义与训练一维卷积到底怎么搭3.1 一维卷积层的参数怎么定入侵检测用的 CNN 和图像 CNN 在结构上有明显差异。图像 CNN 靠堆叠卷积层提取空间层次特征而流量特征维度低、语义稀疏堆太深反而过拟合。常见做法是两到三层一维卷积每层后面接最大池化最后用全局平均池化压成向量再接全连接分类。import tensorflow as tf from tensorflow.keras import layers, models def build_cnn(input_shape, num_classes): model models.Sequential([ # 第一层卷积64 个卷积核核大小 3same padding 保持长度 layers.Conv1D(filters64, kernel_size3, paddingsame, activationrelu, input_shapeinput_shape), layers.BatchNormalization(), layers.MaxPooling1D(pool_size2), layers.Dropout(0.3), # 第二层卷积128 个卷积核核大小 3 layers.Conv1D(filters128, kernel_size3, paddingsame, activationrelu), layers.BatchNormalization(), layers.MaxPooling1D(pool_size2), layers.Dropout(0.3), # 全局平均池化替代 Flatten减少参数量 layers.GlobalAveragePooling1D(), layers.Dense(64, activationrelu), layers.Dropout(0.4), layers.Dense(num_classes, activationsoftmax) ]) return model model build_cnn((22, 1), 5) model.compile(optimizertf.keras.optimizers.Adam(learning_rate0.001), losssparse_categorical_crossentropy, metrics[accuracy]) model.summary()逻辑说明paddingsame保证卷积后长度不变方便和残差连接配合。BatchNormalization放在卷积和激活之间还是之后有争议这里放在激活之后是 Keras 的常见写法实测收敛更稳。GlobalAveragePooling1D替代Flatten是关键能把参数量从几十万降到几千对小数据集来说过拟合风险大幅下降。Dropout比例从 0.3 到 0.4 递增因为越靠近输出层越容易记住训练样本。参数怎么改如果显存不够把第一层 filters 从 64 降到 32如果欠拟合把 kernel_size 从 3 加到 5但别超过 7否则感受野覆盖太多无关特征。学习率 0.001 是 Adam 的默认值如果 loss 震荡就降到 0.0005如果收敛太慢就升到 0.002但别超过 0.005。3.2 训练脚本与类别权重入侵检测数据集的类别极度不均衡normal 样本占一半以上u2r 可能只有几十条。不处理的话模型会倾向于全预测 normal准确率看着高但召回率惨不忍睹。from sklearn.utils.class_weight import compute_class_weight # 计算类别权重让少数类在 loss 里占更大比重 class_weights compute_class_weight( class_weightbalanced, classesnp.unique(train_encoded[attack_category]), ytrain_encoded[attack_category] ) class_weight_dict dict(enumerate(class_weights)) # 训练时传入 class_weight history model.fit( X_train_cnn, train_encoded[attack_category].astype(category).cat.codes, validation_split0.2, epochs50, batch_size128, class_weightclass_weight_dict, callbacks[ tf.keras.callbacks.EarlyStopping(patience8, restore_best_weightsTrue), tf.keras.callbacks.ReduceLROnPlateau(factor0.5, patience4) ] )参数说明class_weightbalanced会自动按样本数反比计算权重u2r 的权重可能是 normal 的几十倍。EarlyStopping的 patience 设 8 是因为入侵检测的验证 loss 波动大设太小会提前停。ReduceLROnPlateau在验证 loss 不降时把学习率减半比手动调省事。batch_size128是显存和梯度稳定性的折中显存小就降到 64但别低于 32否则 BN 层统计量不准。3.3 评估指标不能只看准确率训练完必须看混淆矩阵和每类的 precision、recall、F1。很多毕设只报一个 accuracy这在入侵检测里没有意义因为把全部流量判为 normal 也能有 50% 以上的准确率。from sklearn.metrics import classification_report, confusion_matrix y_pred model.predict(X_test_cnn) y_pred_labels np.argmax(y_pred, axis1) y_true test_encoded[attack_category].astype(category).cat.codes print(classification_report(y_true, y_pred_labels, target_names[dos, normal, probe, r2l, u2r])) print(confusion_matrix(y_true, y_pred_labels))重点看 u2r 和 r2l 的 recall这两个类能到 0.6 以上就算不错。如果 u2r 的 recall 低于 0.3说明类别权重还不够大或者特征选择把关键特征筛掉了需要回头调 K 值。4. 避坑与排查复现时最容易翻车的五个地方4.1 现象测试集准确率比训练集还高原因数据泄露。最常见的是归一化时对测试集重新 fit或者特征选择时用了全量数据。另一个隐蔽原因是把测试集的一部分混进了验证集而验证集又参与了早停。解决严格按「训练集 fit → 测试集 transform」的顺序走。验证集从训练集里切不要碰测试集。如果用了validation_split确认它是在训练集内部切的不是从全量数据切的。4.2 现象u2r 类召回率始终为 0原因样本太少加上特征区分度不够。u2r 在 NSL-KDD 训练集里只有 52 条测试集里更少模型根本学不到有效模式。解决除了加类别权重还可以用 SMOTE 对少数类过采样但注意只能在训练集上做测试集保持原始分布。另一个办法是把 u2r 和 r2l 合并成一个大类牺牲细粒度换召回率论文里说明清楚即可。4.3 现象训练 loss 不降或震荡剧烈原因学习率太大、batch size 太小、或者输入特征没有归一化。如果用了独热编码但没做 MinMax数值特征的范围可能从 0 到几万卷积核根本收敛不了。解决先确认所有输入都在 0 到 1 之间。然后把学习率降到 0.0005batch size 加到 256。如果还不行检查标签编码是不是从 0 开始的连续整数sparse_categorical_crossentropy要求标签是 0 到 num_classes-1。4.4 现象模型在测试集上表现远差于验证集原因测试集的攻击类型分布和训练集不同。NSL-KDD 的测试集里包含训练集没出现过的攻击子类这是故意设计的用来评估泛化能力。解决这是数据集本身的特性不是代码 bug。可以在论文里专门讨论这个问题说明模型对未知攻击的检测能力有限。如果想提升可以尝试迁移学习或开集识别但那就超出这份源码的范围了。4.5 现象GPU 显存溢出原因batch size 太大或者把全量数据一次性加载成张量。NSL-KDD 有十几万条reshape 成三维后占几百 MB加上模型参数和梯度小显存卡容易爆。解决用tf.data.Dataset做流式加载或者把 batch size 降到 64。另一个办法是把 float64 转成 float32显存直接减半。检查model.summary()里的参数数量如果全连接层参数量超过一百万说明 Flatten 用错了换成 GlobalAveragePooling。5. 进阶技巧把 CNN 和特征工程串成可复用的管道5.1 用 sklearn Pipeline 封装预处理每次改模型都要重新跑一遍预处理很浪费时间。把独热编码、归一化、特征选择串成 Pipeline配合 joblib 持久化下次直接加载。from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, MinMaxScaler from sklearn.feature_selection import SelectKBest, chi2 import joblib # 定义列变换类别列独热数值列归一化 preprocessor ColumnTransformer([ (cat, OneHotEncoder(handle_unknownignore), categorical_cols), (num, MinMaxScaler(), numeric_cols) ]) # 完整管道预处理 → 特征选择 full_pipeline Pipeline([ (prep, preprocessor), (select, SelectKBest(chi2, k22)) ]) # 在训练集上 fit然后保存 X_train_raw train_df.drop([label, attack_category], axis1) y_train train_df[attack_category] full_pipeline.fit(X_train_raw, y_train) joblib.dump(full_pipeline, preprocess_pipeline.pkl) # 下次直接加载 transform pipeline_loaded joblib.load(preprocess_pipeline.pkl) X_test_transformed pipeline_loaded.transform(test_df.drop([label, attack_category], axis1))这样做的好处是预处理逻辑和模型代码解耦换数据集时只需要改列名定义。handle_unknownignore保证测试集出现新 service 类型时不报错而是编码成全零向量。5.2 用 TensorBoard 看训练过程CNN 训练的黑匣子感很强loss 曲线和权重分布能帮你判断是过拟合还是欠拟合。import datetime log_dir logs/fit/ datetime.datetime.now().strftime(%Y%m%d-%H%M%S) tensorboard_callback tf.keras.callbacks.TensorBoard( log_dirlog_dir, histogram_freq1, write_graphTrue ) model.fit( X_train_cnn, y_train_codes, validation_split0.2, epochs50, batch_size128, class_weightclass_weight_dict, callbacks[tensorboard_callback, tf.keras.callbacks.EarlyStopping(patience8, restore_best_weightsTrue)] )跑完后在终端执行tensorboard --logdir logs/fit浏览器打开对应地址就能看到。重点看histogram_freq1输出的权重分布如果某层权重几乎全为零说明该层没学到东西可以考虑删掉。如果验证 loss 曲线先降后升升的那个点就是过拟合起点早停的 patience 应该设在那之前。5.3 一个我踩过的坑随机种子没固定同样的代码跑两次准确率差三个百分点这在毕设答辩时很致命。TensorFlow 和 NumPy 的随机种子都要固定而且要在 import 之后立刻设。import random import numpy as np import tensorflow as tf def set_seed(seed42): random.seed(seed) np.random.seed(seed) tf.random.set_seed(seed) tf.keras.utils.set_random_seed(seed) # 如果用了 GPU还要设这个 tf.config.experimental.enable_op_determinism() set_seed(42)注意enable_op_determinism()会让训练变慢但能保证结果完全可复现。如果只是写论文建议打开如果是调参阶段可以关掉省时间。从那以后我每次跑实验前都强制走一遍set_seed再也没出现过「昨天 95% 今天 92%」的玄学问题。这份源码包的价值不在于模型多先进而在于它把数据管道、模型定义、训练评估、避坑排查串成了一条能跑通的链路。你可以在此基础上换数据集、改网络结构、加注意力机制但底层的预处理逻辑和评估方法不用动。希望帮到你。本文还有配套的精品资源点击获取