溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Tensorflow實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)用于人臉關(guān)鍵點識別

發(fā)布時間:2020-10-12 16:15:58 來源:腳本之家 閱讀:139 作者:thriving_fcl 欄目:開發(fā)技術(shù)

今年來人工智能的概念越來越火,AlphaGo以4:1擊敗李世石更是起到推波助瀾的作用。作為一個開挖掘機的菜鳥,深深感到不學(xué)習(xí)一下deep learning早晚要被淘汰。

既然要開始學(xué),當然是搭一個深度神經(jīng)網(wǎng)絡(luò)跑幾個數(shù)據(jù)集感受一下作為入門最直觀了。自己寫代碼實現(xiàn)的話debug的過程和運行效率都會很憂傷,我也不知道怎么調(diào)用GPU… 所以還是站在巨人的肩膀上,用現(xiàn)成的框架吧。粗略了解一下,現(xiàn)在比較知名的有caffe、mxnet、tensorflow等等。選哪個呢?對我來說選擇的標準就兩個,第一要容易安裝(想盡快入門的迫切心情實在難以忍受一大堆的配置安裝…);第二文檔要齊全(這應(yīng)該是廢話 - -)。這幾個大名鼎鼎的框架文檔都是比較齊全的,那就看最容易安裝的。看了幾個文檔,tensorflow算是最容易安裝的了。基本就是pip intall 給定的URL就可以了。安裝方式的文檔可以在tensorflow安裝教程上查看。

tensorflow基本概念與用法

tensorflow直譯過來就是張量流。去年google剛推出tensorflow的時候我就納悶,為什么深度學(xué)習(xí)會牽扯到張量,以前學(xué)彈塑性力學(xué)的時候就是一大堆張量看的很煩…不過還好要理解tensorflow里的tensor完全不用理會那些。先來看一下官方文檔的說明:

class tf.Tensor
Represents a value produced by an Operation.
A Tensor is a symbolic handle to one of the outputs of an Operation. It does not hold the values of that operation's output, but instead provides a means of computing those values in a TensorFlow Session.

首先,Tensor代表了執(zhí)行一個操作(運算)所產(chǎn)生的值。其次,一個Tensor實例并不會保存具體的值,而只是代表了產(chǎn)生這些值的運算方式。好像有些拗口,也就是說假如有一個加法操作add,令c = add(1,1)。那么c就是一個tensor實例了,代表了1+1的結(jié)果,但是它并沒有存儲2這個具體的值,它只知道它代表1+1這個運算。從這里也可以看出,tensorflow里的api都是惰性求值,等真正需要知道具體的值的時候,才會執(zhí)行計算,其他時候都是在定義計算的過程。

Tensor可以代表從常數(shù)一直到N維數(shù)組的值。

Flow指的是,指的是tensorflow這套框架里的數(shù)據(jù)傳遞全部都是tensor,也就是運算的輸入,輸出都是tensor。

常用操作

這里只是簡單介紹一下在后面定義卷積神經(jīng)網(wǎng)絡(luò)的時候會用到的東西。想要了解更詳細的內(nèi)容還得參考官網(wǎng)上的文檔。

首先import tensorflow as tf,后面的tf就代表tensorflow啦。

常數(shù)

tf.constant 是一個Operation,用來產(chǎn)生常數(shù),可以產(chǎn)生scalar與N-D array. a是一個tensor,代表了由constant這個Operation所產(chǎn)生的標量常數(shù)值的過程。 b就是代表了產(chǎn)生一個2*2的array的過程。

a = tf.constant(3)
b = tf.constant(3,shape=[2,2]) 

變量

變量代表了神經(jīng)網(wǎng)絡(luò)中的參數(shù),在優(yōu)化計算的過程中需要被改變。tf.Variable當然也是一個Operation,用來產(chǎn)生一個變量,構(gòu)造函數(shù)需要傳入一個Tensor對象,傳入的這個Tensor對象就決定了這個變量的值的類型(float 或 int)與shape。

變量雖然與Tensor有不同的類型,但是在計算過程中是與Tensor一樣可以作為輸入輸出的。(可以理解為Tensor的派生類,但是實際上可能并不是這樣,我還沒有看源碼)

變量在使用前都必須初始化。

w = tf.Variable(b)

Operation

其實Operation不應(yīng)該單獨拿出來說,因為之前的tf.constant和tf.Variable都是Op,不過還是說一下常規(guī)的操作,比如tf.matmul執(zhí)行矩陣計算,tf.conv2d用于卷積計算,Op的詳細用法以及其他的Op可以參考api文檔。

tf.matmul(m,n)
tf.conv2d(...)

TensorFlow的計算由不同的Operation組成,比如下圖

Tensorflow實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)用于人臉關(guān)鍵點識別

定義了6*(3+5)這個計算過程。6、3、5其實也是Op,這在前面介紹過了。

卷積神經(jīng)網(wǎng)絡(luò)用于人臉關(guān)鍵點識別

寫到這里終于要開始進入正題了,先從CNN做起吧。Tensorflow的tutorial里面有介紹用CNN(卷積神經(jīng)網(wǎng)絡(luò))來識別手寫數(shù)字,直接把那里的代碼copy下來跑一遍也是可以的。但是那比較沒有意思,kaggle上有一個人臉關(guān)鍵點識別的比賽,有數(shù)據(jù)集也比較有意思,就拿這個來練手了。

定義卷積神經(jīng)網(wǎng)絡(luò)

首先是定義網(wǎng)絡(luò)結(jié)構(gòu),在這個例子里我用了3個卷積層,第一個卷積層用3∗3的卷積核,后面兩個用2∗2的卷積核。每個卷積層后面都跟max_pool池化層,之后再跟3個全連接層(兩個隱層一個輸出層)。每個卷積層的feature_map分別用32、64、128。

產(chǎn)生權(quán)值的函數(shù)代碼如下

#根據(jù)給定的shape定義并初始化卷積核的權(quán)值變量
  def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

  #根據(jù)shape初始化bias變量
  def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

定義卷積運算的代碼如下。對tf.nn.con2d()的參數(shù)還是要說明一下
1. x是輸入的樣本,在這里就是圖像。x的shape=[batch, height, width, channels]。

  1. - batch是輸入樣本的數(shù)量
  2. - height, width是每張圖像的高和寬
  3. - channels是輸入的通道,比如初始輸入的圖像是灰度圖,那么channels=1,如果是rgb,那么channels=3。對于第二層卷積層,channels=32。

2. W表示卷積核的參數(shù),shape的含義是[height,width,in_channels,out_channels]。

3. strides參數(shù)表示的是卷積核在輸入x的各個維度下移動的步長。了解CNN的都知道,在寬和高方向stride的大小決定了卷積后圖像的size。這里為什么有4個維度呢?因為strides對應(yīng)的是輸入x的維度,所以strides第一個參數(shù)表示在batch方向移動的步長,第四個參數(shù)表示在channels上移動的步長,這兩個參數(shù)都設(shè)置為1就好。重點就是第二個,第三個參數(shù)的意義,也就是在height于width方向上的步長,這里也都設(shè)置為1。

4. padding參數(shù)用來控制圖片的邊距,'SAME'表示卷積后的圖片與原圖片大小相同,'VALID'的話卷積以后圖像的高為Heightout=Height原圖−Height卷積核+1/StrideHeight, 寬也同理。

def conv2d(x,W):
  return tf.nn.cov2d(x,W,strides=[1,1,1,1],padding='VALID')

接著是定義池化層的代碼,這里用2∗2的max_pool。參數(shù)ksize定義pool窗口的大小,每個維度的意義與之前的strides相同,所以實際上我們設(shè)置第二個,第三個維度就可以了。

def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],strides=[1, 2, 2, 1], padding='SAME')

定義好產(chǎn)生權(quán)重、卷積、池化的函數(shù)以后就要開始組裝這個卷積神經(jīng)網(wǎng)絡(luò)了。定義之前再定義一下輸入樣本x與對應(yīng)的目標值y_。這里用了tf.placeholder表示此時的x與y_是指定shape的站位符,之后在定義網(wǎng)絡(luò)結(jié)構(gòu)的時候并不需要真的輸入了具體的樣本,只要在求值的時候feed進去就可以了。激活函數(shù)用relu,api也就是tf.nn.relu。
keep_prob是最后dropout的參數(shù),dropout的目的是為了抗過擬合。

rmse是損失函數(shù),因為這里的目的是為了檢測人臉關(guān)鍵點的位置,是回歸問題,所以用root-mean-square-error。并且最后的輸出層不需要套softmax,直接輸出y值就可以了。

這樣就組裝好了一個卷積神經(jīng)網(wǎng)絡(luò)。后續(xù)的步驟就是根據(jù)輸入樣本來train這些參數(shù)啦。

  x = tf.placeholder("float", shape=[None, 96, 96, 1])
  y_ = tf.placeholder("float", shape=[None, 30])
  keep_prob = tf.placeholder("float")

  def model():
    W_conv1 = weight_variable([3, 3, 1, 32])
    b_conv1 = bias_variable([32])

    h_conv1 = tf.nn.relu(conv2d(x, W_conv1) + b_conv1)
    h_pool1 = max_pool_2x2(h_conv1)

    W_conv2 = weight_variable([2, 2, 32, 64])
    b_conv2 = bias_variable([64])

    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)

    W_conv3 = weight_variable([2, 2, 64, 128])
    b_conv3 = bias_variable([128])

    h_conv3 = tf.nn.relu(conv2d(h_pool2, W_conv3) + b_conv3)
    h_pool3 = max_pool_2x2(h_conv3)

    W_fc1 = weight_variable([11 * 11 * 128, 500])
    b_fc1 = bias_variable([500])

    h_pool3_flat = tf.reshape(h_pool3, [-1, 11 * 11 * 128])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool3_flat, W_fc1) + b_fc1)

    W_fc2 = weight_variable([500, 500])
    b_fc2 = bias_variable([500])

    h_fc2 = tf.nn.relu(tf.matmul(h_fc1, W_fc2) + b_fc2)
    h_fc2_drop = tf.nn.dropout(h_fc2, keep_prob)

    W_fc3 = weight_variable([500, 30])
    b_fc3 = bias_variable([30])

    y_conv = tf.matmul(h_fc2_drop, W_fc3) + b_fc3
    rmse = tf.sqrt(tf.reduce_mean(tf.square(y_ - y_conv)))
    return y_conv, rmse

訓(xùn)練卷積神經(jīng)網(wǎng)絡(luò)

讀取訓(xùn)練數(shù)據(jù)

定義好卷積神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)之后,就要開始訓(xùn)練。訓(xùn)練首先是要讀取訓(xùn)練樣本。下面的代碼用于讀取樣本。

  import pandas as pd
  import numpy as np

  TRAIN_FILE = 'training.csv'
  TEST_FILE = 'test.csv'
  SAVE_PATH = 'model'


  VALIDATION_SIZE = 100  #驗證集大小
  EPOCHS = 100       #迭代次數(shù)
  BATCH_SIZE = 64     #每個batch大小,稍微大一點的batch會更穩(wěn)定
  EARLY_STOP_PATIENCE = 10 #控制early stopping的參數(shù)


  def input_data(test=False):
    file_name = TEST_FILE if test else TRAIN_FILE
    df = pd.read_csv(file_name)
    cols = df.columns[:-1]

    #dropna()是丟棄有缺失數(shù)據(jù)的樣本,這樣最后7000多個樣本只剩2140個可用的。
    df = df.dropna()  
    df['Image'] = df['Image'].apply(lambda img: np.fromstring(img, sep=' ') / 255.0)

    X = np.vstack(df['Image'])
    X = X.reshape((-1,96,96,1))

    if test:
      y = None
    else:
      y = df[cols].values / 96.0    #將y值縮放到[0,1]區(qū)間

    return X, y

  #最后生成提交結(jié)果的時候要用到
  keypoint_index = {
    'left_eye_center_x':0,
    'left_eye_center_y':1,
    'right_eye_center_x':2,
    'right_eye_center_y':3,
    'left_eye_inner_corner_x':4,
    'left_eye_inner_corner_y':5,
    'left_eye_outer_corner_x':6,
    'left_eye_outer_corner_y':7,
    'right_eye_inner_corner_x':8,
    'right_eye_inner_corner_y':9,
    'right_eye_outer_corner_x':10,
    'right_eye_outer_corner_y':11,
    'left_eyebrow_inner_end_x':12,
    'left_eyebrow_inner_end_y':13,
    'left_eyebrow_outer_end_x':14,
    'left_eyebrow_outer_end_y':15,
    'right_eyebrow_inner_end_x':16,
    'right_eyebrow_inner_end_y':17,
    'right_eyebrow_outer_end_x':18,
    'right_eyebrow_outer_end_y':19,
    'nose_tip_x':20,
    'nose_tip_y':21,
    'mouth_left_corner_x':22,
    'mouth_left_corner_y':23,
    'mouth_right_corner_x':24,
    'mouth_right_corner_y':25,
    'mouth_center_top_lip_x':26,
    'mouth_center_top_lip_y':27,
    'mouth_center_bottom_lip_x':28,
    'mouth_center_bottom_lip_y':29
  }

開始訓(xùn)練

執(zhí)行訓(xùn)練的代碼如下,save_model用于保存當前訓(xùn)練得到在驗證集上loss最小的模型,方便以后直接拿來用。

tf.InteractiveSession()用來生成一個Session,(好像是廢話…)。Session相當于一個引擎,TensorFlow框架要真正的進行計算,都要通過Session引擎來啟動。

tf.train.AdamOptimizer是優(yōu)化的算法,Adam的收斂速度會比較快,1e-3是learning rate,這里先簡單的用固定的。minimize就是要最小化的目標,當然是最小化均方根誤差了。

  def save_model(saver,sess,save_path):
    path = saver.save(sess, save_path)
    print 'model save in :{0}'.format(path)

  if __name__ == '__main__':
    sess = tf.InteractiveSession()
    y_conv, rmse = model()
    train_step = tf.train.AdamOptimizer(1e-3).minimize(rmse)

    #變量都要初始化 
    sess.run(tf.initialize_all_variables())
    X,y = input_data()
    X_valid, y_valid = X[:VALIDATION_SIZE], y[:VALIDATION_SIZE]
    X_train, y_train = X[VALIDATION_SIZE:], y[VALIDATION_SIZE:]

    best_validation_loss = 1000000.0
    current_epoch = 0
    TRAIN_SIZE = X_train.shape[0]
    train_index = range(TRAIN_SIZE)
    random.shuffle(train_index)
    X_train, y_train = X_train[train_index], y_train[train_index]

    saver = tf.train.Saver()

    print 'begin training..., train dataset size:{0}'.format(TRAIN_SIZE)
    for i in xrange(EPOCHS):
      random.shuffle(train_index) #每個epoch都shuffle一下效果更好
      X_train, y_train = X_train[train_index], y_train[train_index]

      for j in xrange(0,TRAIN_SIZE,BATCH_SIZE):
        print 'epoch {0}, train {1} samples done...'.format(i,j)

        train_step.run(feed_dict={x:X_train[j:j+BATCH_SIZE], 
          y_:y_train[j:j+BATCH_SIZE], keep_prob:0.5})

      #電腦太渣,用所有訓(xùn)練樣本計算train_loss居然死機,只好注釋了。
      #train_loss = rmse.eval(feed_dict={x:X_train, y_:y_train, keep_prob: 1.0})
      validation_loss = rmse.eval(feed_dict={x:X_valid, y_:y_valid, keep_prob: 1.0})

      print 'epoch {0} done! validation loss:{1}'.format(i, validation_loss*96.0)
      if validation_loss < best_validation_loss:
        best_validation_loss = validation_loss
        current_epoch = i
        save_model(saver,sess,SAVE_PATH)  #即時保存最好的結(jié)果
      elif (i - current_epoch) >= EARLY_STOP_PATIENCE:
        print 'early stopping'
        break

在測試集上預(yù)測

下面的代碼用于預(yù)測test.csv里面的人臉關(guān)鍵點,最后的y值要乘以96,因為之前縮放到[0,1]區(qū)間了。

  X,y = input_data(test=True)
  y_pred = []

  TEST_SIZE = X.shape[0]
  for j in xrange(0,TEST_SIZE,BATCH_SIZE):
    y_batch = y_conv.eval(feed_dict={x:X[j:j+BATCH_SIZE], keep_prob:1.0})
    y_pred.extend(y_batch)

  print 'predict test image done!'

  output_file = open('submit.csv','w')
  output_file.write('RowId,Location\n')

  IdLookupTable = open('IdLookupTable.csv')
  IdLookupTable.readline()

  for line in IdLookupTable:
    RowId,ImageId,FeatureName = line.rstrip().split(',')
    image_index = int(ImageId) - 1
    feature_index = keypoint_index[FeatureName]
    feature_location = y_pred[image_index][feature_index] * 96
    output_file.write('{0},{1}\n'.format(RowId,feature_location))

  output_file.close()
  IdLookupTable.close()

結(jié)果

用這個結(jié)構(gòu)的卷積神經(jīng)網(wǎng)絡(luò)訓(xùn)練出來的模型,在測試集上預(yù)測的結(jié)果提交以后的成績是3.4144,在kaggle的leaderboard上是41名,初試CNN,感覺還可以了。這只是數(shù)據(jù),還是找一些現(xiàn)實的照片來試試這個模型如何,所以我找了一張anglababy的,標識出來的關(guān)鍵點感覺還算靠譜?;赥ensorFlow的卷積神經(jīng)網(wǎng)絡(luò)先寫到這了,有什么遺漏的想起來再補充,之后對深度學(xué)習(xí)更了解了,再寫寫CNN的原理,bp的推導(dǎo)過程之類的。

Tensorflow實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)用于人臉關(guān)鍵點識別

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI