別大作業(yè)實(shí)戰(zhàn):Python傳統(tǒng)機(jī)器學(xué)習(xí)源碼解析與避坑指南)
簡(jiǎn)介面向高校機(jī)器學(xué)習(xí)課程大作業(yè)場(chǎng)景這份壓縮包提供了基于Python的人臉識(shí)別與性別識(shí)別完整實(shí)現(xiàn)適合作為本科生課程項(xiàng)目參考。內(nèi)容覆蓋照片與視頻兩類輸入既包含人臉與眼睛檢測(cè)也演示了調(diào)用卷積神經(jīng)網(wǎng)絡(luò)模型進(jìn)行性別識(shí)別的兩種方式有助于理解從圖像預(yù)處理、模型加載到輸出結(jié)果的完整工程流程。壓縮包大小約3.52MB核心文件包括Python源碼、預(yù)訓(xùn)練模型、工程說(shuō)明文檔以及BP神經(jīng)網(wǎng)絡(luò)性別檢測(cè)工作報(bào)告針對(duì)照片與視頻分別設(shè)置了多個(gè)獨(dú)立模塊便于按需學(xué)習(xí)。目前已有1449人學(xué)習(xí)下載多為正在完成人臉識(shí)別或機(jī)器學(xué)習(xí)大作業(yè)的學(xué)生。除可直接運(yùn)行的代碼外報(bào)告還系統(tǒng)梳理了BP神經(jīng)網(wǎng)絡(luò)的工作流程、數(shù)據(jù)集處理與實(shí)驗(yàn)分析可顯著降低起步門檻。對(duì)于需要同時(shí)提交源碼、課程報(bào)告和項(xiàng)目說(shuō)明的讀者這份材料提供了完整參考既便于復(fù)現(xiàn)實(shí)驗(yàn)也方便在此基礎(chǔ)上擴(kuò)展新功能。1. 人臉識(shí)別大作業(yè)這套Python機(jī)器學(xué)習(xí)源碼包先解決能跑再解決能講期末周最忙的幾件事里機(jī)器學(xué)習(xí)課程設(shè)計(jì)一定排得上號(hào)。人臉識(shí)別作為各校大作業(yè)的高頻題搜出來(lái)的資源大多是源碼課程報(bào)告項(xiàng)目說(shuō)明三件套的壓縮包。它解決的不是從零發(fā)明算法而是兩個(gè)更現(xiàn)實(shí)的問(wèn)題交上去能跑出結(jié)果答辯時(shí)能講清原理。這類包的內(nèi)容通常是一條基于Python傳統(tǒng)機(jī)器學(xué)習(xí)的完整流程——讀取人臉圖片、統(tǒng)一尺寸、提取特征、訓(xùn)練分類器、對(duì)新照片預(yù)測(cè)再把過(guò)程寫進(jìn)課程報(bào)告配一份讓老師照著命令能復(fù)現(xiàn)的項(xiàng)目說(shuō)明。適合三類人課程設(shè)計(jì)臨期想穩(wěn)過(guò)的學(xué)生、剛?cè)腴TPython還不熟悉工程結(jié)構(gòu)的初學(xué)者、想拿基準(zhǔn)流程做對(duì)比實(shí)驗(yàn)的畢設(shè)起步者。先給結(jié)論這類包的價(jià)值在流程骨架不在模型精度你要做的是跑通它、改參數(shù)、把三件事講清楚。2. 從原理到選型為什么這個(gè)題目用傳統(tǒng)機(jī)器學(xué)習(xí)而不是扔給深度學(xué)習(xí)2.1 三條技術(shù)路線特征臉、Fisherface和LBPH差在哪大作業(yè)源碼里最常見的算法不是CNN而是三種傳統(tǒng)方法PCA特征臉、LDA Fisherface、LBPH局部二值模式。特征臉Eigenface的思路是把每張人臉照片拉成一維向量計(jì)算所有向量的協(xié)方差矩陣用特征值分解找出一組主成分方向。這些主成分向量還原成圖像后看起來(lái)像一張模糊的人臉輪廓所以叫特征臉。識(shí)別時(shí)把新照片投影到特征空間和每個(gè)已注冊(cè)的人臉向量算歐氏距離距離最近的標(biāo)簽就是預(yù)測(cè)結(jié)果。這個(gè)方法的數(shù)學(xué)干凈答辯時(shí)老師問(wèn)你PCA做了什么一張?zhí)卣髂樋梢暬瘓D就能說(shuō)明白。Fisherface是在PCA基礎(chǔ)上加了類別意識(shí)。PCA只關(guān)心整體方差最大不關(guān)心不同人之間的區(qū)分LDA則轉(zhuǎn)而最大化類間散度/類內(nèi)散度這個(gè)比值讓同一人的照片聚攏、不同人的照片分開。實(shí)驗(yàn)里Fisherface在光照變化不大的數(shù)據(jù)集上通常比純PCA略好但它對(duì)樣本數(shù)的需求更高——每類樣本太少時(shí)類內(nèi)散度矩陣估不準(zhǔn)。LBPH走的是另一條路它不計(jì)算全局投影而是給每個(gè)像素統(tǒng)計(jì)鄰域紋理模式生成局部二值模式直方圖再用直方圖之間的相似度做識(shí)別。優(yōu)點(diǎn)是計(jì)算快、對(duì)光照變化相對(duì)魯棒OpenCV的face模塊里直接封裝了現(xiàn)成接口很多課程設(shè)計(jì)源碼會(huì)用OpenCV那一套而不是自己手寫PCA。2.2 為什么是機(jī)器學(xué)習(xí)而不是深度學(xué)習(xí)人臉識(shí)別在工業(yè)界早就被深度學(xué)習(xí)統(tǒng)治了但大作業(yè)場(chǎng)景里傳統(tǒng)機(jī)器學(xué)習(xí)依然是更穩(wěn)的選擇四個(gè)原因第一數(shù)據(jù)量不匹配。公開的課程級(jí)數(shù)據(jù)集一般很小ORL一共400張、Yale大概165張這點(diǎn)樣本喂給CNN訓(xùn)練驗(yàn)證集準(zhǔn)確率波動(dòng)會(huì)非常大很容易陷入訓(xùn)練集98%測(cè)試集不到70%的尷尬。傳統(tǒng)方法參數(shù)少幾百?gòu)垐D足以擬合一個(gè)SVM。第二算力限制。CNN訓(xùn)練要么借GPU要么在CPU上干等一個(gè)下午。傳統(tǒng)方法在CPU上幾十秒就能完成交叉驗(yàn)證迭代調(diào)參的體驗(yàn)完全不同。第三可解釋性。答辯老師會(huì)追問(wèn)為什么選這個(gè)特征為什么分類錯(cuò)誤。PCA降維、SVM間隔、距離閾值這些概念一句話能講清楚而深度學(xué)習(xí)是個(gè)黑匣子學(xué)生版本的CNN很難從內(nèi)部機(jī)理上給出有說(shuō)服力的回答。第四課程評(píng)分標(biāo)準(zhǔn)本身不要求SOTA。大多數(shù)大作業(yè)的給分維度是功能完整、可復(fù)現(xiàn)、報(bào)告清晰不是識(shí)別率排名。一套調(diào)好的傳統(tǒng)流程已經(jīng)能拿到90%以上的識(shí)別率足夠支撐報(bào)告里的實(shí)驗(yàn)章節(jié)。2.3 選型對(duì)照表先定算法再動(dòng)手寫代碼拿到zip之后第一步不是跑代碼是先弄清里面用的是哪條技術(shù)路線這決定了你后面所有修改方向。路線適合數(shù)據(jù)量光照魯棒性可解釋性答辯友好度PCA特征臉小每類5~10張弱高高LDA/Fisherface中每類至少8~10張弱高高LBPH小到中中等中中CNN很大每類數(shù)百?gòu)垙?qiáng)低低課程設(shè)計(jì)源碼里最常見的技術(shù)組合是兩種一是自己用scikit-learn實(shí)現(xiàn)PCA降維 SVM分類二是直接調(diào)OpenCV的EigenFaceRecognizer或LBPHFaceRecognizer。前者的好處是每行代碼都能寫進(jìn)報(bào)告后者的好處是代碼量少、接口穩(wěn)定。如果你拿到的包是前者恭喜它的可改空間最大——換分類器、調(diào)主成分?jǐn)?shù)、做對(duì)比實(shí)驗(yàn)都很順手。實(shí)際選型時(shí)還要考慮一個(gè)容易被忽略的點(diǎn)zip里的項(xiàng)目說(shuō)明寫的是什么。項(xiàng)目說(shuō)明如果承諾Windows 10 Python 3.8可直接運(yùn)行你就不要擅自升級(jí)Python大版本否則OpenCV的輪子可能裝不上后面全是在修環(huán)境的活。3. 把源碼跑起來(lái)環(huán)境配置、ORL數(shù)據(jù)集與三個(gè)最小命令3.1 Python環(huán)境準(zhǔn)備用獨(dú)立虛擬環(huán)境避開python安裝的坑很多新手第一次翻車不是翻在算法而是翻在環(huán)境。系統(tǒng)Python里裝了一堆亂七八糟的包新版舊版互相打架最后import cv2直接崩。拿到源碼包之后第一件事是建一個(gè)干凈的虛擬環(huán)境——這是你交作業(yè)前的后悔藥。我用conda建環(huán)境的習(xí)慣是conda create -n face_det python3.8 -y conda activate face_det然后裝依賴。注意大作業(yè)源碼一般依賴這幾個(gè)包命令里我特意把opencv-contrib-python寫在前面因?yàn)槠胀╫pencv-python不帶face模塊裝了也調(diào)不出cv2.face.LBPHFaceRecognizer。pip install opencv-contrib-python scikit-learn numpy matplotlib pillow joblib參數(shù)說(shuō)明opencv-contrib-pythonOpenCV主庫(kù)加擴(kuò)展模塊face識(shí)別器在這個(gè)包里。只裝opencv-python的話代碼跑到cv2.face會(huì)直接報(bào)no attribute。scikit-learnPCA、SVM、train_test_split、GridSearchCV都靠它。joblib模型持久化用比pickle更省事后面講。裝完可以用一行命令驗(yàn)證環(huán)境到位python -c import cv2, sklearn; print(cv2.__version__, sklearn.__version__)能打出兩個(gè)版本號(hào)就說(shuō)明基礎(chǔ)依賴沒(méi)有缺。如果是在VSCode里跑記得右下角把解釋器切到face_det這個(gè)虛擬環(huán)境否則終端里裝的包IDE里全找不到這個(gè)坑我見人踩過(guò)無(wú)數(shù)次。3.2 數(shù)據(jù)集準(zhǔn)備ORL人臉庫(kù)的目錄結(jié)構(gòu)ORLATT人臉庫(kù)是這類大作業(yè)最常見的配套數(shù)據(jù)集40個(gè)人每人10張共400張112x92的灰度PGM圖片。使用前先確認(rèn)你的數(shù)據(jù)集目錄長(zhǎng)什么樣。常見的目錄結(jié)構(gòu)是orl_faces/ s1/ 1.pgm 2.pgm ... 10.pgm s2/ 1.pgm 2.pgm ... 10.pgm ... s40/ 1.pgm 2.pgm ... 10.pgm在跑訓(xùn)練之前先花一分鐘寫個(gè)腳本掃描目錄別急著訓(xùn)練防止數(shù)據(jù)集路徑不對(duì)導(dǎo)致訓(xùn)練腳本讀零張圖import os from collections import Counter dataset_root orl_faces person_dirs [d for d in sorted(os.listdir(dataset_root)) if os.path.isdir(os.path.join(dataset_root, d))] counts {} for person in person_dirs: path os.path.join(dataset_root, person) files [f for f in os.listdir(path) if f.lower().endswith((.pgm, .jpg, .png))] counts[person] len(files) print(f檢測(cè)到 {len(person_dirs)} 個(gè)人) print(f每人圖片數(shù)量: {set(counts.values())}) print(f異常樣本: {[(k, v) for k, v in counts.items() if v ! 10]})這段代碼的邏輯是先枚舉根目錄下所有子目錄把每個(gè)子目錄里的圖片文件數(shù)統(tǒng)計(jì)出來(lái)。set(counts.values())輸出一個(gè)集合如果結(jié)果是{10}說(shuō)明每人10張全部正常只要集合里出現(xiàn)別的數(shù)字就說(shuō)明某個(gè)人的照片缺失或有雜質(zhì)先去把數(shù)據(jù)修好再往下走。注意數(shù)據(jù)集里偶爾混入隱藏文件和縮略圖過(guò)濾條件里我加了擴(kuò)展名校驗(yàn)?zāi)鼙荛_大部分干擾。3.3 訓(xùn)練、驗(yàn)證、識(shí)別三個(gè)命令項(xiàng)目說(shuō)明要寫的自助流程源碼包里那份項(xiàng)目說(shuō)明文檔本質(zhì)上是給老師看的復(fù)現(xiàn)手冊(cè)。你自己復(fù)現(xiàn)時(shí)先把訓(xùn)練、測(cè)試、識(shí)別三步跑通python train.py --data orl_faces --model output/face_model.pkl --n_components 50 python test.py --model output/face_model.pkl --data orl_faces python recognize.py --model output/face_model.pkl --image test.jpg三步的含義分別是第一條命令讀取orl_faces目錄下的所有人臉圖片做PCA降維到50維訓(xùn)練SVM分類器把模型存成output/face_model.pkl第二條命令重新讀一遍數(shù)據(jù)集按經(jīng)典劃分比例分成訓(xùn)練集和測(cè)試集加載模型跑準(zhǔn)確率第三條命令加載同一個(gè)模型對(duì)準(zhǔn)單張圖片輸出預(yù)測(cè)的人的編號(hào)。這里的參數(shù)需要解釋一下--n_components 50PCA保留的主成分個(gè)數(shù)。50是一個(gè)中庸的起點(diǎn)最后你改這個(gè)數(shù)字去畫準(zhǔn)確率曲線就是報(bào)告里的實(shí)驗(yàn)內(nèi)容。--model模型文件的輸出或加載路徑。訓(xùn)練和識(shí)別必須用同一個(gè)路徑否則會(huì)出現(xiàn)識(shí)別時(shí)加載的模型和剛才訓(xùn)的不是同一個(gè)的烏龍。很多源碼包的項(xiàng)目說(shuō)明里會(huì)寫將數(shù)據(jù)集放在orl_faces目錄下運(yùn)行python train.py即可但它未必會(huì)告訴你路徑分隔符在Windows上要怎么寫。如果你在Windows的cmd里跑output/face_model.pkl這種正斜杠路徑是沒(méi)有問(wèn)題的Python會(huì)自己處理真正坑的是數(shù)據(jù)集路徑里帶了中文OpenCV的imread在Windows上讀有中文路徑的圖片會(huì)靜默返回None圖片讀成空訓(xùn)練數(shù)據(jù)全是零向量準(zhǔn)確率直接崩。項(xiàng)目說(shuō)明文檔里正常會(huì)提醒改成純英文路徑如果沒(méi)提醒你自己改一下。3.4 模型持久化pkl文件是交作業(yè)前的后悔藥訓(xùn)練一個(gè)模型可能只要一分鐘但反復(fù)調(diào)參的過(guò)程會(huì)消耗大量時(shí)間。把模型存下來(lái)的意義在于每次調(diào)完參存一份帶參數(shù)的pkl最后比對(duì)哪份在測(cè)試集上最好就不用每次識(shí)別都重訓(xùn)一遍。import joblib # 訓(xùn)練完成后保存 joblib.dump(pipe, output/face_model.pkl) # 識(shí)別時(shí)加載 pipe joblib.load(output/face_model.pkl) pred pipe.predict([face_vector])[0]說(shuō)明一下dump的第一個(gè)參數(shù)是訓(xùn)練好的Pipeline對(duì)象第二個(gè)參數(shù)是文件路徑load負(fù)責(zé)讀回來(lái)讀回來(lái)的對(duì)象保留了完整的PCA變換、標(biāo)準(zhǔn)化參數(shù)和SVM權(quán)重不需要重新計(jì)算任何東西。我習(xí)慣把模型文件命名為{算法}_{主成分?jǐn)?shù)}_{準(zhǔn)確率}.pkl比如pca50_svm_0.965.pkl這樣交作業(yè)前整理實(shí)驗(yàn)數(shù)據(jù)時(shí)一眼就能看出哪份模型是最好的不用重新跑。4. 核心代碼拆解數(shù)據(jù)加載、PCA降維與SVM分類的落地寫法4.1 數(shù)據(jù)加載與預(yù)處理灰度、resize和直方圖均衡化的順序拿到源碼后第一段值得精讀的代碼就是數(shù)據(jù)加載。它決定了后面所有環(huán)節(jié)的數(shù)據(jù)質(zhì)量。典型寫法如下import cv2 import glob import numpy as np def load_dataset(data_root, target_size(112, 92)): images, labels [], [] for person_idx, person_dir in enumerate(sorted(glob.glob(str(data_root) /*))): for img_path in glob.glob(person_dir /*.pgm): img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) # 統(tǒng)一讀成灰度圖 img cv2.resize(img, target_size) # 尺寸統(tǒng)一 img cv2.equalizeHist(img) # 直方圖均衡化 images.append(img.flatten()) # 二維轉(zhuǎn)一維向量 labels.append(person_idx) return np.array(images), np.array(labels)這段代碼有三個(gè)關(guān)鍵點(diǎn)。一是IMREAD_GRAYSCALE人臉識(shí)別不需要顏色顏色信息只會(huì)增加PCA的計(jì)算量。二是resizeORL本身是112x92但很多源碼包為了統(tǒng)一輸入會(huì)強(qiáng)制resize到固定尺寸訓(xùn)練和識(shí)別時(shí)尺寸必須完全一致否則特征向量長(zhǎng)度對(duì)不上。三是equalizeHist直方圖均衡化把灰度分布拉開讓人臉在光線不均勻時(shí)依然能看出五官輪廓這一步對(duì)LBPH和PCA都有顯著的魯棒性提升。注意flatten()之后每張圖片變成了10304維112乘92的一維向量。這個(gè)維度數(shù)就是后面PCA要降維的對(duì)象。4.2 PCA主成分分析特征臉與累計(jì)方差貢獻(xiàn)率的取舍PCA在scikit-learn里封裝得非常簡(jiǎn)單但代碼簡(jiǎn)單不代表參數(shù)可以亂填。核心問(wèn)題是n_components到底取多少from sklearn.decomposition import PCA pca PCA(n_components50) X_pca pca.fit_transform(X) # X來(lái)自上一個(gè)函數(shù)的輸出 print(pca.explained_variance_ratio_.cumsum()[-1]) # 打印前50個(gè)主成分的累計(jì)方差貢獻(xiàn)率這段代碼的輸出會(huì)是一個(gè)0到1之間的小數(shù)比如0.9327。含義是前50個(gè)主成分保留了原圖93.27%的方差信息。這個(gè)數(shù)字越大保留的信息越多但主成分?jǐn)?shù)越多特征維度越高訓(xùn)練越慢、越容易過(guò)擬合。我一般把目標(biāo)定在0.9到0.95之間。如果50個(gè)主成分已經(jīng)到0.93就不用再加如果只有0.85則把n_components提到80或100重跑一次。你把這個(gè)主成分?jǐn)?shù)-累計(jì)方差貢獻(xiàn)率-識(shí)別準(zhǔn)確率的對(duì)應(yīng)關(guān)系做成表格直接就是課程報(bào)告里的一節(jié)實(shí)驗(yàn)結(jié)果。還有一個(gè)小細(xì)節(jié)pca.components_里存的就是特征臉向量用numpy重排成112x92像素再matplotlib畫出來(lái)能畫出網(wǎng)格狀的人臉輪廓圖。這張圖畫進(jìn)報(bào)告答辯時(shí)展示效果遠(yuǎn)好于貼一堆代碼。4.3 SVM分類器訓(xùn)練把PCA和SVM包進(jìn)一條Pipeline在大作業(yè)里SVM是比樸素貝葉斯和KNN更好的選擇它處理高維特征更穩(wěn)定而且有明確的參數(shù)可以調(diào)。這里我用Pipeline把預(yù)處理、降維、分類串成一條流水線識(shí)別時(shí)直接對(duì)單張圖片操作不容易漏步驟。from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.svm import SVC pipe Pipeline(steps[ (pca, PCA(n_components50)), (scaler, StandardScaler()), (svm, SVC(kernelrbf, C10.0, gamma0.01)) ]) pipe.fit(X_train, y_train) print(測(cè)試集準(zhǔn)確率:, pipe.score(X_test, y_test))Pipeline的邏輯是fit時(shí)按順序執(zhí)行PCA變換、標(biāo)準(zhǔn)化、SVM訓(xùn)練predict時(shí)對(duì)輸入自動(dòng)做同樣的PCA和標(biāo)準(zhǔn)化不需要你手動(dòng)調(diào)用pca.transform再scaler.transform。這里有個(gè)值得寫的技術(shù)點(diǎn)為什么PCA之后還要StandardScaler。PCA輸出的各主成分方差差異很大第一個(gè)主成分的數(shù)值范圍可能比第十幾個(gè)大一個(gè)數(shù)量級(jí)而RBF核的SVM對(duì)特征尺度敏感不做標(biāo)準(zhǔn)化數(shù)值大的主成分會(huì)主導(dǎo)距離計(jì)算導(dǎo)致分類邊界被帶偏。這個(gè)細(xì)節(jié)寫進(jìn)報(bào)告老師會(huì)認(rèn)為你是真調(diào)過(guò)參的不是只會(huì)黏貼代碼。4.4 評(píng)估與報(bào)告數(shù)據(jù)準(zhǔn)確率、混淆矩陣和一張測(cè)試截圖光是打出一個(gè)準(zhǔn)確率數(shù)字不夠課程報(bào)告需要有分析深度。評(píng)估環(huán)節(jié)至少要有三樣輸出準(zhǔn)確率、每個(gè)類別的精確率召回率、混淆矩陣。from sklearn.metrics import classification_report, confusion_matrix y_pred pipe.predict(X_test) print(classification_report(y_test, y_pred, digits3)) print(confusion_matrix(y_test, y_pred))classification_report會(huì)輸出每個(gè)類別的精確率、召回率、F1值digits3把小數(shù)位數(shù)擴(kuò)展到三位數(shù)據(jù)更好看。confusion_matrix輸出40行40列的矩陣如果按ORL的40人來(lái)算矩陣對(duì)角線越亮越好??磮?bào)告時(shí)重點(diǎn)看兩類問(wèn)題一是哪些人的識(shí)別率明顯低于平均說(shuō)明這兩人的樣本可能存在姿態(tài)或表情差異過(guò)大的情況二是哪兩個(gè)人容易互相混淆說(shuō)明特征空間里這兩類樣本距離太近PCA保留的信息不夠區(qū)分他們。這些分析寫進(jìn)課程報(bào)告里比單純貼一行準(zhǔn)確率有價(jià)值得多。必要的時(shí)候再對(duì)測(cè)試集里的某張圖片做一次完整的識(shí)別展示——原圖、預(yù)測(cè)標(biāo)簽、真實(shí)標(biāo)簽、模型置信度或距離值截圖貼到報(bào)告里答辯時(shí)直接給老師看這個(gè)效果圖。5. 大作業(yè)避坑指南五個(gè)讓訓(xùn)練直接翻車的低級(jí)錯(cuò)誤5.1 模型把所有照片都識(shí)別成同一個(gè)人現(xiàn)象訓(xùn)練結(jié)束準(zhǔn)確率看起來(lái)有90%以上但拿一張不在訓(xùn)練集里的人臉去識(shí)別永遠(yuǎn)輸出第一個(gè)人。原因最常見的是標(biāo)簽錯(cuò)位。數(shù)據(jù)加載時(shí)images和labels兩個(gè)列表的追加順序不一致比如images按s1到s40順序讀labels卻使用了從某個(gè)臨時(shí)字典里取的值導(dǎo)致特征和標(biāo)簽錯(cuò)位。另一種可能是測(cè)試圖片的預(yù)處理和訓(xùn)練時(shí)不一致比如訓(xùn)練時(shí)做了equalizeHist識(shí)別時(shí)忘了做特征分布完全對(duì)不上分類器只能把一切情況歸到最近的訓(xùn)練樣本。解決先打印pipe.predict用的向量維度和訓(xùn)練時(shí)的X_train.shape[1]比對(duì)維度不一致是預(yù)處理鏈路斷了。維度一致再看標(biāo)簽寫幾行代碼隨機(jī)抽取十張測(cè)試圖手工標(biāo)注真實(shí)身份和預(yù)測(cè)結(jié)果逐一對(duì)照很快就能定位是錯(cuò)位還是預(yù)處理問(wèn)題。踩過(guò)這個(gè)坑之后我養(yǎng)成了習(xí)慣數(shù)據(jù)加載函數(shù)里最后加一行assert len(images) len(labels)這句斷言能擋住大多數(shù)低級(jí)錯(cuò)誤。5.2 OpenCV提示 face 屬性不存在現(xiàn)象cv2.face.LBPHFaceRecognizer_create()報(bào)AttributeError: module cv2 has no attribute face。原因opencv-python這個(gè)包的主庫(kù)不帶face模塊只有opencv-contrib-python才有。很多安裝教程只讓裝opencv-python代碼一跑就直接翻車。解決先卸載再裝pip uninstall opencv-python opencv-contrib-python -y pip install opencv-contrib-python裝完驗(yàn)證import cv2 print(hasattr(cv2, face))輸出True就正常了。這個(gè)坑出現(xiàn)的概率極高我建議拿到源碼包后先跑這一行驗(yàn)證再繼續(xù)。5.3 訓(xùn)練集準(zhǔn)確率高、留出集卻很低現(xiàn)象訓(xùn)練集上識(shí)別率95%以上test_score只有60%甚至不到怎么調(diào)參都上不去。原因數(shù)據(jù)劃分時(shí)沒(méi)有打亂或者數(shù)據(jù)泄漏。比如訓(xùn)練和測(cè)試都直接取orl_faces里按順序排列的樣本測(cè)試集恰好集中了某幾個(gè)人全部照片模型沒(méi)有見過(guò)這類樣本自然預(yù)測(cè)很差。更隱蔽的一種是同一個(gè)人不同照片之間高度相似導(dǎo)致的信息泄漏如果劃分時(shí)沒(méi)有按人分組同一個(gè)人的幾張照片同時(shí)出現(xiàn)在訓(xùn)練集和測(cè)試集測(cè)試集準(zhǔn)確率會(huì)被虛高估計(jì)。解決用train_test_split顯式打亂并固定隨機(jī)種子from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X_pca, y, test_size0.2, shuffleTrue, random_state42 )shuffleTrue是關(guān)鍵random_state42保證每次運(yùn)行結(jié)果一致方便課程報(bào)告里貼數(shù)據(jù)。如果數(shù)據(jù)集是按人分組的建議直接用GroupShuffleSplit按人劃分保證同一人的照片不會(huì)同時(shí)出現(xiàn)在訓(xùn)練集和測(cè)試集——這個(gè)細(xì)節(jié)講出來(lái)答辯老師會(huì)眼前一亮。5.4 攝像頭實(shí)時(shí)識(shí)別卡頓、誤判多現(xiàn)象用筆記本攝像頭做實(shí)時(shí)演示時(shí)畫面一卡一卡識(shí)別結(jié)果亂跳甚至把背景當(dāng)人臉框出來(lái)。原因?qū)崟r(shí)識(shí)別里最常見的做法是每一幀都跑一遍完整流程——檢測(cè)人臉、預(yù)處理、PCA、SVM預(yù)測(cè)四個(gè)步驟疊在一起普通筆記本CPU根本扛不住。另一個(gè)問(wèn)題是OpenCV的Haar級(jí)聯(lián)人臉檢測(cè)器漏檢和誤檢同時(shí)存在光線變化時(shí)檢測(cè)框位置和大小抖動(dòng)導(dǎo)致同一張臉連續(xù)幾幀被resize到不同尺寸特征不穩(wěn)定。解決實(shí)時(shí)pipeline做三件事。一是降低處理頻率每5幀只做一次檢測(cè)中間幀直接用上一次定位的人臉區(qū)域二是檢測(cè)框外擴(kuò)10%左右再裁剪把下巴和額頭邊緣包進(jìn)去減少因?yàn)椴们形恢枚秳?dòng)帶來(lái)的特征變化三是對(duì)連續(xù)幀的預(yù)測(cè)結(jié)果做簡(jiǎn)單的多數(shù)投票連續(xù)三幀里出現(xiàn)次數(shù)最多的標(biāo)簽作為最終輸出能有效壓制偶發(fā)誤判。5.5 課程報(bào)告交上去查重率超標(biāo)現(xiàn)象代碼跑通了但課程報(bào)告因?yàn)榇蠖螐?fù)述教材和網(wǎng)上的原理介紹查重率紅得刺眼。原因課程報(bào)告里PCA原理SVM原理這種章節(jié)最容易被復(fù)制粘貼。這些知識(shí)性內(nèi)容本來(lái)就有標(biāo)準(zhǔn)表述抄來(lái)抄去查重率必然爆表。解決把知識(shí)性內(nèi)容壓縮到最少把篇幅留給你自己的東西實(shí)驗(yàn)環(huán)境、數(shù)據(jù)集信息、參數(shù)設(shè)置表、準(zhǔn)確率曲線、混淆矩陣、踩坑記錄。原理部分用自己的話重新組織長(zhǎng)度控制在總篇幅的三分之一以內(nèi)。另一個(gè)技巧是把你調(diào)試過(guò)程中真實(shí)遇到過(guò)的問(wèn)題寫進(jìn)去比如OpenCV的face模塊在普通包里不存在訓(xùn)練集和測(cè)試集劃分初版忘記打亂導(dǎo)致驗(yàn)證分?jǐn)?shù)失真這些是查重系統(tǒng)里獨(dú)一無(wú)二的內(nèi)容也是答辯老師最愿意聽的部分因?yàn)樗鼈兪钦鎸?shí)工作痕跡。6. 讓它變成你的作業(yè)加一個(gè)實(shí)時(shí)攝像頭模塊再做兩個(gè)對(duì)比實(shí)驗(yàn)6.1 攝像頭識(shí)別的低配版管線大作業(yè)如果能現(xiàn)場(chǎng)演示實(shí)時(shí)識(shí)別效果遠(yuǎn)比只跑命令行截圖好。最低配的攝像頭識(shí)別管線是Haar級(jí)聯(lián)檢測(cè)人臉裁剪、resize、均衡化再丟進(jìn)訓(xùn)練好的Pipeline預(yù)測(cè)。import cv2 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) cap cv2.VideoCapture(0) while True: ok, frame cap.read() gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(80, 80) ) for (x, y, w, h) in faces: face cv2.resize(gray[y:yh, x:xw], (112, 92)) face cv2.equalizeHist(face) pred pipe.predict(face.flatten().reshape(1, -1))[0] cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, fid{pred}, (x, y - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(face_recognition, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()代碼里的scaleFactor1.1是檢測(cè)窗口每次縮放的步長(zhǎng)越小檢測(cè)越細(xì)但越慢minNeighbors5控制一個(gè)區(qū)域需要被多少個(gè)鄰近窗口確認(rèn)才算人臉數(shù)值越大誤檢越少但也容易漏檢。這兩個(gè)參數(shù)對(duì)門禁、考勤這類近景場(chǎng)景比較友好若是遠(yuǎn)景多人場(chǎng)景minNeighbors可以降到3。6.2 兩個(gè)能放進(jìn)答辯PPT的實(shí)驗(yàn)實(shí)驗(yàn)一改變n_components從20到120每隔20取一個(gè)點(diǎn)畫一條主成分?jǐn)?shù)-準(zhǔn)確率曲線能看出曲線上升后飽和甚至下降的拐點(diǎn)說(shuō)明PCA降維確實(shí)在起作用。實(shí)驗(yàn)二在完全相同的訓(xùn)練集測(cè)試集劃分下比較LBPH和PCASVM的準(zhǔn)確率與單次訓(xùn)練耗時(shí)——這組對(duì)比實(shí)驗(yàn)展示的不只是你跑通了代碼而是你理解了不同算法的邊界。6.3 一點(diǎn)收尾做完這個(gè)項(xiàng)目后我最大的習(xí)慣改變是先搭評(píng)估流程再調(diào)模型。很多同學(xué)先把模型調(diào)到自我感覺(jué)良好最后發(fā)現(xiàn)測(cè)試腳本寫的漏洞百出所有指標(biāo)都不可信。我后來(lái)所有實(shí)驗(yàn)都先把準(zhǔn)確率打印、模型保存、隨機(jī)種子固定這三件事做完再回頭調(diào)參。課程報(bào)告里的每個(gè)數(shù)字都能追溯答辯被追問(wèn)時(shí)心里不慌。這個(gè)習(xí)慣一直用到現(xiàn)在希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取