什麼是支持向量機SVM
支持向量機是20世紀90年代中期發展起來的基於統計學習理論的一種機器學習方法,通過尋求結構化風險最小來提高學習機泛化能力,實現經驗風險和置信範圍的最小化,從而達到在統計樣本量較少的情況下,也能獲得良好統計規律的目的。它是一種二類分類模型,其基本模型定義爲特徵空間上的間隔最大的線性分類器,即支持向量機的學習策略便是間隔最大化,最終可轉化爲一個凸二次規劃問題的求解。
通俗來說,支持向量機是一個能夠將不同類樣本在樣本空間分隔的超平面。 換句話說,給定一些標記(label)好的訓練樣本 (監督式學習), SVM算法輸出一個最優化的分隔超平面。
- 如何評判最優的超平面?
給定分別屬於兩類(圓形和正方形)的二維點,這些點可以通過直線分割。
可以看出,能將兩類樣本分開的直線有無數條。如何定義一條直線好壞的標準?
距離樣本太近的直線不是最優的,因爲這樣的直線對噪聲敏感度高,泛化性較差。 因此我們的目標是找到一條直線,離所有點的距離最遠。
由此, SVM算法的實質是找出一個能夠將某個值最大化的超平面,這個值就是超平面離所有訓練樣本的最小距離。這個最小距離用SVM術語來說叫做間隔(margin) 。 概括一下,最優分割超平面最大化訓練數據的間隔。
SVM的核心——核函數
很多情況下需要分類的數據都是線性不可分數據。一種解決方法是選擇一個核函數(Kernel)K,將原始數據映射到高維空間,使原始數據線性可分問題變爲在高維空間線性可分的問題。如圖所示。
常用核函數:
①線性核:K(x,y)=x·y
②多項式核:K(x,y)=[(x·y)+1]^d
③高斯核:K(x,y)=exp(-|x-y|^2/d^2)
④Sigmoid內積核:K(x,y)=tanh(a(x·y)+b)
OpenCV在很久以前就集成了SVM功能,然而升級到3.0和3.1版本後發生了很大的變化。這裏只對3.1版本進行考慮。
建立訓練樣本
一開始訓練數據用的是int類型,即
int colors[8][3];
Mat trainingDataMat(8, 3, CV_32SC1, colors);
結果報錯:
OpenCV Error: Assertion failed (_samples.type() == CV_32F) in cv::ml::SVMImpl::do_train, file C:\buildslave64\win64_amdocl\master_PackSlave-win64-vc14-shared\opencv\modules\ml\src\svm.cpp, line 1370
查詢官方API發現:訓練數據必須爲CV_32FC1
格式。
訓練支持向量機
//初始化SVM
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::Types::C_SVC);
svm->setKernel(SVM::KernelTypes::LINEAR);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
//訓練
svm->train(trainingDataMat,SampleTypes::ROW_SAMPLE , labelsMat);
- 預測
Mat sample = (Mat_<float>(1, 3) << color[0] / pixelAmount, color[1] / pixelAmount, color[2] / pixelAmount);
float response = svm->predict(sample);
源代碼:
簡單的通過顏色來對蘋果和香蕉進行分類,讀取樣本圖片並計算其像素顏色平均值作爲訓練數據進行訓練。
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/ml.hpp"
#include <iostream>
using namespace std;
using namespace cv;
using namespace cv::ml;
int main(int argc, char** argv)
{
//訓練數據的分類 1.0蘋果-1.0香蕉
int labels[8] = { 1,1,1,1,-1,-1,-1,-1 };
Mat labelsMat(8, 1, CV_32SC1, labels);
//訓練數據矩陣
float colors[8][3];
//蘋果樣本數據
for (int i = 0; i < 4; i++){
string temp = "apple";
char n[1];
itoa(i, n, 10);
temp = temp.append(n);
string imgName = temp.append(".png");
//cout << imgName;
Mat testImg = imread(imgName);
//imshow(imgName, testImg);
int color[3] = { 0, 0, 0 };
int pixelAmount = testImg.rows*testImg.cols;
for (int j = 0; j < testImg.rows; j++){
for (int k = 0; k < testImg.cols; k++){
color[0] += testImg.at<Vec3b>(j, k)[0];
color[1] += testImg.at<Vec3b>(j, k)[1];
color[2] += testImg.at<Vec3b>(j, k)[2];
}
}
cout << "蘋果" << i << " : " << color[0] / pixelAmount << " " << color[1] / pixelAmount << " " << color[2] / pixelAmount << endl;
colors[i][0] = color[0] / pixelAmount;
colors[i][1] = color[1] / pixelAmount;
colors[i][2] = color[2] / pixelAmount;
}
//香蕉樣本數據
for (int i = 0; i < 4; i++){
string temp = "banana";
char n[1];
itoa(i, n, 10);
temp = temp.append(n);
string imgName = temp.append(".png");
//cout << imgName;
Mat testImg = imread(imgName);
//imshow(imgName, testImg);
int color[3] = { 0, 0, 0 };
int pixelAmount = testImg.rows*testImg.cols;
for (int j = 0; j < testImg.rows; j++){
for (int k = 0; k < testImg.cols; k++){
color[0] += testImg.at<Vec3b>(j, k)[0];
color[1] += testImg.at<Vec3b>(j, k)[1];
color[2] += testImg.at<Vec3b>(j, k)[2];
}
}
cout << "香蕉" << i << " : " << color[0] / pixelAmount << " " << color[1] / pixelAmount << " " << color[2] / pixelAmount << endl;
colors[i + 4][0] = color[0] / pixelAmount;
colors[i + 4][1] = color[1] / pixelAmount;
colors[i + 4][2] = color[2] / pixelAmount;
}
Mat trainingDataMat(8, 3, CV_32FC1, colors);
//初始化SVM
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::Types::C_SVC);
svm->setKernel(SVM::KernelTypes::LINEAR);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
//訓練
svm->train(trainingDataMat,SampleTypes::ROW_SAMPLE , labelsMat);
//測試圖片
for (int i = 0; i < 4; i++){
string temp = "test";
char n[1];
itoa(i, n, 10);
temp = temp.append(n);
string imgName = temp.append(".png");
//cout << imgName;
Mat testImg = imread(imgName);
//imshow(imgName, testImg);
int color[3] = { 0, 0, 0 };
int pixelAmount = testImg.rows*testImg.cols;
for (int j = 0; j < testImg.rows; j++){
for (int k = 0; k < testImg.cols; k++){
color[0] += testImg.at<Vec3b>(j, k)[0];
color[1] += testImg.at<Vec3b>(j, k)[1];
color[2] += testImg.at<Vec3b>(j, k)[2];
}
}
cout << "測試" << i << " : " << color[0] / pixelAmount << " " << color[1] / pixelAmount << " " << color[2] / pixelAmount << endl;
Mat sample = (Mat_<float>(1, 3) << color[0] / pixelAmount, color[1] / pixelAmount, color[2] / pixelAmount);
float response = svm->predict(sample);
if (response == 1.0){
cout << "蘋果" << endl;
}
else if (response == -1.0){
cout << "香蕉" << endl;
}
}
waitKey();
int a;
cin >> a;
return 0;
}
樣本圖片:
測試圖片:
測試結果: