Opencv學習筆記 - 使用opencvsharp和Boosting算法處理分類問題

 2023-12-25 阅读 44 评论 0

摘要:????????決策樹非常有用,但單獨使用時它并不是表現最佳的分類器。改進的方法隨機森林和Boosting算法。隨機森林與Boosting算法都是在內部循環中使用決策樹的,因此繼承了決策樹的許多優良屬性,它們通常是機器學習庫中最佳的“開箱即用”監督學習技術。Boo

????????決策樹非常有用,但單獨使用時它并不是表現最佳的分類器。改進的方法隨機森林和Boosting算法。隨機森林與Boosting算法都是在內部循環中使用決策樹的,因此繼承了決策樹的許多優良屬性,它們通常是機器學習庫中最佳的“開箱即用”監督學習技術。Boosting算法又被稱為OpenCV機器學習庫中最好用的監督學習算法。

一、Boosting算法原理

1、Boosting算法的基本思想

????????隨機森林采用的是Bagging算法。Bagging算法的特點是對多個弱學習器進行獨立學習,之后取平均,而Boosting算法是對多個弱學習器依次進行學習。其基本思想是:在逐個訓練弱分類器時,每次迭代都會調整樣本的權重,改變權重的依據是該樣本被上一個弱分類器成功分類的難度;給被錯誤分類的樣本加上較大的權重,讓接下來的弱學習器更多地關注這些困難樣本,盡量避免將其再分錯;最后,通過線性組合的方式集成所有弱學習器,構成強學習器。

????????用于集成的弱學習器都非常簡單,通常是只有一個節點的決策樹,又稱為決策樹樁(Decision Stumps),或是只有很少節點的決策樹。弱學習器是使用加權樣本訓練而成的,最后又通過加權“投票”的方式構成強學習器。因此,Boosting算法的關鍵是樣本權重和弱分類器的“投票”權重如何分配。

2、Boosting算法的學習流程

????????Boosting算法使用加權的原始樣本集依次訓練k個弱學習器,Boosting算法使用加權的原始樣本集依次訓練k個弱學習器。

????????在上述學習過程中,一開始就能夠被正確分類的樣本的權重會慢慢變小,此時有可能造成簡單的樣本反而不能被正確分類的情況。因此,Boosting算法不僅邊學習邊更新樣本的權重,還會把學習過程中得到的所有弱分類器結合在一起,對其加權求和后得到最終的強分類器。?

3、AdaBoost算法

????????Boosting算法更新樣本權重的方法有很多,其中最早提出的是AdaBoost(Adaptive Boosting)算法(自適應增強算法),大多數Boosting算法都是在AdaBoost算法基礎上改進的。AdaBoost算法流程如下所示:?


二、OpenCV函數實現

????????AdaBoost算法一次學習一組分類器,組中的每個分類器都是一個“弱”分類器(僅略高于隨機猜測的表現)。這些弱分類器通常由決策樹樁組成。在訓練過程中,決策樹樁從數據中學習分類決策,并從分類決策對數據的準確性中學習“投票”的權重。在每次訓練分類器之前,都要對數據先進行加權,以便更多地關注上一輪中分類錯誤的數據。此過程將迭代進行,直到最終的強分類器對數據集的總分類誤差降至設定的閾值以下,或者達到設置的迭代次數為止。當有大量訓練數據可用時,此算法通常很有效。

????????OpenCV實現了下表中列出的四種Boosting算法。

????????其中,Real AdaBoost算法和Gentle AdaBoost算法的效果最好。Real AdaBoost算法是一種利用置信度評估的預測技術,在分類問題中有很好的效果;GentleAdaBoost算法賦予異常數據較小的權重,因此在解決回歸問題時效果較好。

????????盡管從理論上講,LogitBoost算法和Gentle AdaBoost算法都可用于回歸任務和二分類任務,但是在OpenCV實現的Boosting算法中,目前還只支持用分類數據進行訓練。原始的Boosting算法只能用在二分類任務中,對于多分類任務則需要使用一些技巧才能完成。?

????????創建AdaBoost模型

cv::ml::Boost::create

????????以下是創建AdaBoost模型時用到的主要函數

????????(1)獲取AdaBoost算法的類型,用于獲取Boost算法的類型,默認值為Boost::REAL。返回int值:DISCRETE = 0,REAL =1, LOGIT = 2, GENTLE = 3。

cv::ml::Boost::getBoostType()

????????(2)設置AdaBoost算法的類型,上表

cv::ml::Boost::setBoostType(int val)

????????(3)獲取弱分類器的數量

int getWeakCount()

????????(4)設置弱分類器的數量

void?cv::ml::Boost::setWeakCount(int val)

????????(6)設置權重修剪率,通過setWeightTrimRate函數設置一個介于0和1(含)之間的閾值,該閾值可隱式地丟棄Boosting算法迭代過程中一些不重要的訓練樣本。

void setWeightTrimRate(double val)

三、二分類任務 - Mushroom數據集

????????mushroom數據集下載地址和介紹請看下文第3節?

Opencv學習筆記 - 使用opencvsharp和隨機森林進行分類和回歸問題_bashendixie5的博客-CSDN博客隨機森林(Random Forest,RF)是一種簡單易用的機器學習算法。即使在沒有超參數調整的情況下,隨機森林在大多數情況下仍可獲得還算不錯的結果。可用于分類任務和回歸任務,是常用的機器學習算法之一。隨機森林是一種監督學習算法,它構建的“森林”是決策樹的集合,通常使用Bagging算法進行集成。隨機森林首先使用訓練出來的分類器集合對新樣本進行分類,然后用多數投票或者對輸出求均值的方法統計所有決策樹的結果。由于森林中的每一棵決策樹都具有...https://blog.csdn.net/bashendixie5/article/details/121805198

1、c++代碼參考

void opencv_adaboost_mushroom()
{//讀取數據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;//創建模型Ptr<Boost> boost = Boost::create();boost->setBoostType(Boost::GENTLE);boost->setWeakCount(100);boost->setWeightTrimRate(0.95);boost->setMaxDepth(5);boost->setUseSurrogates(false);//訓練模型//訓練隨機森林模型cout << "開始訓練..." << endl;boost->train(daraset_forest);cout << "訓練成功..." << endl;//測試cv::Mat results_train, results_test;float forest_train_error = boost->calcError(daraset_forest, false, results_train);float forest_test_error = boost->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;}

? ? ? ? 訓練結果精度如下:

正確答案:1617
錯誤答案:8
測試樣本數:1625
訓練數據集錯誤:0%
測試數據集錯誤:0.492308%

2、c#、opencvsharp代碼參考

? ? ? ??opencvsharp沒給實現數據集劃分,所以還是人工智能手動劃分數據集,打開agaricus-lepiota.data,copy一份,我這里是前7000條進行訓練,后1124條用于測試。

int[,] att = GetTArray(@"D:\Project\deeplearn\dataset\mushroom\agaricus-lepiota.handsplit.train.data", 7000);
int[] label = GetTLabel(@"D:\Project\deeplearn\dataset\mushroom\agaricus-lepiota.handsplit.train.data", 7000);
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:\Project\deeplearn\dataset\mushroom\agaricus-lepiota.handsplit.test.data");
int[] test_label = GetTLabel(@"D:\Project\deeplearn\dataset\mushroom\agaricus-lepiota.handsplit.test.data", 1124);
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);

? ? ? ? 測試集預測結果:

????????正確數量:1124
????????錯誤數量:0

四、多分類任務 -?英文字母分類

? ? ? ? 數據集下載地址,對應含義,見官網:?

letter-recognition | Machine Learning Datahttps://networkrepository.com/letter-recognition.php????????與決策樹或隨機森林分類器不同,目前在OpenCV中實現的Boosting算法僅能處理二分類問題。本節介紹一種展開技巧來解決多分類問題,以增加訓練和預測時的計算量為代價。此技巧不僅適用于Boosting分類器,也適用于其他類型的二分類器。

????????示例代碼7-2演示的是分類A~Z,共26個字母。數據來源于Letter Recognition數據集。該數據集有20000個樣本,下載下來的數據文件的每行表示一個樣本,其中,第1個數據為樣本標簽(26個大寫英文字母之一),第2~17個特征為16維整型特征向量。在展開技巧中,訓練集從1個擴展成26個。

????????展開時,數據集將從20000個樣本擴展到:樣本數×類別數=20000×26個樣本。原來的標簽變成新增的特征。同時,這些擴展后的樣本特征向量的新標簽變為1或0:即true或false。通過學習數據來回答測試樣本是否為a、是否為b、……是否為z的問題,最終得到答案。

????????關鍵代碼分析如下:首先,創建一個數組var_type,該數組指示如何處理每個特征和標簽。其次,創建訓練數據結構。在原始數據中不僅有var_count個特征(在本示例中,原始樣本有16個特征),還有一列用于存放標簽(0或1)。另外,標簽和原始數據之間還有一列,用于存放原始數據中的標簽(即字符型變量)。因此擴展后一共有var_count+2維向量。

????????再次,構造分類器。需要注意的是:在展開技巧中有先驗知識,即必須使用setPriors(Mat(priors))方法設置priors參數。我們以預測一個字母是否為a為例:預測一個字母是a與預測這個字母非a的兩種分類代價并不相同,因為展開使得非a的可能性更高,所以說一個字母是a(即y=1)要比說一個字母不是a(即y=0)的代價要高25倍。因為展開使得有25個樣本向量對應負標簽y=0,而只有1個樣本向量對應正標簽y=1,所以正標簽需要相應地增加權重。

????????最后,進行模型創建與設置、訓練模型等常規動作。

1、c++代碼參考

static bool read_num_class_data(const string& filename, int var_count, Mat* _data, Mat* _responses)
{const int M = 1024;char buf[M + 2];Mat el_ptr(1, var_count, CV_32F);int i;vector<int> responses;_data->release();_responses->release();FILE* f = fopen(filename.c_str(), "rt");if (!f){cout << "Could not read the database " << filename << endl;return false;}for (;;){char* ptr;if (!fgets(buf, M, f) || !strchr(buf, ','))break;responses.push_back((int)buf[0]);ptr = buf + 2;for (i = 0; i < var_count; i++){int n = 0;sscanf(ptr, "%f%n", &el_ptr.at<float>(i), &n);ptr += n + 1;}if (i < var_count)break;_data->push_back(el_ptr);}fclose(f);Mat(responses).copyTo(*_responses);cout << "The database " << filename << " is loaded.\n";return true;
}void opencv_adaboost_letterrecognition()
{time_t now = time(0);char* dt = ctime(&now);//讀取數據const char* file_name = "D:/Project/deeplearn/dataset/letter-recognition/letter-recognition.csv";const int class_count = 26;Mat data, responses, weak_responses;bool ok = read_num_class_data(file_name, 16, &data, &responses);if (!ok){cout << "read num class data fail" << endl;return;}int i, j, k;Ptr<Boost> boost;int nsamples_all = data.rows;	//樣本總數20000int ntrain_samples = (int)(nsamples_all * 0.5);	//一半用于訓練int var_count = data.cols;	//特征維數Mat new_data(ntrain_samples* class_count, var_count+1, CV_32F);Mat new_responses(ntrain_samples * class_count, 1, CV_32S);now = time(0);dt = ctime(&now);for (i=0; i< ntrain_samples; i++)	//遍歷訓練集{const float* data_row = data.ptr<float>(i);for (j = 0; j < class_count; j++)	//遍歷訓練集{float* new_data_row = (float*)new_data.ptr<float>(i * class_count + j);memcpy(new_data_row, data_row, var_count * sizeof(data_row[0]));new_data_row[var_count] = (float)j;new_responses.at<int>(i * class_count + j) = responses.at<int>(i) == j + 'A';}}Mat var_type(1, var_count + 2, CV_8U);var_type.setTo(Scalar::all(VAR_ORDERED));var_type.at<uchar>(var_count) = var_type.at<uchar>(var_count + 1) = VAR_CATEGORICAL;cv::Ptr<TrainData> tdata = TrainData::create(new_data,	//擴展的26倍向量ROW_SAMPLE,new_responses,	//擴展的響應cv::noArray(),cv::noArray(),cv::noArray(),var_type);vector<double> priors(2);priors[0] = 1;priors[1] = 26;now = time(0);dt = ctime(&now);cout << "訓練需要一段時間" << endl;boost = Boost::create();boost->setBoostType(Boost::GENTLE);boost->setWeakCount(100);boost->setWeightTrimRate(0.95);boost->setMaxDepth(5);boost->setUseSurrogates(false);boost->setPriors(Mat(priors));boost->train(tdata);//cout << "訓練完成" << endl;Mat temp_sample(1, var_count + 1, CV_32F);float* tptr = temp_sample.ptr<float>();now = time(0);dt = ctime(&now);cout << "測試開始" << endl;double train_tr = 0, test_tr = 0;for (int i=0; i<nsamples_all; i++){int best_class = 0;double max_sum = -DBL_MAX;const float* ptr = data.ptr<float>(i);for (k = 0; k < var_count; k++){tptr[k] = ptr[k];}for (j = 0; j < class_count; j++){tptr[var_count] = (float)j;float s = boost->predict(temp_sample, noArray(), StatModel::RAW_OUTPUT);if (max_sum < s){max_sum = s;best_class = j + 'A';}}double r = std::abs(best_class - responses.at<int>(i)) < FLT_EPSILON ? 1 : 0;if (i < ntrain_samples)train_tr += r;elsetest_tr += r;}test_tr /= nsamples_all - ntrain_samples;train_tr = ntrain_samples > 0 ? (train_tr / ntrain_samples) : 1.;now = time(0);dt = ctime(&now);printf("識別率:train = %.1f%%, test=%.1f%%\n", train_tr*100., test_tr * 100.);
}

? ? ? ? 當前超參下運行結果:

識別率:train = 84.2%, test=80.2%?

2、?c#、opencvsharp代碼參考

? ? ? ? 沒有實現,可以參考c++的代碼。

五、小結

????????Boosting算法只能直接處理二分類問題,但是可以通過使用展開技巧來處理多分類問題。在使用展開技巧時,將根據類別多少引入額外的計算開銷,因此OpenCV中的Boosting算法并不是解決多分類問題的最快或最方便的方法。僅從這點來看,對于多分類問題,隨機森林是更好的解決方法。

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/196855.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息