Pytorch基于卷积神经网络的人脸表情识别(白话CNN+数据集+代码实战)

本文介绍了基于PyTorch的卷积神经网络(CNN)实现人脸识别,详细讲解了CNN的基础概念,包括卷积、池化、局部感受野、权值共享和池化层。通过数据预处理、模型搭建和训练,展示了如何构建和训练CNN模型,用于七种表情的分类任务。最后,文章提供了完整的代码示例。
摘要由CSDN通过智能技术生成

Pytorch基于卷积神经网络的人脸表情识别(白话CNN+数据集+代码实战)


前言

提示:本篇文章主要是关于李宏毅教授授课视频中的作业进行介绍,小白博主所作工作只是将现有的知识内容结合网路上一些优秀作者的总结以博文的形式加上自己的理解复述一遍。本文主要还是我的学习总结,因为网上的一些知识分布比较零散故作整理叙述。如果有不对的地方,还请帮忙指正。本文不产生任何收益,如有出现禁止转载、侵权的图片,亦或者文字内容请联系我进行修改。


  相关参考:  

  李宏毅2020机器学习深度学习(完整版)国语:链接: link.

  秋沐霖:基于卷积神经网络的面部表情识别(pytorch实现)链接: link

   liangchunjiang:详解CNN卷积神经网络 链接: link.

  Dean0Winchester.深度学习—图像卷积与反卷积(最完美的解释)链接: link.

  呆呆的猫.卷积神经网络超详细介绍 链接: link.

  斋藤康毅:深度学习入门(基于python的理论与实现)

  周志华:机器学习

  高扬,叶振斌:白话强化学习与pytorch

关于卷积神经网络(CNN)

 

卷积神经网络介绍

什么是卷积神经网络?

  卷积神经网络(Convolutional Neural Network, CNN)是一种带有卷积结构的有监督深度神经网络模型,借助卷积层的应用减小了深层网络占有的内存量,通过让卷积核提取图像特征来实现对图像的识别分类。如果你不理解这段话,先不用着急,让我们从头讲起。

机器如何理解一张图片?

为什么用CNN做图像识别

  让我们从自身理解图片的方式出发,当我们去识别一只鸟时,我们会下意识地去寻找它的“鸟嘴”,它的“翅膀”,它的“爪子”…当我们去识别一辆汽车时,我们会下意识地去寻找它的“轮子”,它的“车窗”…

  同样我们可以将这种思路运用在机器识别图片上,当我们让机器去“看”图片时,我们不需要它用整张图的视野大小去看,因为这样太费“脑子”(过多地占用内存)。我们可以创建一个小的过滤窗口(又称为卷积核,如下图红框所示),假设这个红框是个“鸟嘴”过滤窗口(用于识别框内图像是不是一个“鸟嘴”),那么我们可以从左至右,从上至下,用这个窗口扫描整张图片来判断这张图片中是否含有“鸟嘴”,此后我们还可以利用“翅膀”窗口,“爪子”窗口依次进行扫描,借助过滤窗口过滤得到的信息来判断这张图片是不是一只鸟。
 
在这里插入图片描述
 
  但实际上,我们并不会“告诉”机器它应该准备怎样的过滤器(卷积核)去识别图片,在机器进行学习之前的过滤器是随机初始化的,没有任何规律可言,也就是说在还没有开始学习图片之前,机器自身是并不知道它该借助怎么样的过滤器去识别图片的。

  那么我们该如何“教会”机器,学到正确的图片特征,形成好的过滤器呢。我们可以通过计算损失函数值然后利用误差反向传播算法来不断地调整,优化过滤器,直到我们的过滤器被优化得足够好,造成的损失足够小时,我们认为机器“找到”了正确的特征,能够利用这些特征来准确地识别图片。
 

如何理解有监督学习?

 
  有监督学习是机器学习中的一个分类,打个形象的比喻,有监督学习就好比老师(调参侠)让学生(机器)学会做选择题(分类),老师有很多考试卷(样本图片),这些卷子都是有答案(标签)的,我们让机器写这份卷子(对图片进行分类),将他写的答案(模型分类结果)与正确答案(正确标签)进行对比,给他扣分(计算损失函数),机器得到分数后回去“思考”该如何调整做题思路减少扣分(借助损失函数做梯度下降更新原来的模型参数)。
  而有监督就是指,机器是知道标准答案的(用于训练模型的样本是带标签的),从而可以借助“对答案”来及时调整自己的“答题思路”(更新模型参数)。
 

CNN中的卷积

用像素去理解图片

  我们知道所有的图片都由若干个横竖排列的像素点组合而成,其中每个像素点的取值范围为0~255,如果是一张彩色图片(RGB),那我们提取出来的像素矩阵将是3XmXn形式,如果是灰度图片,提取出来的像素矩阵将是1XmXn形式的。如下,我们有一个分辨率为4X4大小的灰色图片,我们将它用像素矩阵的形式进行表示。
在这里插入图片描述

图像与过滤器的卷积运算

  如下图,我们想要用大小为(3,3)的过滤器(卷积核)去扫描我们大小为(4,4)大小的图片。
在这里插入图片描述

  假设我们每次移动向右一步卷积核,那么形成卷积之后的新像素矩阵的过程如下图所示:
在这里插入图片描述
  如果设置有偏置值,则还需要向应用了滤波器的元素加上一个固定值(即偏置大小):
在这里插入图片描述
  如果不是灰度图片,而是三维RGB彩色图片,则我们只需要生成三个卷积核,对R、G、B三个通道进行卷积再求和即可。
在这里插入图片描述

CNN的三个基本思想

 
  卷积神经网络的三个核心思想:局部感受野、权值共享、池化层,CNN借助这三个思想实现网络参数,内存占用的减少,并获得一定程度的位移,从而提升网络性能。

局部感受野(local receptive fields)

  通过上述举例,我们可以得知图片中的联系可以是局部的,当我们找“鸟嘴”时,每个神经元(CNN中的每个卷积核我们可以看作是一个参与搭建神经网络的神经元)不需要“看”整张图片,只需要“看”(感受)这张图片的局部特征即可,然后再在更高层将这些感受到的不同的局部神经元综合起来就可以得到全局(整张图片)的信息了,这样就可以减少连接的数目。
  而感受野其实就是指的这个窗口的大小,因为一种窗口只能学到一个特征,但一类图片不止一个特征,所以我们需要多个窗口去提取多种特征整合成全局信息。

权值共享(shared weights)

  
在这里插入图片描述

  那我们再来理解一下权值共享:因为一个特征窗口我们只用来检测一种特征,那么我们可以把该窗口的权重值w,偏置值b(这里将窗口理解为一个神经元,我们可以根据之前所学习的神经元内部结构来理解这句话,不能理解的朋友可以保留疑问继续往下看)固定住,当窗口在扫描过程中发生移动时权重偏置参数不会发生改变,意味着一个窗口移动后仍然是同一个窗口,扫描检测的是同一种特征。
  注意,权值仅在同一窗口共享,不同窗口间不共享。

池化(pooling)

 池化可以理解为在卷积层上架了一个窗口,不用w,b训练,仅做求积、求max操作。这样可以有效压缩图片大小,节约内存空间。具体操作过程如下图所示:
在这里插入图片描述

在这里插入图片描述

CNN整体结构

 
  相比较DNN,CNN 中新增了 Convolution(卷积) 层 和 Pooling(池化) 层,我们可以把卷积层中的每个卷积核理解成我们之前在DNN中学习到的神经元。CNN 的层的连接顺序是“Convolution - ReLU -(Pooling)”(Pooling层有时会被省略)。这可以理解为之前的“Affi ne - ReLU”连接被替换成了“Convolution -ReLU -(Pooling)”连接。
  如下图所示搭建一个简单的CNN网络,图片像素矩阵输入后,先进入卷积层进行初步的特征提取,利用ReLU层对卷积之后的矩阵进行激活,添加非线性变化。此处往往会再后面再添加一层归一化操作,因为像素点的取值范围为0-255,但随着不断地卷积进行,很可能会导致像素点值超过取值范围,因此我们要及时进行归一化操作,将像素值保持在0-1之间,这样即可以避免数值超标,又可以聚集散落的数值点,方便之后的梯度下降更新。
  经过归一化操作之后,我们还需要进行池化操作,按一定规则提取池化窗口中最具代表性的某个像素点,舍去其他像素点,达到“压缩”信息的目的。
  然后我们可以重复搭建刚才卷积-激活-归一-池化的网络架构若干次,使得卷积核提取的特征逐渐具体化。之后我们将得到的很多小的像素矩阵进行展平成一个一维矩阵,利用全连接层,利用DNN(深度神经网络)的思想,将展平后的一维数组作为输入,搭建若干隐藏层,最后得到一个神经元个数等于分类类别数的输出层,利用softma层对最大概率的分类进行筛选,得到分类结果。(没有看懂的朋友可以带着疑问往下看,理解的朋友可以跳过。)
 
在这里插入图片描述
  卷积层:因为通过卷积运算我们可以提取出图像的特征,通过卷积运算可以使得原始信号的某些特征增强,并且降低噪声。用一个可训练的滤波器去卷积一个输入的图像(第一阶段是输入的图像,后面的阶段就是卷积特征了),然后加一个偏置,得到卷积层。
  池化层:因为对图像进行下采样,可以减少数据处理量同时保留有用信息,采样可以混淆特征的具体位置,因为某个特征找出来之后,它的位置已经不重要了,我们只需要这个特征和其他特征的相对位置,可以应对形变和扭曲带来的同类物体的变化。
  全连接层:采用softmax全连接,得到的激活值即卷积神经网络提取到的图片特征。
 
  如下图,省略中间的ReLU-Normlize(激活-归一),可以分成两段,一段是我们卷积池化的反复过程,另一段是展平后的全连接层。
  其中卷积和池化层可以重复多次。
在这里插入图片描述

CNN的可视化

 

第一层权重的可视化

 
  CNN到底用卷积层“观察”到了什么呢?我们以下面这张图片为例进行举例,通过卷积层可视化来探索CNN中到底进行了什么处理。
 
在这里插入图片描述
 
  学习前的滤波器是随机进行初始化的,所以在黑白的浓淡上没有规律可循,但学习后的滤波器变成了有规律的图像。我们发现,通过学习,滤波器被更新成了有规律的滤波器,比如从白到黑渐变的滤波器、含有块状区域(称为blob)的滤波器等。
 
在这里插入图片描述

 
  如果要问下图中训练后的有规律的滤波器在“观察”什么,答案就是它在观察边缘(颜色变化的分界线)和斑块(局部的块状区域)等。比如,左半部分为白色、右半部分为黑色的滤波器的情况下,如图所示,会对垂直方向上的边缘有响应。
 
在这里插入图片描述
 
  上图中显示了选择两个学习完的滤波器对输入图像进行卷积处理时的结果。我们发现“滤波器1”对垂直方向上的边缘有响应,“滤波器2”对水平方向上的边缘有响应。由此可知,卷积层的滤波器会提取边缘或斑块等原始信息。而刚才实现的CNN会将这些原始信息传递给后面的层。
 

基于分层结构的信息提取

 
  上面的结果是针对第1层的卷积层得出的。第1层的卷积层中提取了边缘或斑块等“低级”信息,那么在堆叠了多层的CNN中,各层中又会提取什么样的信息呢?根据深度学习的可视化相关的研究,随着层次加深,提取的信息(正确地讲,是反映强烈的神经元)也越来越抽象。图中展示了进行一般物体识别(车或狗等)的8层CNN(5层卷积层+2层全连接层+softmax 层)。这个网络结构的名称是AlexNet。AlexNet网络结构堆叠了多层卷积层和池化层,最后经过全连接层输出结果。图中的方块表示的是中间数据,对于这些中间数据,会连续应用卷积运算。
 
在这里插入图片描述
在这里插入图片描述
  CNN的卷积层中提取的信息。第1层的神经元对边缘或斑块有响应,第3层对纹理有响应,第5层对物体部件有响应,最后的全连接层对物体的类别(狗或车)有响应。

卷积神经网络图片分类各层应用实例

在这里插入图片描述

基于卷积神经网络的人脸识别

此处主要参考:秋沐霖:基于卷积神经网络的面部表情识别(pytorch实现)

数据集介绍

  数据集地址:数据集地址:https://pan.baidu.com/s/1hwrq5Abx8NOUse3oew3BXg ,提取码:ukf7
  数据集train.csv,大小为28710行X2305列,每一行包含有label,feature两部分。
在这里插入图片描述

  其中label为图片标签,取值范围为0~6,分别代表了七种表情:(0)生气,(1)厌恶,(2)恐惧,(3)高兴,(4)难过,(5)惊讶和(6)中立(无表情)。如下图所示:
在这里插入图片描述
  feature部分包含2304个数值,代表48X48(分辨率)的人脸图片像素值,其中每个像素值的取值范围为0~255。利用openCV库读取像素矩阵转存为图片如下所示:
在这里插入图片描述
  那我们要做的就是搭建一个CNN网络,让我们训练出来的模型可以尽可能准确地识别图像表情标签。

数据预处理

  因为我们现在手里的文件是csv格式,我们需要先将像素数据还原成图片进行保存,python提供专业的图像处理库openCV,我们将采用openCV库实现我们的图片还原。

数据分离

  在原文件中,标签label与人脸像素数据是在一起的,为了方便之后的训练操作,我们利用数据处理库pandas进行数据分离,我们利用read_csv()函数将csv文件以DataFrame的数据类型读取出来,进行缺失值的值填充,之后将label与feature标签进行拆分,分别保存为label.csv和data.csv两个文件,并指定一个路径,借助openCV库进行图片还原保存:

import pandas as pd
import cv2

def DataProcess():
    # 数据预处理
    # 将label与人脸数据作拆分
    path = 'D:\\python_code\\train.csv'  # 文件路径
    df = pd.read_csv(path)  # pd阅读器打开csv文件
    df = df.fillna(0)  # 空值填充

    # 分别提取标签和特征数据
    df_y = df[['label']]
    df_x = df[['feature']]

    # 将label,feature数据写入csv文件
    df_y.to_csv('label.csv', index=False, header=False)  # 不保存索引(0-N),不保存列名('label')
    df_x.to_csv('data.csv', index=False, header=False)

    # 指定存放图片的路径
    path = 'D:\\python_code\\face'
    # 读取像素数据
    data = np.loadtxt('data.csv')

    # 按行取数据
    for i in range(data.shape[0]):  # 按行读取
        face_array = data[i, :].reshape((48, 48))  # reshape 转成图像矩阵给cv2处理
        cv2.imwrite(path + '//' + '{0}.jpg'.format(i), face_array)  # csv文件转jpg写图片

创建data-label对照表

  首先,我们需要划分一下训练集和验证集。在本次作业中,共有28709张图片,取前24000张图片作为训练集,其他图片作为验证集。新建文件夹train和val,将0.jpg到23999.jpg放进文件夹train_data,将其他图片放进文件夹test_data。
在这里插入图片描述

  在继承torch.utils.data.Dataset类定制自己的数据集时,由于在数据加载过程中需要同时加载出一个样本的数据及其对应的label,因此最好能建立一个data-label对照表,其中记录着data和label的对应关系。

  有童鞋看到这里就会提出疑问了:在人脸可视化过程中,每张图片的命名不都和label的存放顺序是一一对应关系吗,为什么还要多此一举,再重新建立data-label对照表呢?原代码作者秋沐霖是这样回答的.

  “笔者在刚开始的时候也是这么想的,按顺序(0.jpg, 1.jpg, 2.jpg…)加载图片和label(label[0], label[1], label[2]…),岂不是方便、快捷又高效?结果在实际操作的过程中才发现,程序加载文件的机制是按照文件名首字母(或数字)来的,即加载次序是0,1,10,100…,而不是预想中的0,1,2,3…,因此加载出来的图片不能够和label[0],label[1],lable[2],label[3]…一一对应,所以建立data-label对照表还是相当有必要的。”

  简单来说就是,在文件夹中保存图片并不一定是按照名称顺序来排序的,如果出现错乱,就会导致图片标签不对应的情况,因此单独创建一个data-label对应表是有必要的。

  建立data-label对照表的基本思路就是:指定文件夹(train或val),遍历该文件夹下的所有文件,如果该文件是.jpg格式的图片,就将其图片名写入一个列表,同时通过图片名索引出其label,将其label写入另一个列表。最后利用pandas库将这两个列表写入同一个csv文件。

  执行这段代码前,注意修改相关文件路径。代码执行完毕后,会在train_data和label_data文件夹下各生成一个名为dataset.csv的data-label对照表。

import pandas as pd
import os

def data_label(path):
    # 读取label文件
    df_label = pd.read_csv('label.csv',header = None)

    # 查看文件夹下所有文件
    files_dir = os.listdir(path)

    # 用于存放图片名
    path_list = []

    # 用于存放图片对应的label
    label_list = []

    # 遍历该文件夹下的所有文件
    for files_dir in files_dir:
        # 如果某文件是图片,则其文件名以及对应的label取出,分别放入path_list和label_list这两个列表中
        if os.path.splitext(files_dir)[
评论 50
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值