你的顏值能打多少分?讓飛槳來告訴你

  • 2020 年 2 月 12 日
  • 筆記

【飛槳開發者說】鐘山,中科院信工所工程師,主要研究電腦視覺、深度學習。

想必很多人都對自己的顏值到底怎樣充滿好奇,也有很多軟體為大家提供了顏值打分的趣味功能。其實,顏值打分也可以視為一個影像分類問題,今天就向大家介紹如何利用飛槳搭建一個VGG網路,實現一個簡單的顏值打分demo。

01

VGGNet介紹

VGGNet 由牛津大學的視覺幾何組(Visual Geometry Group)和 Google DeepMind 公司提出,是 ILSVRC-2014 中定位任務第一名和分類任務第二名。提出 VGGNet 的主要目的是為了探究在大規模影像識別任務中,卷積網路深度對模型精確度的影響。通過VGGNet,研究人員證明了基於尺寸較小的的卷積核,增加網路深度可以有效提升模型的效果。VGGNet結構簡單,模型的泛化能力好,因此受到研究人員青睞而廣泛使用,到現在依然經常被用作影像特徵提取。

VGGNet引入「模組化」的設計思想,將不同的層進行簡單的組合構成網路模組,再用模組來組裝完整網路,而不再是以「層」為單元組裝網路。VGGNet有5種不同的VGGNet 配置,如上表所示。其中每一列代表一種網路配置,分別用 A~E 來表示。從表格中可以看出所有VGG配置都有五個卷積模組,模組中所有卷積都是3×3卷積核(conv3),因此特徵圖的尺寸在模組內不是變的,每個模組卷積之後緊接著最大池化層。最淺的A網路也稱為VGG-11,它包含11個帶可學習參數層,最深的E網路也稱為VGG-19,包含19個帶可學習參數層。業界普遍認為,更深的網路具有比淺網路更強的表達能力,更能刻畫現實,完成更複雜的任務,VGG-19結構如下圖所示。

VGGNet是基於 AlexNet網路的,VGG在Alexnet(註:Alexnet介紹參考《深度學習導論與應用實踐》5.7.2節內容)基礎上對深度神經網路在深度和寬度上做了更多深入的研究,相較於Alexnet而言,VGG中的3×3卷積核的感受野要小,但是兩個3×3 卷積的卷積級聯其感受野相當於5×5卷積,三個3×3卷積級聯感受野相當於7×7卷積,為什麼要這樣做呢?

首先,每個卷積層後面都跟隨著非線性激活層,這樣整合三個卷積層和三個非線性激活層增加了非線性表達能力,提高網路的判斷能力。

其次是減少了網路的參數,假設輸入特徵圖的通道數為

,輸出特徵圖的通道數為

,級聯三個3×3卷積層中的參數為

,而使用7×7卷積層中的參數為

,參數要多81%。此外C網路中還使用了3×3卷積層,同理也是為了增加非線性與減少網路參數。

介紹完VGG網路結構以及相關基礎之後,接下來我們將在AI Studio平台上用 Paddle Fluid API 搭建一個VGG網路模型,實現對顏值打分的功能。

本實踐程式碼運行的環境配置如下:Python版本為3.7,飛槳版本為1.6.0,電腦配置MacOS 10.15。

02

數據準備

步驟1:

首先,我們需要準備用於模型訓練的數據。這次我們用到的是華南理工大學實驗室公布的人臉照片數據集(https://github.com/HCIILAB/SCUT-FBP5500-Database-Release)。該數據中包含了500張不同女生的彩色照片以及每張照片的顏值得分,其中,得分是由隨機尋找的70人對每張圖片進行打分,最終求平均得出。在本次實踐中,我們將顏值得分全部設定在1-5分並取整,即每個圖片的標籤為[1,2,3,4,5]中的一個。

註:原始數據集中每張影像並沒有專門對人臉部分進行剪裁,影像中包含大量背景。因此,我們將提前去除了非人臉的部分並將圖片大小剪裁為224×224,存儲為本次實踐中使用的數據集,如下圖所示,圖片命名格式是:顏值得分-圖片編號。處理好的數據集獲取地址為 https://pan.baidu.com/s/1B9Hlo031BABA6Hj-r16EQw。

了解了數據的基本資訊之後,我們需要有一個用於獲取圖片數據的數據提供器,在這裡我們定義為data_reader(),它的作用就是提供圖片以及圖片的標籤(即顏值得分)。為了方便對圖片進行歸一化處理和獲得數據的label,我們定義了data_mapper()。

注意:由於飛槳中交叉熵損失函數要求label必須從0開始,而數據集label從1開始,所以在獲取label時,我們將label減1。

#導入必要的包  import os  import paddle  import numpy as np  from PIL importImage  import paddle.fluidas fluid  frommultiprocessing import cpu_count  importmatplotlib.pyplot as plt    def data_mapper(data):      img, label = data      img = paddle.dataset.image.load_image(img)      #將img數組進行進行歸一化處理,得到0到1之間的數值      img= img.flatten().astype('float32')/255.0      return img, int(label)    def data_reader(data_path,buffered_size=512):    print(data_path)    def reader():        for image in os.listdir(data_path):            label = int(image.split('-')[0])-1       #label減1            img = os.path.join(data_path+ '/' +image)            yield img, label    returnpaddle.reader.xmap_readers(data_mapper, reader, cpu_count(), buffered_size)

paddle.reader.xmap_readers() 是飛槳提供的一個方法,功能是多執行緒下,使用自定義映射器 reader 返回樣本到輸出隊列(詳細介紹可在

https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/io_cn/xmap_readers_cn.html#xmap-readers 中查看)。

有了數據提供器data_reader()後,我們就可以很簡潔的得到用於訓練的數據提供器train_reader()和用於測試的數據提供提供器test_reader(),BATCH_SIZE是一個批次的大小,在這裡我們設定為16。

#構造訓練、測試數據提供器  BATCH_SIZE = 16  train_r =data_r(data_path='/home/aistudio/face_image_train')  train_reader =paddle.batch(paddle.reader.shuffle(reader=train_r,buf_size=128),                            batch_size=BATCH_SIZE)  test_r=data_r(data_path='/home/aistudio/face_image_test')  test_reader = paddle.batch(test_r,batch_size=BATCH_SIZE)

03

網路配置

數據準備的工作完成之後,我們開始構造前面已經介紹過的VGG-16網路,網路結構圖如下:

首先用fluid.nets.img_conv_group()函數定義一個卷積模組conv_block(),從而通過配置卷積模組組件VGG-16網路結構。卷積模組conv_block()主要用來設定該模組中的輸入、池化、卷積、激活函數、以及dropout和bacthnorm。獲得了卷積模組後,通過多個卷積模組的組合以及三個全連接層就可以輕鬆獲得一個VGG-16模型。

def vgg_bn_drop(image, type_size):      def conv_block(ipt, num_filter, groups,dropouts):          return fluid.nets.img_conv_group(              input=ipt, # 具有[N,C,H,W]格式的輸入影像              pool_size=2,              pool_stride=2,              conv_num_filter=[num_filter] *groups, # 過濾器個數              conv_filter_size=3, # 過濾器大小              conv_act='relu',              conv_with_batchnorm=True, # 表示在 Conv2d Layer 之後是否使用 BatchNorm              conv_batchnorm_drop_rate=dropouts,#表示 BatchNorm 之後的 Dropout Layer 的丟棄概率              pool_type='max') # 最大池化        conv1 = conv_block(image, 64, 2, [0.0, 0])      conv2 = conv_block(conv1, 128, 2, [0.0, 0])      conv3 = conv_block(conv2, 256, 3, [0.0,0.0, 0])      conv4 = conv_block(conv3, 512, 3, [0.0,0.0, 0])      conv5 = conv_block(conv4, 512, 3, [0.0,0.0, 0])        drop = fluid.layers.dropout(x=conv2,dropout_prob=0.5)      fc1 = fluid.layers.fc(input=drop, size=512,act=None)      bn = fluid.layers.batch_norm(input=fc1, act='relu')      drop2 = fluid.layers.dropout(x=bn,dropout_prob=0.5)      fc2 = fluid.layers.fc(input=drop2, size=1024,act=None)      predict = fluid.layers.fc(input=fc2,size=type_size, act='softmax')      return predict

接下來進行數據層的定義。由於數據是224×224的三通道彩色影像,所以輸入層image的維度為[None,3,224,224],label代表圖片的顏值得分標籤。

# 定義輸入輸出層  # 定義兩個張量  image =fluid.layers.data(name='image', shape=[3, 224, 224], dtype='float32')  label =fluid.layers.data(name='label', shape=[1], dtype='int64')

上面我們定義好了VGG網路結構,這裡我們使用定義好的網路來獲取分類器。

# 獲取分類器  predict=vgg_bn_drop(image,5)

接著是定義損失函數,這裡使用的是交叉熵損失函數,該函數在分類任務上比較常用。定義了一個損失函數之後,還要對它求平均值,因為定義的是一個Batch的損失值。同時還可以定義一個準確率函數,可以在訓練的時候輸出分類的準確率。

# 定義損失函數和準確率函數  cost =fluid.layers.cross_entropy(input=predict, label=label)  avg_cost =fluid.layers.mean(cost)  accuracy =fluid.layers.accuracy(input=predict, label=label) 

為了區別測試和訓練,在這裡我們克隆一個test_program()。

# 克隆main_program得到test_program,使用參數for_test來區分該程式是用來訓練還是用來測試#注意:該fluid.default_main_program().clone()請在optimization之前使用.test_program =fluid.default_main_program().clone(for_test=True)接著定義優化演算法,這裡使用的是Adam優化演算法,指定學習率為0.002。  # 定義優化方法  optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.002)  opts = optimizer.minimize(avg_cost)

用戶完成網路定義後,一段 Fluid 程式中通常存在兩個Program:

(1)fluid.default_startup_program:定義了創建模型參數,輸入輸出,以及模型中可學習參數的初始化等各種操作,由框架自動生成,使用時無需顯示地創建;

(2)fluid.default_main_program :定義了神經網路模型,前向反向計算,以及優化演算法對網路中可學習參數的更新,使用Fluid的核心就是構建起 default_main_program。

04

模型訓練

在上一步驟中定義好了網路模型,即構造好了兩個核心Program,接下來將介紹如何飛槳如何使用Excutor來執行Program:

#定義使用CPU還是GPU,使用CPU時use_cuda = False,使用GPU時use_cuda = True  use_cuda = False  place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()  #創建一個Executor實例exe  exe =fluid.Executor(place)  #正式進行網路訓練前,需先執行參數初始化  exe.run(fluid.default_startup_program())

定義好網路訓練需要的Executor,在執行訓練之前,需要告知網路傳入的數據分為兩部分,第一部分是images值,第二部分是label值:

feeder = fluid.DataFeeder(place=place, feed_list=[image, label])

之後就可以進行正式的訓練了,本實踐中設置訓練輪數60。在Executor的run方法中,feed代表以字典的形式定義了數據傳入網路的順序,feeder在上述程式碼中已經進行了定義,將data[0]、data[1]分別傳給image、label。fetch_list定義了網路的輸出。

在每輪訓練中,每10個batch,列印一次訓練平均誤差和準確率。每輪訓練完成後,使用驗證集進行一次驗證。

EPOCH_NUM = 60  #訓練過程數據記錄  all_train_iter=0  all_train_iters=[]  all_train_costs=[]  all_train_accs=[]    #測試過程數據記錄  all_test_iter=0  all_test_iters=[]  all_test_costs=[]  all_test_accs=[]  model_save_dir ="/home/aistudio/work"    for pass_id inrange(EPOCH_NUM):      # 開始訓練      for batch_id, data inenumerate(train_reader()):      #遍歷訓練集,並為數據加上索引batch_id          train_cost,train_acc =exe.run(program=fluid.default_main_program(),#運行主程式                               feed=feeder.feed(data),                 #喂入一個batch的數據                              fetch_list=[avg_cost, acc])         #fetch均方誤差和準確率          all_train_iter=all_train_iter+BATCH_SIZE          all_train_iters.append(all_train_iter)          all_train_costs.append(train_cost[0])          all_train_accs.append(train_acc[0])          #每10次batch列印一次訓練、進行一次測試          if batch_id % 10== 0:              print('Pass:%d, Batch:%d,Cost:%0.5f, Accuracy:%0.5f' %              (pass_id, batch_id, train_cost[0],train_acc[0]))      # 開始測試      test_costs = []                                                        #測試的損失值      test_accs = []                                                         #測試的準確率      for batch_id, data in enumerate(test_reader()):          test_cost, test_acc =exe.run(program=test_program,              #執行訓練程式                                       feed=feeder.feed(data),            #喂入數據                                       fetch_list=[avg_cost, acc])         #fetch 誤差、準確率          test_costs.append(test_cost[0])                                #記錄每個batch的誤差          test_accs.append(test_acc[0])                             #記錄每個batch的準確率          all_test_iter=all_test_iter+BATCH_SIZE          all_test_iters.append(all_test_iter)         all_test_costs.append(test_cost[0])          all_test_accs.append(test_acc[0])  # 求測試結果的平均值      test_cost = (sum(test_costs) /len(test_costs))        #計算誤差平均值(誤差和/誤差的個數)      test_acc = (sum(test_accs) /len(test_accs))  #計算準確率平均值( 準確率的和/準確率的個數)      print('Test:%d, Cost:%0.5f, ACC:%0.5f' %(pass_id, test_cost, test_acc))      每輪訓練完成後,對模型進行一次保存,使用飛槳提供的fluid.io.save_inference_model()進行模型保存:  # 保存模型  # 如果保存路徑不存在就創建  if not os.path.exists(model_save_dir):      os.makedirs(model_save_dir)  print('savemodels to %s' % (model_save_dir))  fluid.io.save_inference_model(model_save_dir,  # 保存預測Program的路徑                                ['image'],      #預測需要feed的數據                                [predict],       #保存預測結果                                exe)             #executor 保存預測模型

05

模型評估

在這裡我們定義了draw_cost_process()和draw_acc_process()函數,用來可視化展示訓練過程中的損失和準確率的變化趨勢。

def draw_cost_process(title,iters,costs,label_cost):      plt.title(title, fontsize=24)      plt.xlabel("iter", fontsize=20)      plt.ylabel("cost", fontsize=20)      plt.plot(iters,costs,color='red',label=label_cost)      plt.legend()      plt.grid()      plt.show()  defdraw_acc_process(title,iters,acc,label_acc):      plt.title(title, fontsize=24)      plt.xlabel("iter", fontsize=20)      plt.ylabel("acc", fontsize=20)      plt.plot(iters,acc,color='green',label=label_acc)      plt.legend()      plt.grid()      plt.show()

調用draw_acc_process()和draw_cost_process()繪製曲線,方便觀察迭代過程中的變化趨勢,從而對網路訓練結果進行評估。

#調用繪製曲線  draw_acc_process("training",all_train_iters, all_train_accs, "trainning acc")  draw_acc_process("testing",all_test_iters, all_test_accs, "test acc")  draw_cost_process("training",all_train_iters, all_train_costs, "trainning acc")  draw_cost_process("testing",all_test_iters, all_test_costs, "test acc")

從訓練結果來看,模型在測試集上的準確率有一定的提升,損失也在不斷下降。後續大家可以根據訓練結果調整模型參數,對模型進行優化,也可以使用CNN模型來進行對比。

06

模型預測

前面已經進行了模型訓練,並保存了訓練好的模型。接下來就可以使用訓練好的模型對手寫數字圖片進行識別了。

預測之前必須要對預測的影像進行預處理,首先對輸入的圖片進行灰度化,然後壓縮影像大小為224×224,接著將影像轉換成一維向量,最後對一維向量進行歸一化處理。程式碼實現如下所示:

#圖片預處理  defload_image(file):      im = Image.open(file)      im =im.resize((224, 224), Image.ANTIALIAS)                  #resize 影像大小為224*224      if np.array(im).shape[2] == 4             #判斷影像通道數,若為4通道,則將其轉化為3通道          im = np.array(im)[:,:,:3]          im = np.array(im).reshape(1, 3, 224,224).astype(np.float32) #返回新形狀的數組,把它變成一個 numpy 數組以匹配數據饋送格式。          im = im / 255.0                                             #歸一化到[-1~1]之間      return im

接下來使用訓練好的模型對經過預處理的圖片進行預測。首先從指定目錄中載入訓練好的模型,然後喂入要預測的圖片向量,返回模型的輸出結果,即為預測概率,這些概率的總和為1。

# 載入模型並開始預測  infer_exe =fluid.Executor(place)  infer_img='/home/aistudio/image1.jpg'  #獲取訓練好的模型  #從指定目錄中載入 推理model(inference model)  [inference_program,#預測用的program  feed_target_names,#是一個str列表,它包含需要在推理 Program 中提供數據的變數的名稱。  fetch_targets] = fluid.io.load_inference_model(model_save_dir,infer_exe)#fetch_targets:是一個 Variable 列表,從中我們可以得到推斷結果。    img =Image.open(infer_img)  plt.imshow(img)   #根據數組繪製影像  plt.show()        #顯示影像    image=load_image(infer_img)    # 開始預測  results =infer_exe.run(                         inference_program,                      #運行預測程式                         feed={feed_target_names[0]: image},#喂入要預測的數據                         fetch_list=fetch_targets)              #得到推測結果

得到各個標籤的概率值後,獲取概率最大的標籤,即為顏值的預測結果。

# 獲取概率最大的label  print('results',results)  label_list =["1", "2", "3", "4", "5"]  print("inferresults: %s" % label_list[np.argmax(results[0])])

運行結果如下:

results[array([[1.7734790e-12, 8.6307369e-02, 1.1037191e-03, 9.1258878e-01,          7.8247488e-08]], dtype=float32)]infer results: 4

最終我們的美女同事顏值被評估為4分!

至此,恭喜您!已經成功使用飛槳搭建了一個簡單的卷積神經網路並實現了顏值打分。

本實踐程式碼、影片及配套文檔已發布在AI Studio課程裡面,請掃描下方二維碼或點擊鏈接,進入學習:

https://aistudio.baidu.com/aistudio/course/introduce/998