SVM开发详解
EasyPR--開發(fā)詳解(6)SVM開發(fā)詳解
博客園精華區(qū) 原文? http://www.cnblogs.com/subconscious/p/4249581.html 主題 支持向量機(jī)在前面的幾篇文章中,我們介紹了EasyPR中車牌定位模塊的相關(guān)內(nèi)容。本文開始分析車牌定位模塊后續(xù)步驟的車牌判斷模塊。車牌判斷模塊是EasyPR中的基于機(jī)器學(xué)習(xí)模型的一個(gè)模塊,這個(gè)模型就是作者前文中從機(jī)器學(xué)習(xí)談起中提到的SVM(支持向量機(jī))。
我們已經(jīng)知道,車牌定位模塊的輸出是一些候選車牌的圖片。但如何從這些候選車牌圖片中甄選出真正的車牌,就是通過SVM模型判斷/預(yù)測(cè)得到的。
圖1 從候選車牌中選出真正的車牌
簡(jiǎn)單來說,EasyPR的車牌判斷模塊就是將候選車牌的圖片一張張地輸入到SVM模型中,然后問它,這是車牌么?如果SVM模型回答不是,那么就繼續(xù)下一張,如果是,則把圖片放到一個(gè)輸出列表里。最后把列表輸入到下一步處理。由于EasyPR使用的是列表作為輸出,因此它可以輸出一副圖片中所有的車牌,不像一些車牌識(shí)別程序,只能輸出一個(gè)車牌結(jié)果。
圖2 EasyPR輸出多個(gè)車牌
現(xiàn)在,讓我們一步步地,進(jìn)入這個(gè)SVM模型的核心看看,它是如何做到判斷一副圖片是車牌還是不是車牌的?本文主要分為三個(gè)大的部分:
一.SVM應(yīng)用
人類是如何判斷一個(gè)張圖片所表達(dá)的信息呢?簡(jiǎn)單來說,人類在成長過程中,大腦記憶了無數(shù)的圖像,并且依次給這些圖像打上了標(biāo)簽,例如太陽,天空,房子,車子等等。你們還記得當(dāng)年上幼兒園時(shí)的那些教科書么,上面一個(gè)太陽,下面是文字。圖像的組成事實(shí)上就是許多個(gè)像素,由像素組成的這些信息被輸入大腦中,然后得出這個(gè)是什么東西的回答。我們?cè)赟VM模型中一開始輸入的原始信息也是圖像的所有像素,然后SVM模型通過對(duì)這些像素進(jìn)行分析,輸出這個(gè)圖片是否是車牌的結(jié)論。
圖3 通過圖像來學(xué)習(xí)
SVM模型處理的是最簡(jiǎn)單的情況,它只要回答是或者不是這個(gè)“二值”問題,比從許多類中檢索要簡(jiǎn)單很多。
我們可以看一下SVM進(jìn)行判斷的代碼:
int CPlateJudge::plateJudge(const vector<Mat>& inVec, vector<Mat>& resultVec) { int num = inVec.size(); for (int j = 0; j < num; j++) { Mat inMat = inVec[j]; Mat p = histeq(inMat).reshape(1, 1); p.convertTo(p, CV_32FC1); int response = (int)svm.predict(p); if (response == 1) { resultVec.push_back(inMat); } } return 0; } View Code首先我們讀取這幅圖片,然后把這幅圖片轉(zhuǎn)為OPENCV需要的格式;
Mat p = histeq(inMat).reshape(1, 1);p.convertTo(p, CV_32FC1);接著調(diào)用svm的方法predict;
int response = (int)svm.predict(p);perdict方法返回的值是1的話,就代表是車牌,否則就不是;
if (response == 1){resultVec.push_back(inMat);}svm是類CvSVM的一個(gè)對(duì)象。這個(gè)類是opencv里內(nèi)置的一個(gè)機(jī)器學(xué)習(xí)類。
CvSVM svm;opencv的CvSVM的實(shí)現(xiàn)基于libsvm(具體信息可以看opencv的官方文檔的 介紹 )。
libsvm是臺(tái)灣大學(xué)林智仁(Lin Chih-Jen)教授寫的一個(gè)世界知名的svm庫(可能算是目前業(yè)界使用率最高的一個(gè)庫)。官方主頁地址是 這里 。
libsvm的實(shí)現(xiàn)基于SVM這個(gè)算法,90年代初由Vapnik等人提出。國內(nèi)幾篇較好的解釋svm原理的博文:cnblog的LeftNotEasy(解釋的易懂),pluskid的 博文 (專業(yè)有配圖)。
作為支持向量機(jī)的發(fā)明者,Vapnik是一位機(jī)器學(xué)習(xí)界極為重要的大牛。 最近這位大牛也加入了Facebook 。
圖4 SVM之父Vapnik
svm的perdict方法的輸入是待預(yù)測(cè)數(shù)據(jù)的特征,也稱之為features。在這里,我們輸入的特征是圖像全部的像素。由于svm要求輸入的特征應(yīng)該是一個(gè)向量,而Mat是與圖像寬高對(duì)應(yīng)的矩陣,因此在輸入前我們需要使用reshape(1,1)方法把矩陣?yán)斐上蛄俊3巳肯袼匾酝?#xff0c;也可以有其他的特征,具體看第三部分“SVM調(diào)優(yōu)”。
predict方法的輸出是float型的值,我們需要把它轉(zhuǎn)變?yōu)閕nt型后再進(jìn)行判斷。如果是1代表就是車牌,否則不是。這個(gè)"1"的取值是由你在訓(xùn)練時(shí)輸入的標(biāo)簽決定的。標(biāo)簽,又稱之為label,代表某個(gè)數(shù)據(jù)的分類。如果你給SVM模型輸入一個(gè)車牌,并告訴它,這個(gè)圖片的標(biāo)簽是5。那么你這邊判斷時(shí)所用的值就應(yīng)該是5。
以上就是svm模型判斷的全過程。事實(shí)上,在你使用EasyPR的過程中,這些全部都是透明的。你不需要轉(zhuǎn)變圖片格式,也不需要調(diào)用svm模型preditct方法,這些全部由EasyPR在內(nèi)部調(diào)用。
那么,我們應(yīng)該做什么?這里的關(guān)鍵在于CvSVM這個(gè)類。我在前面的機(jī)器學(xué)習(xí)論文中介紹過,機(jī)器學(xué)習(xí)過程的步驟就是首先你搜集大量的數(shù)據(jù),然后把這些數(shù)據(jù)輸入模型中訓(xùn)練,最后再把生成的模型拿出來使用。
訓(xùn)練和預(yù)測(cè)兩個(gè)過程是分開的。也就是說你們?cè)谑褂肊asyPR時(shí)用到的CvSVM類是我在先前就訓(xùn)練好的。我是如何把我訓(xùn)練好的模型交給各位使用的呢?CvSVM類有個(gè)方法,把訓(xùn)練好的結(jié)果以xml文件的形式存儲(chǔ),我就是把這個(gè)xml文件隨EasyPR發(fā)布,并讓程序在執(zhí)行前先加載好這個(gè)xml。這個(gè)xml的位置就是在文件夾Model下面--svm.xml文件。
圖5 model文件夾下的svm.xml
如果看CPlateJudge的代碼,在構(gòu)造函數(shù)中調(diào)用了LoadModel()這個(gè)方法。
CPlateJudge::CPlateJudge() {//cout << "CPlateJudge" << endl;m_path = "model/svm.xml";LoadModel(); }LoadModel()方法的主要任務(wù)就是裝載model文件夾下svm.xml這個(gè)模型。
void CPlateJudge::LoadModel() {svm.clear();svm.load(m_path.c_str(), "svm"); }如果你把這個(gè)xml文件換成其他的,那么你就可以改變EasyPR車牌判斷的內(nèi)核,從而實(shí)現(xiàn)你自己的車牌判斷模塊。
后面的部分全部是告訴你如何有效地實(shí)現(xiàn)一個(gè)自己的模型(也就是svm.xml文件)。如果你對(duì)EasyPR的需求僅僅在應(yīng)用層面,那么到目前的了解就足夠了。如果你希望能夠改善EasyPR的效果,定制一個(gè)自己的車牌判斷模塊,那么請(qǐng)繼續(xù)往下看。
二.SVM訓(xùn)練
恭喜你!從現(xiàn)在開始起,你將真正踏入機(jī)器學(xué)習(xí)這個(gè)神秘并且充滿未知的領(lǐng)域。至今為止,機(jī)器學(xué)習(xí)很多方法的背后原理都非常復(fù)雜,但眾多的實(shí)踐都證明了其有效性。與許多其他學(xué)科不同,機(jī)器學(xué)習(xí)界更為關(guān)注的是最終方法的效果,也就是偏重以實(shí)踐效果作為評(píng)判標(biāo)準(zhǔn)。因此非常適合從工程的角度入手,通過自己動(dòng)手實(shí)踐一個(gè)項(xiàng)目里來學(xué)習(xí),然后再轉(zhuǎn)入理論。這個(gè)過程已經(jīng)被證明是 有效 的,本文的作者在開發(fā)EasyPR的時(shí)候,還沒有任何機(jī)器學(xué)習(xí)的理論基礎(chǔ)。后來的知識(shí)是將通過學(xué)習(xí)相關(guān)課程后獲取的。
簡(jiǎn)而言之,SVM訓(xùn)練部分的目標(biāo)就是通過一批數(shù)據(jù),然后生成一個(gè)代表我們模型的xml文件。
EasyPR中所有關(guān)于訓(xùn)練的方法都可以在 svm_train.cpp 中找到(1.0版位于train/code文件夾下,1.1版位于src/train文件夾下)。
一個(gè)訓(xùn)練過程包含5個(gè)步驟,見下圖:
圖6 一個(gè)完整的SVM訓(xùn)練流程
下面具體講解一下這5個(gè)步驟,步驟后面的括號(hào)里代表的是這個(gè)步驟主要的輸入與輸出。
1. preprocss(原始數(shù)據(jù)->學(xué)習(xí)數(shù)據(jù)(未標(biāo)簽))
預(yù)處理步驟主要處理的是原始數(shù)據(jù)到學(xué)習(xí)數(shù)據(jù)的轉(zhuǎn)換過程。原始數(shù)據(jù)(raw data),表示你一開始拿到的數(shù)據(jù)。這些數(shù)據(jù)的情況是取決你具體的環(huán)境的,可能有各種問題。學(xué)習(xí)數(shù)據(jù)(learn data),是可以被輸入到模型的數(shù)據(jù)。
為了能夠進(jìn)入模型訓(xùn)練,必須將原始數(shù)據(jù)處理為學(xué)習(xí)數(shù)據(jù),同時(shí)也可能進(jìn)行了數(shù)據(jù)的篩選。比方說你有10000張?jiān)紙D片,出于性能考慮,你只想用1000張圖片訓(xùn)練,那么你的預(yù)處理過程就是將這10000張?zhí)幚頌榉嫌?xùn)練要求的1000張。你生成的1000張圖片中應(yīng)該包含兩類數(shù)據(jù):真正的車牌圖片和不是車牌的圖片。如果你想讓你的模型能夠區(qū)分這兩種類型。你就必須給它輸入這兩類的數(shù)據(jù)。
通過EasyPR的車牌定位模塊PlateLocate可以生成大量的候選車牌圖片,里面包括模型需要的車牌和非車牌圖片。但這些候選車牌是沒有經(jīng)過分類的,也就是說沒有標(biāo)簽。下步工作就是給這些數(shù)據(jù)貼上標(biāo)簽。
2. label(學(xué)習(xí)數(shù)據(jù)(未標(biāo)簽)->學(xué)習(xí)數(shù)據(jù))
訓(xùn)練過程的第二步就是將未貼標(biāo)簽的數(shù)據(jù)轉(zhuǎn)化為貼過標(biāo)簽的學(xué)習(xí)數(shù)據(jù)。我們所要做的工作只是將車牌圖片放到一個(gè)文件夾里,非車牌圖片放到另一個(gè)文件夾里。在EasyPR里,這兩個(gè)文件夾分別叫做HasPlate和NoPlate。如果你打開train/data/plate_detect_svm后,你就會(huì)看到這兩個(gè)壓縮包,解壓后就是打好標(biāo)簽的數(shù)據(jù)(1.1版本在同層learn data文件夾下面)。
如果有人問我開發(fā)一個(gè)機(jī)器學(xué)習(xí)系統(tǒng)最耗時(shí)的步驟是哪個(gè),我會(huì)毫不猶豫的回答:“貼標(biāo)簽”。誠然,各位看到的壓縮包里已經(jīng)有打好標(biāo)簽的數(shù)據(jù)了。但各位可能不知道作者花在貼這些標(biāo)簽上的時(shí)間。粗略估計(jì),整個(gè)EasyPR開發(fā)過程中有70%的時(shí)間都在貼標(biāo)簽。SVM模型還好,只有兩個(gè)類,訓(xùn)練數(shù)據(jù)僅有1000張。到了ANN模型那里,字符的類數(shù)有40多個(gè),而且訓(xùn)練數(shù)據(jù)有4000張左右。那時(shí)候的貼標(biāo)簽過程,真是不堪回首的回憶,來回移動(dòng)文件導(dǎo)致作者手經(jīng)常性的非常酸。后來我一度想找個(gè)實(shí)習(xí)生幫我做這些工作。但轉(zhuǎn)念一想,這些苦我都不愿承擔(dān),何苦還要那些小伙子承擔(dān)呢。“己所不欲,勿施于人”。算了,既然這是機(jī)器學(xué)習(xí)者的命,那就欣然接受吧。幸好在這段磨礪的時(shí)光,我逐漸掌握了一個(gè)方法,大幅度減少了我貼標(biāo)簽的時(shí)間與精力。不然,我可能還未開始寫這個(gè)系列的教程,就已經(jīng)累吐血了。開發(fā)EasyPR1.1版本時(shí),新增了一大批數(shù)據(jù),因此又有了貼標(biāo)簽的過程。幸好使用這個(gè)方法,使得相關(guān)時(shí)間大幅度減少。這個(gè)方法叫做 逐次迭代自動(dòng)標(biāo)簽法 。在后面會(huì)介紹這個(gè)方法。
貼標(biāo)簽后的車牌數(shù)據(jù)如下圖:
圖7 在HasPlate文件夾下的圖片
貼標(biāo)簽后的非車牌數(shù)據(jù)下圖:
圖8 在NoPlate文件夾下的圖片
擁有了貼好標(biāo)簽的數(shù)據(jù)以后,下面的步驟是分組,也稱之為divide過程。
3. divide(學(xué)習(xí)數(shù)據(jù)->分組數(shù)據(jù))
分組這個(gè)過程是EasyPR1.1版新引入的方法。
在貼完標(biāo)簽以后,我擁有了車牌圖片和非車牌圖片共幾千張。在我直接訓(xùn)練前,不急。先拿出30%的數(shù)據(jù),只用剩下的70%數(shù)據(jù)進(jìn)行SVM模型的訓(xùn)練,訓(xùn)練好的模型再用這30%數(shù)據(jù)進(jìn)行一個(gè)效果測(cè)試。這30%數(shù)據(jù)充當(dāng)?shù)淖饔镁褪且粋€(gè)評(píng)判數(shù)據(jù)測(cè)試集,稱之為test data,另70%數(shù)據(jù)稱之為train data。于是一個(gè)完整的learn data被分為了train data和test data。
圖9 數(shù)據(jù)分組過程
在EasyPR1.0版是沒有test data概念的,所有數(shù)據(jù)都輸入訓(xùn)練,然后直接在原始的數(shù)據(jù)上進(jìn)行測(cè)試。直接在原始的數(shù)據(jù)集上測(cè)試與單獨(dú)劃分出30%的數(shù)據(jù)測(cè)試效果究竟有多少不同?
事實(shí)上,我們訓(xùn)練出模型的根本目的是為了對(duì)未知的,新的數(shù)據(jù)進(jìn)行預(yù)測(cè)與判斷。
當(dāng)使用訓(xùn)練的數(shù)據(jù)進(jìn)行測(cè)試時(shí),由于模型已經(jīng)考慮到了訓(xùn)練數(shù)據(jù)的特征,因此很難將這個(gè)測(cè)試效果推廣到其他未知數(shù)據(jù)上。如果使用單獨(dú)的測(cè)試集進(jìn)行驗(yàn)證,由于測(cè)試數(shù)據(jù)集跟模型的生成沒有關(guān)聯(lián),因此可以很好的反映出模型推廣到其他場(chǎng)景下的效果。 這個(gè)過程就可以簡(jiǎn)單描述為你不可以拿你給學(xué)生的復(fù)習(xí)提綱卷去考學(xué)生,而是應(yīng)該出一份考察知識(shí)點(diǎn)一樣,但題目不一樣的卷子。前者的方式無法區(qū)分出真正學(xué)會(huì)的人和死記硬背的人,而后者就能有效地反映出哪些人才是真正“學(xué)會(huì)”的。
在divide的過程中,注意無論在train data和test data中都要保持?jǐn)?shù)據(jù)的標(biāo)簽,也就是說車牌數(shù)據(jù)仍然歸到HasPlate文件夾,非車牌數(shù)據(jù)歸到NoPlate文件夾。于是,車牌圖片30%歸到test data下面的hasplate文件夾,70%歸到train data下面的hasplate文件夾,非車牌圖片30%歸到test data下面的noplate文件夾,70%歸到train data下面的noplate文件夾。于是在文件夾train 和 test下面又有兩個(gè)子文件夾,他們的結(jié)構(gòu)樹就是下圖:
圖10 分組后的文件樹
divide數(shù)據(jù)結(jié)束以后,我們就可以進(jìn)入真正的機(jī)器學(xué)習(xí)過程。也就是對(duì)數(shù)據(jù)的訓(xùn)練過程。
4. train(訓(xùn)練數(shù)據(jù)->模型)
模型在代碼里的代表就是CvSVM類。在這一步中所要做的就是加載train data,然后用CvSVM類的train方法進(jìn)行訓(xùn)練。這個(gè)步驟只針對(duì)的是上步中生成的總數(shù)據(jù)70%的訓(xùn)練數(shù)據(jù)。
具體來說,分為以下幾個(gè)子步驟:
1) 加載待訓(xùn)練的車牌數(shù)據(jù)。見下面這段代碼。
void getPlate(Mat& trainingImages, vector<int>& trainingLabels) { char * filePath = "train/data/plate_detect_svm/HasPlate/HasPlate"; vector<string> files; getFiles(filePath, files ); int size = files.size(); if (0 == size) cout << "No File Found in train HasPlate!" << endl; for (int i = 0;i < size;i++) { cout << files[i].c_str() << endl; Mat img = imread(files[i].c_str()); img= img.reshape(1, 1); trainingImages.push_back(img); trainingLabels.push_back(1); } } View Code注意看,車牌圖像我存儲(chǔ)在的是一個(gè)vector<Mat>中,而標(biāo)簽數(shù)據(jù)我存儲(chǔ)在的是一個(gè)vector<int>中。我將train/HasPlate中的圖像依次取出來,存入vector<Mat>。每存入一個(gè)圖像,同時(shí)也往vector<int>中存入一個(gè)int值1,也就是說圖像和標(biāo)簽分別存在不同的vector對(duì)象里,但是保持一一對(duì)應(yīng)的關(guān)系。
2) 加載待訓(xùn)練的非車牌數(shù)據(jù),見下面這段代碼中的函數(shù)。基本內(nèi)容與加載車牌數(shù)據(jù)類似,不同之處在于文件夾是train/NoPlate,并且我往vector<int>中存入的是int值0,代表無車牌。
void getNoPlate(Mat& trainingImages, vector<int>& trainingLabels) { char * filePath = "train/data/plate_detect_svm/NoPlate/NoPlate"; vector<string> files; getFiles(filePath, files ); int size = files.size(); if (0 == size) cout << "No File Found in train NoPlate!" << endl; for (int i = 0;i < size;i++) { cout << files[i].c_str() << endl; Mat img = imread(files[i].c_str()); img= img.reshape(1, 1); trainingImages.push_back(img); trainingLabels.push_back(0); } } View Code3) 將兩者合并。目前擁有了兩個(gè)vector<Mat>和兩個(gè)vector<int>。將代表車牌圖片和非車牌圖片數(shù)據(jù)的兩個(gè)vector<Mat>組成一個(gè)新的Mat--trainingData,而代表車牌圖片與非車牌圖片標(biāo)簽的兩個(gè)vector<int>組成另一個(gè)Mat--classes。接著做一些數(shù)據(jù)類型的調(diào)整,以讓其符合svm訓(xùn)練函數(shù)train的要求。這些做完后,數(shù)據(jù)的準(zhǔn)備工作基本結(jié)束,下面就是參數(shù)配置的工作。
Mat classes;//(numPlates+numNoPlates, 1, CV_32FC1); Mat trainingData;//(numPlates+numNoPlates, imageWidth*imageHeight, CV_32FC1 ); Mat trainingImages; vector<int> trainingLabels; getPlate(trainingImages, trainingLabels); getNoPlate(trainingImages, trainingLabels); Mat(trainingImages).copyTo(trainingData); trainingData.convertTo(trainingData, CV_32FC1); Mat(trainingLabels).copyTo(classes); View Code4) 配置SVM模型的訓(xùn)練參數(shù)。SVM模型的訓(xùn)練需要一個(gè)CvSVMParams的對(duì)象,這個(gè)類是SVM模型中訓(xùn)練對(duì)象的參數(shù)的組合,如何給這里的參數(shù)賦值,是很有講究的一個(gè)工作。注意,這里是SVM訓(xùn)練的核心內(nèi)容,也是最能體現(xiàn)一個(gè)機(jī)器學(xué)習(xí)專家和新手區(qū)別的地方。機(jī)器學(xué)習(xí)最后模型的效果差異有很大因素取決與模型訓(xùn)練時(shí)的參數(shù),尤其是SVM,有非常多的參數(shù)供你配置(見下面的代碼)。參數(shù)眾多是一個(gè)問題,更為顯著的是,機(jī)器學(xué)習(xí)模型中參數(shù)的一點(diǎn)微調(diào)都可能帶來最終結(jié)果的巨大差異。
CvSVMParams SVM_params; SVM_params.svm_type = CvSVM::C_SVC; SVM_params.kernel_type = CvSVM::LINEAR; //CvSVM::LINEAR; SVM_params.degree = 0; SVM_params.gamma = 1; SVM_params.coef0 = 0; SVM_params.C = 1; SVM_params.nu = 0; SVM_params.p = 0; SVM_params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.01);opencv官網(wǎng)文檔對(duì)CvSVMParams類的各個(gè)參數(shù)有一個(gè)詳細(xì)的解釋。如果你上過SVM課程的理論部分,你可能對(duì)這些參數(shù)的意思能搞的明白。但在這里,我們可以不去管參數(shù)的含義,因?yàn)槲覀冇懈玫姆椒ㄈソ鉀Q這個(gè)問題。
圖11 SVM各參數(shù)的作用
這個(gè)原因在于:EasyPR1.0使用的是liner核,也稱之為線型核,因此degree和gamma還有coef0三個(gè)參數(shù)沒有作用。同時(shí),在這里SVM模型用作的問題是分類問題,那么nu和p兩個(gè)參數(shù)也沒有影響。最后唯一能影響的參數(shù)只有Cvalue。到了EasyPR1.1版本以后,默認(rèn)使用的是RBF核,因此需要調(diào)整的參數(shù)多了一個(gè)gamma。
以上參數(shù)的選擇都可以用自動(dòng)訓(xùn)練(train_auto)的方法去解決,在下面的SVM調(diào)優(yōu)部分會(huì)具體介紹train_auto。
5) 開始訓(xùn)練。OK!數(shù)據(jù)載入完畢,參數(shù)配置結(jié)束,一切準(zhǔn)備就緒,下面就是交給opencv的時(shí)間。我們只要將前面的 trainingData,classes,以及CvSVMParams的對(duì)象SVM_params交給CvSVM類的train函數(shù)就可以。另外,直接使用CvSVM的構(gòu)造函數(shù),也可以完成訓(xùn)練過程。例如下面這行代碼:
CvSVM svm(trainingData, classes, Mat(), Mat(), SVM_params);訓(xùn)練開始后,慢慢等一會(huì)。機(jī)器學(xué)習(xí)中數(shù)據(jù)訓(xùn)練的計(jì)算量往往是非常大的,即便現(xiàn)代計(jì)算機(jī)也要運(yùn)行很長時(shí)間。具體的時(shí)間取決于你訓(xùn)練的數(shù)據(jù)量的大小以及模型的復(fù)雜度。在我的2.0GHz的機(jī)器上,訓(xùn)練1000條數(shù)據(jù)的SVM模型的時(shí)間大約在1分鐘左右。
訓(xùn)練完成以后,我們就可以用CvSVM類的對(duì)象
s vm
去進(jìn)行預(yù)測(cè)了。如果我們僅僅需要這個(gè)模型,現(xiàn)在可以把它存到xml文件里,留待下次使用:
FileStorage fsTo("train/svm.xml", cv::FileStorage::WRITE);svm.write(*fsTo, "svm");5. test(測(cè)試數(shù)據(jù)->評(píng)判指標(biāo))
記得我們還有30%的測(cè)試數(shù)據(jù)了么?現(xiàn)在是使用它們的時(shí)候了。將這些數(shù)據(jù)以及它們的標(biāo)簽加載如內(nèi)存,這個(gè)過程與加載訓(xùn)練數(shù)據(jù)的過程是一樣的。接著使用我們訓(xùn)練好的SVM模型去判斷這些圖片。
下面的步驟是對(duì)我們的模型做指標(biāo)評(píng)判的過程。首先,測(cè)試數(shù)據(jù)是有標(biāo)簽的數(shù)據(jù),這意味著我們知道每張圖片是車牌還是不是車牌。另外,用新生成的svm模型對(duì)數(shù)據(jù)進(jìn)行判斷,也會(huì)生成一個(gè)標(biāo)簽,叫做“預(yù)測(cè)標(biāo)簽”。“預(yù)測(cè)標(biāo)簽”與“標(biāo)簽”一般是存在誤差的,這也就是模型的誤差。這種誤差有兩種情況:1.這副圖片是真的車牌,但是svm模型判斷它是“非車牌”;2.這幅圖片不是車牌,但svm模型判斷它是“車牌”。無疑,這兩種情況都屬于svm模型判斷失誤的情況。我們需要設(shè)計(jì)出來兩個(gè)指標(biāo),來分別評(píng)測(cè)這兩種失誤情況發(fā)生的概率。這兩個(gè)指標(biāo)就是下面要說的“準(zhǔn)確率”(precision)和“查全率”(recall)。
準(zhǔn)確率是統(tǒng)計(jì)在我已經(jīng)預(yù)測(cè)為車牌的圖片中,真正車牌數(shù)據(jù)所占的比例。假設(shè)我們用ptrue_rtrue表示預(yù)測(cè)(p)為車牌并且實(shí)際(r)為車牌的數(shù)量,而用ptrue_rfalse表示實(shí)際不為車牌的數(shù)量。
準(zhǔn)確率的計(jì)算公式是:
圖12 precise 準(zhǔn)確率
查全率是統(tǒng)計(jì)真正的車牌圖片中,我預(yù)測(cè)為車牌的圖片所占的比例。同上,我們用ptrue_rtrue表示預(yù)測(cè)與實(shí)際都為車牌的數(shù)量。用pfalse_rtrue表示實(shí)際為車牌,但我預(yù)測(cè)為非車牌的數(shù)量。
查全率的計(jì)算公式是:
圖13 recall 查全率
recall的公式與precision公式唯一的區(qū)別在于右下角。precision是ptrue_rfalse,代表預(yù)測(cè)為車牌但實(shí)際不是的數(shù)量;而recall是pfalse_rtrue,代表預(yù)測(cè)是非車牌但其實(shí)是車牌的數(shù)量。
簡(jiǎn)單來說,precision指標(biāo)的期望含義就是要“ 查的準(zhǔn) ”,recall的期望含義就是“ 不要漏 ”。查全率還有一個(gè)翻譯叫做“召回率”。但很明顯,召回這個(gè)詞沒有反映出查全率所體現(xiàn)出的不要漏的含義。
值得說明的是,precise和recall這兩個(gè)值自然是越高越好。但是如果一個(gè)高,一個(gè)低的話效果會(huì)如何,如何跟兩個(gè)都中等的情況進(jìn)行比較?為了能夠數(shù)字化這種比較。機(jī)器學(xué)習(xí)界又引入了FScore這個(gè)數(shù)值。當(dāng)precise和recall兩者中任一者較高,而另一者較低是,FScore都會(huì)較低。兩者中等的情況下Fscore表現(xiàn)比一高一低要好。當(dāng)兩者都很高時(shí),FScore會(huì)很高。
FScore的計(jì)算公式如下圖:
圖14 Fscore計(jì)算公式
模型測(cè)試以及評(píng)價(jià)指標(biāo)是EasyPR1.1中新增的功能。在 svm_train.cpp 的最下面可以看到這三個(gè)指標(biāo)的計(jì)算過程。
訓(xùn)練心得
通過以上5個(gè)步驟,我們就完成了模型的準(zhǔn)備,訓(xùn)練,測(cè)試的全部過程。下面,說一說過程中的幾點(diǎn)心得。
1. 完善EasyPR的plateLocate功能
在1.1版本中的EasyPR的車牌定位模塊仍然不夠完善。如果你的所有的圖片符合某種通用的模式,參照前面的車牌定位的幾篇教程,以及使用EasyPR新增的Debug模式,你可以將EasyPR的plateLocate模塊改造為適合你的情況。于是,你就可以利用EasyPR為你制造大量的學(xué)習(xí)數(shù)據(jù)。通過原始數(shù)據(jù)的輸入,然后通過plateLocate進(jìn)行定位,再使用EasyPR已有的車牌判斷模塊進(jìn)行圖片的分類,于是你就可以得到一個(gè)基本分好類的學(xué)習(xí)數(shù)據(jù)。下面所需要做的就是人工核對(duì),確認(rèn)一下,保證每張圖片的標(biāo)簽是正確的,然后再輸入模型進(jìn)行訓(xùn)練。
2. 使用“逐次迭代自動(dòng)標(biāo)簽法”。
上面討論的貼標(biāo)簽方法是在EasyPR已經(jīng)提供了一個(gè)訓(xùn)練好的模型的情況下。如果一開始手上任何模型都沒有,該怎么辦?假設(shè)目前手里有成千上萬個(gè)通過定位出來的各種候選車牌,手工一個(gè)個(gè)貼標(biāo)簽的話,豈不會(huì)讓人累吐血?在前文中說過,我在一開始貼標(biāo)簽過程中碰到了這個(gè)問題,在不斷被折磨與痛苦中,我發(fā)現(xiàn)了一個(gè)好方法,大幅度減輕了這整個(gè)工作的痛苦性。
當(dāng)然,這個(gè)方法很簡(jiǎn)單。我如果說出來你一定也不覺得有什么奇妙的。但是如果在你準(zhǔn)備對(duì)1000張圖片進(jìn)行手工貼標(biāo)簽時(shí),相信我,使用這個(gè)方法會(huì)讓你最后的時(shí)間節(jié)省一半。如果你需要雇10個(gè)人來貼標(biāo)簽的話,那么用了這個(gè)方法,可能你最后一個(gè)人都不用雇。
這個(gè)方法被我稱為“逐次迭代自動(dòng)標(biāo)簽法”。
方法核心很簡(jiǎn)單。就是假設(shè)你有3000張未分類的圖片。你從中選出1%,也就是30張出來,手工給它們每個(gè)圖片進(jìn)行分類工作。好的,如今你有了30張貼好標(biāo)簽的數(shù)據(jù)了,下步你把它直接輸入到SVM模型中訓(xùn)練,獲得了一個(gè)簡(jiǎn)單粗曠的模型。之后,你從圖片集中再取出3%的圖片,也就是90張, 然后用剛訓(xùn)練好的模型對(duì)這些圖片進(jìn)行預(yù)測(cè) ,根據(jù)預(yù)測(cè)結(jié)果將它們自動(dòng)分到hasplate和noplate文件夾下面。分完以后,你到這兩個(gè)文件夾下面,看看哪些是預(yù)測(cè)錯(cuò)的,把hasplate里預(yù)測(cè)錯(cuò)的 移動(dòng) 到noplate里,反之,把noplate里預(yù)測(cè)錯(cuò)的移動(dòng)到hasplate里。
接著,你把一開始手工分類好的那30張圖片,結(jié)合調(diào)整分類的90張圖片,總共120張圖片再輸入svm模型中進(jìn)行訓(xùn)練。于是你獲得一個(gè)比最開始粗曠模型更精準(zhǔn)點(diǎn)的模型。然后,你從3000張圖片中再取出6%的圖片來,用這個(gè)模型再對(duì)它們進(jìn)行預(yù)測(cè),分類....
以上反復(fù)。你每訓(xùn)練出一個(gè)新模型,用它來預(yù)測(cè)后面更多的數(shù)據(jù),然后自動(dòng)分類。這樣做最大的好處就是你只需要移動(dòng)那些被分類錯(cuò)誤的圖片。其他的圖片已經(jīng)被正 確的歸類了。注意,在整個(gè)過程中,你每次只需要對(duì)新拿出的數(shù)據(jù)進(jìn)行人工確認(rèn),因?yàn)榍懊娴臄?shù)據(jù)已經(jīng)分好類了。因此,你最好使用兩個(gè)文件夾,一個(gè)是已經(jīng)分好類 的數(shù)據(jù),另一個(gè)是自動(dòng)分類數(shù)據(jù),需要手工確認(rèn)的。這樣兩者不容易亂。
每次從未標(biāo)簽的原始數(shù)據(jù)庫中取出的數(shù)據(jù)不要多,最好不要超過上次數(shù)據(jù)的兩倍。這樣可以保證你的模型的準(zhǔn)確率穩(wěn)步上升。如果想一口吃個(gè)大胖子,例如用30張圖片訓(xùn)練出的模型,去預(yù)測(cè)1000張數(shù)據(jù),那最后結(jié)果跟你手工分類沒有任何區(qū)別了。
整個(gè)方法的原理很簡(jiǎn)單,就是 不斷迭代循環(huán)細(xì)化 的思想。跟軟件工程中迭代開發(fā)過程有異曲同工之妙。你只要理解了其原理,很容易就可以復(fù)用在任何其他機(jī)器學(xué)習(xí)模型的訓(xùn)練中,從而大幅度(或者部分)減輕機(jī)器學(xué)習(xí)過程中貼標(biāo)簽的巨大負(fù)擔(dān)。
回到一個(gè)核心問題,對(duì)于開發(fā)者而言,什么樣的方法才是自己實(shí)現(xiàn)一個(gè)svm.xml的最好方法。有以下幾種選擇。
1.你使用EasyPR提供的svm.xml,這個(gè)方式等同于你沒有訓(xùn)練,那么EasyPR識(shí)別的效率取決于你的環(huán)境與EasyPR的匹配度。運(yùn)氣好的話,這個(gè)效果也會(huì)不錯(cuò)。但如果你的環(huán)境下車牌跟EasyPR默認(rèn)的不一樣。那么可能就會(huì)有點(diǎn)問題。
2.使用EasyPR提供的訓(xùn)練數(shù)據(jù),例如train/data文件下的數(shù)據(jù),這樣生成的效果等同于第一步的,不過你可以調(diào)整參數(shù),試試看模型的表現(xiàn)會(huì)不會(huì)更好一點(diǎn)。
3.使用自己的數(shù)據(jù)進(jìn)行訓(xùn)練。這個(gè)方法的適應(yīng)性最好。首先你得準(zhǔn)備你原始的數(shù)據(jù),并且寫一個(gè)處理方法,能夠?qū)⒃紨?shù)據(jù)轉(zhuǎn)化為學(xué)習(xí)數(shù)據(jù)。下面你調(diào)用EasyPR的PlateLocate方法進(jìn)行處理,將候選車牌圖片從原圖片截取出來。你可以使用逐次迭代自動(dòng)標(biāo)簽思想,使用EasyPR已有的svm模型對(duì)這些候選圖片進(jìn)行預(yù)標(biāo)簽。然后再進(jìn)行肉眼確認(rèn)和手工調(diào)整,以生成標(biāo)準(zhǔn)的貼好標(biāo)簽的數(shù)據(jù)。后面的步驟就可以按照分組,訓(xùn)練,測(cè)試等過程順次走下去。如果你使用了EasyPR1.1版本,后面的這幾個(gè)過程已經(jīng)幫你實(shí)現(xiàn)好代碼了,你甚至可以直接在命令行選擇操作。
以上就是SVM模型訓(xùn)練的部分,通過這個(gè)步驟的學(xué)習(xí),你知道如何通過已有的數(shù)據(jù)去訓(xùn)練出一個(gè)自己的模型。下面的部分,是對(duì)這個(gè)訓(xùn)練過程的一個(gè)思考,討論通過何種方法可以改善我最后模型的效果。
三.SVM調(diào)優(yōu)
SVM調(diào)優(yōu)部分,是通過對(duì)SVM的原理進(jìn)行了解,并運(yùn)用機(jī)器學(xué)習(xí)的一些調(diào)優(yōu)策略進(jìn)行優(yōu)化的步驟。
在這個(gè)部分里,最好要懂一點(diǎn)機(jī)器學(xué)習(xí)的知識(shí)。同時(shí),本部分也會(huì)講的盡量通俗易懂,讓人不會(huì)有理解上的負(fù)擔(dān)。在EasyPR1.0版本中,SVM模型的代碼完全參考了mastering opencv書里的實(shí)現(xiàn)思路。從1.1版本開始,EasyPR對(duì)車牌判斷模塊進(jìn)行了優(yōu)化,使得模型最后的效果有了較大的改善。
具體說來,本部分主要包括如下幾個(gè)子部分:1.RBF核;2.參數(shù)調(diào)優(yōu);3.特征提取;4.接口函數(shù);5.自動(dòng)化。
下面分別對(duì)這幾個(gè)子部分展開介紹。
1.RBF核
SVM中最關(guān)鍵的技巧是核技巧。“核”其實(shí)是一個(gè)函數(shù),通過一些轉(zhuǎn)換規(guī)則把低維的數(shù)據(jù)映射為高維的數(shù)據(jù)。在機(jī)器學(xué)習(xí)里,數(shù)據(jù)跟向量是等同的意思。例如,一個(gè) [174, 72]表示人的身高與體重的數(shù)據(jù)就是一個(gè)兩維的向量。在這里,維度代表的是向量的長度。(務(wù)必要區(qū)分“維度”這個(gè)詞在不同語境下的含義,有的時(shí)候我們會(huì)說向量是一維的,矩陣是二維的,這種說法針對(duì)的是數(shù)據(jù)展開的層次。機(jī)器學(xué)習(xí)里講的維度代表的是向量的長度,與前者不同)
簡(jiǎn)單來說,低維空間到高維空間映射帶來的好處就是可以利用高維空間的線型切割模擬低維空間的非線性分類效果。也就是說,SVM模型其實(shí)只能做線型分類,但是在線型分類前,它可以通過核技巧把數(shù)據(jù)映射到高維,然后在高維空間進(jìn)行線型切割。高維空間的線型切割完后在低維空間中最后看到的效果就是劃出了一條復(fù)雜的分線型分類界限。 從這點(diǎn)來看,SVM并沒有完成真正的非線性分類,而是通過其它方式達(dá)到了類似目的,可謂“曲徑通幽”。
SVM模型總共可以支持多少種核呢。根據(jù)官方文檔,支持的核類型有以下幾種:
liner核和rbf核是所有核中應(yīng)用最廣泛的。
liner核,雖然名稱帶核,但它其實(shí)是無核模型,也就是沒有使用核函數(shù)對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換。因此,它的分類效果僅僅比邏輯回歸好一點(diǎn)。在EasyPR1.0版中,我們的SVM模型應(yīng)用的是liner核。我們用的是圖像的全部像素作為特征。
rbf核,會(huì)將輸入數(shù)據(jù)的特征維數(shù)進(jìn)行一個(gè)維度轉(zhuǎn)換,具體會(huì)轉(zhuǎn)換為多少維?這個(gè)等于你輸入的訓(xùn)練量。假設(shè)你有500張圖片,rbf核會(huì)把每張圖片的數(shù)據(jù)轉(zhuǎn) 換為500維的。如果你有1000張圖片,rbf核會(huì)把每幅圖片的特征轉(zhuǎn)到1000維。這么說來,隨著你輸入訓(xùn)練數(shù)據(jù)量的增長,數(shù)據(jù)的維數(shù)越多。更方便在高維空間下的分類效果,因此最后模型效果表現(xiàn)較好。
既然選擇SVM作為模型,而且SVM中核心的關(guān)鍵技巧是核函數(shù),那么理應(yīng)使用帶核的函數(shù)模型,充分利用數(shù)據(jù)高維化的好處,利用高維的線型分類帶來低維空間下的非線性分類效果。但是,rbf核的使用是需要條件的。
當(dāng)你的數(shù)據(jù)量很大,但是每個(gè)數(shù)據(jù)量的維度一般時(shí),才適合用rbf核。相反,當(dāng)你的數(shù)據(jù)量不多,但是每個(gè)數(shù)據(jù)量的維數(shù)都很大時(shí),適合用線型核。
在EasyPR1.0版中,我們用的是圖像的全部像素作為特征,那么根據(jù)車牌圖像的136×36的大小來看的話,就是4896維的數(shù)據(jù),再加上我們輸入的 是彩色圖像,也就是說有R,G,B三個(gè)通道,那么數(shù)量還要乘以3,也就是14688個(gè)維度。這是一個(gè)非常龐大的數(shù)據(jù)量,你可以把每幅圖片的數(shù)據(jù)理解為長度 為14688的向量。這個(gè)時(shí)候,每個(gè)數(shù)據(jù)的維度很大,而數(shù)據(jù)的總數(shù)很少,如果用rbf核的話,相反效果反而不如無核。
在EasyPR1.1版本時(shí),輸入訓(xùn)練的數(shù)據(jù)有3000張圖片,每個(gè)數(shù)據(jù)的特征改用直方統(tǒng)計(jì),共有172個(gè)維度。這個(gè)場(chǎng)景下,如果用rbf核的話,就會(huì)將每個(gè)數(shù)據(jù)的維度轉(zhuǎn)化為與數(shù)據(jù)總數(shù)一樣的數(shù)量,也就是3000的維度,可以充分利用數(shù)據(jù)高維化后的好處。
因此可以看出,為了讓EasyPR新版使用rbf核技巧,我們給訓(xùn)練數(shù)據(jù)做了增加,擴(kuò)充了兩倍的數(shù)據(jù),同時(shí),減小了每個(gè)數(shù)據(jù)的維度。以此滿足了rbf核的使用條件。通過使用rbf核來訓(xùn)練,充分發(fā)揮了非線性模型分類的優(yōu)勢(shì),因此帶來了較好的分類效果。
但是,使用rbf核也有一個(gè)問題,那就是參數(shù)設(shè)置的問題。在rbf訓(xùn)練的過程中,參數(shù)的選擇會(huì)顯著的影響最后rbf核訓(xùn)練出模型的效果。因此必須對(duì)參數(shù)進(jìn)行最優(yōu)選擇。
2.參數(shù)調(diào)優(yōu)
傳統(tǒng)的參數(shù)調(diào)優(yōu)方法是人手完成的。機(jī)器學(xué)習(xí)工程師觀察訓(xùn)練出的模型與參數(shù)的對(duì)應(yīng)關(guān)系,不斷調(diào)整,尋找最優(yōu)的參數(shù)。由于機(jī)器學(xué)習(xí)工程師大部分時(shí)間在調(diào)整模型的參數(shù),也有了“ 機(jī)器學(xué)習(xí)就是調(diào)參 ”這個(gè)說法。
幸好,opencv的svm方法中提供了一個(gè)自動(dòng)訓(xùn)練的方法。也就是由opencv幫你,不斷改變參數(shù),訓(xùn)練模型,測(cè)試模型,最后選擇模型效果最好的那些參數(shù)。整個(gè)過程是全自動(dòng)的,完全不需要你參與,你只需要輸入你需要調(diào)整參數(shù)的參數(shù)類型,以及每次參數(shù)調(diào)整的步長即可。
現(xiàn)在有個(gè)問題,如何驗(yàn)證svm參數(shù)的效果?你可能會(huì)說,使用訓(xùn)練集以外的那30%測(cè)試集啊。但事實(shí)上,機(jī)器學(xué)習(xí)模型中專門有一個(gè)數(shù)據(jù)集,是用來驗(yàn)證參數(shù)效果的。也就是 交叉驗(yàn)證集(cross validation set,簡(jiǎn)稱validate data) ? 這個(gè)概念。
validate data 就是專門從train data中取出一部分?jǐn)?shù)據(jù),用這部分?jǐn)?shù)據(jù)來驗(yàn)證參數(shù)調(diào)整的效果。比方說現(xiàn)在有70%的訓(xùn)練數(shù)據(jù),從中取出20%的數(shù)據(jù),剩下50%數(shù)據(jù)用來訓(xùn)練,再用訓(xùn)練出來的模型在20%數(shù)據(jù)上進(jìn)行測(cè)試。這20%的數(shù)據(jù)就叫做 validate data 。真正拿來訓(xùn)練的數(shù)據(jù)僅僅只是50%的數(shù)據(jù)。
正如上面把數(shù)據(jù)劃分為test data和train data的理由一樣。為了驗(yàn)證參數(shù)在新數(shù)據(jù)上的推廣性,我們不能用一個(gè)訓(xùn)練數(shù)據(jù)集,所以我們要把訓(xùn)練數(shù)據(jù)集再細(xì)分為train data和 validate data 。在train data上訓(xùn)練,然后在 validate data 上測(cè)試參數(shù)的效果。所以說,在一個(gè)更一般的機(jī)器學(xué)習(xí)場(chǎng)景中,機(jī)器學(xué)習(xí)工程師會(huì)把數(shù)據(jù)分為train data, validate data ,以及test data。在train data上訓(xùn)練模型,用 validate data 測(cè)試參數(shù),最后用test data測(cè)試模型和參數(shù)的整體表現(xiàn)。
說了這么多,那么,大家可能要問,是不是還缺少一個(gè)數(shù)據(jù)集,需要再劃分出來一個(gè) validate data吧。但是答案是No。opencv的train_auto函數(shù)幫你完成了所有工作,你只需要告訴它,你需要?jiǎng)澐侄嗌賯€(gè)子分組,以及 validate data所占的比例。然后train_auto函數(shù)會(huì)自動(dòng)幫你從你輸入的train data中劃分出一部分的
validate data
,然后自動(dòng)測(cè)試,選擇表現(xiàn)效果最好的參數(shù)。
感謝train_auto函數(shù)!既幫我們劃分了參數(shù)驗(yàn)證的數(shù)據(jù)集,還幫我們一步步調(diào)整參數(shù),最后選擇效果最好的那個(gè)參數(shù),可謂是節(jié)省了調(diào)優(yōu)過程中80%的工作。
train_auto函數(shù)的調(diào)用代碼如下:
svm.train_auto(trainingData, classes, Mat(), Mat(), SVM_params, 10, CvSVM::get_default_grid(CvSVM::C), CvSVM::get_default_grid(CvSVM::GAMMA), CvSVM::get_default_grid(CvSVM::P), CvSVM::get_default_grid(CvSVM::NU), CvSVM::get_default_grid(CvSVM::COEF), CvSVM::get_default_grid(CvSVM::DEGREE), true);你唯一需要做的就是泡杯茶,翻翻書,然后慢慢等待這計(jì)算機(jī)幫你處理好所有事情(時(shí)間較長,因?yàn)槊看握{(diào)整參數(shù)又得重新訓(xùn)練一次)。作者最近的一次訓(xùn)練的耗時(shí)為1個(gè)半小時(shí))。
訓(xùn)練完畢后,看看模型和參數(shù)在test data上的表現(xiàn)把。99%的precise和98%的recall。非常棒,比任何一次手工配的效果都好。
3.特征提取
在rbf核介紹時(shí)提到過,輸入數(shù)據(jù)的特征的維度現(xiàn)在是172,那么這個(gè)數(shù)字是如何計(jì)算出來的?現(xiàn)在的特征用的是直方統(tǒng)計(jì)函數(shù),也就是先把圖像二值化,然后統(tǒng)計(jì)圖像中一行元素中1的數(shù)目,由于輸入圖像有36行,因此有36個(gè)值,再統(tǒng)計(jì)圖像中每一列中1的數(shù)目,圖像有136列,因此有136個(gè)值,兩者相加正好等于172。新的輸入數(shù)據(jù)的特征提取函數(shù)就是下面的代碼:
// ! EasyPR的getFeatures回調(diào)函數(shù) // !本函數(shù)是獲取垂直和水平的直方圖圖值 void getHistogramFeatures(const Mat& image, Mat& features) { Mat grayImage; cvtColor(image, grayImage, CV_RGB2GRAY); Mat img_threshold; threshold(grayImage, img_threshold, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY); features = getTheFeatures(img_threshold); }我們輸入數(shù)據(jù)的特征不再是全部的三原色的像素值了,而是抽取過的一些特征。從原始的圖像到抽取后的特征的過程就被稱為特征提取的過程。在1.0版中沒有特征提取的概念,是直接把圖像中全部像素作為特征的。這要感謝群里的“如果有一天”同學(xué),他堅(jiān)持認(rèn)為全部像素的輸入是最低級(jí)的做法,認(rèn)為用特征提取后的效果會(huì)好多。我問大概能到多少準(zhǔn)確率,當(dāng)時(shí)的準(zhǔn)確率有92%,我以為已經(jīng)很高了,結(jié)果他說能到99%。在半信半疑中我嘗試了,果真如他所說,結(jié)合了rbf核與新特征訓(xùn)練的模型達(dá)到的precise在99%左右,而且recall也有98%,這真是個(gè)令人咋舌并且非常驚喜的成績(jī)。
“如果有一天”建議用的是SFIT特征提取或者HOG特征提取,由于時(shí)間原因,這兩者我沒有實(shí)現(xiàn),但是把函數(shù)留在了那里。留待以后有時(shí)間完成。在這個(gè)過程中,我充分體會(huì)到了開源的力量,如果不是把軟件開源,如果不是有這么多優(yōu)秀的大家一起討論,這樣的思路與改善是不可能出現(xiàn)的。
4.接口函數(shù)
由于有SIFT以及HOG等特征沒有實(shí)現(xiàn),而且未來有可能會(huì)有更多有效的特征函數(shù)出現(xiàn)。因此我把特征函數(shù)抽象為借口。使用回調(diào)函數(shù)的思路實(shí)現(xiàn)。所有回調(diào)函數(shù)的代碼都在feature.cpp中,開發(fā)者可以實(shí)現(xiàn)自己的回調(diào)函數(shù),并把它賦值給EasyPR中的某個(gè)函數(shù)指針,從而實(shí)現(xiàn)自定義的特征提取。也許你們會(huì)有更多更好的特征的想法與創(chuàng)意。
關(guān)于特征其實(shí)有更多的思考,原始的SVM模型的輸入是圖像的全部像素,正如人類在小時(shí)候通過圖像識(shí)別各種事物的過程。后來SVM模型的輸入是經(jīng)過抽取的特 征。正如隨著人類接觸的事物越來越多,會(huì)發(fā)現(xiàn)單憑圖像越來越難區(qū)分一些非常相似的東西,于是學(xué)會(huì)了總結(jié)特征。例如太陽就是圓的,黃色,在天空等,可以憑借 這些特征就進(jìn)行區(qū)分和判斷。
從本質(zhì)上說,特征是區(qū)分事物的關(guān)鍵特性。這些特性,一定是從某些維度去看待的。例如,蘋果和梨子,一個(gè)是綠色,一個(gè)是黃色,這就是顏色的維度;魚和鳥,一個(gè)在水里,一個(gè)在空中,這是位置的區(qū)分,也就是空間的維度。 特征,是許多維度中最有區(qū)分意義的維度。 傳統(tǒng)數(shù)據(jù)倉庫中的OLAP,也稱為多維分析,提供了人類從多個(gè)維度觀察,比較的能力。通過人類的觀察比較,從多個(gè)維度中挑選出來的維度,就是要分析目標(biāo)的特征。從這點(diǎn)來看,機(jī)器學(xué)習(xí)與多維分析有了關(guān)聯(lián)。多維分析提供了選擇特征的能力。而機(jī)器學(xué)習(xí)可以根據(jù)這些特征進(jìn)行建模。
機(jī)器學(xué)習(xí)界也有很多算法,專門是用來從數(shù)據(jù)中抽取特征信息的。例如傳統(tǒng)的PCA(主成分分析)算法以及最近流行的深度學(xué)習(xí)中的 AutoEncoder(自動(dòng)編碼機(jī))技術(shù)。這些算法的主要功能就是在數(shù)據(jù)中學(xué)習(xí)出最能夠明顯區(qū)分?jǐn)?shù)據(jù)的特征,從而提升后續(xù)的機(jī)器學(xué)習(xí)分類算法的效果。
說一個(gè)特征學(xué)習(xí)的案例。作者買車時(shí),經(jīng)常會(huì)把大眾的兩款車--邁騰與帕薩特給弄混,因?yàn)閮烧邔?shí)在太像了。大家可以到網(wǎng)上去搜一下這兩車的圖片。如果不依賴后排的文字,光靠外形實(shí)在難以將兩車區(qū)分開來(雖然從生產(chǎn)商來說,前者是一汽大眾生產(chǎn)的,產(chǎn)地在長春,后者是上海大眾生產(chǎn)的,產(chǎn)地在上海。兩個(gè)不同的公司,南北兩個(gè)地方,相差了十萬八千里)。后來我通過仔細(xì)觀察,終于發(fā)現(xiàn)了一個(gè)明顯區(qū)分兩輛車的特征,后來我再也沒有認(rèn)錯(cuò)過。這個(gè)特征就是:邁騰的前臉有四條銀杠,而帕薩特只有三條,邁騰比帕薩特多一條銀杠。可以這么說,就是這么一條銀杠,分割了北和南兩個(gè)地方生產(chǎn)的汽車。
圖15 一條銀杠,分割了“北”和“南”
在這里區(qū)分的過程,我是通過不斷學(xué)習(xí)與研究才發(fā)現(xiàn)了這些區(qū)分的特征,這充分說明了事物的特征也是可以被學(xué)習(xí)的。如果讓機(jī)器學(xué)習(xí)中的特征選擇方法PCA和AutoEncoder來分析的話,按理來說它們也應(yīng)該找出這條銀杠,否則它們就無法做到對(duì)這兩類最有效的分類與判斷。如果沒有找到的話,證明我們目前的特征選擇算法還有很多的改進(jìn)空間(與這個(gè)案例類似的還有大眾的另兩款車,高爾夫和Polo。它們兩的區(qū)分也是用同樣的道理。相比邁騰和帕薩特,高爾夫和Polo價(jià)格差別的更大,所以區(qū)分的特征也更有價(jià)值)。
5.自動(dòng)化
最后我想簡(jiǎn)單談一下EasyPR1.1新增的自動(dòng)化訓(xùn)練功能與命令行。大家可能看到第二部分介紹SVM訓(xùn)練時(shí)我將過程分成了5個(gè)步驟。事實(shí)上,這些步驟中的很多過程是可以模塊化的。一開始的時(shí)候我寫一些不相關(guān)的代碼函數(shù),幫我處理各種需要解決的問題,例如數(shù)據(jù)的分組,打標(biāo)簽等等。但后來,我把思路理清后,我覺得這幾個(gè)步驟中很多的代碼都可以通用。于是我把一些步驟模塊化出來,形成通用的函數(shù),并寫了一個(gè)命令行界面去調(diào)用它們。在你運(yùn)行EasyPR1.1版后,在你看到的第一個(gè)命令行界面選擇“3.SVM訓(xùn)練過程”,你就可以看到這些全部的命令。
圖16 svm訓(xùn)練命令行
這里的命令主要有6個(gè)部分。第一個(gè)部分是最可能需要修改代碼的地方,因?yàn)槊總€(gè)人的原始數(shù)據(jù)(raw data)都是不一樣的,因此你需要在data_prepare.cpp中找到這個(gè)函數(shù),改寫成適應(yīng)你格式的代碼。接下來的第二個(gè)部分以后的功能基本都可以復(fù)用。例如自動(dòng)貼標(biāo)簽(注意貼完以后要人工核對(duì)一下)。
第三個(gè)到第六部分功能類似。如果你的數(shù)據(jù)還沒分組,那么你執(zhí)行3以后,系統(tǒng)自動(dòng)幫你分組,然后訓(xùn)練,再測(cè)試驗(yàn)證。第四個(gè)命令行省略了分組過程。第五個(gè)命令行部分省略訓(xùn)練過程。第六個(gè)命令行省略了前面所有過程,只做最后模型的測(cè)試部分。
讓我們回顧一下SVM調(diào)優(yōu)的五個(gè)思路。第一部分是rbf核,也就是模型選擇層次,根據(jù)你的實(shí)際環(huán)境選擇最合適的模型。第二部分是參數(shù)調(diào)優(yōu),也就是參數(shù)優(yōu)化層次,這部分的參數(shù)最好通過一個(gè)驗(yàn)證集來確認(rèn),也可以使用opencv自帶的train_auto函數(shù)。第三部分是特征抽取部分,也就是特征甄選們,要能選擇出最能反映數(shù)據(jù)本質(zhì)區(qū)別的特征來。在這方面,pca以及深度學(xué)習(xí)技術(shù)中的autoencoder可能都會(huì)有所幫助。第四部分是通用接口部分,為了給優(yōu)化留下空間,需要抽象出接口,方便后續(xù)的改進(jìn)與對(duì)比。第五部分是自動(dòng)化部分,為了節(jié)省時(shí)間,將大量可以自動(dòng)化處理的功能模塊化出來,然后提供一些方便的操作界面。前三部分是從機(jī)器學(xué)習(xí)的效果來提高,后兩部分是從軟件工程的層面去優(yōu)化。
總結(jié)起來,就是 模型,參數(shù),特征,接口,模塊 五個(gè)層面。通過這五個(gè)層面,可以有效的提高機(jī)器學(xué)習(xí)模型訓(xùn)練的效果與速度,從而降低機(jī)器學(xué)習(xí)工程實(shí)施的難度與提升相關(guān)的效率。當(dāng)需要對(duì)機(jī)器學(xué)習(xí)模型進(jìn)行調(diào)優(yōu)的時(shí)候,我們可以從這五個(gè)層面去考慮。
后記
講到這里,本次的SVM開發(fā)詳解也算是結(jié)束了。相信通過這篇文檔,以及作者的一些心得,會(huì)對(duì)你在SVM模型的開發(fā)上面帶來一些幫助。下面的工作可以考慮把這些相關(guān)的方法與思路運(yùn)用到其他領(lǐng)域,或著改善EasyPR目前已有的模型與算法。如果你找出了比目前更好實(shí)現(xiàn)的思路,并且你愿意跟我們分享,那我們是非常歡迎的。
EasyPR1.1的版本發(fā)生了較大的變化。我為了讓它擁有多人協(xié)作,眾包開發(fā)的能力,想過很多辦法。最后決定引入了GDTS(General Data Test Set,通用測(cè)試數(shù)據(jù)集,也就是新的image/general_test下的眾多車牌圖片)以及GDSL(General Data Share License,通用數(shù)據(jù)分享協(xié)議,image/GDSL.txt)。這些概念與協(xié)議的引入非常重要,可能會(huì)改變目前車牌識(shí)別與機(jī)器學(xué)習(xí)在國內(nèi)學(xué)習(xí)研究的格局。在下期的EasyPR開發(fā)詳解中我會(huì)重點(diǎn)介紹1.1版的新加入功能以及這兩個(gè)概念和背后的思想,歡迎繼續(xù)閱讀。
上一篇還是第四篇,為什么本期SVM開發(fā)詳解屬于EasyPR開發(fā)的第六篇?事實(shí)上,對(duì)于目前的車牌定位模塊我們團(tuán)隊(duì)覺得還有改進(jìn)空間,所以第五篇的詳解內(nèi)容是留給改進(jìn)后的車牌定位模塊的。如果有車牌定位模塊方面好的建議或者希望加入開源團(tuán)隊(duì),歡迎跟我們團(tuán)隊(duì)聯(lián)系(easypr_dev@163.com )。您也可以為中國的開源事業(yè)做出一份貢獻(xiàn)。
版權(quán)說明:
本文中的所有文字,圖片,代碼的版權(quán)都是屬于作者和博客園共同所有。歡迎轉(zhuǎn)載,但是務(wù)必注明作者與出處。任何未經(jīng)允許的剽竊以及爬蟲抓取都屬于侵權(quán),作者和博客園保留所有權(quán)利。
總結(jié)
- 上一篇: Mobileye采用单目摄像头做ADAS
- 下一篇: Google I/O大会,炫酷产品汇总