機器學習是由 模型 + 策略 + 算法 構成的,構建一種機器學習方法 (例如,支持向量機),就是具體去肯定這三個要素。html
支持向量機,簡稱 SVM (Support Vector Machine),是一種二分分類模型。算法
定義在特徵空間上的,一種間隔 (margin) 最大的,線性分類器 (linear classifier)機器學習
使間隔最大化,可轉化爲求解凸二次規劃的問題。函數
求解凸二次規劃的最優化算法。學習
供訓練的樣本數據可分爲三類:第一類是線性可分的,第二類是近似線性可分的,第三類是線性不可分的。優化
三種樣本數據對應的 SVM 分別爲:線性可分 (硬間隔最大化),線性 (軟間隔最大化),非線性 (核技巧 + 軟間隔最大化)。ui
爲了方便起見,下文提到的向量機 或 SVM,都是指線性可分支持向量機。spa
n 維歐式空間中,餘維度等於 1 (也即 n-1 維) 的線性子空間,稱爲超平面。code
超平面在二維空間中是直線,在三維空間中是平面,可用來分隔數據。以下圖所示,超平面 (直線) 能將兩類不一樣的數據 (圓點和方點) 分隔開來。htm
若是將數據點記爲 x (n 維向量),則超平面的方程爲 $\ f(x) = \beta_{0} + \beta^{T} x = 0\; $,其中,$\beta $ 爲權重向量 (有的書稱爲 「法向量」)
解釋:右圖中 $\beta^{*}$ 爲超平面 (綠色直線) 的單位法向量 $\ \beta^{*} = \dfrac{\beta}{||\beta||}$,平面中任意點 x 到超平面的距離爲 $\ r = \dfrac{|\beta_{0} + \beta^{T} x|} {||\beta||}$
又附: 平面座標中,一個點 $\;(x_{0}, y_{0})\;$到直線$\;(Ax + By + C = 0)\;$ 的距離爲 $\; d = \dfrac{Ax_{0} + By_{0} + C}{\sqrt{A^{2} + B^{2}}} $
若是取輸出 y 分別爲 +1 和 -1,表明兩種不一樣類別,則對於 x,其對應的 f(x) 有三種可能取值:
1) 當位於超平面上時 (也即圖中的直線上),$ f(x) = \beta_{0} + \beta^{T} x = 0 $
2) 當位於超平面左邊時, $f(x) = \beta_{0} + \beta^{T} x \leq -1$
3) 當位於超平面右邊時, $f(x) = \beta_{0} + \beta^{T} x \geq +1$
假設存在一個超平面,能將 n 個樣本數據正確的分類,則對於任意一個樣本數據$\;(x_{i}, y_{i})$,知足以下約束條件:
$\quad y_{i}(\beta^{T} x_{i} + \beta_{0}) \geq 1 , i = 1, 2, ..., n $
如上圖所示,距離超平面最近的三個樣本點,使得 2) 和 3) 中的等號成立,它們稱爲 「支持向量」。
由於支持向量使得 2) 和 3) 的等號成立,因此它們到超平面的距離:
$\quad r = \dfrac{|\beta_{0} + \beta^{T} x|} {||\beta||} = \dfrac{1}{||\beta||}$
兩個不一樣種類的支持向量 (分別取值爲 +1 和 -1),到超平面的距離之和爲:
$\quad r^{'} = \dfrac{2}{||\beta||}\;$,$r^{'}\;$稱爲 「幾何間隔」 (geometric margin)
一個點距離超平面的遠近,可用來表示分類結果的正確性和確信度。
直觀上看,超平面越是靠近兩類樣本數據的正中間 (也即兩類數據點到超平面的距離越遠),則分類結果的正確性和確信度就越高。
SVM 的學習算法 (或稱最大間隔法),就是基於所給的樣本數據,去尋找到具備 「最大間隔」 的超平面,將不一樣種類的樣本分隔開來。
也即,在知足 「約束條件」 的前提下,使得 $r^{'}$ 的值最大:
$\quad \max \limits_{\beta,\; \beta_{0}} \dfrac{2}{||\beta||} \quad subject\;to \quad y_{i}(\beta^{T} x_{i} + \beta_{0}) \geq 1 , i = 1, 2, ..., n $
再或者,最大化 $r^{'}$,等價於最小化 $||\beta||^{2}$,以下所示:
$\quad \min \limits_{\beta,\;\beta_{0}} \dfrac{1}{2} ||\beta||^{2} \quad subject \; to \quad y_{i} (\beta^{T} x_{i} + \beta_{0}) \geq 1 , i = 1, 2, ..., n $
OpenCV 中 SVM 的實現是基於 libsvm 的,其基本的過程爲:建立 SVM 模型 --> 設置相關參數 --> 樣本數據訓練 --> 預測
static Ptr<SVM> cv::ml::SVM::create ( ); // 建立一個空模型
virtual void cv::ml::SVM::setType (int val); // 設置 SVM 的類型,默認爲 SVM::C_SVC
virtual void cv::ml::SVM::setKernel (int kernelType); // 設置核函數類型,本文爲線性核函數,設爲 SVM::LINEAR
virtual void cv::ml::SVM::setTermCriteria (const cv::TermCriteria & val); // 設置迭代終止準則
// type,準則類型; maxCount,最大迭代次數;epsilo,目標精度
cv::TermCriteria::TermCriteria(int type, int maxCount, double epsilon);
virtual bool cv::ml::StatModel::train ( InputArray samples, // 訓練樣本 int layout, // 訓練樣本爲 「行樣本」 ROW_SAMPLE 或 「列樣本」 COL_SAMPLE InputArray responses // 對應樣本數據的分類結果 )
用來預測一個新樣本的響應,各個參數以下:
// samples,輸入的樣本書數據;results,輸出矩陣,默認不輸出;flags,標識,默認爲 0 virtual float cv::ml::StatModel::predict(InputArray samples, OutputArray results=noArray(),int flags=0) const;
下面是 OpenCV 3.2 中的官方例程,更改了訓練樣本數據
#include <opencv2/core.hpp> #include <opencv2/imgproc.hpp>
#include "opencv2/imgcodecs.hpp" #include <opencv2/highgui.hpp> #include <opencv2/ml.hpp> using namespace cv; using namespace cv::ml; int main() { // 512 x 512 零矩陣 int width = 512, height = 512; Mat image = Mat::zeros(height, width, CV_8UC3); // 訓練樣本 float trainingData[6][2] = { { 500, 60 },{ 245, 40 },{ 480, 250 },{ 160, 380 },{400, 25},{55, 400} }; int labels[6] = {-1, 1, 1, 1,-1,1}; // 每一個樣本數據對應的輸出,由於是二分模型,因此輸出爲 +1 或者 -1 Mat trainingDataMat(6, 2, CV_32FC1, trainingData); Mat labelsMat(6, 1, CV_32SC1, labels); // 訓練 SVM Ptr<SVM> svm = SVM::create(); svm->setType(SVM::C_SVC); svm->setKernel(SVM::LINEAR); svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6)); svm->train(trainingDataMat, ROW_SAMPLE, labelsMat); // 顯示二分分類的結果 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) << j, i); float response = svm->predict(sampleMat); if (response == 1) image.at<Vec3b>(i, j) = blue; else if (response == -1) image.at<Vec3b>(i, j) = green; }
// 畫出訓練樣本數據 int thickness = -1; int lineType = 8; circle(image, Point(500, 60), 5, Scalar(0, 0, 0), thickness, lineType); circle(image, Point(245, 40), 5, Scalar(255, 255, 255), thickness, lineType); circle(image, Point(480, 250), 5, Scalar(255, 255, 255), thickness, lineType); circle(image, Point(160, 380), 5, Scalar(0, 0, 255), thickness, lineType); circle(image, Point(400, 25), 5, Scalar(255, 255, 255), thickness, lineType); circle(image, Point(55, 400), 5, Scalar(0, 0, 255), thickness, lineType);
// 顯示出支持向量
thickness = 2;
lineType = 8;
Mat sv = svm->getUncompressedSupportVectors();
for (int i = 0; i < sv.rows; ++i)
{
const float* v = sv.ptr<float>(i);
circle(image, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), thickness, lineType);
}
imwrite("result.png", image); // 保存訓練的結果 imshow("SVM Simple Example", image); waitKey(0); }
OpenCV 3.2 版本中使用了一個新的函數,來獲取支持向量,即 getUncompressedSupportVectors()
而 OpenCV 3.0 中,獲取支持向量的函數爲 getSupportVectors(),但當內核設爲 SVM::LINEAR 時,該函數並不能獲得支持向量,這是 3.0 版本的缺陷。
運行結果以下圖所示,超平面附近的三個灰色匡白色圓點,即是所謂的 「支持向量」。
<機器學習> 周志軍 第6章
<統計學習方法> 李航 第7章
<The Elements of Statistical Learning_2nd> ch 4.5 , ch 12
"支持向量機系列「 pluskid
OpenCV 3.2 Tutorials -- Machine Learning (ml module) -- Introduction to Support Vector Machines