天天看點

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

本文詳細記錄了台灣大學李宏毅老師機器學習課程第三次作業的完成經過,其中包括項目介紹、思路分析及模型搭建等。在本文中,先是通過OpenCv可視化了資料集,接着重點介紹了在Pytorch中資料的加載過程,随後利用Pytorch搭建卷積神經網絡并進行訓練。文末附源代碼。

  給定資料集train.csv,要求使用卷積神經網絡CNN,根據每個樣本的面部圖檔判斷出其表情。在本項目中,表情共分7類,分别為:(0)生氣,(1)厭惡,(2)恐懼,(3)高興,(4)難過,(5)驚訝和(6)中立(即面無表情,無法歸為前六類)。是以,本項目實質上是一個7分類問題。

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

資料集介紹:

  (1)、CSV檔案,大小為28710行X2305列;

  (2)、在28710行中,其中第一行為描述資訊,即“label”和“feature”兩個單詞,其餘每行内含有一個樣本資訊,即共有28709個樣本;

  (3)、在2305列中,其中第一列為該樣本對應的label,取值範圍為0到6。其餘2304列為包含着每個樣本大小為48X48人臉圖檔的像素值(2304=48X48),每個像素值取值範圍在0到255之間;

  (4)、資料集位址:https://pan.baidu.com/s/1hwrq5Abx8NOUse3oew3BXg ,提取碼:ukf7 。

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

  給定的資料集是csv格式的,考慮到圖檔分類問題的正常做法,決定先将其全部可視化,還原為圖檔檔案再送進模型進行處理。

  借助深度學習架構Pytorch1.0 CPU(窮逼)版本,搭模組化型,由于需用到自己的資料集,是以我們需要重寫其中的資料加載部分,其餘用現成的API即可。

  作業要求使用CNN實作功能,是以基本隻能在調參階段自由發揮(不要鄙視調參,通過這次作業才發現,參數也不是人人都能調得好的,比如我)。

  我們需要将csv中的像素資料還原為圖檔并儲存下來,在python環境下,很多庫都能實作類似的功能,如pillow,opencv等。由于筆者對opencv較為熟悉,且opencv又是專業的圖像處理庫,是以決定采用opencv實作這一功能。

  原檔案中,label和人臉像素資料是集中在一起的。為了友善操作,決定利用pandas庫進行資料分離,即将所有label 讀出後,寫入新建立的檔案label.csv;将所有的像素資料讀出後,寫入新建立的檔案data.csv。

  以上代碼執行完畢後,在該代碼腳本所在的檔案夾下,就會生成兩個新檔案label.csv以及data.csv。在執行代碼前,注意修改train.csv在本地的路徑。

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

  将資料分離後,人臉像素資料全部存儲在data.csv檔案中,其中每行資料就是一張人臉。按行讀取資料,利用opencv将每行的2304個資料恢複為一張48X48的人臉圖檔,并儲存為jpg格式。在儲存這些圖檔時,将第一行資料恢複出的人臉命名為0.jpg,第二行的人臉命名為1.jpg......,以友善與label[0]、label[1]......一一對應。

  以上代碼雖短,但涉及到大量資料的讀取和大批圖檔的寫入,是以占用的記憶體資源較多,且執行時間較長(視機器性能而定,一般要幾分鐘到十幾分鐘不等)。代碼執行完畢,我們來到指定的圖檔存儲路徑,就能發現裡面全部是寫好的人臉圖檔。

  粗略浏覽一下這些人臉圖檔,就能發現這些圖檔資料來源較廣,且并不純淨。就前60張圖檔而言,其中就包含了正面人臉,如1.jpg;側面人臉,如18.jpg;傾斜人臉,如16.jpg;正面人頭,如7.jpg;正面人上半身,如55.jpg;動漫人臉,如38.jpg;以及毫不相關的噪聲,如59.jpg。放大圖檔後仔細觀察,還會發現不少圖檔上還有水印。種種因素均給識别提出了嚴峻的挑戰。

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

  現在我們有了圖檔,但怎麼才能把圖檔讀取出來送給模型呢?

  最簡單粗暴的方法就是直接用opencv将所有圖檔讀取出來,以numpy中array的資料格式直接送給模型。如果這樣做的話,會一次性把所有圖檔全部讀入記憶體,占用大量的記憶體空間,且隻能使用單線程,效率不高,也不友善後續操作。

  其實在pytorch中,有一個類(torch.utils.data.Dataset)是專門用來加載資料的,我們可以通過繼承這個類來定制自己的資料集和加載方法。以下為基本流程。

  首先,我們需要劃分一下訓練集和驗證集。在本次作業中,共有28709張圖檔,取前24000張圖檔作為訓練集,其他圖檔作為驗證集。建立檔案夾train和val,将0.jpg到23999.jpg放進檔案夾train,将其他圖檔放進檔案夾val。

  在繼承torch.utils.data.Dataset類定制自己的資料集時,由于在資料加載過程中需要同時加載出一個樣本的資料及其對應的label,是以最好能建立一個data-label對照表,其中記錄着data和label的對應關系(“data-lable對照表”并非官方名詞,這個技術流程是筆者參考了他人的部落格後自己摸索的,這個名字也是筆者給命的名)。

  有童鞋看到這裡就會提出疑問了:在人臉可視化過程中,每張圖檔的命名不都和label的存放順序是一一對應關系嗎,為什麼還要多此一舉,再重建立立data-label對照表呢?筆者在剛開始的時候也是這麼想的,按順序(0.jpg, 1.jpg, 2.jpg......)加載圖檔和label(label[0], label[1], label[2]......),豈不是友善、快捷又高效?結果在實際操作的過程中才發現,程式加載檔案的機制是按照檔案名首字母(或數字)來的,即加載次序是0,1,10,100......,而不是預想中的0,1,2,3......,是以加載出來的圖檔不能夠和label[0],label[1],lable[2],label[3]......一一對應,是以建立data-label對照表還是相當有必要的。

  建立data-label對照表的基本思路就是:指定檔案夾(train或val),周遊該檔案夾下的所有檔案,如果該檔案是.jpg格式的圖檔,就将其圖檔名寫入一個清單,同時通過圖檔名索引出其label,将其label寫入另一個清單。最後利用pandas庫将這兩個清單寫入同一個csv檔案。 

  執行這段代碼前,注意修改相關檔案路徑。代碼執行完畢後,會在train和val檔案夾下各生成一個名為dataset.csv的data-label對照表。 

  OK,代碼執行完畢,讓我們來看一看data-label對照表裡面具體是什麼樣子吧! 

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

  首先介紹一下Pytorch中Dataset類:Dataset類是Pytorch中圖像資料集中最為重要的一個類,也是Pytorch中所有資料集加載類中應該繼承的父類。其中父類中的兩個私有成員函數getitem()和len()必須被重載,否則将會觸發錯誤提示。其中getitem()可以通過索引擷取資料,len()可以擷取資料集的大小。在Pytorch源碼中,Dataset類的聲明如下:

   我們通過繼承Dataset類來建立我們自己的資料加載類,命名為FaceDataset。

   首先要做的是類的初始化。之前的data-label對照表已經建立完畢,在加載資料時需用到其中的資訊。是以在初始化過程中,我們需要完成對data-label對照表中資料的讀取工作。

  通過pandas庫讀取資料,随後将讀取到的資料放入list或numpy中,友善後期索引。

  接着就要重寫getitem()函數了,該函數的功能是加載資料。在前面的初始化部分,我們已經擷取了所有圖檔的位址,在這個函數中,我們就要通過位址來讀取資料。

  由于是讀取圖檔資料,是以仍然借助opencv庫。需要注意的是,之前可視化資料部分将像素值恢複為人臉圖檔并儲存,得到的是3通道的灰色圖(每個通道都完全一樣),而在這裡我們隻需要用到單通道,是以在圖檔讀取過程中,即使原圖本來就是灰色的,但我們還是要加入參數從cv2.COLOR_BGR2GARY,保證讀出來的資料是單通道的。讀取出來之後,可以考慮進行一些基本的圖像處理操作,如通過高斯模糊降噪、通過直方圖均衡化來增強圖像等(經試驗證明,在本次作業中,直方圖均衡化并沒有什麼卵用,而高斯降噪甚至會降低正确率,可能是因為圖檔分辨率本來就較低,模糊後基本上什麼都看不清了吧)。讀出的資料是48X48的,而後續卷積神經網絡中nn.Conv2d() API所接受的資料格式是(batch_size, channel, width, higth),本次圖檔通道為1,是以我們要将48X48 reshape為1X48X48。

   最後就是重寫len()函數擷取資料集大小了。self.path中存儲着所有的圖檔名,擷取self.path第一維的大小,即為資料集的大小。

  完整代碼:

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)
基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

View Code

  到此為止,我們已經成功地寫好了自己的資料集加載類。那麼這個類該如何使用呢?下面筆者将以訓練集(train檔案夾下的資料)加載為例,講一下整個資料集加載類在模型訓練過程中的使用方法。

  首先,我們需要将這個類執行個體化。

  train_dataset即為我們執行個體化的訓練集,要想加載其中的資料,還需要DataLoader類的輔助。DataLoader類總是配合Dataset類一起使用,DataLoader類可以幫助我們分批次讀取資料,也可以通過這個類選擇讀取資料的方式(順序 or 随機亂序),還可以選擇并行加載資料等,這個類并不要我們重寫。

  最後,我們就能直接從train_loader中直接加載出資料和label了,而且每次都會加載出一個批次(batch)的資料和label。

   通過Pytorch搭建基于卷積神經網絡的分類器。剛開始是自己設計的網絡模型,在訓練時發現準确度一直上不去,折騰一周後走投無路,後來在github上找到了一個做表情識别的開源項目,用的是這個項目的模型結構,但還是沒能達到項目中的精度(acc在74%)。下圖為該開源項目中公布的兩個模型結構,筆者用的是Model B ,且隻采用了其中的卷積-全連接配接部分,如果大家希望進一步提高模型的表現能力,可以考慮向模型中添加Face landmarks + HOG features 部分。

  可以看出,在Model B 的卷積部分,輸入圖檔shape為48X48X1,經過一個3X3X64卷積核的卷積操作,再進行一次2X2的池化,得到一個24X24X64的feature map 1(以上卷積和池化操作的步長均為1,每次卷積前的padding為1,下同)。将feature map 1經過一個3X3X128卷積核的卷積操作,再進行一次2X2的池化,得到一個12X12X128的feature map 2。将feature map 2經過一個3X3X256卷積核的卷積操作,再進行一次2X2的池化,得到一個6X6X256的feature map 3。卷積完畢,資料即将進入全連接配接層。進入全連接配接層之前,要進行資料扁平化,将feature map 3拉一個成長度為6X6X256=9216的一維tensor。随後資料經過dropout後被送進一層含有4096個神經元的隐層,再次經過dropout後被送進一層含有1024個神經元的隐層,之後經過一層含256個神經元的隐層,最終經過含有7個神經元的輸出層。一般再輸出層後都會加上softmax層,取機率最高的類别為分類結果。

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

  我們可以通過繼承nn.Module來定義自己的模型類。以下代碼實作了上述的模型結構。需要注意的是,在代碼中,資料經過最後含7個神經元的線性層後就直接輸出了,并沒有經過softmax層。這是為什麼呢?其實這和Pytorch在這一塊的設計機制有關。因為在實際應用中,softmax層常常和交叉熵這種損失函數聯合使用,是以Pytorch在設計時,就将softmax運算內建到了交叉熵損失函數CrossEntropyLoss()内部,如果使用交叉熵作為損失函數,就預設在計算損失函數前自動進行softmax操作,不需要我們額外加softmax層。Tensorflow也有類似的機制。

  有了模型,就可以通過資料的前向傳播和誤差的反向傳播來訓練模型了。在此之前,還需要指定優化器(即學習率更新的方式)、損失函數以及訓練輪數、學習率等超參數。

  在本次作業中,我們采用的優化器是SGD,即随機梯度下降,其中參數weight_decay為正則項系數;損失函數采用的是交叉熵;可以考慮使用學習率衰減。

  我們訓練的這個模型相對較小,是以可以直接儲存整個模型(包括結構和參數)。

  代碼在CPU上跑起來較慢,視超參數和機器性能不同,一般跑完需耗時幾小時到幾十小時不等。代碼執行時,每輪輸出一次損失值,每5輪輸出一次在訓練集和驗證集上的正确率。有條件的可以在GPU上嘗試。

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)
基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

  這是台灣大學李宏毅老師機器學習課程(2019年春季)第三次作業。在該資料集上,隻用卷積神經網絡和其他輔助手段,能達到的最高分類正确率在75%左右。我前後折騰了近3周,一方面因為能力有限,無人交流指導,另一方面是因為算力有限(窮逼一個,沒有GPU),最終正确率也僅有63%。上面的源代碼不是我的最終模型,一是因為我的模型本來就不好,過拟合有點嚴重;二是因為我希望大家能自己動手體驗一波調參的樂趣。在此抛磚引玉,要是有哪個小夥伴有好的改進方法,歡迎來和我交流鴨~

基于卷積神經網絡的面部表情識别(Pytorch實作)----台大李宏毅機器學習作業3(HW3)

參考資料:

  本次作業釋出位址:https://ntumlta2019.github.io/ml-web-hw3/

  面部表情識别GitHub位址:https://github.com/amineHorseman/facial-expression-recognition-using-cnn

  Pytorch制作資料集:https://ptorch.com/news/215.html

            https://blog.csdn.net/Teeyohuang/article/details/79587125

原創:秋沐霖

部落格首頁:https://www.cnblogs.com/HL-space/

歡迎轉載,轉載請注明出處。

出錯之處,敬請交流、雅正!

創作不易,您的 " 推薦 " 和 " 關注 " ,是給我最大的鼓勵!