简介:本资源是一套完整的基于Python与卷积神经网络(CNN)实现的网络入侵检测毕业设计项目,面向计算机科学、网络安全、人工智能等专业的本科生及初学者,解决传统机器学习方法在流量特征提取能力弱、检测精度低等问题,适用于毕业设计、课程设计及期末大作业等实践场景。压缩包共33个文件,含12个CSV格式的NSL-KDD数据集样本、7个XML配置与IDE工程文件、4个核心Python脚本(Train.py、Predict.py、CNNMould.py、PreHandle.py)、2个模型评估结果图(accuracy.jpg、precision.jpg)、1个训练最优模型文件(best_model.pth)及README.md说明文档等,整体大小为21.58MB,结构清晰、模块解耦明确。目前已有114人学习下载,所有代码经导师指导并高分通过(评审99分),附带完整数据预处理、模型训练、测试预测与可视化全流程,小白可直接运行,无需额外调试,配套文档详述环境配置、参数说明与结果分析逻辑。 我做完这个基于Python+CNN的入侵检测毕业设计后,最大的感受就是:这个题目看起来"满大街都是",但真正能把数据集处理明白、把模型调通、把论文写顺的人,其实不多。很多同学卡在第一步都不知道怎么把NSL-KDD里的字符串特征喂给卷积神经网络。这篇文章我会把整套项目的核心思路、代码细节、踩坑记录都说清楚,内容基于我当时的答辩版本和后期改进版整理,适合正在做毕业设计、或者想入门深度学习在网络安全方向应用的同学直接参考。
先说这个项目是什么:用Python读取公开的网络入侵检测数据集,经过清洗、编码、归一化之后,把每条网络连接记录变成一个向量输入,送入CNN(一维卷积神经网络)里做分类,最终实现"正常流量"和"攻击流量"的识别,也可以进一步细分出DoS、Probe、R2L、U2R等攻击类型。它解决了网络安全里的一个核心问题:在海量网络流量里快速发现异常行为。整个项目包含可运行的源码、可复现的数据集处理脚本、模型训练与评估代码,以及配套的毕设文档说明,属于典型的"有代码、有数据、有论文"的完整课题。
1. 项目整体设计与思路拆解
1.1 为什么网络入侵检测会用到CNN
网络安全领域做入侵检测,传统方法分为两类:基于签名的检测和基于异常的检测。签名检测就像杀毒软件查病毒库,只能识别已知攻击,遇到变种就直接躺平。基于异常的检测思路不一样,它先学习"正常行为长什么样",再通过与正常行为模型的偏离程度来判断是否被攻击。但传统机器学习方法,比如决策树、朴素贝叶斯、SVM,都依赖人工做特征工程,你得手动去设计哪些特征能区分攻击流量,这活儿既费时间又依赖专家经验。
CNN在这里的价值,在于它不需要太多手工特征工程。你可以把一条网络连接里的41个特征值看成一组"一维信号",卷积核在这个信号上滑动,自动提取不同特征之间的局部相关性。比如说,某个攻击类型会同时造成"连接时长异常"和"发送字节数突增",这种组合关系卷积核是可以自己学出来的。用生活化类比:传统方法像医生靠经验看化验单上的某个指标是否超标,CNN像让一个智能系统同时观察所有指标的变化趋势,找出它们之间的异常联动模式。
我在实测中验证过,在NSL-KDD数据集上,CNN的分类效果可以稳定超过普通的多层感知机和大多数传统机器学习模型,尤其在一些复杂攻击类型的识别上优势更明显。
1.2 数据集选型:NSL-KDD的前世今生
做入侵检测的公开数据集,主流的有KDD Cup 99、NSL-KDD、UNSW-NB15、CICIDS2017这几种。我强烈建议毕设选NSL-KDD,原因有两点。第一,它解决了KDD Cup 99最让人头疼的问题:训练集中大量重复记录导致模型严重偏向样本多的类别。NSL-KDD去除了冗余,让训练集和测试集的分布更合理。第二,它的特征维度是41维,做特征工程和模型输入都很方便,论文里也好解释,不会像CICIDS2017那样一上来就是两百多个特征。
NSL-KDD的原始记录长这样:
0,tcp,http,SF,181,5450,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,8,8,0.00,0.00,0.00,0.00,1.00,0.00,0.00,9,9,1.00,0.00,0.10,0.00,0.00,0.00,0.00,0.00,normal最后一位是标签,前面41个是特征。攻击标签有很多细分名称,比如neptune、warezclient、buffer_overflow这些,这其实也给了一个设计点:你可以做二分类(正常/异常),也可以做五分类(将攻击归为DoS、Probe、R2L、U2R四大类),甚至可以做细粒度多分类。毕设建议做到五分类,既有区分度又不至于太复杂,论文里还能写明各类别的识别效果差异。
1.3 环境搭建与技术选型分析
这个项目我用的核心依赖如下:
- Python 3.8或3.9
- TensorFlow 2.x(Keras风格写法)
- scikit-learn 1.x
- pandas、numpy、matplotlib
这里有个取舍要说明一下。深度学习框架我为什么推荐TensorFlow而不是PyTorch?不是说PyTorch不好,而是从毕设答辩的角度讲,TensorFlow的Keras高层API写起来代码更短,模型结构一目了然,对新手非常友好。我见过不少同学用PyTorch写了200行代码,还没开始训练就在报错调bug。Keras的Sequential模型几行就能堆出一个CNN,而且训练过程的日志信息更直观。当然了,如果你导师组里都用PyTorch,那可以换,但本文的代码思路是通用的,换框架只需要改模型定义部分。
还有一个细节:CNN处理表格型数据时,有一个版本差异需要特别注意。经典LeNet、ResNet这些模型是为图像设计的,输入是二维矩阵,用的是Conv2D。而网络流量记录是一维向量,所以在入侵检测里一般用Conv1D,也就是一维卷积,卷积核只沿一个方向滑动。我见过有人强行把41维特征还原成类似图像的二维矩阵再喂给Conv2D,效果并没有变好,反而增加了计算量,意义不大。
2. 数据集预处理与CNN模型构建细节
2.1 数据清洗与特征编码:从原始记录到模型输入
NSL-KDD的41个特征分成四组:基础TCP连接特征(如持续时间、协议类型、端口)、内容特征(如登录失败的次数)、流量特征(如过去两秒内与同一主机连接的次数)、以及主机流量统计特征。这意味着特征之间量纲差异非常大,有的特征值是0和1,有的能到几百甚至上千。
预处理分三步走,顺序不能乱:
第一步,字符串编码。数据里有三个字段是字符型的:protocol_type(如tcp、udp)、service(如http、ftp)、flag(如SF、REJ)。这些不能直接输给模型,必须转成数字。最简单的方法是先用LabelEncoder对不同取值编号,再用One-Hot编码扩展成多个0/1列。我用的是pandas里的get_dummies,它会自动把字符串列变成One-Hot形式。注意:训练集和测试集在做get_dummies之后,列数可能不一致(比如测试集里出现了训练集没有的service值),这时候要手动对齐列,缺失列补0。
第二步,标签映射。把原始攻击名归并成大类:
attack_map = { 'normal': 0, 'back': 1, 'land': 1, 'neptune': 1, 'pod': 1, 'smurf': 1, 'teardrop': 1, 'ipsweep': 2, 'nmap': 2, 'portsweep': 2, 'satan': 2, 'ftp_write': 3, 'guess_passwd': 3, 'imap': 3, 'multihop': 3, 'phf': 3, 'spy': 3, 'warezclient': 3, 'warezmaster': 3, 'buffer_overflow': 4, 'loadmodule': 4, 'perl': 4, 'rootkit': 4 }这套映射关系很多初学同学会写错,特别是把warezclient归错了类。建议对着KDD文档核对,不然测试集效果会莫名其妙地崩。
第三步,数值归一化。这一步不能省。因为CNN内部的激活函数大多对输入的数值范围敏感,如果有的特征是几千,有的是0.01,模型训练会非常不稳定。我用的是MinMaxScaler,把所有特征归一化到0到1之间。注意:fit的时候只能fit训练集,然后transform训练集和测试集,千万不要对测试集单独fit,否则就是数据泄露,测试结果会虚高,答辩时被老师一问就露馅。
2.2 CNN模型结构设计:参数量与原理说明
预处理后的特征维度大概在120左右(41个原始特征加上One-Hot扩展)。我设计的模型结构如下:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, Conv1D, GlobalMaxPooling1D, Input def build_cnn_model(input_dim, num_classes): model = Sequential([ Input(shape=(input_dim, 1)), Conv1D(filters=64, kernel_size=3, activation='relu', padding='same'), Conv1D(filters=128, kernel_size=3, activation='relu', padding='same'), GlobalMaxPooling1D(), Dense(128, activation='relu'), Dropout(0.5), Dense(num_classes, activation='softmax') ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) return model这里有个容易被忽略的点:Conv1D期望的输入形状是(batch_size, steps, channels),也就是每一行记录要reshape成(特征数, 1)。所以在喂数据之前要做X_train = X_train.values.reshape(-1, input_dim, 1)。
再说说设计理由:
- 第一层64个卷积核,负责提取低阶特征组合;第二层128个卷积核,在低阶特征基础上提取更抽象的模式。这种"宽度逐层翻倍"的做法在CNN里很常见,原因是越靠后的层需要越多的特征映射来捕捉更复杂的组合。
- kernel_size=3是经过实验选择的。在特征维度只有120左右时,卷积核太小(如1)退化成逐特征扫描,太大(如5)会把相邻但无关的特征强行绑定。
- padding='same'保证卷积前后维度不变,避免边界信息丢失。你可以试试不用padding,最后一维变化会让模型输出维度不好预测。
- GlobalMaxPooling1D的作用是从整条特征序列里挑出最强烈的响应值。比起Flatten,它能把参数量降下来一大截,并且能保留特征在某一位置上的最强激活,这对二分类/多分类任务非常有效。
- Dropout(0.5)是为了防过拟合。NSL-KDD训练集有12万条左右,模型参数量按我这个结构大概在十几万,理论上不会严重过拟合,但加上Dropout后泛化能力会好一些,测试集上的F1值能提升一两个百分点。
2.3 训练策略与调参要点
训练阶段有几个关键参数:
- batch_size=128:考虑到数据量不大,用128比较稳定。我试过32,收敛快但噪声大,loss曲线起伏严重;试过512,训练快但精度略降。
- epochs上限50:配合EarlyStopping,监控验证集loss,连续5轮不下降就停止训练,保存最佳模型。
- 初始学习率0.001:Adam优化器的默认值,一般不用动。
- 类别权重:NSL-KDD里R2L和U2R的样本数量极少,如果不加处理,模型会"偏向"样本多的类别,导致R2L和U2R的召回率惨不忍睹。可以计算类别权重后传给class_weight参数。简单来说,就是让模型在计算loss时给少样本类别更高的权重,逼迫它更关注这些"罕见攻击"。
还有一个容易踩的坑:数据切分时一定要设stratify参数,按类别比例分层抽样,保证训练集和验证集里每个类别的占比一致。不然运气不好验证集里可能几乎没有U2R样本,模型评估结果会严重失真。
3. 实操全流程:从数据加载到训练评估
3.1 数据读取与预处理代码实现
这一部分我把完整可跑的预处理代码贴出来,你直接复制改路径就能用。
import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler from sklearn.model_selection import train_test_split # 列名来自KDD 99文档 cols = ["duration", "protocol_type", "service", "flag", "src_bytes", "dst_bytes", "land", "wrong_fragment", "urgent", "hot", "num_failed_logins", "logged_in", "num_compromised", "root_shell", "su_attempted", "num_root", "num_file_creations", "num_shells", "num_access_files", "num_outbound_cmds", "is_host_login", "is_guest_login", "count", "srv_count", "serror_rate", "srv_serror_rate", "rerror_rate", "srv_rerror_rate", "same_srv_rate", "diff_srv_rate", "srv_diff_host_rate", "dst_host_count", "dst_host_srv_count", "dst_host_same_srv_rate", "dst_host_diff_srv_rate", "dst_host_same_src_port_rate", "dst_host_srv_diff_host_rate", "dst_host_serror_rate", "dst_host_srv_serror_rate", "dst_host_rerror_rate", "dst_host_srv_rerror_rate", "label"] train_df = pd.read_csv("KDDTrain+.txt", header=None, names=cols) test_df = pd.read_csv("KDDTest+.txt", header=None, names=cols) attack_map = { 'normal': 0, 'back': 1, 'land': 1, 'neptune': 1, 'pod': 1, 'smurf': 1, 'teardrop': 1, 'ipsweep': 2, 'nmap': 2, 'portsweep': 2, 'satan': 2, 'ftp_write': 3, 'guess_passwd': 3, 'imap': 3, 'multihop': 3, 'phf': 3, 'spy': 3, 'warezclient': 3, 'warezmaster': 3, 'buffer_overflow': 4, 'loadmodule': 4, 'perl': 4, 'rootkit': 4 } train_df['label'] = train_df['label'].map(attack_map) test_df['label'] = test_df['label'].map(attack_map) # 合并做One-Hot,再根据列交集对齐 combined = pd.concat([train_df, test_df], axis=0, ignore_index=True) combined = pd.get_dummies(combined, columns=['protocol_type', 'service', 'flag']) X_combined = combined.drop('label', axis=1) y_combined = combined['label'] X_train = X_combined.iloc[:len(train_df), :] y_train = y_combined.iloc[:len(train_df)] X_test = X_combined.iloc[len(train_df):, :] y_test = y_combined.iloc[len(train_df):] # 训练集内部再切一次验证集 X_train_final, X_val, y_train_final, y_val = train_test_split( X_train, y_train, test_size=0.2, stratify=y_train, random_state=42 ) scaler = MinMaxScaler() X_train_scaled = scaler.fit_transform(X_train_final) X_val_scaled = scaler.transform(X_val) X_test_scaled = scaler.transform(X_test) # 统一维度 input_dim = X_train_scaled.shape[1] X_train_cnn = X_train_scaled.reshape(-1, input_dim, 1) X_val_cnn = X_val_scaled.reshape(-1, input_dim, 1) X_test_cnn = X_test_scaled.reshape(-1, input_dim, 1)这里有个细节要注意:get_dummies在合并数据后做,是为了保证训练集和测试集有相同的列结构。如果分开做,列名顺序不一致,输入到模型的特征顺序就乱了,整个结果直接作废。
3.2 模型训练与回调配置
训练部分我加了两个关键回调:ModelCheckpoint和EarlyStopping。前者负责保存验证集上表现最好的权重,后者防止训练过度。有时候验证集acc已经到顶了,但loss还在缓慢下降,这时候EarlyStopping可以帮你提前收手,省时间。
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau model = build_cnn_model(input_dim, num_classes=5) callbacks = [ EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True), ModelCheckpoint('best_model.h5', monitor='val_accuracy', save_best_only=True), ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, min_lr=1e-6) ] history = model.fit( X_train_cnn, y_train_final, validation_data=(X_val_cnn, y_val), epochs=50, batch_size=128, class_weight=class_weight_dict, callbacks=callbacks, verbose=1 )class_weight_dict的构造方式:
from sklearn.utils.class_weight import compute_class_weight class_weights = compute_class_weight( class_weight='balanced', classes=np.unique(y_train_final), y=y_train_final ) class_weight_dict = dict(enumerate(class_weights))这个balanced方案在sklearn里是标配,它根据每个类别的样本量自动反比设置权重。实测下来,U2R的召回率能从不到10%拉到30%左右,效果明显。
3.3 评估指标与可视化
训练完之后,不要只盯着accuracy看,多分类任务里至少要把Precision、Recall、F1-Score和混淆矩阵都打出来。我贴一段评估代码:
from sklearn.metrics import classification_report, confusion_matrix import matplotlib.pyplot as plt import seaborn as sns y_pred = model.predict(X_test_cnn) y_pred_classes = np.argmax(y_pred, axis=1) target_names = ['Normal', 'DoS', 'Probe', 'R2L', 'U2R'] print(classification_report(y_test, y_pred_classes, target_names=target_names)) conf_mat = confusion_matrix(y_test, y_pred_classes) plt.figure(figsize=(8, 6)) sns.heatmap(conf_mat, annot=True, fmt='d', cmap='Blues', xticklabels=target_names, yticklabels=target_names) plt.xlabel('Predicted') plt.ylabel('True') plt.show()关于训练过程的可视化,loss曲线和accuracy曲线一定要画,这是答辩PPT里最直观的素材。我当时把训练集和验证集的曲线都画在同一张图里,两条曲线的走势老师一眼就能看出模型是否过拟合。
4. 实验结果分析与高频问题排查
4.1 指标怎么读:准确率瞒不过你的地方
我在NSL-KDD测试集上的最终结果(五分类)大致如下:
| 类别 | Precision | Recall | F1-Score |
|---|---|---|---|
| Normal | 98% | 96% | 97% |
| DoS | 99% | 97% | 98% |
| Probe | 85% | 88% | 86% |
| R2L | 60% | 45% | 52% |
| U2R | 70% | 30% | 42% |
如果你只报告总体Accuracy,大约在85%左右,看起来还不错。但把各类别指标拆开,就能看出问题:R2L和U2R的样本量太少,模型学到的有效信息有限,尤其U2R(用户到根提权攻击)这类攻击在流量特征上跟正常连接太像了,很难仅靠统计特征区分出来。
答辩时老师一定会问这个问题,所以你论文里甚至实验结果分析里,一定要主动承认这一点,然后给出改进思路,比如用上采样的SMOTE方法扩增少数类样本、用注意力机制让模型更关注关键特征、或者引入网络载荷的内容特征。主动暴露问题是加分项,藏着掖着反而会被老师认为实验不扎实。
4.2 训练过程中的常见坑与解决实录
我把整个调试过程里频率最高的几个问题整理成了一张速查表,每个都是我自己亲手踩过的。
| 现象 | 原因 | 解决办法 |
|---|---|---|
| loss不下降,acc一直40%左右 | 标签没有从0开始连续编码,或者模型输出层节点数与类别数不一致 | 检查标签映射,确保0到4的整数,softmax层节点数=类别数 |
| 训练集acc 99%,测试集acc 60% | 严重过拟合 | 加大Dropout比率,或做数据增强/加入正则化L2 |
| 测试集acc比训练集还高 | 测试集做了归一化时单独fit了scaler,数据泄露 | 统一用训练集fit的scaler去transform测试集 |
| 报错shape不匹配 | 输入维度不对,Conv1D要3D输入 | 用reshape(-1, input_dim, 1) |
| 训练特别慢 | 特征维度过大且没归一化 | 先做MinMaxScaler,再考虑PCA降维 |
| 预测结果全是0类 | 类别极度不均衡,loss被大类主导 | 设置class_weight,或对少数类做SMOTE |
| get_dummies后训练集测试集列数不一样 | 分开做One-Hot编码导致取值集合不同 | 合并数据集后统一做get_dummies,再按行位置切回 |
4.3 从NSL-KDD到真实数据集的鸿沟问题
这里我必须说点实在话。NSL-KDD虽然是最经典的毕设数据集,但它是2000年左右KDD Cup 99的改进版,里面的攻击类型相对老旧,跟现在的真实网络攻击有很大差距。你在论文的"不足与展望"部分,一定要写上:真实网络环境中,流量是连续不断的,入侵检测系统不仅要处理一条条独立的连接记录,还要对长时段的流量做会话切分和聚合,这对模型的实时性和鲁棒性要求更高。
如果把模型用到CICIDS2017之类的现代数据集,你会发现几个新的挑战:特征维度从41维一下子跳到80多甚至几百维,数据量从十几万变成几百万,很多特征字段本身就不连续。那时候就需要考虑流量会话切分、特征筛选、分布式训练等问题。这篇文章里的代码框架仍然适用,只是预处理部分要重新适配。
5. 模型的扩展方向与工程化落地思路
5.1 把模型部署成可用的检测接口
毕设如果只停留在训练出模型,答辩总觉得少了点"生命力"。我当时额外花了两天时间,用Flask写了一个极简的Web接口,把训练好的模型包进去。流程非常简单:
from flask import Flask, request, jsonify import numpy as np from tensorflow.keras.models import load_model app = Flask(__name__) model = load_model('best_model.h5') @app.route('/predict', methods=['POST']) def predict(): data = request.get_json() features = np.array(data['features']).reshape(1, -1) features = scaler.transform(features).reshape(1, -1, 1) pred = model.predict(features) label = int(np.argmax(pred[0])) return jsonify({'predicted_label': label}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)这样做完,老师当场就能看到效果:你随便拿一条测试集数据,POST过去,接口返回对应的攻击类型。整个系统的完整性立刻拉满。而且这也方便你后续把检测模块嵌进一个流量监控Demo里,实现"抓包->特征提取->模型预测->告警"的全链路。
5.2 注意力机制和CNN的融合探索
在CNN模型里加入注意力机制,也是近年论文和毕业设计里很常见的改进方向。不用把注意力想得很玄乎,它的核心思想是:让模型自己学会"重点关注哪些特征"。比如检测R2L攻击时,可能某些内容型特征比流量统计特征更重要,注意力机制会给这些特征分配更高的权重。
一个简单可操作的方案是在卷积层之后加一个SE(Squeeze-and-Excitation)模块。SE模块通过全局平均池化得到每个通道的重要性权重,再去重新校准每个通道的响应。代码实现大概是这样:
from tensorflow.keras.layers import GlobalAveragePooling1D, Dense, Reshape, Multiply def se_block(inputs, reduction=16): channels = inputs.shape[-1] x = GlobalAveragePooling1D()(inputs) x = Dense(channels // reduction, activation='relu')(x) x = Dense(channels, activation='sigmoid')(x) x = Reshape((1, channels))(x) return Multiply()([inputs, x])把这个模块插在两层Conv1D之间,模型参数量增加不多,但分类效果通常能提升一到两个百分点,尤其对R2L和U2R这种难分类别有一定帮助。写论文的时候,这个模块可以作为你的"创新点"展开论述:结合通道注意力机制,增强模型对关键特征的感知能力。
5.3 与Transformer、时序特征的对比思考
说实话,近几年在序列建模任务上,Transformer结构已经逐渐超过CNN。但入侵检测里是否需要用Transformer,我认为要看具体场景。如果数据集是按时间排序的连续流量,Transformer能捕捉长距离依赖关系,效果可能更优。但如果只是像NSL-KDD这样一条条独立的连接记录,特征间的上下文关系有限,CNN的归纳偏置(局部相关性)反而更合适,而且训练速度更快,所需数据量更少,CPU上也能在几分钟内跑完。
如果你想让论文更有深度,可以在实验部分加一组对比:CNN baseline、CNN+Attention、以及一个有Transformer编码器的模型(可以用简单的TransformerEncoder块)。用同样的预处理和训练策略,再把四个模型的效果做成表格,哪组数据好看就重点写哪个方向。这不叫投机,而是科研里很正常的实验对比流程。
6. 常见疑问与毕设答辩要点整理
6.1 老师可能会问的六个问题
根据我身边同学和我自己的答辩经验,以下问题被问到的概率极高,建议提前准备:
- 为什么选择CNN而不是LSTM?回答思路:NSL-KDD中每条记录独立,无时序依赖,CNN提取局部特征能力强;LSTM更擅长处理序列,但计算开销大;如果后续扩展到连续流量检测,可以考虑LSTM。
- 数据集中攻击样本不平衡怎么办?回答思路:类别权重、SMOTE过采样、数据增强、以及使用F1-Score而非Accuracy作为评价指标。
- 归一化和标准化的区别?回答思路:归一化把数据映射到0到1,对分布无要求;标准化把数据变成均值为0、方差为1,适合原始数据呈高斯分布的情况。NSL-KDD特征分布复杂,归一化更稳妥。
- 卷积核大小怎么选?回答思路:1太小提不出特征组合,3是常用经验值,5会引入过多噪声;做对比实验验证。
- 你的模型在真实网络中能用吗?回答思路:框架可迁移,但需要重新处理流量数据、适配新特征,且真实环境对实时性要求更高,需要剪枝和量化。
- 创新点是什么?回答思路:特征工程上用更系统的方法对齐训练测试集;模型结构上引入注意力机制;系统实现上有完整的推理接口。
6.2 答辩演示建议
不要一上来就打开训练代码,太劝退。建议先展示系统框图:数据预处理模块、CNN训练模块、模型评估模块、检测接口模块,然后直接现场加载保存好的模型,对测试集进行预测,展示混淆矩阵和指标表格。最后打开Web接口,现场输入几个样本验证结果。这套流程走下来,基本就是一次完整的项目验收现场。
6.3 一个容易被忽略的细节:随机种子固定
为了让实验结果可复现,代码开头一定要设置随机种子:
import random import numpy as np import tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)如果不固定,同样的代码跑两次,可能得到两个略有差异的结果,论文里的数字就说不清了。有老师较真让你现场重跑,结果对不上非常尴尬。
最后再分享一个小心得:做这类毕设,最大的误区不是模型调不出来,而是以为"模型越复杂越好"。我在实验早期试过一个深度比这个大得多的网络,效果反而下降,原因是数据量不够支撑大规模模型的训练。从简单的两层Conv1D出发,逐步加层、加注意力模块,每一轮改动都记录实验效果,这样论文里的"实验与分析"章节才真正有东西可写。这是整个项目里最花时间、也最值得花时间的地方。
本文还有配套的精品资源,点击获取