zoukankan      html  css  js  c++  java
  • pytorch实现性别检测

    卷积神经网络的训练是耗时的,很多场合不可能每次都从随机初始化参数开始训练网络。
     
    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, 2)

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

    #冻结参数,不训练卷积层网络
    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=25, 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=20, 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=45, 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 > 0 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 #4 '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(224),#从图片中心截取
                    T.RandomHorizontalFlip(),#随机水平翻转给定的PIL.Image,翻转概率为0.5  
            T.ToTensor(),#转成Tensor格式,大小范围为[0,1]
            normalize
        ]),
    
        'val': T.Compose([
            T.Resize(256),#重新设定大小 
            T.CenterCrop(224),
            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=4, shuffle=True,num_workers=4) 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, 2)
    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=45, 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=200):
        # 定义训练开始时间
        since = time.time()
        #用于保存最优的权重
        best_model_wts = copy.deepcopy(model.state_dict())
        #最优精度值
        best_train_acc = 0.0
        best_val_acc = 0.0
        best_iteration = 0
    
        # # meters,统计指标:平滑处理之后的损失,还有混淆矩阵
        # loss_meter = meter.AverageValueMeter()#能够计算所有数的平均值和标准差,用来统计一个epoch中损失的平均值
        # confusion_matrix = meter.ConfusionMeter(2)#用来统计分类问题中的分类情况,是一个比准确率更详细的统计指标
    
        # 对整个数据集进行num_epochs次训练
        for epoch in range(num_epochs):
            print('Epoch {}/{}'.format(epoch, num_epochs - 1))
            print('-' * 10)
            
            #用于存储train acc还没有与val acc比较之前的值
            temp = 0
            # 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 = 0
    
                # 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=1,得到每行中的最大值的位置索引,用来判别其为0或1
                        _, preds = torch.max(outputs, 1)
                        # 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(0)得到的是一批图片的数量,这里为4
                    #两者相乘得到的是4张图片的总损失
                    #叠加得到所有数据的损失
                    running_loss += loss.item() * inputs.size(0)
                    #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 > 0 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(224),
                    T.CenterCrop(224),
                    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([4, 3, 224, 224])
        out = make_grid(data) #这样得到的输出的数据就是将四张图合成了一张图的大小,为
        # print(out.size()) #torch.Size([3, 228, 906])
        #因为反标准化时需要将图片的维度从(channels,imgsize,imgsieze)变成(imgsize,imgsieze,channels),这样才能与下面的std,mean正确计算
        inp = torch.transpose(out, 0, 2)
        # print(inp.size()) #返回torch.Size([906, 228, 3])
        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, 0, 2)
        # print(inp.size()) #返回torch.Size([3, 228, 906])
    
        #注意,这里是因为设置了batch_size为四,所以title中才这样,如果你的batch_size不是4这里需要做一些更改
        viz.images(inp, opts=dict(title='{},{},{},{}'.format(preds[0].item(), preds[1].item(), preds[2].item(), preds[3].item())))
        #比如下面这个就是将batch_size改成1的结果
        # viz.images(inp, opts=dict(title='{}'.format(preds[0].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=4 ,shuffle=True,num_workers=4) for inputs in dataloaders: inputs = inputs.to(device) # 当前批次的训练输入 outputs = model_test(inputs) _, preds = torch.max(outputs, 1) 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也能达到相近的效果,大家可以试试


  • 相关阅读:
    02 微服务应用的认证与授权
    01 微服务架构中的服务发现作用以及两种协议的调用区别
    04 redis的数据结构与对象
    03 Spring的事务管理的基本使用
    02 Spring的AOP的基础概念与简单使用
    k8s环境部署
    elk安装
    实现AD域验证登录--
    linux SSH各配置项解释
    Linux学习之SAMBA共享(密码验证)
  • 原文地址:https://www.cnblogs.com/wanghui-garcia/p/10679089.html
Copyright © 2011-2022 走看看