基于ResNet迁移学习的LFW人脸识别分类
基于ResNet遷移學(xué)習(xí)的LFW人臉識別分類
LFW數(shù)據(jù)集(Labeled Faces in the Wild)是馬薩諸塞大學(xué)阿姆斯特分校計算機(jī)視覺研究所整理制作的一個非限制環(huán)境下人臉數(shù)據(jù)集,包含5749人合計13233張圖片,圖片大小都是250x250
本代碼背景是一份CNN的人臉分類報告,僅需要完成簡單的人臉分類即可,不需要完成人臉識別,因此就當(dāng)作是人臉識別的簡單入門,之后的話可能會根據(jù)自己的興趣做一個人臉識別檢測的demo程序用在樹莓派上面
PS. 基于pytorch-gpu 1.5.1實現(xiàn),但是為了通用性所以改成了cpu版本,需要使用gpu的同學(xué)請自行添加相應(yīng)代碼
數(shù)據(jù)集準(zhǔn)備
下載數(shù)據(jù)集
可以到LFW官網(wǎng)上下載數(shù)據(jù)集,下載之后會有好幾個壓縮包,我們只需要其中的lfw.tgz文件,解壓之后就得到了包含所有圖片的文件夾
也可以直接拿我下好的數(shù)據(jù)集,下面是度娘鏈接
鏈接:https://pan.baidu.com/s/152iVUmPoMDQN_B94hJWETA
提取碼:7a6h
復(fù)制這段內(nèi)容后打開百度網(wǎng)盤手機(jī)App,操作更方便哦–來自百度網(wǎng)盤超級會員V5的分享(炫耀下我的v5的(~ ̄▽ ̄)~)
制作DataSet
考慮到LFW原始數(shù)據(jù)集中有很多人只有一張照片,也有部分名人,像布什這種一個人就有上百張照片,一方面為了保持每個人對應(yīng)的人臉照片量合適,另一方面盡量減少需要分類的人的個數(shù)以減小網(wǎng)絡(luò)大小方便訓(xùn)練,因此需要從LFW數(shù)據(jù)集中挑選一部分照片用于本次實驗。這里最終挑選的是擁有30-100張照片的這部分人,共有29人,也就是說最終的CNN需要分類的個數(shù)為29類,對于小實驗而言可以接受了
制作過程分為以下幾步:
PS. 在圖像處理的時候,因為ResNet的圖片輸入大小是224x224,因此做了一個中心裁剪
class MyDataSet(Dataset):'''定義數(shù)據(jù)集,用于將讀取到的圖片數(shù)據(jù)轉(zhuǎn)換并處理成CNN神經(jīng)網(wǎng)絡(luò)需要的格式'''def __init__(self, DataArray, LabelArray):super(MyDataSet, self).__init__()self.data = DataArrayself.label = LabelArraydef __getitem__(self, index):# 對圖片的預(yù)處理步驟# 1. 中心縮放至224(ResNet的輸入大小)# 2. 隨機(jī)旋轉(zhuǎn)0-30°# 3. 對圖片進(jìn)行歸一化,參數(shù)來源為pytorch官方文檔im_trans = transforms.Compose([transforms.ToPILImage(),transforms.CenterCrop(size=224),transforms.RandomRotation((0, 30)),transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])])return im_trans(self.data[index]), t.tensor(self.label[index], dtype=t.long)def __len__(self):return self.label.shape[0]# 讀取LFW數(shù)據(jù)集,將圖片數(shù)據(jù)讀入數(shù)組并將名字轉(zhuǎn)換為標(biāo)簽 path = r'face+\lfw' pathlist = map(lambda x: '\\'.join([path, x]), os.listdir(path)) namedict = {} data, label = [], [] idx = 0 for item in pathlist:dirlist = os.listdir(item)# 選取擁有30-100張照片的人作為數(shù)據(jù)來源# 太少網(wǎng)絡(luò)不容易學(xué)習(xí)到其人臉特征,太多的話則容易過擬合if not (30<= len(dirlist) <= 100):continue# data: 存儲人像照片的三通道數(shù)據(jù)# label: 存儲人像的對應(yīng)標(biāo)簽(整數(shù))# namedict: 記錄label中整數(shù)與人名的對應(yīng)關(guān)系for picpath in dirlist:data.append(image.imread(item + '\\' + picpath))label.append(idx)namedict[str(idx)] = item.split('\\')[-1]idx += 1# 隨機(jī)打亂數(shù)據(jù),重新排序并按照8:2的比例分割訓(xùn)練集和測試集 data, label = np.stack(data), np.array(label) idx = np.random.permutation(data.shape[0]) data, label = data[idx], label[idx] train_X, test_X, train_Y, test_Y = train_test_split(data, label, test_size=0.2)# 將分割好的訓(xùn)練集和測試集處理為pytorch所需的格式 TrainSet = MyDataSet(train_X, train_Y) TestSet = MyDataSet(test_X, test_Y) TrainLoader = DataLoader(TrainSet, batch_size=32, shuffle=True, drop_last=True) TestLoader = DataLoader(TestSet, batch_size=32, shuffle=True, drop_last=True)調(diào)用ResNet18
pytorch官方提供了很多CNN網(wǎng)絡(luò)的現(xiàn)成版本可以直接調(diào)用,就不用自己費(fèi)力去寫了。而且官方提供的網(wǎng)絡(luò)都有預(yù)訓(xùn)練版本,可以直接拿在ImageNet訓(xùn)練過的CNN網(wǎng)絡(luò)在我們的簡易LFW數(shù)據(jù)集上稍微訓(xùn)練微調(diào),從而實現(xiàn)遷移學(xué)習(xí),效果一般都會比較好。
考慮到我們簡易LFW數(shù)據(jù)集的規(guī)模,用ResNet18就可以了,把pretrained屬性設(shè)置為True使用預(yù)訓(xùn)練版本,初始使用的話會自動下載網(wǎng)絡(luò)參數(shù),需要等一會。ResNet18模型沒辦法直接運(yùn)用在我們的數(shù)據(jù)集上,需要做如下三點(diǎn)變換
進(jìn)行遷移學(xué)習(xí)
之后的步驟就跟通常的CNN訓(xùn)練沒有區(qū)別了,設(shè)置好參數(shù)按照模板進(jìn)行訓(xùn)練即可,由于遷移學(xué)習(xí)的效果比較好,因此這里也不需要特別設(shè)置網(wǎng)絡(luò)訓(xùn)練的參數(shù),保持默認(rèn)即可
# 定義交叉熵?fù)p失函數(shù)和Adam優(yōu)化器(學(xué)習(xí)率,權(quán)重衰減使用默認(rèn)值) loss = nn.CrossEntropyLoss() optimizer = t.optim.Adam(resnet.parameters())def train(net, dataloader, testdataloader, optimizer, criterion, epocs=20):# 以下四個參數(shù)分別用于存儲訓(xùn)練和測試的損失函數(shù)值以及分類準(zhǔn)確率train_loss_arr, train_acc_arr, test_loss_arr, test_acc_arr = [], [], [], []for epoc in range(epocs):net.train()TrainLoss, TrainAcc = 0, 0for BatchIdx, (InputData, Labels) in enumerate(dataloader):Outputs = net(InputData)optimizer.zero_grad()loss = criterion(Outputs.squeeze(), Labels)loss.backward()optimizer.step()TrainLoss += loss.item()_, pred = t.max(Outputs.data, 1)TrainAcc += t.mean(pred.eq(Labels.data.view_as(pred)).type(t.FloatTensor)).item() * len(InputData)if BatchIdx % 10 == 0 and BatchIdx > 0:print('Bathch: {}/{}\tLoss: {}\tAcc: {}%'.format(BatchIdx, len(dataloader), round(TrainLoss, 2), round(100*TrainAcc/((BatchIdx+1) * InputData.shape[0]), 2)))train_acc_arr.append(100*TrainAcc/(len(dataloader)*32))train_loss_arr.append(TrainLoss)TestLoss, TestAcc = 0, 0with t.no_grad():net.eval()for BatchIdx, (InputData, Labels) in enumerate(testdataloader):Outputs = net(InputData)loss = criterion(Outputs.squeeze(), Labels)TestLoss += loss.item()_, pred = t.max(Outputs.data, 1)TestAcc += t.mean(pred.eq(Labels.data.view_as(pred)).type(t.FloatTensor)).item() * len(InputData)print('Loss: {}\tAcc: {}%'.format(round(TrainLoss, 2),round(100*TestAcc/(len(testdataloader) * 32), 2)))print('-'*60) test_acc_arr.append(100*TestAcc/(len(testdataloader)*32))test_loss_arr.append(TestLoss)return train_loss_arr, train_acc_arr, test_loss_arr, test_acc_arr# 進(jìn)行訓(xùn)練并繪制訓(xùn)練曲線 train_loss_arr, train_acc_arr, test_loss_arr, test_acc_arr = train(resnet, TrainLoader, TestLoader, optimizer, loss) fig = plt.figure() ax1 = fig.add_subplot(121) ax1.plot(train_loss_arr, label='train loss') ax1.plot(test_loss_arr, label='test loss') ax1.legend() ax1.set_title('Loss Curve') ax1.set_xlabel('epocs') ax1.set_ylabel('loss') ax2 = fig.add_subplot(122) ax2.plot(train_acc_arr, label='train acc') ax2.plot(test_acc_arr, label='test acc') ax2.legend() ax2.set_title('Accuracy Curve') ax2.set_xlabel('epocs') ax2.set_ylabel('loss') plt.show()# 打印測試集的真實/預(yù)測結(jié)果 for InputData, Labels in enumerate(TestSet):Outputs = resnet(Labels[0].unsqueeze(0))_, pred = t.max(Outputs.data, 1)pred_name = namedict[str(pred.item())]real_name = namedict[str(Labels[1].item())]print('real name: {}\t\t\t\tpredict name: {}'.format(real_name, pred_name)) t.save(resnet, r'face+\resnet.pth')模型分類結(jié)果
訓(xùn)練完成后模型的分類準(zhǔn)確率訓(xùn)練集上差不多99%,測試集上最高可以到90%,還是比較符合預(yù)期了,畢竟整個網(wǎng)絡(luò)其實沒有進(jìn)行太多的調(diào)整
拿lfw_test中的8張人臉照片進(jìn)行測試,其中6張正確,2張錯誤,看了下分類錯誤的兩張之一
左邊是Jean Chretien(加拿大前總理),右邊是大名鼎鼎的貝克漢姆,網(wǎng)絡(luò)把總理的人臉照片錯誤識別成了貝克漢姆。講道理,有一說一,我覺得沒啥毛病,總理也挺帥的😎😎😎
有興趣的同學(xué)也可以了解下總理的故事,還挺勵志的。
完整代碼GitHub地址:https://github.com/Staaaying/record-repo/tree/main/face-classfication/resnet
總結(jié)
以上是生活随笔為你收集整理的基于ResNet迁移学习的LFW人脸识别分类的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# Json转list List转js
- 下一篇: 中兴面试题2