每年毕业季帮学弟学妹把关毕设选题的时候,总能看到这类题目:《基于Python和CNN卷积神经网络识别花卉是否绽放》。去年A同学拿过来的时候我第一反应是"又是个标准图像分类",但真正陪他把整套流程跑下来才发现,这个题目里藏着好几个不仔细拆就会翻车的细节:什么叫"绽放"、数据集到底从哪来、网络该搭多深、迁移学习要不要用、答辩时怎么把每个选择解释清楚。这篇文章我就以这个题目为骨架,把从选题拆解到最终交付的完整链路过一遍,把我实际做这类项目时踩过的坑、验证过的参数、觉得能直接抄走的经验都写出来。不管你是正在定毕设题目的本科生,还是想快速上手CNN图像分类的初学者,这套思路应该都能直接复用。
1. 这个毕设题目到底在解决什么问题
1.1 任务拆解:先把"是否绽放"定义清楚
很多人在毕设开题时最容易犯的错,就是把题目当成理所当然。拿到"识别花卉是否绽放"这句话,你要先问自己三个问题:输入是什么?输出是什么?"绽放"的边界在哪里?
输入很简单,一张花卉图片,可能是单支花的特写,也可能是花园里的一大片。输出是一个二分类标签:绽放 / 未绽放。但这个二分类的判定标准如果不在项目一开始就定死,后面标注数据时会崩溃。什么叫绽放?完全打开算绽放,还是开了50%就算?花苞顶端露色但花瓣没展开,算哪边?已经开到开始凋谢的,又算哪边?
我的建议是把判定标准写成一条可执行的规则:以花冠(花瓣集合)的展开程度为主线,花瓣相互分离且能清晰看到花蕊的区域,记为绽放;花瓣仍聚拢成苞状或仅顶端微开,记为未绽放。至于中间地带,宁可统一归到未绽放,也不要搞出一个模糊的"半开"类别。二分类任务里标注重心摇摆是灾难性的,后面训练时模型会在这些样本上反复横跳。
这个定义过程本身就是很好的毕设素材,论文的需求分析章节可以直接用,答辩时老师问"你的任务是怎么定义的",你能说出这套界定逻辑,比单纯说"我就是判断开没开"要有说服力得多。
1.2 为什么是CNN:传统机器学习方案差在哪
这个题目如果把"CNN"三个字去掉,其实用传统机器学习也能做:对图像提取颜色直方图、纹理特征(如LBP)、边缘方向直方图,喂给SVM或随机森林分类。我确实见过有人这么做,效果也不至于不能用,但这里有个根本问题:花卉是否绽放,靠的是花瓣之间的相对几何关系、开放程度的细微结构变化,这些信息用人工设计的特征很难表达干净。
举个具体例子:一株郁金香,含苞时整体是细长的椭圆轮廓,绽放时变成杯状展开。颜色直方图在两种状态下差异可能很小,因为花苞和外层花瓣颜色接近;LBP纹理特征能抓到表面纹理变化,但花瓣从聚拢到展开的过渡是一个连续过程,人工特征很难找到稳定的分界线。
CNN在这里的核心优势是端到端学习特征。卷积层会自动从数据中学到一套从低级到高级的表示:浅层学边缘、颜色块,中层学花瓣纹理、花蕊结构,深层学会把"是否展开"这种语义特征组合起来。不需要人告诉它该看哪里,它自己会从标注数据里总结规律。
打个生活化的比方:传统方法像厨师按菜谱挑菜,菜谱上写着"看颜色、看纹理",但真到了菜市场还是会挑花眼;CNN像学徒跟着看了一万筐菜,心里自己长出了一套"什么菜新鲜"的判断标准。这个"判断标准"就是卷积核里的权重。
1.3 项目的实际应用场景与扩展空间
毕设选题最好能讲清楚应用场景,这不只是为了论文"研究意义"那一章好听,也是答辩时回答"你做这个有什么用"的素材。
花卉绽放识别最直接的应用是智慧农业和园林管理:温室里批量检测开花进度、指导花期调控;城市公园对樱花、海棠这类观赏花卉做绽放指数监测,辅助游客接待管理。再往产品端想,可以做成一个拍照识花的App功能——用户拍一盆花,告诉它"开没开",顺便推送养护建议。这个场景非常适合做毕业设计演示,搭一个简单的网页接口或者本地GUI,现场拍一张花就能出结果,视觉效果比纯跑测试集好得多。
另外这个题目天然是可扩展的:把二分类改成多分类(花苞、初开、盛放、凋谢),或者在检测出花的位置之后再判断绽放状态,就升级成了目标检测加细粒度分类的任务。这些扩展点写在论文的"未来展望"里,既是加分项,也说明你确实理解了问题本身。
2. 数据集准备:真正决定项目成败的环节
2.1 数据来源三条路:自采、公开数据集、二次标注
很多第一次做深度学习项目的同学,把80%的精力花在调模型上,结果模型换来换去准确率上不去,最后的根因往往是数据量太少或者标签太脏。数据集准备这个阶段,我建议投入整个项目至少四成的时间。
先说第一条路:自采。拿手机在校园、公园、植物园拍不同状态的花。注意几个细节:同一品种尽量涵盖不同开放程度;拍摄距离和角度保持相对统一;一天中不同时段的光照都拍一些,避免模型把光线条件当成分类依据。自采数据的优点是自己完全可控,答辩时能讲"数据来源于实地采集",缺点是耗时,一个类别没几百张撑不起训练。
第二条路:公开数据集。这类题目最常用的公开数据集是花卉品种分类数据集,但它们绝大多数标注的是品种,并不标注绽放状态。所以直接拿来用是不可行的,你需要做二次标注——把图片下载下来,按你前面定义的标准重新分成绽放和未绽放两类。这个过程很枯燥,但能让你对数据分布有极深的理解。
第三条路其实是前两条的混合:公开数据做基础量,自采数据做补充和验证。我推荐的做法是,公开数据里挑出质量高、品种多样、背景干净的图片,再加上自己拍的一定比例图片,混合成一个自定义数据集。这样做的好处是既有量又有真实性,测试时还能单独留出一批"完全没见过的场景"做泛化验证。
2.2 清洗与增强:哪些操作会毁掉你的模型
数据清洗听起来没有技术含量,但恰恰是区分"能跑"和"能打"的分水岭。拿到图片先做几件固定的脏活:
- 去重:同一朵花连拍很多张,内容高度相似,会严重高估模型效果。用感知哈希算法算一下图片相似度,相似度超过阈值的只留一张。
- 去模糊:用拉普拉斯算子的方差判断清晰度,方差过低的直接删。
- 去错误标注:把标注可疑的样本单独抽出来人工复核,特别是"部分绽放"的样本要严格按定义的规则归类。
- 去无关内容:有水印、贴纸、文字遮挡的图片,训练时会让模型学到"有水印=某种类别"这种荒谬特征的苗头。
清洗之后是数据增强。我常用的增强配置是:水平翻转(概率0.5)、随机旋转±15度、随机亮度抖动±20%、随机对比度抖动±20%、随机裁剪后缩放回原尺寸。增强有两个边界要注意:一是不要做超现实的变换,比如上下颠倒、极端的色彩反转,花朵的姿态和颜色是有物理约束的,改过头等于制造脏数据;二是增强只作用于训练集,验证集和测试集必须保持原始状态,否则评估结果就失真了。
关于类别不平衡,这个题目里天然容易不均衡:花盛开的季节图片多,花苞和没开花的图片少;公开数据里盛开的高清图占大多数。处理办法有两个,一是补采/补找未绽放类别的数据,二是在训练时给少数类加权。加权计算方式很直接:w = 总样本数 / (类别数 × 本类样本数),比如共2000张,绽放1600张、未绽放400张,未绽放类权重就是2000 / (2 × 400) = 2.5,相当于每张未绽放图惩罚力度放大2.5倍。
2.3 数据划分:最容易被忽视的泄漏陷阱
划分训练集、验证集、测试集这门功课,看着简单,坑却最深。标准比例是7:2:1或8:1:1,但这只是第一步。真正的关键是分层划分和同源隔离。
分层划分指的是划分时保证每个子集里两个类别的比例一致。如果你随机划分,运气不好可能出现验证集里90%都是绽放,模型在验证集上的表现就会忽高忽低,你很难判断到底是谁的锅。
同源隔离更隐蔽。如果你同一时间段、同一场景、同一朵花连拍了20张,其中15张进了训练集、5张进了测试集,那测试集就是"开卷考试"——模型相当于见过这朵花了。识别准确率虚高,一换真实场景就崩。正确做法是把同一批采集来源的图片整体划到同一个子集,比如按拍摄日期或按植物个体划分。我在这个项目里就是把公开数据集按品种整体切分:一个品种要么在训练集,要么在测试集,绝不让同一品种的图像横跨两边。这样做会让测试难度明显变大,但得到的数字才是真实可信的。
另外强烈建议单独留一个"硬测试集":里面全是和训练集风格差异很大的图,比如不同光照、不同拍摄设备、不同背景。这个集合不参与调参,只在最后评估一次,专门用来考验模型的泛化能力。写论文时"模型在常规测试集95%,在跨场景测试集89%"这种表达,比单一准确率有说服力得多。
3. CNN网络怎么搭:从零搭建与迁移学习两条路线的完整对比
3.1 从零搭一个轻量CNN:结构设计与参数量手算
从零搭建网络的路线,适合想彻底搞懂CNN原理、答辩时能经得起追问的同学。这里给一个我常用的小型结构,输入统一为224×224×3的RGB图像,由三个卷积块加一个分类头组成。
import tensorflow as tf from tensorflow.keras import layers, models def build_small_cnn(input_shape=(224, 224, 3)): model = models.Sequential([ # 卷积块1:边缘和颜色 layers.Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=input_shape), layers.MaxPooling2D((2, 2)), # 卷积块2:纹理和局部形状 layers.Conv2D(64, (3, 3), padding='same', activation='relu'), layers.MaxPooling2D((2, 2)), # 卷积块3:花瓣结构和开放程度语义 layers.Conv2D(128, (3, 3), padding='same', activation='relu'), layers.MaxPooling2D((2, 2)), # 全局平均池化,替代Flatten+Dense,大幅减少参数 layers.GlobalAveragePooling2D(), layers.Dropout(0.5), layers.Dense(1, activation='sigmoid') ]) return model model = build_small_cnn() model.summary()这里每个选择都值得在论文里展开讲。卷积核为什么用3×3?因为堆叠多个3×3卷积可以获得更大的感受野,两层3×3等价于一层5×5、三层等价于一层7×7,但参数量更少,非线性表达能力更强。MaxPooling为什么是2×2步长2?它把特征图在宽高上各缩小一半,既降低计算量,又带来一定程度的平移不变性。激活函数用ReLU而不是sigmoid或tanh,是为了缓解梯度消失,让深层网络能真正训练起来。最后接sigmoid是因为二分类输出一个0到1之间的概率,配合二元交叉熵损失天然匹配。
我特意在分类头用了GlobalAveragePooling2D而不是Flatten加Dense,这里有个非常实在的参数量问题。三个池化后特征图是28×28×128,如果Flatten就是100352个值,接一个256节点的全连接层,仅这一层就有约2570万参数,比前面所有卷积层加起来还多几十倍。训练这么庞大的全连接层,在几千张图的小数据集上几乎必然过拟合。换成全局平均池化,直接把每个通道的28×28特征图平均成一个数,得到128维向量再接全连接层,参数瞬间降到129个。这个对比数据写进论文是很好的"结构设计理由"。
3.2 迁移学习路线:用预训练模型微调的完整流程
如果数据量不够大,或者你想在有限时间内拿到更高准确率,迁移学习是更稳妥的路线。核心思路:拿在ImageNet这种千万级数据集上预训练好的模型(ResNet50、VGG16、MobileNetV3这类都行),把它的分类头换掉,用自己的数据微调。
具体流程我建议分两阶段:
from tensorflow.keras.applications import ResNet50 from tensorflow.keras import layers, models, optimizers # 第一阶段:冻结骨干,只训练新分类头 base = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3)) base.trainable = False model = models.Sequential([ base, layers.GlobalAveragePooling2D(), layers.Dropout(0.5), layers.Dense(1, activation='sigmoid') ]) model.compile(optimizer=optimizers.Adam(learning_rate=1e-3), loss='binary_crossentropy', metrics=['accuracy']) # 训练几个epoch,让分类头先收敛 # 之后进入第二阶段:解冻骨干的后30%,用小学习率微调 base.trainable = True for layer in base.layers[:100]: layer.trainable = False model.compile(optimizer=optimizers.Adam(learning_rate=1e-5), loss='binary_crossentropy', metrics=['accuracy'])为什么必须分两个阶段?因为如果一开始就让整个网络可训练,随机初始化的新分类头会回传巨大的梯度,把预训练骨干辛辛苦苦学好的特征全部冲掉,这在微调里叫"灾难性遗忘"。先冻结骨干让分类头适应你的数据,再用极小学习率慢慢放开部分骨干层,才能保住预训练特征的底子。
3.3 两条路线怎么选:一个对比表解决纠结
很多同学问过我到底走哪条路,我的回答是:看你的目标和答辩策略。
| 对比维度 | 从零搭建轻量CNN | 迁移学习微调预训练模型 |
|---|---|---|
| 数据量要求 | 单类至少1000张才稳 | 单类300张左右即可起步 |
| 训练时间 | CPU也能勉强跑,GPU更快 | 建议GPU,CPU很煎熬 |
| 预期准确率 | 90%上下,看数据难度 | 95%上下,普遍更高 |
| 可解释性 | 结构自己设计的,每个参数都清楚 | 底层卷积核是继承来的,不够透明 |
| 答辩风险 | 需要深入解释每层设计 | 可能被问"这算你自己做的吗" |
| 代码工作量 | 较大,要自己调结构 | 较小,重点是调训练策略 |
我的建议是,如果时间充裕、想锻炼底层能力,两条路都做:从零CNN作为baseline,迁移学习作为提升方案,论文里做一个对比实验,这本身就是完整的研究闭环。如果时间紧张只想稳过,直接做迁移学习,但一定要把"为什么迁移学习有效"讲透,并在论文里如实说明骨干网络来源,不藏着掖着。
4. 训练过程中的四个典型坑与完整定位过程
4.1 损失函数、优化器与训练策略的基线选择
在动手训练之前,先把基线策略定好。损失函数用二元交叉熵(binary crossentropy),不要用均方误差MSE。原因是分类任务的输出是概率,交叉熵衡量的是两个概率分布之间的距离,梯度更新更平稳;MSE配合sigmoid在输出接近0或1时梯度会趋于0,训练速度极慢。
优化器选Adam,初始学习率:从零搭建用1e-3,迁移学习第一段用1e-3、第二段降到1e-5。批次大小16到32之间,看显卡显存来定。训练时固定随机种子,保证每次结果可复现:
import random import numpy as np import tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)另外一定要配置早停和模型检查点。早停监控验证集损失,连续10个epoch不下降就停止;检查点每个epoch结束后保存最佳权重,防止训练后期过拟合把最好的模型覆盖掉:
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint callbacks = [ EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True), ModelCheckpoint('best_model.h5', monitor='val_acc', save_best_only=True) ]4.2 坑一:Loss不收敛,问题出在数据归一化
A同学的第一次训练,训练集损失一路在0.7附近横盘,20个epoch纹丝不动。他先怀疑网络结构,又怀疑学习率,折腾了半天。最后我让他把输入打印出来看,发现像素值范围是0到255,而网络默认输入分布是0到1附近。归一化不一致,相当于把输入特征整体放大了255倍,网络第一层卷积的输入分布完全错位,梯度更新自然很怪。
修复方式很简单:数据加载时除以255,或者用tf.keras.applications.resnet50.preprocess_input做标准化,这条基线一改,损失马上就掉下去了。这个坑的教训是:遇到Loss不收敛,先检查输入数据分布,再看网络结构,顺序不能反。
4.3 坑二:过拟合从训练第10轮开始
从零搭建时,前期训练曲线很漂亮,训练准确率一路冲到99%,验证准确率却卡在75%左右,验证损失在某个点之后不降反升。这是再典型不过的过拟合:模型把训练集的细节背了下来,包括背景纹理、拍摄角度、甚至噪声,但它没有学到"花瓣打开程度"这个泛化特征。
我用三招组合拳解决:第一,数据增强强度往上提,增大了旋转角度上限并加入随机缩放;第二,Dropout从0.3提到0.5;第三,加L2正则化,在卷积层设置kernel_regularizer=tf.keras.regularizers.l2(1e-4)。三管齐下之后,验证准确率回升到了91%。这里要说明一点:过拟合的性价比最高的解药永远是更多样的数据,正则化只是兜底,两者不能本末倒置。
4.4 坑三:验证集指标剧烈震荡
另一个阶段,验证损失每个epoch都像坐过山车,今天0.35明天0.8后天0.28,完全无法判断模型是好是坏。查了一圈发现两个叠加的原因:一是验证集里未绽放类样本太少,只有几十张,每一次预测错两三张,准确率波动就非常大;二是当时没用分层划分,验证集类别比例和训练集不一致。
解决方法也简单:把验证集的分层划分重新做一遍,保证两个类别占比大致均衡;再把批次大小从16提到32,减少每个batch内类别分布的随机性。做完之后曲线平滑多了。顺带提一句,如果数据量实在小,单次验证集指标波动是不可避免的,这时候以验证损失为准比以验证准确率为准更可靠。
4.5 坑四:微调时把预训练权重洗掉了
迁移学习第一段训练很顺利,准确率到了94%,此时我解冻了ResNet50的后半部分,设置1e-5的学习率继续微调。结果第二天来看日志,验证准确率掉回了88%,再也没恢复。原因在于解冻时没有只解冻"后半部分",而是把整个骨干都设成了可训练,且新分类头的梯度仍然过大,一次大步更新就把骨干权重搅乱了。
这个问题的正确姿势是三层递进:先冻结全部骨干训练分类头几个epoch;然后只解冻最后一个卷积块,用小学习率继续训练;最后如果还想压榨精度,再逐块解冻,每解冻一层都要观察验证集表现,一旦变差立刻回退。另外微调阶段建议冻结BatchNormalization层的参数,因为你的小batch统计量和大数据集预训练时的统计量差异很大,让它们跟着微调反而容易出问题。
4.6 训练曲线的读图方法
训练时学会看曲线,能省下一半调参时间。我总结了一套快速判断口诀:
- 训练损失和验证损失都高:欠拟合,加模型容量或训练更多轮。
- 训练损失低、验证损失高:过拟合,加强正则化或增强数据。
- 两条曲线震荡剧烈:学习率偏高或batch太小,往低调。
- 训练损失在某个数值附近长期横盘:大概率是数据归一化错了或标签错了。
- 验证损失在训练后期突然翘起:过拟合已经开始,早停该发挥作用了。
训练日志要保留完整,建议用TensorBoard记录每个epoch的损失、准确率、学习率。论文里贴出来的训练曲线图,配合上面这套解读逻辑,会显得非常专业。
5. 模型评估:准确率之外必须看懂的指标与错误样本复盘
5.1 为什么不能只看准确率
准确率是最直观的指标,但在这个题目里它会骗人。想象一个极端情况:数据集里90%是绽放图片,你训练一个"永远预测绽放"的傻瓜模型,准确率也有90%。如果你只看准确率,会误以为模型已经学到了东西。
所以评估必须同时看精确率(Precision)、召回率(Recall)和F1分数。用sklearn一行代码就能算:
from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_true, y_pred, target_names=['未绽放', '绽放'])) print(confusion_matrix(y_true, y_pred))精确率看的是"模型说有开,真的开了多少";召回率看的是"所有真开了的花,模型找回来多少"。在这个场景里,我认为召回率更重要:如果一个花农用你的系统判断花期,漏报一朵开了的花,等于错过了采摘或管理时机;而误报一朵没开的花,顶多让人白跑一趟。这个"哪个错误更贵"的思考,也是答辩时体现产品思维的好素材。
5.2 混淆矩阵与业务侧的错误代价
训练完成后打印混淆矩阵,我见过两类典型错误:未绽放被误判为绽放,以及绽放被误判为未绽放。
| 预测为未绽放 | 预测为绽放 |
|---|---|
| 真实未绽放:TN | 真实已绽放:FN(漏报) |
| 真实已绽放:FP(误报) | 真实已绽放:TP |
漏报和误报的业务代价并不相同。面向游客的花期查询场景,误报会让游客白跑一趟,但漏报影响小一些;面向温室管理的开花监测,漏报直接错失人工干预窗口,代价更大。你可以在论文里根据应用场景选择合适的阈值:sigmoid输出默认0.5为界,如果更怕漏报,可以把阈值调到0.4;如果更怕误报,调到0.6。这个操作简单到只有一行代码,但能体现你对评估指标的深入理解。
5.3 错误样本复盘:哪些花让模型翻车
把测试集里预测错误的图片全部打印出来,一张一张看,是我觉得这个项目里最值钱的一步。A同学的错误样本集中在几类:花苞与叶子颜色相近导致轮廓不清、花朵被其他物体遮挡、远处小面积花朵分辨率不足、以及同一枝上既有已经开的又有没开的花。
这类复盘会直接告诉你下一步该怎么走,而不是盲目加数据。比如"花苞与叶子颜色相近"提示模型主要在利用颜色特征而不是形状特征,那就要补充不同颜色的花苞样本或者用更强的数据增强强迫模型学结构特征;"远处小面积花朵"提示低分辨率是瓶颈,可以考虑先用目标检测框出花区域,再对区域做绽放分类。
5.4 一个有参考价值的实验结果区间
我拿类似的数据规模做过一组对比实验,大致结果供参考:从零搭建的三层CNN在均衡测试集上能到88%到92%的准确率;在同一个数据集上做ResNet50迁移学习,能达到94%到97%;加上跨场景硬测试集之后,两个方案都会掉3到5个百分点。这个区间不是标准答案,你的数据难度不同结果自然不同,但它能给你一个心理预期:如果迁移学习连90%都到不了,大概率是数据或训练流程有问题,而不是模型不行。
6. 毕设交付与答辩:别让代码砸在自己手里
6.1 代码工程化:让答辩老师一眼看懂你的工程能力
很多毕设代码是"能跑但没法看"的状态:一个超大Jupyter Notebook跑到底,脚本里全是魔法数字,模型权重文件散落各处。这种交付水平很容易在答辩环节吃亏。我建议按下面的结构整理:
flower_bloom/ ├── data/ │ ├── raw/ # 原始图片 │ ├── processed/ # 清洗增强后的数据 │ └── split/ # 训练/验证/测试划分索引 ├── src/ │ ├── data_loader.py │ ├── build_model.py │ ├── train.py │ └── evaluate.py ├── models/ │ ├── best_model.h5 │ └── training_log.csv ├── config.py # 所有超参数集中管理 ├── requirements.txt └── README.md几个加分细节:所有超参数写进config.py,不要散落在代码里;requirements.txt锁定依赖版本,保证换台机器能复现;README里写清运行步骤和数据集说明;模型权重文件和训练日志都保留,答辩现场可以直接展示历史曲线。这些工作不复杂,但能实打实拉开和同组人的差距。
6.2 论文结构与时序安排
论文的标准八股结构大家都懂,我重点说三个容易被忽视的点。第一,相关工作章节不能只罗列"某某人用CNN做了什么",要有一个递进逻辑:从传统图像分类到深度学习分类、从通用图像分类到细粒度图像识别、再到本题目特有的"状态识别"问题。第二,实验章节必须包含消融实验,哪怕只是"baseline vs 加数据增强 vs 迁移学习"三行对比表,也足以说明你理解每个模块的作用。第三,时间安排上,数据集准备永远最先开始,因为它耗时最长且不可压缩,模型训练和论文写作可以部分重叠。
6.3 答辩现场的高频问题与回答思路
按我旁观多届答辩的经验,老师问来问去就这么几个问题,提前准备等于开卷考试。
"为什么用CNN不用传统方法?"——从自动特征学习和层次化特征表达两个角度回答,结合花卉绽放识别的具体难点,不要背教科书。
"你的网络结构和VGG/ResNet相比有什么考虑?"——如果是从零搭建,讲清楚你如何在参数量、感受野、防止过拟合之间做取舍;如果是迁移学习,讲清楚为什么用预训练模型、你的微调策略是什么。
"如果换一种花,模型还会准吗?"——要诚实:已知品种内表现好,跨品种会下降;解决方法包括补充数据、做目标检测预处理、以及扩大训练品种覆盖范围。这题考的是对泛化局限的认知。
"你训练过程遇到过什么问题?"——把第4章那些坑讲明白,重点突出你是如何定位和解决的,这类过程性回答比结果数字更能证明你真的做过。
收官阶段的实用建议
最后这条建议,是我反复跟A同学强调的,也是这个项目给我最深的体会:先用每类20张图片跑通全流程,再想着把数据量堆到几千张。小样本快速验证代码的正确性、确认评估流程没毛病,然后再大规模采集和训练,能省下大量返工时间。整个项目做完回头看,真正决定成败的不是网络结构选得多花哨,而是数据定义清不清楚、评估是否诚实、代码能不能复现。这几点做到了,这个毕设的收获会远超一个分数本身。