TensorFlow实践|TensorFlow实现人脸表情识别

作者:darlingwood2013


运行程序输入:

import input_emotion_data

data = input_emotion_data.read_data(input_dir = 'emotion')

import cnnemotion
cnnemotion.run_training(data=data, n_training_data=data.train.images.shape[0],
               is_CAENN=False, batch_size=170, n_training_epochs = 10, n_cae_training_epochs=5)


input_emotion_data.py

def read_data(input_dir, cut64x64=False, test_rate = 0.2, one_hot=True):
    temp_dir = path.join(input_dir, 'temp')

os.path.join()

os.path.join()是在拼接路径时使用的,上句返回路径"emotion/temp"

在运行语句"import input_emotion_data"后,在emotion文件夹里生成temp文件夹,但temp里面是空的。

在运行语句“data = input_emotion_data.read_data(input_dir = 'emotion')”后,temp文件变成如下:

并且输出了一些one-hot数组和内容是浮点数的数组。

它们究竟是什么?我猜是labels和images~不过images长得形状有点奇怪,要好好研究下它为什么长那样。


  # 如果序列化文件存在,则直接将其数据反序列化读入data中,然后函数返回data即可。
 filename = "Emotion_pic_cut64x64_%s_rate_%.1f_onehot_%s.pickle" % (str(cut64x64), test_rate, str(one_hot))
    if path.exists(path.join(temp_dir, filename)):
        with open(path.join(temp_dir, filename), 'rb') as f_pickle:
            data = pickle.load(f_pickle)
            print("type(data)")
            print(type(data))
        return data

  # 否则,继续运行read_data()函数下面的其他语句
       pic_with_anger_dir = path.join(temp_dir, 'anger')
    pic_with_happiness_dir = path.join(temp_dir, 'happiness')
    pic_with_sadness_dir = path.join(temp_dir, 'sadness')
    pic_with_calm_dir = path.join(temp_dir, 'calm')
  

    if not path.exists(temp_dir):
        os.mkdir(temp_dir) 

序列化

在程序运行的过程中,所有的变量都是在内存中,但是一旦程序结束,变量所占用的内存就被操作系统全部回收。

我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling。

序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

pickle.load(file):注解:从file中读取一个字符串,并将它重构为原来的python对象。


 # unzip
    zipfile.ZipFile(path.join(input_dir, 'anger.zip')).extractall(path=temp_dir)
    zipfile.ZipFile(path.join(input_dir, 'happiness.zip')).extractall(path=temp_dir)
    zipfile.ZipFile(path.join(input_dir, 'sadness.zip')).extractall(path=temp_dir)
    zipfile.ZipFile(path.join(input_dir, 'calm.zip')).extractall(path=temp_dir)

# get all pictures filename
    pic_with_anger_filenames = os.listdir(pic_with_anger_dir)
    pic_with_happiness_filenames = os.listdir(pic_with_happiness_dir)
    pic_with_sadness_filenames = os.listdir(pic_with_sadness_dir)
    pic_with_calm_filenames = os.listdir(pic_with_calm_dir)

listdir()

os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。这个列表以字母顺序。 它不包括 '.' 和'..' 即使它在文件夹中。

只支持在 Unix, Windows 下使用。

方法格式如下:os.listdir(path)


 for pic_filename in pic_with_anger_filenames:                #anger的数据预处理
        img = Image.open(path.join(pic_with_anger_dir, pic_filename))
        img = np.asarray(img, dtype = np.float32)
        print("anger_img in line 60")
        print(img)
        print(img.shape[0],img.shape[1])
        if cut64x64 == True:
            img = img[7:71, 7:71, 0] # 原图大小为79*79,现只居中取其64*64,而且图像是灰度图像,三个通道的值均相同,因此只取其中一个通道即可
        img = img.reshape(img.shape[0], img.shape[1], 1) / 256.
        print("anger_img in line 66")
        print(img)
        print(img.shape[0],img.shape[1],img.shape[2])
        pic_with_anger.append(img)
    pic_with_anger = np.stack(pic_with_anger, axis = 0)

输出:



可以看出来,经过np.asarray()的图像是一个64*64的二维数组,且其中每个元素在0到255之间;

而经过img.reshape()的二维数组变成了一个64*64*1的三维数组,且其中每个元素都小于1。


numpy.asarray()

>>> a =[[1,2],[1,0]]
>>> a = numpy.asarray(a)
>>> a
array([[1, 2],
       [1, 0]])
>>> numpy.asarray(a,'f')
array([[ 1.,  2.],
       [ 1.,  0.]], dtype=float32)

Image.open()


灰度图像

在计算机领域中,这类图像通常显示为从最暗黑色到最亮的白色的灰度,尽管理论上这个采样可以任何颜色的不同深浅,甚至可以是不同亮度上的不同颜色。

用于显示的灰度图像通常用每个采样像素8位的非线性尺度来保存,这样可以有256级灰度。这种精度刚刚能够避免可见的条带失真,并且非常易于编程。


np.stack()

看上去是合并数组的。

    >>> arrays = [np.random.randn(3, 4) for _ in range(10)]  
    >>> np.stack(arrays, axis=0).shape  
    (10, 3, 4)  
      
    >>>  
      
    >>> np.stack(arrays, axis=1).shape  
    (3, 10, 4)  
      
    >>>  
      
    >>> np.stack(arrays, axis=2).shape  
    (3, 4, 10)  
      
    >>>  
      
    >>> a = np.array([1, 2, 3])  
    >>> b = np.array([2, 3, 4])  
    >>> np.stack((a, b))  
    array([[1, 2, 3],  
           [2, 3, 4]])  
      
    >>>  
      
    >>> np.stack((a, b), axis=-1)  
    array([[1, 2],  
           [2, 3],  
           [3, 4]])  

cnnemotion.py

神经网络中对epoch和batch的理解:

一次epoch=所有训练数据forward+backward后更新参数的过程。
一次iteration=[batch size]个训练数据forward+backward后更新参数过程。

for i in range(data.train.images.shape[0] // batch_size):
‘//’表示地板除,//除法不管操作数为何种数值类型,总是会舍去小数部分,返回数字序列中比真正的商小的最接近的数字。


utils.py

class DataSet(object):
  """create same API with input_data.DataSet, but not change the data format
  """
  def __init__(self, images, labels):
    self._num_examples = images.shape[0]
    self._images = images
    self._labels = labels
    self._epochs_completed = 0
    self._index_in_epoch = 0
    # Shuffle the data
    perm = np.arange(self._num_examples)
    np.random.shuffle(perm)
    self._images = self._images[perm]
    self._labels = self._labels[perm]        
  @property
  def images(self):
    return self._images
  @property
  def labels(self):
    return self._labels
  @property
  def num_examples(self):
    return self._num_examples
  @property
  def epochs_completed(self):
    return self._epochs_completed
  def next_batch(self, batch_size):
    start = self._index_in_epoch
    self._index_in_epoch += batch_size
    if self._index_in_epoch > self._num_examples:
        # Finished epoch
        self._epochs_completed += 1
        # Shuffle the data
        perm = np.arange(self._num_examples)
        np.random.shuffle(perm)
        self._images = self._images[perm]
        self._labels = self._labels[perm]
        # Start next epoch
        start = 0
        self._index_in_epoch = batch_size
        assert batch_size <= self._num_examples
    end = self._index_in_epoch
    return self._images[start:end], self._labels[start:end]

np.arange()

np.random.shuffle()

>>> import numpy as np
>>> perm = np.arange(20)      # 返回一个序列,可以被当做向量使用
>>> perm
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19])
>>> np.random.shuffle(perm)   # 用于将列表中的元素打乱
>>> perm
array([11,  0,  9,  4, 16,  7,  1, 14, 12, 18,  8, 15, 19,  5, 13,  3,  2,
       17, 10,  6])


错误

1.AttributeError: module 'tensorflow' has no attribute 'global_variables_initializer'

将tf.global_variables_initializer()改成tf.initialize_all_variables()就好啦~

(不是说initialize_all_variables已被弃用,在2017-03-02之后删除,使用tf.global_variables_initializer代替吗???)




表情数据库

  • JAFFE数据库:数据库是由10个人的7种正面表情(6种基本表情+1种自然表情)组成的213幅灰度图像,图像是以大小为256*256的8位灰度级存储的,格式为.tiff型,平均每个人每种表情有2到4张。图片命名中包含表情标注。表情包括HAP(高兴) SAD(悲伤) SUR(惊讶) ANG(生气) DIS(厌恶) FEA(恐惧) NE(自然)。

  • CK+数据库:这个数据库包括123个subjects, 593 个 image sequence, 每个image sequence的最后一张 Frame 都有action units 的label, 而在这593个image sequence中,有327个sequence 有 emotion的 label。图像是以大小为480*480的8位灰度级存储的。这个数据库是人脸表情识别中比较流行的一个数据库,很多文章都会用到这个数据做测试。表情包括Disgust 3(厌恶59)Happy 5(高兴69)Surprise 7(惊讶83)Fear 4(恐惧25)Angry 1(生气45)Contempt 2(蔑视18)Sadness 6(忧伤28) Neutral 0(自然)。




问题

  • 如何确定需要几层网络?几个卷积层?几个dropout?几个全连接层?有什么参考依据吗?
  • 如何确定每层需要多少个特征图?有参考依据吗?

目前进度

2017年3月16日版本:

  • 基本模型建立起来了,只有197个训练数据,batch取120,epoch取50,发现准确率在50%左右,但在测试集上准确率只有30%左右。

  • 只有四类表情:生气(45幅),平静(112幅),开心(69幅),忧伤(28幅)。
  • 存在问题:训练数据量太小;泛化率太差!

2017年3月20日结果:

  • 将jaffe数据库处理成只包含人脸的96*96大小图像
  • 将ck+数据库处理成只包含人脸的64*64大小图像

2017年3月22日结果:

  • 解决了一个warning,通过在这里f1 = f1_score(y_true, y_pred, average="weighted")添加average属性。
  • 加入了dropout操作,不过正不正确不晓得啦~

2017年3月23日结果:

  • 对数据集进行旋转操作

目前结果

2017年3月20日结果:

1.来自CK+数据集(生气(45幅),平静(112幅),开心(69幅),忧伤(28幅)),训练集共211幅图像,分四类。

2.来自CK+和jaffe数据集(生气(75幅),平静(142幅),开心(100幅),忧伤(59幅)),训练集共302幅图像,分四类。

对于今天跑出来的结果,我很不明白。

为什么在第一种情况下大约10个epoch参数就不再更新了?并且前面正确率变化幅度那样的大?

为什么在第二种情况下爱经过了120个epoch参数还是没有平稳?

怎样选择epoch,minibatch?

现在应该从哪些方面改进?有什么基准吗?

需要对CK+库里的图像做光照补偿吗?

图像预处理需要做到什么程度?

UFLDL图像预处理


3月20号抛出模型以后,就一筹莫展,不知道该怎么评估自己的模型。

紧接着就去翻看视频资料、图书资料,去查找评估模型的方法。

Epoch 119, loss=0.00, AUC=1.00, accuracy=1.0000, precision=1.00, recall=1.00, f1=1.00 (3.021 sec)
test set: AUC=0.85, accuracy=0.6351, precision=0.70, recall=0.64, f1=0.66

听说训练集上正确率达到100%,很可能是模型过拟合了。所以接下来打算再仔细研读代码,增加dropout试试。


2017年3月22日结果:

1.修改batch_size为50(原来为120),正确率提高啦~

Epoch 49, loss=0.03, AUC=1.00, accuracy=0.9967, precision=1.00, recall=1.00, f1=1.00 (2.407 sec)
test set: AUC=0.86, accuracy=0.7027, precision=0.72, recall=0.70, f1=0.71

2.加入dropout操作,训练的时候0.5

使用dropout了以后,每次运行结果竟然会差这么多!!运行了4次,其中1,3次像第一张图,2,4次像第二张图。

哈哈哈,为什么呀???

参考文献

  1. 浅析Python中的序列化存储的方法
  2. Python图像处理库:PIL中Image,ImageDraw等基本模块介绍
  3. Python numpy函数hstack() vstack() stack() dstack() vsplit() concatenate()
  4. JAFFE数据库
发表评论

4个评论

  • qq_38716184

    博主可以分享一下吗??可以的话310435452@qq.com谢谢了

    2017-09-12 10:18:57回复

  • weixin_39295857

    博主你好,可以分享一下完整的代码和数据库文件吗?14738427@qq.com,感谢!

    2017-08-07 09:18:01回复

  • weixin_36348510

    可不可以分享下全部的代码?急急急1757259762@qq.com,蟹蟹啦

    2017-08-05 20:07:14回复

  • YangLinsist

    博主,能不能共享一份完整的代码学习一下?YLinsist@163.com。。。谢谢了

    2017-07-10 14:35:23回复

我要留言×

技术领域:

我要留言×

留言成功,我们将在审核后加至投票列表中!

提示x

人工智能开发框架知识库已成功保存至我的图谱现在你可以用它来管理自己的知识内容了

删除图谱提示×

你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?

删除节点提示×

无法删除该知识节点,因该节点下仍保存有相关知识内容!

删除节点提示×

你确定要删除该知识节点吗?