Tensorflow2.0 DenseNet实现图片分类(猫狗大战)

1.DenseNet网络结构

论文题目:《Densely Connected Convolutional Networks》
论文地址:https://arxiv.org/abs/1608.06993

DenseNet是CVPR2017的Best Paper,网络的基本思路与ResNet差不多,但是它的不同之处是将前面所有层与后面层的密集连接,目的是实现特征重用。这一特点使DenseNet在参数和计算成本更少的情形下实现比ResNet更优的性能。Densenet由DenseBlock和间隔模块Transition Layer组成:
1.DenseBlock:将前面所有层与后面层的进行密集连接,在同一个DenseBlock当中,通道数会发生改变,但是特征层的高宽不会发生改变,如下图所示:
Tensorflow2.0 DenseNet实现图片分类(猫狗大战)_第1张图片
2.Transition Layer: 作用是连接两个相邻的DenseBlock,并且通过Pooling使特征图大小降低,一般会使用一个步长为2的AveragePooling2D缩小特征层的宽高

DenseNet网络整体结构:
Tensorflow2.0 DenseNet实现图片分类(猫狗大战)_第2张图片
DenseNet一种有四种结构,分别为:DenseNet121,DenseNet169,DenseNet201,DenseNet264,这次实验我们选用DenseNet121网络结构进行图片分类。

tensorflow.keras.applications模块内置了许多模型,包括Xception、MobileNet、VGG等,我们可以使用内置的DenseNet121模型,只需修改最后的全连接层即可。

2.数据集

采用kaggle上的猫狗数据集,总共10000张图片,猫和狗分别有5000张,取4000张图片作为训练集,1000张图片作为验证集

kaggle数据集地址:https://www.kaggle.com/chetankv/dogs-cats-images

百度网盘地址:
链接:https://pan.baidu.com/s/1INeCl1LHeqT4QIiTMToy1g
提取码:ux1j

Tensorflow2.0 DenseNet实现图片分类(猫狗大战)_第3张图片
图片样本

Tensorflow2.0 DenseNet实现图片分类(猫狗大战)_第4张图片
可以看到,每张图片的大小都不同,当我们进行训练的时候需要将所有图片resize到相同的大小(一般为224x224),然后输入到网络进行训练

3.代码

3.1 数据读取

#导入相应的库
from tensorflow.keras.callbacks import ReduceLROnPlateau,ModelCheckpoint
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import tensorflow as tf
from PIL import Image
import numpy as np  
import itertools
import json
import os

#设置图片的高和宽,一次训练所选取的样本数,迭代次数
im_height = 224
im_width = 224
batch_size = 256
epochs = 10

# 创建保存模型的文件夹
if not os.path.exists("save_weights"):
    os.makedirs("save_weights")


image_path = "../input/dogs-cats-images/dog vs cat/dataset/" # 猫狗数据集路径
train_dir = image_path + "training_set" #训练集路径
validation_dir = image_path + "test_set" #验证集路径

# 定义训练集图像生成器,并进行图像增强
train_image_generator = ImageDataGenerator( rescale=1./255, # 归一化
                                            rotation_range=40, #旋转范围
                                            width_shift_range=0.2, #水平平移范围
                                            height_shift_range=0.2, #垂直平移范围
                                           shear_range=0.2, #剪切变换的程度
                                            zoom_range=0.2, #剪切变换的程度
                                            horizontal_flip=True,  #水平翻转
                                            fill_mode='nearest')
                                            
# 使用图像生成器从文件夹train_dir中读取样本,对标签进行one-hot编码
train_data_gen = train_image_generator.flow_from_directory(directory=train_dir, #从训练集路径读取图片
                                                           batch_size=batch_size, #一次训练所选取的样本数
                                                           shuffle=True, #打乱标签
                                                           target_size=(im_height, im_width), #图片resize到224x224大小
                                                           class_mode='categorical') #one-hot编码
                                                           
# 训练集样本数        
total_train = train_data_gen.n 

# 定义验证集图像生成器,并对图像进行预处理
validation_image_generator = ImageDataGenerator(rescale=1./255) # 归一化

# 使用图像生成器从验证集validation_dir中读取样本
val_data_gen = validation_image_generator.flow_from_directory(directory=validation_dir,#从验证集路径读取图片
                                                              batch_size=batch_size, #一次训练所选取的样本数
                                                              shuffle=False,  #不打乱标签
                                                              target_size=(im_height, im_width), #图片resize到224x224大小
                                                              class_mode='categorical') #one-hot编码
                                                              
# 验证集样本数      
total_val = val_data_gen.n

执行结果:

Found 8000 images belonging to 2 classes.
Found 2000 images belonging to 2 classes.

训练集一共有8000张图片,验证集一共有2000张图片,总共2个类别

3.2 构建模型

#使用tf.keras.applications中的DenseNet121网络,并且使用官方的预训练模型
covn_base = tf.keras.applications.DenseNet121(weights='imagenet',include_top=False,input_shape=(224,224,3))
covn_base.trainable = True

#冻结前面的层,训练最后5层
for layers in covn_base.layers[:-5]:
    layers.trainable = False
    
#构建模型    
model = tf.keras.Sequential()
model.add(covn_base)
model.add(tf.keras.layers.GlobalAveragePooling2D())  #加入全局平均池化层
model.add(tf.keras.layers.Dense(512,activation='relu'))  #添加全连接层
model.add(tf.keras.layers.Dropout(rate=0.5))  #添加Dropout层,防止过拟合
model.add(tf.keras.layers.Dense(2,activation='softmax'))  #添加输出层(2分类)
model.summary()   #打印每层参数信息 

#编译模型
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001), #使用adam优化器,学习率为0.0001
              loss=tf.keras.losses.CategoricalCrossentropy(from_logits=False), #交叉熵损失函数
              metrics=["accuracy"]) #评价函数

执行结果:

Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/densenet/densenet121_weights_tf_dim_ordering_tf_kernels_notop.h5
29089792/29084464 [==============================] - 1s 0us/step
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
densenet121 (Model)          (None, 7, 7, 1024)        7037504   
_________________________________________________________________
global_average_pooling2d (Gl (None, 1024)              0         
_________________________________________________________________
dense (Dense)                (None, 512)               524800    
_________________________________________________________________
dropout (Dropout)            (None, 512)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 2)                 1026      
=================================================================
Total params: 7,563,330
Trainable params: 564,738
Non-trainable params: 6,998,592
_________________________________________________________________

由于大部分层被冻结了,所以可训练的参数很少,只有564,738个参数

3.3 训练模型

#回调函数1:学习率衰减
reduce_lr = ReduceLROnPlateau(
                                monitor='val_loss', #需要监视的值
                                factor=0.1,  #学习率衰减为原来的1/10
                                patience=2,  #当patience个epoch过去而模型性能不提升时,学习率减少的动作会被触发
                                mode='auto', #当监测值为val_acc时,模式应为max,当监测值为val_loss时,模式应为min,在auto模式下,评价准则由被监测值的名字自动推断
                                verbose=1 #如果为True,则为每次更新输出一条消息,默认值:False
                             )
#回调函数2:保存最优模型
checkpoint = ModelCheckpoint(
                                filepath='./save_weights/myDenseNet121.ckpt', #保存模型的路径
                                monitor='val_acc', #需要监视的值
                                save_weights_only=False, #若设置为True,则只保存模型权重,否则将保存整个模型(包括模型结构,配置信息等)
                                save_best_only=True, #当设置为True时,监测值有改进时才会保存当前的模型
                                mode='auto', #当监测值为val_acc时,模式应为max,当监测值为val_loss时,模式应为min,在auto模式下,评价准则由被监测值的名字自动推断
                                period=1 #CheckPoint之间的间隔的epoch数
                            )
#开始训练
history = model.fit(x=train_data_gen,   #输入训练集
                    steps_per_epoch=total_train // batch_size, #一个epoch包含的训练步数
                    epochs=epochs, #训练模型迭代次数
                    validation_data=val_data_gen,  #输入验证集
                    validation_steps=total_val // batch_size, #一个epoch包含的训练步数
                    callbacks=[checkpoint, reduce_lr]) #执行回调函数
                    
#保存训练好的模型权重                    
model.save_weights('./save_weights/myNASNetMobile.ckpt',save_format='tf') 

# 记录训练集和验证集的准确率和损失值
history_dict = history.history
train_loss = history_dict["loss"] #训练集损失值
train_accuracy = history_dict["accuracy"] #训练集准确率
val_loss = history_dict["val_loss"] #验证集损失值
val_accuracy = history_dict["val_accuracy"] #验证集准确率

执行结果:

Epoch 1/10
31/31 [==============================] - 809s 26s/step - loss: 0.5136 - accuracy: 0.7619 - val_loss: 0.0909 - val_accuracy: 0.9732 - lr: 1.0000e-04
Epoch 2/10
31/31 [==============================] - 837s 27s/step - loss: 0.1873 - accuracy: 0.9246 - val_loss: 0.0579 - val_accuracy: 0.9799 - lr: 1.0000e-04
Epoch 3/10
31/31 [==============================] - 819s 26s/step - loss: 0.1431 - accuracy: 0.9440 - val_loss: 0.0473 - val_accuracy: 0.9833 - lr: 1.0000e-04
Epoch 4/10
31/31 [==============================] - 820s 26s/step - loss: 0.1229 - accuracy: 0.9494 - val_loss: 0.0471 - val_accuracy: 0.9833 - lr: 1.0000e-04
Epoch 5/10
31/31 [==============================] - 825s 27s/step - loss: 0.1168 - accuracy: 0.9498 - val_loss: 0.0412 - val_accuracy: 0.9855 - lr: 1.0000e-04
Epoch 6/10
31/31 [==============================] - 833s 27s/step - loss: 0.1046 - accuracy: 0.9568 - val_loss: 0.0390 - val_accuracy: 0.9872 - lr: 1.0000e-04
Epoch 7/10
31/31 [==============================] - 831s 27s/step - loss: 0.1020 - accuracy: 0.9591 - val_loss: 0.0433 - val_accuracy: 0.9860 - lr: 1.0000e-04
Epoch 8/10
31/31 [==============================] - 818s 26s/step - loss: 0.0950 - accuracy: 0.9615 - val_loss: 0.0370 - val_accuracy: 0.9877 - lr: 1.0000e-04
Epoch 9/10
31/31 [==============================] - 819s 26s/step - loss: 0.0909 - accuracy: 0.9619 - val_loss: 0.0369 - val_accuracy: 0.9877 - lr: 1.0000e-04
Epoch 10/10
31/31 [==============================] - ETA: 0s - loss: 0.0895 - accuracy: 0.9635 
Epoch 00010: ReduceLROnPlateau reducing learning rate to 9.999999747378752e-06.
31/31 [==============================] - 840s 27s/step - loss: 0.0895 - accuracy: 0.9635 - val_loss: 0.0377 - val_accuracy: 0.9888 - lr: 1.0000e-04

从结果可以看出,使用迁移学习的时候模型收敛的速度很快,当训练第10个epoch时训练集准确率为96.35%,验证集的准确率为98.88%

3.4 评估模型

3.4.1 绘制损失值曲线

plt.figure()
plt.plot(range(epochs), train_loss, label='train_loss')
plt.plot(range(epochs), val_loss, label='val_loss')
plt.legend()
plt.xlabel('epochs')
plt.ylabel('loss')

执行结果:
Tensorflow2.0 DenseNet实现图片分类(猫狗大战)_第5张图片
记录每个epoch的训练集和验证集损失值并绘制出来进行对比

3.4.2 绘制准确率曲线

plt.figure()
plt.plot(range(epochs), train_accuracy, label='train_accuracy')
plt.plot(range(epochs), val_accuracy, label='val_accuracy')
plt.legend()
plt.xlabel('epochs')
plt.ylabel('accuracy')
plt.show()

执行结果:
Tensorflow2.0 DenseNet实现图片分类(猫狗大战)_第6张图片
记录每个epoch的训练集和验证集准确率并绘制出来进行对比

3.4.3 绘制混淆矩阵

def plot_confusion_matrix(cm, target_names,title='Confusion matrix',cmap=None,normalize=False):
    accuracy = np.trace(cm) / float(np.sum(cm)) #计算准确率
    misclass = 1 - accuracy #计算错误率
    if cmap is None:
        cmap = plt.get_cmap('Blues') #颜色设置成蓝色
    plt.figure(figsize=(10, 8)) #设置窗口尺寸
    plt.imshow(cm, interpolation='nearest', cmap=cmap) #显示图片
    plt.title(title) #显示标题
    plt.colorbar() #绘制颜色条

    if target_names is not None:
        tick_marks = np.arange(len(target_names))
        plt.xticks(tick_marks, target_names, rotation=45) #x坐标标签旋转45度
        plt.yticks(tick_marks, target_names) #y坐标

    if normalize:
        cm = cm.astype('float32') / cm.sum(axis=1)
        cm = np.round(cm,2) #对数字保留两位小数
        

    thresh = cm.max() / 1.5 if normalize else cm.max() / 2
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): #将cm.shape[0]、cm.shape[1]中的元素组成元组,遍历元组中每一个数字
        if normalize: #标准化
            plt.text(j, i, "{:0.2f}".format(cm[i, j]), #保留两位小数
                     horizontalalignment="center",  #数字在方框中间
                     color="white" if cm[i, j] > thresh else "black")  #设置字体颜色
        else:  #非标准化
            plt.text(j, i, "{:,}".format(cm[i, j]),
                     horizontalalignment="center",  #数字在方框中间
                     color="white" if cm[i, j] > thresh else "black") #设置字体颜色

    plt.tight_layout() #自动调整子图参数,使之填充整个图像区域
    plt.ylabel('True label') #y方向上的标签
    plt.xlabel("Predicted label\naccuracy={:0.4f}\n misclass={:0.4f}".format(accuracy, misclass)) #x方向上的标签
    plt.show() #显示图片

#猫和狗两种标签,存入到labels中
labels = ['cats','dogs']

# 预测验证集数据整体准确率
Y_pred = model.predict_generator(val_data_gen, total_val // batch_size + 1)
# 将预测的结果转化为one hit向量
Y_pred_classes = np.argmax(Y_pred, axis = 1)
# 计算混淆矩阵
confusion_mtx = confusion_matrix(y_true = val_data_gen.classes,y_pred = Y_pred_classes)
# 绘制混淆矩阵
plot_confusion_matrix(confusion_mtx, normalize=True, target_names=labels)

执行结果:
Tensorflow2.0 DenseNet实现图片分类(猫狗大战)_第7张图片
可以看出,验证集中猫和狗预测的准确率分别为98%和99%,整体预测的准确率为98.95%,说明效果还是不错的

3.5 测试模型

从网上任意下载一张猫或狗的图片,将其输入模型中,查看预测的结果

#获取数据集的类别编码
class_indices = train_data_gen.class_indices 
#将编码和对应的类别存入字典
inverse_dict = dict((val, key) for key, val in class_indices.items()) 
#加载测试图片
img = Image.open("../input/cat-and-dog-myself/1.jpg")
# 将图片resize到224x224大小
img = img.resize((im_width, im_height))
# 归一化
img1 = np.array(img) / 255.
# 将图片增加一个维度,目的是匹配网络模型
img1 = (np.expand_dims(img1, 0))
#将预测结果转化为概率值
result = np.squeeze(model.predict(img1))
predict_class = np.argmax(result)
#print(inverse_dict[int(predict_class)],result[predict_class])
#将预测的结果打印在图片上面
plt.title([inverse_dict[int(predict_class)],result[predict_class]])
#显示图片
plt.imshow(img)

执行结果:
Tensorflow2.0 DenseNet实现图片分类(猫狗大战)_第8张图片
经过预测,这张图片有99.14%的概率是猫,结果正确,大功告成!

你可能感兴趣的:(python,机器学习,深度学习,计算机视觉,卷积神经网络)