4 months ago

深度学习中,需要解决一个最困难的问题不是有关神经网络的问题,而是如何获取具有正确格式的正确数据
本次从Kaggle平台获取一个表情识别数据集,构建CNN网络来识别图片里面人物的表情

1.数据集介绍

首先到Kaggle官网上下载该facial-keypoints-detector数据集

输入:48x48像素灰度值(0到255之间)
目标:情绪类别(0到6之间:愤怒= 0,厌恶= 1,恐惧= 2,快乐= 3,悲伤= 4,惊喜= 5,中立= 6)
可以使用的辅助信息,但不能作为在测试时预测情绪的输入:identity(-1:未知,正整数= ID,不是所有连续的整数值)。辅助文件包含与所有训练示例相关联的标识。

数据下载解压后出现三个文件,一个训练集,一个测试集,还有一个文件不知道是什么东东
而且测试集居然没有标签,看来这个测试集是没法用了,只能从训练集中手动划出一部分做为测试集来验证准确率


网上有现成的函数用来处理原始数据集,这里我们尝试手工处理

2.原始数据预处理

整个过程各种艰辛,还好本人有强大的内心和扎实的代码功底
现在简单回忆一下推理过程






最后的代码只有寥寥几行搞定

def dataset_preprocess(dataset,image_size=48):
    labels = dataset["Emotion"]
    labels = tf.one_hot(labels,depth=10,axis=1,dtype=tf.float32)
    images = dataset["Pixels"].str.split(' ', expand=True)
    images = images.values.reshape([-1,image_size,image_size,1]).astype(np.float32)

    return images,labels

3.CNN网络完整代码

构建两层卷积神经网络和两层全连接神经网络



由于原本的测试集没有标签,这次就暂不验证准确率了,仅仅展示一下损失函数变化过程

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

def dataset_preprocess(dataset,image_size=48):
    labels = dataset["Emotion"]
    labels = tf.one_hot(labels,depth=10,axis=1,dtype=tf.float32)
    images = dataset["Pixels"].str.split(' ', expand=True)
    images = images.values.reshape([-1,image_size,image_size,1]).astype(np.float32)

    return images,labels

def face_detect_CNN_model(input_data):
    ## 第一层卷积
    # 初始化权重
    k1 = tf.Variable(tf.random_normal([5, 5, 1, 32]))
    b1 = tf.Variable(tf.random_normal([32, ]))
    # 卷积
    conv1 = tf.nn.conv2d(input_data, k1, [1, 1, 1, 1], "SAME")
    # 激活
    act1 = tf.nn.relu(conv1 + b1)
    # 池化
    pool1 = tf.nn.max_pool(act1, [1, 2, 2, 1], [1, 2, 2, 1], "SAME")

    ## 第二层卷积
    # 初始化权重
    k2 = tf.Variable(tf.random_normal([3, 3, 32, 64]))
    b2 = tf.Variable(tf.random_normal([64, ]))
    # 卷积
    conv2 = tf.nn.conv2d(pool1, k2, [1, 1, 1, 1], "SAME")
    # 激活
    act2 = tf.nn.relu(conv2 + b2)
    # 池化
    pool2 = tf.nn.max_pool(act2, [1, 2, 2, 1], [1, 2, 2, 1], "SAME")

    ## 第三层全连接
    # 把输入转化成一阶
    num = pool2.shape[1].value * pool2.shape[2].value * pool2.shape[3].value
    flat = tf.reshape(pool2, [-1, num])
    w3 = tf.Variable(tf.random_normal([num, 256]))
    b3 = tf.Variable(tf.random_normal([256, ]))
    # 线性操作
    l3 = tf.matmul(flat, w3) + b3
    # 激活
    act3 = tf.nn.relu(l3)

    ## 第四层全连接(输出层)
    w4 = tf.Variable(tf.random_normal([256, 10]))
    b4 = tf.Variable(tf.random_normal([10, ]))
    # 线性操作
    l4 = tf.matmul(act3, w4) + b4

    return l4


def main():
    image_size=48
    # 特征值占位
    input_data = tf.placeholder(tf.float32, [None, image_size, image_size, 1])
    # 目标值占位
    true = tf.placeholder(tf.float32, [None, 10])
    # 导入数据集
    train_dataset = pd.read_csv("./facial-keypoints-detector/train.csv")
    #test_dataset = pd.read_csv("./facial-keypoints-detector/test.csv")
    train_images,train_labels=dataset_preprocess(train_dataset,image_size)
    #test_images,test_labels=dataset_preprocess(test_dataset,image_size)

    # 损失函数
    model = face_detect_CNN_model(input_data)
    loss = tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(labels=true, logits=model))
    # 优化器
    opti = tf.train.GradientDescentOptimizer(0.001).minimize(loss)
    loss_map = []

    # 创建会话
    sess=tf.Session()
    sess.run(tf.global_variables_initializer())
    for i in range(10):
        sess.run(opti, {input_data: train_images, true: sess.run(train_labels)})
        loss_map.append(sess.run(loss, {input_data: train_images, true: sess.run(train_labels)}))

    plt.plot([i for i in range(len(loss_map))], loss_map)
    plt.show()


if __name__ == '__main__':
    main()


最后展示的图片是空白的,打印初损失函数后发现原理出现了梯度爆炸情况
(笨呀,其实从图上坐标就可以看出问题,无需打印损失函数)

[7.871837e+33, nan, nan, nan, nan, nan, nan, nan, nan, nan]

拼命尝试加入了许多办法,诸如加入l2正则和dropout,甚至喜出望外地发现原始数据忘了归一化,然后都没有解决问题
最后发现只需要将梯度下降法由传统方式改为AdamOptimizer,即可解决梯度爆炸问题

4.代码优化

上面的代码依然存在 训练时间太长,没有测试集等缺陷,现在着手进行优化

1.手动实现小批量梯度下降

next_batch函数的实现

def get_next_batch(data, step):
    offset = (step * BATCH_SIZE) % (images.shape[0] - BATCH_SIZE) #精妙利用取余功能,赞叹
    batch_data = data[offset: offset + BATCH_SIZE]
    return batch_data

2.从原来数据集中划出其中的0.3作为测试集

这个代码比较简单,不单独列出

3.手动实现one-hot编码

使用一下神奇的map函数

def create_onehot_label(x):
        label = np.zeros((1, labels_nums), dtype=np.float32)
    label[:, int(x)] = 1
    return label

train_labels = np.array(list(map(create_onehot_label, data_frame['Emotion'].values))).reshape(-1, labels_nums)

5.更新后完整代码

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

def dataset_preprocess(dataset,image_size=48,labels_nums=7):
    # 原始数据中提取labels
    labels = dataset["Emotion"]
    # 构建one-hot编码
    def create_onehot_label(x):
        label = np.zeros((1, labels_nums), dtype=np.float32)

        label[:, int(x)] = 1
        return label
    labels = np.array(list(map(create_onehot_label, labels.values))).reshape(-1, labels_nums)

    # 原始数据中提取images
    images = dataset["Pixels"].str.split(' ', expand=True)
    images = images.values.reshape([-1,image_size,image_size,1]).astype(np.float32)/255.0

    # 划分训练集和测试集
    length=labels.shape[0]
    train_labels=labels[:int(length*0.8),:]
    test_labels=labels[int(length*0.8):length,:]
    train_images=images[:int(length*0.8),:,:,:]
    test_images=images[int(length*0.8):length,:,:,:]

    return train_images,train_labels,test_images,test_labels

def get_next_batch(data, step, batch_size):
    offset = (step * batch_size) % (data.shape[0] - batch_size) #精妙利用取余功能,赞叹
    batch_data = data[offset: offset + batch_size]
    return batch_data

def face_detect_CNN_model(input_data):
    ## 第一层卷积
    # 初始化权重
    k1 = tf.Variable(tf.random_normal([5, 5, 1, 32]))
    b1 = tf.Variable(tf.random_normal([32, ]))
    # 卷积
    conv1 = tf.nn.conv2d(input_data, k1, [1, 1, 1, 1], "SAME")
    # 激活
    act1 = tf.nn.relu(conv1 + b1)
    # 池化
    pool1 = tf.nn.max_pool(act1, [1, 2, 2, 1], [1, 2, 2, 1], "SAME")
    # 加入L2正则
    tf.add_to_collection("losses",tf.nn.l2_loss(k1))
    tf.add_to_collection("losses",tf.nn.l2_loss(b1))

    ## 第二层卷积
    # 初始化权重
    k2 = tf.Variable(tf.random_normal([3, 3, 32, 64]))
    b2 = tf.Variable(tf.random_normal([64, ]))
    # 卷积
    conv2 = tf.nn.conv2d(pool1, k2, [1, 1, 1, 1], "SAME")
    # 激活
    act2 = tf.nn.relu(conv2 + b2)
    # 池化
    pool2 = tf.nn.max_pool(act2, [1, 2, 2, 1], [1, 2, 2, 1], "SAME")
    # 加入L2正则
    tf.add_to_collection("losses",tf.nn.l2_loss(k2))
    tf.add_to_collection("losses",tf.nn.l2_loss(b2))

    ## 第三层全连接
    # 把输入转化成一阶
    num = pool2.shape[1].value * pool2.shape[2].value * pool2.shape[3].value
    flat = tf.reshape(pool2, [-1, num])
    w3 = tf.Variable(tf.random_normal([num, 256]))
    b3 = tf.Variable(tf.random_normal([256, ]))
    # 线性操作
    l3 = tf.matmul(flat, w3) + b3
    # 激活
    act3 = tf.nn.relu(l3)
    # 加入dropout
    drop=tf.nn.dropout(act3,keep_prob=0.5)

    ## 第四层全连接(输出层)
    w4 = tf.Variable(tf.random_normal([256, 7]))
    b4 = tf.Variable(tf.random_normal([7, ]))
    # 线性操作
    l4 = tf.matmul(drop, w4) + b4

    return l4

def main():
    image_size=48
    batch_size=128
    labels_nums=7
    # 特征值占位
    input_data = tf.placeholder(tf.float32, [None, image_size, image_size, 1])
    # 目标值占位
    true = tf.placeholder(tf.float32, [None, 7])
    # 导入数据集
    dataset = pd.read_csv("./facial-keypoints-detector/train.csv")
    train_images,train_labels,test_images,test_labels=dataset_preprocess(dataset,image_size,labels_nums)

    # 损失函数
    model = face_detect_CNN_model(input_data)
    loss = tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(labels=true, logits=model))
    ##损失函数加入L2正则
    reg_losses = tf.add_n(tf.get_collection("losses"))
    loss = loss + 0.01*reg_losses
    loss_map = []

    # 优化器
    opti = tf.train.AdamOptimizer(0.001).minimize(loss)

    # 准确率
    predict = tf.nn.softmax(model)
    temp = tf.equal(tf.arg_max(predict, 1), tf.arg_max(true, 1))
    temp = tf.cast(temp, tf.float32)
    accurancy = tf.reduce_mean(temp)
    acc_map = []

    # 创建会话
    sess=tf.Session()
    sess.run(tf.global_variables_initializer())
    for i in range(100):
        batch_train_images=get_next_batch(train_images,i,batch_size)
        batch_train_labels=get_next_batch(train_labels,i,batch_size)
        sess.run(opti, {input_data:batch_train_images , true: batch_train_labels})
        loss_map.append(sess.run(loss, {input_data: batch_train_images, true: batch_train_labels}))
        acc_map.append(sess.run(accurancy, {input_data: test_images, true: test_labels}))

    plt.plot(list(range(len(loss_map))), loss_map)
    plt.show()
    plt.plot(list(range(len(acc_map))), acc_map)
    plt.show()


if __name__ == '__main__':
    main()

训练100次简单看一下效果


6.三天后补记

有没有发现这里的损失函数依然是大的惊人,而且非常地不科学,是不是有什么问题
其实我早就发现了,只是多遍检查后百思不得其姐姐,今天突然回过神来,睁大眼睛看一下损失函数这里


把损失函数改成reduce_mean之后果然正常了好多,这种马大哈问题不知道之前有几次一摸一样的了
改完后是酱紫的,也就损失函数的数值被平均后稍微好看些了吧

参考资料:
三个老外:《tensorflow深度学习》
Kaggle官方数据集
Github:EmotionDetectorUtils.py

← 【深度学习】Keras与TPU初体验 【python】生成器小试牛刀 →