简介:一套基于CNN与LSTM组合模型的网络流量检测系统Python源码,面向高校计算机、人工智能、通信工程、自动化等专业学生的课程设计、期末作业、项目演示及入门进阶实践,也适合作为毕设基础。项目在本地测试可运行,课设评审分达95分以上,涵盖数据预处理、模型搭建、训练评估与主流程调用等完整环节,配套文档说明,能帮助读者理解流量特征提取与序列建模思路,并可根据自身需求二次修改扩展。压缩包共6个文件,其中5个py脚本分别对应数据预处理、模型定义、训练测试、入口调用等功能模块,1个txt为文档说明,整包仅6KB,轻量便捷,适合快速部署与本地运行。目前已有296人学习/下载,适合需要完成课设、毕设或初探深度学习流量识别场景的开发者。
1. 基于CNN+LSTM的网络流量检测系统课设:这套源码方案到底值不值得你投入
网络流量检测是网络安全方向课设里最"能打"的选题之一,而CNN+LSTM是这个场景下最稳妥的深度学习组合。原因是网络流量本质上是按时间排列的packet序列,CNN擅长从局部包模式里提取空间特征,LSTM擅长吃下跨几十上百个包的时间依赖,两者叠加正好覆盖流量检测这类时序建模任务。这篇文章面向想用Python源码快速跑通课设、又不想只在PPT上画架构图的学生,以及需要在内网验证流量异常检测可行性的工程师。我会从数据预处理、模型搭建、训练评估到真实部署验证,把每一步命令、参数和踩过的坑完整展开。
2. 流量检测建模选型:为什么这个场景偏偏吃CNN+LSTM这套组合
2.1 网络流量数据不是普通表格:从pcap到会话矩阵的转换思路
很多课设做到一半翻车,根源是把流量数据当成了普通分类表格。pcap文件里记录的是一串独立报文,每条报文有IP头、TCP/UDP头、长度和负载,单独看每个包几乎分辨不出攻击行为——DDoS是大量短连接在几秒内涌入,端口扫描是同一个源IP在短时间内探测多个端口,C2通信则是稳定的长连接加规律性心跳包。这些行为模式全部藏在"包与包之间的关系"里,所以第一步必须把pcap重组成有意义的结构。
业界的主流做法是把原始pcap转换成"流"级样本。流的概念来源于NetFlow:五元组(源IP、目的IP、源端口、目的端口、协议号)相同的双向包归为一条流。比如一个源IP对另一个IP发起扫描,源端口随机但目的端口固定,五元组聚合后就能看到一条流里大量目的端口相似的包。这样做之后,每个样本不再是一个孤立的包,而是一串有序包序列,深度学习模型才有东西可以学。
流还有一个必须处理的边界问题:TCP连接有FIN/RST标志可以判定结束,UDP没有明确的结束信号。常见做法是设定idle timeout超时阈值,比如一条流超过80秒没有新包到达就判定会话结束。这个参数直接影响样本形态,取值太短会把长连接截断成多条小流,取值太长会让同一条流里混入前后两个不同攻击阶段的数据。我的习惯是结合数据集特点去设,UNSW-NB15这类基准数据集通常包间隔密集,30到80秒都合理,真实内网环境则要先用统计工具看会话时长的分布。
2.2 CNN做空间特征提取、LSTM做时序依赖:两者如何分工
分清楚CNN和LSTM各自负责哪一层,是模型结构设计的核心。Conv1d在包的序列上按kernel size滑动窗口,提取的是连续K个包之间的局部模式:比如连续三个包的载荷长度突然从几百字节掉到几十字节、TCP标志位出现SYN和ACK交替的规律,这些都是卷积核能捕捉的短程特征。因为Conv1d的卷积核是共享的,它对"模式出现的位置"并不敏感,这对流量检测非常合适——攻击特征不一定出现在流的开头,可能藏在中间任意位置。
LSTM则负责把这些局部特征串起来。网络流量里的攻击行为往往有长期依赖:一个完整的端口扫描可能要跨越几百个包才表现出"某IP在系统性地探测开放端口"这个语义,单看每5个包的局部模式不够。LSTM的遗忘门机制可以决定保留多长的历史信息,双向LSTM则同时看到当前包之前和之后的上下文,在流量检测里通常比单向结构多3到5个百分点的F1。
这里要打击一个误区:不是LSTM不能直接吃原始特征,而是直接拿80维原始数值喂给LSTM,收敛速度会非常慢。CNN可以先做一次降维和特征提纯,把raw features压缩成更高层次的表达,LSTM再吃压缩后的序列,整体训练速度和稳定性都会好不少。实际调参中这条经验比任何网络结构创新都管用。
2.3 公开数据集怎么选:UNSW-NB15、CICIDS2017和自采流量的取舍
| 数据集 | 样本形式 | 攻击类型 | 适合场景 | 主要坑 |
|---|---|---|---|---|
| UNSW-NB15 | 已提取特征的CSV | Fuzzers、DoS、Exploits、Generic、Reconnaissance等9类 | 课设常规选择,标注规范、类别全 | 数据文件里含大量归一化特征,需自行分析特征含义 |
| CICIDS2017 | 原始pcap和CSV | DDoS、暴力破解、Web攻击、端口扫描等 | 论文benchmark对比 | 样本不均衡极严重,正常流量占比高达80%以上 |
| 自采流量 | 自抓pcap | 自己打标签 | 验证真实环境效果 | 标签成本高,攻击流量难模拟 |
课设最推荐UNSW-NB15,原因是它结构干净:每条记录已经是数值特征,不需要你处理pcap抓包,可以专注在模型部分。CICIDS2017更适合想做完整pcap解析链路的人,但它的类别不均衡会让初学的训练过程非常难看。自采流量我不建议作为主要数据集,课设阶段想凑出足够的攻击样本太难了,可以作为训练后泛化验证的补充。
3. 数据预处理实操:从抓包文件到模型能吃的张量
3.1 五元组分流与会话切分:把杂乱报文变成有序序列
拿到pcap之后,我一般先用tshark把需要的报文头字段批量导出成CSV,而不是直接用scapy解析。原因很直接:tshark是C实现的,处理几个GB的pcap不会卡死,而且字段名标准化,不用自己跟字节偏移搏斗。导出的常用命令长这样:
tshark -r input.pcap -T fields \ -E header=y -E separator=, \ -e frame.time_epoch \ -e ip.src -e ip.dst \ -e tcp.srcport -e tcp.dstport \ -e udp.srcport -e udp.dstport \ -e ip.proto \ -e frame.len \ -e tcp.flags.syn -e tcp.flags.ack \ -e tcp.window_size -e tcp.len \ -e tcp.options.timestamp.tsval > raw_flows.csv-e参数指定导出的字段,header=y让第一行带字段名,separator=,指定分隔符。这套组合导出的字段足够搭建最基础的流量检测特征:时间戳用来算包间隔,IP和端口用来做五元组分流,frame.len记录包总长度,TCP标志位和窗口值能反映连接交互模式。注意tshark对不是TCP的包,tcp开头的字段会直接留空,所以CSV里会有大量空值,这一步先不处理,留到数值化时再说。
CSV拿到后进入流切分阶段,核心逻辑是按五元组聚合再按超时切开:
import pandas as pd from collections import defaultdict def split_flows(df, idle_timeout=80): """ 按五元组聚合报文,并按 idle_timeout 切分会话。 返回一个list,每个元素是一条流(按时间排序的报文list)。 """ # 先补全端口字段:TCP/ UDP 端口合成一列 df["src_port"] = df["tcp.srcport"].fillna(df["udp.srcport"]) df["dst_port"] = df["tcp.dstport"].fillna(df["udp.dstport"]) df = df.dropna(subset=["ip.src", "ip.dst", "src_port", "dst_port"]) raw_groups = defaultdict(list) for row in df.itertuples(index=False): # 双向流归并:源和目的互换也归到同一组 ip_pair = tuple(sorted((row.ip_src, row.ip_dst))) port_pair = tuple(sorted((int(row.src_port), int(row.dst_port)))) key = (row.ip_proto, ip_pair, port_pair) raw_groups[key].append(row) flows = [] for rows in raw_groups.values(): rows.sort(key=lambda r: r.frame_time_epoch) current = [] last_ts = None for r in rows: if last_ts is not None and r.frame_time_epoch - last_ts > idle_timeout: if current: flows.append(current) current = [] current.append(r) last_ts = r.frame_time_epoch if current: flows.append(current) return flows代码里的几个关键点:sorted函数处理IP和端口对,保证客户端和服务端的往返包进入同一条流,否则一条TCP连接会被拆成两条单向流,后续LSTM完全学不到"请求-响应"的交互模式;按frame.time_epoch做二次排序很重要,pcap里的包不一定严格按时间排列,乱序会影响时间差特征的准确性;idle_timeout传80秒是UNSW-NB15常见配置,如果发现切出来的流平均长度普遍偏短,可以先降低到30秒试跑对比。
3.2 特征数值化与归一化:让CNN和LSTM读到同一尺度
流切好之后要做的不是直接喂模型,而是把每条流变成固定形状的张量。一条流里有几百个包,每个包选几个数值特征,就得到一个维度为[包数, 特征数]的矩阵。我在课设里常用5个基础特征:当前包与上一个包的时间间隔、包总长度frame.len、SYN标志位、ACK标志位、TCP负载长度tcp.len与总长度的比值。SYN和ACK能直接反映连接握手和应答节奏,这对识别扫描和暴力破解非常有效。
构建序列特征矩阵的代码:
import numpy as np def build_sequence_matrix(flow, max_len=150, feature_dim=5): """ 把一条流转换成 [max_len, feature_dim] 的数值矩阵。 超过max_len截断,不足则补零。 """ seq = [] prev_ts = None for r in flow: if prev_ts is None: interval = 0.0 else: interval = min(r.frame_time_epoch - prev_ts, 65535.0) prev_ts = r.frame_time_epoch tcp_len = float(r.tcp_len) if pd.notna(r.tcp_len) else 0.0 frame_len = float(r.frame_len) syn = 1.0 if (pd.notna(r.tcp_flags_syn) and r.tcp_flags_syn == 1) else 0.0 ack = 1.0 if (pd.notna(r.tcp_flags_ack) and r.tcp_flags_ack == 1) else 0.0 seq.append([ interval, frame_len, syn, ack, frame_len / max(tcp_len, 1.0) ]) if len(seq) >= max_len: break # 不够长的流用零向量补齐,保证dataloader能拼成batch while len(seq) < max_len: seq.append([0.0] * feature_dim) return np.array(seq[:max_len], dtype=np.float32)帧长和TCP负载长度的比值这里有个细节:可能为1也可能为几十,因为frame_len包含IP头和TCP头,而tcp_len只有TCP payload。如果你采集的是加密流量,这个值会偏小且稳定,因为它反映的是负载的真实字节数。
数据集里所有流都转成矩阵后,要做一个全局归一化,不归一化直接训练多层CNN基本等于玄学调参。用StandardScaler对三维张量展开后的特征维度做标准化:
from sklearn.preprocessing import StandardScaler def normalize_sequences(all_sequences): """ all_sequences: [n_flows, seq_len, feature_dim] 只在每个特征维度上做z-score标准化。 """ n_flows, seq_len, feat_dim = all_sequences.shape flat = all_sequences.reshape(-1, feat_dim) scaler = StandardScaler() flat_scaled = scaler.fit_transform(flat) return flat_scaled.reshape(n_flows, seq_len, feat_dim)这个实现有个小坑:补零的padding部分也被纳入均值和方差计算。严格做法是先对非padding行做统计,再把padding位置设成0,否则标准化后零向量会变成负数向量,相当于给模型注入了错误信息。课设阶段如果padding比例不大,问题不严重,但我在后面会把这一点写进避坑手册,它是测试集和真实场景表现不一致的原因之一。
3.3 样本不均衡处理:类别权重与SMOTE的实际调参
流量数据集的正常样本占比通常超过80%,直接把原始数据喂给模型会出现"全预测正常类也能有80%准确率"的假象。处理不均衡有两个层次:训练损失层面和数据层面。
最简单的做法是在CrossEntropyLoss里传class_weight,让少数类(攻击类)的单样本损失权重更高:
import torch # 假设类别0是正常流量,类别1是攻击流量 # 按样本比例的反比设置权重,例如正常:攻击 = 4:1 class_weight = torch.tensor([1.0, 4.0]) loss_fn = torch.nn.CrossEntropyLoss(weight=class_weight)weight参数的数值含义是:损失值乘上对应类别权重。正常类是1.0、攻击类是4.0,相当于每一条攻击样本的损失被放大4倍,模型会偏向把梯度投向攻击类。实际调参时weight不要设太极端,超过10倍容易让模型把大量正常流量误判成攻击,F1反而掉下来。
数据层面的SMOTE需要特别小心。流量样本是变长序列,直接对[seq_len, feature_dim]的三维张量做SMOTE插值会生成不存在的包模式,训练出来的模型在真实流量上几乎必挂。如果一定要用SMOTE,正确姿势是把每条流先池化成统计特征向量——比如把包间隔、包长度分布等压缩成25到50维的统计量,在统计向量上做SMOTE,再用生成样本补充训练集。课设阶段我建议直接用class_weight,既简单又能清楚解释,答辩时被问到也容易说圆。
4. 搭建并训练CNN+LSTM模型:PyTorch实现与参数细调
4.1 模型结构:Conv1d、BiLSTM与Attention的组合落地
模型搭建我在PyTorch里用Conv1d加双向LSTM加attention softmax聚合的方式。整体思路是:卷积层沿时间维滑动,把局部包模式抽象成高维特征;双向LSTM再对该序列建模,输出的每个时间步隐状态经过注意力加权合并成整条流的一个向量;最后过一个全连接层输出正常/恶意两类。
import torch import torch.nn as nn class FlowClassifier(nn.Module): """ 基于CNN + BiLSTM + Attention的流量二分类模型。 输入x形状: [batch, seq_len, feature_dim] 输出: [batch, 2] 的logits """ def __init__(self, input_dim=5, hidden_dim=128, num_layers=2, dropout=0.3): super().__init__() # 卷积层:沿序列维度提取局部包模式 self.conv1 = nn.Sequential( nn.Conv1d(input_dim, 64, kernel_size=3, padding=1), nn.BatchNorm1d(64), nn.ReLU(), ) self.conv2 = nn.Sequential( nn.Conv1d(64, 128, kernel_size=3, padding=1), nn.BatchNorm1d(128), nn.ReLU(), ) # 双向LSTM对卷积序列建模 self.lstm = nn.LSTM( input_size=128, hidden_size=hidden_dim, num_layers=num_layers, batch_first=True, bidirectional=True, dropout=dropout ) # 注意力打分层 self.attn = nn.Linear(hidden_dim * 2, 1, bias=False) self.classifier = nn.Sequential( nn.Dropout(dropout), nn.Linear(hidden_dim * 2, 2) ) def forward(self, x): # [batch, seq_len, feat_dim] -> [batch, feat_dim, seq_len] x = x.permute(0, 2, 1) x = self.conv1(x) x = self.conv2(x) # 回到 [batch, seq_len, 128],对齐LSTM的输入格式 x = x.permute(0, 2, 1) lstm_out, _ = self.lstm(x) # [batch, seq_len, hidden_dim*2] # 注意力权重:每个时间步一个标量,softmax归一化 attn_w = torch.softmax(self.attn(lstm_out), dim=1) # 加权求和得到整个流的向量表示 context = torch.sum(lstm_out * attn_w, dim=1) return self.classifier(context)几个参数的选型理由要说明白。kernel_size取3是因为流量模式通常是短程的,2到5个包之间已经能看到显著的握手或突变行为,更大的核不会提升效果反而增加参数量。BatchNorm放在Conv1d和ReLU之间是标准做法,它能缓解梯度传播问题,让深层网络训练稳定。双向LSTM的hidden_dim取128,双向叠加后输出维度是256,这个大小对课设数据量是够用的;如果数据量只有几万条流,hidden_dim加到256容易过拟合。Attention用的是最简单的加性打分,没有引入多头机制,因为流量检测的数据量和任务复杂度用不上更重的结构。
4.2 训练循环与超参配置:lr、batch_size、seq_len怎么配合
训练环节最容易失控的是超参组合。我的参考基线是:lr=1e-3、batch_size=128、seq_len=150、epochs=30、AdamW优化器加CosineAnnealing学习率调度。seq_len这个参数比很多人想象的重要——把每条流截断到前150个包,已经能覆盖绝大多数检测任务的关键交互;强行取500个包会因为padding比例变高,让模型学到大量无意义的零向量。
def train_one_epoch(model, loader, optimizer, loss_fn, device, max_grad_norm=5.0): model.train() total_loss = 0.0 for x, y in loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() logits = model(x) loss = loss_fn(logits, y) loss.backward() # 梯度裁剪:LSTM训练中防止梯度爆炸的关键 torch.nn.utils.clip_grad_norm_(model.parameters(), max_grad_norm) optimizer.step() total_loss += loss.item() return total_loss / len(loader)梯度裁剪这一行我建议永远保留,max_grad_norm取5.0。双向LSTM的反向传播路径很长,梯度爆炸一旦发生,loss会突然变成nan,而且不是每次都触发,属于那种"偶尔翻车、很难复现"的问题。裁剪后即使偶发梯度异常的样本,也不会毁掉整个训练状态。
完整训练的调用放到脚本里执行:
model = FlowClassifier(input_dim=5).to(device) optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30) loss_fn = torch.nn.CrossEntropyLoss(weight=class_weight) for epoch in range(30): avg_loss = train_one_epoch(model, train_loader, optimizer, loss_fn, device) scheduler.step() # 每个epoch结束后在验证集上算一次F1,保留最优模型权重 val_f1 = evaluate_f1(model, val_loader, device) print(f"epoch={epoch:02d} loss={avg_loss:.4f} val_f1={val_f1:.4f}")weight_decay设1e-4是个微妙的平衡点:太大让模型欠拟合,太小起不到正则作用。CosineAnnealing配合这个任务效果稳定,因为它前期保持较高学习率快速收敛,后期平滑降低学习率微调局部结构,比固定学习率跑到底通常多2到3个点的F1。
4.3 阈值选择与评估协议:用F1和混淆矩阵代替单一准确率
流量检测里准确率是最有欺骗性的指标。正常样本占80%时,全预测正常准确率就是80%,但这显然不是你要的系统。课设答辩时把F1和混淆矩阵亮出来,比任何花哨的模型图都说明问题。混淆矩阵要关注两个数:正常被误判成攻击的比例和攻击被漏掉的比例。前者决定运维成本,后者决定安全风险。
import numpy as np from sklearn.metrics import f1_score, confusion_matrix # 在验证集上输出每个样本预测为攻击类的概率 def evaluate_f1(model, val_loader, device): model.eval() probs, labels = [], [] with torch.no_grad(): for x, y in val_loader: x = x.to(device) logits = model(x) probs.extend(torch.softmax(logits, dim=1)[:, 1].cpu().numpy()) labels.extend(y.numpy()) probs = np.array(probs) labels = np.array(labels) # 在验证集上搜索最优阈值,而不是默认用0.5 best_th, best_f1 = 0.5, 0.0 for th in np.arange(0.1, 0.9, 0.05): cur_f1 = f1_score(labels, probs > th) if cur_f1 > best_f1: best_th, best_f1 = th, cur_f1 preds = probs > best_th cm = confusion_matrix(labels, preds) return best_f1阈值搜索这个细节很多人不知道。模型输出的softmax概率不是天然适合0.5这个阈值的,类别不均衡下模型输出的概率会偏移。在验证集上从0.1到0.9网格搜索F1最高的阈值,通常能比默认0.5高3到5个点。这个阈值要保存下来,测试集评估时用同一个阈值,不能拿测试集再调一次,否则就是数据泄露。
5. CNN+LSTM流量检测避坑手册:从Loss不降到真实流量失效的排查路径
5.1 训练Loss下降缓慢,验证集准确率卡在80%上不去
现象:训练15个epoch后Loss还在0.6附近徘徊,验证集准确率稳定在80%,把预测结果打印出来发现几乎全是正常类。
原因:这是两类问题叠加。第一,特征尺度没做归一化,frame_len是1500字节级别的数值,而时间间隔是0到几秒的小数,Conv1d的权重更新被大数值维度主导;第二,class_weight没有生效,模型预测倾向还是被多数类拉走。
解决:回到数据集先重新做一次StandardScaler,并且检查归一化后每个特征的均值是否接近0、方差是否接近1;class_weight在损失函数里传参后,打印一个batch的loss值确认加权生效;把lr从1e-3降到3e-4再跑5个epoch对比,如果F1涨了说明原始lr偏高。
5.2 测试集表现好,换成实时抓包后检测率暴跌
现象:在UNSW-NB15划分的测试集上F1达到0.93,把自己环境里抓的真实流量放进去,攻击检出率直接掉到30%。
原因:首先是数据泄露,这是最常见的坑。比如特征里用了整条流的统计值——平均包长、总包数、累计传输字节——这些统计量在划分数据集时就和标签一起出现了。模型学到的是"总包数多就是攻击"这类后验规律,一旦换成只看了前50个包的实时场景,这些统计量根本算不出来。其次是分布漂移,UNSW-NB15是仿真环境流量,和真实网络里服务类型、流量占比都有差异。
解决:严格按时间顺序划分数据集,前70%训练、后15%验证、最后15%测试,保证测试集的时间窗口在训练集之后;流量统计特征全部改成"截至当前包的前缀累计值",而不是全流统计值;拿到真实pcap后先做一次特征分布对比,对偏移最大的几个特征做经验校准,比如把自采流量的包长分布对齐到训练集的量纲。
5.3 双向LSTM一加就显存溢出(OOM)
现象:hidden_dim提到256、num_layers设为3,训练第一个batch直接报CUDA out of memory,换小batch_size降到16依然炸。
原因:双向LSTM在时间维上的显存占用是batch × seq_len × hidden_dim × num_layers × 4字节再乘两个方向,seq_len=150时中间要保存的隐状态和梯度非常大。attention层还会额外算一个[batch, seq_len, seq_len]的权重矩阵。
解决:先砍hidden_dim到128、num_layers保持2层跑通基线;batch_size设为64以下,如果显存仍然吃紧可以用梯度累积替代直接加大batch:
# 等效batch_size=128,但显存占用和batch_size=32一样 real_batch = 32 accum_steps = 4 for i, (x, y) in enumerate(loader): loss = loss_fn(model(x), y) / accum_steps loss.backward() if (i + 1) % accum_steps == 0: optimizer.step() optimizer.zero_grad()loss除以accum_steps是必须的,否则累积梯度后等效batch增大了、但学习率没变,等效动量变大,loss会震荡。这个技巧在课设里被问起来也很加分,说明你理解显存和梯度之间的关系。
5.4 卷积层堆叠过深,LSTM反而起不到作用
现象:把Conv1d加到4层,中间插入MaxPool1d(kernel_size=8, stride=8),训练后发现lstm_out输出不同时间步的向量几乎都相同,最终分类完全靠CNN的输出,LSTM形同虚设。
原因:最大池化把seq_len从150压到不足20,LSTM只能看到十几个时间步的长序列,长期依赖信息被池化操作提前抹掉了。CNN本质上是局部特征提取器,池化下采样确实能减小计算量,但流量检测需要保持足够的时间分辨率让LSTM发挥作用。
解决:卷积层控制在2层,只做特征通道扩展不做时间维大幅压缩;如果一定要加池化,kernel_size取2、stride取2把150压到75,保留足够的时间步给LSTM。判断LSTM是否有效有个简单方法:训练完成后随机挑几个验证集样本,打印attention权重分布,如果权重几乎均匀分布在所有时间步上,说明模型没有学到关键时序位置。
6. 验证与交付:坐实检测率,再把课设往前推一步
6.1 用独立时间窗口数据做泛化验证,补全课设文档
模型训练完不要直接画图收工。先做一次严格的时间窗口验证:把原始数据集按时间排序后切出最后15%作为final_test,这个集合在训练和阈值选择阶段都不允许被碰。训练阶段只停留在train和val上,最后才在final_test上评估一次,记录的F1、召回率、误报率才是可交付的数字。
课设的说明文档按这个顺序写就够了:数据来源与预处理步骤、模型结构图和每一层的参数、训练超参与曲线、验证集混淆矩阵、与单CNN或单LSTM基线的对比。其中对比实验是答辩时的加分项,因为CNN和LSTM各自贡献了多少,一张对比表说得最清楚。
6.2 从二分类到多分类:识别具体攻击类型
如果做完二分类还想往深走一步,把模型的输出维度从2改成UNSW-NB15的9个攻击类别加正常类共10类即可。损失函数仍然用CrossEntropyLoss,但class_weight要改成按每个类别的样本比例倒数计算,评估指标改用macro-F1而不是整体准确率。多分类能让课设从"能检测"上升到"能定位问题",这也是企业做NIDS时真正需要的。位置方向做深之后,流量检测的价值就不再是跑通一个模型,而是形成一条从抓包、切流、建模到部署验证的完整方法论。我现在拿到一个新的pcap,会先按这个路径快速过一遍再做精细调参,这套流程希望帮到你。
本文还有配套的精品资源,点击获取