使用PaddlePaddle框架实现CK+表情数据集的识别

一、前期准备

1.PaddlePaddle-gpu安装

如果是在windows上安装,需要注意以下几点:
(1)win7系统上PaddlePaddle最高只支持CUDA 8.0,可点击这里前往官网下载CUDA,以及点击这里官网下载cuDNN(或者前往百度网盘下载CUDA 8.0和相应的cuDNN)。
(2)win10家庭版无法使用PaddlePaddle-gpu,虽然可以通过pip install命令正常安装PaddlePaddle,但是无法使用,下图官方文档给的说明中不包含有win10家庭版
(3)如果是win10专业版或企业版安装CUDA,推荐安装CUDA 9.0,这里小编亲测可用,点击这里前往下载win10系统的CUDA 9.0和相应的cuDNN吧。最后关于CUDA的安装过程很简单,运行安装程序默认安装就行,然后把cuDNN中的库放到CUDA对应的安装目录下。

在这里插入图片描述

安装paddlepaddle-gpu时注意,需要以管理员权限运行cmd,然后执行pip install paddlepaddle-gpu下载安装即可,如果想指定安装的版本,则执行pip install paddlepaddle-gpu==x.x.x(现有的paddlepaddle-gpu versions如下: 1.3.0, 1.3.1, 1.3.2, 1.4.0, 1.4.1, 1.5.0.post87, 1.5.0.post97, 1.5.1.post87, 1.5.1.post97)。

2.CK+数据集下载

官网下载链接请点这里:http://www.consortium.ri.cmu.edu/ckagree/
在这里插入图片描述
不过需要提交一些信息,邮箱很重要,因为它会把下载链接、用户名和口令发送到你的邮箱。
在这里插入图片描述
当然,为了给看到此文的小伙伴们提供便利,我已经将CK+数据集上传至了百度网盘,点击这里前往网盘下载CK+数据集吧。

二、CK+数据集的预处理

1.数据集结构介绍

首先来看一下CK+数据集的文件,主要包含cohn-kanade-images.zipEmotion_labels.zip这两部分,这两个大文件夹里面的子文件夹是相同的(如下图所示),其实每个子文件夹(如’S005’)是表示一个人的表情图片,因此该数据集是采集了123人的表情图片。

在这里插入图片描述
然后进入第一层子文件夹(比如’S014’)中,可以看到其包含有若干个子文件夹(‘001’ ~ ‘006’不等),再进入第二层子文件(比如’001’)中,可以看到里面是一个人所做的若干表情图片,这些图片连续起来其实是这个人做这个表情从平静到激动的变化过程。同样Emotion_labels文件夹中也是这样的结构,只是最里层的是存储标签值的txt文件,这里提醒下原始的标签值是1~7。

在这里插入图片描述
在这里插入图片描述

2. 表情图片以及标签的读取

源代码如下所示,首先是获取二级子目录的列表,得到这样的list: [‘S005\001’, ‘S010\001’, ‘S010\002’, …];然后就是遍历子目录去获取表情图片和对应的标签文件;最后将表情数据和标签值绑定在一起得到这样的list: [[data1, label1], [data2, label2], …]

 	# 获取子目录列表, like ['S005\001', 'S010\001', 'S010\002', ...]
    dir_list = []
    for root, dirs, _ in os.walk(image_dir):
        for rdir in dirs:
            for _, sub_dirs, _ in os.walk(root + '\\' + rdir):
                for sub_dir in sub_dirs:
                    dir_list.append(rdir + '\\' + sub_dir)
                break
        break
	
	# like [[data1, label1], [data2, label2], ...]
    data_label = []

    # 遍历子目录获取文件
    for path in dir_list:
        # 处理 images
        for root, _, files in os.walk(image_dir + '\\' + path):
            for i in range(0, len(files)):
                if files[i].split('.')[1] == 'png':
                    # 裁剪图片,并将其转为数据矩阵
                    img_data = image_to_matrix(root + '\\' + files[i])
                    # 处理相应的 label
                    for lroot, _, lfiles in os.walk(label_dir + '\\' + path):
                        if len(lfiles) > 0:  # picture has label
                            label = get_label(lroot + '\\' + lfiles[0])
                            data_label.append([img_data, label])
                        break
            break

3. 表情图片的处理

首先是进行脸部中心区域的检测,这里使用了opencv进行人脸识别;其次就是进行图片灰度化,然后对识别出的脸部中心区域进行裁剪,实现的代码如下:

	# 裁剪人脸部分
	def image_cut(file_name):
	    # cv2读取图片
	    im = cv2.imread(file_name)
	    gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
	    # cv2检测人脸中心区域
	    faces = face_cascade.detectMultiScale(
	        gray,
	        scaleFactor=1.15,
	        minNeighbors=5,
	        minSize=(5, 5)
	    )
	
	    if len(faces) > 0:
	        for (x, y, w, h) in faces:
	            # PIL读取图片
	            img = Image.open(file_name)
	            # 转换为灰度图片
	            img = img.convert("L")
	            # 裁剪人脸核心部分
	            crop = img.crop((x, y, x + w, y + h))
	            # 缩小为120*120
	            crop = crop.resize((purpose_size, purpose_size))
	            return crop
	    return None

裁剪完成之后,还需要把图片转换为数据矩阵,实现的方法如下所示,需要注意的是这里把数据进行归一化到0~1之间,为的是让后面模型训练的时候能够尽快提取到特征,从而加速模型的收敛。

	# 将图片转换为数据矩阵
	def image_to_matrix(filename):
	    # 裁剪并缩小
	    img = image_cut(filename)
	    data = img.getdata()
	    # 归一化到(0,1)
	    return np.array(data, dtype=float) / 255.0

4. 标签值的处理

标签值的处理比较简单,首先就是读取txt文件中的标签值,然后就是将原始的标签值1-7转换为0-6,因为多元分类问题标签一般都是从0开始,这也符合平时对list、set等元素的索引顺序。

	# 获取文件中的label值
	def get_label(file_name):
	    f = open(file_name, 'r+')
	    line = f.readline()  # only one row
	    line_data = line.split(' ')
	    label = float(line_data[3])
	    f.close()
	    # 1-7 的标签值转为 0-6
	    return int(label) - 1

5. 数据的保存

将第1步中得到的data_label(形如 [[data1, label1], [data2, label2], …] ,list类型)保存为pickle文件,因为pickle本身是用来存大量的数据的,而且通过pickle文件读取数据的效率比较高。

	# 写入数据到pkl文件
    pkl_file = '../data/data_label_list_120.pkl'
    with open(pkl_file, 'wb') as f:
        pickle.dump(data_label, f)
        f.close()

三、构建神经网络模型

该神经网络模型通过paddlepaddle中的fluid来实现,主要包含两个卷积层、两个池化层、一个全连接层和一个softmax输出层,具体的实现代码如下。关于每个层的参数在代码中都有注释介绍,就不赘述了。

	def convolutional_neural_network(img):
	    """
	    定义卷积神经网络分类器:
	        输入的二维图像,经过两个卷积-池化层,一个全连接层,使用以softmax为激活函数的输出层
	
	    Return:
	        prediction -- 分类的结果
	    """
	    # 第一个卷积-池化层
	    # 使用32个5*5的滤波器,池化大小为3,池化步长为2,激活函数为Relu
	    conv_pool_1 = fluid.nets.simple_img_conv_pool(
	        input=img,
	        filter_size=5,
	        num_filters=32,
	        pool_size=3,
	        pool_stride=2,
	        act='relu')
	    conv_pool_1 = fluid.layers.batch_norm(conv_pool_1)
	    # 第二个卷积-池化层
	    # 使用64个5*5的滤波器,池化大小为3,池化步长为2,激活函数为Relu
	    conv_pool_2 = fluid.nets.simple_img_conv_pool(
	        input=conv_pool_1,
	        filter_size=5,
	        num_filters=64,
	        pool_size=3,
	        pool_stride=2,
	        act='relu')
	    # 全连接层,输出为512维特征
	    fc1 = fluid.layers.fc(input=conv_pool_2, size=512, act=None)
	    # 以softmax为激活函数的全连接输出层,输出层的大小必须为类别数7
	    prediction = fluid.layers.fc(input=fc1, size=7, act='softmax')
	    return prediction

四、模型的训练和测试

1. 数据的读取

主要就是读取之前保存好的pickle文件,然后将所有数据打乱后按照8:2来划分训练集和测试集,读取pickle文件借助的是pandas.read_pickle()方法,读出来后的数据类型与存储的一致,即依然是最初的data_label(list类型)。

	# 读取pkl文件的数据
	def read_data(file_name):
	    data = pd.read_pickle(file_name)
	    # 随机打乱后划分数据集,测试集占0.2
	    train_data, test_data = train_test_split(data, shuffle=True, test_size=0.2, random_state=42)
	    return train_data, test_data

2. batch数据的生成

为了提高程序执行的效率,这里生成batch数据借助了yield函数,它能够返回一个generator迭代器,不仅能够降低数据对内存的消耗,还能够加速程序的运行。

	def generator_batches(x, bsize=50):
	    """
	    generator batch data from x
	    :param x: list x, like [[train_data, train_label], ...]
	    :param bsize: batch size
	    :return: generator batch data
	    """
	    n = len(x) // bsize
	    x = x[:n * bsize]
	    for i in range(0, len(x), bsize):
	        yield x[i:i + bsize]

3. 配置训练程序

训练程序主要是调用自定义的神经网络来训练,得到预测的结果,其中主要是使用交叉熵作为损失函数,最后返回的预测结果、平均损失和正确率这些信息。

	def train_program(img, label):
	    """
	    配置train_program
	
	    Return:
	        prediction -- 分类的结果
	        avg_cost -- 平均损失
	        acc -- 分类的准确率
	    """
	    prediction = convolutional_neural_network(img)  # 使用自定义的卷积神经网络
	    # 使用类交叉熵函数计算predict和label之间的损失函数
	    cost = fluid.layers.cross_entropy(input=prediction, label=label)
	    # 计算平均损失
	    avg_cost = fluid.layers.mean(cost)
	    # 计算分类准确率
	    acc = fluid.layers.accuracy(input=prediction, label=label)
	    return prediction, [avg_cost, acc]

4. 构建测试程序

测试程序主要是在评估训练的模型的好坏,并返回模型在测试集上的好坏评估指标(平均损失值和平均准确率),神经网络中一般需要在模型训练的过程中进行多轮测试。

	# 训练过程中测试模型的好坏
	def train_test(test_program, feeder, test_data, batch_size):
	    # 将分类准确率存储在acc_set中
	    acc_set = []
	    # 将平均损失存储在avg_loss_set中
	    avg_loss_set = []
	    # 将测试集 yield 出的每一个数据传入网络中进行训练
	    for data in generator_batches(test_data, batch_size):
	        acc_np, avg_loss_np = exe.run(
	            program=test_program,
	            feed=feeder.feed(data),
	            fetch_list=[acc, avg_loss])
	        acc_set.append(float(acc_np))
	        avg_loss_set.append(float(avg_loss_np))
	    # 获得测试数据上的准确率和损失值
	    acc_val_mean = np.array(acc_set).mean()
	    avg_loss_val_mean = np.array(avg_loss_set).mean()
	    # 返回平均损失值,平均准确率
	    return avg_loss_val_mean, acc_val_mean

五、模型的运行、保存和加载

1. 主运行程序和模型的保存

主程序的功能就是读取数据,定义神经网络的输入和输出、一些超参数(epochs、batch_size、learning_rate等)、fluid的执行器等,以及设置模型的训练过程保存模型等。

	if __name__ == '__main__':
	
	    pkl_file = '../data/data_label_list_120.pkl'
	    train_data, test_data = read_data(pkl_file)
	
	    # 一个minibatch中有64个数据
	    batch_size = 52
	    # 该模型运行在CPU上
	    use_cuda = True  # 如想使用GPU,请设置为 True
	    place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
	    # 创建执行器
	    exe = fluid.Executor(place)
	
	    # 输入的原始图像数据,大小为120*120*1
	    img = fluid.layers.data(name='img', shape=[1, image_size, image_size], dtype='float32')
	    # 标签层,名称为label,对应输入图片的类别标签
	    label = fluid.layers.data(name='label', shape=[1], dtype='int64')
	    # 告知网络传入的数据分为两部分,第一部分是img值,第二部分是label值
	    feeder = fluid.DataFeeder(feed_list=[img, label], place=place)
	
	    # 调用train_program 获取预测值,损失值,
	    prediction, [avg_loss, acc] = train_program(img, label)
	    # 选择Adam优化器
	    optimizer = fluid.optimizer.Adam(learning_rate=0.001)
	    optimizer.minimize(avg_loss)
	    # 训练的轮数
	    epochs = 10
	    # 将模型参数存储在名为 save_dirname 的文件中
	    save_dirname = '../model/cnn_paddle.model'
	    # 设置 main_program 和 test_program
	    main_program = fluid.default_main_program()
	    test_program = fluid.default_main_program().clone(for_test=True)
	
	    exe.run(fluid.default_startup_program())
	
	    # 开始训练
	    lists = []
	    step = 0
	    for epoch_id in range(0, epochs):
	        for data in generator_batches(train_data, batch_size):
	            metrics = exe.run(main_program,
	                              feed=feeder.feed(data),
	                              fetch_list=[avg_loss, acc])
	            if step % 50 == 0:  # 每训练50次 打印一次log
	                print("Pass %d, Batch %d, Cost %f" % (step, epoch_id, metrics[0]))
	            step += 1
	
	        # 测试每个epoch的分类效果
	        avg_loss_val, acc_val = train_test(test_program, feeder, test_data, batch_size)
	        print("Test with Epoch %d, avg_cost: %s, acc: %s" % (epoch_id, avg_loss_val, acc_val))
	        lists.append((epoch_id, avg_loss_val, acc_val))
	
	        # 保存训练好的模型参数用于预测
	        if save_dirname is not None:
	            fluid.io.save_inference_model(save_dirname,
	                                          ['img'], [prediction], exe,
	                                          model_filename=None,
	                                          params_filename=None)
	
	    # 选择效果最好的pass
	    best = sorted(lists, key=lambda lt: float(lt[1]))[0]
	    print('Best pass is %s, testing Avgcost is %s' % (best[0], best[1]))
	    print('The classification accuracy is %.2f%%' % (float(best[2]) * 100))
	
	    # 训练完成后可进行预测
	    # predict()
	
	    print('\n--------------------------Program Finished---------------------------\n')

2. 加载模型和预测

当训练好的模型保存成功后,调用该函数能够加载保存好的模型,然后对输入的表情图片进行预测。

	def predict(save_dirname, image_file):
		"""
	    加载保存的模型进行预测
	    :param save_dirname: 模型保存的路径
	    :param image_file: 要预测的表情图片路径
	    :return: 
	    """
	    img_data = image_to_matrix(image_file)
	    # 转为输入所需的大小,注意需要转换为float32类型
	    img_data = img_data.reshape((1, 1, image_size, image_size)).astype(np.float32)
	    use_cuda = False  # 如想使用GPU,请设置为 True
	    place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
	    # 创建执行器
	    exe = fluid.Executor(place)
	    exe.run(fluid.default_startup_program())
	    inference_scope = fluid.core.Scope()
	
	    with fluid.scope_guard(inference_scope):
	        # 使用 fluid.io.load_inference_model 获取 inference program desc,
	        # feed_target_names 用于指定需要传入网络的变量名
	        # fetch_targets 指定希望从网络中fetch出的变量名
	        [inference_program, feed_target_names,
	         fetch_targets] = fluid.io.load_inference_model(
	            save_dirname, exe, None, None)
	
	        # 将feed构建成字典 {feed_target_name: feed_target_data}
	        # 结果将包含一个与fetch_targets对应的数据列表
	        results = exe.run(inference_program,
	                          feed={feed_target_names[0]: img_data},
	                          fetch_list=fetch_targets)
	        lab = np.argsort(results)
	        # 打印图片的预测结果,结果 +1 转为最初的 1-7
	        print('Inference result of ' + image_file + ' is: %d' % (lab[0][0][-1] + 1))

六、致谢

github链接

本博文中讲述的所有代码,以及整个项目代码都已在github上公开,欢迎大家下载学习,点击这里前往github仓库。如果你觉得这篇博客不错,请记得点赞哦~ 当然如果有任何问题,也欢迎大家提问留言,谢谢。

  • 10
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
### 回答1: ck表情识别数据集是一个广泛应用于计算机视觉领域的数据集,用于情感识别表情分类任务。数据集包含了来自不同人的面部表情图像,涵盖了7种不同的情感或表情类别,即“生气”、“厌恶”、“恐惧”、“高兴”、“伤心”、“惊讶”和“中立”。 数据分布表是指按照不同类别或情感对数据进行统计和分布的一张表格。在ck表情识别数据集的数据分布表中,每一列代表一种情感类别,每一行代表一个样本。表中的元素表示对应样本是否属于对应情感类别,通常用二进制表示,1表示属于,0表示不属于。 在ck表情识别数据集的数据分布表中,可以统计每一类情感所占的比例,即每个类别下的样本数量占总样本数量的比例。通过观察数据分布表,我们可以了解到每个情感类别的数据量是否平衡,是否存在样本数量过少或过多的情况。 这样的数据分布信息对于训练和评估基于ck数据集表情分类模型非常重要。如果某个情感类别的数据量过少,可能导致模型对该类别的分类效果不佳。因此,可以根据数据分布表的信息,对数据集进行采样或调整,使得各个情感类别的数据量相对均衡,从而提高模型的泛化能力和识别准确率。 总而言之,ck表情识别数据集的数据分布表展示了数据集中不同情感类别的样本数量和分布情况,帮助我们了解和调整数据集,从而提高表情识别模型的性能。 ### 回答2: ck 表情识别数据集是用于进行情绪识别的一个研究数据集,其中包含了来自于13位志愿者的表情图像,共计327个样本。这些样本涵盖了包括快乐、悲伤、厌恶、惊讶、愤怒和恐惧等六种不同的情绪。 数据分布表将这些样本按照情绪类别进行了分类,并统计了每个情绪类别中样本的数量。根据数据分布表,可以清晰地了解到每种情绪类别的样本数量以及相对比例。 通过对数据分布表的分析,我们可以发现一些有趣的事实。首先,快乐和悲伤是数据集中最常见的两种情绪,分别占据了样本总数的30%和23%。而相对而言,恐惧是样本数量最少的情绪,仅占据了总数的5%。这些数据反映出了这些情绪在日常生活中的普遍程度。 其次,我们可以进一步观察各种情绪类别的变化趋势。例如,愤怒和厌恶在数据集中的数量相对较少,这可能与这两种情绪往往相对较为稀少有关。而惊讶在数据集中的数量较为均衡,说明这种情绪在样本中普遍存在。 最后,在进行模型训练和测试时,我们可以根据数据分布表的信息来调整样本的处理方式。例如,如果我们希望模型更好地识别恐惧情绪,可以通过数据增强技术增加恐惧情绪的样本数目,以增加模型在这一类别的训练效果。 综上所述,ck 表情识别数据集的数据分布表提供了样本分布情况的详细信息,它对于研究人员们进行情绪识别研究以及模型优化都具有重要指导意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值