毕业设计 - 题目:基于深度学习的人脸表情识别 - 卷积神经网络 毕设 代码
文章目錄
- 0 簡介
- 1 項目說明
- 2 數據集介紹:
- 3 思路分析及代碼實現
- 3.1 數據可視化
- 3.2 數據分離
- 3.3 數據可視化
- 3.4 在pytorch下創建數據集
- 3.4.1 創建data-label對照表
- 3.4.2 重寫Dataset類
- 3.4.3 數據集的使用
- 4 網絡模型搭建
- 4.1 訓練模型
- 4.2 模型的保存與加載
- 5 相關源碼
- 6 最后
0 簡介
今天學長向大家介紹一個機器視覺項目
基于深度學習的人臉表情識別
1 項目說明
給定數據集train.csv,要求使用卷積神經網絡CNN,根據每個樣本的面部圖片判斷出其表情。在本項目中,表情共分7類,分別為:(0)生氣,(1)厭惡,(2)恐懼,(3)高興,(4)難過,(5)驚訝和(6)中立(即面無表情,無法歸為前六類)。所以,本項目實質上是一個7分類問題。
2 數據集介紹:
-
(1)、CSV文件,大小為28710行X2305列;
-
(2)、在28710行中,其中第一行為描述信息,即“label”和“feature”兩個單詞,其余每行內含有一個樣本信息,即共有28709個樣本;
-
(3)、在2305列中,其中第一列為該樣本對應的label,取值范圍為0到6。其余2304列為包含著每個樣本大小為48X48人臉圖片的像素值(2304=48X48),每個像素值取值范圍在0到255之間;
3 思路分析及代碼實現
給定的數據集是csv格式的,考慮到圖片分類問題的常規做法,決定先將其全部可視化,還原為圖片文件再送進模型進行處理。
借助深度學習框架Pytorch1.0 CPU(窮逼)版本,搭建模型,由于需用到自己的數據集,因此我們需要重寫其中的數據加載部分,其余用現成的API即可。
學長這里使用CNN實現功能,因此基本只能在調參階段自由發揮(不要鄙視調參,參數也不是人人都能調得好的,比如我)。
3.1 數據可視化
我們需要將csv中的像素數據還原為圖片并保存下來,在python環境下,很多庫都能實現類似的功能,如pillow,opencv等。由于筆者對opencv較為熟悉,且opencv又是專業的圖像處理庫,因此決定采用opencv實現這一功能
3.2 數據分離
原文件中,label和人臉像素數據是集中在一起的。為了方便操作,決定利用pandas庫進行數據分離,即將所有label 讀出后,寫入新創建的文件label.csv;將所有的像素數據讀出后,寫入新創建的文件data.csv。
# 將label和像素數據分離 import pandas as pd# 修改為train.csv在本地的相對或絕對地址 path = './/ml2019spring-hw3//train.csv' # 讀取數據 df = pd.read_csv(path) # 提取label數據 df_y = df[['label']] # 提取feature(即像素)數據 df_x = df[['feature']] # 將label寫入label.csv df_y.to_csv('label.csv', index=False, header=False) # 將feature數據寫入data.csv df_x.to_csv('data.csv', index=False, header=False)以上代碼執行完畢后,在該代碼腳本所在的文件夾下,就會生成兩個新文件label.csv以及data.csv。在執行代碼前,注意修改train.csv在本地的路徑。
3.3 數據可視化
將數據分離后,人臉像素數據全部存儲在data.csv文件中,其中每行數據就是一張人臉。按行讀取數據,利用opencv將每行的2304個數據恢復為一張48X48的人臉圖片,并保存為jpg格式。在保存這些圖片時,將第一行數據恢復出的人臉命名為0.jpg,第二行的人臉命名為1.jpg…,以方便與label[0]、label[1]…一一對應。
import cv2 import numpy as np# 指定存放圖片的路徑 path = './/face' # 讀取像素數據 data = np.loadtxt('data.csv')# 按行取數據 for i in range(data.shape[0]):face_array = data[i, :].reshape((48, 48)) # reshapecv2.imwrite(path + '//' + '{}.jpg'.format(i), face_array) # 寫圖片以上代碼雖短,但涉及到大量數據的讀取和大批圖片的寫入,因此占用的內存資源較多,且執行時間較長(視機器性能而定,一般要幾分鐘到十幾分鐘不等)。代碼執行完畢,我們來到指定的圖片存儲路徑,就能發現里面全部是寫好的人臉圖片。
粗略瀏覽一下這些人臉圖片,就能發現這些圖片數據來源較廣,且并不純凈。就前60張圖片而言,其中就包含了正面人臉,如1.jpg;側面人臉,如18.jpg;傾斜人臉,如16.jpg;正面人頭,如7.jpg;正面人上半身,如55.jpg;動漫人臉,如38.jpg;以及毫不相關的噪聲,如59.jpg。放大圖片后仔細觀察,還會發現不少圖片上還有水印。種種因素均給識別提出了嚴峻的挑戰。
3.4 在pytorch下創建數據集
現在我們有了圖片,但怎么才能把圖片讀取出來送給模型呢?
最簡單粗暴的方法就是直接用opencv將所有圖片讀取出來,以numpy中array的數據格式直接送給模型。如果這樣做的話,會一次性把所有圖片全部讀入內存,占用大量的內存空間,且只能使用單線程,效率不高,也不方便后續操作。
其實在pytorch中,有一個類(torch.utils.data.Dataset)是專門用來加載數據的,我們可以通過繼承這個類來定制自己的數據集和加載方法。以下為基本流程。
3.4.1 創建data-label對照表
首先,我們需要劃分一下訓練集和驗證集。在本次作業中,共有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對照表。
import os import pandas as pddef data_label(path):# 讀取label文件df_label = pd.read_csv('label.csv', header = None)# 查看該文件夾下所有文件files_dir = os.listdir(path)# 用于存放圖片名path_list = []# 用于存放圖片對應的labellabel_list = []# 遍歷該文件夾下的所有文件for file_dir in files_dir:# 如果某文件是圖片,則將其文件名以及對應的label取出,分別放入path_list和label_list這兩個列表中if os.path.splitext(file_dir)[1] == ".jpg":path_list.append(file_dir)index = int(os.path.splitext(file_dir)[0])label_list.append(df_label.iat[index, 0])# 將兩個列表寫進dataset.csv文件path_s = pd.Series(path_list)label_s = pd.Series(label_list)df = pd.DataFrame()df['path'] = path_sdf['label'] = label_sdf.to_csv(path+'\\dataset.csv', index=False, header=False)def main():# 指定文件夾路徑train_path = 'F:\\0gold\\ML\\LHY_class\\FaceData\\train'val_path = 'F:\\0gold\\ML\\LHY_class\\FaceData\\val'data_label(train_path)data_label(val_path)if __name__ == "__main__":main()OK,代碼執行完畢,讓我們來看一看data-label對照表里面具體是什么樣子吧!
3.4.2 重寫Dataset類
首先介紹一下Pytorch中Dataset類:Dataset類是Pytorch中圖像數據集中最為重要的一個類,也是Pytorch中所有數據集加載類中應該繼承的父類。其中父類中的兩個私有成員函數getitem()和len()必須被重載,否則將會觸發錯誤提示。其中getitem()可以通過索引獲取數據,len()可以獲取數據集的大小。在Pytorch源碼中,Dataset類的聲明如下:
class Dataset(object):"""An abstract class representing a Dataset.All other datasets should subclass it. All subclasses should override``__len__``, that provides the size of the dataset, and ``__getitem__``,supporting integer indexing in range from 0 to len(self) exclusive."""def __getitem__(self, index):raise NotImplementedErrordef __len__(self):raise NotImplementedErrordef __add__(self, other):return ConcatDataset([self, other])我們通過繼承Dataset類來創建我們自己的數據加載類,命名為FaceDataset。
import torch from torch.utils import data import numpy as np import pandas as pd import cv2class FaceDataset(data.Dataset):首先要做的是類的初始化。之前的data-label對照表已經創建完畢,在加載數據時需用到其中的信息。因此在初始化過程中,我們需要完成對data-label對照表中數據的讀取工作。
通過pandas庫讀取數據,隨后將讀取到的數據放入list或numpy中,方便后期索引。
# 初始化 def __init__(self, root):super(FaceDataset, self).__init__()# root為train或val文件夾的地址self.root = root# 讀取data-label對照表中的內容df_path = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[0]) # 讀取第一列文件名df_label = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[1]) # 讀取第二列label# 將其中內容放入numpy,方便后期索引self.path = np.array(df_path)[:, 0]self.label = np.array(df_label)[:, 0]接著就要重寫getitem()函數了,該函數的功能是加載數據。在前面的初始化部分,我們已經獲取了所有圖片的地址,在這個函數中,我們就要通過地址來讀取數據。
由于是讀取圖片數據,因此仍然借助opencv庫。需要注意的是,之前可視化數據部分將像素值恢復為人臉圖片并保存,得到的是3通道的灰色圖(每個通道都完全一樣),而在這里我們只需要用到單通道,因此在圖片讀取過程中,即使原圖本來就是灰色的,但我們還是要加入參數從cv2.COLOR_BGR2GARY,保證讀出來的數據是單通道的。讀取出來之后,可以考慮進行一些基本的圖像處理操作,如通過高斯模糊降噪、通過直方圖均衡化來增強圖像等(經試驗證明,在本次作業中,直方圖均衡化并沒有什么卵用,而高斯降噪甚至會降低正確率,可能是因為圖片分辨率本來就較低,模糊后基本上什么都看不清了吧)。讀出的數據是48X48的,而后續卷積神經網絡中nn.Conv2d() API所接受的數據格式是(batch_size, channel, width, higth),本次圖片通道為1,因此我們要將48X48 reshape為1X48X48。
# 讀取某幅圖片,item為索引號 def __getitem__(self, item):face = cv2.imread(self.root + '\\' + self.path[item])# 讀取單通道灰度圖face_gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)# 高斯模糊# face_Gus = cv2.GaussianBlur(face_gray, (3,3), 0)# 直方圖均衡化face_hist = cv2.equalizeHist(face_gray)# 像素值標準化face_normalized = face_hist.reshape(1, 48, 48) / 255.0 # 為與pytorch中卷積神經網絡API的設計相適配,需reshape原圖# 用于訓練的數據需為tensor類型face_tensor = torch.from_numpy(face_normalized) # 將python中的numpy數據類型轉化為pytorch中的tensor數據類型face_tensor = face_tensor.type('torch.FloatTensor') # 指定為'torch.FloatTensor'型,否則送進模型后會因數據類型不匹配而報錯label = self.label[item]return face_tensor, label最后就是重寫len()函數獲取數據集大小了。self.path中存儲著所有的圖片名,獲取self.path第一維的大小,即為數據集的大小。
1 # 獲取數據集樣本個數 2 def __len__(self): 3 return self.path.shape[0] class FaceDataset(data.Dataset):# 初始化def __init__(self, root):super(FaceDataset, self).__init__()self.root = rootdf_path = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[0])df_label = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[1])self.path = np.array(df_path)[:, 0]self.label = np.array(df_label)[:, 0]# 讀取某幅圖片,item為索引號def __getitem__(self, item):face = cv2.imread(self.root + '\\' + self.path[item])# 讀取單通道灰度圖face_gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)# 高斯模糊# face_Gus = cv2.GaussianBlur(face_gray, (3,3), 0)# 直方圖均衡化face_hist = cv2.equalizeHist(face_gray)# 像素值標準化face_normalized = face_hist.reshape(1, 48, 48) / 255.0 # 為與pytorch中卷積神經網絡API的設計相適配,需reshape原圖# 用于訓練的數據需為tensor類型face_tensor = torch.from_numpy(face_normalized) # 將python中的numpy數據類型轉化為pytorch中的tensor數據類型face_tensor = face_tensor.type('torch.FloatTensor') # 指定為'torch.FloatTensor'型,否則送進模型后會因數據類型不匹配而報錯label = self.label[item]return face_tensor, label# 獲取數據集樣本個數def __len__(self):return self.path.shape[0]3.4.3 數據集的使用
到此為止,我們已經成功地寫好了自己的數據集加載類。那么這個類該如何使用呢?下面筆者將以訓練集(train文件夾下的數據)加載為例,講一下整個數據集加載類在模型訓練過程中的使用方法。
首先,我們需要將這個類實例化。
1 # 數據集實例化(創建數據集) 2 train_dataset = FaceDataset(root='E:\\WSD\\HW3\\FaceData\\train')train_dataset即為我們實例化的訓練集,要想加載其中的數據,還需要DataLoader類的輔助。DataLoader類總是配合Dataset類一起使用,DataLoader類可以幫助我們分批次讀取數據,也可以通過這個類選擇讀取數據的方式(順序 or 隨機亂序),還可以選擇并行加載數據等,這個類并不要我們重寫。
1 # 載入數據并分割batch 2 train_loader = data.DataLoader(train_dataset, batch_size)最后,我們就能直接從train_loader中直接加載出數據和label了,而且每次都會加載出一個批次(batch)的數據和label。
1 for images, labels in train_loader: 2 ''' 3 通過images和labels訓練模型 4 '''4 網絡模型搭建
通過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層,取概率最高的類別為分類結果。
我們可以通過繼承nn.Module來定義自己的模型類。以下代碼實現了上述的模型結構。需要注意的是,在代碼中,數據經過最后含7個神經元的線性層后就直接輸出了,并沒有經過softmax層。這是為什么呢?其實這和Pytorch在這一塊的設計機制有關。因為在實際應用中,softmax層常常和交叉熵這種損失函數聯合使用,因此Pytorch在設計時,就將softmax運算集成到了交叉熵損失函數CrossEntropyLoss()內部,如果使用交叉熵作為損失函數,就默認在計算損失函數前自動進行softmax操作,不需要我們額外加softmax層。Tensorflow也有類似的機制。
class FaceCNN(nn.Module):# 初始化網絡結構def __init__(self):super(FaceCNN, self).__init__()# 第一次卷積、池化self.conv1 = nn.Sequential(# 輸入通道數in_channels,輸出通道數(即卷積核的通道數)out_channels,卷積核大小kernel_size,步長stride,對稱填0行列數padding# input:(bitch_size, 1, 48, 48), output:(bitch_size, 64, 48, 48), (48-3+2*1)/1+1 = 48nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1), # 卷積層nn.BatchNorm2d(num_features=64), # 歸一化nn.RReLU(inplace=True), # 激活函數# output(bitch_size, 64, 24, 24)nn.MaxPool2d(kernel_size=2, stride=2), # 最大值池化)# 第二次卷積、池化self.conv2 = nn.Sequential(# input:(bitch_size, 64, 24, 24), output:(bitch_size, 128, 24, 24), (24-3+2*1)/1+1 = 24nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),nn.BatchNorm2d(num_features=128),nn.RReLU(inplace=True),# output:(bitch_size, 128, 12 ,12)nn.MaxPool2d(kernel_size=2, stride=2),)# 第三次卷積、池化self.conv3 = nn.Sequential(# input:(bitch_size, 128, 12, 12), output:(bitch_size, 256, 12, 12), (12-3+2*1)/1+1 = 12nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),nn.BatchNorm2d(num_features=256),nn.RReLU(inplace=True),# output:(bitch_size, 256, 6 ,6)nn.MaxPool2d(kernel_size=2, stride=2),)# 參數初始化self.conv1.apply(gaussian_weights_init)self.conv2.apply(gaussian_weights_init)self.conv3.apply(gaussian_weights_init)# 全連接層self.fc = nn.Sequential(nn.Dropout(p=0.2),nn.Linear(in_features=256*6*6, out_features=4096),nn.RReLU(inplace=True),nn.Dropout(p=0.5),nn.Linear(in_features=4096, out_features=1024),nn.RReLU(inplace=True),nn.Linear(in_features=1024, out_features=256),nn.RReLU(inplace=True),nn.Linear(in_features=256, out_features=7),)# 前向傳播def forward(self, x):x = self.conv1(x)x = self.conv2(x)x = self.conv3(x)# 數據扁平化x = x.view(x.shape[0], -1)y = self.fc(x)return y4.1 訓練模型
有了模型,就可以通過數據的前向傳播和誤差的反向傳播來訓練模型了。在此之前,還需要指定優化器(即學習率更新的方式)、損失函數以及訓練輪數、學習率等超參數。
在本次作業中,我們采用的優化器是SGD,即隨機梯度下降,其中參數weight_decay為正則項系數;損失函數采用的是交叉熵;可以考慮使用學習率衰減。
def train(train_dataset, batch_size, epochs, learning_rate, wt_decay):# 載入數據并分割batchtrain_loader = data.DataLoader(train_dataset, batch_size)# 構建模型model = FaceCNN()# 損失函數loss_function = nn.CrossEntropyLoss()# 優化器optimizer = optim.SGD(model.parameters(), lr=learning_rate, weight_decay=wt_decay)# 學習率衰減# scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.8)# 逐輪訓練for epoch in range(epochs):# 記錄損失值loss_rate = 0# scheduler.step() # 學習率衰減model.train() # 模型訓練for images, labels in train_loader:# 梯度清零optimizer.zero_grad()# 前向傳播output = model.forward(images)# 誤差計算loss_rate = loss_function(output, labels)# 誤差的反向傳播loss_rate.backward()# 更新參數optimizer.step()4.2 模型的保存與加載
我們訓練的這個模型相對較小,因此可以直接保存整個模型(包括結構和參數)。
# 模型保存 torch.save(model, 'model_net1.pkl') # 模型加載 model_parm = 'model_net1.pkl' model = torch.load(net_parm)5 相關源碼
代碼在CPU上跑起來較慢,視超參數和機器性能不同,一般跑完需耗時幾小時到幾十小時不等。代碼執行時,每輪輸出一次損失值,每5輪輸出一次在訓練集和驗證集上的正確率。有條件的可以在GPU上嘗試。
import torch import torch.utils.data as data import torch.nn as nn import torch.optim as optim import numpy as np import pandas as pd import cv2# 參數初始化 def gaussian_weights_init(m):classname = m.__class__.__name__# 字符串查找find,找不到返回-1,不等-1即字符串中含有該字符if classname.find('Conv') != -1:m.weight.data.normal_(0.0, 0.04)# 人臉旋轉,嘗試過但效果并不好,本次并未用到 def imgProcess(img):# 通道分離(b, g, r) = cv2.split(img)# 直方圖均衡化bH = cv2.equalizeHist(b)gH = cv2.equalizeHist(g)rH = cv2.equalizeHist(r)# 順時針旋轉15度矩陣M0 = cv2.getRotationMatrix2D((24,24),15,1)# 逆時針旋轉15度矩陣M1 = cv2.getRotationMatrix2D((24,24),15,1)# 旋轉gH = cv2.warpAffine(gH, M0, (48, 48))rH = cv2.warpAffine(rH, M1, (48, 48))# 通道合并img_processed = cv2.merge((bH, gH, rH))return img_processed# 驗證模型在驗證集上的正確率 def validate(model, dataset, batch_size):val_loader = data.DataLoader(dataset, batch_size)result, num = 0.0, 0for images, labels in val_loader:pred = model.forward(images)pred = np.argmax(pred.data.numpy(), axis=1)labels = labels.data.numpy()result += np.sum((pred == labels))num += len(images)acc = result / numreturn accclass FaceDataset(data.Dataset):# 初始化def __init__(self, root):super(FaceDataset, self).__init__()self.root = rootdf_path = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[0])df_label = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[1])self.path = np.array(df_path)[:, 0]self.label = np.array(df_label)[:, 0]# 讀取某幅圖片,item為索引號def __getitem__(self, item):# 圖像數據用于訓練,需為tensor類型,label用numpy或list均可face = cv2.imread(self.root + '\\' + self.path[item])# 讀取單通道灰度圖face_gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)# 高斯模糊# face_Gus = cv2.GaussianBlur(face_gray, (3,3), 0)# 直方圖均衡化face_hist = cv2.equalizeHist(face_gray)# 像素值標準化face_normalized = face_hist.reshape(1, 48, 48) / 255.0face_tensor = torch.from_numpy(face_normalized)face_tensor = face_tensor.type('torch.FloatTensor')label = self.label[item]return face_tensor, label# 獲取數據集樣本個數def __len__(self):return self.path.shape[0]class FaceCNN(nn.Module):# 初始化網絡結構def __init__(self):super(FaceCNN, self).__init__()# 第一次卷積、池化self.conv1 = nn.Sequential(# 輸入通道數in_channels,輸出通道數(即卷積核的通道數)out_channels,卷積核大小kernel_size,步長stride,對稱填0行列數padding# input:(bitch_size, 1, 48, 48), output:(bitch_size, 64, 48, 48), (48-3+2*1)/1+1 = 48nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1), # 卷積層nn.BatchNorm2d(num_features=64), # 歸一化nn.RReLU(inplace=True), # 激活函數# output(bitch_size, 64, 24, 24)nn.MaxPool2d(kernel_size=2, stride=2), # 最大值池化)# 第二次卷積、池化self.conv2 = nn.Sequential(# input:(bitch_size, 64, 24, 24), output:(bitch_size, 128, 24, 24), (24-3+2*1)/1+1 = 24nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),nn.BatchNorm2d(num_features=128),nn.RReLU(inplace=True),# output:(bitch_size, 128, 12 ,12)nn.MaxPool2d(kernel_size=2, stride=2),)# 第三次卷積、池化self.conv3 = nn.Sequential(# input:(bitch_size, 128, 12, 12), output:(bitch_size, 256, 12, 12), (12-3+2*1)/1+1 = 12nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),nn.BatchNorm2d(num_features=256),nn.RReLU(inplace=True),# output:(bitch_size, 256, 6 ,6)nn.MaxPool2d(kernel_size=2, stride=2),)# 參數初始化self.conv1.apply(gaussian_weights_init)self.conv2.apply(gaussian_weights_init)self.conv3.apply(gaussian_weights_init)# 全連接層self.fc = nn.Sequential(nn.Dropout(p=0.2),nn.Linear(in_features=256*6*6, out_features=4096),nn.RReLU(inplace=True),nn.Dropout(p=0.5),nn.Linear(in_features=4096, out_features=1024),nn.RReLU(inplace=True),nn.Linear(in_features=1024, out_features=256),nn.RReLU(inplace=True),nn.Linear(in_features=256, out_features=7),)# 前向傳播def forward(self, x):x = self.conv1(x)x = self.conv2(x)x = self.conv3(x)# 數據扁平化x = x.view(x.shape[0], -1)y = self.fc(x)return ydef train(train_dataset, val_dataset, batch_size, epochs, learning_rate, wt_decay):# 載入數據并分割batchtrain_loader = data.DataLoader(train_dataset, batch_size)# 構建模型model = FaceCNN()# 損失函數loss_function = nn.CrossEntropyLoss()# 優化器optimizer = optim.SGD(model.parameters(), lr=learning_rate, weight_decay=wt_decay)# 學習率衰減# scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.8)# 逐輪訓練for epoch in range(epochs):# 記錄損失值loss_rate = 0# scheduler.step() # 學習率衰減model.train() # 模型訓練for images, labels in train_loader:# 梯度清零optimizer.zero_grad()# 前向傳播output = model.forward(images)# 誤差計算loss_rate = loss_function(output, labels)# 誤差的反向傳播loss_rate.backward()# 更新參數optimizer.step()# 打印每輪的損失print('After {} epochs , the loss_rate is : '.format(epoch+1), loss_rate.item())if epoch % 5 == 0:model.eval() # 模型評估acc_train = validate(model, train_dataset, batch_size)acc_val = validate(model, val_dataset, batch_size)print('After {} epochs , the acc_train is : '.format(epoch+1), acc_train)print('After {} epochs , the acc_val is : '.format(epoch+1), acc_val)return modeldef main():# 數據集實例化(創建數據集)train_dataset = FaceDataset(root='E:\\WSD\\HW3\\FaceData\\train')val_dataset = FaceDataset(root='E:\\WSD\\HW3\\FaceData\\val')# 超參數可自行指定model = train(train_dataset, val_dataset, batch_size=128, epochs=100, learning_rate=0.1, wt_decay=0)# 保存模型torch.save(model, 'model_net1.pkl')if __name__ == '__main__':main()6 最后
總結
以上是生活随笔為你收集整理的毕业设计 - 题目:基于深度学习的人脸表情识别 - 卷积神经网络 毕设 代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人生苦短,我用python——当我在玩p
- 下一篇: 聚焦和增强卷积神经网络