
简介这是一套基于Python机器学习的入侵检测系统项目代码面向网络安全与机器学习方向的初学者及进阶学习者可作为毕业设计、课程设计、大作业或工程实训的参考实现。压缩包共包含21个文件以Python脚本、XML配置、Markdown说明等类型为主整体大小仅29KB轻量精炼便于快速阅读与修改。核心代码按典型流程组织覆盖网络数据包嗅探、数据预处理与特征提取、基于SVM支持向量机的分类检测等环节并配有算法目录与README说明帮助梳理模型原理和工程结构。项目还保留了IDE配置和版本管理辅助文件方便在本地环境直接运行调试。这种从数据采集到模型检测的完整路径对理解机器学习在安全领域的实际应用很有帮助。目前已有91人学习下载适合希望将机器学习算法落地到安全检测场景的读者也可作为算法比较、特征工程优化或系统功能扩展的起点。1. 基于Python机器学习的入侵检测系统模型不是瓶颈数据和特征才是基于 Python 机器学习的入侵检测系统听上去是把经典安全产品塞进一套标准机器学习流程实际做起来你会发现模型反而是整个链路里最不花时间的一环。真正决定这个系统能不能用的是先想清楚“拿什么数据做检测”和“检测结果谁来看”。我见过太多人把公开流量数据集直接丢给随机森林训练集准确率刷到 99%拉到真实网段一跑误报率高到没人愿意点开告警。这篇文章按我自己的落地路径来讲选数据集、做特征、定评估口径再谈怎么交给运维闭环。适合两类读者有 Python 和机器学习基础、想切入网络安全方向的数据工程师以及安全运维想给现有规则型 IDS 加一个智能检测模块的人。假设你已经装好 Python 3 和 VS Code下面代码在 Jupyter 或脚本里都能直接跑。2. 数据集选型为什么别一上来就抓真实流量做入侵检测系统第一步不是写模型而是找一份能回答“什么是攻击”的数据。真实流量需要打标签这个成本极高而且一旦业务方知道流量被录下来合规和隐私的麻烦比模型难搞得多。公开数据集仍然是入门和验证的首选把数据集选对后面才不至于白干。2.1 四个公开数据集横评哪个能直接用哪个有坑数据集发布时间攻击类型数据形式主要坑KDD Cup 199919994 大类已提取特征的 CSV重复记录极多环境严重过时NSL-KDD20094 大类已提取特征的 CSV同样老旧但干净适合学流程UNSW-NB1520159 大类CSV含原始流量特征字段多需要熟悉特征命名CICIDS2017201714 种攻击PCAP 按天拆分的 CSVCSV 列数不统一、重复行多需拼表清洗我一般会这么选第一次跑通流程用 NSL-KDD它特征已经提取好格式简单社区资料多适合把“训练—评估—调参”这条链路走顺。等你想贴近真实场景再换 CICIDS2017。它最接近现代企业网络形态包含 DDoS、端口扫描、暴力破解等常见攻击但 CSV 按天切割还有列数不一致和重复样本的问题直接读会踩坑。2.2 把 CICIDS2017 按天拆分的 CSV 拼成一张训练表CICIDS2017 的 CSV 文件是按天拆开的周一到周五每天一个或多个文件手工拼表很容易出问题。下面这段代码把同目录下所有 CSV 拼成一张表并做基础清理。import pandas as pd from pathlib import Path data_dir Path(./CICIDS2017) frames [] # 遍历目录下所有 CSV 文件并读取 for f in sorted(data_dir.glob(*.csv)): df pd.read_csv(f, encodingutf-8, low_memoryFalse) frames.append(df) # 纵向拼接 df pd.concat(frames, ignore_indexTrue) # 去重公开数据集里存在大量完全重复的行 df df.drop_duplicates() # 清洗列名去掉空格、替换为下划线 df.columns [c.strip().replace( , _) for c in df.columns] print(df.shape) print(df[Label].value_counts())逻辑说明先循环读取所有 CSV用pd.concat纵向拼接因为每个文件是同一周内不同时间段的流量行之间是并列关系。drop_duplicates这一步很容易被忽略但 CICIDS2017 原始 CSV 里存在重复记录不清理会让模型把重复样本同时分进训练集和测试集评估指标虚高。参数说明encodingutf-8是必须的部分文件包含特殊字符low_memoryFalse让 pandas 一次性推断列类型避免大文件分块读取导致的类型不一致。ignore_indexTrue会重新生成行号防止不同文件里的索引重复。2.3 标签重映射从 14 种攻击到“正常 / 异常”二分类CICIDS2017 的 Label 列既有BENIGN也有DDoS,PortScan,Brute Force等具体攻击名。做第一版检测时没必要分那么细先把攻击类全部归一到 1良性归到 0。label_map {BENIGN: 0} for label in df[Label].unique(): if label ! BENIGN: label_map[label] 1 df[label_bin] df[Label].map(label_map) print(df[label_bin].value_counts())逻辑说明BENIGN保持为 0其余攻击名称全部映射为 1。这样训练集就变成了二分类问题模型输出“是否属于攻击流量”。参数说明如果你后续要识别具体攻击类型比如区分 DDoS 和端口扫描就不要做二分类而是保留原始标签。实际工程里建议先跑通二分类再逐步细分类别否则第一次调参会同时面对多分类和样本不平衡两个问题。到这里数据管线已经通了。很多初学者在“机器学习 应用流程”里会先学模型再找数据真实项目恰恰反着来数据清洗和特征理解花的精力远多于调参。3. 特征工程与模型选型流量先变成特征向量模型才有意义数据集的 CSV 已经帮你把特征提取好了但理解这些特征怎么来是做入侵检测绕不开的一步。如果拿到的是一堆 PCAP 原始流量包你需要自己把网络流量变成模型能吃的特征向量。3.1 从 PCAP 到特征向量流窗口、包统计与熵原始流量要变成表格常见做法是设置一个时间窗口统计窗口内的包数量、字节数、平均包长、时间间隔以及目的端口分布的熵。下面是用tshark把 PCAP 解成一行一个包的 CSV再按 2 秒窗口聚合出特征。import subprocess import pandas as pd import numpy as np pcap_path capture.pcap out_csv packets.csv # 用 tshark 提取时间戳、IP、端口、包长度 cmd [ tshark, -r, pcap_path, -T, fields, -e, frame.time_epoch, -e, ip.src, -e, ip.dst, -e, tcp.srcport, -e, tcp.dstport, -e, frame.len, -E, headery, -E, separator,, ] with open(out_csv, w) as f: subprocess.run(cmd, stdoutf) packets pd.read_csv(out_csv) packets[time_epoch] packets[frame.time_epoch].astype(float) packets packets.sort_values(time_epoch) # 计算连续包之间的到达间隔 packets[inter_arrival] packets[time_epoch].diff().fillna(0) # 按 2 秒窗口聚合特征 packets[window_id] (packets[time_epoch] // 2).astype(int) features packets.groupby(window_id).agg( pkt_count(frame.len, count), bytes_sum(frame.len, sum), mean_len(frame.len, mean), std_len(frame.len, std), inter_mean(inter_arrival, mean), ) print(features.head())逻辑说明frame.time_epoch是 Unix 时间戳作为窗口切分依据frame.len是包长度。窗口内的包数、总字节数、平均包长、包长标准差以及包到达间隔均值共同描述这段流量的形态。比如 DDoS 攻击通常表现为窗口内包数暴增、包长集中而慢速扫描则表现为时间间隔异常。参数说明window2表示 2 秒一个窗口。窗口太小特征稀疏且噪音大窗口太大短促的攻击会被大量正常流量淹没。我做第一版时用 2 秒后续根据告警粒度的需求调成 5 秒或 10 秒。inter_arrival的均值对慢速扫描敏感但要注意它第一个值会是 0需要填充。3.2 第一版模型为什么选树模型而不是深度学习很多人一提到机器学习检测就直接上 LSTM、注意力机制理由是流量是序列数据。这个判断没有错但在工程落地上第一版我会用随机森林或 XGBoost原因有三个。第一表格型结构化数据上树模型的表现一直很稳不需要像深度学习那样精细调参第二特征重要性可以直接输出能告诉安全运营团队“这个模型是根据什么特征判攻击的”第三推理速度快单条样本预测在毫秒级能接在线检测。无监督方案也可以用孤立森林它适合完全没有标签的环境。但实际体验是无监督模型的误报率通常比有监督高因为任何偏离历史分布的流量都会被标成异常而企业网络里业务变化本身就频繁。第一版建议先做有监督把数据标签利用起来。3.3 最小可复现训练代码随机森林跑通二分类下面代码直接用上一章处理好的 CSV 做训练特征列选择除标签和标识字段外的所有数值列。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split import pandas as pd df pd.read_csv(cicids_clean.csv) # 剔除纯标识列和标签列保留数值特征 drop_cols [Flow ID, Src IP, Dst IP, Src Port, Dst Port, Timestamp, Label, label_bin] feature_cols [c for c in df.columns if c not in drop_cols] X df[feature_cols].fillna(0) y df[label_bin] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) clf RandomForestClassifier( n_estimators300, max_depth12, min_samples_leaf5, class_weightbalanced_subsample, n_jobs-1, random_state42, ) clf.fit(X_train, y_train) print(train acc:, clf.score(X_train, y_train)) print(test acc:, clf.score(X_test, y_test))逻辑说明先剔除五元组和时间戳这类标识字段剩下的特征参与训练。fillna(0)处理空值因为 CICIDS2017 某些列在没有对应协议时会留空。stratifyy保证训练集和测试集里正常样本与攻击样本比例一致防止小概率类在切分时被抽空。参数说明n_estimators300是树的数量300 棵以后准确率增长趋缓收益下降max_depth12限制单棵树深度防止过拟合特征数几十个时 12 层足够min_samples_leaf5强制叶子节点至少 5 个样本减少噪声拟合class_weightbalanced_subsample让每棵树的抽样自动按类别频率加权这是处理不平衡数据最省事的做法。提示Src Port这类高基数字段在入侵检测里不能直接进模型。源端口多数情况下是客户端随机分配的模型会把“端口号等于 56789 就是攻击”这种噪声当成规律后面专门讲这个问题。3.4 特征重要性裁剪先跑一版再砍特征模型训练完成后第一件事就是看特征重要性。用随机森林可以直接拿到每个特征的贡献度用来砍掉无效列。import pandas as pd importance pd.Series(clf.feature_importances_, indexfeature_cols) importance.sort_values(ascendingTrue).tail(10).plot.barh()逻辑说明feature_importances_输出每个特征对分类贡献的相对值越靠后的特征贡献越小。排序后画出最低的 10 个特征如果里面有看起来像随机数或标识符的列直接移除重训。特征数量从几十个压缩到十几个模型推理速度会快一个量级在线检测场景收益非常明显。参数说明特征重要性只代表该特征在树分裂时的贡献不代表因果含义。某些高度相关的特征会被树模型分摊重要性看起来单个不重要删掉后模型反而变差。稳妥做法是每次删掉贡献最低的 12 个特征重跑一版对比 F1再继续删。4. 训练策略与评估指标入侵检测的漏报比误报更贵安全场景里漏掉一次攻击的代价远高于误报一次所以评估口径和训练策略不能照搬普通分类任务。这一章讲时间切分、不平衡处理和评估代码。4.1 按时间顺序切分而不是随机切分流量数据有强时间连续性攻击行为往往集中在某几分钟内。如果随机打乱再切分同一个攻击时段的数据会同时出现在训练集和测试集里模型相当于“记住了”这段攻击流量测试指标自然很高。真实部署时模型要预测的是未来流量所以训练集必须在时间上早于测试集。df df.sort_values(Timestamp) # 先按时间排序 split int(len(df) * 0.7) train, test df.iloc[:split], df.iloc[split:]逻辑说明先按时间戳排序再取前 70% 当训练集后 30% 当测试集。这样模拟的是“用过去的行为预测未来”。直接用train_test_split随机切分在这里是错的它会把同一攻击窗口的数据同时分到两边。参数说明7:3 是经验分割比例。如果攻击时段在时间轴上分布不均匀比如大量攻击集中在下半天单纯前 70% 会导致训练集和测试集的攻击类别分布差异大。更稳妥的办法是按天切分比如前 4 天训练、后 1 天测试这更贴近模型上线后的使用方式。4.2 样本不平衡处理先试内置加权再考虑过采样入侵检测数据集里攻击样本占比通常只有个位数百分比。模型如果不做任何处理会把几乎所有样本都判成正常因为这样准确率也能到 90% 以上。处理不平衡优先用class_weight它不改样本数量只改损失函数里的权重。clf RandomForestClassifier( n_estimators300, max_depth12, min_samples_leaf5, class_weightbalanced_subsample, n_jobs-1, random_state42, ) clf.fit(X_train, y_train)逻辑说明balanced_subsample会在每棵树的抽样过程中自动按类别频率调整权重少数类样本的误差被放大模型被迫更重视攻击样本。它比手动sample_weight省事也不增加内存占用。参数说明如果balanced_subsample之后召回率还上不去再用 SMOTE 做特征空间插值。但 SMOTE 在流量数据上要小心它是在特征向量间做线性插值有可能生成现实中不存在的“四不像”流量特征线上反而拖垮准确率。我的经验是先试内置加权效果不够再上 SMOTE一般二分类问题内置加权能撑住。4.3 用混淆矩阵和 F1 评估不再看 accuracy准确率在这个场景会骗人。正常流量占 99% 时模型把所有样本判为正常准确率也有 99%但这个系统没有任何检测能力。安全评估看两个数误报率 FPR 和漏报率 FNR。from sklearn.metrics import confusion_matrix, classification_report y_pred clf.predict(X_test) tn, fp, fn, tp confusion_matrix(y_test, y_pred).ravel() fpr fp / (fp tn) fnr fn / (fn tp) print(FPR (误报率): %.4f % fpr) print(FNR (漏报率): %.4f % fnr) print(classification_report(y_test, y_pred, digits4))逻辑说明confusion_matrix返回的是 [TN, FP, FN, TP] 四个值。FPR 是正常流量被误判为攻击的比例代表误报对运营产生的噪音FNR 是攻击流量被漏判的比例代表系统的失职。安全场景里 FNR 的权重高于 FPR漏掉一次攻击可能造成真实损失误报最多消耗分析师时间。参数说明classification_report里的precision表示判为正类的样本里确实为正类的比例recall表示正类样本里被找出来的比例。入侵检测里优先保recall因为系统可以接受多一些误报来换取少一些漏报。实际项目里我会以“FNR 低于 1%FPR 低于 0.5%”作为一版模型的可上线门槛达不到就回去调特征。5. 避坑五个常见的机器学习入侵检测翻车点从数据清洗到模型上线中间有不少“看起来能跑、一上线就翻车”的坑。下面几条是我做这个方向时踩过的血泪经验按“现象 → 原因 → 解决”写清楚。5.1 随机打乱数据再切分指标虚高 20 个百分点现象训练集和测试集用train_test_split随机切分测试准确率 99%误报率低到接近 0觉得模型已经完美。把同一模型放到第二天真实流量上准确率掉到 80%漏报率翻了几倍。原因流量具有时间局部性攻击往往集中在特定时间窗口。随机切分会把同一攻击窗口的数据同时分进训练集和测试集模型等于提前看到了答案。解决按时间排序后切分保证训练集时间早于测试集。最严格的评估是按“训练周 → 测试周”跨时段评估这样才能证明模型学的是流量规律而不只是记住攻击时段的特征。5.2 数据集重复行没有清洗模型在“背样本”现象用 CICIDS2017 拼表后直接训练评估指标好得不真实但在其他攻击样本上表现很差。原因公开数据集某些 CSV 存在大量完全重复的行。重复样本会同时进入训练集和测试集模型识别到的是“我看过这一行”而不是“攻击流量的规律”。解决在拼接后立即执行drop_duplicates()然后打印训练集和测试集的行数对比。如果测试集里某条样本和训练集完全一致评估就没有意义。更严格一点可以按流五元组加时间戳做去重。5.3 只盯 accuracy模型对攻击样本完全失灵现象模型准确率 99%但查看混淆矩阵发现攻击样本的召回率只有 20%绝大多数攻击都被判成了正常。原因数据不平衡正常样本占绝对多数模型学习到“全判正常”就能获取高准确率。准确率这个指标在不平衡场景下天然偏向多数类无法反映模型对少数类攻击的检测能力。解决用混淆矩阵计算 F1、FPR、FNR 三个指标。上线前把 FNR 作为核心指标漏报率降不下来准确率再高也不能用。5.4 特征里混入源端口号模型学到了“随机数规律”现象模型在训练测试集上 F1 接近 1换成新采集的数据后效果崩塌。原因特征表里包含源端口号、Flow ID 这类高基数标识字段。源端口通常是客户端随机分配的和攻击行为没有必然关系但树模型会强行用它做分裂学到的规律在上线后完全不成立。解决在特征工程阶段明确剔除 Flow ID、Src IP、Dst IP、Src Port、Dst Port、Timestamp 这类标识列。如果业务上需要按流维度聚合特征可以把端口和 IP 转为“是否为常见端口”“是否内外网通信”这类低基数语义特征而不是把原始端口号直接喂给模型。6. 模型上线前的闭环验证从模型输出到安全运营手里的最后一步模型训练完只是拿到了一个“会打分的函数”。真正让入侵检测系统产生价值的是把它接进运营流程形成一个“检测 → 告警 → 处置 → 回流”的闭环。我的习惯是上线前先做一个灰度回放选取最近一周的真实流量按时间顺序让模型逐条打分看误报和漏报在时间轴上的分布。# 按时间顺序回放流量模拟在线检测 test_sorted test.sort_values(Timestamp) for idx, row in test_sorted.iterrows(): score clf.predict_proba(row[feature_cols].values.reshape(1, -1))[0, 1] is_alert score 0.7 # 阈值可调逻辑说明这里用predict_proba取样本被判为攻击的概率而不是直接用 0/1 结果。概率输出可以调阈值比如 0.7 判攻击这样可以通过调整阈值在误报和漏报之间做取舍比重新训练模型灵活得多。我对阈值的调试经验是先在回放集上跑一遍统计不同阈值下的 FPR 和 FNR画出曲线选择漏报率优先达标的最高阈值。运营团队抱怨告警太多就上调阈值安全事件漏了就下调阈值。这个调阈值的流程远比重训模型常见得多。再往后把模型输出接进工单系统。每条告警附上模型预测概率和贡献度最高的三个特征字段安全分析师看到告警时能快速判断是误报还是真攻击。处置结果每周回流一次重新评估阈值和特征形成持续迭代。这个方向值不值得做我的判断是规则型 IDS 能解决已知攻击但面对绕过和变种时非常吃力基于机器学习的检测模块作为补充防御已经是大团队的标准配置。它的门槛不在模型精度的几个百分点而在数据管线和运营闭环是否真的接起来了。我的习惯是每周重新跑一次回放评估记录这一周误报和漏报的分布发现某个攻击类别的召回率连续三周下降就回到特征工程去看那一类攻击的数据分布发生了哪些变化。这套“持续回放、小步调参”的节奏比一开始就憋大招训练一个大模型踏实得多。希望这些经验能帮你少踩几个坑更快把这个方向落地到自己的环境里。本文还有配套的精品资源点击获取