機器學習中,需要總樣本集,拆分成訓練集、測試集,計算訓練、測試、整體的准確率。
OpenCV在ml.hpp中為我們准備了特有格式TrainData,它會把標簽、特征集融合到其中,方便操作。
針對TrainData類型,提供了非常完美的函數,具體介紹如下:
1、拆分TrainData類型總樣本集,注意默認是會打亂行順序的。
setTrainTestSplitRatio(double ratio, bool shuffle=true); //比例方式,前ratio(0~1)行是訓練集,推薦使用此函數
或
setTrainTestSplit(int count, bool shuffle=true); //具體指定方式,前count行是訓練集
第二個參數默認為true,即打亂行后再拆分。
2、計算准確率,此函數計算的是錯誤率,准確率用100減下即可
float calcError( const Ptr<TrainData>& data, bool test, OutputArray resp )
data——融合了標簽、特征的總樣本集,TrainData類型
test——計算訓練集准確率設為false,測試集准確率設為true,總的准確率設為false同時data不可以使用拆分函數setTrainTestSplitRatio或setTrainTestSplit。
resp——預測的標簽結果,內部調用了predict函數
3、獲取訓練集、測試集
Mat getTrainSamples()
Mat getTestSamples()
【TrainData類型的解釋】
下圖是letter-recognition.data文件的內容,第1列是標簽,后16列是特征,需要分別讀入到Mat格式的responses和samples中。之后用create函數創建TrainData類型對象(融合了標簽、特征)。
此解釋針對的是分類問題,而不是回歸擬合。samples特征集中每1行為1個樣本,每1列為1個特征。
Ptr<TrainData> create(InputArray samples, int layout, InputArray responses, InputArray varIdx=noArray(), InputArray sampleIdx=noArray(),InputArray sampleWeights=noArray(), InputArray varType=noArray());
samples——特征集(特征變量組成的集合),必須是32FC1(32位浮點單通道)類型
layout——樣本布局,ROW_SAMPLE = 0,COL_SAMPLE = 1,此處只用前者
responses——標簽集,必須是CV_32S類型(即int型)。一維向量,與每一行樣本對應。
varIdx——參與訓練的特征(默認都參與),元素為0、非0的一維向量(行列都可),大小為特征數(列數)。1對應的樣本參與訓練(非0有效),格式用CV_8U即可
sampleIdx——參與訓練的樣本(默認都參與),同上,大小為總樣本數(行數)
sampleWeights——樣本權重,忽略不用
varType——特征變量的類型,忽略不用
總樣本集(上圖)中responses在哪一列都可以,只要保證samples(行為樣本,列為特征)、responses(行、列都可以)即可。
將總樣本集拆分為訓練集和測試集的方法有兩種:用自帶函數(推薦),用參數sampleWeights(元素為0、非0的一維向量(行列都可))。
方法一:使用加粗部分代碼即可,setTrainTestSplit或setTrainTestSplitRatio、getTrainSamples、getTestSamples
Ptr<TrainData> tdata=TrainData::create(samples, ROW_SAMPLE, responses);//其他參數默認,即所有樣本包含在TrainData類對象tdata中 //tdata->setTrainTestSplit(16000);//前16000行為訓練集 tdata->setTrainTestSplitRatio(0.8);//比例方式,前80%行作為訓練集,推薦 Mat trainSet = tdata->getTrainSamples();//獲取訓練集 Mat testSet = tdata->getTestSamples();//獲取測試集 //Mat res = tdata->getResponses();//所有標簽 //Mat classNum = tdata->getClassLabels();//26類(分26類問題),65~90
tdata是TrainData類型(包含了標簽、特征),trainSet 、testSet 是Mat類型,只有特征。
方法二:
Mat sample_idx = Mat::zeros(1, samples.rows, CV_8U); Mat train_samples = sample_idx.colRange(0, (int)(data.rows*0.8)); //操作train_samples就是操作sample_idx,淺拷貝。sample_idx中前80%變為1
train_samples.setTo(Scalar::all(1)); Ptr<TrainData> tdata=TrainData::create(samples, ROW_SAMPLE, responses, noArray(), sample_idx);//sample_idx中1對應的樣本參與訓練(非0有效)
此時tdata中共有前80%的樣本(既有特征又有標簽)。
【樣本集注意點】
如果樣本集不同類別間是雜亂的,那么上述函數可以隨意用。不過,一般我們會自己歸類,如2分類問題。正樣本、負樣本各自獨立有序。為了保證正負樣本都會參與訓練,所以最好不要拆分TrainData類型總樣本集。否則可能會出現前80%行一種類別過多的情況。
如果可以保證拆分后,訓練集不同類別樣本均衡,那可以使用訓練集、測試集這種我們習慣的方式。否則,不推薦拆分。