ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CNN-SVM组合实战:小样本遥感图像分类的特征提取与决策优化

CNN-SVM组合实战:小样本遥感图像分类的特征提取与决策优化 简介一份面向深度学习与图像分类实践者的CNN-SVM融合实现资源聚焦如何将卷积神经网络自动提取的特征输入支持向量机完成分类以提升模型精度适合图像识别及遥感地物分类等空间特征明显的任务。资源包共8个文件压缩后约8KB包含6个Python脚本、1个说明文档和1个Markdown文件其中Python脚本完整覆盖了CNN模型训练、深度特征提取、SVM训练与预测并附有t-SNE可视化脚本用于检验特征分布说明文档和README则梳理了从Keras构建网络到scikit-learn接入SVM的衔接思路。目前已有2086人学习/下载。读者通过这份资源能拿到可直接运行的融合流程示例既能掌握用CNN最后一层激活输出作为SVM输入的核心做法也能参考现有代码调整网络层数、核函数与惩罚参数快速迁移到自身图像分类任务中。1. CNN-SVM 这个组合卷积特征交给支持向量机为什么在小样本图像上更管用看过不少深度学习做图像分类的项目最后的分类器几乎清一色是 Softmax很少有人会回头再用一个经典 SVM。这份源码包恰好是反着来的先用卷积神经网络把图像转成特征向量再交给支持向量机划分类别。路线听起来传统但我在小样本、类别不平衡的遥感图像分类场景里实际跑过这个组合往往比单独调 CNN 更容易出效果尤其当你的训练样本只有几百张、想靠数据增强硬撑 Softmax 又撑不动的时候。这份资源是一个完整的 Python 实现工程目录叫 Land_Use_CNN-master核心脚本包括训练 CNN、提取特征、训练 SVM、预测和 t-SNE 可视化一条链路走到底。适合样本量不大、想快速拿到可解释决策边界的从业者也适合想把深度学习和 SVM 衔接机制看明白的初学者。解压后按顺序跑一小时内能看到分类报告和可视化输出。2. 源码包结构与执行顺序六个 Python 脚本各管哪一段2.1 文件清单与依赖关系拿到压缩包先别急着跑先看一遍文件列表弄清楚每条脚本的上下游关系。包里除了说明文档核心是 6 个脚本和 Land_Use_CNN-master 目录文件职责产出物train.py训练 CNN 分类网络保存权重cnn_model.h5extract_features.py加载 CNN截取倒数第二层输出作为特征features.npy、labels.npytrain_svm.py用特征训练 SVM 分类器svm_model.pkl、scaler.pklpredict.py用 CNN 模型预测单张图分类结果svm_predict.py加载 SVM 模型预测输出分类报告评估指标t_sne.py对特征做 t-SNE 可视化二维散点图执行链是严格单向的train.py → extract_features.py → train_svm.py → svm_predict.py。t_sne.py 可以插在提取特征之后、训练 SVM 之前用来验证 CNN 提取的特征是否真的分得开。提示跳过 train.py 直接跑 extract_features.py会在 load_model 那一步直接报错因为没有权重文件。常见做法是先把 CNN 训到收敛哪怕分类准确率一般特征也比随机初始化强很多。2.2 环境安装与版本选择这个项目用的是 Keras 构建 CNN、Scikit-learn 训练 SVM两者都需要 python 环境。版本搭配我踩过一轮坑最后固定下来的组合是这样conda create -n cnn_svm python3.8 conda activate cnn_svm pip install tensorflow2.10.0 pip install scikit-learn1.2.2 pip install numpy1.23.5 pandas matplotlib joblib为什么要锁这几个版本TensorFlow 2.10 是最后一个能在 Windows 原生环境稳定安装的版本2.11 以上对 Windows 的 CUDA 支持方式变了容易在编译环节翻车。scikit-learn 1.2.2 配 numpy 1.23.5 是官方测试过的组合直接跑 t_sne.py 不会因为版本不匹配报错。如果你用的是 macOS 或 LinuxTensorFlow 版本可以放宽但 numpy 和 scikit-learn 的建议不要动。装完后建议跑一条冒烟命令python -c import tensorflow, sklearn, numpy; print(tensorflow.__version__, sklearn.__version__, numpy.__version__)三个版本号都打印出来再继续能省掉后面一半的报错排查时间。这一步看起来多余实际作用是把环境问题从项目问题里隔离出来。2.3 train.py 大致做了什么train.py 的任务是训练一个能对图像分类的 CNN但它的产出不只是给 Softmax 分类用的更多是为后续特征提取服务。典型的模型结构是这样# train.py 中的核心模型结构适用于 64x64 的 RGB 图像 from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout model Sequential([ Conv2D(32, (3, 3), activationrelu, paddingsame, input_shape(64, 64, 3)), MaxPooling2D(pool_size(2, 2)), Conv2D(64, (3, 3), activationrelu, paddingsame), MaxPooling2D(pool_size(2, 2)), Flatten(), Dense(128, activationrelu), # 这一层的输出后面会被 SVM 当特征用 Dropout(0.5), Dense(n_class, activationsoftmax) # n_class 按你的数据集类别数改 ]) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy])参数说明input_shape 按工程默认的 64×64 写如果你的 Land Use 图像是 128×128把第一个卷积层的 input_shape 同步改掉即可。paddingsame 是让卷积不缩小特征图尺寸对浅层网络来说信息保留更完整。Dropout 0.5 加在全连接层之后防止特征维度不高时过拟合。训练完成后准确率只是参考指标。即使 CNN 自己分类只有 80% 出头只要特征不是完全乱成一团后续 SVM 都可能拉到更高。这也是这个组合最反直觉的地方实验里 CNNSoftmax 到 85% 就上不去了同样的特征交给 SVM 能到 90%。原因在于 Softmax 的决策边界是线性的而 SVM 用核函数在高维空间里找的边界更贴合小样本的分布。3. 特征提取层为什么截倒数第二层而不是直接用预测结果3.1 像素直接喂给 SVM 的问题有人会问既然 SVM 能分类为什么不让它直接吃原始图像理论上可以64×64×312288 维像素特征RBF 核的 SVM 能算但有两个实际问题一是矩阵规模样本量到了几千SVM 要计算样本两两之间的核矩阵内存和时间都扛不住二是像素级特征对光照、平移、缩放太敏感SVM 需要大量样本才能把这些干扰项学进边界里。用 CNN 卷积层做特征提取本质上是一种有监督的降维。卷积层一层层把局部纹理和边缘组合成语义信息最终在全连接层形成 128 维或 512 维的浓缩特征向量。这一步把原始图像的空间冗余压缩掉了SVM 再在这份特征上做分类代价就小得多。3.2 该取哪一层的激活值不是每一层都值得拿来做 SVM 输入选错层效果甚至会比直接跑 Softmax 还差。各层特征的性质差异很大层位置维度示例特征性质适合做 SVM 输入吗第一个卷积层输出32×64×64边缘、色块等低级纹理不适合维度太高且语义弱最后一个卷积层输出64×8×8局部模式组合可以但需要先展平并降维倒数第二个全连接层128全局语义特征最常用信息浓缩且维度可控Softmax 输出层n_class归一化概率不适合概率丢失方差边界信息少我一般取倒数第二个全连接层的激活值也就是 Softmax 之前那层 Dense 的输出。这个位置的特征已经融合了全局信息又没被归一化SVM 能更好地利用特征分布的相对差异。3.3 extract_features.py 的实现逻辑extract_features.py 的写法核心是用 Keras 的 Model 接口截断网络# extract_features.py加载训练好的 CNN截取特征层输出 from tensorflow.keras.models import Sequential, Model, load_model import numpy as np model load_model(cnn_model.h5) # 从后往前找最后一个 Dense 层取它的输入作为特征 for i in range(len(model.layers) - 1, -1, -1): if dense in model.layers[i].name: feat_layer_index i break feature_model Model(inputsmodel.input, outputsmodel.layers[feat_layer_index].output) features feature_model.predict(x_data, batch_size32, verbose1) np.save(features.npy, features) np.save(labels.npy, y_data)这里的逻辑说明不用写死 model.layers[-2]因为网络最后可能还有 Dropout 或 BatchNormalization写死索引容易在改结构之后取错层。用循环从尾部找到第一个名字含 dense 的层取它上一层的输出语义上正好是激活后的特征。batch_size32 控制显存和内存占用显存不够就降到 16。这一步的产出 features.npy 是二维数组每一行对应一张图的 128 维特征向量labels.npy 是和它行数对齐的标签数组。这两个文件必须一起保存、一起 shuffle后面 SVM 训练阶段只认这两个文件不做任何跨文件对齐。4. 训练 SVMC 值、核函数与特征标准化的组合4.1 train_svm.py 拆解拿到特征向量之后SVM 部分的代码比 CNN 简单一个量级但参数影响反而更玄学。train_svm.py 的核心流程分为三步标准化特征、训练 SVC、保存模型和标准化器# train_svm.py特征标准化 RBF 核 SVM 训练 import numpy as np import joblib from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.metrics import classification_report features np.load(features.npy) labels np.load(labels.npy) scaler StandardScaler() X_scaled scaler.fit_transform(features) clf SVC(C1.0, kernelrbf, gammascale, class_weightbalanced, probabilityTrue, random_state42) clf.fit(X_scaled, labels) joblib.dump(clf, svm_model.pkl) joblib.dump(scaler, scaler.pkl)参数说明StandardScaler 先把每个特征维度减去均值、除以标准差这一步对 SVM 至关重要。CNN 提取的特征不同维度量纲不一致有的维数值域在 -10 到 10有的在 0 到 1SVM 计算距离时会把量纲大的维度主导掉。class_weightbalanced 是在类别分布不均时自动给少数类加权Land Use 这类遥感图像标注经常有类别比例悬殊这个参数几乎每次都要开。probabilityTrue 是为了后续能输出概率值代价是训练时间增加不需要概率可以关掉。4.2 核函数和 C、gamma 怎么调SVM 的核函数选择直接决定决策边界的形态。对 CNN 提取的高维特征三种核通常这样选核函数适用场景参数注意点linear特征已经足够抽象线性可分基本只需调 C训练最快rbf特征分布重叠、非线性强需要同时调 C 和 gammaRBF 是默认首选poly特征之间存在低阶多项式关系阶数不好估容易过拟合少用我一般先用 RBF 配合默认的 gammascale它按 1 / (特征维度 × 特征方差) 自动估计 gamma比手动给定一个固定值更稳。C 从 1 开始试小样本下 C 太大容易过拟合训练集太小的 C 又会让边界过于宽松。特征维度在 128 上下时C 在 1 到 100 之间的效果区别不大真正敏感的是 gamma 和是否做标准化。4.3 网格搜索替代手试手动调参容易漏最省事的方案是直接网格搜索把 C 和 gamma 的候选值丢给 GridSearchCV用交叉验证选出最优组合# 网格搜索找 C 和 gamma 的最优组合 from sklearn.model_selection import GridSearchCV param_grid { C: [0.1, 1, 10, 100], gamma: [0.001, 0.01, 0.1, scale] } grid GridSearchCV(SVC(kernelrbf, class_weightbalanced, probabilityTrue), param_grid, cv5, scoringf1_macro, n_jobs-1) grid.fit(X_scaled, labels) print(best params:, grid.best_params_) print(best cv score:, grid.best_score_)这里的逻辑说明scoring 用 f1_macro 而不是 accuracy因为类别不平衡时 accuracy 会虚高。cv5 表示五折交叉验证样本量少可以减少折叠数。n_jobs-1 让所有 CPU 核并行搜索能快不少但数据量特别大时会吃满内存。网格搜索跑完把 grid.best_params_ 填回 SVC 再全量训练一次得到的模型才是最终上线的版本。注意 GridSearchCV 里面已经做过一次标准化吗没有标准化必须在搜索之外单独做GridSearchCV 只管分类器参数。如果你把 StandardScaler 放进 Pipeline搜索时交叉验证的每一折都只用了训练部分的特征统计量这样更规范但在特征维度不算太高时效果差异不大。5. 常见问题与排查五个容易翻车的地方5.1 训练报告很漂亮预测时全归到某一类现象svm_predict.py 输出的分类报告里某一类 precision 和 recall 接近 1其他类别几乎全为 0预测结果清一色是同一个类别。原因CNN 提取的特征在小样本下本身有偏加上 SVM 默认不考虑类别不平衡决策边界整体偏移到样本量大的那一类。这类问题在 RBF 核上尤其明显。解决训练 SVC 时带上 class_weightbalanced让少数类的错误代价放大。如果加了还是压不住对少数类做简单过采样把样本数少的类别重复采样到次小类别的数量再做标准化和训练。5.2 SVM 效果比 CNN 自带的 Softmax 还差现象提取特征后 SVM 分类准确率反而低于 train.py 里的 CNN 准确率差 5 到 10 个点。原因取错了特征层。常见的是直接把 Softmax 输出喂给 SVM或者取到了第一个卷积层的展平输出。Softmax 输出经过归一化概率值之间相互制约丢失了特征分布的细节浅层卷积特征又没有语义浓缩维度还高SVM 学不到有效边界。解决按照第 3 章的循环找最后一个 Dense 层的输入作为特征。如果工程里给的特征提取脚本是直接取 model.layers[-2].output先打印各层名字核对一遍确认取的是 Dense 层输入而不是 Dropout 层输入。5.3 RBF 核训练时间过长内存占用异常现象特征维度在 128 左右、样本量上万时GridSearchCV 一次搜索跑了十分钟还没完内存也跟着涨。原因RBF 核要计算样本两两之间的核矩阵复杂度接近样本量的平方。样本上万、候选参数几十组时总计算量很可观。特征维度高时即使样本量不大kernel 计算也会慢。解决先对特征做 PCA 降到 64 到 128 维再喂 SVM。降维损失一点特征细节但 SVM 的分类准确率通常不会降太多训练时间能缩短一个数量级。另一个做法是先把 RBF 换成 linear 试试如果 linear 效果已经够用就没有必要执着于 RBF。5.4 训练时准确率高线下评估却对不上现象train_svm.py 里的交叉验证 score 很高svm_predict.py 单独跑另一批数据的准确率明显偏低。原因很大的可能性是特征和标签的对应关系在 shuffle 时错位了。extract_features.py 保存的 features.npy 和 labels.npy 如果各自 shuffle或者后续载入时用了不同的索引顺序SVM 学到的是错位的标签分布。这个问题很隐蔽代码不会报错但结果完全不可信。解决提取特征时同时输出一个长度为样本数的索引数组训练和预测都按这个索引取特征和标签保证两批数据严格对齐。判断是否错位的最快方法是对比 features.npy 的行数与 labels.npy 的长度不一致一定是提取阶段就有问题。5.5 numpy 或 scikit-learn 版本冲突导致运行崩溃现象t_sne.py 或 svm_predict.py 运行时报错类似 sklearn 内部抛 numpy 的 AttributeError或者两个包相互提示接口不兼容。原因scikit-learn 新版本依赖较新 numpy而 TensorFlow 2.10 对 numpy 有上限要求pip 升级某个包时会把另一个包破坏。解决严格按第 2 章的版本组合安装scikit-learn 1.2.2、numpy 1.23.5、TensorFlow 2.10.0。装完之后不要单独升级任何一个包。已经升级过导致报错的直接用 conda 重建环境比降级来回折腾快得多。6. 训练 SVM 之前先用 t-SNE 看一眼特征好不好6.1 t_sne.py 的使用方式SVM 调参调得再认真如果 CNN 提取的特征本身分不开后面全是白费。所以我排执行顺序的时候习惯把 t_sne.py 放在 extract_features.py 之后、train_svm.py 之前先用可视化确认特征结构。t_sne.py 的核心就几行# t_sne.py将特征降到二维按标签着色观察聚类情况 import numpy as np import matplotlib.pyplot as plt from sklearn.manifold import TSNE from sklearn.decomposition import PCA features np.load(features.npy) labels np.load(labels.npy) feat_reduced PCA(n_components50).fit_transform(features) tsne TSNE(n_components2, perplexity30, random_state42) feat_2d tsne.fit_transform(feat_reduced) plt.figure(figsize(8, 8)) plt.scatter(feat_2d[:, 0], feat_2d[:, 1], clabels, cmaptab10, s5, alpha0.8) plt.colorbar() plt.savefig(feature_tsne.png, dpi200)参数说明先做 PCA 降到 50 维再接 t-SNE是常见做法。PCA 去掉噪声并大幅缩短 t-SNE 的计算时间同时保留样本间的全局结构。perplexity30 是 t-SNE 里最常用的值样本几千到几万之间都能用如果你的样本量特别小比如不到 200降到 10 到 15 效果更好。random_state42 固定随机种子保证每次跑出的图一致方便对比不同训练轮次的特征变化。6.2 怎么看这张图合格的 t-SNE 图应该呈现同一类别聚成一块、不同类别之间有可见间隔的形态。边缘有一两个点混到别的类别里是正常现象但如果出现两种极端情况就要回头处理一是所有点混成一个圆团说明 CNN 还没学到有效特征回去调大 train.py 的训练轮数或者加深模型二是出现多条细长的线状簇说明特征被某一维度主导检查一下是否忘了标准化。这两种情况即使 SVM 交叉验证分数不低也是假象换到新数据大概率撑不住。有了这张图做索引后面调 C 和 gamma 就更有方向感。特征聚得紧凑时C 不用开太大gamma 用默认即可特征叠得厉害时优先考虑加特征维度和训练轮数而不是死磕 SVM 参数。6.3 一个经常被忽略的细节跑了多组网格搜索之后我把最优 SVM 模型和之前在 CNN 随手训出的 Softmax 模型做了对比发现 SVM 确实略胜一筹但优势没有想象中大。后来把那次的特征重新跑了一遍 t-SNE 才意识到特征是单次训练的中间产物训练过程本身波动很大同一份数据换一个随机种子训出来的 CNN特征分布也会有差异后接的 SVM 自然跟着上下浮动。从那以后我每次提取完特征都会固定随机种子重训两遍 CNN比较两遍特征的 t-SNE 轮廓差异明显才去调 SVM 参数避免在偶然波动上花太多时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表