1. 决策树:图像分类中的“老朋友”与新挑战
大家好,我是老张,在AI和算法领域摸爬滚打了十几年。今天想和大家聊聊一个听起来很“古典”,但在特定场景下依然非常能打的算法——决策树,以及我们如何用它来处理图像分类任务。
说到图像分类,大家第一时间想到的肯定是卷积神经网络(CNN)。确实,CNN在图像领域是当之无愧的霸主。但在我经手的很多实际项目中,尤其是在资源受限的边缘设备、需要快速原型验证,或者数据量不大但特征明确的场景下,决策树常常能带来意想不到的惊喜。它就像一个经验丰富的老兵,虽然不像深度学习新贵那样光芒四射,但胜在稳定、可解释性强,而且几乎不需要调参就能跑起来。
你可能会有疑问:图像数据动辄成千上万个像素点,每个像素都是一个特征,决策树这种基于特征划分的模型,面对如此高维的数据,不会直接“晕”过去吗?这个问题问到了点子上。直接拿原始像素值喂给决策树,效果通常会很差,因为它无法理解像素之间的空间关系。但这并不意味着决策树在图像领域无用武之地。恰恰相反,通过一些巧妙的特征工程和调优策略,决策树完全可以在图像分类任务中达到实用级的精度,比如处理Fashion MNIST(服装分类)或者手写数字识别这类问题。它的优势在于,一旦模型建立,我们可以清晰地知道模型是根据图像的哪个部分、哪个特征做出的判断,这种“白盒”特性在医疗影像、工业质检等需要解释性的领域至关重要。
接下来的内容,我会带你从零开始,走完一个完整的决策树图像分类项目。我们会聚焦于几个核心实战环节:如何为图像数据设计有效的特征、如何选择适合的决策树算法、如何避免模型过拟合,以及最终如何可视化模型的决策过程,让你不仅“会用”,更能“看懂”和“调优”。
2. 图像特征工程:为决策树准备“食材”
决策树是个“挑食”的模型。你喂给它好的特征,它就能给出好的结果。对于图像数据,我们不能直接把二维像素矩阵扔进去,必须进行特征转换。这一步的目标,是把图像从“像素空间”映射到“特征空间”,让决策树能够高效地进行划分。
2.1 像素二值化:化繁为简
对于像手写数字(MNIST)或简单图标这类图像,背景和前景对比往往比较明显。一个非常直接有效的策略就是二值化。我们可以设定一个阈值,将灰度图像转换为只有0和1的黑白图像。
import numpy as np
from PIL import Image
def image_binarization(image_array, threshold=128):
"""
将灰度图像数组二值化。
:param image_array: 灰度图像数组,值范围0-255
:param threshold: 二值化阈值
:return: 二值化后的图像数组(0或1)
"""
binary_image = np.where(image_array > threshold, 1, 0)
return binary_image.flatten() # 展平为一维特征向量
# 示例:处理一张Fashion MNIST中的T恤图像
# 假设 img 是一个 28x28 的numpy数组
# binary_features = image_binarization(img, threshold=127)
二值化之后,一个28x28的图像就变成了一个长度为784的特征向量,每个特征只有0或1两种取值。这极大地简化了决策树需要处理的特征空间。我在一个手写数字的早期项目里试过,仅仅使用二值化特征,配合适当的决策树深度控制,测试集准确率就能轻松达到85%以上。这对于一个如此简单的模型来说,已经相当不错了。
2.2 连续区间划分:处理灰度信息
然而,二值化会丢失大量的灰度信息。对于更复杂的图像,如Fashion MNIST中的鞋子、包包,我们需要保留更多的纹理和阴影细节。这时,我们可以对像素的灰度值(0-255)进行离散化分桶。
一种简单的方法是将0-255的区间均匀划分为N个桶,例如划分为4个区间:[0, 63], [64, 127], [128, 191], [192, 255]。然后将每个像素值映射到对应的桶编号(0,1,2,3)。这样,每个特征就从256种可能减少到4种可能,既保留了灰度信息,又控制了特征取值的数量,避免了决策树过度生长。
def pixel_discretization(image_array, bins=4):
"""
将像素灰度值离散化到指定数量的桶中。
:param image_array: 灰度图像数组
:param bins: 划分的桶数量
:return: 离散化后的特征向量
"""
# 计算每个桶的边界
bin_edges = np.linspace(0, 256, bins+1, dtype=int)
discretized = np.digitize(image_array, bin_edges) - 1 # digitize返回1到bins+1,减1变为0到bins-1
return discretized.flatten()
除了全局分桶,更精细的做法可以结合图像处理技术,比如计算图像局部区域的统计特征:小块区域(如3x3或5x5窗口)的像素均值、方差、梯度直方图(HOG的简化版)等。这些特征能更好地捕捉纹理和边缘信息。我曾在一个小型交通标志识别项目中使用过3x3区域的像素均值作为特征,配合CART决策树,模型在简单背景下的识别率超过了90%,而且推理速度极快。
2.3 特征选择与降维
即使经过离散化,784维的特征对于决策树来说依然很高。我们可以使用一些简单的过滤式方法进行特征初选。例如,计算每个像素位置在所有训练样本上的方差,方差越大的像素点,说明其包含的类别区分信息可能越多。我们可以只保留方差最大的前K个像素位置作为特征。
def variance_based_feature_selection(X_train, top_k=200):
"""
基于方差选择最重要的像素特征。
:param X_train: 训练集,形状为 (n_samples, n_features)
:param top_k: 要保留的特征数量
:return: 特征选择掩码(布尔数组)
"""
feature_variances = np.var(X_train, axis=0)
# 获取方差最大的top_k个特征的索引
top_indices = np.argsort(feature_variances)[-top_k:]
mask = np.zeros(X_train.shape[1], dtype=bool)
mask[top_indices] = True
return mask
# 使用示例
# feature_mask = variance_based_feature_selection(X_train_flattened, top_k=150)
#



被折叠的 条评论
为什么被折叠?



