卷积神经网络的训练是耗时的,很多场合不可能每次都从随机初始化参数开始训练网络。
 
1.训练
pytorch中自带几种常用的深度学习网络预训练模型,如VGG、ResNet等。往往为了加快学习的进度,在训练的初期我们直接加载pre-train模型中预先训练好的参数,所以这里使用的网络是:
torchvision.models.Resnet34(pretrained=True)

然后更改其最后的全连接层。因为resnet网络最后一层分类层fc是对1000种类型进行划分,对于自己的数据集,这里进行的是性别检测,只有男/女2类,所以修改的代码为:

#提取fc层中固定的参数
fc_features = model.fc.in_features
#修改类别为2
model.fc = nn.Linear(fc_features, )

在网上看见一些教程仅对最后一层全连接层的参数进行训练,所以他们的设定为:

#冻结参数,不训练卷积层网络
for param in model_conv.parameters():
param.requires_grad = False

然后下面就是定义你使用的损失函数,优化器和学习率调整的算法:

注意在这里可以看见优化器中输入的参数为model_conv.fc.parameters(),即仅对全连接层fc的参数进行调参

#定义使用的损失函数为交叉熵代价函数
criterion = nn.CrossEntropyLoss()
#定义使用的优化器
optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.0001, momentum=0.9)
#设置自动递减的学习率,等间隔调整学习率,即在25个step时,将学习率调整为 lr*gamma
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_conv, step_size=, gamma=0.1)

但是我训练过程发现这样做的效果不是很好,我还是对整个网络的参数都进行了训练,所以我没有进行冻结参数的设定,同时使用的算法中传入的参数是model_conv.parameters()

这样得到的最好训练结果是:

train Loss: 0.1020 Acc: 0.9617
val Loss: 0.0622 Acc: 0.9820
Training complete in 336m 56s
Best val Acc: 0.982000

可见其实还没有训练完,还是欠拟合的状态

⚠️补充知识:optimizer.step()和scheduler.step()的区别:
optimizer.step()通常用在每个mini-batch之中,而scheduler.step()通常用在epoch里面,但是不绝对,可以根据具体的需求来做。只有用了optimizer.step(),模型才会更新,而scheduler.step()是对lr进行调整。

然后后面我调了一下优化器,从momentum改成了Adam,并将学习率的调整step_size更改为20,让其一开始能更快收敛:

#定义的优化器
optimizer_conv = optim.Adam(model_conv.parameters(), lr=0.0001, betas=(0.9, 0.99))
#设置自动递减的学习率,等间隔调整学习率,即在20个step时,将学习率调整为 lr*gamma
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_conv, step_size=, gamma=0.1)

运行结果优化为:

train Loss: 0.0668 Acc: 0.9725
val Loss: 0.0630 Acc: 0.9820
Training complete in 385m 42s
Best val Acc: 0.986000

可见效果好了一点,但是还是没能训练完,之后又对代码进行了更改,将step_size调节得更大,使得一开始能够收敛得快一些,以免后面的学习率过小后一直收敛不下去:

optimizer_conv = optim.Adam(model_conv.parameters(), lr=0.0001, betas=(0.9, 0.99))
#设置自动递减的学习率,等间隔调整学习率,即在45个step时,将学习率调整为 lr*gamma
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_conv, step_size=, gamma=0.1)

判断的代码也进行了更改,不再是以最高的val acc作为最优参数的选择,而是选择train acc最高且train acc大于val acc的训练参数:

            # deep copy the model
# 对模型进行深度复制 if phase == 'train' and epoch_acc > best_train_acc:
temp = epoch_acc
if phase =='val' and epoch_acc > and epoch_acc < temp:
best_train_acc = temp
best_val_acc = epoch_acc
best_iteration = epoch
best_model_wts = copy.deepcopy(model.state_dict())

这样的返回值果然更好了一点:

Training complete in 500m 10s
Best epoch: 166.000000
Best train Acc: 0.985175
Best val Acc: 0.984000

代码运行过程中出现了错误:

RuntimeError: Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same 

这个的解决办法是为model_conv也添加.to(device):

model_conv.to(device)

另一个错误是:

RuntimeError: Expected object of backend CPU but got backend CUDA for argument # 'mat1' 

觉得原因可能是因为我的机器上面只有一个cuda,所以要显示指明使用的是'cuda:0':

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

或者是因为一开始我将model_conv.to(device)写错了位置,写到了model_conv.fc = nn.Linear(fc_features, 2)之前,写到其后面即可

所以最后的训练代码为:

# coding:utf8
from torchvision import datasets, models
from torch import nn, optim
from torchvision import transforms as T
from torch.utils import data import os
import copy
import time
import torch #首先进行数据的处理
data_dir = './data'
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") #转换图片数据
normalize = T.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])
data_transforms ={
'train': T.Compose([
T.RandomResizedCrop(),#从图片中心截取
T.RandomHorizontalFlip(),#随机水平翻转给定的PIL.Image,翻转概率为0.
T.ToTensor(),#转成Tensor格式,大小范围为[,]
normalize
]), 'val': T.Compose([
T.Resize(),#重新设定大小
T.CenterCrop(),
T.ToTensor(),
normalize
]),
} #加载图片
#man的label为0, woman的label为1
image_datasets = {x : datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'val']} #得到train和val中的数据量
dataset_sizes = {x : len(image_datasets[x].imgs) for x in ['train', 'val']}
dataloaders = {x : data.DataLoader(image_datasets[x], batch_size=, shuffle=True,num_workers=) for x in ['train', 'val']} #然后选择使用的模型
model_conv = models.resnet34(pretrained=True)
#冻结参数,不训练卷积层网络
#for param in model_conv.parameters():
# param.requires_grad = False #提取fc全连接层中固定的参数,后面的训练只对全连接层的参数进行优化
fc_features = model_conv.fc.in_features
#修改类别为2,即man和woman
model_conv.fc = nn.Linear(fc_features, )
model_conv.to(device)
#定义使用的损失函数为交叉熵代价函数
criterion = nn.CrossEntropyLoss()
#定义使用的优化器
#optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.0001, momentum=0.9)
#optimizer_conv = optim.SGD(model_conv.parameters(), lr=0.0001, momentum=0.9)
optimizer_conv = optim.Adam(model_conv.parameters(), lr=0.0001, betas=(0.9, 0.99))
#设置自动递减的学习率,等间隔调整学习率,即在7个step时,将学习率调整为 lr*gamma
exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer_conv, step_size=, gamma=0.1)
#exp_lr_scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer_conv, mode='min', verbose=True) # 训练模型
# 参数说明:
# model:待训练的模型
# criterion:评价函数
# optimizer:优化器
# scheduler:学习率
# num_epochs:表示实现完整训练的次数,一个epoch表示一整個训练周期
def train_model(model, criterion, optimizer, scheduler, num_epochs=):
# 定义训练开始时间
since = time.time()
#用于保存最优的权重
best_model_wts = copy.deepcopy(model.state_dict())
#最优精度值
best_train_acc = 0.0
best_val_acc = 0.0
best_iteration = # # meters,统计指标:平滑处理之后的损失,还有混淆矩阵
# loss_meter = meter.AverageValueMeter()#能够计算所有数的平均值和标准差,用来统计一个epoch中损失的平均值
# confusion_matrix = meter.ConfusionMeter()#用来统计分类问题中的分类情况,是一个比准确率更详细的统计指标 # 对整个数据集进行num_epochs次训练
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch, num_epochs - ))
print('-' * ) #用于存储train acc还没有与val acc比较之前的值
temp =
# Each epoch has a training and validation phase
# 每轮训练训练包含`train`和`val`的数据
for phase in ['train', 'val']:
if phase == 'train':
# 学习率步进
scheduler.step()
# 设置模型的模式为训练模式(因为在预测模式下,采用了`Dropout`方法的模型会关闭部分神经元)
model.train() # Set model to training mode
else:
# 预测模式
model.eval() # Set model to evaluate mode running_loss = 0.0
running_corrects = # Iterate over data.
# 遍历数据,这里的`dataloaders`近似于一个迭代器,每一次迭代都生成一批`inputs`和`labels`数据,
# 一批有四个图片,一共有dataset_sizes['train']/4或dataset_sizes['val']/4批
# 这里循环几次就看有几批数据
for inputs, labels in dataloaders[phase]:
inputs = inputs.to(device) # 当前批次的训练输入
labels = labels.to(device) # 当前批次的标签输入
# print('input : ', inputs)
# print('labels : ', labels) # 将梯度参数归0
optimizer.zero_grad() # 前向计算
# track history if only in train
with torch.set_grad_enabled(phase == 'train'):
# 相应输入对应的输出
outputs = model(inputs)
# print('outputs : ', outputs)
# 取输出的最大值作为预测值preds,dim=,得到每行中的最大值的位置索引,用来判别其为0或1
_, preds = torch.max(outputs, )
# print('preds : ', preds)
# 计算预测的输出与实际的标签之间的误差
loss = criterion(outputs, labels)
# backward + optimize only if in training phase
if phase == 'train':
# 对误差进行反向传播
loss.backward()
#scheduler.step(loss) #当使用的学习率递减函数为optim.lr_scheduler.ReduceLROnPlateau时,使用在这里
# 执行优化器对梯度进行优化
optimizer.step() # loss_meter.add(loss.item())
# confusion_matrix.add(outputs.detach(), labels.detach()) # statistics
# 计算`running_loss`和`running_corrects`
#loss.item()得到的是此时损失loss的值
#inputs.size()得到的是一批图片的数量,这里为4
#两者相乘得到的是4张图片的总损失
#叠加得到所有数据的损失
running_loss += loss.item() * inputs.size()
#torch.sum(preds == labels.data)判断得到的结果中有几个正确,running_corrects得到四个中正确的个数
#叠加得到所有数据中判断成功的个数
running_corrects += torch.sum(preds == labels.data) # 当前轮的损失,除以所有数据量个数得到平均loss值
epoch_loss = running_loss / dataset_sizes[phase]
# 当前轮的精度,除以所有数据量个数得到平均准确度
epoch_acc = running_corrects.double() / dataset_sizes[phase] print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc)) # deep copy the model
# 对模型进行深度复制
if phase == 'train' and epoch_acc > best_train_acc:
temp = epoch_acc
if phase =='val' and epoch_acc > and epoch_acc < temp:
best_train_acc = temp
best_val_acc = epoch_acc
best_iteration = epoch
best_model_wts = copy.deepcopy(model.state_dict()) # 计算训练所需要的总时间
time_elapsed = time.time() - since
print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
print('Best epoch: {:4f}'.format(best_iteration))
print('Best train Acc: {:4f}'.format(best_train_acc))
print('Best val Acc: {:4f}'.format(best_val_acc)) # load best model weights
# 加载模型的最优权重
model.load_state_dict(best_model_wts)
return model if __name__ == '__main__':
model_train = train_model(model_conv, criterion, optimizer_conv, exp_lr_scheduler)
torch.save(model_train, 'GenderTest.pkl')
 
2.测试

使用上面生成的GenderTest.pkl进行测试:

测试数据集dataset_test.py的设计为:

# coding:utf8
import os
from torchvision import transforms as T
from PIL import Image
from torch.utils import data class GenderData(data.Dataset):
def __init__(self, root, transforms=None):
#将图片路径存储在imgs列表中
imgs = [os.path.join(root, img) for img in os.listdir(root)] self.imgs = imgs
if transforms is None:
#设置对数据进行转换的transform
normalize = T.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])
self.transforms = T.Compose([
T.Resize(),
T.CenterCrop(),
T.ToTensor(),
normalize
]) def __getitem__(self, index):
img_path = self.imgs[index]
#得到图片数据
data = Image.open(img_path)
#对数据进行转换
data = self.transforms(data)
return data def __len__(self):
return len(self.imgs)

测试代码test.py为:

# coding:utf8
import visdom
from datasets_test import GenderData
from torch.utils import data
from torchvision.utils import make_grid
import torch def visualize(data, preds):
viz = visdom.Visdom(env='main')
# print(data.size()) #一开始的大小为torch.Size([, , , ])
out = make_grid(data) #这样得到的输出的数据就是将四张图合成了一张图的大小,为
# print(out.size()) #torch.Size([, , ])
#因为反标准化时需要将图片的维度从(channels,imgsize,imgsieze)变成(imgsize,imgsieze,channels),这样才能与下面的std,mean正确计算
inp = torch.transpose(out, , )
# print(inp.size()) #返回torch.Size([, , ])
mean = torch.FloatTensor([0.485, 0.456, 0.406])
std = torch.FloatTensor([0.229, 0.224, 0.225])
inp = std * inp + mean
#计算完后还是要将维度变回来,所以再进行一次转换
inp = torch.transpose(inp, , )
# print(inp.size()) #返回torch.Size([, , ]) #注意,这里是因为设置了batch_size为四,所以title中才这样,如果你的batch_size不是4这里需要做一些更改
viz.images(inp, opts=dict(title='{},{},{},{}'.format(preds[].item(), preds[].item(), preds[].item(), preds[].item())))
#比如下面这个就是将batch_size改成1的结果
# viz.images(inp, opts=dict(title='{}'.format(preds[].item()))) def self_dataset():
data_test_root = './data/test1' #测试数据集所在的路径
test_data = GenderData(data_test_root)
#如果只测试一张图片,这里batch_size要改成1
dataloaders = data.DataLoader(test_data, batch_size= ,shuffle=True,num_workers=)
for inputs in dataloaders:
inputs = inputs.to(device) # 当前批次的训练输入
outputs = model_test(inputs)
_, preds = torch.max(outputs, )
visualize(inputs,preds) if __name__ == '__main__':
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print(device) #导入上面训练得到的效果最佳的网络,因为测试是在只有cpu的机器上跑的,所以这里要添加map_location='cpu'
model_test = torch.load('./GenderTest.pkl',map_location='cpu')
#如果你是在有GPU的机器上跑的,可以删掉map_location='cpu',并添加一行
#model_test.to(device)
model_test.eval() dataloaders = self_dataset()

要将visdom打开:

python -m visdom.server

然后运行:

python test.py

即可以在visdom中看见结果,如下面的图片被判断为女生,右上角显示1:

当然,这其实是国荣哥的剧照,只能说天生丽质的,应该不是我训练的效果问题

测试时出现一个问题:

OSError: cannot identify image file './data/test/.DS_Store'

这是因为mac系统中会自动生成一个.DS_Store隐藏文件,这里保存着针对这个目录的特殊信息和设置配置,例如查看方式、图标大小以及这个目录的一些附属元数据

而当我们想要打开图片的时候它会被当作一个图片文件路径加载,进而报错,因为它并不是一个图片文件,解决办法是在该图片文件夹下输入删除命令:

sudo find / -name ".DS_Store" -depth -exec rm {} \;

或者更简单的方法是到图片文件夹处直接运行:

rm .DS_Store

后面换成使用resnet18也能达到相近的效果,大家可以试试

pytorch实现性别检测的更多相关文章

  1. python——进行年龄和性别检测

    年龄和性别检测 使用Python编程语言带你完成使用机器学习进行年龄和性别检测的任务. 首先需要编写用于检测人脸的代码,因为如果没有人脸检测,我们将无法进一步完成年龄和性别预测的任务. 下一步是预测图 ...

  2. resnet18全连接层改成卷积层

    想要尝试一下将resnet18最后一层的全连接层改成卷积层看会不会对网络效果和网络大小有什么影响 1.首先先对train.py中的更改是: train.py代码可见:pytorch实现性别检测 # m ...

  3. 从零玩转人脸识别之RGB人脸活体检测

    从零玩转RGB人脸活体检测 前言 本期教程人脸识别第三方平台为虹软科技,本文章讲解的是人脸识别RGB活体追踪技术,免费的功能很多可以自行搭配,希望在你看完本章课程有所收获. ArcFace 离线SDK ...

  4. 资源分享 | PyTea:不用运行代码,静态分析pytorch模型的错误

    ​  前言  ​​​​​​​本文介绍一个Pytorch模型的静态分析器 PyTea,它不需要运行代码,即可在几秒钟之内扫描分析出模型中的张量形状错误.文末附使用方法. 本文转载自机器之心 编辑:CV技 ...

  5. 基于深度学习的人脸性别识别系统(含UI界面,Python代码)

    摘要:人脸性别识别是人脸识别领域的一个热门方向,本文详细介绍基于深度学习的人脸性别识别系统,在介绍算法原理的同时,给出Python的实现代码以及PyQt的UI界面.在界面中可以选择人脸图片.视频进行检 ...

  6. .Net Core建站(3):搭建三层架构

    啊,终于到写三层架构的时候了,老实说,我都不知道自己这个算不算三层架构,姑且就当它是吧,具体属于哪一个体系,希望有大佬指点一下(^o^)/ 不晓得有人注意到没有,我写了三篇博客,然后就改了三次标题ヽ( ...

  7. 一些图像识别初创公司产品及API搜集ing...

    一些公司的产品路线可以很好地给我们启示,欢迎看客补充. 一.微软认知服务API 1.年龄.性别检测 2.物体分类.识别 3.识别名人 全新的名人识别模块可以识别20万来自全球各地涉及商界.政界.体育界 ...

  8. 虹软人脸识别ArcFace2.0 Android SDK使用教程

    一.获取SDK 1.进入ArcFace2.0的申请地址 https://ai.arcsoft.com.cn/product/arcface.html 2.填写信息申请并提交 申请通过后即可下载SDK, ...

  9. 【资源分享】ArcFace Demo [Android]

    虹软人脸识别引擎Android的Demo演示,可以直接下载使用. 下载地址: https://github.com/asdfqwrasdf/ArcFaceDemo readme: 工程如何使用? 下载 ...

随机推荐

  1. RDIFramework.NET ━ .NET快速信息化系统开发框架 V3.2->WinForm版本新增新的角色授权管理界面效率更高、更规范

    角色授权管理模块主要是对角色的相应权限进行集中设置.在角色权限管理模块中,管理员可以添加或移除指定角色所包含的用户.可以分配或授予指定角色的模块(菜单)的访问权限.可以收回或分配指定角色的操作(功能) ...

  2. 图像检索(1): 再论SIFT-基于vlfeat实现

    概述 基于内容的图像检索技术是采用某种算法来提取图像中的特征,并将特征存储起来,组成图像特征数据库.当需要检索图像时,采用相同的特征提取技术提取出待检索图像的特征,并根据某种相似性准则计算得到特征数据 ...

  3. [十九]JavaIO之PipedReader 和 PipedWriter

    功能简介 还记得PipedInputStream  和 PipedOutputStream么 我们之前是这么说的: p, li { white-space: pre-wrap; } 使用管道通信时,必 ...

  4. c#委托中的同步和异步方法即BeginInvoke和EndInvoke

    学习多线程之前我们先了解一下电脑的一些概念,比如进程,线程,这个参考https://www.cnblogs.com/loverwangshan/p/10409755.html 这篇文章.今天我们接着来 ...

  5. lua的String

    基础字符串函数 字符串库中有一些函数非常简单,如:    1). string.len(s) 返回字符串s的长度:    2). string.rep(s,n) 返回字符串s重复n次的结果:    3 ...

  6. EF三种编程方式的区别Database first ,Model first ,code first

    首先对于EF中先出现的datebase  first和model first两种编程方式,其的区别根据字面意思很容易能够理解. datebase  first就是代表数据库优先,那么前提就是先创建数据 ...

  7. JQuery实现数组移除指定元素

    公式: 数组.splice($.inArray(元素,数组),数量); 实例: var arr = ['a','b','c','d']; arr.splice($.inArray('c',arr),1 ...

  8. Java开发笔记(十九)规律变化的for循环

    前面介绍while循环时,有个名叫year的整型变量频繁出现,并且它是控制循环进出的关键要素.不管哪一种while写法,都存在三处与year有关的操作,分别是“year = 0”.“year<l ...

  9. Android开发——使用intent传递对象

    intent传递对象有两种方法: 方式一:Serializable 方式 方式二:Parcelable方式 在这里不多介绍了,这一篇就是快速上手使用教程,至于详细原理介绍的,请看这一篇http://w ...

  10. 【JVM】7、深入理解Java G1垃圾收集器

    本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践. 一,什么是垃圾回收 首先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么 ...