opencv 中创建决策树
cv::ml::DTrees类表示单个决策树或决策树集合,它是RTrees和
Boost的基类。
CART是二叉树,可用于分类或回归。对于分类,每个叶子节点都
标有类标签,多个叶子节点可能具有相同的标签。对于回归,每个叶
子节点都被分配了常数,因此近似函数是分段常数。
-
创建空决策树
cv::ml::DTrees::create函数可使用指定的参数创建空决策树,
之后使用cv::ml::StatModel::train函数训练该决策树模型;或者使用Algorithm::load (filename)从文件中加载决策树模型。 -
模型的基本设置
以下是构建决策树模型的必要参数,绝大部分参数有默认值
- MaxDepth:树的最大可能深度
根节点的深度为零,训练算法在深度小于MaxDepth时尝试切分节
点。如果满足其他终止标准,或者树被修剪,则实际深度可以更小。默认值为INT_MAX。
设置或获取参数MaxDepth的方法如下:
- MinSampleCount:节点中的最小样本数
如果节点中的样本数小于MinSampleCount,则不会切分该节点。
默认值为10。
设置或获取参数MinSampleCount的方法如下:
- RegressionAccuracy:回归树的终止标准
如果节点中的估计值与该节点中的样本值之间的所有绝对差值小
于该参数,则该节点不会被进一步切分。默认值为0.01f。
设置或获取参数RegressionAccuracy的方法如下:
- MaxCategories:表示特征属性为类形式时最大类的数量
算法会将分类变量的可能值聚类到K≤MaxCategories群集中,以
便找到次优切分。如果训练过程尝试进行切分的离散变量需要超过
MaxCategories值,则寻找精确的最佳子集可能需要很长时间。许多决策树引擎(包括OpenCV的实现)在这种情况下将尝试通过把所有样本聚类到MaxCategories集群中来寻找次优切分,其中,某些类别会被合并在一起。
对于具有N>MaxCategories可能值的分类变量,聚类仅应用于n>2类的分类问题。在回归和只有2类分类的情况下,不采用聚类也可以有效地找到最优切分,因此在n≤2时不使用该参数。默认值为10。
设置或获取参数MaxCategories的方法如下:
- CVFolds:使用k折叠交叉验证剪枝时的交叉验证折数
如果CVFolds>1,则算法使用k折叠交叉验证程序修剪构建的决策
树,其中,k=CVFolds,默认值为10。
设置或获取参数CVFolds的方法如下:
- Use1SERule:应用1SE规则剪枝标志位
如果标志位为true,剪枝将应用1SE规则,这将使树更紧凑,更能
抵抗训练数据噪声,但准确度稍差。默认值为true。
设置或获取参数Use1SERule的方法如下:
- TruncatePrunedTree:截断剪枝树标志位,默认值为true
如果标志位为true,则从树中物理移除已修剪的分枝,否则它们
会被保留,并且可以从原始未修剪(或修剪得不那么大)的树中获得结果。
设置或获取参数truncatePrunedTree的方法如下:
- priors:先验类概率Mat数组,默认值为空矩阵Priors=Mat()。
该参数可用于将决策树偏好调整到某个类。例如,想要检测一些
罕见的异常,即在训练集中正常比异常多很多,此时只需将每个案例都视为正常,即可实现非常好的分类性能。为了避免这种情况,可以指定先验,人为地增加异常概率(高达0.5,甚至更高),因此错误分类的异常权重变得更大,这样决策树就能做出适当地调整,以更好地检测异常样本。
还可以将此参数视为预测类别的权重,以确定为错误分类提供的
相对权重。也就是说,如果第一类别的权重是1,第二类别的权重是10,则预测第二类别的每个错误等同于在预测第一类别时犯10个错误。
设置或获取参数Priors的方法如下:
- UseSurrogates:是否构建代理切分标志位
如果为true,那么将建立代理切分。代理切分指的是,如果当前
样本缺少某些特征的值,那么该样本就无法继续沿着树向下切分,也就无法到达叶子节点,即没有预测输出。在这种情况下,可以利用当前节点下面的所有子节点中的叶子节点来预测输出的平均值,作为这个样本的预测输出,故称之为代理切分。当代理切分标志位为true时,允许使用缺少的数据。此外,如果需要计算特征(属性)的重要性,则需要将代理切分标志位设置为true。
设置或获取参数UseSurrogates的方法如下:
训练决策树
与KNN一样,决策树也是使用cv::ml::StatModel::train函数(简
称train函数)来训练模型的。
train函数:
函数参数:
- samples:训练集。
- layout:指定训练集的样本排列方式。具体如下:
• ROW_SAMPLE:每个训练样本占一行的训练集。
• COL_SAMPLE:每个训练样本占一列的训练集。 - responses与训练集样本排列顺序对应的标签向量。
树从根节点开始递归构建,所有训练数据(特征向量和响应)用
于切分根节点。在每个节点中,基于某些标准找到最佳决策规则(最佳“主要”切分)。
如前文所述,在训练中基尼系数表示的“不纯度”用于分类,均方误差之和用于回归。如有必要,找到代理切分。
它们类似于训练数据的主要切分结果。使用左子节点和右子节点之间的主要和替代切分(就像在预测过程中完成的那样)来划分所有数据。该过程以递归方式切分左右节点。当出现以下任意一种情况时,每个节点的递归过程都可能会停止。
- 构造的树分支的深度已达到指定的最大值。
- 当统计上没有进一步切分节点时,节点中的训练样本数小于指
定的阈值。 - 节点中的所有样本属于同一类,或者在回归的情况下,变化太
小。 - 与随机选择相比,最佳找到的切分没有任何明显的改善。
在构建决策树时,如有必要,可以使用交叉验证程序对其进行修剪。通常,此过程仅适用于独立决策树。如果构建的树过小,则应使用自己的方案防止过拟合。
使用决策树预测
训练好的决策树模型不仅可以根据输入样本特征向量获得响应,
还可以利用决策树计算变量的重要性。
- 预测过程
了解OpenCV中决策树模型的预测过程,有助于我们理解决策树的
原理并更好地应用它。决策树的预测过程从根节点开始,从每个非叶
子节点开始,过程向左(选择左子节点作为下一个观察节点)或向右
移动,基于某个变量的值,该变量的索引存储在被观察节点中。连续
变量与分类变量的预测过程如下。
连续变量。将变量值与存储在节点中的阈值进行比较。如果该值
小于阈值,则过程向左移动,否则向右移动。例如,如果重量小于1千克,则程序向左移动,否则向右移动。
分类变量。测试离散变量值,从变量可以采用的有限值集合中查
看它是否属于某个值的子集(也存储在节点中)。如果是,则向左移动,否则向右移动。例如,如果颜色为绿色或红色,请转到左侧,否则转到右侧。
在 每 个 节 点 中 , 都 使 用 了 ( variable_index ( 阈 值 ) ,
decision_rule(子集))这样一对实体。这样一对实体被称为一个切分(在variable_index上的切分)。一旦到达叶子节点,分配给该节点的值将用作预测输出。
有时,输入向量的某些特征缺失会导致预测过程卡在某个节点
中。为了避免这种情况,决策树使用代理切分。也就是说,除最佳的“主要”切分外,每个树节点也可以被分成具有几乎相同结果的一个或多个其他变量。
决策树模型与其他机器学习模型一样,测试可以分为一次测试一
个(多个)样本或者一次性测试整个数据集上的误差。
1)一次测试一个或多个样本
使用标准的cv::ml::StatModel::predict函数(简称predict函
数)预测所提供样本的响应。
predict函数:
函数参数:
- samples:输入样本,浮点型矩阵。
- results:可选的输出结果矩阵。
- flags : 可 选 标 志 , 取 决 于 模 型 。 参 阅
cv::ml::StatModel::Flags函数。
2)一次性测试整个数据集上的误差
使用标准的cv::ml::StatModel::calcError函数(简称calcError
函数)可以测试整个数据集上的误差。该函数使用predict函数来计算错误。对于回归模型,误差计算使用均方误差MSE;对于分类模型,计算错误分类样本的百分比(0%~100%)。
calcError函数:
函数参数:
- data:可以是TrainData类型的训练数据或者测试数据。
- test:如果为true,则在数据的测试子集上计算误差,否则在
数据的训练子集上计算误差。注意,如果加载了一个完全不同的数据集 来 评 估 已 经 训 练 过 的 模 型 , 则 不 需 要 使 用
TrainData::setTrainTestSplitRatio方法设置切分测试子集,此时无论test=true还是test=false,都将计算整个新数据集的误差。 - resp:可选的输出响应。
- 计算变量的重要性
决策树不仅可用于预测,还可用于各种数据分析。前文曾介绍
过,选择最佳切分属性是决策树的关键。因此决策树具有计算每个样本属性(特征)的重要性的能力,在OpenCV中称之为计算变量重要性。例如,当使用邮件信息中出现的一组单词作为特征向量的垃圾邮件过滤器时,变量重要性评级可用于确定最“垃圾邮件指示”单词,从而有助于保持字典大小合理。
应用示例
import cv2
import numpy as np# 创建一个决策树分类器
decision_tree = cv2.ml.DTrees_create()# 创建一些训练数据
train_data = np.array([[1.0, 2.0], [2.0, 3.0], [3.0, 4.0], [10.0, 12.0], [11.0, 13.0]], dtype=np.float32)
responses = np.array([0, 0, 0, 1, 1], dtype=np.int32)# 进行PCA降维
num_components = 1
pca = cv2.PCACompute(train_data, mean=None, maxComponents=num_components)# 使用PCA进行降维
train_data_reduced = cv2.PCAProject(train_data, pca[0])# 创建一个决策树分类器
decision_tree = cv2.ml.DTrees_create()# 设置决策树参数
params = dict(maxDepth=2)
decision_tree.setCVFolds(1) # 设置交叉验证折数# 将降维后的训练数据与类别标签整合为训练集
train_data_with_labels = cv2.ml.TrainData_create(samples=train_data_reduced,layout=cv2.ml.ROW_SAMPLE,responses=responses)# 训练决策树分类器
decision_tree.train(train_data_with_labels)# 创建一个测试样本并进行降维
test_sample = np.array([[2.5, 3.5]], dtype=np.float32)
test_sample_reduced = cv2.PCAProject(test_sample, pca[0])# 使用决策树进行预测
result = decision_tree.predict(test_sample_reduced)
print("Predicted class:", result[1][0][0])