ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

并行CNN网络在KDD99网络攻击检测中的复现与部署实践

并行CNN网络在KDD99网络攻击检测中的复现与部署实践 简介这是一篇发表于《太原师范学院学报自然科学版》的学术论文面向网络安全、数据分析与深度学习交叉领域的研究者和从业者聚焦传统网络攻击检测中特征提取能力不足、误报率偏高的问题。文中提出一种基于并行卷积神经网络的在线攻击检测方法包含CNN1和CNN2两个特征提取分支通过全连接层融合并利用Softmax分类识别攻击类型并以KDD Cup99数据集完成训练与验证。资源包内仅有1个PDF文件压缩包大小为3.12MB完整保留了论文正文、卷积网络结构图与实验结果等内容。目前已有569人学习下载常被用作深度学习与网络安全相关课题的参考文献或专业指导材料。读者通过该PDF可系统了解经典卷积网络模型、并行网络参数设置、训练流程及与已有方法的对比结论有助于快速把握该类检测技术的研究脉络和实现细节。1. 并行卷积网络在网络攻击检测里的复现价值如果手里只有一批 KDD Cup99 流量记录想跑出一个能区分 DoS、Probe、R2L、U2R 和正常会话的实时检测模型多数人第一反应是上 XGBoost 或者 MLP。但这篇 2018 年的论文给出的是一个更“硬核”的思路把 41 维流量特征映射成图片后用两个卷积神经网络并行提取特征再拼接全连接层分类。深度学习 cnn 在图像任务里能自动提特征在流量分析里同样能通过灰度图把特征工程省掉一部分尤其是 DoS 和 Probe 这类在空间分布上有明显模式的攻击CNN 的平移不变性比传统机器学习模型更占优势。文中 CNN1 和 CNN2 两个分支的图像尺寸、卷积核配置完全不同一个细粒度、一个粗粒度效果是准确率最高到 98.8%误报率压到 0.6%。适合读这篇的人有两类一类是想复现经典流量检测论文的研究生另一类是想把数据集和代码快速拼到公司安全平台上的工程师。笔记会按“流量怎么变成图 → 双分支网络怎么搭 → 训练与评估细节 → 复现时最容易翻车的点 → 在线检测怎么落地”的顺序拆中间给出 TensorFlow 直接能跑的代码和参数表。不看原文照着这份梳理也能把网络结构搭出来。2. 从 KDD99 原始记录到灰度图预处理决定网络上限2.1 41 维特征向量不适合直接进卷积层网络攻击检测里最经典的做法是拿 KDD Cup99 的每条记录做二分类或五分类特征包括协议类型、服务类型、连接时长、源字节数、登录失败次数、root 权限操作次数等。传统机器学习模型逻辑回归、SVM、浅层神经网络直接吃 41 维特征就能跑但把卷积网络搬进来后有一个结构性问题卷积运算期望输入有局部空间关系比如相邻像素的纹理边界而 KDD99 的特征向量按“基本特征-内容特征-流量特征-主机统计”排列属性之间没有天然的二维空间结构。常见的处理思路是把一维向量按固定长度重排成二维矩阵再把每个数值映射成像素灰度值。原文对数据做“属性映射、数据归一化和数据转换”然后用两种分辨率喂给两个网络。数据归一化这一步最容易被跳过但也是影响收敛速度的关键。特征属性里有连续值、离散值和极不均匀的数值范围不归一化直接映射灰度图会整体偏向暗色卷积核学不到对比度信息。protocol_type tcp - 0, udp - 1, icmp - 2 service http - 0, ftp - 1, smtp - 2, ... flag SF - 0, REJ - 1, S0 - 2, ...离散属性做编号映射连续属性按最大最小值缩放到 [0, 255] 区间。41 维特征加 1 维类别标签取前 40 个特征列重排成 5x8 或 8x5 的矩阵再补零到论文说的 64x64 和 160x96。这里必须说明原论文没有公开完整的转换代码我按 2018 年前后卷积网络处理一维信号最常见的做法补一段。import numpy as np from sklearn.preprocessing import MinMaxScaler def kdd99_to_grayscale(features, width64, height64): features: shape (sample_count, 41)最后一列是标签 返回的 image 是 64x64 的单通道灰度图 data features[:, :41].astype(np.float32) continuous_cols [i for i in range(41) if i not in (1, 2, 3)] # 跳过协议、服务、flag枚举列 scaler MinMaxScaler(feature_range(0, 255)) data[:, continuous_cols] scaler.fit_transform(data[:, continuous_cols]) images [] for row in data: # 40维特征向量 - 8x5 - 用双线性插值放大到 64x64 grid row[:40].reshape(8, 5) img np.kron(grid, np.ones((8, 13))) # 简单放大等效于最近邻 img img[:64, :64] images.append(img.astype(np.uint8)) return np.array(images)逻辑说明离散特征列protocol_type、service、flag不参与 MinMax 缩放否则编号的数值差会主导灰度分布。第 42 个位置是标签列要么单独拆出来要么像上面代码一样切片时排除。放大到 64x64 用 np.kron 做最近邻插值最省事160x96 同理比例不一致不影响卷积网络读取空间特征。2.2 两种输入分辨率解决什么矛盾原文让 CNN1 吃 160x96 的高分辨率图CNN2 吃 64x64 的低分辨率图。这样设计的真实意图不是“数据增强”而是让模型同时看到不同感受野下的攻击模式。DoS 攻击通常表现为大量短连接在灰度图上会形成密集的横向纹理Probe 扫描则留下周期性脉冲这些需要局部精细特征而某些 R2L 攻击的行为特征散布在整个会话里需要更大的感受野。把 KDD99 记录按固定滑窗排布成多行图像比单条记录重排更能体现攻击的时序上下文。原文没有提滑窗但“在线网络安全攻击检测”的使用场景要求模型具备时序分段能力。我一般做法是取一条 TCP 会话内的多条记录按时间顺序拼成一幅 160x96 的“伪图像”行数是时间步列数是特征维度。这样卷积核沿着时间轴滑动时提取的实际上是短时间窗口内的特征组合模式。3. 双分支 CNN 结构拆解CNN1、CNN2 的尺寸链推导3.1 CNN1 与 CNN2 的卷积核参数表原文对两个分支的描述值得逐句核对。CNN1 输入 160x964 个卷积层和 3 个池化层CNN2 输入 64x642 个卷积层和 1 个池化层。两个分支的全连接输出都是 50 维拼接后接 20 维全连接最后 Softmax 分类。分支层卷积核/池化窗口步长输出尺寸CNN1卷积120 个 14x61147x91CNN1池化120 个 2x2274x46CNN1卷积2100 个 8x4167x43CNN1最大池化100 个 2x2234x22CNN1卷积3200 个 2x2133x21CNN1平均池化200 个 2x2217x11CNN1卷积4300 个 6x4112x8CNN2卷积150 个 12x12414x14CNN2池化50 个 2x227x7CNN2卷积2100 个 7x711x1需要注意原文对 CNN1 的描述有几处明显的不自洽比如第三层卷积输出 74x46按前面的尺寸链根本算不出来。上面这张表是按输入尺寸、卷积核、步长逐层推导后得到的最合理版本复现时如果完全照抄论文参数会直接报维度错误。3.2 为什么池化层交替用最大池化和平均池化CNN1 里第一层池化用 2x2 最大池化最后一层换成平均池化。最大池化保留纹理响应最强的位置这对 DoS 攻击的突发流量有效平均池化则保留区域内的整体响应水平适合捕捉 U2R 这种单条记录即可判别的攻击。训练时把两种池化混用梯度回传路径比单一池化更稳定因为最大池化在反向传播时只回传给最大值位置参数更新稀疏平均池化的回传是稠密的两者交替能避免某一层的梯度消失。3.3 双分支全连接拼接与 Softmax 分类import tensorflow as tf from tensorflow.keras import layers, Model def build_parallel_cnn(input_cnn1(160, 96, 1), input_cnn2(64, 64, 1), num_classes5): # 分支1细粒度特征 inp1 layers.Input(shapeinput_cnn1) x1 layers.Conv2D(20, (14, 6), strides1, paddingsame, activationrelu)(inp1) x1 layers.MaxPooling2D((2, 2), strides2)(x1) x1 layers.Conv2D(100, (8, 4), strides1, paddingvalid, activationrelu)(x1) x1 layers.MaxPooling2D((2, 2), strides2)(x1) x1 layers.Conv2D(200, (2, 2), strides1, paddingvalid, activationrelu)(x1) x1 layers.AveragePooling2D((2, 2), strides2)(x1) x1 layers.Conv2D(300, (6, 4), strides1, paddingvalid, activationrelu)(x1) x1 layers.Flatten()(x1) x1 layers.Dense(50, activationrelu)(x1) # 分支2粗粒度特征 inp2 layers.Input(shapeinput_cnn2) x2 layers.Conv2D(50, (12, 12), strides4, paddingvalid, activationrelu)(inp2) x2 layers.MaxPooling2D((2, 2), strides2)(x2) x2 layers.Conv2D(100, (7, 7), strides1, paddingvalid, activationrelu)(x2) x2 layers.Flatten()(x2) x2 layers.Dense(50, activationrelu)(x2) merged layers.Concatenate()([x1, x2]) merged layers.Dense(20, activationrelu)(merged) output layers.Dense(num_classes, activationsoftmax)(merged) model Model(inputs[inp1, inp2], outputsoutput) return model代码说明两个分支各自提取 50 维特征后拼接成 100 维再压缩到 20 维。这样设计的冗余度比直接拼原始特征高很多。CNN1 参数多但输入分辨率大负责局部细节CNN2 参数少但步长 4 的卷积核感受野极大能在前几层就捕捉全局分布。训练时如果 GPU 显存有限可以把 CNN1 的卷积核数量减半准确率掉约 1.5%但训练速度快近一倍。4. 训练流程、误差阈值与误报率评估的现实逻辑4.1 KDD99 数据切分的比例陷阱原文训练集和测试集比例分别为 41% 和 59%这和常见数据集的 80/20 划分完全不同。复现时必须注意测试集里 DoS 占比 77.5%训练集里是 64%。类别分布不一致会放大模型的偏置。更合理的做法是按类别分层采样保证 Normal、DoS、Probe、R2L、U2R 在训练集和测试集中的相对比例一致。训练过程的逻辑链是灰度图输入 → 双分支前向传播 → Softmax 输出五类概率 → 交叉熵损失 → 反向传播更新权重 → 每轮用验证集算准确率。当训练误差低于设定阈值原文用“小于一定阈值”这种模糊写法实际通常设 0.02 或 0.05就停止训练然后用测试集做最终评估。这种基于损失阈值的早停比固定 epoch 更省时间。4.2 准确率和误报率的标准计算from sklearn.metrics import accuracy_score, confusion_matrix def evaluate_detection(y_true, y_pred): acc accuracy_score(y_true, y_pred) cm confusion_matrix(y_true, y_pred) # 误报率正常样本被误判为攻击的比例 normal_idx 0 # 标签 0 对应 Normal fp cm[normal_idx, 1:].sum() tn cm[normal_idx, normal_idx] fpr fp / (fp tn) # 检出率攻击样本被正确识别的比例 tp cm[1:, 1:].sum() fn cm[1:, normal_idx].sum() tpr tp / (tp fn) return {accuracy: acc, false_positive_rate: fpr, detection_rate: tpr}参数说明准确率用测试集全部样本计算误报率只看 Normal 类别被误报的比例。入侵检测领域对误报率极其敏感因为安全运营人员一天要处理上千条告警误报率高会直接导致告警疲劳。训练时如果发现误报率高优先调整损失函数中正负样本的权重而不是盲目堆卷积层。4.3 五类攻击的逐类别对比原文表 3 的数据可以直接拿来验证模型复现效果类别本文准确率本文误报率单分支CNN准确率Normal98.8%0.6%95.6%DoS96.1%2.6%94.7%Probe95.9%3.8%95.2%R2L94.6%2.7%94.3%U2R95.2%1.3%97.8%复现时想达到这个水平有个前提必须把 U2R 样本做上采样或过采样因为训练集里 U2R 只有 56 条占 0.2%模型很容易把所有样本都判成 Normal。原文对这个问题没有展开实际操作中最好的办法是对 U2R 和 R2L 类别做 SMOTE 合成或者用 Focal Loss 抑制 easy negative 的梯度。5. 复现时的高频故障与维数核对表5.1 卷积输出尺寸不匹配反推 padding 与 stride论文里 CNN1 卷积层输出 74x46按输入 160x96、卷积核 14x6、步长 1 计算输出应该是 (160-141)x(96-61)即 147x91。出现这种差异只有一种解释要么步长是 2要么 padding 后做了裁剪。把 160 减到 74正好是 ((160-14)/2)1步长 296 到 46 同理。所以第一层卷积必须设 strides2否则后续所有层都会级联错位。# 错误写法会在这层后面直接报维度错误 x1 layers.Conv2D(20, (14, 6), strides1, input_shape(160, 96, 1))(inp1) # 正确写法strides2并配合 same padding 防止边缘信息过早丢失 x1 layers.Conv2D(20, (14, 6), strides2, paddingsame, input_shape(160, 96, 1))(inp1)复现时不要把论文里的输出尺寸当作必须对齐的目标而应该用公式核验每层输出。用一个自动计算函数在搭网络前先跑一遍把所有层的输出尺寸打印出来确认没有负数维度再开始训练。5.2 灰度图的信息损失把 41 维特征映射到灰度图本质是信息压缩。KDD99 里的类别特征如果直接用编号映射编号间距会给卷积核带来“伪梯度”。应改用 one-hot 编码后再拼接到连续特征后面但 one-hot 会扩大特征维度需要重新设计矩阵形状。更稳妥的操作是把 41 维特征按类型分成三组 A 组连接基本特征1-9 B 组内容特征10-22 C 组流量统计特征23-41 每组单独归一化再按 A 组在上、B 组在右、C 组居中的方式拼成二维图这种分区方式比直接重排更符合特征语义卷积核滑过时能同时看到“同一会话的不同维度”。5.3 训练不收敛与梯度不稳定并行双分支结构的一个隐性问题是分支梯度量级不一致。CNN1 层深、参数多CNN2 层浅、步长大两个分支的梯度差异可能达到数十倍。两个分支拼接后的全连接层会受大梯度分支主导。常见解决办法是给每个分支单独设学习率或者在全连接拼接前加 LayerNorm。from tensorflow.keras.layers import LayerNormalization x1 layers.Dense(50, activationrelu)(x1) x1 LayerNormalization()(x1) x2 layers.Dense(50, activationrelu)(x2) x2 LayerNormalization()(x2)加入 LayerNorm 后两个分支输出分布对齐拼接层能同时收到两个模态的有效信号。论文训练时用的是 2018 年的 TensorFlow 版本没有这个机制复现时加上不会影响原始设计意图反而更稳。5.4 训练集与测试集的随机切分导致结果虚高原始数据集按文件顺序切分如果直接train_test_split且不固定随机种子相当于测试集泄露了部分训练分布。KDD Cup99 本身有不同攻击变体正确做法是按攻击子类型进行分组切分同一子类型的样本只能出现在训练集或测试集不能同时出现。组切分后准确率通常会下降 3-8 个百分点但结果更可信。6. 在线检测场景下的部署把 64x64 输入变成实时推理管道离线训练只解决“能不能检测”的问题在线场景还要处理“检测多快、内存多大、特征怎么实时算”。KDD99 的 41 维特征在真实网络流量里无法直接拿到需要先用抓包工具把 TCP 会话重组再按会话统计计算特征。原文提到模型是“在线网络安全攻击检测方法”但实际 2018 年的实现并没有完整的实时管道下面这套是目前工程上最常用的改造。import time from collections import defaultdict class KDD99FeatureExtractor: def __init__(self, time_window3.0): self.sessions defaultdict(list) self.time_window time_window def feed(self, packet): key (packet[src_ip], packet[dst_ip], packet[src_port], packet[dst_port], packet[protocol]) self.sessions[key].append(packet) def flush(self, now): features [] for key, packets in list(self.sessions.items()): session_start packets[0][timestamp] if now - session_start self.time_window: features.append(self._session_to_41dim(packets)) del self.sessions[key] return features会话窗口 3 秒是权衡值太短会把长连接强行分段丢失时序特征太长缓存的内存占用会持续上涨。特征算好后直接送进灰度图转换函数然后跑前向推理。部署时建议用 ONNX Runtime 代替 TensorFlow Serving单次推理从 12ms 压到 3ms。验证模型是否正常构造一批纯 Normal 流量模型输出置信度最高的类别应为 Normal且概率大于 0.9再注入 Syn Flood 流量DoS 类概率应显著上升。用这个方法做冒烟测试比看训练集准确率更有意义。如果要在线把模型做成闭环把每 10 分钟窗口内的预测结果回传对置信度低于 0.5 的样本打上“待人工确认”标记作为下一轮训练的半监督候选集。这条路径里模型检测率比人工规则的覆盖率高代价是推理特征工程要跟网络环境一起维护。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表