随机森林(Random Forest,RF)是一种简单易用的机器学习算法。即使在没有超参数调整的情况下,随机森林在大多数情况下仍可获得还算不错的结果。可用于分类任务和回归任务,是常用的机器学习算法之一。
一、随机森林原理
1、基本思想
随机森林是一种监督学习算法,它构建的“森林”是决策树的集合,通常使用Bagging算法进行集成。随机森林首先使用训练出来的分类器集合对新样本进行分类,然后用多数投票或者对输出求均值的方法统计所有决策树的结果。由于森林中的每一棵决策树都具有独立性,因而可以通过投票和求平均值的方法获得比单棵决策树更好的性能。
2、Bagging算法
由于随机森林通常采用Bagging算法对决策树进行集成,因此有必要了解Bagging算法的工作流程与原理。某些分类器的分类准确率有时只稍好于随机猜测,这样的分类器称为弱分类器。为了提高分类器的性能,通常使用集成学习(EnsembleLearning)的方法将若干弱分类器组合之后生成一个强分类器。Bagging算法和Boosting算法是集成学习领域的基本算法。
(1)Bagging算法的流程
Bagging算法的流程分为训练和测试两个阶段。
(1)训练阶段
从原始训练集中使用Bootstrapping抽样方法先随机抽取N个训练样本,之后把这N个训练样本放回原训练集,共进行k轮抽取,得到k个训练子集。使用这k个训练子集,训练k个基础模型(基础模型可以是决策树或神经网络等)。
(2)测试阶段
对于每个测试样本,都使用所有训练好的基础模型进行预测;之后结合所有k个基础模型的结果进行预测。如果是回归问题,则采用k个基础模型的预测平均值作为最终预测结果;如果是分类问题,则对k个基础模型的分类结果进行投票表决,得票最多的类别为最终分类结果。
(2)Bootstrapping抽样方法
bootstrapping方法的实现很简单,假设抽取的样本大小为n:在原样本中有放回的抽样,抽取n次。每抽一次形成一个新的样本,重复操作,形成很多新样本,通过这些样本就可以计算出样本的一个分布。
也就是说,当N足够大时,在Bagging算法的每轮随机采样中,训练集中约有36.8%的数据不会被采样集抽中。这部分没有被抽到的数据,称之为袋外数据(Out Of Bag,OOB)。由于这些数据没有参与训练,所以可用来验证模型的泛化能力。
3、Bagging算法的原理
假设有3个分类器,分别对17个样本进行分类测试。如果错误很少见,则每个分类器都有可能在不同的样本上犯错。假设3个分类器的预测结果如下图所示,每个样本最多被错误分类一次,其他类别的标签则是正确的。如果每个样本的最终预测都采用3个分类器的结果进行投票表决,那幺每个样本都将得到正确的预测。就好像虽然一个人偶然会判断错误,但是将被其他人的正确判断所纠正一样。
3个分类器的预测结果示意图
通过上述例子可以得出一个重要结论,即如果每个分类器倾向于在不同的样本中犯错误,那幺Bagging算法将取得很好的效果。
4、随机森林
从前文的分析可知,Bagging算法可以有效防止出现过拟合,其原理是通过鼓励弱分类器间的多样性(即不同的弱分类器尽可能在不同的样本中犯错误)提高投票表决时获得正确结论的概率。
随机森林采用决策树作为Bagging算法的基础模型,通过“随机”步骤增加决策树之间的多样性,从而提高预测精度。显然“随机”是随机森林算法的关键。它通过两部分保证随机性,即保证树间的多样性。首先,通过Bootstrapping抽样方法保证训练子集的独立性;其次,在对树的节点进行切分时,随机选择样本特征(属性)子集,并从特征子集中计算最佳切分属性。具体而言,如果某个样本包含n个属性,则随机森林在分割节点时,先从这n个属性中随机抽取m个(m<n)属性构成特征子集,再在特征子集的范围内选择最佳切分属性,这样即可保证决策树的多样性。
二、OpenCV函数说明
OpenCV中的随机森林是根据Leo Breiman(随机森林是Leo Breiman于2001提出的一个组合分类 算法 )的随机森林理论实现的。对于分类问题,随机森林通过对各树的预测类别进行投票,选择票数最多的类别作为输出;对于回归问题,随机森林则计算所有树的平均值作为总的输出。为了提高稳健性,随机森林使用袋外数据(OOB)来验证切分,通常将OOB数量设置为所有数据样本的三分之一。
在随机森林中,所有树都以相同的参数进行训练,但训练集不同。如前文所述,在每棵树的每个节点上,不是所有属性都用于寻找最佳切分的,而是在属性的随机子集中寻找最佳属性。对于每个节点,将生成一个新的属性子集。对于所有节点和所有树,其属性子集的大小都是固定的。如果属性总数为n,则在训练时,属性子集中的特征个数默认设置为对√ ̄n取整。此外,所有的决策树都不做剪枝。
随机森林中的cv::ml::RTrees类继承自决策树的cv::ml::DTrees类。
使用cv::ml::RTrees::create函数创建空模型
使用cv::ml::Algorithm::load函数加载预先训练好的模型
构建随机森林模型时的参数说明:
(1)设置特征(属性)子集的大小:
设置每棵树节点上随机选择的特征子集的大小,这些子集可用于寻找最佳切分。如果将其设置为0,则大小将设置为要素总数的平方根,其默认值为0。这一参数是随机森林的唯一关键参数。可以使用cv::ml::RTrees::getActiveVarCount函数获取当前特征子集的大小。
(1)设置特征(属性)子集的大小:
如果val= true,则计算变量重要性,其默认值为false。可以通过cv::ml::RTrees::getCalculateVarImportance函数查看是否计算变量重要性。如果设置为true,则会带来额外的时间开销。可以通过cv::ml::RTrees::getVarImportance函数检索变量的重要性。
(3)获取样本特征的重要性:
在训练阶段,如果将setCalculateVarImportance设置为true,则可计算得出变量重要性数组,如果设置为false,则返回空矩阵。
(4)设置算法终止条件:
既可以把终止条件设置为训练指定数量的树,并将其添加到森林中,也可以把终止条件设置为达到足够的精度(以OOB误差来衡量)。一般来说,拥有的树越多,准确性越好。但是,在超过一定数量的树后准确性的提高幅度通常会降低。需要注意的是,树的数量的增多会使预测时间呈线性增加。默认值为TermCriteria(TermCriteria::MAX_ITERS+TermCriteria::EPS, 50, 0.1),表示树的最大数量为50,或者OOB误差<0.1。可以使用cv::ml::RTrees::getTermCriteria函数获取当前的终止条件。
随机森林也是使用cv::ml::StatModel::train函数训练模型
使用标准的cv::ml::StatModel::predict函数预测单个样本的响应
使用cv::ml::StatModel::calcError函数一次性计算数据集中训练集或者测试集上的误差(opencvsharp截止4.5.3目前没有实现)
三、分类任务 – 随机森林和Mushroom数据集
mushroom数据集下载地址和介绍请看下文第3节
1、c++代码参考
void opencvforest() { //读取数据 const char* file_name = "D:/Project/deeplearn/dataset/mushroom/agaricus-lepiota.data"; cv::Ptr<TrainData> daraset_forest = TrainData::loadFromCSV(file_name, 0,//从数据文件开头跳过的行数 0,//样本的标签从此列开始(就是说第一列是标签) 1,//样本输入特征向量从此列开始(从第二列开始往后都是数据) "cat[0-22]"); //验证数据 int n_samples = daraset_forest->getNSamples(); int n_features = daraset_forest->getNVars(); cout << "每个样本有" << n_features << "个特征" << endl; if (n_samples == 0) { cout << "读取文件错误" << file_name << endl; exit(-1); } else { cout << "从" << file_name << "中,读取了" << n_samples << "个样本" << endl; } //划分训练集与测试集,按80%和20%比例划分 daraset_forest->setTrainTestSplitRatio(0.8, false); int n_train_samples = daraset_forest->getNTrainSamples(); int n_test_samples = daraset_forest->getNTestSamples(); cout << "Training samples:" << n_train_samples << endl << "Test samples:" << n_test_samples << endl; //创建随机森林模型 cv::Ptr<RTrees> forest = RTrees::create(); //设置模型参数 forest->setActiveVarCount(0); forest->setCalculateVarImportance(true); forest->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 100, 0.01)); //训练随机森林模型 cout << "开始训练..." << endl; forest->train(daraset_forest); cout << "训练成功..." << endl; //输出属性重要性分数 Mat var_importance = forest->getVarImportance(); if (!var_importance.empty()) { double rt_imp_sum = sum(var_importance)[0]; printf("var#\timportance(%%): "); int n = (int)var_importance.total();//矩阵元素总个数 for (int i = 0; i < n; i++) { printf("%-2d\t%-4.1f ",i,100.f * var_importance.at<float>(i) / rt_imp_sum); } } //测试 cv::Mat results_train, results_test; float forest_train_error = forest->calcError(daraset_forest, false, results_train); float forest_test_error = forest->calcError(daraset_forest, true, results_test); //统计输出结果 int t = 0, f = 0, total = 0; cv::Mat expected_responses_forest = daraset_forest->getTestResponses(); //获取测试集标签 std::vector<cv::String> names_forest; daraset_forest->getNames(names_forest); for (int i=0; i< daraset_forest->getNTestSamples(); i++) { float responses = results_test.at<float>(i, 0); float expected = expected_responses_forest.at<float>(i, 0); cv::String r_str = names_forest[(int)responses]; cv::String e_str = names_forest[(int)expected]; if (responses == expected) { t++; } else { f++; } total++; } cout << "正确答案:" << t << endl; cout << "错误答案:" << f << endl; cout << "测试样本数:" << total << endl; cout << "训练数据集错误:" << forest_train_error << "%" << endl; cout << "测试数据集错误:" << forest_test_error << "%" << endl; }
数据集划分、重要性结果输出如下:
在测试集上测试结果如下:
正确答案:1570
错误答案:55
测试样本数:1625
训练数据集错误:2.23111%
测试数据集错误:3.38462%
2、c#、opencvsharp代码参考
opencvsharp没给实现数据集划分,所以还是人工智能手动划分数据集,打开agaricus-lepiota.data,copy一份,我这里是前7000条进行训练,后1124条用于测试。
//整理数据和标签 int[,] att = GetTArray(@"D:\mushroom\agaricus-lepiota.handsplit.train.data"); int[] label = GetTLabel(@"D:\mushroom\agaricus-lepiota.handsplit.train.data"); InputArray array = InputArray.Create(att); InputArray outarray = InputArray.Create(label); //创建随机森林模型和设置参数 OpenCvSharp.ML.RTrees dtrees = OpenCvSharp.ML.RTrees.Create(); dtrees.ActiveVarCount = false; dtrees.CalculateVarImportance = true; dtrees.TermCriteria = new TermCriteria(CriteriaType.Eps | CriteriaType.MaxIter, 100, 0.01); //随机森林模型训练 dtrees.Train(array, OpenCvSharp.ML.SampleTypes.RowSample, outarray); //输出属性重要性分数 Mat var_importance = dtrees.GetVarImportance(); if (!var_importance.Empty()) { double rt_imp_sum = Cv2.Sum(var_importance)[0]; int n = (int)var_importance.Total();//矩阵元素总个数 for (int i = 0; i < n; i++) { Console.WriteLine(i + ":" + (100f * var_importance.At<float>(i) / rt_imp_sum)); } } //测试 int t = 0; int f = 0; List<int[]> test_arr = GetTestArray(@"D:\mushroom\agaricus-lepiota.handsplit.test.data"); int[] test_label = GetTLabel(@"D:\mushroom\agaricus-lepiota.handsplit.test.data"); for (int i = 0; i < test_arr.Count; i++) { Mat p = new Mat(1, 22, OpenCvSharp.MatType.CV_32F, test_arr[i]); float rrr = dtrees.Predict(p); //System.Console.WriteLine("" + rrr); if(test_label[i] == (int)rrr) { t++; } else { f++; } } System.Console.WriteLine("正确数量:" + t); System.Console.WriteLine("错误数量:" + f);
GetTArray、GetLabel读取数据等相关函数参考上一篇的相关代码 Opencv学习笔记 – 使用opencvsharp和决策树进行训练和预测_bashendixie5的博客-CSDN博客 一、决策树决策树是最早的机器学习算法之一,起源于对人类某些决策过程的模仿,属于监督学习算法。决策树的优点是易于理解,有些决策树既可以做分类,也可以做回归。在排名前十的数据挖掘算法中有两种是决策树[1]。决策树有许多不同版本,典型版本是最早出现的ID3算法,以及对其改进后形成的C4.5算法,这两种算法可用于分类。对ID3算法改进的另一个分支为“分类和回归树”(Classification AndRegression Trees,CART)算法,可用于分类或回归。CART算法为随机森林和B… https://blog.csdn.net/bashendixie5/article/details/121383341 测试集上输出结果如下:
正确数量:1117
错误数量:7
3、比较决策树与随机森林
分别使用决策树和随机森林代码,将setTrainTestSplitRatio(0.8, false)方法中数据集的划分比例分别设置为[0.7, 0.75, 0.8, 0.85, 0.9],即可得到随机森林与决策树在蘑菇可食性数据集上,在不同训练数据、测试数据比例情况下的训练集误差与测试集误差了。如下图所示,随机森林的训练集误差大于决策树的训练集误差;而其测试集误差却小于或等于决策树的测试集误差。因此,说明随机森林有助于降低模型对训练数据的过拟合。而且,并没有设置任何超参数。
四、回归任务 – 随机森林和Boston Housing数据集
波士顿房价数据集(Boston House Price Dataset)包含对房价的预测,以千美元计,给定的条件是 房屋及其相邻房屋的详细信息。该数据集是一个回归问题。每个类的观察值数量是均等的,共有 506 个观察,13 个输入变量和1个输 出变量。
波士顿数据集下载地址:
链接:https://pan.baidu.com/s/1KwWq32xjJLGKZvLa8C9kJg
提取码:c4t0
可以使用文本编辑器,手动拆分训练数据集(housing-train.csv)与测试数据集(housing-test.csv)。训练数据集中包含451个样本,测试数据集中包含55个样本,用均方根误差(RMSE)和均方误差(MSE)评价随机森林的预测准确性。
注意:数据集中默认有缺失数据,请删除或者自行估算替代数据。
打开数据集可以按照第一行的头,理解对应现实含义。
1、c++代码参考
//读取数据 const char* file_name = "D:/Project/deeplearn/dataset/boston_housing/train_data.csv"; cv::Ptr<TrainData> daraset_forest = TrainData::loadFromCSV(file_name, 0,//从数据文件开头跳过的行数 -1,//就是说最后一列是标签 -1);//当为-1时就是说,最后一列是标签,那就是上一个参数所在列 //创建随机森林模型 cv::Ptr<RTrees> forest = RTrees::create(); //设置模型参数 forest->setActiveVarCount(0); forest->setCalculateVarImportance(true); forest->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 100, 0.01)); //训练随机森林模型 cout << "开始训练..." << endl; forest->train(daraset_forest); cout << "训练成功..." << endl; //输出属性重要性分数 Mat var_importance = forest->getVarImportance(); if (!var_importance.empty()) { double rt_imp_sum = sum(var_importance)[0]; printf("var#\timportance(%%): "); int n = (int)var_importance.total();//矩阵元素总个数 for (int i = 0; i < n; i++) { printf("%-2d\t%-4.1f ", i, 100.f * var_importance.at<float>(i) / rt_imp_sum); } } //训练集合精度 cv::Mat results_train; float MSE_train = forest->calcError(daraset_forest, false, results_train); //统计输出结果 int total = 0; cv::Mat expected_responses = daraset_forest->getResponses(); float square_error = 0.0; for (int i = 0; i < expected_responses.rows; i++) { float responses = results_train.at<float>(i, 0); float expected = expected_responses.at<float>(i, 0); square_error += (expected - responses) * (expected - responses); total++; } //计算RMSE指标 float RMSE_train = sqrt(square_error/ total); forest->save("trained_forest.xml"); cout << "trained mse:" << MSE_train << endl; cout << "trained rmse:" << RMSE_train << endl;
2、c#、opencvsharp代码参考
这里没有进行opencvsharp的demo实现,可以参考上面的mushroom数据集的opencvsharp的代码。
五、小结
随机森林既可用于回归任务和分类任务,也可以轻松查看样本属性的相对重要性。随机森林的超参数非常简单,通常使用默认超参数即可取得良好的预测结果。此外,只要随机森林中有足够多的决策树,就不容易使模型出现过拟合。
随机森林的主要局限性在于,大量决策树会影响速度,从而不利于实时预测。通常准确的预测需要更多的决策树,而这会导致模型变慢。在大多数实际应用中,随机森林都足以应对,但是在某些情况下,例如,当对运行速度要求很高时,则应首先考虑其他方法。
随机森林被用于许多不同的领域,如银行、股票市场、医药和电子商务等。例如,在金融中,它可用于检测更有可能按时偿还债务或更频繁地使用银行服务的客户,此外,它还可用于检测欺骗银行的欺诈者。在交易证券中,该算法可用于预测股票的未来行为。在医疗保健领域,它可用于识别药物中正确的成分组合,并分析患者的病史以便识别疾病。在电子商务中,随机森林可用来确定客户是否真的喜欢该产品。
虽然随机森林通常在测试数据集上表现最好或接近最好,但是最好的策略仍然是在得到训练数据后尝试使用不同类型的分类器进行测试,经过比较后再做出正确的决定。
Be First to Comment