ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

当入侵检测遇上深度学习:从特征工程到模型部署

2026/9/15 2:35:11 拓冰建站 浏览量
当入侵检测遇上深度学习:从特征工程到模型部署 简介这是一个面向网络安全学习者与算法工程师的入侵检测系统项目资源包聚焦如何借助深度学习与机器学习技术识别网络攻击流量。包内围绕KDD数据集展开涵盖决策树、随机森林、SVM、KNN、朴素贝叶斯等经典机器学习分类器以及多个DNN深层神经网络变体的实现代码、预测标签、概率输出与模型保存文件可支撑从数据预处理、模型训练到性能评估的完整流程。资源共75个文件以26个Python脚本为核心配合CSV数据集、TXT预测结果、PDF论文以及工程配置文件等整体约21.64MB按脚本、数据、输出等维度组织便于按需查阅。已吸引610人学习下载。对入门IDS算法对比或复现相关实验而言可直接利用其中的多算法预测标签与概率文件快速分析模型差异也可参考所附论文“Evaluating Shallow and Deep Neural Networks for Network Intrusion Detection Systems in Cyber Security”理解浅层与深层网络在入侵检测中的设计思路是一份兼顾代码与理论的学习素材。1. 当 IDS 遇到深度学习先看数据再看模型流量告警堆积成山、误报率高到运维直接躺平这是传统基于签名的入侵检测系统IDS在真实网络环境里的常态。签名库只能匹配已知攻击面对加密流量和变种攻击基本失灵。标题里“深度学习和机器学习”的组合指向的是一条更务实的路线用机器学习处理特征明确、样本量适中的检测任务用深度学习处理原始流量或高维时序特征里的隐式模式。这个系统的核心不是模型有多深而是怎么把网络流量变成模型能学的东西再把模型的判断接回告警链路。适合正在做安全数据分析、想从规则检测转向异常检测或需要给团队交付一个可复现的检测原型的工程师。2. 入侵检测的特征工程与数据集构造深度学习也需要好特征而不是原始报文2.1 为什么入侵检测不能像图像分类那样直接端到端图像分类可以把像素矩阵直接喂给卷积网络但网络流量不行。一个原始数据包只有几十到一千多字节攻击行为往往分散在多个数据包、多条流里特征不在单包内而在流的统计量、连接状态和时序关系中。常见做法是先把 pcap 切分成流flow再按五元组聚合提取持续时间、上下行字节数、包长分布、标志位出现频率等统计量。这一步没做好模型再深也是空转。实际项目中我一般会先用 CICFlowMeter 或 tshark 把 pcap 转成 CSV 形式的流特征再做标签对齐。CICFlowMeter 输出的特征有 80 多个覆盖流持续时间、平均包长、包间隔等基本够用。但要注意它输出的时间戳字段格式在不同版本里有差异解析时最好统一转成 epoch 秒。2.2 选数据集NSL-KDD 做基准CICIDS2017 做验证NSL-KDD 虽然老但去掉了原 KDD’99 里的冗余记录训练集和测试集分布更合理适合做模型选型的基准。CICIDS2017 包含良性和多种攻击流量更接近真实环境但类别不平衡严重需要做分层采样或类别权重调整。一个可复现的加载流程如下import pandas as pd from sklearn.model_selection import train_test_split df pd.read_csv(cicids2017_sample.csv) # CICIDS2017 里有些行末尾多一个点号先清洗 df.columns [c.strip() for c in df.columns] df df[~df[ Label].astype(str).str.endswith(.)] # 标签二值化BENIGN 为 0其余攻击为 1 df[label] df[ Label].apply(lambda x: 0 if x BENIGN else 1) # 去掉纯 NaN 列和无穷值 df df.replace([float(inf), float(-inf)], float(nan)).dropna(axis1, howall) df df.dropna() X df.drop(columns[ Label, label]) y df[label] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 )这段代码先清洗列名尾部的空格和行尾的点号这是 CICIDS2017 原始 CSV 的常见脏数据问题。然后做二值化把 Benign 归为 0所有攻击归为 1适合先跑通二分类基线。stratifyy保证切分后正负样本比例一致避免测试集里恰好少了一类攻击。2.3 特征缩放与类别不平衡标准化要按训练集拟合树模型不依赖特征缩放但后续要对比神经网络或逻辑回归就必须做标准化。这里有个容易踩的坑标准化时必须在训练集上fit再 transform 测试集不能拿全量数据拟合。否则测试集信息泄露模型评估会虚高。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)入侵检测场景本身是典型的异常检测问题攻击样本远少于正常流量。处理方式有三种用class_weightbalanced让损失函数按样本量加权用 SMOTE 做合成少数类过采样或者把任务重新定义为无监督异常检测只学正常流量的分布。第一种最省事但面对极端不平衡时效果有限第三种适合没有标签的场景但调阈值需要额外验证集。3. 机器学习基线随机森林与孤立森林的双轨验证3.1 为什么先跑随机森林而不是直接上深度学习深度学习在入侵检测上的优势是自动特征提取但这建立在数据量足够大、特征分布稳定的前提下。大多数内部网络的安全数据远远达不到 ImageNet 的量级而且攻击模式变化快重新训练成本高。随机森林作为集成模型的代表对特征交互有天然的处理能力在几千到几万条样本上就能训练出可靠的模型还自带特征重要性方便反推哪些流量特征对检测最有效。更重要的是随机森林可以作为深度学习的“试金石”。如果随机森林在某个特征集上已经能做到 99% 的准确率那深度学习带来的增益有限不值得为了零点几个百分点引入复杂的训练流程。反过来如果随机森林卡在 90% 上下说明特征表达不够这时深度模型才有发挥空间。3.2 用 GridSearchCV 调随机森林的树深度和叶子节点数随机森林的默认参数在低维特征上通常够用但 IDS 特征集动辄 80 维需要对max_depth和min_samples_leaf做约束。树太深会记住噪声太浅又欠拟合。下面是一组可以直接抄的调参代码from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [200, 400], max_depth: [20, 40, None], min_samples_leaf: [1, 5, 10], class_weight: [balanced, None] } rf RandomForestClassifier(random_state42, n_jobs-1) grid GridSearchCV( rf, param_grid, scoringf1, cv5, verbose1, n_jobs-1 ) grid.fit(X_train_scaled, y_train) print(grid.best_params_) print(grid.best_score_)用scoringf1而不是accuracy是因为入侵检测里多数类正常流量占绝对优势准确率虚高没有参考价值。F1 分数同时看精确率和召回率能在误报和漏报之间找平衡。class_weightbalanced会自动调整权重但不一定比手动设置阈值效果好所以放在网格里一起搜。3.3 孤立森林用于无监督异常检测的参考基线有监督模型依赖标签但真实环境里很难拿到干净准确的攻击标签。孤立森林的思路是随机切分特征空间异常样本因为“离群”更容易被孤立出来所以路径长度更短。这个方法不需要标签适合在无标注流量上先做一轮粗筛。from sklearn.ensemble import IsolationForest iso_forest IsolationForest( contamination0.1, # 预期异常比例 random_state42, n_jobs-1 ) iso_forest.fit(X_train_scaled) # 只喂特征不需要标签 y_pred iso_forest.predict(X_test_scaled) # predict 返回 1 表示正常-1 表示异常contamination是核心参数表达你对数据集中攻击比例的“先验估计”。设太大会把正常流量误判成攻击设太小会漏掉真实攻击。一个常见的做法是先设 0.1 跑一轮把判为异常的样本记下来人工抽检一批按实际命中率回调这个值。4. 深度学习方案用 1D-CNN 和 Autoencoder 检测恶意流量4.1 为什么选 1D-CNN 而不是 LSTM 或 TransformerIDS 的流特征本质是一维特征向量相邻维度之间的“顺序”是人为定义的不一定有强时序依赖。LSTM 适合真正的序列数据比如一段连续的数据包大小序列Transformer 适合长依赖场景但需要大量数据和更长的训练时间。在流统计特征上1D-CNN 通过卷积核跨维度提取局部组合特征训练速度快参数量少对中小规模安全数据集最友好。如果数据是原始数据包字节序列或者按时间排序的包长序列LSTM 或 Transformer 才有明显优势。所以方案选择取决于“数据形态”而不是“哪个模型更先进”。4.2 搭一个 1D-CNN 分类器把准确率和召回率分开看import torch import torch.nn as nn class IDSConv1D(nn.Module): def __init__(self, n_features, num_classes1): super().__init__() self.conv_block nn.Sequential( nn.Conv1d(1, 64, kernel_size3, padding1), nn.ReLU(), nn.BatchNorm1d(64), nn.MaxPool1d(2), nn.Conv1d(64, 32, kernel_size3, padding1), nn.ReLU(), nn.BatchNorm1d(32), nn.AdaptiveAvgPool1d(8), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(32 * 8, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, num_classes), ) def forward(self, x): # 输入形状: (batch, n_features) - (batch, 1, n_features) x x.unsqueeze(1) x self.conv_block(x) return self.classifier(x)输入形状是(batch, n_features)unsqueeze(1)加一个通道维变成(batch, 1, n_features)这是 Conv1d 要求的三维输入。MaxPool1d(2)长度减半能扩大感受野并减小特征图尺寸。最后的AdaptiveAvgPool1d(8)把不同长度输入统一成 8 个输出维度这样全连接层的输入维度就固定了。训练时用 BCEWithLogitsLoss 配合 sigmoid 输出比单独用 sigmoid BCELoss 数值更稳定from torch.utils.data import DataLoader, TensorDataset X_train_t torch.tensor(X_train_scaled, dtypetorch.float32) y_train_t torch.tensor(y_train.values.astype(float32).reshape(-1, 1)) dataset TensorDataset(X_train_t, y_train_t) loader DataLoader(dataset, batch_size256, shuffleTrue) model IDSConv1D(X_train_scaled.shape[1]) optimizer torch.optim.AdamW(model.parameters(), lr1e-3) criterion nn.BCEWithLogitsLoss() for epoch in range(20): for xb, yb in loader: optimizer.zero_grad() logits model(xb) loss criterion(logits, yb) loss.backward() optimizer.step()用AdamW而不是Adam因为带权重衰减的解耦方式在中小数据集上更稳不容易把稀疏特征上的权重压死。batch_size256对几十万条样本的数据集来说不会太占显存也能保证梯度估计足够平滑。4.3 Autoencoder 做无监督异常检测用重建误差挑出攻击流量Autoencoder 的思路是只用正常流量训练让模型学会“压缩-重建”正常流量的特征。攻击流量在测试时重建误差会明显偏高因为模型没见过这种模式。class IDSAutoencoder(nn.Module): def __init__(self, n_features, encoding_dim16): super().__init__() self.encoder nn.Sequential( nn.Linear(n_features, 64), nn.ReLU(), nn.Linear(64, encoding_dim), ) self.decoder nn.Sequential( nn.Linear(encoding_dim, 64), nn.ReLU(), nn.Linear(64, n_features), ) def forward(self, x): return self.decoder(self.encoder(x))训练时只喂y_train 0的样本损失函数用 MSE让模型学会重建正常流量。推理时对每个样本计算重建误差然后按误差分布选阈值。model IDSAutoencoder(X_train_scaled.shape[1]) criterion nn.MSELoss() # 只选正常样本做训练 normal_idx y_train.values 0 X_normal torch.tensor(X_train_scaled[normal_idx], dtypetorch.float32) dataset_normal TensorDataset(X_normal, X_normal) loader_normal DataLoader(dataset_normal, batch_size128, shuffleTrue) for epoch in range(30): for xb, _ in loader_normal: recon model(xb) loss criterion(recon, xb) optimizer.zero_grad() loss.backward() optimizer.step()阈值的选择有两种方式在验证集上画 PR 曲线取满足“召回率不低于 90%”的最低阈值或者取正常样本重建误差分布的 99 分位数。前者可控业务指标后者简单直接但不够精细。5. 模型部署与评估从混淆矩阵到在线学习5.1 保存模型和标准化器推理时顺序不能反模型上线时最常见的错误是只保存模型权重忘了保存标准化器。推理时直接拿原始特征进模型数值范围完全不对效果崩掉也不奇怪。正确做法是把标准化器和模型一起持久化。import joblib joblib.dump(scaler, ids_scaler.pkl) joblib.dump(grid.best_estimator_, ids_rf_model.pkl) torch.save(model.state_dict(), ids_cnn.pt)推理时先加载 scaler再加载模型scaler joblib.load(ids_scaler.pkl) model IDSConv1D(n_features) model.load_state_dict(torch.load(ids_cnn.pt, map_locationcpu)) model.eval() def predict(flow_features): scaled scaler.transform(flow_features.reshape(1, -1)) with torch.no_grad(): logits model(torch.tensor(scaled, dtypetorch.float32)) prob torch.sigmoid(logits).item() return prob 0.5注意model.eval()必须调用否则 BatchNorm 在推理时还会用训练集的统计量更新导致结果不一致。5.2 评估指标要按安全场景选不要只看 AUC入侵检测的代价矩阵不对称漏报一个攻击可能造成数据泄露误报一个正常请求只是多一条告警。所以要看精确率-召回率曲线下的面积PR-AUC而不是 ROC-AUC因为 ROC 对类别不平衡不敏感。指标计算公式适用场景准确率(TPTN)/(TPTNFPFN)类别均衡时参考IDS 场景慎用精确率TP/(TPFP)误报敏感的场景比如告警疲劳召回率TP/(TPFN)漏报敏感的场景比如合规要求F1 分数2×P×R/(PR)默认首选平衡误报和漏报假正率FP/(FPTN)衡量误报对运维的干扰程度调阈值时用验证集的 PR 曲线选曲线上离右上角最近的点或者直接指定“召回率不低于 90% 时的最大精确率”。在代码里实现from sklearn.metrics import precision_recall_curve probs grid.predict_proba(X_test_scaled)[:, 1] precision, recall, thresholds precision_recall_curve(y_test, probs) # 取召回率 0.9 的最高精确率 valid recall 0.9 best_idx precision[valid].argmax() best_threshold thresholds[valid][best_idx] print(Best threshold:, best_threshold)这里的best_threshold来自验证集不是测试集。在测试集上用这个阈值评估一次得到的指标才能反映真实泛化效果。5.3 持续集成流量特征管线模型要跟着数据演进入侵检测模型不能训一次用完一年。网络流量分布会随业务变化模型性能会衰减。一个成本较低的维护方式是每周用最近 7 天的流量特征做一次性能评估用 PSI群体稳定性指数监控特征分布漂移。当 PSI 超过 0.25 或召回率下降超过 5 个百分点时触发重新训练。def psi(expected, actual, bins10): # 把两个分布分成 10 个区间比较占比变化 expected_pct np.histogram(expected, binsbins)[0] / len(expected) actual_pct np.histogram(actual, binsbins)[0] / len(actual) psi_value np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct)) return psi_value模型重新训练只更新特征统计和模型权重不需要重写推理代码。数据管线里同样要给标签加上置信度自动化打标容易引入噪声抽样人工复核能避免模型学到过期攻击模式。5.4 一个诊断技巧把注意力放在误报样本上模型上线后真正有价值的工作不在调参而在分析误报样本。把 False Positive 的流量特征可视化画成 t-SNE 或直接打印特征值分布很多时候会发现误报集中在某种特定协议或端口上。这时最优解法往往不是加模型复杂度而是在特征管线里加一条过滤规则识别出已知的业务探测或运维扫描流量直接标记为白名单。这类改进对误报率的降低效果通常比换更强力的模型明显。本文还有配套的精品资源点击获取