VGG_Net实现人脸性别识别

数据处理部分

VGG_Net包含两个部分,一个是特征提取部分,一个是分类器部分。
实际应用中,由于样本的缺失,很少有人会从头开始训练整个卷积神经网络(以随机初始化权重的方式)。相反,通常的做法是在一个很大的数据集上对神经网络进行预训练(例如ImageNet,该网络包含1000种类别的100多万张图片),然后将训练好的卷积神经网络用来权重初始化或者作为特征提取器。
这两个迁移学习的主要场景如下:
1. 对卷积神经网络进行微调(Finetuning the convnet):与随机初始化的方式不同,该方法通过一个已经经过预训练的网络来对神经网络的权重进行初始化(例如使用上文中所说到的在Image Net上训练得到的网络)剩下的训练部分与普通的网络训练方式一样。
2. 卷积神经网络作为一个特征提取器(ConvNet as fixed feature extractor): 在这种方式下,神经网络的权重除了全连接层外都不会再发生变化,也就是权重的大小会被固定下来。而最后的全连接层会通过随机的方式对其权重进行初始化,并且在对该网络进行训练的过程中只对当前层进行训练。

Pytorch提供了很好的数据预处理的封装。本文所讨论的是人脸性别属于分类问题,在对分类的数据进行处理的时候,可以使用Pytorch提供的ImageFolder类来实现数据预处理。

例如,在本次训练中,要训练的数据分为trainval两个部分。
文件目录如下所示:

.
├── train
│   ├── man
│   └── woman
└── val
    ├── man
    └── woman

首先需要定义数据集的根目录:

data_dir = '/home/xx/workspace/wiki_crop/gender/'

然后,对于trainval这两个分别使用ImageFolder处理:

image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x))
                  for x in ['train','val']}

这时,ImageFolder已经完成了照片数据的分类,并将这些图片的分类信息放倒了image_datasets变量中,
image_datasets变量的结构如下所示:
这里写图片描述

可以看到,ImageFolder类已经将性别manwoman做好了分类,并赋值为0和1。并且,训练数据以及测试数据被很好的分开。

有了ImageFolder获取到的image_datasets,这里只是找到了数据的路径以及相对应的类别,Pytorch还提供了DataLoader类,用于在训练时,实时获取数据对应的训练数据。代码如下:

dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,
                                             shuffle=True, num_workers=4)
              for x in ['train', 'val']}

DataLoader的第一个参数为上面获取到的image_datasets,第二个参数为batch_size,表示的是批训练时每批样本的数量。参数shuffle表示的是是否打乱数据的顺序,True表示打乱。参数num_workers表示参与计算的CPU
核心数。

数据预处理

Pytorch提供了一个数据预处理的操作对象。定义如下:

data_transforms = {
    'train': transforms.Compose([
        # transforms.RandomResizedCrop(224),
        transforms.CenterCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

data_transforms对象在ImageFolder进行数据处理的时候作为参数传入,可以将上面数据处理的代码改为如下形式:

image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train','val']}

建立网络模型

Pytorch提供了很多训练好的网络模型的实现,这些网络结构都是成熟的算法,可以直接拿来用。
出于算法速度的考虑,本文选取了一种较为快速的模型训练方式,即将卷积神经网络作为特征提取器,将提取出的特征送到全连接层进行训练。
使用到的代码如下所示:

model_conv = torchvision.models.resnet18(pretrained=True)

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

# Parameters of newly constructed modules have requires_grad=True by default
num_ftrs = model_conv.fc.in_features

有了模型以后,需要对该模型进行优化。优化的好不好,需要有一个评价指标,如下所示为对于该评价指标的定义:

# 定义交叉熵代价函数
criterion = nn.CrossEntropyLoss()

有了代价函数以后,在优化模型的时候还需要一个优化器。这里选择了随机梯度下降算法,定义如下:

optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)

在执行梯度优化的时候,需要设置一个学习率,Pytorch提供了一个学习率自动递减的学习率对象,定义如下:

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)

有了这些以后,就可以开始对模型进行训练了。

模型训练

本次教程主要使用了在第一章中提到的模型训练方法:将卷积神经网络作为特征提取器。因此在训练神经网络的时候,只会对当前网络模型的全连接层进行训练(这里的全连阶层是自己定义的)。训练的流程定义如下函数所示,局部重要内容会有汉语标注:

# 训练模型
# 参数说明:
# model:待训练的模型
# criterion:评价函数
# optimizer:优化器
# scheduler:学习率
# num_epochs:表示实现完整训练的次数,一个epoch表示一整個训练周期
def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
    # 定义训练开始时间
    since = time.time()
    #用于保存最优的权重
    best_model_wts = copy.deepcopy(model.state_dict())
    #最优精度值
    best_acc = 0.0
    # 对整个数据集进行num_epochs次训练
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        # 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`数据
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)   # 当前批次的训练输入 
                labels = labels.to(device)   # 当前批次的标签输入

                # 将梯度参数归0
                optimizer.zero_grad()

                # 前向计算
                # track history if only in train
                with torch.set_grad_enabled(phase == 'train'):
                    # 相应输入对应的输出
                    outputs = model(inputs)
                    # 取输出的最大值作为预测值preds
                    _, preds = torch.max(outputs, 1)
                    # 计算预测的输出与实际的标签之间的误差
                    loss = criterion(outputs, labels)
                    # backward + optimize only if in training phase
                    if phase == 'train':
                    # 对误差进行反向传播
                        loss.backward()
                        # 执行优化器对梯度进行优化
                        optimizer.step()

                # statistics
                # 计算`running_loss`和`running_corrects`
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)
        # 当前轮的损失
            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 == 'val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())

        print()
    # 计算训练所需要的总时间
    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    # load best model weights
    # 加载模型的最优权重
    model.load_state_dict(best_model_wts)
    return model

可视化预测结果

定义如下函数,对训练好的模型进行测试:

def visualize_model(model, num_images=6):
    was_training = model.training
    model.eval()
    images_so_far = 0
    fig = plt.figure()

    with torch.no_grad():
        for i, (inputs, labels) in enumerate(dataloaders['val']):
            inputs = inputs.to(device)
            labels = labels.to(device)

            outputs = model(inputs)
            _, preds = torch.max(outputs, 1)

            for j in range(inputs.size()[0]):
                images_so_far += 1
                ax = plt.subplot(num_images//2, 2, images_so_far)
                ax.axis('off')
                ax.set_title('predicted: {}'.format(class_names[preds[j]]))
                imshow(inputs.cpu().data[j])

                if images_so_far == num_images:
                    model.train(mode=was_training)
                    return
        model.train(mode=was_training)

使用自定义的网络结构

上面讲了如何使用现有的神经网络模型来实现本文的人脸性别识别任务。那么如果想自己定义一个网络结构来实现这样的任务该怎么做呢?此时我们可以使用自定义的网络结构来实现。下面所示的代码定义了一个性别识别的模型:

import torch
import torch.nn as nn
import torch.nn.functional as F

class GENDER_Net(nn.Module):
    def __init__(self):
        super(GENDER_Net, self).__init__()
        # 第一个卷积层,核的大小为5*5
        self.conv1 = nn.Conv2d(3, 6, kernel_size=5)
        # 第二个卷积层,核的大小为5*5
        self.conv2 = nn.Conv2d(6, 10, kernel_size=5)
        # 第一个全连接层,第一个参数输入的维度取决于卷积后图像特征的维度,第二个参数为输出维度可以任意
        self.fc1 = nn.Linear(5000, 800)
        # 定义一个Dropout失活层,若不手动定义参数,则默认参数为p=0.5
        self.fc1_drop = nn.Dropout2d()
        # 定义全连接层的输出
        self.fc2 = nn.Linear(800, 2)
    # 前向传播的过程
    def forward(self, x):
        x = self.conv1(x)
        x = F.max_pool2d(x,2)
        x = F.relu(x)
        # x = F.relu(F.max_pool2d(self.conv1(x), 2))
        x = self.conv2(x)
        x = self.conv2_drop(x)
        x = F.max_pool2d(x,2)
        x = F.relu(x)
        # x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
        x = x.view(-1, 5000)
        x = self.fc1(x)
        x = F.relu(x)
        # x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
    # softmax输出,网络的预测输出对应于输出值最大时所处的位置
        return F.softmax(x)

在使用自定义模型时,直接使用下面代码进行声明:

model = GENDER_Net()

模型的保存与调用

保存

模型训练好以后,需要将其保存到本地,以便下一次直接使用。下面的代码显示了如何保存模型:

torch.save(model,"GenderModel.pkl")

调用

下面的代码演示,如何调用训练好的、保存到本地的模型:

model = torch.load("GenderModel.pkl")
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值