卷积神经网络实战——表情识别(Pytorch)超详细理解,含Pyqt5的可操作界面
Posted 燃烧吧哥们
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了卷积神经网络实战——表情识别(Pytorch)超详细理解,含Pyqt5的可操作界面相关的知识,希望对你有一定的参考价值。
卷积神经网络实战——表情识别(Pytorch)
这里作一下申明,之前对于神经网络的搭建解释的不够全面,这里进行补充,训练过程的代码可能太过繁琐不好理解,现在进行补充和修改,然后关于数据集用的是阿里天池公开课里面的表情识别数据集。
变化的部分会以红色字体标出
日志:
2022.6.20:添加exp_lr_scheduler.step(),更新学习率,不加这一步学习率不会更新。
前面六篇文章详细介绍了神经网络的基础,下面就一个简单的识别案例对前六章进行一个总结(可能有人对前六章在具体的网络中是干什么的有疑惑,下面进行具体代码分析)。
一、数据集
二、构建神经网络
首先我们要构建一个搭建网络的类,在类中主要实现网络的两部分,一部分是网络层的定义,另一部分将网络层连接起来构成网络
在之前的篇章神经网络基础篇中我们知道网络的构建需要以下:
(1)卷积层
(2)激活
(3)池化
(4)全连接
(5)Batch Normalization
当然不同的网络构造采用的方式不一样,有的只包含其中的几层。
接下来对要构造的神经网络进行一个说明:
我们要构造的是一个3层卷积,3层全连接的网络,其中包含激活,以及Batch Normalization。
下图是网络结构的整体流程图,后面的部分没有画图,直接写了尺寸大小:
下面是构建网络层的代码(网络并不绝对,可以尝试不同的网络构建)
关于网络的python基础构建可以参考我的这篇文章(要想不借助别人的代码自己构建这一点很重要)
卷积神经网络构建的python基础-详细理解(Pytorch)
这里解释一下torch.nn,和 torch.nn.functional
前者可以自动提取可学习的参数,模型有可学习的参数可以采用(像卷积、全连接这些),后者进行简单的数学运算,适合像Relu这样的激活函数(激活函数无参数)直接运算。
import torch.nn as nn
import torch.nn.functional as F
class simpleconv(nn.Module):
def __init__(self, nclass):
super().__init__()
# conv1
self.conv1 = nn.Conv2d(3, 12, 3, 2) #卷积核大小:3,步长:2
self.bn1 = nn.BatchNorm2d(12)
# conv2
self.conv2 = nn.Conv2d(12, 24, 3, 2)#卷积核大小:3,步长:2
self.bn2 = nn.BatchNorm2d(24)
# conv3
self.conv3 = nn.Conv2d(24, 48, 3, 2)#卷积核大小:3,步长:2
self.bn3 = nn.BatchNorm2d(48)
# 三层全连接
self.fc1 = nn.Linear(48 * 5 * 5, 1200)
self.fc2 = nn.Linear(1200, 128)
self.fc3 = nn.Linear(128,nclass) # 最后一层全连接输出必须时类别数
def forward(self, x):
x = F.relu(self.bn1(self.conv1(x))) # torch.Size([128, 12, 23, 23])
x = F.relu(self.bn2(self.conv2(x))) # torch.Size([128, 24, 11, 11])
x = F.relu(self.bn3(self.conv3(x))) # torch.Size([128, 48, 5, 5])
x = x.view(-1 , 48*5*5) # 压缩维度 torch.Size([128, 1200])
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
if __name__ == '__main__':
import torch
x = torch.randn(128,3,48,48) # 生成随机数字的tensor
model = simpleconv(4)
y = model(x)
关于压缩维度的操作,可以参考我的这一篇博客神经网络中view(),torch.flatten(),torch.nn.Flatten()详解
#更改:
将super(simpleconv, self)._init_()更换为super()._init_(),目前大部分采用的python版本大都是python3,因此可以改用
【注】
Python3.x 和 Python2.x 的一个区别是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx
那么这里简单总结一下,网络构建所学的东西
(1)python的基础,要知道 类的创建,继承,_init_(), forward(), self 这些的具体原理,作用
(2)要知道神经网络结果,层与层之间是怎么操作的,激活,池化等都具有怎样的作用,以及他们尺寸通道的变化。
这些之前的文章都有讲解过。
三、网络训练(理解)
我们在训练卷积神经网络的时候,主要串联以下几点,就不会太过迷茫
(1)输入数据(处理过后),数据在网络中进行卷积等操作(网络的构建)
(2)卷积,全连接操作过后得到最终的预测值,并与真实标签值计算损失(损失函数)
(3)根据损失值计算梯度进而更新参数(优化器,即梯度下降的方式)
下面我将详细的描述各个训练部分
网络的训练主要包含以下部分:
1、定义分类网络初始化相关训练参数
2、数据输入网络前的准备(数据,真实标签)
3、模型准备
4、选用损失函数
5、选用的优化器,学习率
6、训练
1、分类网络的初始化相关训练参数
关于训练参数,我们想到的有如下几条:
(1)训练轮数:就是我们想要将整个数据集训练几遍。
(2)batchsize:就是我们想要一次性输入多少数据(根据电脑性能进行选择)。
(3)类别:要识别的类别数量。
(5)数据路径:这个路径是存放训练集和测试集的根目录。
# 1、设置一些网络训练的参数
batch_size = 128 # 输入网络的batch(即一次性输入多少张图片)
nclass = 4 # 网络识别的类别
data_dir = './data' # 数据 路径
num_epochs = 100 # 训练轮数100轮
2、数据输入网络前的准备(数据,真实标签)
这一部分我们主要考虑什么样的数据才能输入到pytorch网络中进行训练呢?
(1)数据加载器:torchvision库中的datasets.ImageFolder,该库可以完成数据的读取和处理,有如下几个属性:
self.classes:一般来说是文件的名字
self.class_to_idx:类别对应的索引,从0开始
self.imgs:保存(img-path, class) tuple的列表
需要注意的是下面的transforms操作,transforms.Normalize只能对PIL、tensor格式进行操作,因此需要放在ToTensor之后,详情见我的另一篇博客
神经网络数据增强transforms的相关操作(持续更新)
# (1)数据增强处理:
data_transforms = transforms.Compose([transforms.Resize((64, 64)),
transforms.RandomSizedCrop(48),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])
# (2)进行数据读取和数据预处理:
image_datasets = datasets.ImageFolder(os.path.join(data_dir, 'train'), data_transforms)
#有以下几个属性:
print(image_datasets.classes) # 数据标签
print(image_datasets.class_to_idx) # 数据标签代号
print(image_datasets.imgs) # 路径,标签
>> ['expression_none', 'mouth_open', 'pouting', 'smile']
>>'expression_none': 0, 'mouth_open': 1, 'pouting': 2, 'smile': 3
>>[('./data\\\\train\\\\expression_none\\\\1000none.jpg', 0)...]
(2)上一步可以得到图片数据和标签,但是我们输入网络需要的是批量的图片数据以及对应标签的,因此还需将图片及标签进行batchsize分批。
dataloaders = torch.utils.data.DataLoader(image_datasets,
batch_size,
shuffle=True,
num_workers=0)
此时我们的数据就已经准备好了,可能有的小伙伴还不太理解,下面画一幅图进行理解。
总的来说如上图,这只是我个人的理解,有不对的地方望指教。
3、模型准备
模型这里主要是是否使用GPU
# 初始化网络模型,判断是否能用GPU进行训练
if torch.cuda.is_available():
device = "cuda"
else:
device = "cpu"
model = simpleconv(nclass).to(device)
4、选用损失函数
一般来说分类损失采用交叉熵损失函数,关于交叉熵损失见我的这篇博客。
卷积神经网络的深入理解-优化目标(损失函数)篇
criterion = nn.CrossEntropyLoss() # 交叉熵损失
5、选用的优化器,学习率
优化器即梯度更新的方式,详细见我的这篇博客
卷积神经网络的深入理解-最优化方法(梯度下降篇)(持续更新)
这里采用的是一个基本方法SGD,优化器和学习率主要是用于更新网络模型的参数,这里设置学习率每200轮变为原来的1/10。
optimizer_ft = optim.SGD(model.parameters(), lr=0.1, momentum=0.8) # 梯度下降方式,用于更新梯度
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=200, gamma=0.1) # 根据训练的轮数调整学习率
6、训练和验证
将模型,数据,损失,优化器(主要是这四大部分)…传入训练函数
这里,去掉了Variable以及.data,理由是版本大于等于0.4.0的Pytorch将Variable与Tensor合并了,具体见我的这篇文章
Variable与Tensor合并后,关于训练、验证的相关变化
def train(train_loader,model,criterion,optimizer,device,len_train,batch_size):
num_loss = 0.0
num_corrects = 0.0
model.train()
for i ,(data,target) in enumerate(train_loader): # enumerate:列举
data = data.to(device)
target = target.to(device)
optimizer.zero_grad()
outputs = model(data)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs,target)
loss.backward()
optimizer.step()
num_corrects += torch.sum(preds==target).item()
num_loss = num_loss + loss.item()
train_loss = num_loss / (len_train//batch_size+1) # 每一个epoch的训练损失
train_acc = num_corrects / len_train # 每一个epoch的训练正确率
return train_loss,train_acc
def val(val_loader,model,criterion,device,len_val,batch_size):
num_loss = 0.0
num_corrects = 0.0
model.eval() # 将模型转化到验证模式
with torch.no_grad(): # # 模型的参数都不会进行更新(把模型的参数固定下来)
for i ,(data,target) in enumerate(val_loader):
data = data.to(device)
target = target.to(device)
outputs = model(data)
_, preds = torch.max(outputs.data, 1)
loss = criterion(outputs, target)
num_corrects += torch.sum(preds == target).item()
num_loss = num_loss + loss.item()
val_loss = num_loss / (len_val//batch_size+1) # 每一个epoch的训练损失
val_acc = num_corrects / len_val # 每一个epoch的训练正确率
return val_loss,val_acc
四、网络训练测试总代码
原先代码进行了重写,同时也发现了原先的一些错误,
(1)打印的loss不太对,之前是直接计算总的loss然后除以数据量,但是这和batch有关,因为不管是多少个batch得到的loss都只是一个,所以做了如下操作:
num_loss / (len_train//batch_size+1)
(2)将训练与验证分开,更加方便理解
网络见二、构建神经网络
import torch
from net import simpleconv
from torchvision import transforms, datasets
import os
def train(train_loader,model,criterion,optimizer,device,len_train,batch_size):
num_loss = 0.0
num_corrects = 0.0
model.train()
for i ,(data,target) in enumerate(train_loader): # enumerate:列举
data = data.to(device)
target = target.to(device)
optimizer.zero_grad()
outputs = model(data)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs,target)
loss.backward()
optimizer.step()
num_corrects += torch.sum(preds==target).item()
num_loss = num_loss + loss.item()
train_loss = num_loss / (len_train//batch_size+1) # 每一个epoch的训练损失
train_acc = num_corrects / len_train # 每一个epoch的训练正确率
return train_loss,train_acc
def val(val_loader,model,criterion,device,len_val,batch_size):
num_loss = 0.0
num_corrects = 0.0
model.eval() # 将模型转化到验证模式
with torch.no_grad(): # # 模型的参数都不会进行更新(把模型的参数固定下来)
for i ,(data,target) in enumerate(val_loader):
data = data.to(device)
target = target.to(device)
outputs = model(data)
_, preds = torch.max(outputs.data, 1)
loss = criterion(outputs, target)
num_corrects += torch.sum(preds == target).item()
num_loss = num_loss + loss.item()
val_loss = num_loss / (len_val//batch_size+1) # 每一个epoch的训练损失
val_acc = num_corrects / len_val # 每一个epoch的训练正确率
return val_loss,val_acc
# 设置网络参数
batch_size = 128
nclass = 4
num_epochs = 300
data_dir = './data'
# 初始化网络模型
if torch.cuda.is_available():
device = "cuda"
else:
device = "cpu"
model = simpleconv(nclass).to(device)
# 准备数据
# 1、数据增强处理
train_transforms = transforms.Compose([
transforms.Resize((64, 64)),
transforms.RandomSizedCrop(48),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])
val_transforms = transforms.Compose([
transforms.Resize((64, 64)),
transforms.CenterCrop(48),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])
# 2、数据读取与处理
train_datasets = datasets.ImageFolder(os.path.join(data_dir, 'train'), train_transforms)
val_datasets = datasets.ImageFolder(os.path.join(data_dir, 'val'), val_transforms)
len_train = len(train_datasets)
len_val = len(val_datasets)
# 3、数据封装(batch)
train_loaders = torch.utils.data.DataLoader(
dataset=train_datasets,
batch_size=batch_size,
shuffle=True,
num_workers=0
)
val_loaders = torch.utils.data.DataLoader(
dataset=val_datasets,
batch_size=batch_size,
shuffle=True,
num_workers=0
)
# 损失函数(用于更新参数)、优化器(梯度下降以一个什么样的方式下降)、学习率
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
criterion = nn.CrossEntropyLoss() # 交叉熵损失函数
optimizer_ft = optim.SGD(model.parameters(), lr=0.1, momentum=0.8) # 优化器
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=200, gamma=0.1) # 根据训练的轮数调整学习率
from tensorboardX import SummaryWriter
import time
writer = SummaryWriter() # 用于生成可视化的图
best_acc = 0.0
for epoch in range(num_epochs):
start = time.time()
train_loss,train_acc = train(train_loaders,model,criterion,optimizer_ft,device,len_train,batch_size)
exp_lr_scheduler .step() # 更新学习率
val_loss,val_acc = val(val_loaders,model,criterion,device,len_val,batch_size)
# 保存最好的模型
if val_acc > best_acc:
best_acc = val_acc
torch.save(model.state_dict(), './best.pth')
writer.add_scalar('trainloss', train_loss, epoch)
writer.add_scalar('trainacc', train_acc, epoch)
writer.add_scalar('valloss', val_loss, epoch)
writer.add_scalar('valacc', val_acc, epoch)
end = time.time()
print('[/]: train_loss::.3f, train_acc::.3f,eval_loss::.3f, eval_acc::.3f, time::.3f'.format(epoch+1,num_epochs,train_loss,train_acc,val_loss,val_acc,end-start))
writer.close()
下面是有关结果
五、基于Pyqt5的Gui设计
这是界面的设计代码参考的
这篇博客
from PyQt5.QtWidgets import (QGridLayout, QPushButton, QLabel)
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
from PyQt5.QtCore import Qt
# 预测脚本
import torch
import torchvision.transforms as transforms
from PIL import Image
from net import simpleconv
import numpy as np
def predict(img):
device = torch.device('cpu')
data_tranform = transforms.Compose([transforms.Resize((48, 48)),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])
img = data_tranform(img) # 数据预处理
img = torch.unsqueeze(img, dim=0深度学习卷积神经网络(CNN)人脸表情识别系统(GUI界面)
PyTorch学习实战第四篇:MNIST数据集的读取显示以及全连接实现数字识别