OpenCV之ml 模块. 机器学习:支持向量机(SVM)介绍 支持向量机对线性不可分数据的处理
支持向量機(SVM)介紹
目標
本文檔嘗試解答如下問題:
- 如何使用OpenCV函數?CvSVM::train?訓練一個SVM分類器, 以及用?CvSVM::predict?測試訓練結果。
什么是支持向量機(SVM)?
支持向量機 (SVM) 是一個類分類器,正式的定義是一個能夠將不同類樣本在樣本空間分隔的超平面。 換句話說,給定一些標記(label)好的訓練樣本 (監督式學習), SVM算法輸出一個最優化的分隔超平面。
如何來界定一個超平面是不是最優的呢? 考慮如下問題:
假設給定一些分屬于兩類的2維點,這些點可以通過直線分割, 我們要找到一條最優的分割線.Note
?在這個示例中,我們考慮卡迪爾平面內的點與線,而不是高維的向量與超平面。 這一簡化是為了讓我們以更加直覺的方式建立起對SVM概念的理解, 但是其基本的原理同樣適用于更高維的樣本分類情形。
在上面的圖中, 你可以直覺的觀察到有多種可能的直線可以將樣本分開。 那是不是某條直線比其他的更加合適呢? 我們可以憑直覺來定義一條評價直線好壞的標準:
距離樣本太近的直線不是最優的,因為這樣的直線對噪聲敏感度高,泛化性較差。 因此我們的目標是找到一條直線,離所有點的距離最遠。由此, SVM算法的實質是找出一個能夠將某個值最大化的超平面,這個值就是超平面離所有訓練樣本的最小距離。這個最小距離用SVM術語來說叫做?間隔(margin)?。 概括一下,最優分割超平面?最大化?訓練數據的間隔。
如何計算最優超平面?
下面的公式定義了超平面的表達式:
?叫做?權重向量?,??叫做?偏置(bias)?。
See also
?關于超平面的更加詳細的說明可以參考T. Hastie, R. Tibshirani 和 J. H. Friedman的書籍?Elements of Statistical Learning?, section 4.5 (Seperating Hyperplanes)。
最優超平面可以有無數種表達方式,即通過任意的縮放??和??。 習慣上我們使用以下方式來表達最優超平面
式中??表示離超平面最近的那些點。 這些點被稱為?支持向量**。 該超平面也稱為 **canonical 超平面.
通過幾何學的知識,我們知道點??到超平面??的距離為:
特別的,對于 canonical 超平面, 表達式中的分子為1,因此支持向量到canonical 超平面的距離是
剛才我們介紹了間隔(margin),這里表示為?, 它的取值是最近距離的2倍:
最后最大化??轉化為在附加限制條件下最小化函數??。 限制條件隱含超平面將所有訓練樣本??正確分類的條件,
式中??表示樣本的類別標記。
這是一個拉格朗日優化問題,可以通過拉格朗日乘數法得到最優超平面的權重向量??和偏置??。
源碼
| 123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/ml/ml.hpp>using namespace cv;int main() {// Data for visual representationint width = 512, height = 512;Mat image = Mat::zeros(height, width, CV_8UC3);// Set up training datafloat labels[4] = {1.0, -1.0, -1.0, -1.0};Mat labelsMat(3, 1, CV_32FC1, labels);float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };Mat trainingDataMat(3, 2, CV_32FC1, trainingData);// Set up SVM's parametersCvSVMParams params;params.svm_type = CvSVM::C_SVC;params.kernel_type = CvSVM::LINEAR;params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);// Train the SVMCvSVM SVM;SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);Vec3b green(0,255,0), blue (255,0,0);// Show the decision regions given by the SVMfor (int i = 0; i < image.rows; ++i)for (int j = 0; j < image.cols; ++j){Mat sampleMat = (Mat_<float>(1,2) << i,j);float response = SVM.predict(sampleMat);if (response == 1)image.at<Vec3b>(j, i) = green;else if (response == -1) image.at<Vec3b>(j, i) = blue;}// Show the training dataint thickness = -1;int lineType = 8;circle( image, Point(501, 10), 5, Scalar( 0, 0, 0), thickness, lineType);circle( image, Point(255, 10), 5, Scalar(255, 255, 255), thickness, lineType);circle( image, Point(501, 255), 5, Scalar(255, 255, 255), thickness, lineType);circle( image, Point( 10, 501), 5, Scalar(255, 255, 255), thickness, lineType);// Show support vectorsthickness = 2;lineType = 8;int c = SVM.get_support_vector_count();for (int i = 0; i < c; ++i){const float* v = SVM.get_support_vector(i);circle( image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness, lineType);}imwrite("result.png", image); // save the image imshow("SVM Simple Example", image); // show it to the userwaitKey(0);} |
解釋
本例中的訓練樣本由分屬于兩個類別的2維點組成, 其中一類包含一個樣本點,另一類包含三個點。
float labels[4] = {1.0, -1.0, -1.0, -1.0}; float trainingData[4][2] = {{501, 10}, {255, 10}, {501, 255}, {10, 501}};函數?CvSVM::train?要求訓練數據儲存于float類型的?Mat?結構中, 因此我們定義了以下矩陣:
Mat trainingDataMat(3, 2, CV_32FC1, trainingData); Mat labelsMat (3, 1, CV_32FC1, labels);設置SVM參數
此教程中,我們以可線性分割的分屬兩類的訓練樣本簡單講解了SVM的基本原理。 然而,SVM的實際應用情形可能復雜得多 (比如非線性分割數據問題,SVM核函數的選擇問題等等)。 總而言之,我們需要在訓練之前對SVM做一些參數設定。 這些參數保存在類?CvSVMParams?中。
CvSVMParams params; params.svm_type = CvSVM::C_SVC; params.kernel_type = CvSVM::LINEAR; params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);-
SVM類型. 這里我們選擇了?CvSVM::C_SVC?類型,該類型可以用于n-類分類問題 (n??2)。 這個參數定義在CvSVMParams.svm_type?屬性中.
Note
?CvSVM::C_SVC?類型的重要特征是它可以處理非完美分類的問題 (及訓練數據不可以完全的線性分割)。在本例中這一特征的意義并不大,因為我們的數據是可以線性分割的,我們這里選擇它是因為它是最常被使用的SVM類型。
-
SVM 核類型. 我們沒有討論核函數,因為對于本例的樣本,核函數的討論沒有必要。然而,有必要簡單說一下核函數背后的主要思想, 核函數的目的是為了將訓練樣本映射到更有利于可線性分割的樣本集。 映射的結果是增加了樣本向量的維度,這一過程通過核函數完成。 此處我們選擇的核函數類型是?CvSVM::LINEAR?表示不需要進行映射。 該參數由CvSVMParams.kernel_type?屬性定義。
-
算法終止條件. SVM訓練的過程就是一個通過?迭代?方式解決約束條件下的二次優化問題,這里我們指定一個最大迭代次數和容許誤差,以允許算法在適當的條件下停止計算。 該參數定義在?cvTermCriteria?結構中。
訓練支持向量機
調用函數?CvSVM::train?來建立SVM模型。
CvSVM SVM; SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);SVM區域分割
函數?CvSVM::predict?通過重建訓練完畢的支持向量機來將輸入的樣本分類。 本例中我們通過該函數給向量空間著色, 及將圖像中的每個像素當作卡迪爾平面上的一點,每一點的著色取決于SVM對該點的分類類別:綠色表示標記為1的點,藍色表示標記為-1的點。
Vec3b green(0,255,0), blue (255,0,0);for (int i = 0; i < image.rows; ++i)for (int j = 0; j < image.cols; ++j){Mat sampleMat = (Mat_<float>(1,2) << i,j);float response = SVM.predict(sampleMat);if (response == 1)image.at<Vec3b>(j, i) = green;elseif (response == -1)image.at<Vec3b>(j, i) = blue;}支持向量
這里用了幾個函數來獲取支持向量的信息。 函數?CvSVM::get_support_vector_count?輸出支持向量的數量,函數CvSVM::get_support_vector?根據輸入支持向量的索引來獲取指定位置的支持向量。 通過這一方法我們找到訓練樣本的支持向量并突出顯示它們。
int c = SVM.get_support_vector_count();for (int i = 0; i < c; ++i) { const float* v = SVM.get_support_vector(i); // get and then highlight with grayscale circle( image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness, lineType); }結果
- 程序創建了一張圖像,在其中顯示了訓練樣本,其中一個類顯示為白色圓圈,另一個類顯示為黑色圓圈。
- 訓練得到SVM,并將圖像的每一個像素分類。 分類的結果將圖像分為藍綠兩部分,中間線就是最優分割超平面。
- 最后支持向量通過灰色邊框加重顯示。
支持向量機對線性不可分數據的處理
目標
本文檔嘗試解答如下問題:
- 在訓練數據線性不可分時,如何定義此情形下支持向量機的最優化問題。
- 如何設置?CvSVMParams?中的參數來解決此類問題。
動機
為什么需要將支持向量機優化問題擴展到線性不可分的情形? 在多數計算機視覺運用中,我們需要的不僅僅是一個簡單的SVM線性分類器, 我們需要更加強大的工具來解決?訓練數據無法用一個超平面分割?的情形。
我們以人臉識別來做一個例子,訓練數據包含一組人臉圖像和一組非人臉圖像(除了人臉之外的任何物體)。 這些訓練數據超級復雜,以至于為每個樣本找到一個合適的表達 (特征向量) 以讓它們能夠線性分割是非常困難的。
最優化問題的擴展
還記得我們用支持向量機來找到一個最優超平面。 既然現在訓練數據線性不可分,我們必須承認這個最優超平面會將一些樣本劃分到錯誤的類別中。 在這種情形下的優化問題,需要將?錯分類(misclassification)?當作一個變量來考慮。新的模型需要包含原來線性可分情形下的最優化條件,即最大間隔(margin), 以及在線性不可分時分類錯誤最小化。
我們還是從最大化?間隔?這一條件來推導我們的最優化問題的模型(這在?前一節?已經討論了):
在這個模型中加入錯分類變量有多種方法。比如,我們可以最小化一個函數,該函數定義為在原來模型的基礎上再加上一個常量乘以樣本被錯誤分類的次數:
然而,這并不是一個好的解決方案,其中一個原因是它沒有考慮錯分類的樣本距離同類樣本所屬區域的大小。 因此一個更好的方法是考慮?錯分類樣本離同類區域的距離:
這里為每一個樣本定義一個新的參數??, 這個參數包含對應樣本離同類區域的距離。 下圖顯示了兩類線性不可分的樣本,以及一個分割超平面和錯分類樣本距離同類區域的距離。
Note
?圖中只顯示了錯分類樣本的距離,其余樣本由于已經處于同類區域內部所以距離為零。
紅色和藍色直線表示各自區域的邊際間隔, 每個??表示從錯分類樣本到同類區域邊際間隔的距離。
最后我們得到最優問題的最終模型:
關于參數C的選擇, 明顯的取決于訓練樣本的分布情況。 盡管并不存在一個普遍的答案,但是記住下面幾點規則還是有用的:
- C比較大時分類錯誤率較小,但是間隔也較小。 在這種情形下, 錯分類對模型函數產生較大的影響,既然優化的目的是為了最小化這個模型函數,那么錯分類的情形必然會受到抑制。
- C比較小時間隔較大,但是分類錯誤率也較大。 在這種情形下,模型函數中錯分類之和這一項對優化過程的影響變小,優化過程將更加關注于尋找到一個能產生較大間隔的超平面。
源碼
你可以從OpenCV源碼庫文件夾?samples/cpp/tutorial_code/gpu/non_linear_svms/non_linear_svms?下載源碼和視頻, 或者?從此處下載.
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | #include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/ml/ml.hpp>#define NTRAINING_SAMPLES 100 // Number of training samples per class #define FRAC_LINEAR_SEP 0.9f // Fraction of samples which compose the linear separable partusing namespace cv; using namespace std;int main() {// Data for visual representationconst int WIDTH = 512, HEIGHT = 512;Mat I = Mat::zeros(HEIGHT, WIDTH, CV_8UC3);//--------------------- 1. Set up training data randomly ---------------------------------------Mat trainData(2*NTRAINING_SAMPLES, 2, CV_32FC1);Mat labels (2*NTRAINING_SAMPLES, 1, CV_32FC1);RNG rng(100); // Random value generation class// Set up the linearly separable part of the training dataint nLinearSamples = (int) (FRAC_LINEAR_SEP * NTRAINING_SAMPLES);// Generate random points for the class 1Mat trainClass = trainData.rowRange(0, nLinearSamples);// The x coordinate of the points is in [0, 0.4)Mat c = trainClass.colRange(0, 1);rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(0.4 * WIDTH));// The y coordinate of the points is in [0, 1)c = trainClass.colRange(1,2);rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));// Generate random points for the class 2trainClass = trainData.rowRange(2*NTRAINING_SAMPLES-nLinearSamples, 2*NTRAINING_SAMPLES);// The x coordinate of the points is in [0.6, 1]c = trainClass.colRange(0 , 1); rng.fill(c, RNG::UNIFORM, Scalar(0.6*WIDTH), Scalar(WIDTH));// The y coordinate of the points is in [0, 1)c = trainClass.colRange(1,2);rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));//------------------ Set up the non-linearly separable part of the training data ---------------// Generate random points for the classes 1 and 2trainClass = trainData.rowRange( nLinearSamples, 2*NTRAINING_SAMPLES-nLinearSamples);// The x coordinate of the points is in [0.4, 0.6)c = trainClass.colRange(0,1);rng.fill(c, RNG::UNIFORM, Scalar(0.4*WIDTH), Scalar(0.6*WIDTH)); // The y coordinate of the points is in [0, 1)c = trainClass.colRange(1,2);rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));//------------------------- Set up the labels for the classes ---------------------------------labels.rowRange( 0, NTRAINING_SAMPLES).setTo(1); // Class 1labels.rowRange(NTRAINING_SAMPLES, 2*NTRAINING_SAMPLES).setTo(2); // Class 2//------------------------ 2. Set up the support vector machines parameters --------------------CvSVMParams params;params.svm_type = SVM::C_SVC;params.C = 0.1;params.kernel_type = SVM::LINEAR;params.term_crit = TermCriteria(CV_TERMCRIT_ITER, (int)1e7, 1e-6);//------------------------ 3. Train the svm ----------------------------------------------------cout << "Starting training process" << endl;CvSVM svm;svm.train(trainData, labels, Mat(), Mat(), params);cout << "Finished training process" << endl;//------------------------ 4. Show the decision regions ----------------------------------------Vec3b green(0,100,0), blue (100,0,0);for (int i = 0; i < I.rows; ++i)for (int j = 0; j < I.cols; ++j){Mat sampleMat = (Mat_<float>(1,2) << i, j);float response = svm.predict(sampleMat);if (response == 1) I.at<Vec3b>(j, i) = green;else if (response == 2) I.at<Vec3b>(j, i) = blue;}//----------------------- 5. Show the training data --------------------------------------------int thick = -1;int lineType = 8;float px, py;// Class 1for (int i = 0; i < NTRAINING_SAMPLES; ++i){px = trainData.at<float>(i,0);py = trainData.at<float>(i,1);circle(I, Point( (int) px, (int) py ), 3, Scalar(0, 255, 0), thick, lineType);}// Class 2for (int i = NTRAINING_SAMPLES; i <2*NTRAINING_SAMPLES; ++i){px = trainData.at<float>(i,0);py = trainData.at<float>(i,1);circle(I, Point( (int) px, (int) py ), 3, Scalar(255, 0, 0), thick, lineType);}//------------------------- 6. Show support vectors --------------------------------------------thick = 2;lineType = 8;int x = svm.get_support_vector_count();for (int i = 0; i < x; ++i){const float* v = svm.get_support_vector(i);circle( I, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thick, lineType);}imwrite("result.png", I); // save the Imageimshow("SVM for Non-Linear Training Data", I); // show it to the userwaitKey(0); } |
解釋
本例中的訓練樣本由分屬于兩個類別的2維點組成。 為了讓程序更加吸引人,我們用均勻概率密度函數(PDF)隨機生成樣本.
我們將樣本的生成代碼分成兩部分。
在第一部分我們生成兩類線性可分樣本
// class 1 隨機樣本生成 Mat trainClass = trainData.rowRange(0, nLinearSamples); // x 坐標范圍 [0, 0.4) Mat c = trainClass.colRange(0, 1); rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(0.4 * WIDTH)); // y 坐標范圍 [0, 1) c = trainClass.colRange(1,2); rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));// class 2 隨機樣本生成 trainClass = trainData.rowRange(2*NTRAINING_SAMPLES-nLinearSamples, 2*NTRAINING_SAMPLES); // x 坐標范圍 [0.6, 1] c = trainClass.colRange(0 , 1); rng.fill(c, RNG::UNIFORM, Scalar(0.6*WIDTH), Scalar(WIDTH)); // y 坐標范圍 [0, 1) c = trainClass.colRange(1,2); rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));在第二部分我們同時生成重疊分布線性不可分的兩類樣本.
// classes 1 , 2 隨機樣本生成 trainClass = trainData.rowRange( nLinearSamples, 2*NTRAINING_SAMPLES-nLinearSamples); // x 坐標范圍 [0.4, 0.6) c = trainClass.colRange(0,1); rng.fill(c, RNG::UNIFORM, Scalar(0.4*WIDTH), Scalar(0.6*WIDTH)); // y 坐標范圍 [0, 1) c = trainClass.colRange(1,2); rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));See also
?前一節?支持向量機(SVM)介紹?提到了類?CvSVMParams?中的一些參數需要在訓練SVM之前設置。
CvSVMParams params; params.svm_type = SVM::C_SVC; params.C = 0.1; params.kernel_type = SVM::LINEAR; params.term_crit = TermCriteria(CV_TERMCRIT_ITER, (int)1e7, 1e-6);這里的設置和?前一節?的設置有兩處不一樣的地方
-
CvSVM::C_SVC. 此處取值較小,目的是優化時不過分懲罰分類錯誤。這樣做的目的是為了得到一個與直覺預期比較接近的分隔線。 您可以通過調整該參數來加深你對問題的理解。
Note
?這里在兩類之間重疊區域的點比較少,縮小?FRAC_LINEAR_SEP?會增加不可分區域的點數,此時?CvSVM::C_SVC?參數的調整對結果的影響深遠。
-
算法終止條件. 最大迭代次數需要顯著增加來容許非線性可分的訓練數據, 這里的最大迭代設置是前一節的10的5次方倍。
調用函數?CvSVM::train?來建立SVM模型。 注意訓練過程可能耗時比較長,您需要多一點耐心來等待。
CvSVM svm; svm.train(trainData, labels, Mat(), Mat(), params);函數?CvSVM::predict?通過重建訓練完畢的支持向量機來將輸入的樣本分類。 本例中我們通過該函數給向量空間著色, 即將圖像中的每個像素當作卡迪爾平面上的一點,每一點的著色取決于SVM對該點的分類類別:深綠色表示分類為1的點,深藍色表示分類為2的點。
Vec3b green(0,100,0), blue (100,0,0); for (int i = 0; i < I.rows; ++i)for (int j = 0; j < I.cols; ++j){Mat sampleMat = (Mat_<float>(1,2) << i, j);float response = svm.predict(sampleMat);if (response == 1) I.at<Vec3b>(j, i) = green;else if (response == 2) I.at<Vec3b>(j, i) = blue;}函數?circle?被用來顯示訓練樣本。 標記為1的樣本用淺綠表示,標記為2的樣本用淺藍表示。
int thick = -1; int lineType = 8; float px, py; // Class 1 for (int i = 0; i < NTRAINING_SAMPLES; ++i) {px = trainData.at<float>(i,0);py = trainData.at<float>(i,1);circle(I, Point( (int) px, (int) py ), 3, Scalar(0, 255, 0), thick, lineType); } // Class 2 for (int i = NTRAINING_SAMPLES; i <2*NTRAINING_SAMPLES; ++i) {px = trainData.at<float>(i,0);py = trainData.at<float>(i,1);circle(I, Point( (int) px, (int) py ), 3, Scalar(255, 0, 0), thick, lineType); }這里用了幾個函數來獲取支持向量的信息。 函數?CvSVM::get_support_vector_count?輸出支持向量的數量,函數CvSVM::get_support_vector?根據輸入支持向量的索引來獲取指定位置的支持向量。 通過這一方法我們找到訓練樣本的支持向量并突出顯示它們。
thick = 2; lineType = 8; int x = svm.get_support_vector_count();for (int i = 0; i < x; ++i) {const float* v = svm.get_support_vector(i);circle( I, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thick, lineType); }結果
- 程序創建了一張圖像,在其中顯示了訓練樣本,其中一個類顯示為淺綠色圓圈,另一個類顯示為淺藍色圓圈。
- 訓練得到SVM,并將圖像的每一個像素分類。 分類的結果將圖像分為藍綠兩部分,中間線就是最優分割超平面。由于樣本非線性可分, 自然就有一些被錯分類的樣本。 一些綠色點被劃分到藍色區域, 一些藍色點被劃分到綠色區域。
- 最后支持向量通過灰色邊框加重顯示。
你可以在?YouTube?觀看本程序的實時運行.
from:?http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/ml/table_of_content_ml/table_of_content_ml.html#table-of-content-ml
總結
以上是生活随笔為你收集整理的OpenCV之ml 模块. 机器学习:支持向量机(SVM)介绍 支持向量机对线性不可分数据的处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenCV之objdetect 模块.
- 下一篇: OpenCV之gpu 模块. 使用GPU