沒錯,kNN演算法也要玩人臉識別
kNN(K-nearst neighbor)作為機器學習入門演算法,簡單直接易於理解;本文嘗試使用kNN演算法,做一個簡單的人臉識別程序.要將一張張人臉圖片,處理成kNN演算法能夠計算的向量,還需要使用其他的工具對圖片進行特徵提取,這裡使用開源工具openFace(基於Torch的神經網路模型),來提人臉特徵點.基本思路就是:1.神經網路處理圖片,提取特徵點 2.機器學習演算法訓練分類器分類,識別。最後用87張人臉圖片測試,accuracy達到95.31%!
一、kNN演算法
kNN演算法很好理解,如下圖首先存在一個帶標籤的樣本空間,現在要預測綠色圓球是屬於哪一類(方塊還是三角?)分下面幾步走:
1.遍歷全局,計算預測樣本與其他每一個樣本點的距離,按照由近到遠排序;常用距離有 曼哈頓距離、歐式距離和閔可夫斯基距離
2.需要預先定義一個超參數參數K值,K是一個大於1的整數,表示納入投票決策的樣本數目
3.統計離預測樣本最近的K個樣本,統計各個分類數目,返回數量最多的類的標籤作為預測值
下圖如果K=3,則離綠球最近的三個樣本中,三角形最多(2個),這時預測標籤會返回三角形,如果K=5,那麼正方形有3個,這時結果就變成了正方形。K值的選取,需要對業務的理解,多嘗試,採用交叉驗證的方式確定。
二、openFace
cmusatyalab/openface
1.找到圖片中的人臉,也就是人臉檢測;將圖像分割成一些16×16像素的小方塊。在每個小方塊中,我們將計算出每個主方向上有多少個梯度(有多少指向上,指向右上,指向右等)。然後我們將用指向性最強那個方向的箭頭來代替原來的那個小方塊,這樣一張圖片就轉換成簡單形式:
2. 估計面部的特徵點
openFace使用機器學習庫dlib的shape_predictor_68_face_landmarks,進行特徵點的估計和人臉的對正,這在後文的代碼中會寫到。
3.用一個卷積神經網路提出人臉的特徵值
卷積神經網路在這裡不是用來去識別人臉的,干識別人臉的活交給我們的主角kNN,高大上的神經網路這次淪為打工仔,乾的活就是將一張人臉提取出128位特徵點(取值範圍為0到4),這樣對人臉的識別工作就只需要處理128位的數字,大大降低了計算量,看吧,神經網路即使淪為配角,也是這麼光彩奪目~~~。至於為什麼是128位而不是其他?下面是Florian Schroff大神做了試驗的,128位在留出的100萬張圖片的測試集中,準確度最高。有興趣可以拜讀論文(牆裂推薦,下載地址都為你準備好了,不點一下嗎?):
FaceNet: A Unified Embedding for Face Recognition and Clustering卷積神經網路提取完圖片上人臉的128位特徵值embedding,就可以打卡下班,後面流程就交給主角kNN。
三、代碼
嗶嗶那麼久,早就不耐煩,Talk is cheap,show me the code!
首先得參照官網教程安裝好openCV,openFace,及import用到的package。還有Torch框架,用來載入神經網路。
1.先用openFace寫一些特徵點提取工具吧:
對於卷積神經網路的權重模型,openFace的作者已經為我們預先用大量的數據集照片訓練好了,用這個nn4.small2.v1.t7預訓練模型就好啦,不是我瞎推薦,看下面的理由,這個權重模型在LFW數據集上有92.92%的良好表現,如果不好使,你來找我也沒用^_^!。代碼中的文件路徑請根據自己實際情況修改。
創建一個forModelTest.py文件:
# coding=utf-8import numpy as npimport openfaceimport osimport cv2import pandas as pd# dlib的人臉位置提取模型的路徑dlibFacePredictor = /opt/openface/models/dlib/shape_predictor_68_face_landmarks.dat networkModel = /opt/openface/models/openface/nn4.small2.v1.t7 align = openface.AlignDlib(dlibFacePredictor)net = openface.TorchNeuralNet(networkModel, 96) # 神經網路輸入圖片尺寸96*96cwd = os.getcwd()# 這是提取訓練集數據和標籤,也就是基礎集的特徵點,然後用pandas處理保存到一個csv文件中,#以備用查看,需要為每一個人創建一個文件夾,文件夾裡面放上同一個人的圖片,我的圖片樣本首先都按照#照規則命名好了,因此可以直接將圖片名稱的字元串,split後提取出標籤,在此說明。def baseImageRep(imagesFileName): imagesFileName = imagesFileName personFileNameDir = os.path.join(cwd, imagesFileName) personFileName = os.listdir(personFileNameDir) labels = [] dataSet = [] for person in personFileName: personDir = os.path.join(personFileNameDir,person) imagesList = os.listdir(personDir) for image in imagesList: try: imagePath = os.path.join(personDir, image) # 目標圖片的絕對路徑 img = cv2.imread(imagePath) if img is None: img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if img is None: print img is None:%s % image bb = align.getLargestFaceBoundingBox(img) alignedFace = align.align(96,img, bb, landmarkIndices=openface.AlignDlib.OUTER_EYES_AND_NOSE) rep = net.forward(alignedFace) # 提取目標文件的128位特徵值 dataSet.append(rep) label = image.split(_)[0] labels.append(label) except Exception as e: print image print 提取人物的特徵點:, person assert len(dataSet) == len(labels) # 樣本數和標籤數要相等 dataSetDf = pd.DataFrame(dataSet,columns=map(lambda x: 特徵值_%d% x, range(1,129))) labelsDf = pd.DataFrame(labels,columns=[lables]) dataAllDf = pd.concat([labelsDf,dataSetDf], axis=1) print 正在生成基礎集特徵點文件 repBaseData.csv dataAllDf.to_csv(repBaseData.csv) print 文件保存完成 repBaseData.csv dataSet = np.array(dataSet) # 轉化為numpy數組???需要嗎???當然需要,這裡坑了一下,kNN遍歷計算距離時需要用到numpy array # labels = np.array(labels) # labels沒有進行計算,可以不轉化為numpy array,直接return 一個list return dataSet, labels# 這是提取測試集特徵值def testImagesRep(testImages): testImageFilePath = os.path.join(cwd, testImages) imagesList = os.listdir(testImageFilePath) dataTest = [] labelsTest = [] for image in imagesList: imagePath = os.path.join(testImageFilePath, image) # 目標圖片的絕對路徑 try: img = cv2.imread(imagePath) if img is None: img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if img is None: print img is None:%s % image bb = align.getLargestFaceBoundingBox(img) alignedFace = align.align(96, img, bb, landmarkIndices=openface.AlignDlib.OUTER_EYES_AND_NOSE) rep = net.forward(alignedFace) # 提取目標文件的128位特徵值 dataTest.append(rep) labelTest = image.split(_)[0] labelsTest.append(labelTest) except Exception as e: print image finally: print 提取測試集中人物特徵點, imagePath assert len(dataTest)==len(labelsTest) # 斷言樣本數和labels數量相等 datasSetTestDf = pd.DataFrame(dataTest, columns=map(lambda x: 特徵值_%d % x, range(1, 129))) labelsTestDf = pd.DataFrame(labelsTest, columns=[lables]) dataAllTestDf = pd.concat([labelsTestDf, datasSetTestDf], axis=1) print 正在生成測試樣本集的特徵值 repTestData.csv dataAllTestDf.to_csv(repTestData.csv) print 文件完成保存 repTestData.csv dataTest=np.array(dataTest) return dataTest, labelsTest
接下來用numpy寫一個kNN分類:
#k近鄰分類器def kNNClassify(inX, dataSet, labels, k=3): :param inX: 測試的樣本128位特徵值 :param dataSet: 帶標籤基礎集數據,128列的numpy數組 :param labels: 基礎集的標籤,numpy array,List均可,只取數,沒計算,不影響 :param k: 就是著名的K了,自己根據每個人的樣本數量選擇吧 :return: 預測標籤label disTance = map(sum, np.power(dataSet-inX, 2)) # 計算歐幾里得距離 data = np.vstack((disTance, labels)) # 將數據和標籤整合 dataT = data.T dataT = dataT.tolist() for i in range(len(dataT)): dataT[i][0] = float(dataT[i][0]) dataT.sort() count = dict() for i in range(k): if dataT[i][1] not in count: count[dataT[i][1]] = 1 else: count[dataT[i][1]] +=1 # 根據字典的value值,對字典進行排序,可以有這3種方法: # res = sorted(count.items(),key=lambda x:x[1],reverse=True) # 方法1 # res=sorted(count.items(), key=operator.itemgetter(1),reverse=True) # 方法2 res =zip(count.values(), count.keys()) res = sorted(res, reverse=True) label = res[0][1] # 取距離最近的一個tuple,第二個元素就是這個樣本的標籤 return label
新建一個main.py文件
# coding=utf-8import openfaceimport cv2from forModelTest import *#我將基礎集的人臉防在同目錄的BaseImages裡面,測試集的放在testImages裡面,測試集裡面#的圖片就不需要按照文件夾放了,就是做識別用的嘛。def kNNmodelTest(baseData, Baselabels, testData, y_true): labelsPredict = [] for (index, testSample) in enumerate(testData): y_hat = kNNClassify(testSample, baseData, Baselabels,k=3) # kNN演算法預測的標籤 labelsPredict.append(y_hat) if y_hat==y_true[index]: flag=正確 else: flag = sorry,錯誤--! print 測試輸入為, y_true[index],------>,預測結果:, y_hat, ,flag resCompare = [True if labelsPredict[i] == y_true[i] else False for i in range(len(y_true))] # 比較真偽 accuracy = sum(resCompare)*100/float(len(y_true)) # 預測的準確率 print K近鄰演算法分類器 accuracy={:.2f}%.format(accuracy) return Noneif __name__ == __main__: dataSet, labels = baseImageRep(BaseImages) # 提取基礎集的特徵點和標籤 dataTest, labelsTest = testImagesRep(testImages) # 提取測試集的特徵點和標籤 kNNmodelTest(baseData=dataSet, Baselabels=labels, testData=dataTest, y_true=labelsTest) # 開始測試
運行main.py看看識別效果吧:
由於用的是真人的頭像,名字就都遮擋處理,先需要對基礎樣本集的特徵點和標籤(姓
名)進行提取,建立樣本空間。
然後,提取測試的人臉圖片的特徵點和標籤。
最後,將測試圖片的128位特徵向量,傳入kNN演算法中,計算返回預測的label,再與正確的label比較,最終計算出87張測試人臉照片的識別accuracy=95.31%。效果還不錯哦!
四、一些思考和擴展
1.kNN演算法有哪些優點和不足?
優點是簡單有效,對異常點不敏感,準確度高,有新的樣本集加入時,不需要重新訓練模型,比較適用於樣本容量比較大的類域的自動分類;
缺點是,每次都要對計算樣本與空間中所有值的距離,類別評分不是規格化的(不像概率評分,無法給出數據的基礎結構信息,還有一個致命缺點是當樣本不平衡時,如一個類的樣本容量很大,而其他類樣本容量很小時,有可能導致當輸入一個新樣本時,該樣本的K個鄰居中大容量類的樣本占多數,可以採用權值分配的方法(和該樣本距離小的鄰居權值大)來改進,超參數K值如何選擇?比如我K值取3時,準確度為95.31%,而K值取5時accuracy降到了89.06%。
2. 可以用其他機器學習演算法代替kNN嗎?
當然了,流程框架是 卷積神經網路提出人臉特徵點+訓練機器學習演算法分類器+用分類器分類,當然可以用其他分類器代替,SVM、決策樹、貝葉斯、隨機森林、Adaboost都可以,可以ensemble多個弱分類,組成強分類器投票得出預測結果。
我就直接用sklearn的SVC做了一個支持向量機分類器,採用徑向基核函數rbf將數據映射到高維空間,在高維空間中,數據就會變得線性可分:
from sklearn.svm import SVCfrom sklearn.grid_search import GridSearchCV # 不用grid_search了from forModelTest import *import warningswarnings.filterwarnings(ignore) # 忽略警告warning煩人def svmModelTest(trainData, trainLabels, testData, y_true): clf = SVC(C=1, kernel=rbf, probability=True, gamma=2) clf.fit(trainData, trainLabels) # 訓練SVM模型 labelsPredict=[] for (index, testSample) in enumerate(testData): y_hat = clf.predict(testSample) # 用svm預測 labelsPredict.append(y_hat) if y_hat==y_true[index]: flag=正確 else: flag = sorry,錯誤--! print 測試輸入為, y_true[index],------>,預測結果:, y_hat, ,flag resCompare = [True if labelsPredict[i] == y_true[i] else False for i in range(len(y_true))] # 比較真偽 accuracy = sum(resCompare) * 100 / float(len(y_true)) # 預測的準確率 print SVM演算法分類器 accuracy={:.2f}%.format(accuracy) return Noneif __name__ == __main__: dataSet, labels = baseImageRep(BaseImages) # 提取基礎集的特徵點和標籤 dataTest, labelsTest = testImagesRep(testImages) # 提取測試集的特徵點和標籤 labels = np.array(labels).reshape(-1,1) # sklearn高版本要求不能輸入一維向量,整成二維的吧。。。 svmModelTest(trainData=dataSet, testData=dataTest, trainLabels=labels, y_true=labelsTest)
最後,預測準確度為 98.44%!!比KNN提高了3個百分點!:
驚不驚喜,意不意外!。。。。。。最後還是被SVM搶了風頭。所以,今天的豬腳到底是誰??????
推薦閱讀:
※相比於深度學習,傳統的機器學習演算法難道就此沒落了嗎,還有必要去學習嗎?
※梯度,梯度下降,隨機梯度下降,batch梯度下降
※複習:PCA有關
※Learning Explanatory Rules from Noisy Data 閱讀筆記3
※7本書帶你掌握數據科學中的數學基礎(附下載)