文章目录
  一、项目背景
  二、数据处理
    1、标签与特征分离
    2、数据可视化
    3、训练集和测试集
  三、模型搭建
  四、模型训练
  五、完整代码

一、项目背景
数据集cnn_train.csv包含人类面部表情的图片的label和feature。在这里,面部表情识别相当于一个分类问题,共有7个类别。
其中label包括7种类型表情:

一共有28709个label,说明包含了28709张表情包嘿嘿。
每一行就是一张表情包48*48=2304个像素,相当于4848个灰度值(intensity)(0为黑, 255为白)

二、数据处理
  1、标签与特征分离
    这一步为了后面方便读取数据集,对原数据进行处理,分离后分别保存为cnn_label.csv和cnn_data.csv.

# cnn_feature_label.py 将label和像素数据分离
import pandas as pd path = 'cnn_train.csv'# 原数据路径
# 读取数据
df = pd.read_csv(path)
# 提取label数据
df_y = df[['label']]
# 提取feature(即像素)数据
df_x = df[['feature']]
# 将label写入label.csv
df_y.to_csv('cnn_label.csv', index=False, header=False)
# 将feature数据写入data.csv
df_x.to_csv('cnn_data.csv', index=False, header=False)

执行之后生成结果文件:

  2、数据可视化
    完成与标签分离后,下一步我们对特征进一步处理,也就是将每个数据行的2304个像素值合成每张48*48的表情图。

# face_view.py 数据可视化
import cv2
import numpy as np # 指定存放图片的路径
path = './/face'
# 读取像素数据
data = np.loadtxt('cnn_data.csv') # 按行取数据
for i in range(data.shape[0]):
  face_array = data[i, :].reshape((48, 48)) # reshape
  cv2.imwrite(path + '//' + '{}.jpg'.format(i), face_array) # 写图片

这段代码将写入28709张表情图,执行需要一小段时间。
结果如下:

  3、训练集和测试集
第一步,我们要训练模型,需要划分一下训练集和验证集。一共有28709张图片,我取前24000张图片作为训练集,其他图片作为验证集。新建文件夹cnn_train和cnn_val,将0.jpg到23999.jpg放进文件夹cnn_train,将其他图片放进文件夹cnn_val。
第二步,对每张图片标记属于哪一个类别,存放在dataset.csv中,分别在刚刚训练集和测试集执行标记任务。

# cnn_picture_label.py 表情图片和类别标注
import os
import pandas as pd def data_label(path):
# 读取label文件
df_label = pd.read_csv('cnn_label.csv', header=None)
# 查看该文件夹下所有文件
files_dir = os.listdir(path)
# 用于存放图片名
path_list = []
# 用于存放图片对应的label
label_list = []
# 遍历该文件夹下的所有文件
for file_dir in files_dir:
# 如果某文件是图片,则将其文件名以及对应的label取出,分别放入path_list和label_list这两个列表中
if os.path.splitext(file_dir)[1] == ".jpg":
path_list.append(file_dir)
index = int(os.path.splitext(file_dir)[0])
label_list.append(df_label.iat[index, 0]) # 将两个列表写进dataset.csv文件
path_s = pd.Series(path_list)
label_s = pd.Series(label_list)
df = pd.DataFrame()
df['path'] = path_s
df['label'] = label_s
df.to_csv(path + '\\dataset.csv', index=False, header=False) def main():
# 指定文件夹路径
train_path = 'D:\\PyCharm_Project\\deep learning\\model\\cnn_train'
val_path = 'D:\\PyCharm_Project\\deep learning\\model\\cnn_val'
data_label(train_path)
data_label(val_path) if __name__ == "__main__":
main()

完成之后如图:

第三步,重写Dataset类,它是Pytorch中图像数据集加载的一个基类,源码如下,我们需要重写类来实现加载上面的图像数据集。

import bisect
import warnings from torch._utils import _accumulate
from torch import randperm class Dataset(object):
r"""An abstract class representing a :class:`Dataset`. All datasets that represent a map from keys to data samples should subclass
it. All subclasses should overwrite :meth:`__getitem__`, supporting fetching a
data sample for a given key. Subclasses could also optionally overwrite
:meth:`__len__`, which is expected to return the size of the dataset by many
:class:`~torch.utils.data.Sampler` implementations and the default options
of :class:`~torch.utils.data.DataLoader`. .. note::
:class:`~torch.utils.data.DataLoader` by default constructs a index
sampler that yields integral indices. To make it work with a map-style
dataset with non-integral indices/keys, a custom sampler must be provided.
""" def __getitem__(self, index):
raise NotImplementedError def __add__(self, other):
return ConcatDataset([self, other]) # No `def __len__(self)` default?
# See NOTE [ Lack of Default `__len__` in Python Abstract Base Classes ]
# in pytorch/torch/utils/data/sampler.py

重写之后如下,自定义类名为FaceDataset:

class FaceDataset(data.Dataset):
# 初始化
def __init__(self, root):
super(FaceDataset, self).__init__()
self.root = root
df_path = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[0])
df_label = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[1])
self.path = np.array(df_path)[:, 0]
self.label = np.array(df_label)[:, 0] # 读取某幅图片,item为索引号
def __getitem__(self, item):
# 图像数据用于训练,需为tensor类型,label用numpy或list均可
face = cv2.imread(self.root + '\\' + self.path[item])
# 读取单通道灰度图
face_gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)
# 直方图均衡化
face_hist = cv2.equalizeHist(face_gray)
"""
像素值标准化
读出的数据是48X48的,而后续卷积神经网络中nn.Conv2d() API所接受的数据格式是(batch_size, channel, width, higth),
本次图片通道为1,因此我们要将48X48 reshape为1X48X48。
"""
face_normalized = face_hist.reshape(1, 48, 48) / 255.0
face_tensor = torch.from_numpy(face_normalized)
face_tensor = face_tensor.type('torch.FloatTensor')
label = self.label[item]
return face_tensor, label # 获取数据集样本个数
def __len__(self):
return self.path.shape[0]

到此,就实现了数据集加载的过程,下面准备使用这个类将数据喂给模型训练了。

三、模型搭建

这是Github上面部表情识别的一个开源项目的模型结构,我们使用model B搭建网络模型。使用RRelu(随机修正线性单元)作为激活函数。卷积神经网络模型如下:

class FaceCNN(nn.Module):
# 初始化网络结构
def __init__(self):
super(FaceCNN, self).__init__() # 第一层卷积、池化
self.conv1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1), # 卷积层
nn.BatchNorm2d(num_features=64), # 归一化
nn.RReLU(inplace=True), # 激活函数
nn.MaxPool2d(kernel_size=2, stride=2), # 最大值池化
) # 第二层卷积、池化
self.conv2 = nn.Sequential(
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(num_features=128),
nn.RReLU(inplace=True),
# output:(bitch_size, 128, 12 ,12)
nn.MaxPool2d(kernel_size=2, stride=2),
) # 第三层卷积、池化
self.conv3 = nn.Sequential(
nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(num_features=256),
nn.RReLU(inplace=True),
# output:(bitch_size, 256, 6 ,6)
nn.MaxPool2d(kernel_size=2, stride=2),
) # 参数初始化
self.conv1.apply(gaussian_weights_init)
self.conv2.apply(gaussian_weights_init)
self.conv3.apply(gaussian_weights_init) # 全连接层
self.fc = nn.Sequential(
nn.Dropout(p=0.2),
nn.Linear(in_features=256 * 6 * 6, out_features=4096),
nn.RReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(in_features=4096, out_features=1024),
nn.RReLU(inplace=True),
nn.Linear(in_features=1024, out_features=256),
nn.RReLU(inplace=True),
nn.Linear(in_features=256, out_features=7),
) # 前向传播
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
# 数据扁平化
x = x.view(x.shape[0], -1)
y = self.fc(x)
return y

参数解析:
输入通道数in_channels,输出通道数(即卷积核的通道数)out_channels,卷积核大小kernel_size,步长stride,对称填0行列数padding。
第一层卷积:input:(bitch_size, 1, 48, 48), output(bitch_size, 64, 24, 24)
第二层卷积:input:(bitch_size, 64, 24, 24), output(bitch_size, 128, 12, 12)
第三层卷积:input:(bitch_size, 128, 12, 12), output:(bitch_size, 256, 6, 6)

四、模型训练
损失函数使用交叉熵,优化器是随机梯度下降SGD,其中weight_decay为正则项系数,每轮训练打印损失值,每5轮训练打印准确率。

def train(train_dataset, val_dataset, batch_size, epochs, learning_rate, wt_decay):
# 载入数据并分割batch
train_loader = data.DataLoader(train_dataset, batch_size)
# 构建模型
model = FaceCNN()
# 损失函数
loss_function = nn.CrossEntropyLoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=learning_rate, weight_decay=wt_decay)
# 逐轮训练
for epoch in range(epochs):
# 记录损失值
loss_rate = 0
# scheduler.step() # 学习率衰减
model.train() # 模型训练
for images, labels in train_loader:
# 梯度清零
optimizer.zero_grad()
# 前向传播
output = model.forward(images)
# 误差计算
loss_rate = loss_function(output, labels)
# 误差的反向传播
loss_rate.backward()
# 更新参数
optimizer.step() # 打印每轮的损失
print('After {} epochs , the loss_rate is : '.format(epoch + 1), loss_rate.item())
if epoch % 5 == 0:
model.eval() # 模型评估
acc_train = validate(model, train_dataset, batch_size)
acc_val = validate(model, val_dataset, batch_size)
print('After {} epochs , the acc_train is : '.format(epoch + 1), acc_train)
print('After {} epochs , the acc_val is : '.format(epoch + 1), acc_val) return model

五、完整代码

 """
CNN_face.py 基于卷积神经网络的面部表情识别(Pytorch实现)
"""
import torch
import torch.utils.data as data
import torch.nn as nn
import torch.optim as optim
import numpy as np
import pandas as pd
import cv2 # 参数初始化
def gaussian_weights_init(m):
classname = m.__class__.__name__
# 字符串查找find,找不到返回-1,不等-1即字符串中含有该字符
if classname.find('Conv') != -1:
m.weight.data.normal_(0.0, 0.04) # 验证模型在验证集上的正确率
def validate(model, dataset, batch_size):
val_loader = data.DataLoader(dataset, batch_size)
result, num = 0.0, 0
for images, labels in val_loader:
pred = model.forward(images)
pred = np.argmax(pred.data.numpy(), axis=1)
labels = labels.data.numpy()
result += np.sum((pred == labels))
num += len(images)
acc = result / num
return acc class FaceDataset(data.Dataset):
# 初始化
def __init__(self, root):
super(FaceDataset, self).__init__()
self.root = root
df_path = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[0])
df_label = pd.read_csv(root + '\\dataset.csv', header=None, usecols=[1])
self.path = np.array(df_path)[:, 0]
self.label = np.array(df_label)[:, 0] # 读取某幅图片,item为索引号
def __getitem__(self, item):
# 图像数据用于训练,需为tensor类型,label用numpy或list均可
face = cv2.imread(self.root + '\\' + self.path[item])
# 读取单通道灰度图
face_gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)
# 直方图均衡化
face_hist = cv2.equalizeHist(face_gray)
"""
像素值标准化
读出的数据是48X48的,而后续卷积神经网络中nn.Conv2d() API所接受的数据格式是(batch_size, channel, width, higth),
本次图片通道为1,因此我们要将48X48 reshape为1X48X48。
"""
face_normalized = face_hist.reshape(1, 48, 48) / 255.0
face_tensor = torch.from_numpy(face_normalized)
face_tensor = face_tensor.type('torch.FloatTensor')
label = self.label[item]
return face_tensor, label # 获取数据集样本个数
def __len__(self):
return self.path.shape[0] class FaceCNN(nn.Module):
# 初始化网络结构
def __init__(self):
super(FaceCNN, self).__init__() # 第一次卷积、池化
self.conv1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1), # 卷积层
nn.BatchNorm2d(num_features=64), # 归一化
nn.RReLU(inplace=True), # 激活函数
nn.MaxPool2d(kernel_size=2, stride=2), # 最大值池化
) # 第二次卷积、池化
self.conv2 = nn.Sequential(
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(num_features=128),
nn.RReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
) # 第三次卷积、池化
self.conv3 = nn.Sequential(
nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(num_features=256),
nn.RReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
) # 参数初始化
self.conv1.apply(gaussian_weights_init)
self.conv2.apply(gaussian_weights_init)
self.conv3.apply(gaussian_weights_init) # 全连接层
self.fc = nn.Sequential(
nn.Dropout(p=0.2),
nn.Linear(in_features=256 * 6 * 6, out_features=4096),
nn.RReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(in_features=4096, out_features=1024),
nn.RReLU(inplace=True),
nn.Linear(in_features=1024, out_features=256),
nn.RReLU(inplace=True),
nn.Linear(in_features=256, out_features=7),
) # 前向传播
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
# 数据扁平化
x = x.view(x.shape[0], -1)
y = self.fc(x)
return y def train(train_dataset, val_dataset, batch_size, epochs, learning_rate, wt_decay):
# 载入数据并分割batch
train_loader = data.DataLoader(train_dataset, batch_size)
# 构建模型
model = FaceCNN()
# 损失函数
loss_function = nn.CrossEntropyLoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=learning_rate, weight_decay=wt_decay)
# 学习率衰减
# scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.8)
# 逐轮训练
for epoch in range(epochs):
# 记录损失值
loss_rate = 0
# scheduler.step() # 学习率衰减
model.train() # 模型训练
for images, labels in train_loader:
# 梯度清零
optimizer.zero_grad()
# 前向传播
output = model.forward(images)
# 误差计算
loss_rate = loss_function(output, labels)
# 误差的反向传播
loss_rate.backward()
# 更新参数
optimizer.step() # 打印每轮的损失
print('After {} epochs , the loss_rate is : '.format(epoch + 1), loss_rate.item())
if epoch % 5 == 0:
model.eval() # 模型评估
acc_train = validate(model, train_dataset, batch_size)
acc_val = validate(model, val_dataset, batch_size)
print('After {} epochs , the acc_train is : '.format(epoch + 1), acc_train)
print('After {} epochs , the acc_val is : '.format(epoch + 1), acc_val) return model def main():
# 数据集实例化(创建数据集)
train_dataset = FaceDataset(root='D:\PyCharm_Project\deep learning\model\cnn_train')
val_dataset = FaceDataset(root='D:\PyCharm_Project\deep learning\model\cnn_val')
# 超参数可自行指定
model = train(train_dataset, val_dataset, batch_size=128, epochs=100, learning_rate=0.1, wt_decay=0)
# 保存模型
torch.save(model, 'model_net.pkl') if __name__ == '__main__':
main()

以上程序代码的执行过程需要较长时间,目前我只能在CPU上跑程序,速度慢,算力不足,我差不多用了1天时间训练100轮,训练时间看不同电脑设备配置,如果在GPU上跑会快很多。
下面截取几个训练结果:

从结果可以看出,训练在60轮的时候,模型在训练集上的准确率达到99%以上,而在测试集上只有60%左右,很明显出现过拟合的情况,还可以进一步优化参数,使用正则等方法防止过拟合。另外,后面几十轮训练的提升很低,还需要找出原因。
这个过程我还在学习中,上面是目前达到的结果,希望之后能够把这个模型进一步优化,提高准确率。
小结:
学习了机器学习和深度学习有一段时间,基本上看的是李宏毅老师讲解的理论知识,还未真正去实现训练一个模型。这篇记录我第一次学习的项目过程,多有不足,还需不断实践。目前遇到的问题是:1、基本的理论知识能够理解,但是在公式推导和模型选择还未很好掌握。2、未具备训练一个模型的经验(代码实现),后续需要学习实战项目。

参考资料:

机器学习-李宏毅(2019)视频
https://ntumlta2019.github.io/ml-web-hw3/
https://www.cnblogs.com/HL-space/p/10888556.html
https://github.com/amineHorseman/facial-expression-recognition-using-cnn
————————————————

这是我的CSDN博客链接,欢迎交流:
版权声明:本文为CSDN博主「Charzous」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Charzous/article/details/107452464

Pytorch实现基于卷积神经网络的面部表情识别(详细步骤)的更多相关文章

  1. 基于卷积神经网络的面部表情识别(Pytorch实现)----台大李宏毅机器学习作业3(HW3)

    一.项目说明 给定数据集train.csv,要求使用卷积神经网络CNN,根据每个样本的面部图片判断出其表情.在本项目中,表情共分7类,分别为:(0)生气,(1)厌恶,(2)恐惧,(3)高兴,(4)难过 ...

  2. 基于卷积神经网络的人脸识别项目_使用Tensorflow-gpu+dilib+sklearn

    https://www.cnblogs.com/31415926535x/p/11001669.html 基于卷积神经网络的人脸识别项目_使用Tensorflow-gpu+dilib+sklearn ...

  3. 深度学习项目——基于卷积神经网络(CNN)的人脸在线识别系统

    基于卷积神经网络(CNN)的人脸在线识别系统 本设计研究人脸识别技术,基于卷积神经网络构建了一套人脸在线检测识别系统,系统将由以下几个部分构成: 制作人脸数据集.CNN神经网络模型训练.人脸检测.人脸 ...

  4. 硕毕论文_基于 3D 卷积神经网络的行为识别算法研究

    论文标题:基于 3D 卷积神经网络的行为识别算法研究 来源/作者机构情况: 中  国  地  质  大  学(北京),计算机学院,图像处理方向 解决问题/主要思想贡献: 1. 使用张量CP分解的原理, ...

  5. 【RS】Automatic recommendation technology for learning resources with convolutional neural network - 基于卷积神经网络的学习资源自动推荐技术

    [论文标题]Automatic recommendation technology for learning resources with convolutional neural network ( ...

  6. 完全基于卷积神经网络的seq2seq

    本文参考文献: Gehring J, Auli M, Grangier D, et al. Convolutional Sequence to Sequence Learning[J]. arXiv ...

  7. PyTorch基础——使用卷积神经网络识别手写数字

    一.介绍 实验内容 内容包括用 PyTorch 来实现一个卷积神经网络,从而实现手写数字识别任务. 除此之外,还对卷积神经网络的卷积核.特征图等进行了分析,引出了过滤器的概念,并简单示了卷积神经网络的 ...

  8. 基于卷积神经网络CNN的电影推荐系统

    本项目使用文本卷积神经网络,并使用MovieLens数据集完成电影推荐的任务. 推荐系统在日常的网络应用中无处不在,比如网上购物.网上买书.新闻app.社交网络.音乐网站.电影网站等等等等,有人的地方 ...

  9. 使用卷积神经网络CNN训练识别mnist

    算的的上是自己搭建的第一个卷积神经网络.网络结构比较简单. 输入为单通道的mnist数据集.它是一张28*28,包含784个特征值的图片 我们第一层输入,使用5*5的卷积核进行卷积,输出32张特征图, ...

随机推荐

  1. Unity常见的三种数据本地持久化方案

    做游戏的时候常常会有数据配置或者存读档的需求,本文整理了常用的几种解决方案,分别是Unity自带的PlayerPrefs类,XML文件和Json文件. 一. PlayerPrefs 这是Unity自带 ...

  2. app之功能测试

    1 什么是APP测试? App测试就是软件工程师对这类应用软件进行功能测试,性能测试,安全性测试以及兼容性测试等. 对于app测试我们一般采用的是黑盒测试方法,也会在必要的时候进行自动化测试以及性能测 ...

  3. Day12-微信小程序实战-交友小程序-优化“附近的人”页面与serach组件的布局和样式以及搜索历史记录和本地缓存*内附代码)

    回顾/:我们已经实现了显示附近的人的功能了,可以多个人看到附近的人页面了 但是还是要进行优化有几个问题:1.我们用户选择了其他的自定义头像之后,在首页可以看到头像的变化,但是在附近的人中头像会变成报错 ...

  4. 计算机网络之tcp/ip协议族

    TCP/IP协议族是一个四层协议系统: 1. 数据链路层   1.1 作用  (1) 实现网卡接口的网络驱动,以处理数据在以太网线等物理媒介上的传输  (2) 网络驱动程序隐藏了不同物理网络的不同电气 ...

  5. SerializableClob转String

    ORACLE数据库读取CLOB字段,JAVA中获取到类型为SerializableClob,如何转换成String类型那? 以下是代码示例:         SerializableClob sc = ...

  6. 猿灯塔:关于Java面试,你应该准备这些知识点

    自天子以至于庶人,壹是皆以修身为本 <礼记·大学> 马老师说过,员工的离职原因很多,只有两点最真实: 钱,没给到位 心,受委屈了 当然,我是想换个平台,换个方向,想清楚为什么要跳槽,如果真 ...

  7. 面试WEB前端如何才能通过?

    从事web前端工作七年时间,因为一直是非常热爱编程的,从小就有兴趣,大学就是学计算机的,技术应该比一般同龄的都要好一些,今天我想给大家讲述一下,目前想要做web前端开发,面试成功应该如何去学习,要具备 ...

  8. linux系统配置常用命令top

    本人测试系统:centos7 命令名称:top Linux top命令用于实时显示 process 的动态. 参数:-b 批处理 -c 显示完整的治命令 -I 忽略失效过程 -s 保密模式 -S 累积 ...

  9. NPOI升级版本问题

    最近做了一个导出Word的功能,需要样式与排版果断选择了NPOI,本以为支持Excel很好,支持Word应该也不会错吧,万万没想到还是挣扎了小一星期. 我的项目是一个13年项目,NPOI版本还很旧,我 ...

  10. Linux多任务编程之六:编写多进程程序及其代码(转)

    来源:CSDN  作者:王文松  转自Linux公社 ------------------------------------------------------------------------- ...