尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于深度学习的水稻叶部病害识别:96.9%准确率的CNN+PCA方案拆解

基于深度学习的水稻叶部病害识别:96.9%准确率的CNN+PCA方案拆解 简介这是一份针对水稻叶部病害智能识别的学术研究PDF适合农业信息化、计算机视觉方向的研究人员、算法学习者以及从事作物病害防治的技术人员参考。资源为单文件PDF大小3.14MB内容源自期刊《信息记录材料》2018年第12期包含摘要、关键词、正文及参考文献便于直接阅读与引用。该资料已有74人学习下载属于轻量实用的专业文献。文稿提出基于Caffe平台构建含4个卷积层、3个池化层和1个全连接层的深度网络通过PCA降维及2000幅病害图像训练与10重交叉验证对北方寒地水稻稻瘟病、纹枯病等常见病害平均识别率达96.9%并梳理了国内外图像识别在植物病害诊断中的研究脉络可为相关课题设计、算法选型与实验复现提供参考也展示了深度学习相对传统图像识别方法在特征提取与泛化能力上的优势。1. 基于深度学习的水稻叶部病害识别一份 96.9% 识别率的完整方案拆解寒地水稻田里最让人头疼的事就是病害来了靠肉眼挨垄看等发现稻瘟病扩散最佳防治窗口早过了。这篇论文走的是深度学习路线建了包含 5 类常见水稻叶部病害稻瘟病、纹枯病、恶苗病、叶鞘腐败病、立枯病的图像库用 Caffe 框架搭了一个 4 卷积层 3 Pooling 层 1 全连接层的卷积神经网络把 2000 幅样本在 10 重交叉验证下跑到平均识别率 96.9%单次推理 78ms同时把 SVM 和 BP 两条传统路线都压了下去。适合正在做作物病害识别、农业图像分类或者毕设选型的人照着复现能省掉一大截“从零试网络结构”的时间。2. 数据预处理与 PCA 降维12288 维图像怎么压到 400 维2.1 图像采集与数据规模2000 幅图、5 类病害的构成论文的数据来源分两条线一部分是田间自然光照条件下用佳能 EOS 5D Mark IIIEF24-70mm F2.8L II USM实地拍摄针对北方寒地水稻生长中期采集叶部病害另一部分是从《水稻病虫害防治图解》《北方水稻病虫害防治彩色图谱》这类书里扫描补进来的。混合采集的思路很实际田间照片和图书扫描件在背景、光照、叶片姿态上的分布差异相当于顺手做了数据增广比纯实验室条件拍出来的图泛化得更开。原文写的口径是每种病害采集 200 幅、共 2000 幅。按列出的 5 类病害算5 类 × 200 幅 1000 幅数字对不上。我复现时倾向于理解为每类包含多个子类或多次拍摄批次总数以 2000 为准。自己做数据集时不用纠结这个口径按病害类别数平分总样本量就行。图像统一存成 JPG归一化到 64×64 大小。这个归一化是前期 PCA 降维用的后面进网络又被 resize 到 96×96×3这个矛盾点后面避坑章会专门提。作者只处理单帧图片没做视频帧抽取数据准备的负担不算大。2.2 均值化与 PCA为什么保留 97% 主成分就够了原始图像像素展开成向量每幅图是 64×64×3 12288 维。这个维度直接喂给分类器矩阵运算和内存开销都扛不住更重要的是像素维之间存在大量冗余相邻像素在纹理和颜色上高度相关直接做分类会把噪声也学进去。PCA 的活就是把这一万多维压成几百个不相关的主成分同时尽量留住方差信息。论文里先做均值化处理做法是对每个特征维除以该维最大值得到这一维的均值再让每个样本减去这个均值。这样做的目的有二一是把不同像素维的取值范围拉到同一尺度避免某几个亮度特别高的像素主导整个协方差矩阵二是后续 PCA 计算的是中心化后的协方差结构不做这一步主成分会被像素亮度均值带偏。PCA 的具体操作就是求中心化矩阵的协方差矩阵特征分解按特征值从大到小取前 k 个特征向量构成投影矩阵再把原样本投影到新空间。论文取 k400把 12288 维降到 400 维保留 97% 的主要成分。97% 这个阈值我理解是拿验证集试出来的太高比如 99%会把高维噪声一并留下太低则丢纹理细节病害识别这类细粒度问题 95% 以下会明显掉点。2.3 PCA 的 Python 实现从图像文件夹到 400 维特征矩阵照论文的公式用 NumPy 实现一遍在参数上贴近原文import os import cv2 import numpy as np IMG_DIR rice_diseases # 每个病害类别一个子目录 IMG_SIZE 64 # 论文 PCA 阶段用的 64*64 N_COMPONENTS 400 # 降到 400 维保留约 97% 主成分 X, labels, classes [], [], sorted(os.listdir(IMG_DIR)) for label, cls in enumerate(classes): # label 从 0 开始编号 for fname in os.listdir(os.path.join(IMG_DIR, cls)): img cv2.imread(os.path.join(IMG_DIR, cls, fname)) img cv2.resize(img, (IMG_SIZE, IMG_SIZE)) X.append(img.flatten()) # 64*64*3 12288 维 labels.append(label) X np.array(X, dtypenp.float32) labels np.array(labels, dtypenp.int32) # 按论文做法每维除以最大值得到该维均值再减去均值 max_vals np.max(X, axis0) X_norm X / (max_vals 1e-8) # 防止某像素维度全为 0 除零 mean_vec np.mean(X_norm, axis0) X_center X_norm - mean_vec # 中心化 # 求协方差矩阵并做特征分解取前 400 个主成分 cov np.cov(X_center, rowvarFalse) eig_vals, eig_vecs np.linalg.eigh(cov) # 得到特征值和特征向量 idx np.argsort(eig_vals)[::-1] # 从大到小排列 top_vecs eig_vecs[:, idx[:N_COMPONENTS]] # 对应 97% 方差的前 400 个 X_pca X_center.dot(top_vecs) np.save(X_pca.npy, X_pca) np.save(labels.npy, labels)这段代码把论文的公式落成了可跑的流程。第 13 行用 max_vals 归一化是在复刻论文“除以每维最大值得到该维平均值”那一步加 1e-8 是防止某个像素维在所有图里恰好都是 0。第 24 行直接 np.cov 在 12288 维上会非常慢样本量只有千级时更推荐用 sklearn.decomposition.PCA 的 svd_solverrandomized 或者先做 SVD 降维再算协方差这里写成显式特征分解是为了让你看清论文推导落到工程上不必照抄。N_COMPONENTS 想做得更严谨的话可以改成循环里累计 explained_variance_ratio_ 来动态选维数让代码自己找 97% 阈值对应的分量数。3. 网络结构与 Caffe 实现4 个卷积层、3 个 Pooling 层的设计细节3.1 结构选型为什么是 4 层卷积而不是更深的网络论文在相关工作里引了黄双萍等人用 GoogLeNet 检测水稻穗瘟、准确率到 92% 的例子说明当时深度卷积神经网络做农业病害已经是主流。但作者最终没有上 GoogLeNet 这类 20 层往上的深网络而是选了 4 个卷积层 3 个 Pooling 层 1 个全连接层的浅层结构。原因很直接手里的有效样本只有 2000 幅深度网络在小样本上极其容易过拟合训练集准确率冲得很高验证集掉得很难看4 层卷积在这个规模下刚好够表达叶片纹理差异又不至于把背景噪点也背下来。另一个选型理由是落地方便。Caffe 是当时对工业界最友好的深度学习框架网络结构用 prototxt 文本描述不用写前向反向代码训练入口和调参入口分得清清楚楚配上 Matlab 接口可以跑实验、出图表一把梭。论文的实验也确实是在 Ubuntu 14.04 Caffe Matlab 上完成的。放在今天这个结构用 PyTorch 重写就是几行 Conv2dMaxPool2dLinear但理解它为什么这么搭仍然值得。选卷积核大小时有个经验第一层用 5×5、后面用 3×3感受野逐层变大通道数从 32 起步逐层翻倍。这样在浅层抓边缘和颜色块在深层抓病斑的整体形态——稻瘟病的梭形斑、纹枯病的不规则云纹斑本质都是“局部纹理强 边缘形状有区分度”的目标卷积池化交替正好对口。3.2 逐层结构与尺寸变化输入 96×96×3 到全连接层网络入口是 96×96×3 的图像注意前文出现过 64×64 的归一化属于 PDF 前后不一致避坑章展开。结构与尺寸演变大致如下层名类型关键参数输出尺寸宽×高×通道dataInput96×96×396×96×3conv1Convolution5×5, pad 296×96×C1pool1PoolingMAX, 2×2, stride 248×48×C1conv2Convolution5×5, pad 248×48×C2pool2PoolingMAX, 2×2, stride 224×24×C2conv3Convolution3×3, pad 124×24×C3pool3PoolingMAX, 2×2, stride 212×12×C3conv4Convolution3×3, pad 112×12×C4fc1InnerProduct输出 N 维1×NoutputSoftmax5 类1×5论文原文没有给出每个卷积层的具体通道数我按这个样本规模的习惯配法给一版参考C132、C264、C3128、C4128fc1 输出 256 维。这套配置在 2000 幅量级下训练参数量可控显存占用也小。你换自己的数据时通道数可以按倍数缩放但别一上来就 256 起步样本量不够的话全连接层会把参数拉到百万级过拟合概率直接翻倍。池化放在第三层之后是刻意的。前两次池化把 96×96 降到 24×24病斑的细节特征主要靠前面两层卷积保留第三层池化后再接最后一层卷积既有足够的平移不变性又不会把特征图压得太狠导致小病斑漏检。全连接层拼接全局信息最后 Softmax 输出每类概率。3.3 train_val.prototxt 网络定义一份可直接改的模板Caffe 里网络结构、训练集和验证集的绑定都在 train_val.prototxt 里。把核心层贴上name: RiceLeafNet layer { name: data type: Data top: data top: label data_param { source: rice_train_lmdb # 训练集 lmdb由 convert_imageset 生成 batch_size: 64 backend: LMDB } transform_param { mean_file: rice_mean.binaryproto # 均值文件compute_image_mean 生成 } } layer { name: conv1 type: Convolution bottom: data top: conv1 convolution_param { num_output: 32 kernel_size: 5 stride: 1 pad: 2 # 保持特征图尺寸不缩 } } layer { name: relu1 type: ReLU bottom: conv1 top: conv1 } layer { name: pool1 type: Pooling bottom: conv1 top: pool1 pooling_param { pool: MAX kernel_size: 2 stride: 2 } } # conv2 / pool2 / conv3 / pool3 / conv4 与上面同模式 # 参数分别调成 64、128、128kernel 后两层换 3*3 layer { name: fc1 type: InnerProduct bottom: conv4 top: fc1 inner_product_param { num_output: 256 } # 全连接层特征维度 } layer { name: loss type: SoftmaxWithLoss bottom: fc1 bottom: label top: loss }这份 prototxt 把论文的网络结构翻译成了 Caffe 能直接训练的格式。三个点值得留意一是 Data 层在实际使用中要同时写训练和验证两个数据源训练分支在 phase 里标注 TRAIN验证分支标注 TEST我这里省略了验证分支防止模板太长二是 conv1 的 pad 设为 2 是为了让 96×96 输入经过 5×5 卷积后尺寸不缩水后两层 3×3 卷积配 pad 1 同理特征图尺寸只由池化层控制后面全连接层的维度好算三是全连接层输出维度一旦定下来换输入尺寸比如改成 64×64就得重算 fc1 的输入维度这也是很多人复现翻车的根源。提示Caffe 对层名称大小写敏感prototxt 里写了什么名字训练脚本和 deploy 文件就要用同一个名字改了名字没同步改 deploy加载权重时就会报维度对不上。4. 训练策略与 10 重交叉验证平均 96.9% 是怎么测出来的4.1 solver.prototxt 参数学习率、batch_size 与迭代次数的取舍Caffe 的训练行为不写在网络文件里而是由 solver.prototxt 单独控制。论文训练基于 Ubuntu 14.04Matlab 平台调 Caffe 接口我用 Caffe 的经典 solver 写法把关键参数梳理一遍net: train_val.prototxt test_iter: 20 # 验证集 400 张 / batch 20 20 轮 test_interval: 100 # 每 100 次迭代跑一次验证 base_lr: 0.01 # 初始学习率 lr_policy: step stepsize: 1000 # 每 1000 次迭代学习率衰减一次 gamma: 0.1 # 衰减系数 momentum: 0.9 # 动量加速收敛并抑制震荡 weight_decay: 0.0005 # 权重衰减防止过拟合 display: 50 # 每 50 次迭代打印一次 loss max_iter: 3000 # 总迭代上限 snapshot: 1000 # 每 1000 次迭代存一次模型快照 solver_mode: GPU论文原文给了一组参数c、n、p、n1、p1、p2、n2 等PDF 里这组参数经过扫描排版后辨识度不高能确认的范围是 batch 数量级、池化间隔与类别数相关量。复现时不必纠结这组手写字母直接按上面的常规配置起步base_lr 0.01、batch_size 64、max_iter 3000用验证集 loss 的变化再调。base_lr 是最容易出问题的那个——Caffe 默认示例里 0.01 是针对 ImageNet 的2000 幅小数据集上常需要降到 0.001 才稳否则前几步 loss 直接冲上 NaN。test_iter 和 test_interval 的配合容易算错。验证集是 40% 即约 800 幅按 2000 幅口径batch 64 时跑一轮验证需要 ceil(800/64) ≈ 13 个 batchtest_iter 设成 13 或稍大的整数值都行设小了验证准确率波动大设大了白等。10 重交叉验证每折的数据规模是动态变化的solver 里最好按最大折的验证集规模来配 test_iter。4.2 10 重交叉验证与 6:4 划分小样本实验怎么测才靠谱论文的实验设计是随机选 60% 训练、40% 测试再叠加 10 重交叉验证。这个组合在样本量只有 2000 时很关键单次 6:4 划分的偶然性太大某一折训练集里恰好少了某种病害的代表性样本准确率能掉到 90% 以下10 重交叉验证把数据切成 10 份轮流拿 1 份当验证、其余 9 份训练跑 10 次取平均把划分运气摊平。复现时我建议用分层交叉验证而不是默认的随机 KFold。病害图像按来源分批次采集随机切分很容易让某一折全是同一天、同一块田的图模型在这种折上既作弊又翻车——训练时见过同场景、验证时场景太单一。分层切分能保证每折里 5 类病害的比例和总体一致。用 sklearn 实现很简单from sklearn.model_selection import StratifiedKFold X_pca np.load(X_pca.npy) # PCA 降维后的 400 维特征 labels np.load(labels.npy) # 类别标签 skf StratifiedKFold(n_splits10, shuffleTrue, random_state42) for fold, (train_idx, val_idx) in enumerate(skf.split(X_pca, labels)): # 每折内部再按 6:4 从 train_idx 里拆出训练子集和测试子集 # 也可以直接把 val_idx 当测试集训练子集再从剩余 9 折中取 X_train, X_val X_pca[train_idx], X_pca[val_idx] y_train, y_val labels[train_idx], labels[val_idx] # 在这里把 X_train/y_train 写成 lmdb 送给 Caffe 训练 # 每一折都保存 accuracy最后取平均这段代码的用意是让交叉验证的划分逻辑可复现。shuffleTrue 配合固定 random_state42保证每次重跑脚本切分结果一致实验记录里写“准确率 96.9%”才有意义不固定随机种子的话同一个网络同一批数据两次跑出来差 2~3 个百分点是常态。StratifiedKFold 的关键词是“Stratified”它会按 labels 的比例做分层采样避免某一折验证集里某种病害一张都没有。4.3 结果对比CNN 与 SVM、BP 的准确率和耗时论文把三条路线放到同一批数据上对比结果如下方法平均识别率运行时间深度学习 CNN论文方法96.9%78msSVM86.4%81msBP 神经网络89.3%85msCNN 比 SVM 高了 10.5 个百分点比 BP 高了 7.6 个百分点训练时间还略短。这个对比很有说服力也符合当时的普遍结论SVM 靠手工设计特征颜色共生矩阵、边缘检测算子这类特征表达上限锁死遇到叶片光照变化、病斑形态变异就掉点BP 虽然也是神经网络但层数浅、没有卷积的局部感受野和平移不变性对图像空间结构建模能力弱。CNN 端到端自己学特征从像素直接到类别概率把纹理、边缘、形状信息都吃进去了。细看单类结果稻瘟病的识别率是 5 类里偏低的那一档拖低了整体平均。原因不复杂稻瘟病有急性型、慢性型、褐点型等多种病斑表现同为稻瘟病视觉差异比不同病害之间的差异还大。如果你的项目碰到的也是这种“类内差异大于类间差异”的问题别急着加网络深度先把数据里的细分表型归归类或者给这类样本多配几倍数量比换模型更管用。提示论文记录的单张运行时间 78ms 是在当时的 GPU/CPU 环境下测的换现代显卡重跑只会更快。比较价值在于同环境下的相对快慢别拿它当今天的性能基准。5. 复现避坑Caffe 跑水稻病害模型的四个翻车现场5.1 尺寸不一致64×64 归一化和 96×96 网络输入的矛盾现象按论文前文“归一化处理为 64×64 大小”做数据PCA 降维没问题但一进网络就报维度错误全连接层 Shape mismatch。原因论文第 2 章写的是 64×64 归一化第 3 章写网络输入是 96×96×3两个尺寸在文本里前后不一致。Caffe 的 Data 层按 96×96 读图数据做成 64×64 就直接崩。解决以网络输入为准图像统一 resize 到 96×96×3 再进网络PCA 那一步继续用 64×64 展平没影响因为 PCA 只是降维不参与网络训练。如果显存小想用 64×64 输入也可以但要把 conv 层 pad 和 fc1 输入维度按新尺寸重算等于自己改了一版网络结构。我一般会加一个脚本统一检查数据集的尺寸分布跑训练前先打印 batch 里每张图的 shape。5.2 交叉验证划分不均同网络两次训练差 5 个点现象同一份数据、同一套网络参数上一次训练验证集准确率 95.8%重跑一次只有 91.2%loss 曲线走势还正常。原因用默认随机切分某一折验证集里某种病害的样本比例和整体差太多甚至出现一整折没有恶苗病的情况模型在该折上的表现被严重拉偏。小样本实验里这个问题被放大2000 幅图切 10 折每折只有 200 幅再细分成 5 类每一类每折平均只有 40 幅稍有偏斜准确率就波动几个点。解决交叉验证一律走 StratifiedKFold并固定 random_state每折开始前打印验证集类别分布做确认。另外一个容易漏的点是图像按目录读取的顺序往往按文件夹名排序直接 KFold 会把同一个文件夹里的图全部划到同一折相当于人为制造了分布偏移。shuffle 必须开。5.3 Caffe 数据层报错与 loss 为 NaN现象训练启动后 loss 直接打印 nan或者 Data 层报 “Data layer prefetch” 错误训练进程卡死。原因常见有三。一是 lmdb 数据没生成或路径错Data 层找不到 rice_train_lmdb二是没有做 mean_file或者 data 层引用了均值文件但路径不对三是 base_lr 太大小数据集上 0.01 起步很容易让梯度爆炸loss 直接冲成 nan。解决先用 Caffe 自带的 convert_imageset 把图片列表转成 lmdb再 compute_image_mean 生成 rice_mean.binaryprotoprototxt 里两个路径核对无误再开跑学习率先压到 0.001看 loss 能正常下降再往上调。这里有个经验Caffe 的 Data layer 对图片尺寸和通道顺序非常严格输入若是灰度图会静默报错检查 lmdb 之前先把图片统一转成三通道 JPG。5.4 PCA 阈值设得过高99% 主成分反而拖慢训练现象把 PCA 的保留主成分阈值从 97% 提到 99%特征维度从 400 涨到 700 多训练时间明显变长准确率不仅没涨反而掉了 1 个点左右。原因保留 99% 方差意味着把大量处于高维空间的细节噪声也留下来了这些维度对分类没有判别力还会让全连接层的输入维度变大参数随之增多过拟合风险上升。PCA 降维不是保得越多越好97% 这个阈值是论文拿实验试出来的平衡点。解决按论文先用 400 维、97% 阈值跑基线如果想自己验证做一个“主成分数量-验证集准确率”的曲线找准确率拐点再定维度。一般 300~500 维之间会有明显平台期超过平台期继续加维度获得的准确率提升可以忽略。这条经验对任何小样本图像分类任务通用不止水稻病害。6. 模型验证与特征提取deploy.prototxt 和全连接层特征的几种用法6.1 用 deploy.prototxt 提取全连接层病斑特征模型训完除了直接分类还能把它当特征提取器用。论文特意提到选择全连接层作为病斑特征层这就是典型的迁移用法把 fc1 层输出的 256 维向量当成人工设计的特征再喂给线性 SVM 或做聚类。deploy.prototxt 和训练用的 train_val.prototxt 差别在于去掉 loss 层、只保留到 fc1 和 Softmax。用 Caffe 的 Python 接口提取特征import caffe net caffe.Net(deploy.prototxt, rice_model.caffemodel, caffe.TEST) # 输入单张 96*96*3 图Caffe 默认按 NCHW先扩一个 batch 维 net.blobs[data].data[0] img_96x96x3.transpose(2, 0, 1) _ net.forward() feat net.blobs[fc1].data[0] # 256 维病斑特征可直接存成 npy prob net.blobs[prob].data[0] # 5 类概率argmax 得到类别实际用途有两类比较顺手一是把短小数据集里所有图片过一遍网络拿到特征矩阵后跑一次聚类能发现哪些病害子类是视觉上天然混在一起的辅助重新整理数据二是和 PCA 特征做对比实验看模型学到的病斑特征比手工 PCA 特征强多少。deploy 文件的层名必须和训练文件完全一致否则加载权重时名字对不上就报维度错误。6.2 从 Caffe 迁移到 PyTorch 的验证思路如果现在才动手我不会劝你在 Ubuntu 14.04 上重新配 Caffe 环境那个依赖链条太磨人了。更快的路子是把这份网络结构转写成 PyTorch——四层卷积加一个全连接半天就能跑通import torch.nn as nn class RiceNet(nn.Module): def __init__(self, n_classes5): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, 5, padding2), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 5, padding2), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(128, 128, 3, padding1), nn.ReLU(), ) self.classifier nn.Linear(128 * 12 * 12, n_classes) def forward(self, x): x self.features(x) return self.classifier(x.view(x.size(0), -1))96×96 输入经过三次 MaxPool2d 尺寸减半三次变成 12×12所以全连接输入是 128×12×12。这个数值关系是迁移时最容易算错的点——把池化次数改了全连接维度就得跟着改。验证阶段用测试集算混淆矩阵看哪些类互相混淆比只看平均准确率更能发现模型的真实短板。这份论文 PDF 里还保留了完整的参考文献和表 1 的原始数据做结题报告或写论文时拿它做对照很方便。从那以后我每次复现这类小样本图像分类项目都会强制先跑一遍这个流程数据尺寸统一检查、StratifiedKFold 固定随机种子、先浅层 CNN 出基线再决定要不要上更深的结构。这份论文的框架到今天依然管用少走了不少弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表