Tensorflow实现卷积神经网络用于人脸关键点识别


Posted in Python onMarch 05, 2018

今年来人工智能的概念越来越火,AlphaGo以4:1击败李世石更是起到推波助澜的作用。作为一个开挖掘机的菜鸟,深深感到不学习一下deep learning早晚要被淘汰。

既然要开始学,当然是搭一个深度神经网络跑几个数据集感受一下作为入门最直观了。自己写代码实现的话debug的过程和运行效率都会很忧伤,我也不知道怎么调用GPU… 所以还是站在巨人的肩膀上,用现成的框架吧。粗略了解一下,现在比较知名的有caffe、mxnet、tensorflow等等。选哪个呢?对我来说选择的标准就两个,第一要容易安装(想尽快入门的迫切心情实在难以忍受一大堆的配置安装…);第二文档要齐全(这应该是废话 - -)。这几个大名鼎鼎的框架文档都是比较齐全的,那就看最容易安装的。看了几个文档,tensorflow算是最容易安装的了。基本就是pip intall 给定的URL就可以了。安装方式的文档可以在tensorflow安装教程上查看。

tensorflow基本概念与用法

tensorflow直译过来就是张量流。去年google刚推出tensorflow的时候我就纳闷,为什么深度学习会牵扯到张量,以前学弹塑性力学的时候就是一大堆张量看的很烦…不过还好要理解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代表了执行一个操作(运算)所产生的值。其次,一个Tensor实例并不会保存具体的值,而只是代表了产生这些值的运算方式。好像有些拗口,也就是说假如有一个加法操作add,令c = add(1,1)。那么c就是一个tensor实例了,代表了1+1的结果,但是它并没有存储2这个具体的值,它只知道它代表1+1这个运算。从这里也可以看出,tensorflow里的api都是惰性求值,等真正需要知道具体的值的时候,才会执行计算,其他时候都是在定义计算的过程。

Tensor可以代表从常数一直到N维数组的值。

Flow指的是,指的是tensorflow这套框架里的数据传递全部都是tensor,也就是运算的输入,输出都是tensor。

常用操作

这里只是简单介绍一下在后面定义卷积神经网络的时候会用到的东西。想要了解更详细的内容还得参考官网上的文档。

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

常数

tf.constant 是一个Operation,用来产生常数,可以产生scalar与N-D array. a是一个tensor,代表了由constant这个Operation所产生的标量常数值的过程。 b就是代表了产生一个2*2的array的过程。

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

变量

变量代表了神经网络中的参数,在优化计算的过程中需要被改变。tf.Variable当然也是一个Operation,用来产生一个变量,构造函数需要传入一个Tensor对象,传入的这个Tensor对象就决定了这个变量的值的类型(float 或 int)与shape。

变量虽然与Tensor有不同的类型,但是在计算过程中是与Tensor一样可以作为输入输出的。(可以理解为Tensor的派生类,但是实际上可能并不是这样,我还没有看源码)

变量在使用前都必须初始化。

w = tf.Variable(b)

Operation

其实Operation不应该单独拿出来说,因为之前的tf.constant和tf.Variable都是Op,不过还是说一下常规的操作,比如tf.matmul执行矩阵计算,tf.conv2d用于卷积计算,Op的详细用法以及其他的Op可以参考api文档。

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

TensorFlow的计算由不同的Operation组成,比如下图

Tensorflow实现卷积神经网络用于人脸关键点识别

定义了6*(3+5)这个计算过程。6、3、5其实也是Op,这在前面介绍过了。

卷积神经网络用于人脸关键点识别

写到这里终于要开始进入正题了,先从CNN做起吧。Tensorflow的tutorial里面有介绍用CNN(卷积神经网络)来识别手写数字,直接把那里的代码copy下来跑一遍也是可以的。但是那比较没有意思,kaggle上有一个人脸关键点识别的比赛,有数据集也比较有意思,就拿这个来练手了。

定义卷积神经网络

首先是定义网络结构,在这个例子里我用了3个卷积层,第一个卷积层用3∗3的卷积核,后面两个用2∗2的卷积核。每个卷积层后面都跟max_pool池化层,之后再跟3个全连接层(两个隐层一个输出层)。每个卷积层的feature_map分别用32、64、128。

产生权值的函数代码如下

#根据给定的shape定义并初始化卷积核的权值变量
  def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

  #根据shape初始化bias变量
  def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

定义卷积运算的代码如下。对tf.nn.con2d()的参数还是要说明一下
1. x是输入的样本,在这里就是图像。x的shape=[batch, height, width, channels]。

  1. - batch是输入样本的数量
  2. - height, width是每张图像的高和宽
  3. - channels是输入的通道,比如初始输入的图像是灰度图,那么channels=1,如果是rgb,那么channels=3。对于第二层卷积层,channels=32。

2. W表示卷积核的参数,shape的含义是[height,width,in_channels,out_channels]。

3. strides参数表示的是卷积核在输入x的各个维度下移动的步长。了解CNN的都知道,在宽和高方向stride的大小决定了卷积后图像的size。这里为什么有4个维度呢?因为strides对应的是输入x的维度,所以strides第一个参数表示在batch方向移动的步长,第四个参数表示在channels上移动的步长,这两个参数都设置为1就好。重点就是第二个,第三个参数的意义,也就是在height于width方向上的步长,这里也都设置为1。

4. padding参数用来控制图片的边距,'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。参数ksize定义pool窗口的大小,每个维度的意义与之前的strides相同,所以实际上我们设置第二个,第三个维度就可以了。

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

定义好产生权重、卷积、池化的函数以后就要开始组装这个卷积神经网络了。定义之前再定义一下输入样本x与对应的目标值y_。这里用了tf.placeholder表示此时的x与y_是指定shape的站位符,之后在定义网络结构的时候并不需要真的输入了具体的样本,只要在求值的时候feed进去就可以了。激活函数用relu,api也就是tf.nn.relu。
keep_prob是最后dropout的参数,dropout的目的是为了抗过拟合。

rmse是损失函数,因为这里的目的是为了检测人脸关键点的位置,是回归问题,所以用root-mean-square-error。并且最后的输出层不需要套softmax,直接输出y值就可以了。

这样就组装好了一个卷积神经网络。后续的步骤就是根据输入样本来train这些参数啦。

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

训练卷积神经网络

读取训练数据

定义好卷积神经网络的结构之后,就要开始训练。训练首先是要读取训练样本。下面的代码用于读取样本。

import pandas as pd
  import numpy as np

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


  VALIDATION_SIZE = 100  #验证集大小
  EPOCHS = 100       #迭代次数
  BATCH_SIZE = 64     #每个batch大小,稍微大一点的batch会更稳定
  EARLY_STOP_PATIENCE = 10 #控制early stopping的参数


  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()是丢弃有缺失数据的样本,这样最后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]区间

    return X, y

  #最后生成提交结果的时候要用到
  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
  }

开始训练

执行训练的代码如下,save_model用于保存当前训练得到在验证集上loss最小的模型,方便以后直接拿来用。

tf.InteractiveSession()用来生成一个Session,(好像是废话…)。Session相当于一个引擎,TensorFlow框架要真正的进行计算,都要通过Session引擎来启动。

tf.train.AdamOptimizer是优化的算法,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})

      #电脑太渣,用所有训练样本计算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)  #即时保存最好的结果
      elif (i - current_epoch) >= EARLY_STOP_PATIENCE:
        print 'early stopping'
        break

在测试集上预测

下面的代码用于预测test.csv里面的人脸关键点,最后的y值要乘以96,因为之前缩放到[0,1]区间了。

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()

结果

用这个结构的卷积神经网络训练出来的模型,在测试集上预测的结果提交以后的成绩是3.4144,在kaggle的leaderboard上是41名,初试CNN,感觉还可以了。这只是数据,还是找一些现实的照片来试试这个模型如何,所以我找了一张anglababy的,标识出来的关键点感觉还算靠谱。基于TensorFlow的卷积神经网络先写到这了,有什么遗漏的想起来再补充,之后对深度学习更了解了,再写写CNN的原理,bp的推导过程之类的。

Tensorflow实现卷积神经网络用于人脸关键点识别

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
在Python中封装GObject模块进行图形化程序编程的教程
Apr 14 Python
详解Python的Flask框架中的signals信号机制
Jun 13 Python
解决Pyinstaller 打包exe文件 取消dos窗口(黑框框)的问题
Jun 21 Python
PyQt5使用QTimer实现电子时钟
Jul 29 Python
python中open函数的基本用法示例
Sep 07 Python
Python 矩阵转置的几种方法小结
Dec 02 Python
python实现将json多行数据传入到mysql中使用
Dec 31 Python
Pytorch训练过程出现nan的解决方式
Jan 02 Python
浅谈Python中threading join和setDaemon用法及区别说明
May 02 Python
python3跳出一个循环的实例操作
Aug 18 Python
详解Python生成器和基于生成器的协程
Jun 03 Python
python unittest单元测试的步骤分析
Aug 02 Python
python入门教程 python入门神图一张
Mar 05 #Python
详解TensorFlow在windows上安装与简单示例
Mar 05 #Python
python 中if else 语句的作用及示例代码
Mar 05 #Python
运用TensorFlow进行简单实现线性回归、梯度下降示例
Mar 05 #Python
tf.truncated_normal与tf.random_normal的详细用法
Mar 05 #Python
用tensorflow搭建CNN的方法
Mar 05 #Python
利用TensorFlow训练简单的二分类神经网络模型的方法
Mar 05 #Python
You might like
收集的DedeCMS一些使用经验
2007/03/17 PHP
PHP载入图像imagecreatefrom_gif_jpeg_png系列函数用法分析
2016/11/14 PHP
php file_get_contents取文件中数组元素的方法
2017/04/01 PHP
Yii2框架加载css和js文件的方法分析
2019/05/25 PHP
客户端脚本中常常出现的一些问题和调试技巧
2007/01/09 Javascript
语义化 H1 标签
2008/01/14 Javascript
跨浏览器的 mouseenter mouseleave 以及 compareDocumentPosition的使用说明
2010/05/04 Javascript
iframe自适应宽度、高度 ie6 7 8,firefox 3.86下测试通过
2010/07/29 Javascript
jquery easyui使用心得
2014/07/07 Javascript
Jquery实现简单的轮播效果(代码管用)
2016/03/14 Javascript
Bootstrap Search Suggest使用例子
2016/12/21 Javascript
AngularJS使用Filter自定义过滤器控制ng-repeat去除重复功能示例
2018/04/21 Javascript
详解Webpack多环境代码打包的方法
2018/08/03 Javascript
解决在vue项目中webpack打包后字体不生效的问题
2018/09/01 Javascript
react 中父组件与子组件双向绑定问题
2019/05/20 Javascript
基于vue-cli3创建libs库的实现方法
2019/12/04 Javascript
jquery实现垂直手风琴导航栏
2020/02/18 jQuery
Vue使用vue-draggable 插件在不同列表之间拖拽功能
2020/03/12 Javascript
JS如何实现在弹出窗口中加载页面
2020/12/03 Javascript
[15:23]教你分分钟做大人:虚空假面
2014/10/30 DOTA
[28:07]完美世界DOTA2联赛PWL S3 Phoenix vs INK ICE 第二场 12.13
2020/12/17 DOTA
Python中类的继承代码实例
2014/10/28 Python
python实现的文件夹清理程序分享
2014/11/22 Python
python+django加载静态网页模板解析
2017/12/12 Python
Python中一行和多行import模块问题
2018/04/01 Python
利用HTML5+CSS3实现3D转换效果实例详解
2017/05/02 HTML / CSS
中邮全球便购:中国邮政速递物流
2017/03/04 全球购物
以下的初始化有什么区别
2013/12/16 面试题
会计专业自荐信范文
2013/12/02 职场文书
房地产项目建议书
2014/03/12 职场文书
2015年招聘工作总结
2014/12/12 职场文书
受资助学生感谢信
2015/01/21 职场文书
关于调整工作时间的通知
2015/04/24 职场文书
解析CSS 提取图片主题色功能(小技巧)
2021/05/12 HTML / CSS
Java实现多文件上传功能
2021/06/30 Java/Android
使用python求解迷宫问题的三种实现方法
2022/03/17 Python