基于深度学习的人脸表情识别系统

基于深度学习的人脸表情识别系统

摘 要

随着社会的进步和经济的发展,人工智能已经开始应用于各种各样的场景,最典型的应用就是机器人的应用。人机交互的设计已经越来越成熟,而机器人要想了解人的正确想法就不应仅体现在语言上,还应该在其他方面分析出人的正确情感,表情识别分析就是一个主要攻克点。本文从自然神经网络的介绍一直到运用卷积神经网络搭建模型进行具体的分析,包括自然神经网络和卷积神经网络的知识归纳,包括一些数学推导过程,对模型搭建过程和结果分析,会将所用的知识点进行详细归纳让人更好地理解和实现实际的模型搭建。本文的模型搭建最终使用2012年ILSVRC的冠军AlexNet实现,且会对模型进行分析。结尾也会适当提出运用VGG网络、ResNet、GoogLeNet等具有较多层神经网络的方法对模型的改进。
关键词:表情识别、卷积神经网络、知识归纳、数学推导、AlexNet

Facial expression recognition system based on deep learning

Abstract
With the progress of society and the development of economy, artificial intelligence has been applied in a variety of scenarios, the most typical application is the application of robots. The design of human-computer interaction has become more and more mature. In order to understand the correct thoughts of people, robots should not only be reflected in language, but also analyze the correct emotions of people in other aspects. Facial expression recognition analysis is a major breakthrough point. This article from the nature of the neural network is introduced to use convolution neural network to build model for concrete analysis, including natural neural network and the convolution of the neural network knowledge, including some mathematical derivation, analysis on the structures, processes and results of the model, will be used in the knowledge points in detail summarized make people better understand and realize the actual model is set up. The model building in this paper is finally implemented by AlexNet, the champion of ILSVRC in 2012, and the model will be analyzed. At the end of the paper, it will also put forward the improvement of the model by using VGG network, ResNet, GoogLeNet and other methods with more layers of neural networks.
Keywords: Facial expression recognition ; convolutional neural network; Knowledge of induction ; Mathematical deduction ; AlexNet ;

目 录

1 绪论
1.1 人脸表情识别的目的和现实意义 1
1.2 人脸表情识别技术国内外现状 1
1.3 环境的搭建 2
1.3.1 Python 2
1.3.2 Anaconda3 3
1.3.3 jupyter notebook 3
2深度学习的基本原理
2.1引言 3
2.2自然神经网络的基本原理 3
2.3卷积神经网络的基本原理 5
2.3.1引言 5
2.3.2 CPU与GPU 5
2.3.3激活函数与学习率 5
2.3.4学习率 6
2.3.5感受野和卷积核 6
2.3.6卷积层和池化层 7
2.3.7填充 8
2.3.8全连接层 8
2.3.9特征归一化 9
2.3.10损失函数与优化器 9
2.3.11 dropout(正则化) 10
2.3.12 BatchNormalization 10
2.3.13混淆矩阵和ROC曲线 11
2.3.14 epochs与batch_size 11
2.3.15 training loss与validation loss的关系 11
3基于卷积神经网络的人脸表情识别
3.1引言 11
3.2数据的预处理 12
3.3图片的转换 13
3.4搭建模型 15
3.5直接用.csv文件中的数据进行模型搭建与训练 15
3.6最终模型展示 17
3.7结果分析 19
3.8改进过程分析 20
4关于VGG网络、ResNet、GoogLeNet的图像处理
4.1引言 21
4.2 VGG网络 21
4.3 ResNet 21
4.4 GoogLeNet 21
5总结
参考文献 23
致谢 24
附录 25

1 绪论

人工智能是近几年的热点,python的简易学习和GPU的发展,极大的促进了机器学习和深度学习的发展,为了解决各种问题演化出了各种神经网络如:自然神经网络、卷积神经网络等。随着人们不断地去学习和优化,各种各样的模型被人们搭建出来,也出现了各种算法。本文将从基础的自然神经网络开始,一直到用卷积神经网络进行图像处理,会详细的分析和总结所有知识点,供大家学习参考。
1.1人脸表情识别的目的和现实意义
随着科技的不断进步,人们对机器人以及其他人工智能的研究已经越来越成熟,人机的交互一直是机器人研究的一个热门,怎么样才能使机器人更好的理解和读懂人们的意思并进行正确的行为是一个重要的课题。那怎么样才能让机器人准确的分析出人此时此刻的心理状态呢?
一提到这个问题,人们的主要思路肯定是对机器人本身的词库进行拓展,并且设成堆的关键词,让机器人准确的抓取人们语言中所要表达的情感。但是曾经有个著名的心理学家提出,人类在生活交流就是一种信息的传递,而用语言进行信息传递的方式只占总体的7%,肢体语言及其他方式占总体的百分之38%,通过人脸表情进行信息传达的方式高达55%。在生活中,可以从一个人的表情分析出一个人此时此刻的心理状态,哲学家将人的表情分为七大类,分别是开心、悲伤、恐惧、愤怒、厌恶、惊讶、中性七种。这说明如果能用一个模型进行人脸表情的识别和分析就能对人们此时此刻的心情状态进行准确的分析,这对机器人分析出人的情感的帮助远远大于对关键词的分析,对于机器人的发展以及实际应用是一个巨大的飞跃。
1.2人脸表情识别技术国内外现状
早在19世纪,达尔文就发表过一篇文章《论人类和动物的表情》,表面了表情的一致性,说明表情是不按性别与种族来判断的。Facial Expression Recognition (简称FER)在国外已经较为成熟,因为在几年前就举办了关于表情识别的比赛,比如2013年的FER2013,Emotiw等。因为各种神经网络都是国外的大牛提出,所以国内在表情识别领域一直处于追赶的一方。但借助互联网平台可以方便与国外的深度学习进行交流和探讨,国内的深度学习算法也发展得很快,总体上也不算太过于落后。
人脸表情识别技术主要分为两部分,一部分是在图片中将表情特征进行提取,然后进行模型的搭建与训练,另一部分就是人脸的检测。人脸的检测分为动态检测和静态检测,静态检测要对图片进行处理对人脸进行扫描和识别,运用各种算法进行特征提取;动态监测要通过特定的摄像头进行动态人脸的识别扫描和处理后传送给模型,其中涉及到3D模型的算法,传给模型的过程中也需要进行类似静态检测的过程对人脸进行扫描和识别。人脸识别是深度学习和机器视觉的一个交互学科和热点,本文不作着重解释。
对于表情特征提取现国内外具有很多热门的算法,主要有几何特征提取、运动特征的提取、统计特征提取和频率域特征提取。几何提取就是对人脸表情的各个特征进行提取和分析,如眼睛、嘴巴等,这是最原始的做法也最好理解和实现,本文也是运用CNN网络对人脸表情进行提取后识别表情。
对于其他特征提取这里也介绍几个现阶段国内外热门的人脸表情识别方法:
1.Gabor变换:通过不同的卷积核频率对图像进行多频率的分析,可以有效的提取不同空间位置中不同细节程度的特征,经常与支持向量机一起使用。在空间域中有较好的分辨能力且有明显的方向选择性。但如果是低维度的特征会出现识别和匹配困难的问题,样本少时准确率也会大大降低;
2.支持向量机:是一种人类识别的分类器,常与Gabor滤波器一起使用,但是当样本过大时计算量会很大,学习的过程会很复杂;
3.光流法:将运动图像用函数表示,根据图像强度守恒原理建立光流约束方程,通过求解方程计算运动参数。可以反映人脸表情变化的实际规律,并且受外界的影响较小,尤其是光照的变化对识别率不会产生太大的变化。但是因为计算量大和识别模型、算法较复杂,在小样本模型中适用性小;
4.隐马尔模型:由观察的面部表情序列模型计算面部表情序列的概率,选用最佳准则来决定状态的转移,得到相应的模型参数。采用隐马尔模型的识别准确率较高,但是对前期的面部表情序列模型要求较高,对表情识别算法的准确率也有较大的影响。
1.3环境的搭建
要想项目顺利进行,环境的搭建是必不可少的一步。首先需要明确想要的工作环境,我是在window系统下进行项目的进行。(对以后想从事深度学习工作的学者建议使用Linux系统,以为企业的项目一般都会用Linux系统进行,提前熟悉有助于以后就业).
1.3.1 Python
第一步是安装python3.6.3(3.6版本的python对代码和各种源码包的兼容性更强)
Python的主要的功能就是将编程语言一行一行地进行转译,每转译一行程序就会得到相应的叙述,然后才会进行下一行程序的转译,就像和机器人互动一样,每给他一条或多条指令后会得到相应的回复后再编译下一个指令。安装完python要配置环境变量,在我的电脑-属性-高级设置-环境变量里进行添加。
1.3. 2 Anaconda3
第二步是安装Anaconda3,Anaconda3是Python的发行版本,集成了python和常见的软件库,比如numpy、pandas等,因为集成了很多软件库所以文件比较大。这里要注意一点每一个软件库都是分版本的,比如你在网上找的一个项目想验证一下,但是可能因为numpy的版本不匹配导致代码编译失败,这时可用Anaconda自带的虚拟环境进行软件库指定安装,用conda指定版本安装。
1.3. 3 jupyter notebook
第三步是安装jupyter notebook,jupyter Notebook也可以称为Python notebook。是一个交互式笔记本,可以在运行代码时像word操作一样可以做大量解释说明。它就是一个网页程序,可以理解为运用浏览器编写Python文件, 一般可用于机器学习、数据清理和转换和统计建模等。jupyter Notebook可以直接用Anaconda自带的虚拟环境用pip jupyter notebook指令就可以直接安装使用。

2深度学习的基本原理

2.1引言
在深度学习真正发展前,具有浅层网络和深层网络的划分,将机器学习分为两个阶段:第一阶段的机器学习运用了Backpropagation,也就是BP算法。当时也是机器学习比较热门的时期,提出了很多不同的神经网络模型,比如支持向量机(SVM)、最大熵法(LR);第二个阶段就是在2006年,机器学习的专家Hinton提出使用多层神经网络进行模型搭建,使深度学习更快的发展。
2.2自然神经网络的基本原理
把自然神经网络用科学领域的理论进行表述分析,就是一种模仿生物神经网络,比如动物的中枢神经系统,特别是大脑的结构和功能的模型,用于对函数进行近似地计算。
在这里插入图片描述

图2.1自然神经网络示意图
神经网络模型的核心体系结构是由大量简单的神经元处理节点组成的,这些节点是在不同的层次上相互连接和组织的。一个层中的单个节点连接到上一层和下一层中的其他几个节点。接收并处理来自一个层的输入,以生成传递到下一层的输出。自然神经网络的第一层为输入层,最后一层为输出层,输入层和输出层之间的每一层都为隐藏层。
分析完神经网络后可以得出两个基本的组成成分,分别是神经元和突触,而在自然神经网络中他们代表着两个重要的参数:节点和权值。节点也称为感知器,具有一个或多个权重输入的计算单元,以节点组织成层后构成网络。权重则是指在这个节点数据的重要性。举个简单的例子:在一张白纸上画一个苹果并对这张纸运用神经网络进行分析,边缘的空白部分在神经网络中的权重就很低,而苹果的权重就会很高,在后面会提到权值共享等概念。
为了卷积神经网络搭建过程的分析,必须先总结一下BP算法和梯度下降法。BP算法主要分成两部分:一部分就是激励传播,另一部分就是权值更新。激励传播是指每次进行数据迭代时,将数据传送到网络后经过响应得到相应响应的输出,再将输入和输出求差值,从而获得隐藏层和输出层的响应误差。权值更新是指在输出时将输入激励和响应误差相乘,从而获得权重的梯度,将梯度乘于k后加到原来的权重上。然后不断地进行激励传播和权值更新,直到达到想要的目标值附近。而梯度下降法就是一种权值更新的方法,梯度的分析要从导数、偏导数和方向导数进行分析,具体的推导过程就不叙述了,总结一下就是梯度是函数在某一点最大的方向导数,此时沿这个方向有最大的变化率,在优化目标函数的时,选择最大方向导数的方向进行权值更新就可以更快的达到想要的结果。
2. 3卷积神经网络的基本原理
2.3.1引言
提到图像处理就必定会提及卷积神经网络,因为图像都是有一个一个的像素点组成,大的图片会有成千上个像素点,而卷积神经网络能运用卷积核对图像进行批量处理并且权值共享。
2.3.2 CPU与GPU
提到图像处理就必定会提及卷积神经网络,因为图像都是有一个一个的像素点组成,大的图片会有成千上个像素点,而卷积神经网络能运用卷积核对图像进行批量处理并且权值共享。
2.3.3 激活函数与学习率
神经元的输入值等于权值相乘再加上一个偏置,使用激活函数并不是用来激活神经元,而是为了使神经网络具备分层的非线性能力,使网络能解决更多的的问题。激活函数用于对输入进行非线性变换,将其映射到输出,主要的激活函数有:
1.Sigmoid :Sigmoid 是一个将实值输入压缩到【0,1】的区间,因为函数图像与指数函数相近,在物理意义上比其他激活函数更接近生物神经元,所以使用范围比较广,主要用于二分类的输出层。但是Sigmoid具有饱和性,进入饱和区后会导致梯度变得非常小,会产生梯度消失的现象。它的公式如下:

函数图像:
在这里插入图片描述

图2.2 Sigmoid与Tanh的变化曲线

2.Tanh: Tanh(Hyperbolic tangent function)中文名叫双曲正切函数,是将一个输入压缩到【-1,1】的区间,函数图像比较平滑且具有单调性。但是tanh与Sigmoid一样具有饱和性,进入饱和区后会造成梯度消失。它的公式如下:

3.Relu :(rectified linear unit)是近代深度学习和机器学习中最常用的激活函数。但是它的有缺点也很明显,它的缺点是:当输入值为负数时,其导数的结果始终为0,此时神经元不能更新参数,相对于生物结构就是神经元已经死去,这种现象被称为“Dead Neuron”;但它的优点是:在梯度下降的过程中收敛速度更快,在模型搭建过程中参数的计算更快。为了解决relu的缺点,引入了各种新型的relu函数,比如带泄露单元的relu(Leaky Relu)、随机relu函数(RRelu)。
2.3.4学习率
总结完激活函数后就要总结一下学习率:在神经网络中需要不断地对权值进行优化,当采用梯度下降法时会有一个初始解,然后在初始值确定一个方向导数进行移动,移动的步长由自己定义,使初始解在不断优化的过程中更靠近预测的目标值。而在移动的过程中步长太大会导致可能跨过最优解,步长太小会使迭代次数增加,需要更多成本,因此步长的调制就很重要,而学习率就是运用于对步长的调整。例如学习率为0.01时,每次调整的步长为0.01梯度。
2.3.5感受野和卷积核
卷积神经网络是受哺乳动物视觉的启发,将视觉皮层包含着一系列组合在一起的细腿称为感受野,他们是小而敏感的视觉区域。这些细胞在输入空间上充当局部的过滤器,适合用来处理具有局部相关性的图像。通俗的定义感受野就是每一层卷积网络输出的特征图上的像素点在输入中映射的区域。

在这里插入图片描述

图2.3 卷积过程示意图

在图中可以清楚的明白感受野的概念,图中3×3的矩阵就是卷积核。
一张N×N的图像要对他进行处理分析就要进行线性的滤波,可以用一个矩阵对图像进行处理,输出的每个像素点为感受野像素和滤波器矩阵相应元素的乘积和。移动的距离为步长,一般都为1,而卷积核的大小一般都为3×3或5×5,奇数的卷积核才具有半径的概念。而卷积核中所有元素之和应该等于1,这是为了使图片经过卷积后亮度保持不变,当元素和大于1时卷积后图像会变得比原图更亮,反之小于1时图片会变暗。对于卷积后每个像素值的大小超过255则直接截断到0或255。这里说明一下普通的黑白色图中每个像素点只有一个通道,0-255代表灰度值,而彩图像的每个像素点具有三个通道用来确定像素点RGB值,同样一张255×255的图片,卷积处理黑白图只需要处理255×255的像素点,而处理彩色图片需要处理255×255×3的像素点。
2.3.6卷积层和池化层
卷积神经网络分为输入层、输出层和隐藏层,而隐藏层又分为卷积层和池化层。卷积层就是由若干个卷积核构成,每个卷积核的元素都是通过BP算法不断优化得到。卷积是为了提取输图像的特征,卷积层数较少的神经网络只能从图像中提取简易的特征参数,只有多层的卷积网络才能从低级特征中提取出更复杂的特征参数。卷积的过程也是一个数学计算的过程,在数学中有严谨的推导和计算。假设有矩阵A3×3和B2×2 ,
A = , B = , 那么B卷积A的结果就是让B在矩阵A上滑动。换言之,就是B与A的所有2×2连续子矩阵做对于元素积之和的运算。得到结果C = 矩阵B就是上文提到的卷积核或滤波器,矩阵C称为得到的特征图,过程可以定义为窄卷积。如果卷积前先对矩阵A预先进行填充后进行计算的过程称为宽卷积。
经过多次卷积后的输出特征图有可能会因为特征向量出现过拟合现象。在深度学习存在过拟合的概念,总结一下就是指在最终训练出的模型会对训练数据完美的匹配,这种模型在实际使用中可能准确率很低,只能对训练数据表现出高的准确率。为了解决过拟合问题可以运用池化层对参数进行优化。池化层一般在卷积层的后面添加,用于压缩参数。主要作用表现在两个方面:一个是保持原图的主要特征,例如有一个8×8的图片,里面有个数字,可能偏左或者偏右,用2×2的filters将他池化后会变成4×4的图片,原图的主要特征还会映射在同样的位置。而另一个作用是特征降维,如果一幅图像很大,则需要将冗余的信息去掉并且把主要特征提取出来,这就是特征降维,也是池化最主要的作用。池化的过程也可以用矩阵大致表示出来,假设矩阵C为6×4的矩阵,池化窗口为2×2,则按照池化窗口大小将矩阵C分割成6块不相交的2×2小矩阵,对每个块中的所有元素做求和平均操作,称为平均池化,取最大值则称为最大池化。假如C = , 则平均池化的结果为, 最大池化的结果为, 由于池化也称为下采样 ,用S = down©表示,为了使池化层具有可学习性,一般令:S = βdown©+ b 其中β和b为标量参数。
2.3.7填充
图片经过不断的卷积会变得越来越小,需要提取主要的特征就需要多层的卷积,为了不影响与后面全连接层的数据交换,引入填充的概念。可以在每次卷积后对特征图进行0填充或1填充。顾名思义就是在卷积后的特征图周围全部填上0或者1,使卷积后的特征图与原图的大小保持不变。
2.3.8全连接层
全连接层是将之前卷积池化得到的全部特征参数通过权值矩阵重新组装,因为用到了全部的特征参数所以叫做全连接层。卷积将图像的特征提取出来后,全连接层会将特征进行统一分析归类。举个例子,假如图片中有一只猫,卷积层可以将图片中的猫的各个特征提取出来,相对于其他动物来说比如猫的眼睛比狗的眼睛小,猫的皮毛相对于其他动物的不同,对比了很多个特征以后发现它有猫的眼睛,猫的皮毛和尾巴,然后经过全连接层进行分类得到几个不同的动物,经过比较得出猫的权值最大从而判断图片中的动物为猫。
全连接层还有个作用,假设两张不同的图片中都有一只猫,只是猫在图片中的位置不一样,但是经过卷积池化后,经过计算后得到的特征图是一样的,但可能因为特征位置的不同导致分类结果不一样,运用全连接层就能解决这个问题。但是因为空间结构的忽略,导致全连接层不适用于在空间位置上找特点物品的模型,所以要解决非线性的问题需用两次以上的全连接。但是全连接层的数据量很大,约占整个神经网络的70%以上,所以一般都只用2或者3层。
2.3.9特征归一化
在深度学习中,不同的特征向量具有不同的量纲,会影响到数据的分析结果,为了消除量纲的影响,需要对数据进行标准化处理。原数据经过标准化后,特征指标会处于同一数量级,可以综合对比评价。归一化的目的就是将预处理的数据限定在一定的区间内,从而消除奇异样本数据导致的不良影响。
常用的归一化的方法有两种:Min-Max Normalization(最大最小标准化)和Z-score标准化方法。最大最小标准化是将结果映射到0和1之间,适用于数据比较集中的情况,当涉及距离度量和协方差时不适用,用后一种方法处理数据后会使数据符合标准正态分布。这种方法可以处理距离度量和协方差的数据,但是要求原始数据分布近似为高斯分布。
2.3.10损失函数与优化器
损失函数和优化器是模型里必须有的两个函数,每种函数在keras库里都已经定义好了,只需要把头文件加进去然后调用函数名,写上参数就可以使用。总结一下损失函数和优化器:损失函数又称为目标函数,用于计算预测值与标签值之间的差。而优化器是为了调整学习率和动量。
在这里插入图片描述

图2.4 损失函数分析图

举个模拟线性方程的例子,w1是第一次迭代的权值,而粗线是真实的方程,目的是要经过不断的迭代使w接近真实值。图中的竖线分别是三次迭代后预测的Y与真实Y的差值,在二维数据中可以直接相减得出差值,但是在多维数据就会使用到平方差、均方差等不同的公式计算,这就是损失函数的作用。损失函数也可以用来建立一些准则来衡量决策函数的好坏,然后用所有的训练样本评价决策函数的风险。
假设风险函数R(θ)是在已知的训练样本(经验数据)上计算得到,表达式为,需要找到一个值(θ),使风险函数最小。常见的损失函数有0-1损失函数, 平方损失函数。而对于分类问题,最常用的就是交叉熵函数,,相对于真实类别的似然函数,常用最小化处理,最小化负对数似然损失函数就是交叉熵损失函数。
优化器的学习率在上文有提到这里不细说,动量在本文调参中不涉及,在实际应用中也比较少,不做细讲。
2.3.11 dropout(正则化)
Dropout的作用是在CNN最后的全连接层进行数据的随即丢弃,值得注意的是暂时性的丢弃,在第二次的全连接层可能会被再次暂时丢弃也有可能被按照label进行分类,dropout的使用也是为了防止CNN网络的过拟合。我看过一篇论文专门分析dropout,讲解的很深有很精确的公式验证和说明,其中有个例子可以很生动的说明dropout的过程:在自然界中,进行有性繁殖的动物的后代会从父母双方各继承一半的基因,有性繁殖会将基于不断地拆分重组,从而破坏了大段基于的联合适应性。但是为什么在物竞天择的世界里自然大部分中大型动物都是有性繁殖呢?因为有性繁殖不仅仅可以将优秀的基于传递给下一代,还可以降低基因之间的联合适应性,从而使复杂的一大段基因联合适应性转化成成一段段小基因的联合适应性。就像军队作战时,50个人互相配合进行任务和五个人一组分成十组进行分组行动进行任务的效果会有很多区别,后者的成功的机会会更大。所以dropout也能达到同样的效果,它使神经元和随机挑选出的其他神经元工作,减弱了神经元之间节点的适应性,增强了泛化能力。
在这里插入图片描述

图2.5 正则化过程示意图

2.3.12 BatchNormalization
BatchNormalization简称BN算法,是对输入进行归一化后给到激活函数的,解决了输入发生偏移的影响。运用算法后可以加快训练速度,增大学习率。采用BN算法后可以不使用池化层,因为参数归一化后分布变得明显,经过一个激活函数以后,神经元会自动削弱或者去除一些神经元,就不用再对其进行dropout处理。
2.3.13混淆矩阵和ROC曲线
混淆矩阵是作为分类的准确度标准,是学习效果进行评估的指标。混淆矩阵含有TN(true negative)、FP(false negative)、FP(false negative)、TP(true positive),比如一个怀孕的女生去做检查,给出结论是怀孕了则是TN(true negative),如果检测结果是没怀孕则是FP(false negative),一个男生去检测结果是怀孕了则是FP(false negative),没怀孕则是TP(true positive)。由这四个参数引申出两个参数-精确率和召回率,精确率(TPR)也称之为查准率,指实际为正样本(TP)的预测结果占总预测为正样本(TP+FP)的比重;召回率(FPR)也称为查全率,指实际为正样本预测(TP)占正确预测结果(TP+FN)的比重。混淆矩阵可以通过使用scikit-learn模块实现。
ROC曲线就是由精确率和召回率的点组成,横轴为精确率,纵轴为查准率。ROC曲线下的面积越大代表模型的准确性越高,泛化能力越强。ROC曲线图在1×1的区间内,中的四个顶点分别代表各种极端:
(0,1)表示此时所有正样本都被正确的预测,没有负样本被预测为正样本;
(1,0)表此时所有的正样本都被错误的认为是负样本;
(0,0)表示此时分类器将所有的样本都判定为负样本;
(1,1)表示此时分类器将所有的样本都判定为正样本。
2.3.14 epochs与batch_size
batch_size为批处理长度,在梯度下降法中的的参数更新有两种方法,第一种是计算全部参数的损失函数,进行梯度更新,这种方法叫批梯度下降法。另一种是每遍历一个数据就计算损失函数进行梯度更新,且不需要遍历全部数据,称为随机梯度下降法。前一种方法计算量大,后一种方法收敛性能没那么好,所以采用了折中的方法进行小批量梯度下降,然后分多个批次,减少了随机性和计算量。
举个例子:10000个样本,batch_size为100,则需要迭代10次,此时算训练完一次整个样本,用epoch表示。例如样本为10000,batch_size为100,epoch为5就意味着10000个样本每一次训练完成需要迭代10次,整个模型的搭建要训练5次。
2.3.15 training loss与validation loss的关系
在模型验证分析时,常用的分析方法之一就是对比training loss 和validation loss函数图像,从而方便对模型的进一步改进。
当training loss 下降,validation loss下降,说明神经网络还在学习;
当training loss 下降,validation loss不变,说明神经网络过拟合;
当training loss 不变,validation loss下降,说明数据集有问题;
当training loss 不变,validation loss不变,说明停止学习,需要减小学习率;
当training loss 上升,validation loss上升,说明超参数设置不当,数据集有问题。

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

3.1引言
总结完自然神经网络和卷积神经网络中基础的概念和知识点后,将在这一小节进行用卷积神经网络架构的AlexNet进行模型搭建和分析。
3.2数据的预处理
在进行模型搭建前,数据的处理是最繁琐也是最重要的,因为只要你懂得神经网络的各种原理,在模型搭建好后可以由计算机自己计算分析,不需要人为的干涉它。但是计算机处理的数据需要人为得给它,而且不能有脏数据等,所以对数据的预处理很重要。
本设计用的数据是kaggle的fer2013数据集, 数据集不是一张张图片而是将数据保存到csv文件中,所以可以运用panda的函数进行数据读取也可以直接转换成图片识别。数据集中分为训练集、测试集和验证集。文件中的label由0-7组成,分别代表着:0(愤怒)、1(厌恶)、2(恐惧)、3(开心)、4(伤心)、5(惊讶)、6(中性)等七种表情。其中训练集具有28708张图片,测试集具有3589张图片,验证集具有3589张图片,图片大小都为48×48×1。
本文会在附录会给出下载地址。
3.3图片的转换
首先将文件转化为.csv格式,这样才能用python对数据进行批量处理。需要根据label分成三个.csv文件,分别代表训练集、测试集和验证机,用database_path加文件路径获取文件内的数据。这里需要提一下因为用的是有监督学习。机器学习分为有监督学习和无监督学习,有监督学习和无监督学习的区别在于训练数据中有没有带有标签(label),大部分的卷积神经网络都是采用有监督的学习,事先为每组数据每张图片打上标签,分好类型然后让机器学习;无监督学习的数据不含有label,适用于增强学习等其他神经网络。
因为带有标签,需要把label给到中间变量然后删去再将数据进行转化变成图片。
在这里插入图片描述
图3.1 去除label的过程展示

然后将去掉标签后的数据打开,然后根据每一个像素点的灰度值转化为48×48的图片。
在这里插入图片描述

图3.2 转化成图片的过程展示

效果展示:
在这里插入图片描述

(a)

在这里插入图片描述
(b)
图3.3 转化后效果展示

3.4搭建模型
首先定义图像的长和宽,目的是告诉网络输入图像矩阵的大小(48×48×1),确认图像的通道分量,便于搭建网络结构。
在这里插入图片描述

图3.4 模型搭建示意图1

然后开始定义网络结构,首先先建立一个model,然后开始添加卷积层、激活函数、池化层。
在这里插入图片描述

图3.5 模型搭建示意图2

搭建的原始模型中第一层卷积层用的是32个5×5的卷积核进行原始图像的特征提取,没有写步长默认为1,采用0填充,使用relu做激活函数,然后再之后构建一个kernel size 为33的池化层,步长为2。第三层用32个4×4的卷积核进行第二次卷积,采用0填充,使用relu做激活函数。第四层构建一个kernel size 为 33的池化层,步长为2;第五层为64个5×5的卷积核进行第三次卷积,第六层构建一个kernel size 为33的池化层,步长为2。然后添加全连接层,需要先将feature map转成n1的一维网络。第一层全连接层长度为2048,激活函数为relu,然后正则化百分之40的特征参数。再构建第二层的全连接层,长度为2048,激活函数为relu,正则化百分之40的特征参数。然后添加一层BatchNormalization,最后将全连接层的特征参数按label分为7类,激活函数采用sigmoid。
然后就是选择优化器和损失函数,优化器选择的是SGD(随机梯度下降)也叫增量梯度下降,是对随机样本进行更新,是t次迭代选取的样本。学习率为0.005,损失函数用的是categorical_crossentropy。这时模型搭建完毕,可以用训练数据进行模型训练了。先对图片集进行读取,然后设置epochs和batch_size,将训练数据、测试数据和验证数据进行标准化,class_mode要选用binary,因为是多分类问题。
在这里插入图片描述

图3.6 模型搭建示意图3

最后用model.fit开始model的训练。
3.5直接用.csv文件中的数据进行模型搭建与训练
一开始是使用.csv数据文件中的数据转化为图片,再对图片继续特征读取与模型训练,这是一个比较传统但是比较耗费CPU的过程,可以直接用数据进行模型搭建的过程。
直接使用.csv文件的话需要先检查训练数据是否有空值,这会对训练过程产生巨大的影响,因为少了一个像素点的值,后面的像素点就会往前移一个,整个图片就会糊了,这里要注意0不等于空值,可以用rain.isnull().any().describe()命令实现。
然后需要将训练集和测试集进行标准化,再利用reshape函数将训练数据变成height = 28px, width = 28px , 通道为 1的图片数据,但是这组数据只是相对于中间变量不会像上一种方法输出图片。然后使用one-hot编码将0-6数字标签编码成七维向量。One-hot是将类别变量编码转换为机器学习算法易于利用的一种形式的过程,因为彼此独立,所以用于分类问题,对于数据之间存在某种连续性关系的数据则不能使用one-hot。将训练集合按照9:1 分成训练集合和验证集进行10折交叉验证,交叉验证的好处是可以在一定程度上减小过拟合且还可以从有限的数据中获取尽可能多的有效信息。 然后就是模型的搭建和训练,过程大致和上面一样,代码附录有。
3.6最终模型展示
在这里插入图片描述

图3.7 最终模型展示
在这里插入图片描述

图3.8 最终模型各层网络参数展示
在这里插入图片描述

图3.9 优化器与损失函数定义示意
3.7结果分析
经过50次训练后,准确率来到了57.65%,从第一次训练后的24.94%提升了32.71%,从效果上看模型还算成果,但是根据模型的实际情况分析。首先可以先看看training loss 和validation loss 的曲线图,用plt库就可以实现绘画。
根据2.3.14的结论可以看出,最终搭建的模型还是处于过拟合的状态。这个最终模型是经过多次改良后得到的,原本搭建的CNN模型因为dropout随机丢弃的数据不多,没有应用BatchNormalization,过拟合的程度更加严重,即使在算法上已经采用了随机梯度下降法,在每一层卷积层后添加了池化层,在优化器和损失函数上更换了四五个组合,但是最终拟合程度较好的还是SGD(优化器)和categorical_crossentropy(损失函数)。
在这里插入图片描述

图3.10 Training loss与validation loss 函数图像
再来看看混淆矩阵:从矩阵中可以看出fer2013数据集中label 1(厌恶)的数据较少,对角线的数据占总体数据越大越好,模型准确率在60%左右,所以混淆矩阵整体分布还算均匀,label 3(开心)的训练效果较好。
在这里插入图片描述

图3.11 混淆矩阵示意图
最后再看看ROC曲线:因为label 1(厌恶)的准确率最高,所以class1曲线下的面积最大,从图中可以看出模型准确率还算不错,有可能是数据集的问题,导致模型准度达到了瓶颈。
在这里插入图片描述

图3.11 ROC曲线示意图示意图
再用验证集val中的label 1验证一次模型准确率,计算出的准确率为99.89%,对于此模型中因为厌恶的数据集较多,拟合程度较好,虽然出现轻微过拟合现象,但用来验证fer2013数据集中图片的准确率还是比较高的。
在这里插入图片描述

图3.12 模型验证示意图
3.8改进过程分析
在最初模型建好后,就发现了模型过拟合的问题,事实证明在神经网络的搭建过程中,尤其是卷积神经网络对图像处理的应用,解决过拟合问题是最主要的问题。我问过导师在这种情况下应该怎么解决:第一种就是将CNN模型中的参数减少,但是因为本身采用的是AlexNet的架构,卷积层、池化层和全连接层的层数相对于其他CNN模型中已经算少了,就只能改动其他参数的方法进行改良。在优化器的选择挑选了SGD、RMSprop、Adam,损失函数选择了categorical_crossentropy和binary_crossentropy,通过不断组合SGD和binary_crossentropy果然还是比较符合人脸识别,因为categorical_crossentropy适合二分类问题,如果选择这个损失函数的精准率更高反而是个更大的问题。最终还更改了dropout的数据、学习率、用了BN算法,最后准确率达到接近58%。老师还提了个方法就是增大数据集,但是这个方法涉及的问题很多,首先就是数据的重新整理,在深度学习中数据整理永远是占最多时间的,其次更大的数据集意味着对计算机的算力要求更高。如果要彻底解决这个问题用后一种方法会更可行,但在第二种方法短时间内实现不了的情况下尽可能的把各个参数优化好,完成此次毕业设计。

4关于VGG网络、ResNet、GoogLeNet的图像处理

4.1引言
ILSVRC(ImageNet Large Scale Visual Recognition Challenge)是近几年来深度学习图像处理方面最具权威的比赛,每年在图像处理方面都会有很大的进步,近几年最著名的就是VGG网络、ResNet、GoogLeNet。
4.2 VGG网络
VGG网络就是重复重复使用3x3卷积和2x2池化增加网络深度,在2014年的ILSVRC比赛中,VGG 在Top-5中取得了92.3%的正确率(top5表示只要概率向量中最大的前五名里有分类正确的则为正确)。输入的图片为2242243,具有13个卷积层和3个全连接层,所以也叫VGG16网络。VGG还有另一种版本叫VGG19,多增加了3层卷积层。
4.3 ResNet
ResNet提出了Inception模块,可在保持计算成本的同时增加网络的深度和宽度。ResNet的核心思想是残差模块和深度残差网络,解决深层网络难以训练的问题。
4.4 GoogLeNet
GoogLeNet和VGG网络都是ILSVRC2014提出,GoogLeNet获得了第一名。VGG继承了LeNet以及AlexNet的一些框架结构,增加了卷积池化层数。虽然深度只有22层,但在相对情况比较AlexNet和VGG的参数会少很多,因此在内存或计算资源有限时,GoogleNet是比较好的选择而且性能更加优越。
GoogLeNet和ResNet都采用了模块化的结构(Inception结构),方便增添和修改。不难看出,VGG网络、ResNet、GoogLeNet都是采用了跟多层的网络结构,通过增大参数量来提高准确率。如果在样本数据充足的条件下用这三种网路进行人脸表情识别的搭建效果会更好。当然对样本的选择也有要求,输入的大小至少2242243,48481的数据集用AlexNet会比较合适,参数优化也会更简单。

5总结

人脸表情识别在未来的人机交互中必定是一个热门的技术点,虽然己经有很多不同结构的模型,并且取得了不错的识别效果,但是使模型变得更精准、算法更优化、实现方式更简易一直是人们在努力实现的目标。作为一名初学者。通过三个月的学习,在机器学习与深度学习也只能算是勉强入门。在杨老师和几位线上授课的老师的教导下,我也顺利用所学知识搭建出一个人脸表情识别模型,虽然准确率只在60%,且有一定的过拟合现象,但是这也代表着三个月的学习成果,在一次次的调参和优化中,我明白了深度学习基础知识的重要性。调参不是盲目的调超参数,而要懂得对数据进行分析,要对算法有一定的了解才能更好的选择。在训练数据不够多的前提不能选择大的神经网络,尽可能选择能消除过拟合的函数和算法。每搭建一层网络,给一个函数时都应该问自己一句为什么这么做,是不是有更好的选择。总而言之此次毕业设计也算顺利完成,希望在未来能有更好的算法,更完善的数据能搭建出更好的模型,也希望本文能帮助更多初学者理解和完成人脸表情识别。

参考文献

[1]施徐敢. 基于深度学习的人脸表情识别[D], 浙江:浙江理工大学,2015-03-08, 12。
[2]吕亚丹. 基于深度学习的人脸表情识别[D],天津:天津大学,2014-15-01,18.
[3]Ekman P,Friesen WV,HagerJC. Facial action coding system(FACS) [J].A technique for the measurement of facial action Consulting,PaloAlto,1978,32-35
[4]Cootes TF,Taylor CJ,Cooper DH,et al. Active shape models-their training and application[J].Computer Vision and Image Understanding,1995,61(l): 38-59.
[5]赵艳. 基于深度学习的表情识别研究[D],重庆:重庆邮电大学,2016-04-03,22-24.
[6]Cootes TF,Edwards GJ,Taylor CJ. Comparing Active Shape Models with Active Appearance Models[C]. BMVC,Nottingham,1999,173-182.
[7]李江. 基于深度学习的人脸表情识别研究与实现[D],四川:西南科技大学,2017-05-01,33-34.
[8]Cheon Y,Kim D. Natural facial expression recognition using differential-AAM and manifold learning[J]. Pattern Recognition,2009,42(7):1340-1350.
[9]Zhang Z,Lyons M,Schuster M,et al. Companson between geometry-based and Gabor-wavelets-based Facial expression recognition using multi-layer perceptron[C]. Third IEEE International Conference on Automatic Face and Gesture Recognition,1998 Proceedings Nara1998,454-459.
[10]李江. 基于深度学习的人脸表情识别研究与实现[D],四川:西南科技大学,2017-05-01,33-34.

谢 辞

走的最快的总是时间,来不及感叹,大学生活已近尾声,四年多的努力与付出,随着本次论文的完成,将要划下完美的句号。本论文设计在杨聚庆老师的悉心指导和严格要求下业已完成,从课题选择到具体的写作过程,论文初稿与定稿无不凝聚着杨聚庆老师的心血和汗水,在我的毕业设计期间,杨聚庆老师为我论文的指导有着启蒙的意义,我的二稿和一稿无论在内容还是格式方面都有翻天覆地的变化。我做毕业设计的每个阶段,从选题到查阅资料,论文提纲的确定,中期论文的修改,后期论文格式调整等各个环节中都给予了我悉心的指导。这几个月以来,杨聚庆老师不仅在学业上给我以精心指导,同时还在思想上给予我以无微不至的关怀,在此谨向杨老师致以诚挚的谢意和崇高的敬意。
同时,本篇毕业论文的写作也得到了何飞、韩烨、王健等老师的热情帮助。感谢在整个毕业设计期间和我密切合作的老师,和曾经在各个方面给予过我帮助的伙伴们。在此,我再一次真诚地向帮助过我的老师和同学表示感谢!

附 录

附录1

fer2013数据集:https://pan.baidu.com/s/1oOjfM1IIh6s8NXoLdqMYiA(提取码vxoy)

附录2

图片转化源码

import csv
import os
import numpy as np
from PIL import Image
database_path = r’D:\input\fer2013’
datasets_path = r’.\datasets’
csv_file = os.path.join(database_path, ‘fer2013.csv’)
train_csv = os.path.join(datasets_path, ‘train.csv’)
val_csv = os.path.join(datasets_path, ‘val.csv’)
test_csv = os.path.join(datasets_path, ‘test.csv’)
with open(csv_file) as f:
csvr = csv.reader(f)
header = next(csvr)
rows = [row for row in csvr]

trn = [row[:-1] for row in rows if row[-1] == 'Training']
csv.writer(open(train_csv,'w+'), lineterminator='\n').writerows([header[:-1]] + trn)
print(len(trn))

val = [row[:-1] for row in rows if row[-1] == 'PublicTest']
csv.writer(open(val_csv, 'w+'), lineterminator='\n').writerows([header[:-1]] + val)
print(len(val))

tst = [row[:-1] for row in rows if row[-1] == 'PrivateTest']
csv.writer(open(test_csv,'w+'), lineterminator='\n').writerows([header[:-1]] + tst)
print(len(tst))

datasets_path = r’.\datasets’
train_csv = os.path.join(datasets_path, ‘train.csv’)
val_csv = os.path.join(datasets_path, ‘val.csv’)
test_csv = os.path.join(datasets_path, ‘test.csv’)

train_set = os.path.join(datasets_path, ‘train’)
val_set = os.path.join(datasets_path, ‘val’)
test_set = os.path.join(datasets_path, ‘test’)

for save_path, csv_file in [(train_set, train_csv), (val_set, val_csv), (test_set, test_csv)]:
if not os.path.exists(save_path):
os.makedirs(save_path)

num = 1
with open(csv_file) as f:
    csvr = csv.reader(f)
    header = next(csvr)
    for i, (label, pixel) in enumerate(csvr):
        pixel = np.asarray([float(p) for p in pixel.split()]).reshape(48, 48)
        subfolder = os.path.join(save_path, label)
        if not os.path.exists(subfolder):
            os.makedirs(subfolder)
        im = Image.fromarray(pixel).convert('L')
        image_name = os.path.join(subfolder, '{:05d}.jpg'.format(i))
        print(image_name)
        im.save(image_name)

附录3
模型搭建与数据分析源码

from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, MaxPool2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.utils import to_categorical
from keras.preprocessing.image import img_to_array
from keras import backend as K
from keras.optimizers import SGD
import numpy as np
import keras
from keras.layers import Input
from keras.preprocessing import image
from keras.applications.imagenet_utils import preprocess_input, decode_predictions
import time
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import seaborn as sns
%matplotlib inline
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import itertools
from keras.optimizers import RMSprop
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ReduceLROnPlateau
from keras.layers.normalization import BatchNormalization
from keras.callbacks import ReduceLROnPlateau
from sklearn.model_selection import train_test_split
from itertools import cycle
from sklearn.metrics import roc_curve, auc

sns.set(style=‘white’, context=‘notebook’, palette=‘deep’)
import csv
import os
train = pd.read_csv(“train.csv”)
validation = pd.read_csv(“val.csv”)
test = pd.read_csv(“test.csv”)
Y_train = train[“label”]
Y_test = test[‘label’]
X_train = train.drop(labels = [“label”],axis = 1)
X_test = test.drop(labels = [“label”],axis = 1)
Y_train.value_counts()
Y_test.value_counts()
X_train = X_train / 255.0
X_test = X_test / 255.0
X_train = X_train.values.reshape(-1,48,48,1)
X_test = X_test.values.reshape(-1,48,48,1)
Y_train = to_categorical(Y_train, num_classes = 7)
Y_test = to_categorical(Y_test, num_classes = 7)
random_seed = 2
X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size = 0.1, random_state=random_seed)
g = plt.imshow(X_train[0][:,:,0],cmap=‘gray’)
batch_size = 128
num_classes = 7
epochs = 50
input_shape = (48,48,1)
model = Sequential()
model.add(Conv2D(32,kernel_size=(1,1),activation=‘relu’,kernel_initializer=‘he_normal’,input_shape=input_shape))
model.add(Conv2D(32,kernel_size=(5,5),activation=‘relu’,padding=‘same’,kernel_initializer=‘he_normal’))
model.add(MaxPool2D((3, 3),strides=2))
model.add(Conv2D(32,kernel_size=(4,4),activation=‘relu’,padding=‘same’,kernel_initializer=‘he_normal’))
model.add(MaxPool2D((3, 3),strides=2))
model.add(Conv2D(64,(5,5),activation=‘relu’,padding=‘same’,kernel_initializer=‘he_normal’))
model.add(MaxPool2D((3, 3),strides=2))
model.add(Flatten())
model.add(Dense(2048, activation=‘relu’))
model.add(Dropout(0.5))
model.add(Dense(1024, activation=‘relu’))
model.add(Dropout(0.5))
model.add(BatchNormalization())
model.add(Dense(num_classes, activation=‘softmax’))
model.summary()
from keras import optimizers
optimizer = SGD(lr=0.005, momentum=0.9, decay=0.000006, nesterov=True)
model.compile(optimizer = optimizer , loss = ‘categorical_crossentropy’, metrics=[“accuracy”])
learning_rate_reduction = ReduceLROnPlateau(monitor=‘val_acc’, patience=3, verbose=1, factor=0.5, min_lr=0.00001)
history = model.fit(X_train,Y_train, batch_size= batch_size, epochs = epochs , validation_data = (X_val,Y_val),callbacks=[learning_rate_reduction])
fig, ax = plt.subplots(2,1)

ax[0].plot(history.history[‘loss’], color=‘b’, label=“Training loss”)
ax[0].plot(history.history[‘val_loss’],color=‘r’,label=“validation loss”,axes =ax[0])
legend = ax[0].legend(loc=‘best’, shadow=True)

ax[1].plot(history.history[‘accuracy’], color=‘b’, label=“Training accuracy”)
ax[1].plot(history.history[‘val_accuracy’],color=‘r’,label=“Validation accuracy”)
legend = ax[1].legend(loc=‘best’, shadow=True)
def plot_confusion_matrix(cm, classes,
normalize=False,
title=‘Confusion matrix’,
cmap=plt.cm.Blues):
“”"
This function prints and plots the confusion matrix.
Normalization can be applied by setting normalize=True.
“”"
plt.imshow(cm, interpolation=‘nearest’, cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=45)
plt.yticks(tick_marks, classes)

if normalize:
    cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]

thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
    plt.text(j, i, cm[i, j],
             horizontalalignment="center",
             color="white" if cm[i, j] > thresh else "black")

plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')

Y_pred = model.predict(X_val)
Y_pred_classes = np.argmax(Y_pred,axis = 1)
Y_true = np.argmax(Y_val,axis = 1)
confusion_mtx = confusion_matrix(Y_true, Y_pred_classes)
plot_confusion_matrix(confusion_mtx, classes = range(7))
errors = (Y_pred_classes - Y_true != 0)

Y_pred_classes_errors = Y_pred_classes[errors]
Y_pred_errors = Y_pred[errors]
Y_true_errors = Y_true[errors]
X_val_errors = X_val[errors]

def display_errors(errors_index,img_errors,pred_errors, obs_errors):
“”" This function shows 6 images with their predicted and real labels"“”
n = 0
nrows = 3
ncols = 3
fig, ax = plt.subplots(nrows,ncols,sharex=True,sharey=True)
for row in range(nrows):
for col in range(ncols):
error = errors_index[n]
ax[row,col].imshow((img_errors[error]).reshape((48,48)))
ax[row,col].set_title("Predicted label :{}\nTrue label :{}”. format(pred_errors[error] ,
obs_errors[error]))
n += 1
Y_pred_errors_prob = np.max(Y_pred_errors,axis = 1)
true_prob_errors = np.diagonal(np.take(Y_pred_errors, Y_true_errors, axis=1))
delta_pred_true_errors = Y_pred_errors_prob - true_prob_errors
sorted_dela_errors = np.argsort(delta_pred_true_errors)
most_important_errors = sorted_dela_errors[-9:]
display_errors(most_important_errors, X_val_errors, Y_pred_classes_errors, Y_true_errors)
from sklearn.metrics import roc_curve, auc
fpr = dict()
tpr = dict()
roc_auc = dict()
y_score = model.predict(X_test)
for i in range(num_classes):
fpr[i], tpr[i], _ = roc_curve(Y_test[:,i], y_score[:,i])
roc_auc[i] = auc(fpr[i], tpr[i])
for i in range(num_classes):
plt.plot(fpr[i], tpr[i], lw = 2,
label=‘ROC curve of class {0} (area = {1:0.2f})’
‘’.format(i, roc_auc[i]))

plt.plot([0, 1], [0, 1], ‘k–’, lw=2)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel(‘False Positive Rate’)
plt.ylabel(‘True Positive Rate’)
plt.title(‘Some extension of Receiver operating characteristic to multi-class’)
plt.legend(loc=“lower right”)
plt.show()

  • 27
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_1406299528

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值