
做入侵检测相关的工作CIC-IDS2017 这个数据集基本是绕不开的。我最早用它的时候光是把那 8 个 CSV 文件拼到一起读进内存就折腾了半天后面又栽在数据清洗和评估指标上。这篇文章把我从下载原始数据、预处理、特征工程到模型训练测试的完整流程梳理出来代码都是我自己跑过改过的你可以直接拿去用。不管你是刚接触网络流量分析的新手还是想快速验证一个检测模型的老手这套流程都能帮你少踩几个坑。1. 先搞清楚 CIC-IDS2017 到底是个什么数据集很多人拿到 CIC-IDS2017 就直接开始读 CSV结果发现列名带着空格、里面有 Infinity 和 NaN训练出来的模型还莫名其妙的差。问题就出在没搞懂这个数据集的“出身”。它由加拿大网络安全研究所发布目的是提供一份接近真实网络流量的入侵检测基准数据。跟老一代的 KDD99、NSL-KDD 相比它的流量场景更丰富特征也更贴近现代网络环境所以现在学术界和工业界做 IDS 评估大多转到了这个数据集上。1.1 数据集背景与 8 个 CSV 文件的对应关系CIC-IDS2017 的原始数据是 5 天抓的 PCAP 包分成了周一到周五。官方用 CICFlowMeter 工具从 PCAP 里提取出流特征生成了一组 CSV 文件。很多资料只说“8个CSV”但没告诉你这几个文件分别对应哪些攻击场景文件名称对应时间主要攻击类型Monday-WorkingHours.pcap_ISCX.csv周一上午、下午仅有正常流量BENIGNTuesday-WorkingHours.pcap_ISCX.csv周二上午、下午FTP-Patator、SSH-Patator暴力破解Wednesday-WorkingHours.pcap_ISCX.csv周三上午、下午DoS slowloris、DoS Slowhttptest、DoS Hulk、DoS GoldenEye、HeartbleedThursday-WorkingHours-Morning-WebAttacks.pcap_ISCX.csv周四上午Web Attack – Brute Force、Web Attack – XSS、Web Attack – Sql InjectionThursday-WorkingHours-Afternoon-Infiltration.pcap_ISCX.csv周四下午Infiltration 内网渗透攻击Friday-WorkingHours-Morning.pcap_ISCX.csv周五上午Botnet 僵尸网络流量Friday-WorkingHours-Afternoon-PortScan.pcap_ISCX.csv周五下午PortScan 端口扫描Friday-WorkingHours-Afternoon-DDoS.pcap_ISCX.csv周五下午DDoS LOIT这些文件在官方压缩包里其实放在 MachineLearningCSV 目录下下载后建议先解压到本地固定路径。注意周一的文件只有正常流量是用来做基线训练的。真正做分类时通常把多个文件拼起来。1.2 特征维度与标签体系速览CICFlowMeter 对每条双向流提取了 80 多个特征常见列有 Flow Duration、Total Fwd Packets、Total Backward Packets、Fwd Packet Length Mean、Flow Bytes/s、Flow Packets/s、Average Packet Size 等等。最后一列是 Label它的值直接是 BENIGN 或者具体的攻击名比如 DoS Hulk、PortScan、DDoS。这里有一个特别容易让人困惑的地方CSV 第一行表头其实是带空格的。比如 Destination Port 前面有一个空格 Flow Duration 前面也有空格。直接用 pandas 读取时列名会带着这些空格处理起来极容易踩坑。我一般读取后直接 strip 一遍列名省得后面每次写代码都出问题。另外同一份数据集在不同论文里报告的结果差异很大原因之一就是有人把 15 类标签压缩成二分类正常/异常有人用多分类还有人把少数攻击类合并到“其他攻击”。做实验前先想好自己的任务定义不要拿别人的结果直接横向比较否则会被误导。1.3 为什么选这个数据集而不选 KDD99 或 NSL-KDDKDD99 和 NSL-KDD 虽然经典但它们的流量特征来源于 1998 年的网络环境放到现在做实时入侵检测说服力不够。CIC-IDS2017 的 PCAP 采集为期 5 天包含大量真实网络背景流量攻击类型覆盖了暴力破解、Web 攻击、DoS、僵尸网络、端口扫描和 DDoS 等主流威胁。它的缺点也很明显数据量大完整读入内存需要十几 GB RAM类极度不平衡BENIGN 占比很高部分攻击样本很少CSV 里有非法值。这些问题不是 bug而是真实场景的产物反而更接近落地部署会遇到的情况。所以我个人一直把它作为入侵检测算法验证的首选基准。2. 搭建训练测试环境把数据完整读进来CIC-IDS2017 的 CSV 文件加起来有 8 个单文件几百 MB 到 1GB 以上直接 pd.read_csv() 全部拼起来会吃很多内存。我在 16GB 内存的老机器上跑过拼完确实能读但再做特征工程就容易卡死。建议有条件的话先把环境内存提到 32GB或者用分批读取的思路。2.1 推荐环境与依赖清单我自己比较稳定的组合是这样的Python 3.9 以上3.10、3.11 都行pandas 2.xnumpyscikit-learnlightgbm 或 xgboost二选一后面训练会用到matplotlib、seaborn画混淆矩阵和特征分布用imbalanced-learn做采样处理时用如果只有 CPU跑传统机器学习完全够用。深度模型可以后面再考虑 GPU先把流程跑通最重要。2.2 加载全部 CSV 的脚本模板与内存优化我把加载脚本写成固定模板每次打开新项目都是复制这个文件再改路径。核心逻辑是遍历目录下所有以 .csv 结尾的文件pandas 读取后先把列名空格去掉再加一列用来标记来源文件最后用 concat 合并。import pandas as pd import glob import os data_dir ./MachineLearningCSV/MachineLearningCSV/ df_list [] for csv_file in sorted(glob.glob(os.path.join(data_dir, *.csv))): print(正在读取:, csv_file) tmp_df pd.read_csv(csv_file) # 去掉列名首尾空格CIC-IDS2017的列名很多带前导空格 tmp_df.columns [col.strip() for col in tmp_df.columns] df_list.append(tmp_df) df pd.concat(df_list, ignore_indexTrue) print(合并后数据集形状:, df.shape) print(标签分布:) print(df[Label].value_counts())这段代码跑完之后你会看到一个很大的 DataFrame。首次加载时我建议先不要直接做任何操作先打印 shape 和标签分布确认 8 个文件都被正确读进来。如果出现某几个文件读不到多半是路径问题用 glob 输出所有匹配文件排查即可。2.3 数据清洗Infinity、NaN、类别标签编码CIC-IDS2017 里有个老熟人攻击样本的某些特征列会出现 Infinity 或者 NaN。这些值不处理掉训练时 sklearn 直接报错或者模型莫名其妙学出很差的边界。我的固定处理流程分四步把非数值列过滤掉只保留 float64 / int64 类型列把 Label 列单独拎出来对剩余特征列用 np.isfinite 筛掉包含空值、无穷值的行把类别标签做编码同时记录类别列表方便后面画混淆矩阵import numpy as np from sklearn.preprocessing import LabelEncoder # 先保存标签列 label_col df[Label] # 筛选数值型特征列 num_cols df.select_dtypes(include[np.number]).columns.tolist() feature_df df[num_cols].copy() # 将无穷值和缺失值统一转为 NaN feature_df feature_df.replace([np.inf, -np.inf], np.nan) feature_df feature_df.dropna(axis0, howany) # 对应的标签行也要对齐 label_col label_col.loc[feature_df.index] le LabelEncoder() y le.fit_transform(label_col) X feature_df print(清洗后特征矩阵:, X.shape) print(类别映射:, dict(zip(le.classes_, le.transform(le.classes_))))注意一个细节dropna(axis0) 会删掉所有包含 NaN 的行但特征里原本也有像 Flow Bytes/s 这种分母为 0 导致无穷大的列务必先替换再删除。如果这里图省事不做清洗后面随机森林跑起来虽然能出结果但测试集的分数会变飘很难复现。3. 特征工程与样本切分按训练集测试集的规矩来特征工程这一步决定了你模型能力的上限。CIC-IDS2017 自带 80 多维特征但并不是全部都有用。有些列是标识列比如 Flow ID、Source IP、Destination IP直接参与训练会带来严重的数据泄漏模型在测试集上看着分数很高实际部署时一塌糊涂。3.1 特征选择思路与过滤非数值列我在处理时固定过滤掉这几类列Flow ID、Source IP、Destination IP、Timestamp 这几种对分类任务没有泛化意义的列。还有一类坑是 Source Port、Destination Port端口本身不完全是噪声特别是 PortScan 场景下目的端口很关键但如果你的测试环境网络和训练环境差异很大端口可能会导致过拟合。我的做法是先用全特征跑一个基准结果再对比是否去掉端口列根据验证集表现决定。drop_cols [Flow ID, Source IP, Destination IP, Timestamp] X X.drop(columns[c for c in drop_cols if c in X.columns], errorsignore)做完这步后特征数量通常在 78 左右。后续想压缩特征做快速验证时可以用随机森林的 feature_importances_ 取 top 20或者直接跑一个 LightGBM 看重要性排序。实测下来Flow Duration、Fwd Packet Length Mean、Bwd Packet Length Mean、Flow Bytes/s、Average Packet Size 这几个特征的贡献度都比较靠前。3.2 切分策略与标签平衡处理CIC-IDS2017 有多种切分方式。最朴素的是随机切分用 train_test_split(X, y, test_size0.3, stratifyy) 保证训练集和测试集里各类别比例一致。但更严格的实验会按时间切例如周一到周四做训练、周五做测试这样更接近攻击检测的“未来数据”场景。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) print(训练集形状:, X_train.shape) print(测试集形状:, X_test.shape)stratify 参数一定要带上因为数据集极度不平衡。如果不做分层抽样极端情况下测试集里某个攻击类别一条样本都没有F1 分数会直接失真。另一个问题是大部分正常流量和攻击流量样本数量差距悬殊。我见过有人直接全部训练结果模型在 DDoS 上表现不错但在 Web Attack 上几乎没效果。先跑通流程可以不做采样如果要追求更好的均衡指标再考虑 SMOTE 或者 NearMiss。3.3 标准化与避免数据泄漏决策树、随机森林这类树模型不需要做标准化。但如果你用神经网络、逻辑回归或者 KNN就必须对特征做标准化。这里有个非常关键的禁忌fit 只能在训练集上做然后再 transform 测试集绝对不能用整个数据集去 fit否则就是数据泄漏测试结果偏高。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)如果你想要更高的一致性可以把它封装进 sklearn 的 Pipeline。我自己的习惯是把预处理和模型一起做成 Pipeline这样在调参和交叉验证时可以避免重复写清洗逻辑也防止误用测试集信息。4. 模型训练从传统机器学习到深度网络CIC-IDS2017 上表现好的模型不少。传统机器学习里随机森林和 LightGBM 是性价比之王训练快、效果稳。深度学习方面简单的多层感知机MLP也能达到不错的效果但需要更细调参。我的建议是先跑树模型拿到一个强基线再决定要不要上深度网络。4.1 随机森林与 XGBoost 的快速上手代码先跑随机森林用默认参数快速看效果from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report rf_clf RandomForestClassifier(n_estimators100, random_state42, n_jobs-1) rf_clf.fit(X_train, y_train) y_pred_rf rf_clf.predict(X_test) print(classification_report(y_test, y_pred_rf, target_namesle.classes_))如果特征数量多我建议直接上 LightGBM训练速度快且准确率通常略高于随机森林import lightgbm as lgb lgb_clf lgb.LGBMClassifier( n_estimators200, learning_rate0.1, num_leaves31, random_state42, n_jobs-1 ) lgb_clf.fit(X_train, y_train) y_pred_lgb lgb_clf.predict(X_test) print(classification_report(y_test, y_pred_lgb, target_namesle.classes_))这一步不需要刻意把参数调得太细。先看分类报告确认哪些类别 Recall 低再针对性地做特征或采样比盲目调参高效得多。4.2 简单 DNN 分类器的实现树模型效果虽好但很多时候项目要求用深度学习方案便于后续做在线增量或部署到 GPU 服务上。我用 Keras 搭过一个简单的 MLP结构是 128-64-32 的全连接层加 Dropout输出层 softmax。这里不展开太多调参细节给一个能跑的模板。import tensorflow as tf from tensorflow.keras import layers, models model models.Sequential([ layers.Input(shape(X_train_scaled.shape[1],)), layers.Dense(128, activationrelu), layers.Dropout(0.3), layers.Dense(64, activationrelu), layers.Dropout(0.3), layers.Dense(32, activationrelu), layers.Dense(len(le.classes_), activationsoftmax) ]) model.compile( optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy] ) history model.fit( X_train_scaled, y_train, validation_split0.2, epochs20, batch_size256, verbose1 )注意这里 y_train 不需要做 one-hot用 sparse_categorical_crossentropy 配合整数标签就行。跑 20 个 epoch 后观察验证损失曲线如果验证损失开始反弹说明已经过拟合应该用 EarlyStopping 提前结束。4.3 训练过程的监控、过拟合判断与调参心得训练过程中我习惯记录三样东西训练集损失、验证集损失、验证集 F1。对于树模型直接看分类报告里各个类别的 Precision 和 Recall 是否均衡。对于神经网络画 loss 曲线非常关键。如果训练 loss 不断下降但验证 loss 翘头就是典型的过拟合。解决办法包括加大 Dropout、减小网络宽度、加 L2 正则、减少 epoch 或用早停。调参时不要一上来就跑网格搜索。CIC-IDS2017 类多、样本多全数据量下网格搜索非常耗时。先用小采样跑一组默认参数建立基线然后用 Optuna 或 RandomizedSearchCV 在关键参数上搜索比如 n_estimators、max_depth、learning_rate、num_leaves。这样既省时间又能找到比较稳定的参数组合。5. 测试报告与模型评估别只看 Accuracy很多初学者在 CIC-IDS2017 上跑完一看 Accuracy 99%就觉得模型无敌了。实际上这个数据集类别严重不平衡BENIGN 占了绝大多数如果你的模型把 BENIGN 全部判断对把其他攻击大部分判断错Accuracy 照样可能超过 95%。所以必须看细粒度的评估指标。5.1 分类报告、混淆矩阵与 ROC-AUCsklearn 的 classification_report 是必看的。它输出每个类别的 precision、recall、f1-score还有一个 support代表该类别在测试集里的样本数。我通常会重点看那些 support 很小的类比如 Web Attack – Sql Injection、Infiltration、Heartbleed。这些类样本少、难识别recall 低是常态但如果在报告里直接消失多半是模型把所有样本都预测成了别的类。混淆矩阵能告诉你具体错在哪里。我用 seaborn 画热力图把对角线之外的错误配对比看清楚import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix cm confusion_matrix(y_test, y_pred_lgb) plt.figure(figsize(16, 12)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsle.classes_, yticklabelsle.classes_) plt.xticks(rotation90) plt.yticks(rotation0) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi150)ROC-AUC 在多分类场景下要按类别分别计算然后做 macro 平均或 weighted 平均。但说实话对入侵检测来说我更关心的是“攻击样本到底有没有被漏掉”所以 Recall 和 FPR 的参考价值往往比单一 AUC 更高。5.2 多分类下的宏平均与加权平均多分类评估指标有两个容易混淆的版本。macro average 是先计算每个类别的指标再做算术平均它对小样本类别更敏感如果你的模型把小攻击类学得很差macro F1 会被拉低。weighted average 是按各类样本量加权平均结果偏向大类容易掩盖小类的问题。报告入侵检测结果时我一般同时给出 macro F1 和 weighted F1并单独列出关键攻击类的检测率。否则评审或者老板根本看不出你的模型对小攻击类的真实水平。5.3 FPR 与检测率的真实含义入侵检测常用的两个指标是检测率和误报率。检测率等价于召回率也就是真实攻击样本中被正确识别出来的比例。误报率是正常流量被误判为攻击的比例这个数值在实际部署中非常重要。假设误报率是 0.1%但网络每秒几万条流量一天下来误报数量就很可观安全运营人员会被报警疲劳拖垮。所以选择模型时我不会只盯着测试集上的 F1而是用分类阈值进一步压低误报率。比如对 LightGBM可以用 predict_proba 拿到每个类别的概率再设定一个阈值只有概率超过阈值才判定为攻击否则归为 BENIGN。这样能灵活平衡检测率和误报率。6. 常见问题排查与避坑实录CIC-IDS2017 用久了总会遇到一些重复出现的问题。我把踩过比较多的坑整理成一张速查表方便你排查。6.1 数据格式问题速查表现象原因解决办法pandas 读入后列名带空格CICFlowMeter 生成 CSV 的表头有前导空格读取后对 columns 做 strip训练时报 Input contains NaN攻击样本的某些特征存在 NaN/Infinity先 replace inf 为 NaN再用 dropna标签列读出来有前导空格和列名问题同源对 tag 列做 strip 或者统一 replace多文件拼接后样本重叠不同时间段的 PCAP 理论上不会重叠但注意去重可用 df.drop_duplicates() 去重内存不足直接死机CSV 文件太大拼接后占用高只读取需要的列或使用 dtype 压缩6.2 内存溢出与服务器环境建议如果你只有 16GB 内存的笔记本建议别一次性用 pd.concat 拼 8 个大 CSV。你可以用 chunked 读取或者只读特定列。pandas read_csv 里可以用 usecols 参数指定需要的列这样内存占用能降一半以上。# 只需要读取数值特征和标签 cols_to_read [ Destination Port, Flow Duration, Total Fwd Packets, Label] tmp_df pd.read_csv(csv_file, usecolscols_to_read)另外如果是在服务器上训练记得优先用磁盘空间充足的目录解压/tmp 空间不够会直接导致写入失败。我踩过一次坑解压到 /tmp 导致空间写满后面所有 CSV 都读取失败。6.3 样本不平衡引发的评估陷阱CIC-IDS2017 的不平衡程度非常极端。BENIGN 样本是所有的攻击样本总量好几倍而像 Web Attack – SQL Injection、Infiltration 这类攻击的样本数量可能只有几百条。这种情况下如果你不做采样模型很难学到少数类特征。最简单的处理办法是训练时给 sklearn 模型传 class_weightbalancedLightGBM 里可以设置 is_unbalanceTrue 或 scale_pos_weight。对少数攻击类我建议单独监督指标观察每个攻击类的 recall 是否低到离谱。如果确实太低可以尝试对少数类做 SMOTE 过采样但也要小心过采样导致模型过拟合。最好用验证集而不是测试集来评估过采样效果防止把测试集信息间接引入训练。最后分享一个小技巧我在跑了很多轮实验之后养成了一个习惯每次训练前先随机抽 5000 条样本快速跑一遍完整流程从加载、清洗、训练到评估都用最小的数据量过一遍确认代码和指标没有明显问题后再上全量数据。这个习惯帮我避免了无数次“熬夜等一个明显错误的结果”。另一个非常值得做的事是保存每次实验的配置和结论。CIC-IDS2017 的数据量大一次全量训练可能要几十分钟如果你不记录参数和切分随机种子后面复现结果会非常难受。我用 CSV 记录每组实验的数据集版本、特征列、模型参数、训练时间、验证集 F1 和测试集 F1这样对比分析时一目了然。如果你后续想把这套流程扩展到实时流量检测可以在这个基础上引入滑动窗口重训练机制用新抓取的流量持续更新模型。CIC-IDS2017 作为离线评测基准很成熟但真实环境还要考虑加密流量和概念漂移那是后话了。