☰
SVM图像分类实战:HOG+LBP特征工程与RBF核调参
2026/10/6 2:58:24 网站建设 项目流程

简介:本资源是一份基于传统机器学习方法的Kaggle猫狗图像分类高分实战项目,面向计算机相关专业本科生、机器学习初学者及课程设计/期末大作业需求者,聚焦SVM模型在图像分类任务中的完整实现与深度分析。压缩包共4个文件(391KB),含核心训练脚本train.py、结构清晰的README.md说明文档、.DS_Store系统文件及一份详实的Word版高分报告(.docx),涵盖数据预处理、特征提取(如HOG+PCA)、SVM参数调优、交叉验证与结果可视化全流程。项目经导师指导并获98分评审高分认可,报告中包含实验设计逻辑、关键代码注释、性能对比分析与常见问题排错建议,可直接用于学习复现或作为机器学习课程实践范本。目前已有94人下载学习,内容精炼、路径明确,适合夯实传统ML基础并衔接深度学习入门。

1. 用 SVM 在 Kaggle 猫狗数据集上跑出 98 分:不是靠调参玄学,而是特征工程+核函数选型的硬功夫

你可能已经试过用 ResNet、VGG 在猫狗分类上轻松刷到 99%+ 准确率——但当你被要求交一份「不许用深度学习」的机器学习课程设计时,SVM 就不再是教科书里的公式,而是一道必须亲手拆解的硬题。这个资源包不是“用 sklearn.SVC(fit=True) 跑通就完事”的玩具项目,它是一份经导师逐行审阅、答辩现场被追问 7 个参数含义、最终拿下 98 分的完整交付物:从原始 Kaggletrain.zip解压后的 25,000 张图,到手工提取 HOG+LBP 特征向量,再到 RBF 核的 γ 与 C 的网格搜索边界控制,最后输出带交叉验证曲线、混淆矩阵热力图、特征重要性排序(通过 SVM 权重映射回图像块)的 Word 报告。它专为两类人准备:一是被期末大作业卡在「传统方法」要求上的本科生,二是想真正理解「为什么 SVM 在小样本图像任务上仍具竞争力」的转行者。它不教你如何跳过特征工程,而是把每一步——包括为什么不用 PCA 降维而用标准化后直接喂入、为什么训练集要强制按 4:1 划分而非默认 3:1、为什么测试集必须保留原始文件名路径用于报告溯源——全写进train.py的注释和报告.docx的「方法论依据」章节里。


2. 特征工程实操:HOG + LBP 双通道手工特征提取,拒绝端到端黑匣子

传统机器学习做图像分类,核心不在模型本身,而在特征是否能承载判别信息。这个项目没用预训练 CNN 提取特征(那已属迁移学习范畴),而是回归经典:用 OpenCV 手工构建可解释、可调试、可复现的特征管道。整个流程封装在codes/feature_extractor.py中,但关键逻辑全部内嵌于train.py的extract_features()函数里——这是评审老师重点抽查的部分。

2.1 图像预处理:尺寸归一化与灰度化不是可选项,而是 SVM 收敛前提

SVM 对输入尺度极度敏感,尤其当像素值范围在 0–255 时,RBF 核的 γ 值会因数值量级失衡而失效。项目采用两级归一化:

  • 空间归一化:所有图片 resize 到 128×128(非 224×224),原因明确写在报告第 3.2 节:“128 是 HOG 梯度计算窗口(8×8 cell)与 block(2×2 cell)整除的最小公倍数,避免插值引入伪梯度”;
  • 像素归一化:转灰度后,不做 min-max 缩放,而是用StandardScaler按通道全局拟合——这点常被忽略,但train.py第 47 行明确调用scaler.fit_transform(features),且 scaler 保存为scaler.pkl供推理复用。
# train.py 第 42–46 行 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) resized = cv2.resize(gray, (128, 128)) # HOG 参数:winSize=(128,128) 与图像尺寸严格一致,blockSize=(16,16) 对应 2×2 cell hog_feat = cv2.HOGDescriptor( _winSize=(128, 128), _blockSize=(16, 16), # 2×2 cells → 16×16 pixels _blockStride=(8, 8), # stride = cell size → 8×8 _cellSize=(8, 8), # fundamental unit _nbins=9 # gradient orientation bins ).compute(resized)

提示:_winSize必须等于图像尺寸,否则 HOG 描述符长度不固定,无法堆叠成特征矩阵。项目中若误设为(64,64),后续np.vstack()会报ValueError: all the input arrays must have same number of dimensions——这是评审时第一个被揪出的硬伤。

2.2 HOG 特征:梯度方向直方图不是“拿来即用”,而是要验证其对猫耳/狗鼻的敏感性

HOG 擅长捕捉轮廓与纹理,但猫狗差异常在局部细节(如猫耳尖锐度、狗鼻湿润反光)。项目在codes/visualize_hog.py中提供了可视化脚本,用matplotlib叠加原始图与 HOG 矢量场,重点验证两点:

  • cell 大小选择:_cellSize=(8,8)对应 8×8 像素区域,足够覆盖猫耳边缘(约 10–15 像素宽),若设为(16,16)则丢失耳尖细节;
  • bin 数设置:_nbins=9覆盖 0–180° 梯度方向(无向),比 18-bin(有向)更鲁棒——报告第 4.1 节附有消融实验:9-bin 比 18-bin 在验证集上高 1.2% 准确率,因猫狗毛发方向随机性强,有向统计反而引入噪声。

HOG 特征向量长度 =(winSize/blockSize)^2 × (blockSize/cellSize)^2 × nbins
代入得:(128/16)^2 × (16/8)^2 × 9 = 8² × 2² × 9 = 64 × 4 × 9 = 2304维。这是后续所有实验的基准维度。

2.3 LBP 特征:局部二值模式补足 HOG 缺失的微观纹理判据

HOG 对光照变化鲁棒但对细微纹理不敏感;LBP 恰好相反。项目采用 Uniform LBP(ULBP),其核心是cv2.face.LBPHFaceRecognizer_create()的底层逻辑,但手动实现以保证可追溯:

# codes/lbp_extractor.py 关键片段 def uniform_lbp(img): lbp = np.zeros_like(img, dtype=np.uint8) for i in range(1, img.shape[0]-1): for j in range(1, img.shape[1]-1): center = img[i, j] binary = '' # 顺时针采样 8 邻域 for di, dj in [(-1,-1), (-1,0), (-1,1), (0,1), (1,1), (1,0), (1,-1), (0,-1)]: if img[i+di, j+dj] >= center: binary += '1' else: binary += '0' # 统计跳变次数(0→1 或 1→0) transitions = sum((binary[k] != binary[(k+1)%8]) for k in range(8)) if transitions <= 2: # Uniform pattern lbp[i, j] = int(binary, 2) else: lbp[i, j] = 59 # 59 是 ULBP 中非 uniform pattern 的统一编码 return lbp

Uniform LBP 将 256 种模式压缩至 59 类(0–57 为 uniform,58 为 non-uniform,59 为 fallback),大幅降低维度。项目中 LBP 直方图 bin 数设为 59,经cv2.calcHist()计算后拉平为 59 维向量。最终 HOG(2304 维)+ LBP(59 维)= 2363 维特征,远低于原始 128×128=16384 像素,且物理意义清晰:HOG 抓结构,LBP 抓纹理。

2.4 特征拼接与标准化:为什么不用 PCA 而坚持 StandardScaler?

常见误区是“高维就该降维”,但该项目报告第 5.3 节用实验数据反驳:PCA 保留 95% 方差需 1800+ 主成分,而 SVM 在 2363 维原始特征上训练更快、泛化更好。原因在于:

  • HOG 与 LBP 量纲不同(HOG 值域 ≈ [-1,1],LBP 为整数 0–59),PCA 会因尺度差异扭曲主方向;
  • SVM 的决策超平面依赖各维度权重,PCA 后的主成分失去原始物理意义,无法在报告中解释“哪类纹理对分类贡献最大”。

因此,项目采用StandardScaler对全部 2363 维做零均值单位方差缩放,代码位于train.py第 52 行:

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train_hog_lbp) # X_train_hog_lbp shape: (n_samples, 2363) X_test_scaled = scaler.transform(X_test_hog_lbp) # 注意:test 用 train 的 scaler

fit_transform仅在训练集上调用,transform用同一 scaler 处理测试集——这是防止数据泄露的铁律,也是评审扣分高频点。


3. SVM 模型构建与调参:RBF 核的 γ 与 C 不是暴力网格搜索,而是有边界的启发式扫描

SVM 的威力不在默认参数,而在对核函数与正则项的精准控制。该项目未使用GridSearchCV的全量穷举(耗时且易过拟合),而是基于特征维度与样本量,设计三层递进式搜索空间,全部写死在train.py的tune_svm()函数中。

3.1 核函数选型:为什么弃用 Linear/Sigmoid,死磕 RBF?

报告第 6.1 节给出明确结论:Linear 核在猫狗数据上准确率仅 72.3%,因线性不可分;Sigmoid 核在验证集波动极大(±5.8%),因参数敏感且缺乏理论支撑。RBF(Radial Basis Function)成为唯一选择,其决策边界能包裹复杂簇状分布——猫狗在 HOG-LBP 特征空间中恰呈非凸分布,可视化见codes/plot_tsne.py输出的 t-SNE 图。

RBF 核公式:K(x_i, x_j) = exp(-γ ||x_i - x_j||²)
其中γ控制单个样本的影响半径:γ 越大,影响越局域,易过拟合;γ 越小,影响越平滑,易欠拟合。

3.2 C 参数:正则化强度的物理意义与安全边界

C是惩罚系数,定义为1/λ(λ 为正则化系数)。项目将C设为[0.1, 1, 10, 100]四档,依据是:

  • 训练样本数n=20000(项目按 8:2 划分,20k 训练,5k 测试),根据经验公式C ≈ n / 1000,中心值取 20,故取 log10 空间[10⁻¹, 10⁰, 10¹, 10²];
  • C=100时验证集准确率达峰(97.6%),但测试集跌至 95.1%,确认过拟合;
  • C=10时训练/验证/测试三者差距 <0.8%,为平衡点。
# train.py 第 88–92 行:精简版调参逻辑 C_range = [0.1, 1, 10, 100] gamma_range = [0.0001, 0.001, 0.01, 0.1] # γ 的候选值,按 10⁻⁴ 到 10⁻¹ 设置 best_score = 0 for C in C_range: for gamma in gamma_range: clf = SVC(C=C, gamma=gamma, kernel='rbf', random_state=42) score = cross_val_score(clf, X_train_scaled, y_train, cv=5, scoring='accuracy').mean() if score > best_score: best_score, best_C, best_gamma = score, C, gamma

注意:cross_val_score使用 5 折交叉验证,scoring='accuracy'明确指定指标,避免 sklearn 默认的r2(回归指标)误用。

3.3 γ 参数:从特征维度反推安全搜索区间

γ的合理范围与特征维度d=2363强相关。项目采用启发式公式:γ ∈ [1/(d×σ²), 1/σ²],其中σ²是特征方差均值。train.py第 65 行计算得σ² ≈ 1.2,故:

  • 下界:1/(2363×1.2) ≈ 0.00035→ 取0.0001(保守下探)
  • 上界:1/1.2 ≈ 0.83→ 取0.1(防过拟合)

最终gamma_range = [0.0001, 0.001, 0.01, 0.1],共 4 个值,而非盲目np.logspace(-5, 1, 20)。实测gamma=0.01与C=10组合在测试集达 97.8%,为最终模型参数。

3.4 模型持久化:不只是joblib.dump(),而是带元数据的完整快照

模型保存不仅存.pkl,还同步记录关键元数据,确保答辩时可复现:

# train.py 第 115–122 行 import joblib model_info = { 'model': clf, 'scaler': scaler, 'feature_dim': X_train_scaled.shape[1], # 2363 'C_used': best_C, 'gamma_used': best_gamma, 'cv_score': best_score, 'train_acc': clf.score(X_train_scaled, y_train), 'test_acc': clf.score(X_test_scaled, y_test) } joblib.dump(model_info, 'models/svm_best.pkl')

model_info字典包含所有可验证参数,report.docx的「模型参数表」直接读取此字典生成,杜绝手填错误。


4. 高分报告撰写:从实验记录到学术表达,Word 文档就是你的答辩提词器

98 分的硬核之处,不在代码跑通,而在报告让评审老师一眼看懂「你为什么这么做」。这份报告.docx不是模板套壳,而是把train.py的每个关键决策点,转化为学术写作的因果链。它被拆解为 6 个逻辑闭环章节,全部对应代码中的可验证操作。

4.1 方法论依据:每一句结论都有代码行号锚定

报告第 2 章「方法论选择」不是罗列教科书定义,而是逐条回应潜在质疑:

  • Q:为何不用 SIFT 或 SURF?
    A:SIFT 特征点数量不稳定(猫狗毛发纹理导致检测点稀疏),实测 2000 张图平均仅 127 个关键点,无法构成固定长度向量;而 HOG/LBP 输出恒为 2363 维(见train.pyL38–L75)。

  • Q:为何不划分 validation set 而用 CV?
    A:Kaggle 原始数据无官方验证集,项目采用StratifiedKFold(n_splits=5)保证每折猫狗比例一致(train.pyL85),避免因随机划分导致某折猫占比过高而虚高准确率。

  • Q:为何测试集不参与 scaler 拟合?
    A:train.pyL52–L53 明确分离fit_transform与transform,若误用scaler.fit_transform(X_test),会导致数据泄露,实测使测试准确率虚高 3.2%(见codes/debug_scaler_leak.py)。

每条回答后标注代码位置,答辩时可即时打开 VS Code 定位。

4.2 实验结果可视化:Matplotlib 图表必须带误差棒与显著性标记

报告中所有图表均来自codes/plot_results.py,且强制包含统计严谨性:

  • 交叉验证曲线图:横轴C,纵轴CV Accuracy,每点带 ±1 std error(5 折标准差),plt.errorbar()实现;
  • 混淆矩阵热力图:使用seaborn.heatmap(),annot=True显示数字,fmt='.1f'保留一位小数,颜色映射cmap='Blues',并添加plt.title('Confusion Matrix (Test Set, n=5000)')明确样本量;
  • 特征权重分析图:将 SVM 的clf.dual_coef_映射回 HOG cell 网格,用plt.imshow()可视化高权重区域(猫耳、狗鼻周围),证明模型关注点符合人类先验。

注意:所有图表保存为.png并嵌入 Word,禁止截图。plot_results.py第 121 行调用plt.savefig('figs/cm_test.png', dpi=300, bbox_inches='tight'),确保印刷清晰。

4.3 消融实验表格:用数据说话,拒绝“我觉得”

报告第 7 章「消融研究」是 98 分的关键证据,表格完全由代码自动输出:

特征组合训练集 Acc验证集 Acc测试集 Acc特征维度
HOG only92.1%91.8%91.3%2304
LBP only84.7%84.2%83.9%59
HOG+LBP97.2%97.0%97.8%2363

该表由codes/ablation_study.py运行生成,脚本遍历三种组合,调用相同tune_svm()流程,确保公平比较。表格中加粗显示最优值,并在脚注注明:“所有实验固定 C=10, γ=0.01, random_state=42”。

4.4 答辩问答预演:把评审可能问的 12 个问题写进附录

报告附录 B 直接列出答辩高频问题及标准答案,例如:

  • Q:SVM 训练时间长达 47 分钟,是否考虑用 LinearSVC 加速?
    A:LinearSVC 仅支持线性核,而本任务线性不可分(Linear 核最高仅 72.3%)。RBF 核的 O(n²) 复杂度不可避免,但通过cache_size=2000(train.pyL95)将核矩阵缓存至内存,相比默认 200MB 提速 3.2 倍。

  • Q:测试集准确率 97.8%,是否达到 Kaggle 排名前 10%?
    A:Kaggle 猫狗竞赛 Top 10% 阈值为 97.5%(截至 2023.10),本项目 97.8% 对应排名 7.3%,详情见codes/kaggle_benchmark.py输出。

这些问题全部源自往届答辩实录,答案精确到代码行与参数值,杜绝临场编造。


5. 避坑指南:98 分背后踩过的 5 个血泪坑,现在帮你绕开

这个项目拿 98 分,不是因为没犯错,而是把所有典型错误都提前踩过、记录、修复,并写进README.md的「常见问题」章节。以下是评审老师当场指出、或答辩时被追问的 5 个真实坑,按「现象→原因→解决」结构整理,每个都对应train.py的具体行号。

5.1 现象:ValueError: Input contains NaN, infinity or a value too large for dtype('float64')

原因:cv2.HOGDescriptor.compute()对全黑/全白图像返回None,而项目初始未过滤异常图。Kaggle 数据中约 0.3% 图片因 JPEG 损坏导致cv2.imread()返回None,后续resize()报错。
解决:在extract_features()开头增加校验(train.pyL35–L37):

if img is None: print(f"Warning: {img_path} failed to load, skipping...") continue if len(img.shape) == 0: # 空图像 continue

5.2 现象:测试集准确率突然暴跌 15%,clf.score()返回 0.82 而非预期 0.97

原因:StandardScaler的transform()被误用于训练集(即scaler.transform(X_train)),导致训练特征未标准化,而测试集被标准化,分布错位。
解决:严格区分fit_transform(仅训练集)与transform(测试集),并在train.pyL50 添加断言:

assert X_train_scaled.mean() < 1e-10, "Training features not standardized!" assert abs(X_test_scaled.mean()) < 0.1, "Test features standardized correctly"

5.3 现象:cross_val_score结果每次运行波动超 ±3%,无法复现

原因:StratifiedKFold未设random_state,导致折划分随机,5 折 CV 的 mean/std 不稳定。
解决:显式传入random_state=42(train.pyL85):

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) score = cross_val_score(clf, X_train_scaled, y_train, cv=skf, scoring='accuracy').mean()

5.4 现象:joblib.load('models/svm_best.pkl')报ModuleNotFoundError: No module named 'sklearn.svm._classes'

原因:模型保存时 sklearn 版本为 1.2.2,加载时为 1.3.0,内部模块路径变更。
解决:在README.md明确要求pip install scikit-learn==1.2.2,并添加版本锁:

# train.py 开头强制检查 import sklearn assert sklearn.__version__ == '1.2.2', f"Require sklearn 1.2.2, got {sklearn.__version__}"

5.5 现象:报告中混淆矩阵热力图显示猫预测为狗的比例高达 42%,但实际测试集猫狗各 2500 张,应接近 50%

原因:confusion_matrix(y_true, y_pred)的labels参数未指定顺序,默认按np.unique(y_true)排序,若标签为字符串'cat'/'dog',则unique返回['cat','dog'],但y_true中'dog'在前,导致矩阵行列颠倒。
解决:强制指定labels=['cat','dog'](plot_results.pyL63):

cm = confusion_matrix(y_true, y_pred, labels=['cat','dog']) sns.heatmap(cm, annot=True, fmt='d', xticklabels=['cat','dog'], yticklabels=['cat','dog'])

6. 进阶技巧:用 SVM 决策函数可视化,把黑匣子变成可解释的诊断工具

拿到 97.8% 准确率只是起点,真正的高分在于让模型“开口说话”。这个项目最被评审老师夸赞的点,是codes/decision_boundary_viz.py中的决策函数可视化——它不满足于输出准确率,而是把 SVM 的decision_function()值映射回原始图像空间,生成“模型置信度热力图”,直观展示模型到底在看什么。

6.1 决策函数值:不是概率,而是到超平面的有向距离

SVM 的decision_function(X)返回一个数组,每个元素是样本x_i到最优超平面的有向距离:正值表示判为正类(如dog),负值为负类(cat),绝对值越大表示置信度越高。项目将此值与图像坐标绑定,生成可解释热力图。

# codes/decision_boundary_viz.py 核心逻辑 def visualize_decision_map(model, scaler, img_path, class_label='dog'): img = cv2.imread(img_path) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) resized = cv2.resize(gray, (128, 128)) # 提取 HOG+LBP 特征(同 train.py) hog_feat = hog_descriptor.compute(resized) lbp_feat = uniform_lbp(resized).flatten() features = np.hstack([hog_feat.flatten(), lbp_feat]) # 标准化 & 预测 features_scaled = scaler.transform(features.reshape(1, -1)) decision_value = model.decision_function(features_scaled)[0] # scalar # 关键:将 decision_value 反向映射到图像块 # 按 HOG cell 网格(16×16 cells)分配权重 cell_weights = np.zeros((16, 16)) # 128/8 = 16 cells per dim # 简化版:用 decision_value * HOG 梯度幅值加权(真实项目用 dual_coef_ 反推) for i in range(16): for j in range(16): cell_roi = resized[i*8:(i+1)*8, j*8:(j+1)*8] mag, _ = cv2.cartToPolar(cv2.Sobel(cell_roi, cv2.CV_32F, 1, 0), cv2.Sobel(cell_roi, cv2.CV_32F, 0, 1)) cell_weights[i, j] = decision_value * mag.mean() # 可视化 plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) plt.title('Original Image') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(cell_weights, cmap='RdBu_r', vmin=-abs(cell_weights).max(), vmax=abs(cell_weights).max()) plt.colorbar(label='Decision Weight') plt.title(f'Decision Map (Class: {class_label})') plt.axis('off') plt.tight_layout() plt.savefig(f'viz/{os.path.basename(img_path)}_decision.png', dpi=300)

这段代码的关键在于:它没有把decision_function当作黑盒输出,而是将其与 HOG 的物理单元(8×8 像素 cell)关联,生成每个 cell 对最终判决的贡献热力图。例如,一张狗图的热力图会在鼻部、眼睛周围呈现强红色(正向贡献),而猫图则在耳尖、胡须区亮起——这直接验证了模型学习到了生物特征,而非数据集偏差(如背景色)。

6.2 三类典型图的决策图谱:建立模型可信度证据链

项目在viz/目录下预生成了 3 类图的决策热力图,作为报告附录 C 的核心证据:

图像类型决策图特征说明
高置信猫图耳尖、眼周强负值(蓝色),面部中央弱响应模型聚焦猫耳尖锐轮廓,符合先验
高置信狗图鼻部、嘴角强正值(红色),毛发区域中性鼻部湿润反光是关键判据,非毛色
误判图(猫被判狗)耳部权重微弱,鼻部区域异常高亮该猫图鼻部反光过强,被误判为狗鼻,建议数据增强增加猫鼻反光样本

这些图谱不是随意挑选,而是从测试集中按decision_function绝对值排序,取 top3 和 bottom3 生成,确保代表性。

6.3 从决策图到课程设计升级:把 SVM 变成教学演示工具

这个技巧的终极价值,是让课程设计超越“跑通即止”,变成可演示、可互动的教学资产。我在指导学生时,会让他们:

  • 修改class_label参数,对比同一张图在cat/dog两类下的决策图差异;
  • 裁剪图像局部(如只留耳朵),观察决策值从 -12.3 降至 -0.8,证明局部特征不足;
  • 用cv2.addWeighted()将热力图叠加到原图,生成带标注的汇报 PPT 图。

从那以后我每次带课程设计,都强制学生走一遍decision_boundary_viz.py,哪怕只生成一张图,也要在报告里解释“为什么这个区域权重最高”。因为评审老师真正想看到的,不是你有多快跑出 97.8%,而是你能否让模型说出它的理由——而这,正是传统机器学习区别于深度学习的尊严所在。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询