课程内容来源于日月光华老师的《Tensorflow2.0深度学习入门与实战》,本文仅仅作为学习笔记。猫狗数据集是深度学习入门经常用到的一个数据集。猫狗数据集原始数据集有上万张,这里在训练的过程中,仅仅使用了训练集猫和狗分别1500张,训练集分别500张。在文末提供了两个版本的数据集,即完整版以及学习测试版本。

数据集的读取

使用python中的glob库来获取原始训练资料。关于glob的使用,在『新手入门CNN综合实例_卷积神经网络实战_卫星图像识别』一分钟就进行过介绍。这里再次复习一下:

glob模块常用来查找文件目录和文件,并将搜索的到的结果返回到一个列表中,常见的两个方法有glob.glob()和glob.iglob(),可以和常用的find功能进行类比,glob支持*?[]这三种通配符。glob.glob()可同时获取所有的匹配路径,而glob.iglob()一次只能获取一个匹配路径。

  • *代表0个或多个字符
  • ?代表一个字符
  • []匹配指定范围内的字符,如[0-9]匹配数字

数据集的读取主要是获取图片的路径以及对象的标签。

import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import glob
import os

# 使用glob方法获取train下的所有jpg图片,具体的路径需要根据自己的实际情况修改
train_image_path = glob.glob('./dc_2000/train/*/*.jpg')

# 获取标签,并转换为数字表示,例如dog是0,cat是1
train_image_label = [int(p.split('\\')[1]=='cat')  for p in train_image_path]

读取数据集

创建dataset

# 定义一个读取图像的函数
def load_preprosess_image(path,label):
    image = tf.io.read_file(path)
    image = tf.image.decode_jpeg(image,channels=3)    # 解码图片,并且设置channels=3,RGB彩色图片
    image = tf.image.resize(image,[256,256])          # 对图片进行扭曲,由于扭曲图片不会影响对猫狗的判断,所以本次可以,但不通用
    image = tf.cast(image,tf.float32)   
    image = image/255                                 # 先转换成float类型,再进行归一化处理
    label = tf.reshape(label,[1])                     # [0,0,1]——>[[0],[0],[1]]
    return image,label

train_image_ds = tf.data.Dataset.from_tensor_slices((train_image_path,train_image_label))

# 根据CPU性能进行自动分配并行运算
# AUTOTUNE就是让计算机自己做决定
AUTOTUNE = tf.data.experimental.AUTOTUNE  
train_image_ds = train_image_ds.map(load_preprosess_image,num_parallel_calls=AUTOTUNE)

BATCH_SIZE = 32
train_count = len(train_image_path)

train_image_ds = train_image_ds.shuffle(train_count).batch(BATCH_SIZE)
train_image_ds = train_image_ds.prefetch(AUTOTUNE)  #训练的时候预读取一部分数据,这样可以加快速度

可以通过取出来一个batch测试一下,也就是32张256*256并且是3通道的图片。

测试batch里的值

创建网络模型

model = keras.Sequential([
    tf.keras.layers.Conv2D(64,(3,3),input_shape=(256,256,3),activation='relu'),
    tf.keras.layers.MaxPool2D(),
    tf.keras.layers.Conv2D(128,(3,3),activation='relu'),
    tf.keras.layers.MaxPool2D(),
    tf.keras.layers.Conv2D(256,(3,3),activation='relu'),    
    tf.keras.layers.MaxPool2D(),
    tf.keras.layers.Conv2D(512,(3,3),activation='relu'),
    tf.keras.layers.MaxPool2D(),
    tf.keras.layers.Conv2D(1024,(3,3),activation='relu'),
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(256,activation='relu'),
    tf.keras.layers.Dense(1)
])

最后的1个Dense层tf.keras.layers.Dense(1)并没有设置激活函数sigmoid。由于这是一个猫狗数据集两种分类,也就是一个二分类问题。可以直接通过判断最后的值是大于0或者小于0就可以判断出来分类,这样就不需要进行映射。

sigmoid函数

在进行损失函数计算的时候,设置参数from_logits=True即可。

自定义训练过程

# 设置优化器
optimizer = tf.keras.optimizers.Adam()

epoch_loss_avg = tf.keras.metrics.Mean('train_loss')    # 训练的loss值
train_accurancy = tf.keras.metrics.Accuracy()           # 训练的准确率

def train_step(model,images,labels):
    with tf.GradientTape() as t:
        pred = model(images)
        loss_step = tf.keras.losses.BinaryCrossentropy(from_logits=True)(labels,pred)
        
    #  计算loss_step与model.trainable_variables之间的梯度
    grads = t.gradient(loss_step,model.trainable_variables)   
    
    optimizer.apply_gradients(zip(grads,model.trainable_variables))      #  对模型的参数进行优化
    epoch_loss_avg(loss_step)                                            #  计算loss值
    train_accurancy(labels,tf.cast(pred>0,tf.int32))                     #  计算准确率
    # 准确率计算需要转化为tf.int32格式,因为原始label就是这个格式

# 记录一些标量的值
train_loss_results = []
train_acc_results = []

num_epoch = 30

def train():
    # 设置进行训练
    for epoch in range(num_epoch):
        for imgs_,labels_ in train_image_ds:
            train_step(model,imgs_,labels_)
            print('.',end="")
        print()

        train_loss_results.append(epoch_loss_avg.result())
        train_acc_results.append(train_accurancy)

        # 输出loss和acc结果
        print('Epoch:{}: loss:{:.3f},accurancy:{:,.3f}'.format(
            epoch + 1,
            epoch_loss_avg.result(),
            train_accurancy.result()
        ))

        # 对下一个epoch开始前进行loss和acc进行重置
        epoch_loss_avg.reset_states()
        train_accurancy.reset_states()

train()        

部分训练结果:

部分训练结果

添加测试数据集的指标

如果只是训练train数据,很有可能出现过拟合等情况而不知道。所以有必要增加test测试集。关于过拟合等知识,前面已经结果过了,可以看文章『什么是过拟合?什么是欠拟合?过拟合解决方法之使用Dropout抑制』来简单的复习一下。

这一小段只要是在前面的基础上增加上测试集的指标。首先是要解决测试集的数据集的问题,基本可以类似于训练集的部分:

# 创建测试集的数据集
test_image_path = glob.glob('./dc_2000/test/*/*.jpg')

# 获取标签,并转换为数字表示,例如dog是0,cat是1
test_image_label = [int(p.split('\\')[1]=='cat')  for p in test_image_path]

test_image_ds = tf.data.Dataset.from_tensor_slices((test_image_path,test_image_label))

test_image_ds = test_image_ds.map(load_preprosess_image,num_parallel_calls=AUTOTUNE)

# 测试集没有必要进行shuffle乱序
test_image_ds = test_image_ds.batch(BATCH_SIZE)
train_image_ds = test_image_ds.prefetch(AUTOTUNE)  #训练的时候预读取一部分数据,这样可以加快速度

然后就是test的step计算,在测试集的计算中,会比较简洁,因为不需要进行梯度计算和优化。

def test_step(model,images,labels):
    pred = model(images,training=False)
    loss_step = tf.keras.losses.BinaryCrossentropy(from_logits=True)(labels,pred)
    epoch_loss_avg_test(loss_step)                                            #  计算loss值
    test_accurancy(labels,tf.cast(pred>0,tf.int32))                     #  计算准确率

需要注意的是,在测试集中,设置training = False,将模块设置为评估模式。

加入测试集之后的完整代码(由于是在Jupyter Notebook上复制过来的,按顺序来看不是很规范):

import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import glob
import os

train_image_path = glob.glob('./dc_2000/train/*/*.jpg')
# 获取标签,并转换为数字表示,例如dog是0,cat是1
train_image_label = [int(p.split('\\')[1]=='cat')  for p in train_image_path]

# 创建测试集的数据集
test_image_path = glob.glob('./dc_2000/test/*/*.jpg')
# 获取标签,并转换为数字表示,例如dog是0,cat是1
test_image_label = [int(p.split('\\')[1]=='cat')  for p in test_image_path]


# 定义一个读取图像的函数
def load_preprosess_image(path,label):
    image = tf.io.read_file(path)
    image = tf.image.decode_jpeg(image,channels=3)    # 解码图片,并且设置channels=3,RGB彩色图片
    # 对图片进行扭曲,由于扭曲图片不会影响对猫狗的判断,所以本次可以,但不通用
    image = tf.image.resize(image,[256,256])          
    image = tf.cast(image,tf.float32)   
    image = image/255                                 # 先转换成float类型,再进行归一化处理
    label = tf.reshape(label,[1])                     # [0,0,1]——>[[0],[0],[1]]
    return image,label

train_image_ds = tf.data.Dataset.from_tensor_slices((train_image_path,train_image_label))
AUTOTUNE = tf.data.experimental.AUTOTUNE  # 根据CPU性能进行自动分配并行运算
train_image_ds = train_image_ds.map(load_preprosess_image,num_parallel_calls=AUTOTUNE)

BATCH_SIZE = 32
train_count = len(train_image_path)

train_image_ds = train_image_ds.shuffle(train_count).batch(BATCH_SIZE)
train_image_ds = train_image_ds.prefetch(AUTOTUNE)  #训练的时候预读取一部分数据,这样可以加快速度

test_image_ds = tf.data.Dataset.from_tensor_slices((test_image_path,test_image_label))
test_image_ds = test_image_ds.map(load_preprosess_image,num_parallel_calls=AUTOTUNE)
test_image_ds = test_image_ds.batch(BATCH_SIZE)
test_image_ds = test_image_ds.prefetch(AUTOTUNE)  #训练的时候预读取一部分数据,这样可以加快速度

# 创建模型
model = keras.Sequential([
    tf.keras.layers.Conv2D(64,(3,3),input_shape=(256,256,3),activation='relu'),
    tf.keras.layers.MaxPool2D(),
    tf.keras.layers.Conv2D(128,(3,3),activation='relu'),
    tf.keras.layers.MaxPool2D(),
    tf.keras.layers.Conv2D(256,(3,3),activation='relu'),    
    tf.keras.layers.MaxPool2D(),
    tf.keras.layers.Conv2D(512,(3,3),activation='relu'),
    tf.keras.layers.MaxPool2D(),
    tf.keras.layers.Conv2D(1024,(3,3),activation='relu'),
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(256,activation='relu'),
    tf.keras.layers.Dense(1)
])

# 设置优化器
optimizer = tf.keras.optimizers.Adam()

# 训练集的loss以及acc
epoch_loss_avg = tf.keras.metrics.Mean('train_loss')    # 训练的loss值
train_accurancy = tf.keras.metrics.Accuracy()           # 训练的准确率

# 测试集的loss以及acc
epoch_loss_avg_test = tf.keras.metrics.Mean('test_loss')    # 测试集的loss值
test_accurancy = tf.keras.metrics.Accuracy()                # 测试集的准确率


def train_step(model,images,labels):
    with tf.GradientTape() as t:
        pred = model(images)
        loss_step = tf.keras.losses.BinaryCrossentropy(from_logits=True)(labels,pred)
    grads = t.gradient(loss_step,model.trainable_variables)              #  计算loss_step与model.trainable_variables之间的梯度
    optimizer.apply_gradients(zip(grads,model.trainable_variables))      #  对模型的参数进行优化
    epoch_loss_avg(loss_step)                                            #  计算loss值
    train_accurancy(labels,tf.cast(pred>0,tf.int32))                     #  计算准确率
    # 准确率计算需要转化为tf.int32格式,因为原始label就是这个格式
    

def test_step(model,images,labels):
    pred = model(images,training=False)
    loss_step = tf.keras.losses.BinaryCrossentropy(from_logits=True)(labels,pred)
    epoch_loss_avg_test(loss_step)                                            #  计算loss值
    test_accurancy(labels,tf.cast(pred>0,tf.int32))                           #  计算准确率    
    
# 记录一些标量的值
train_loss_results = []
train_acc_results = []

test_loss_results = []
test_acc_results = []
    
num_epoch = 30   

def train()
    # 设置进行训练
    for epoch in range(num_epoch):
        for imgs_,labels_ in train_image_ds:
            train_step(model,imgs_,labels_)
            print('.',end="")
        print()

        train_loss_results.append(epoch_loss_avg.result())
        train_acc_results.append(train_accurancy.result())   

        for imgs_,labels_ in test_image_ds:
            test_step(model,imgs_,labels_)
        test_loss_results.append(epoch_loss_avg_test.result())
        test_acc_results.append(test_accurancy.result())

        # 输出loss和acc结果
        print('Epoch:{}: loss:{:.3f},accurancy:{:,.3f},test_loss:{:.3f},test_accurancy:{:,.3f}'.format(
            epoch + 1,
            epoch_loss_avg.result(),
            train_accurancy.result(),
            epoch_loss_avg_test.result(),
            test_accurancy.result(),
        ))

        # 对下一个epoch开始前进行loss和acc进行重置
        epoch_loss_avg.reset_states()
        train_accurancy.reset_states()

        epoch_loss_avg_test.reset_states()
        test_accurancy.reset_states()
        
train()        

部分训练结果(结果不是很理想):

部分训练结果

模型的优化

一般有两种途径,一种是增加网络的宽度,另一种是增加网络的深度。

宽度指 tf.keras.layers.Conv2D(128,(3,3),activation='relu'),中的128

深度指模型的层数。

一般还是选择增加深度,效果更加明显,也是主要的优化方向。

还可以在每一个卷积层后面增加一个BatchNormalization,具体可以参考文章『什么是数据批标准化?深度学习中的批标准化概念详解及其使用』。

数据集下载

猫狗数据集_2000张小批量_学习测试版本_66MB

完整版本的猫狗数据集_815MB

本文标签: 深度学习TensorFlow

版权声明 ▶ 本网站名称:陶小桃Blog
▶ 本文链接:https://www.52txr.cn/2022/CatDog.html
▶ 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行核实删除。
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处并附带原文链接!!
▶ 站长邮箱 [email protected][email protected] ,如不方便留言可邮件联系。

小陶的个人微信公众号

学累了就来张美女照片养养眼吧,身体是革命的本钱,要多多休息哦~ 随机美女图片

最后修改:2023 年 03 月 23 日
如果觉得我的文章对你有用,请随意赞赏!