Vgg16识别4种简单人眼神态(TensorFlow)

发布于:2023-01-22 ⋅ 阅读:(291) ⋅ 点赞:(0)

项目数据及源码

可在github下载:

https://github.com/chenshunpeng/Recognition-of-eye-state-based-on-Vgg16

1.数据处理

1.1.数据介绍

一共4种眼部神态,分别是闭眼,朝前,朝左,朝右看,数据介绍如下图:

Eye_dataset
    ├── close_look
    │   └── eye_closed (XXX).XXX(1,148 张图片,共4.15 MB)
    │ 
    ├── forward_look
    │   └── forward_look (XXX).XXX(1,038 张图片,共7.37 MB)
    │ 
    ├── left_look
    │   └── left_(XXX).XXX(1,049 张图片,共11.0 MB)
    │         
    ├── right_look
    └── └──	right_(XXX).XXX(1,073 张图片,共9.69 MB)

1.2.导入数据

查看tensorflow版本:

import tensorflow as tf
tf.__version__

版本:'2.9.1'

科普一下 Keras 和 PyTorch 的优劣:(详细可看:传送门

在某些情况下,需要在特定的机器学习领域中寻找特定的模型。

例如,当进行目标检测比赛时,想要实现 DETR(Facebook 的 Data-Efficient transformer),结果发现大部分资源都是用 PyTorch 编写的,因此在这种情况下,使用 PyTorch 更加容易。

另外,PyTorch 的代码实现更长,因为它们涵盖了许多底层细节,这既是优点也是缺点。当你是初学者时先学习低层级的细节,然后再使用更高层级的 API(例如 Keras)非常有帮助。

但是,这同时也是一个缺点,因为你会发现自己迷失于许多细节和相当长的代码段中。因此,从本质上讲,如果你的工作期限很紧,最好选择 Keras 而不是 PyTorch。

设置GPU环境:

import tensorflow as tf

gpus = tf.config.list_physical_devices("GPU")
if gpus:
    tf.config.experimental.set_memory_growth(gpus[0], True)  #设置GPU显存用量按需使用
    tf.config.set_visible_devices([gpus[0]],"GPU")

# 打印显卡信息,确认GPU可用
print(gpus)

输出:[PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]

简单介绍我配置的环境:

我电脑支持的cuda(打开nvidia(桌面右键)->选择左下角的系统信息->组件)

在这里插入图片描述

nvcc -V查看下载的CUDA版本:

在这里插入图片描述

之后安装cuDNN,注册后到官网下载即可:

在这里插入图片描述

首先cd C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.4\extras\demo_suite,之后执行bandwidthTest.exe

在这里插入图片描述

执行deviceQuery.exe

在这里插入图片描述

出现上图这2个PASS,即为cuDNN安装成功

导入并查看数据:

对于随机数种子的使用,可看:Tensorflow应用–tf.set_random_seed 的用法

import matplotlib.pyplot as plt
# 支持中文
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

import os,PIL

# 设置随机种子尽可能使结果可以重现
import numpy as np
np.random.seed(1)

# 设置随机种子尽可能使结果可以重现
import tensorflow as tf
# tf.random.set_seed:设置全局随机种子
tf.random.set_seed(1)

import pathlib

data_dir = "E:\demo_study\jupyter\Jupyter_notebook\Recognition-of-eye-state-based-on-CNN\Eye_dataset"
data_dir = pathlib.Path(data_dir)

# */*的意思为获取文件夹下的所有文件及它们的子文件
# https://blog.csdn.net/Crystal_remember/article/details/116804009
image_count = len(list(data_dir.glob('*/*')))
print("图片总数为:",image_count)

输出:图片总数为: 4308

1.3.数据预处理

初始化参数:

batch_size = 8
img_height = 224
img_width = 224

使用image_dataset_from_directory方法将磁盘中的数据加载到tf.data.Dataset

函数原型:

tf.keras.preprocessing.image_dataset_from_directory(
    directory,
    labels="inferred",
    label_mode="int",
    class_names=None,
    color_mode="rgb",
    batch_size=32,
    image_size=(256, 256),
    shuffle=True,
    seed=None,
    validation_split=None,
    subset=None,
    interpolation="bilinear",
    follow_links=False,
)

官网介绍:tf.keras.utils.image_dataset_from_directory

对于validation_split项,官网解释:Optional float between 0 and 1, fraction of data to reserve for validation.

对于subset项,官网解释:Subset of the data to return. One of “training” or “validation”. Only used if validation_split is set.

这部分代码:

train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir, #WindowsPath('E:/demo_study/jupyter/Jupyter_notebook/Recognition-of-eye-state-based-on-CNN/Eye_dataset')
    validation_split=0.2,
    subset="training",
    seed=12,
    image_size=(img_height, img_width),
    batch_size=batch_size)

输出:

Found 4308 files belonging to 4 classes.
Using 3447 files for training.

同理配置验证集:

val_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="validation",
    seed=12,
    image_size=(img_height, img_width),
    batch_size=batch_size)

输出:

Found 4308 files belonging to 4 classes.
Using 861 files for validation.

我们可以通过class_names输出数据集的标签,标签将按字母顺序对应于目录名称

class_names = train_ds.class_names
print(class_names)
class_names = val_ds.class_names
print(class_names)

输出:

['close_look', 'forward_look', 'left_look', 'right_look']
['close_look', 'forward_look', 'left_look', 'right_look']

1.4.可视化数据

plt.figure(figsize=(10, 5))  # 图形的宽为10高为5
plt.suptitle("数据展示")

for images, labels in train_ds.take(1):
    for i in range(8):
        ax = plt.subplot(2, 4, i + 1)  
        plt.imshow(images[i].numpy().astype("uint8"))
        plt.title(class_names[labels[i]])
        plt.savefig('pic1.jpg', dpi=600) #指定分辨率保存
        plt.axis("off")

结果:

请添加图片描述
查看图像和标签各自的数据格式:

for image_batch, labels_batch in train_ds:
    print(image_batch.shape)
    print(labels_batch.shape)
    break

输出:

(8, 224, 224, 3)
(8,)

解释:

  • Image_batch是形状(8, 224, 224, 3)的张量,即一批形状是240x240x3的8张图片(最后一维指的是彩色通道RGB)
  • Label_batch是形状(8,)的张量,即对应8张图片的标签

1.5.配置数据集

shuffle() : 打乱数据,详细可参考:数据集shuffle方法中buffer_size的理解

prefetch() :预取数据,加速运行,详细可参考:Better performance with the tf.data API

cache() :将数据集缓存到内存当中,加速运行

推荐一篇博客:【学习笔记】使用tf.data对预处理过程优化

prefetch()功能详细介绍:它使得训练步骤的预处理和模型执行部分重叠起来,原来是:

在这里插入图片描述

prefetch()之后是:

在这里插入图片描述
当然,不这么处理也可以的

AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds   = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

2.网络设计

2.1.vgg16简单介绍

具体可看论文:Very Deep Convolutional Networks for Large-Scale Image Recognition

VGG-16 架构如下:

在这里插入图片描述
具体过程:(借鉴自:深度学习——VGG16模型详解

  1. 输入图像尺寸为224x224x3,经64个通道为3的3x3的卷积核,步长为1,padding=same填充,卷积两次,再经ReLU激活,输出的尺寸大小为224x224x64
  2. 经max pooling(最大化池化),滤波器为2x2,步长为2,图像尺寸减半,池化后的尺寸变为112x112x64
  3. 经128个3x3的卷积核,两次卷积,ReLU激活,尺寸变为112x112x128
  4. max pooling池化,尺寸变为56x56x128
  5. 经256个3x3的卷积核,三次卷积,ReLU激活,尺寸变为56x56x256
  6. max pooling池化,尺寸变为28x28x256
  7. 经512个3x3的卷积核,三次卷积,ReLU激活,尺寸变为28x28x512
  8. max pooling池化,尺寸变为14x14x512
  9. 经512个3x3的卷积核,三次卷积,ReLU,尺寸变为14x14x512
  10. max pooling池化,尺寸变为7x7x512
  11. 然后Flatten(),将数据拉平成向量,变成一维51277=25088。
  12. 再经过两层1x1x4096,一层1x1x1000的全连接层(共三层),经ReLU激活
  13. 最后通过softmax输出1000个预测结果

从上面的过程可以看出VGG网络结构还是挺简洁的,都是由小卷积核、小池化核、ReLU组合而成,简化图如下(也是本文所用的网络结构):

在这里插入图片描述

2.2.进行训练

调用官方vgg16网络模型

model = tf.keras.applications.VGG16()
# 打印模型信息
model.summary()

即下图的D模型:

在这里插入图片描述

输出:

Model: "vgg16"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_1 (InputLayer)        [(None, 224, 224, 3)]     0         
                                                                 
 block1_conv1 (Conv2D)       (None, 224, 224, 64)      1792      
                                                                 
 block1_conv2 (Conv2D)       (None, 224, 224, 64)      36928     
                                                                 
 block1_pool (MaxPooling2D)  (None, 112, 112, 64)      0         
                                                                 
 block2_conv1 (Conv2D)       (None, 112, 112, 128)     73856     
                                                                 
 block2_conv2 (Conv2D)       (None, 112, 112, 128)     147584    
                                                                 
 block2_pool (MaxPooling2D)  (None, 56, 56, 128)       0         
                                                                 
 block3_conv1 (Conv2D)       (None, 56, 56, 256)       295168    
                                                                 
 block3_conv2 (Conv2D)       (None, 56, 56, 256)       590080    
                                                                 
 block3_conv3 (Conv2D)       (None, 56, 56, 256)       590080    
                                                                 
 block3_pool (MaxPooling2D)  (None, 28, 28, 256)       0         
                                                                 
 block4_conv1 (Conv2D)       (None, 28, 28, 512)       1180160   
                                                                 
 block4_conv2 (Conv2D)       (None, 28, 28, 512)       2359808   
                                                                 
 block4_conv3 (Conv2D)       (None, 28, 28, 512)       2359808   
                                                                 
 block4_pool (MaxPooling2D)  (None, 14, 14, 512)       0         
                                                                 
 block5_conv1 (Conv2D)       (None, 14, 14, 512)       2359808   
                                                                 
 block5_conv2 (Conv2D)       (None, 14, 14, 512)       2359808   
                                                                 
 block5_conv3 (Conv2D)       (None, 14, 14, 512)       2359808   
                                                                 
 block5_pool (MaxPooling2D)  (None, 7, 7, 512)         0         
                                                                 
 flatten (Flatten)           (None, 25088)             0         
                                                                 
 fc1 (Dense)                 (None, 4096)              102764544 
                                                                 
 fc2 (Dense)                 (None, 4096)              16781312  
                                                                 
 predictions (Dense)         (None, 1000)              4097000   
                                                                 
=================================================================
Total params: 138,357,544
Trainable params: 138,357,544
Non-trainable params: 0
_________________________________________________________________

设置动态学习率

# 设置初始学习率
initial_learning_rate = 1e-4

lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
        initial_learning_rate, 
        decay_steps=20,      # 注意这里是指 steps,不是指 epochs
        decay_rate=0.96,     # lr经过一次衰减就会变成 decay_rate*lr
        staircase=True)

# 将指数衰减学习率送入优化器
optimizer = tf.keras.optimizers.Adam(learning_rate=lr_schedule)

模型的编译

  • 损失函数(loss):用于衡量模型在训练期间的准确率,这里用sparse_categorical_crossentropy,原理与categorical_crossentropy(多类交叉熵损失 )一样,不过真实值采用的整数编码(例如第0个类用数字0表示,第3个类用数字3表示,官方可看:tf.keras.losses.SparseCategoricalCrossentropy
  • 优化器(optimizer):决定模型如何根据其看到的数据和自身的损失函数进行更新,这里是Adam(官方可看:tf.keras.optimizers.Adam
  • 评价函数(metrics):用于监控训练和测试步骤,本次使用accuracy,即被正确分类的图像的比率(官方可看:tf.keras.metrics.Accuracy
model.compile(optimizer=optimizer,
              loss     ='sparse_categorical_crossentropy',
              metrics  =['accuracy'])

训练模型

epochs = 10
history = model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=epochs
)

这里 s t e p step step i t e r a t i o n iteration iteration)的个数为: s t e p = ⌈ e x a m p l e N u m s ∗ e p o c h ​ b a t c h s i z e ⌉ = ⌈ 3447 ∗ 1 8 ⌉ = 431 step=\lceil \dfrac{exampleNums∗epoch ​ }{batch size}\rceil=\lceil \dfrac{3447∗1}{8}\rceil=431 step=batchsizeexampleNumsepoch=834471=431

输出:

Epoch 1/10
431/431 [==============================] - 266s 604ms/step - loss: 0.4236 - accuracy: 0.8567 - val_loss: 0.1470 - val_accuracy: 0.9501
Epoch 2/10
431/431 [==============================] - 254s 589ms/step - loss: 0.0895 - accuracy: 0.9698 - val_loss: 0.0959 - val_accuracy: 0.9721
Epoch 3/10
431/431 [==============================] - 254s 589ms/step - loss: 0.0406 - accuracy: 0.9881 - val_loss: 0.0923 - val_accuracy: 0.9733
Epoch 4/10
431/431 [==============================] - 253s 587ms/step - loss: 0.0215 - accuracy: 0.9942 - val_loss: 0.1004 - val_accuracy: 0.9733
Epoch 5/10
431/431 [==============================] - 253s 587ms/step - loss: 0.0161 - accuracy: 0.9951 - val_loss: 0.0996 - val_accuracy: 0.9791
Epoch 6/10
431/431 [==============================] - 253s 587ms/step - loss: 0.0133 - accuracy: 0.9962 - val_loss: 0.1016 - val_accuracy: 0.9779
Epoch 7/10
431/431 [==============================] - 253s 587ms/step - loss: 0.0116 - accuracy: 0.9965 - val_loss: 0.1027 - val_accuracy: 0.9779
Epoch 8/10
431/431 [==============================] - 253s 587ms/step - loss: 0.0109 - accuracy: 0.9971 - val_loss: 0.1033 - val_accuracy: 0.9779
Epoch 9/10
431/431 [==============================] - 253s 587ms/step - loss: 0.0106 - accuracy: 0.9971 - val_loss: 0.1036 - val_accuracy: 0.9779
Epoch 10/10
431/431 [==============================] - 253s 587ms/step - loss: 0.0104 - accuracy: 0.9971 - val_loss: 0.1036 - val_accuracy: 0.9779

3.模型评估

3.1.准确率评估

Accuracy与Loss图

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.savefig('pic2.jpg', dpi=600) #指定分辨率保存
plt.show()

请添加图片描述

3.2.绘制混淆矩阵

confusion_matrix()介绍可看:sklearn.metrics.confusion_matrix

Seaborn:基于 Matplotlib 核心库进行了更高阶的 API 封装,其优势在配色更加舒服、以及图形元素的样式更加细腻

定义一个绘制混淆矩阵图的函数plot_cm

from sklearn.metrics import confusion_matrix
import seaborn as sns
import pandas as pd

def plot_cm(labels, predictions):
    
    # 生成混淆矩阵
    conf_numpy = confusion_matrix(labels, predictions)
    # 将矩阵转化为 DataFrame
    conf_df = pd.DataFrame(conf_numpy, index=class_names ,columns=class_names)  
    plt.figure(figsize=(8,7))
    sns.heatmap(conf_df, annot=True, fmt="d", cmap="BuPu")
    plt.title('混淆矩阵',fontsize=15)
    plt.ylabel('真实值',fontsize=14)
    plt.xlabel('预测值',fontsize=14)
    plt.savefig('pic3.jpg', dpi=600) #指定分辨率保存

取部分验证数据(.take(1))生成混淆矩阵:

val_pre   = []
val_label = []

for images, labels in val_ds:#这里可以取部分验证数据(.take(1))生成混淆矩阵
    for image, label in zip(images, labels):
        # 需要给图片增加一个维度
        img_array = tf.expand_dims(image, 0) 
        # 使用模型预测图片中的人物
        prediction = model.predict(img_array)

        val_pre.append(class_names[np.argmax(prediction)])
        val_label.append(class_names[label])

输出(图像处理):

1/1 [==============================] - 2s 2s/step
1/1 [==============================] - 0s 23ms/step
1/1 [==============================] - 0s 21ms/step
...

查看一下conf_numpy变量:

conf_numpy = confusion_matrix(val_label, val_pre)
conf_numpy

输出:

array([[223,   0,   0,   0],
       [  0, 209,   2,   1],
       [  0,   0, 209,   3],
       [  1,   9,   3, 201]], dtype=int64)

之后调用plot_cm()进行绘图:

plot_cm(val_label, val_pre)

请添加图片描述

记得保存模型

# 保存模型
model.save('model/17_model.h5')

3.3.进行预测

# model = tf.keras.models.load_model('model/17_model.h5')
plt.figure(figsize=(20, 10))  # 图形的宽为10高为5
plt.suptitle("预测结果展示")

num = -1
for images, labels in val_ds.take(2):
    for i in range(8):
        num = num + 1
        plt.subplots_adjust(left=None, bottom=None, right=None, top=None , wspace=0.2, hspace=0.2)
        if num >= 15:
            break
        ax = plt.subplot(3, 5, num + 1)  
        
        # 显示图片
        plt.imshow(images[i].numpy().astype("uint8"))
        
        # 需要给图片增加一个维度
        img_array = tf.expand_dims(images[i], 0) 
        
        # 使用模型预测图片中的人物
        predictions = model.predict(img_array)
        plt.title("True value: {}\npredictive value: {}".format(class_names[labels[i]],class_names[np.argmax(predictions)]))
        plt.savefig('pic4.jpg', dpi=600) #指定分辨率保存
        plt.axis("off")

结果(红色框内为预测错误的情况):

在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看