基于深度卷积神经网络的人脸识别考勤系统-VGG-PYTHON-QT(4)人脸分类模型训练

本篇

 本篇将会讲解本系统所实现的卷积神经网络分类模型的训练等完整过程,数据集即上一篇博客中自己采集的人脸数据集,20人,每人50张彩色图:

在实际实践过程中,出现过各种各样的问题,也尝试了各种各样的方法提高精度,针对图像多分类的模型训练,总结了如下一些思路:

  1. 图像尺寸:避免差距过大的图像缩放造成失真,尺寸更大的图片可以更好保留更多特征。
  2. 图像通道:在对速度有要求时优先转化为灰度图进行操作,否则可以彩色图和灰度图都尝试训练一次。
  3. 图像增强:可以丰富数据集,但也不用过分地添加增强操作反而降低了数据集质量,可以有针对的选择图像增强操作,打乱数据集、归一化、轻微放缩、左右反转、对比度调整、轻度的旋转等,可以很好地减轻数据不足的问题从而提高泛化能力。
  4. 回调函数:在训练时务必添加多种回调函数,实时监测acc等数值,适时地做出对应调整,尤其是动态学习率调整和提前结束这两种回调函数,可以有效缩短训练时间。
  5. 绘制acc、valacc、loss、valloss曲线:通过曲线的方式可以最直观地分辨出模型的问题,是过拟合还是欠拟合一下就能看出,以做出对应调整。
  6. 数据集划分:有条件的情况下,一定要划分训练集、验证集、测试集,使用添加交叉验证等。
  7. 超参调整:神经元数量、batchsize、dropout值都需要特别关照,个人感觉尤其batchsize小到1大到128都有尝试的必要,需要具体情况具体分析,和数据集数量和质量关系很大。
  8. 优化函数:SGDM和ADAM可以都试试。
  9. tensorboard:有些图虽然不一定看得懂,但看看还是相当好的。
  10. 模型:多试试各种经典模型结构进行微调,添加dropout、batchnormalize等,说不定就找到效果极佳的。
  11. 实在不行就修改数据集,优化扩充,进行针对性的优化例如人脸扶正、背景删除等,数据集质量高的话,模型也会是质的飞跃。

        系统首先会按照预设地址搜索人像图片所在文件夹,获取所有的图像地址,再借助Keras的图片生成器函数,分批从数据集中抽取固定数量的样本并进行数据增强,再输入模型进行训练。在训练模型时,系统会记录每轮结束时的损失函数值,当连续多轮损失函数值未下降,则视为训练完成并保存模型到本地固定位置。大致的模型训练过程如图所示:

         本系统采用的分类模型在原本Vgg-16网络模型的基础上,保持Vgg-16上层的卷积层和池化层不变,添加了两层dropout层和全连接层,模型结构如图5.4所示。在训练模型时,选用Adam优化算法加快模型收敛速度并一定程度防止过拟合。其中,Adam是一种自适应学习率的算法,不同于经典的随机梯度下降方法始终保持一个学习率用于所有的权重更新,Adam会利用梯度的一阶矩和二阶矩对不同的系数计算各自不同的学习率,达到加速收敛和防止过拟合的目的。

模型定义的代码如下:

conv_base = VGG16(weights='imagenet', include_top=False, input_shape=(self.IMAGE_SIZE,self.IMAGE_SIZE,3))
        conv_base.trainable = False
        flag = False
        for layer in conv_base.layers:
            if layer.name == 'block5_conv1':
                flag = True
            if flag:
                layer.trainable = True
        conv_base.summary()
        self.model = models.Sequential()
        self.model.add(conv_base)
        self.model.add(layers.Flatten())
        self.model.add(layers.Dense(1024, activation='relu'))
        self.model.add(layers.Dropout(0.5))
        self.model.add(layers.Dense(512, activation='relu'))
        self.model.add(layers.Dropout(0.5))
        self.model.add(layers.Dense(len(self.train_generator.class_indices), activation='softmax'))
        self.model.summary()

        为了进一步提升模型分类效果,本系统在分批抽取数据集输入模型进行训练时,会同时对输入的数据进行随机数据增强。本系统进行的数据增强包括尺寸变换(限制图像尺寸为144*144)、对比度变换(适应更多不同光照情况)、左右翻转变换、重缩放(设置为1/255,将像素值放缩到0和1之间更有利于模型的收敛)。

图像增强生成器代码如下:

def generator(data_path,IMAGE_SIZE,BATCH_SIZE):
    datagen = ImageDataGenerator(rescale=1.0 / 255,
                                 brightness_range=[0.6,1.8],
                                 validation_split=0.3,
                                 #rotation_range=10,
                                 zoom_range=0.1,
                                 shear_range=0.1,
                                 horizontal_flip=True,
                                 #width_shift_range=0.1,
                                 #height_shift_range=0.1,
                                 )
    train_generator = datagen.flow_from_directory(
        data_path, target_size=(IMAGE_SIZE, IMAGE_SIZE), color_mode="rgb",
        class_mode='categorical', batch_size=BATCH_SIZE, shuffle=True,subset='training')#,save_to_dir='./train'

    validation_generator = datagen.flow_from_directory(
        data_path, target_size=(IMAGE_SIZE, IMAGE_SIZE), color_mode='rgb',
        class_mode='categorical', batch_size=BATCH_SIZE, shuffle=True, subset='validation')
    print(train_generator.class_indices)
    return train_generator,validation_generator

        为了提高模型训练效率,尽量减少模型训练时长,在模型训练时添加了KERAS回调函数:EarlyStopping函数(监控每轮训练得到的损失值,若连续12轮损失值得不到下降就中断训练)和ReduceLROnPlateau函数(监控每轮训练得到的损失值,若连续4轮损失值得不到下降就降低学习率,跳出局部最小值)。

        模型训练代码如下,其中tensorboard部分解除注释即会生成tensorboard log,返回history方便绘制acc等曲线:

    def train(self):
        self.model.compile(loss='categorical_crossentropy',
                           optimizer='adam',
                           metrics=['accuracy'])
        callbacks_list=[
            keras.callbacks.EarlyStopping(
                monitor='accuracy',
                patience=10,
                verbose=1,
                mode='max'
            ),
            keras.callbacks.ReduceLROnPlateau(
                monitor='loss',
                factor=0.2,
                patience=5,
                verbose=1,
                mode='auto'
            )
            # keras.callbacks.TensorBoard(#tensorboard --logdir=./log
            #     log_dir='./log',
            #     histogram_freq=1,
            #     batch_size=self.BATCH_SIZE,
            #     write_graph=True,
            #     write_grads=False, write_images=True, embeddings_freq=0,
            #     embeddings_layer_names=None, embeddings_metadata=None, embeddings_data=None,
            #     update_freq='epoch')
        ]
        history=self.model.fit(self.train_generator,
                batch_size = self.BATCH_SIZE,
                epochs = self.EPOCHS,
                shuffle=True,
                verbose=1,
                validation_data=self.validation_generator,
                callbacks=callbacks_list
                )
        return history

        经过试验,在二十个类别,每类50张图片的人脸数据集上取得了相当不错的分类效果,在真实场景下的人脸签到也做到了迅速准确的人脸识别。

 调整后的VGG网络模型结构

 ACC and LOSS曲线


完整模型训练和测试代码如下,尽可能对函数进行了封装,方便调用和调参,感觉参数上还有优化空间

import random
import cv2
import keras
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten, Conv2D, BatchNormalization, Input, ZeroPadding2D, Add, \
    AveragePooling2D
from keras.layers import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.models import load_model
from keras import backend as K
import os
from keras.applications import VGG16
from keras import models
from keras.utils import plot_model
from matplotlib import pyplot as plt
from tensorflow.python.keras.applications.vgg16 import VGG16, layers
from tensorflow.python.keras.callbacks import ModelCheckpoint
from tensorflow.python.keras.models import Model

os.environ['CUDA_VISIBLE_DEVICES'] = "-1"
def plot_acc_loss_curve(history):
    # 显示训练集和验证集的acc和loss曲线
    #print(history.history.keys())
    acc = history.history['accuracy']
    val_acc=history.history['val_accuracy']
    loss = history.history['loss']
    val_loss=history.history['val_loss']

    plt.figure(figsize=(10, 10))
    plt.plot(acc, label='acc')
    plt.plot(loss, label='loss')
    plt.plot(val_acc, label='val_acc')
    plt.plot(val_loss, label='val_loss')
    plt.title('Training Acc And Loss')
    plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=4,
               ncol=4, mode="expand", borderaxespad=0.)
    plt.grid()
    plt.show()

def generator(data_path,IMAGE_SIZE,BATCH_SIZE):
    datagen = ImageDataGenerator(rescale=1.0 / 255,
                                 brightness_range=[0.6,1.8],
                                 validation_split=0.3,
                                 #rotation_range=10,
                                 zoom_range=0.1,
                                 shear_range=0.1,
                                 horizontal_flip=True,
                                 #width_shift_range=0.1,
                                 #height_shift_range=0.1,
                                 )
    train_generator = datagen.flow_from_directory(
        data_path, target_size=(IMAGE_SIZE, IMAGE_SIZE), color_mode="rgb",
        class_mode='categorical', batch_size=BATCH_SIZE, shuffle=True,subset='training')#,save_to_dir='./train'

    validation_generator = datagen.flow_from_directory(
        data_path, target_size=(IMAGE_SIZE, IMAGE_SIZE), color_mode='rgb',
        class_mode='categorical', batch_size=BATCH_SIZE, shuffle=True, subset='validation')
    print(train_generator.class_indices)
    return train_generator,validation_generator
class MyModel:
    def __init__(self,MODEL_PATH,DATA_PATH,IMAGE_SIZE,BATCH_SIZE,EPOCHS):
        self.model = None
        self.train_generator=None
        self.validation_generator=None
        self.labels=None
        self.MODEL_PATH=MODEL_PATH
        self.DATA_PATH=DATA_PATH
        self.IMAGE_SIZE=IMAGE_SIZE
        self.BATCH_SIZE=BATCH_SIZE
        self.EPOCHS=EPOCHS
        # 建立模型
    def build_model(self):
        # 构建一个空的网络模型,它是一个线性堆叠模型,各神经网络层会被顺序添加,专业名称为序贯模型或线性堆叠模型
        self.train_generator,self.validation_generator=generator(self.DATA_PATH,self.IMAGE_SIZE,self.BATCH_SIZE)

        conv_base = VGG16(weights='imagenet', include_top=False, input_shape=(self.IMAGE_SIZE,self.IMAGE_SIZE,3))
        conv_base.trainable = False
        flag = False
        for layer in conv_base.layers:
            if layer.name == 'block5_conv1':
                flag = True
            if flag:
                layer.trainable = True
        conv_base.summary()
        self.model = models.Sequential()
        self.model.add(conv_base)
        self.model.add(layers.Flatten())
        self.model.add(layers.Dense(1024, activation='relu'))
        self.model.add(layers.Dropout(0.5))
        self.model.add(layers.Dense(512, activation='relu'))
        self.model.add(layers.Dropout(0.5))
        self.model.add(layers.Dense(len(self.train_generator.class_indices), activation='softmax'))
        self.model.summary()
        plot_model(model=self.model, show_shapes=True, to_file='model.png', show_layer_names=True)
    #训练模型
    def train(self):
        sgd = SGD(lr = 0.0001, decay = 1e-6,
                  momentum = 0.8, nesterov = True)  # 采用SGD+momentum的优化器进行训练,首先生成一个优化器对象
        self.model.compile(loss='categorical_crossentropy',
                           optimizer='adam',
                           metrics=['accuracy'])
        callbacks_list=[
            keras.callbacks.EarlyStopping(
                monitor='accuracy',
                patience=10,
                verbose=1,
                mode='max'
            ),
            keras.callbacks.ReduceLROnPlateau(
                monitor='loss',
                factor=0.2,
                patience=5,
                verbose=1,
                mode='auto'
            )
            # keras.callbacks.TensorBoard(#tensorboard --logdir=./log
            #     log_dir='./log',
            #     histogram_freq=1,
            #     batch_size=self.BATCH_SIZE,
            #     write_graph=True,
            #     write_grads=False, write_images=True, embeddings_freq=0,
            #     embeddings_layer_names=None, embeddings_metadata=None, embeddings_data=None,
            #     update_freq='epoch')
        ]
        history=self.model.fit(self.train_generator,
                batch_size = self.BATCH_SIZE,
                epochs = self.EPOCHS,
                shuffle=True,
                verbose=1,
                validation_data=self.validation_generator,
                callbacks=callbacks_list
                )
        return history

    def save_model(self, file_path):
        file_path=self.MODEL_PATH+file_path
        self.model.save(file_path)
    def load_model(self, file_path):
        file_path = self.MODEL_PATH + file_path
        self.model = load_model(file_path)
    def evaluatee(self):
        x_test, y_test = next(self.train_generator)
        print(len(y_test))
        score = self.model.evaluate(x_test, y_test,
                                              verbose=1,
                                              )
        print("%s: %.2f%%" % (self.model.metrics_names[1], score[1] * 100))
    # 识别人脸
    def face_predict(self, image):
        # 依然是根据后端系统确定维度顺序  #first=th  last=tf
        if K.image_data_format() == 'channels_first' and image.shape != (1, 3, self.IMAGE_SIZE, self.IMAGE_SIZE):
            image = cv2.resize(image, (self.IMAGE_SIZE, self.IMAGE_SIZE))  # 尺寸必须与训练集一致都应该是IMAGE_SIZE x IMAGE_SIZE
            image = image.reshape((1, 3, self.IMAGE_SIZE, self.IMAGE_SIZE))  # 与模型训练不同,这次只是针对1张图片进行预测
        elif K.image_data_format() == 'channels_last' and image.shape != (1, self.IMAGE_SIZE, self.IMAGE_SIZE, 3):
            image = cv2.resize(image, (self.IMAGE_SIZE, self.IMAGE_SIZE))
            image = image.reshape((1, self.IMAGE_SIZE, self.IMAGE_SIZE, 3))
            # 浮点并归一化
        image = image.astype('float32')
        image /= 255
        sco = self.model.predict(image)
        result = np.argmax(sco,1)
        if sco[0][result[0]]>=0.9:
            print(result[0], 'result:', sco)
            return result[0]
        else:
            print(-1, 'result:', sco)
            return -1
if __name__ == '__main__':
    MODEL_PATH = 'E:\\PyCharm_workspace\\bishe\\model\\'
    DATA_PATH='E:\\PyCharm_workspace\\bishe\\TrainingData_2'
    IMAGE_SIZE = 144
    BATCH_SIZE = 16
    EPOCHS=500
    print('输入:')
    str=input()
    if str=='1':#训练并保存模型
        model = MyModel(MODEL_PATH,DATA_PATH,IMAGE_SIZE,BATCH_SIZE,EPOCHS)
        model.build_model()
        his=model.train()
        plot_acc_loss_curve(his)
        model.save_model('tr_data_2.face.model.h5')
    elif str=='2':#测试
        model = MyModel(MODEL_PATH,DATA_PATH,IMAGE_SIZE,BATCH_SIZE,EPOCHS)
        model.build_model()
        model.load_model(file_path = 'tr_data_2.face.model.h5')
        model.evaluatee()

同时针对完全一样的数据集,我也尝试了ResNet-50分类模型,但经过多次实际训练和测试发现,ResNet-50网络的训练结果在训练时间更长的情况下反而不如VGG-16网络模型的训练效果。可能写的不是很好就不拿出来了。

 

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙子树下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值