ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

加密恶意流量检测:DoH与CTU-13数据集的特征工程与模型复现

加密恶意流量检测:DoH与CTU-13数据集的特征工程与模型复现 简介这是一份面向Python毕业设计、课程设计与期末大作业的机器学习实战项目聚焦加密恶意流量的分析与检测包含完整源码与配套文档说明。代码注释详细新手也能读懂简单部署即可运行适合作为高评分毕业设计参考。压缩包共217个文件约25.6MB核心包括4个Python脚本、2个pcap原始流量样本、165个日志文件、6个CSV特征数据、6个npy特征矩阵以及14个HTML可视化页面并附带markdown文档、样式文件与图片素材覆盖数据预处理、特征选择、模型训练与结果展示的完整流程。实验针对doH流量与CTU-13数据集展开提供了相关性分析与Boruta特征选择前后的模型效果对比能够直接用于论文配图、答辩演示或二次开发。目前已有187人学习下载适用于需要高质量项目范例的计算机专业学生。1. 加密恶意流量检测从特征文件到可复现的毕设方案拿到这份基于机器学习的加密恶意流量分析与检测项目时我第一反应是看它到底给了什么。解压之后没有花哨的前端界面也没有几百兆的预训练模型而是一套完整的“特征工程 建模结果 数据可视化”闭环——六个 CSV 文件对应两套数据集的特征筛选与模型输出两个 HTML 文件把检测结果直接可视化成可交互图表。对做毕设或课程设计的同学来说这份资源的价值在于它不是黑匣子你能看到 DoH 隧道流量和 CTU-13 僵尸网络流量各自是怎么做相关性过滤、怎么用 Boruta 算法筛特征、模型跑完后的评估指标落在哪里。我拆完后的结论是这是一个适合复现并二次扩展的流量检测底稿新手跟着数据流走能交出一份完整的分析报告熟手则可以拿它的特征筛选结果替换自己的分类器做对比实验。2. 先读懂两份数据集的差异DoH 与 CTU-13 的检测场景完全不同2.1 从 CSV 文件名反推项目的技术路线先看项目的文件清单doh_corr_features.csv、doh_boruta_features.csv、ctu13_corr_features.csv、ctu13_boruta_features.csv这四个文件命名非常直白corr代表相关性分析后的特征集boruta代表经过 Boruta 特征选择算法筛选后的特征集。两个*_model_result.csv则是各自数据集上最终模型的结果记录包括训练集和测试集的评估指标。show_data_doh.html和show_data_ctu-13.html是用可视化库生成的交互式报告直接在浏览器打开就能看到特征分布、类别平衡情况和模型评估图表。这两套数据集在加密流量检测领域是很有代表性的组合。DoHDNS over HTTPS流量属于加密隧道类恶意通信攻击者把 DNS 查询隐藏在 HTTPS 流量里绕过传统防火墙CTU-13 则是捷克理工大学发布的僵尸网络流量数据集包含七种不同的僵尸网络样本是恶意流量检测论文里最常被引用的公开数据集之一。项目同时选这两套数据本质上是在验证“同一套特征工程方法是否能同时适配隧道型恶意流量和僵尸网络型恶意流量”这个设计本身就很适合写进论文的实验章节。2.2 特征文件里到底存了什么用 pandas 快速读取并理解字段下载项目后我习惯先写一个小脚本把四个特征文件读出来看列名和维度避免后面分析时对着黑匣子猜。常见做法是用 pandas 直接读 CSV然后打印形状和前几行。import pandas as pd # 读取四个特征文件 doh_corr pd.read_csv(doh_corr_features.csv) doh_boruta pd.read_csv(doh_boruta_features.csv) ctu_corr pd.read_csv(ctu13_corr_features.csv) ctu_boruta pd.read_csv(ctu13_boruta_features.csv) # 查看每个文件的维度 for name, df in [(doh_corr, doh_corr), (doh_boruta, doh_boruta), (ctu_corr, ctu_corr), (ctu_boruta, ctu_boruta)]: print(f{name}: {df.shape[0]} 行, {df.shape[1]} 列) # 查看列名 print(\nDoH 相关性特征:\n, doh_corr.columns.tolist()) print(\nDoH Boruta 特征:\n, doh_boruta.columns.tolist())这段脚本的作用是快速建立对数据规模的认知。shape[0]是样本量shape[1]是特征数量两者结合可以判断特征筛选的压缩比。我拆完后注意到Boruta 筛选后的特征数量明显少于相关性筛选后的特征数量这说明项目对特征做了两轮精简第一轮用相关性矩阵把高度共线的特征剔除第二轮用 Boruta 算法做基于随机森林的重要性评估保留真正对分类有贡献的特征子集。参数上需要关注的是pd.read_csv默认把第一行当作列名如果你的 CSV 文件没有表头要加headerNone并手动指定names列表。另外如果项目里有中文字段名建议在读取时加上encodinggbk或encodingutf-8做兼容两个都试一下就行。2.3 相关性筛选与 Boruta 筛选到底差在哪在我的经验里很多同学分不清corr和boruta特征文件的意义以为只是两种不同的筛选方法随便选一个用。实际上它们的筛选逻辑完全不同使用的场景也不同。相关性筛选的思路是计算特征两两之间的皮尔逊相关系数设定一个阈值常见是 0.8 或 0.9如果两个特征的相关性超过阈值就保留其中一个本质是解决多重共线性问题。它的优点是计算快、可解释性强缺点是无法评估单个特征对目标类别的区分能力——两个完全独立的特征可能都与标签无关但相关性筛选照样会把它们保留下来。Boruta 算法的思路则完全不同。它在原始特征旁边生成影子特征把影子特征和原始特征放在一起训练随机森林拿原始特征的重要性分数和影子特征的最大重要性分数做比较只有明显高于影子特征的原始特征才被判定为“真正重要”。这个过程天然考虑了特征对分类的贡献筛选出的特征子集在模型训练中往往表现更好。项目里同时保留两套特征结果一个合理的解读是先用相关性筛选去掉冗余再用 Boruta 做重要性排序最终模型用的是 Boruta 筛选后的特征集。这里给一个我在复现时常用的对比思路可以验证两套特征集的差异是否显著from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score # 假设标签列名为 Label根据实际文件调整 X_corr doh_corr.drop(columns[Label]) y_corr doh_corr[Label] X_boruta doh_boruta.drop(columns[Label]) y_boruta doh_boruta[Label] # 相同模型、相同交叉验证设置下比较两套特征 rf RandomForestClassifier(n_estimators100, random_state42) score_corr cross_val_score(rf, X_corr, y_corr, cv5, scoringf1_macro) score_boruta cross_val_score(rf, X_boruta, y_boruta, cv5, scoringf1_macro) print(f相关性特征集 F1: {score_corr.mean():.4f} (/- {score_corr.std():.4f})) print(fBoruta特征集 F1: {score_boruta.mean():.4f} (/- {score_boruta.std():.4f}))这段代码的逻辑是控制变量模型都用随机森林交叉验证都用 5 折唯一的变量是特征集。输出结果如果 Boruta 显著更好说明特征筛选这一步确实起到了降维提效的作用如果相差不大则说明数据本身冗余度低两种方法都可以接受。random_state42固定了随机种子保证实验可复现这是做对比实验时最容易忽略的细节不固定种子的话每次结果都会有波动论文里没法解释。3. 模型结果文件解读评估指标与文件对应关系3.1 打开*_model_result.csv从指标反推模型选型两个模型结果文件是理解项目建模阶段的关键。doh_boruta_model_result.csv和ctu13_boruta_model_result.csv的命名已经说明模型使用的是 Boruta 筛选后的特征集。用 pandas 读取后可以看到每一折或者是最终模型的评估指标典型字段包括 accuracy、precision、recall、f1-score、auc 这些二分类常用指标。import pandas as pd model_result pd.read_csv(doh_boruta_model_result.csv) print(model_result.columns.tolist()) print(model_result.to_string())注意这个 CSV 的格式可能是每行一个评估指标也可能是一行包含全部指标的多列格式。如果是前者to_string()可以直接展示全部内容如果是行数特别多的长格式建议用model_result.pivot()做透视。从项目提供的文件名看它大概率是每次交叉验证的折叠记录包含 train 和 test 两部分的指标。指标解读是毕设答辩时最容易被打分老师追问的地方。我建议重点关注两个点一是类别不平衡问题恶意流量数据集中正常流量往往占绝大多数单纯看 accuracy 会被类别比例欺骗必须同时看 precision 和 recall二是看测试集和训练集的指标差距如果训练集 F1 达到 0.99 而测试集只有 0.85说明模型过拟合需要回到特征选择环节或者调整模型正则化参数。3.2 从模型结果反推项目使用过的模型类型虽然项目文件里没有直接给出训练脚本但从文件命名习惯和特征文件格式可以推断模型选型。文件名为boruta_model_result而非xgb_model_result或rf_model_result说明项目可能尝试了多种模型最终把结果统一保存。常见做法是在 Boruta 特征选择后对比逻辑回归、随机森林、XGBoost、LightGBM 的效果。我这里给一个可复现的对比实验脚本你可以直接套用到自己的数据上from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 读取 Boruta 筛选后的特征 df pd.read_csv(doh_boruta_features.csv) X df.drop(columns[Label]) y df[Label] # 划分训练集和测试集保持类别比例 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 定义三个候选模型 models { LR: LogisticRegression(max_iter1000, class_weightbalanced), RF: RandomForestClassifier(n_estimators200, class_weightbalanced, random_state42, n_jobs-1), XGB: XGBClassifier(use_label_encoderFalse, eval_metriclogloss, random_state42, scale_pos_weight9) } # 逐个训练并输出测试集评估报告 for name, model in models.items(): model.fit(X_train, y_train) print(f\n {name} ) print(classification_report(y_test, model.predict(X_test)))这个脚本做了三个关键设置。stratifyy保证训练集和测试集中正常流量与恶意流量的比例与原始数据一致避免划分后测试集里恶意样本太少导致评估失真。class_weightbalanced让模型在训练时自动加权少数类样本缓解类别不平衡的影响。scale_pos_weight9在 XGBoost 里起到类似作用9 这个值是按正常的正负样本比粗略估算的你可以根据项目数据的实际比例调整——正负样本比是 1:9 就用 9是 1:20 就用 20直接设成负样本数除以正样本数即可。3.3 为什么同时保留两份模型结果项目同时给出 DoH 和 CTU-13 两份模型结果这背后是加密流量检测领域一个常见的痛模型在单一数据集上表现良好不代表换一个数据集还能泛化。DoH 流量的特征集中在 TLS 握手阶段的指纹、流量包间隔、DNS 查询长度分布而 CTU-13 僵尸网络的流量特征更多体现在连接持续时间、上下行字节比、端口使用模式上。两份结果对照看才能说明这套特征工程与建模方法不是针对某个数据集过拟合出来的。我在复现时建议做一次跨数据集验证把在 DoH 数据上学到的分类器直接拿到 CTU-13 特征上测试看指标掉多少import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import f1_score # 在 DoH 数据上训练 doh pd.read_csv(doh_boruta_features.csv) X_doh doh.drop(columns[Label]) y_doh doh[Label] # 在 CTU-13 数据上测试需要先对齐特征列 ctu pd.read_csv(ctu13_boruta_features.csv) # 找到 DoH 和 CTU-13 共有的特征列 common_cols [c for c in X_doh.columns if c in ctu.columns] X_ctu ctu[common_cols] y_ctu ctu[Label] model RandomForestClassifier(n_estimators200, random_state42) model.fit(X_doh[common_cols], y_doh) y_pred model.predict(X_ctu) print(f跨数据集 F1: {f1_score(y_ctu, y_pred, pos_label1):.4f})如果这个跨数据集 F1 远低于数据集内测试的 F1说明两个数据集的分布差异较大模型需要各自训练如果差距不大说明这套特征具有跨场景泛化能力。这个结果对毕业论文的“泛化性讨论”章节非常有用建议如实记录。4. 打开可视化报告HTML 文件里藏着什么分析逻辑4.1 浏览器直接打开 HTML先看结论再看过程项目提供的两个 HTML 文件不需要搭建服务器环境直接用浏览器双击打开就能渲染。show_data_doh.html和show_data_ctu-13.html是典型的 Exploratory Data Analysis 报告内容通常包括各特征的分布直方图、特征与标签的关系、相关性热力图以及类别占比饼图。这些图表的分析价值不在于美观而在于帮你快速形成对数据的直觉判断。我打开后重点看三块。第一块是类别占比如果恶意样本占比低于 5%后续建模必须考虑类别不平衡问题第二块是核心特征的分布正常的特征分布应该是双峰或明显可分如果某些特征在两类样本上分布完全重叠说明这个特征没有区分能力第三块是相关性热力图深色格子集中的区域就是前面相关性筛选要剔除的冗余特征群。4.2 自定义扩展在 Jupyter Notebook 里把分析过程复现出来如果想把可视化分析扩展到自己的数据集或者想在论文里增加更多图表常见做法是在 Jupyter Notebook 里按同样的流程处理。下面这段代码复现了 HTML 报告中核心图表的生成逻辑import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # 读取 Boruta 筛选后的特征 df pd.read_csv(doh_boruta_features.csv) # 设置绘图风格 sns.set_style(whitegrid) plt.rcParams[figure.figsize] (12, 6) # 图1: 类别分布 plt.subplot(1, 2, 1) df[Label].value_counts().plot(kindbar, color[#3498db, #e74c3c]) plt.title(类别分布) plt.xlabel(类别) plt.ylabel(样本数) # 图2: 某个数值特征的分布对比 plt.subplot(1, 2, 2) sns.kdeplot(datadf, xdf.columns[0], hueLabel, fillTrue, alpha0.5) plt.title(f特征 {df.columns[0]} 分组分布) plt.tight_layout() plt.savefig(doh_eda.png, dpi150) plt.show()这段脚本里sns.kdeplot的hueLabel参数会把特征分布按类别拆成两条曲线重叠面积越小说明该特征区分能力越强。fillTrue给曲线加填充色方便视觉对比alpha0.5控制透明度——两条曲线重叠时如果透明度太低会互相遮挡。df.columns[0]只是示例实际使用时要换成你关心的具体特征名比如flow_bytes_per_second或tls_handshake_time。4.3 两个 HTML 文件对论文撰写的支持作用很多同学以为这两个 HTML 文件只是给答辩老师演示用的“花瓶”实际上它们对论文撰写有实质帮助。你在写“数据探索”这一章时可以直接引用 HTML 报告中的图表数据作为特征分布的描述依据比如“图 3-1 展示了 packet_length_mean 在正常流量与 DoH 隧道流量中的分布差异可见恶意样本的分组集中在较大数值区间”。这种表述比凭空描述要可靠得多。更重要的是HTML 报告可以帮助你在写论文前就发现问题。如果打开报告发现特征分布严重偏斜、或者两类样本存在大量重叠区域你可以在论文中直接以“数据特征导致模型局限性”为由解释某些指标不理想的原因而不是等到模型跑完再找理由。5. 复现与部署避坑指南特征对齐、编码与路径问题5.1 特征列不一致跨数据复现时最常见的翻车点我在把 DoH 的特征处理流程往 CTU-13 上迁移时遇到过一个问题两个数据集的原始特征列名不完全一致有的特征是共有字段但命名不同比如 DoH 里叫flow_durationCTU-13 里叫Duration。如果直接 pd.concat 合并两个数据集pandas 会把不同的列名当作不同的特征导致矩阵维度爆炸模型训练时还会报列数不匹配错误。解决方法是先做列名对齐核心代码逻辑如下import pandas as pd doh pd.read_csv(doh_boruta_features.csv) ctu pd.read_csv(ctu13_boruta_features.csv) # 创建特征名映射表需要根据实际文件里的列名调整 col_mapping { flow_duration: Duration, flow_bytes_per_sec: BytesPerSec, flow_packets_per_sec: PacketsPerSec } # 把 CTU-13 的列名统一成 DoH 风格 ctu_renamed ctu.rename(columns{v: k for k, v in col_mapping.items()}) # 只保留两边共有的列 common_cols [c for c in doh.columns if c in ctu_renamed.columns] print(f共有特征列: {len(common_cols)} 个)这段代码的关键是rename(columns{v: k})做了键值反转——原映射表的键是 DoH 的列名、值是 CTU-13 的列名反转后就能把 CTU-13 的列名改成 DoH 的风格。执行完打印共有特征列的数量如果少于原始列数的 60%说明两个数据集的特征交集太少强行统一会导致信息严重丢失。5.2 数据预处理那套锅标准化、缺失值与标签编码决策树类模型随机森林、XGBoost、LightGBM对特征尺度不敏感不做标准化也可以跑出不错的结果但线性模型逻辑回归、SVM对特征尺度高度敏感。如果你的实验要同时跑线性模型和树模型我建议统一做一次标准化from sklearn.preprocessing import StandardScaler import pandas as pd df pd.read_csv(doh_boruta_features.csv) X df.drop(columns[Label]) y df[Label] # 对特征做标准化 scaler StandardScaler() X_scaled pd.DataFrame(scaler.fit_transform(X), columnsX.columns) # 检查每个特征缺失值情况 missing_ratio df.isnull().sum() / len(df) print(missing_ratio[missing_ratio 0.1])StandardScaler会把每个特征变成均值为 0、方差为 1 的分布这在特征取值范围差异很大的场景下非常有效——比如有的特征从 0 到 1有的特征从 0 到 100000线性模型会天然偏向数值大的特征。scaler.fit_transform只应该在训练集上执行fit测试集和新的预测样本要用同一个已经拟合好的scaler.transform这是最基本的防数据泄漏原则。5.3 文件路径中文与编码问题Windows 下的血泪经验这个项目解压后如果路径含中文在 pandas 读取 CSV 时偶尔会报 UnicodeDecodeError尤其是在 Windows 的默认编码环境下。我习惯在读取时显式指定编码方式并用errorsignore容错import pandas as pd # 优先尝试 UTF-8失败后回退到 GBK for enc in [utf-8, gbk, latin1]: try: df pd.read_csv(doh_corr_features.csv, encodingenc) print(f成功用 {enc} 编码读取) break except UnicodeDecodeError: continuelatin1编码是一个兜底选项它几乎是“万能编码”不会抛 UnicodeDecodeError但可能会把中文显示成乱码。做毕设时我一般只在另外两个编码都失败的情况下才用latin1然后单独处理乱码列。另外建议把项目文件放到纯英文路径下比如D:\project\doh_detect\可以避免很多环境问题。5.4 模型结果 CSV 记录的指标口径不统一两个model_result.csv文件里的指标口径可能不一致——比如 DoH 文件记录的是宏平均 F1CTU-13 文件记录的是加权平均 F1直接拿来对比会得出错误的结论。我建议在阅读这两个文件时先确认每列指标的计算方式import pandas as pd df1 pd.read_csv(doh_boruta_model_result.csv) df2 pd.read_csv(ctu13_boruta_model_result.csv) # 看列名差异 print(DoH 结果列:, df1.columns.tolist()) print(CTU-13 结果列:, df2.columns.tolist()) # 如果两边都有 f1 列对比数值分布 if f1 in df1.columns and f1 in df2.columns: print(fDoH F1 均值: {df1[f1].mean():.4f}) print(fCTU-13 F1 均值: {df2[f1].mean():.4f})如果发现两边指标列名不同不要急着合并对比先到项目文档说明里找指标定义。如果文档里没写就在论文中分别描述两个数据集的评估结果不强行做横向比较。答辩时老师问起如实说明“两套数据的评估口径不同分别展示各自表现”是能被接受的。6. 进阶验证思路用十折交叉验证和小样本实验确认模型有效拆完这份资源后我建议你不要只停留在“跑通脚本、输出结果”的层面应该多做一步验证。我一般会额外做两件事来确认模型不是偶然的产物一是把固定的 train/test 划分改成十折交叉验证看指标的波动范围二是把训练样本量砍半观察指标下降的斜率判断模型是真正学到了规律还是记住了样本。十折交叉验证实现很简单from sklearn.model_selection import cross_validate from sklearn.ensemble import RandomForestClassifier import pandas as pd df pd.read_csv(doh_boruta_features.csv) X df.drop(columns[Label]) y df[Label] model RandomForestClassifier(n_estimators200, random_state42, n_jobs-1) # scoring 同时评估多个指标 scoring { f1: f1_macro, precision: precision_macro, recall: recall_macro, auc: roc_auc } results cross_validate( model, X, y, cv10, scoringscoring, return_train_scoreFalse ) for metric in scoring.keys(): scores results[ftest_{metric}] print(f{metric}: {scores.mean():.4f} (/- {scores.std():.4f}))十折交叉验证的输出能让你看到指标的标准差。如果 F1 的标准差超过 0.1说明模型在不同数据子集上表现不稳定可能需要回到特征选择环节或者增加训练数据。return_train_scoreFalse只保留测试结果避免输出太多冗余信息。cv10是十折如果数据量小可以改成cv5两者没有绝对优劣。样本量减半实验的做法是在训练时用np.random.choice随机抽取 50% 的样本训练看指标与全量训练的差距import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import f1_score # 全量训练 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) model_full RandomForestClassifier(n_estimators200, random_state42) model_full.fit(X_train, y_train) f1_full f1_score(y_test, model_full.predict(X_test), pos_label1) # 只用 50% 样本训练 half_idx np.random.RandomState(42).choice( len(X_train), sizeint(len(X_train) * 0.5), replaceFalse ) model_half RandomForestClassifier(n_estimators200, random_state42) model_half.fit(X_train.iloc[half_idx], y_train.iloc[half_idx]) f1_half f1_score(y_test, model_half.predict(X_test), pos_label1) drop_ratio (f1_full - f1_half) / f1_full print(f全量 F1: {f1_full:.4f}) print(f半量 F1: {f1_half:.4f}) print(fF1 下降比例: {drop_ratio:.2%})如果 F1 下降比例在 5% 以内说明数据目前不是瓶颈模型已经充分利用了现有信息的核心规律如果下降超过 15%说明样本量对模型影响大论文里可以写“未来可引入更多标注数据进一步提升性能”。需要注意两次实验都用random_state42固定随机数保证对比是在控制变量的前提下进行的。每次做对比实验我都会强制自己走一遍这个流程——固定随机种子、控制变量、记录指标波动区间这三件事做完实验结果才有底气写进论文。希望帮到你。本文还有配套的精品资源点击获取
返回列表