为什么我在kaggle平台上编写猫狗识别程序,一是因为我的个人电脑没有GPU,kaggle平台提供了GPU,使用GPU可以大大提高训练的效率;二是kaggle平台上提供了猫狗数据集。
1、部署kaggle开发环境,这部分不再描述,下面是我的设置情况。
2、在kaggle平台上安装GPU版本的tensorflow2.3.0
pip install tensorflow-gpu==2.3.0
3、导入需要的包
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import glob
import os
4、读取数据集,获取数据集中每条数据的路径,路径存放到列表中。其中,train_image_path用于存放训练数据集,test_image_path用于存放验证数据集。
train_image_path = glob.glob('../input/cat-and-dog/training_set/training_set/*/*.jpg')
test_image_path = glob.glob('../input/cat-and-dog/test_set/test_set/*/*.jpg')
5、对训练数据进行乱序处理。
np.random.shuffle(train_image_path)
注意:对训练数据进行乱序处理很重要,我最初就是因为没有对训练数据集进行乱序处理,导致了在测试数据集上的识别精度始终为50%左右,无法提高。
6、获取数据集中的每条数据对应的标签。dog的标签值为0,cat的标签值为1。
#p为每张图片的路径
#p.split('/')[-2]作用是对路径进行切分,并取出倒数第2个元素,该元素的值为cat或者dog
#如果p.split('/')[-2]的值为cat则返回1,如果为dog则返回0
#train_image_label为每张图片的标签,图片为cat值为1,图片为dog值为0
train_image_label = [int(p.split('training_set/training_set/')[1].split('/')[0] == 'cats') for p in train_image_path]
test_image_label = [int(p.split('test_set/test_set/')[1].split('/')[0] == 'cats') for p in test_image_path]
7、查看一下训练数据集和测试数据集有多少条数据
len(train_image_path)
结果为8005
len(test_image_path)
结果为2023
8、定义图片的预处理函数
def load_preprosess_image(path, label):
image = tf.io.read_file(path)
image = tf.image.decode_jpeg(image, channels=3) #由于图像是彩色的,所以channels=3
image = tf.image.resize(image, [360, 360]) #将图片大小值为360*360
image = tf.image.random_flip_left_right(image)
image = tf.image.random_flip_up_down(image)
image = tf.cast(image, tf.float32) #图片像素值的默认类型为uint8,现在需要将数据类型转换为tf.float32
image = image/255 #图片每一像素的值范围是0-255,现在需做归一化处理,所以除以255
label = tf.reshape(label, [1]) #label的形式为[1,2,3],将label的形式转化为[[1], [2], [3]]
return image, label
9、将图片的路径和标签组合列表中的一项
train_image_ds = tf.data.Dataset.from_tensor_slices((train_image_path, train_image_label))
test_image_ds = tf.data.Dataset.from_tensor_slices((test_image_path, test_image_label))
10、调用load_preprosess_image进行图片预处理。
AUTOTUNE = tf.data.experimental.AUTOTUNE #TensorFlow根据系统的实际情况,自动选择合适的值
#调用load_preprosess_image函数,进行图片处理。num_parallel_calls为TensorFlow选择的cpu的核心数。
train_image_ds = train_image_ds.map(load_preprosess_image, num_parallel_calls=AUTOTUNE)
#调用load_preprosess_image函数,进行图片处理。num_parallel_calls为TensorFlow选择的cpu的核心数。
test_image_ds = test_image_ds.map(load_preprosess_image, num_parallel_calls=AUTOTUNE)
11、下面可以看一下数据集的信息
test_image_ds
结果为<ParallelMapDataset shapes: ((360, 360, 3), (1,)), types: (tf.float32, tf.int32)>,表示每张图片的大小为360*360,厚度为3(即彩色图片),对应图片标签的长度为1。
train_image_ds
结果为<ParallelMapDataset shapes: ((360, 360, 3), (1,)), types: (tf.float32, tf.int32)>,表示每张图片的大小为360*360,厚度为3(即彩色图片),对应图片标签的长度为1。
12、设置数据集的相关属性等操作。训练时,每次从数据集中读取一批(即32条数据)。
BATCH_SIZE = 32
train_count = len(train_image_path)
test_count = len(test_image_path)
train_image_ds = train_image_ds.shuffle(500).batch(BATCH_SIZE)
train_image_ds = train_image_ds.prefetch(AUTOTUNE) #在训练期间,后台同时读取数据并进行转换操作,目的是提高效率
test_image_ds = test_image_ds.batch(BATCH_SIZE)
test_image_ds = test_image_ds.prefetch(AUTOTUNE) #在训练期间,后台同时读取数据并进行转换操作,目的是提高效率
13、下面从train_image_ds中读取一次数据
imgs, labels = next(iter(train_image_ds))
通过下面代码可以查看读取到的数据的形状
imgs.shape
结果为TensorShape([32, 360, 360, 3]),32代表一批次32张图片,每张图片的大小为360*360,厚度为3(即彩色图片)。
下面代码查看显示最后一张图片
plt.imshow(imgs[-1])
14、构建训练模型
model = keras.Sequential([
tf.keras.layers.Conv2D(64, (3, 3), input_shape=(256, 256, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Conv2D(128, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Conv2D(128, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Conv2D(256, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Conv2D(256, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Conv2D(512, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Conv2D(512, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D(),
tf.keras.layers.Conv2D(1024, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Conv2D(1024, (3, 3), activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(256, activation='relu'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Dense(1)
])
下面代码可以查看该模型的情况
model.summary()
部分结果为
15、执行下面代码,使用该模型就可以对图片进行预测。
pred = model(imgs)
看一下pred
pred.shape
结果为TensorShape([32, 1]),由于imgs是32张图片,预测的结果也是输出32项结果。
问题来了,该模型尚未进行训练,为什么能输出结果来,这是因为构建模型后,会分配给模型的相应的默认权重,因此也能输出结果,当然结果是十分不准确的。要想输出的结果准确,就需要训练,训练的过程就是逐步更新权重的过程。
16、更新权重的过程包含当前模型预测结果与标签值的比较,通过比较出来的误差来更新模型的权重。那么如何比较呢,通过二元交叉熵来比较。下面代码定义二元交叉熵。
ls = tf.keras.losses.BinaryCrossentropy() #二元交叉熵
下面举例比较
ls([0., 0., 1., 1.], [1., 1., 1., 1.])
结果为<tf.Tensor: shape=(), dtype=float32, numpy=7.6666193>
ls([[0.], [0.], [1.], [1.]], [[1.], [1.], [1.], [1.]])
结果为<tf.Tensor: shape=(), dtype=float32, numpy=7.6666193>
也可以下面这样
#二元交叉熵, binary_crossentropy为小写,前面的BinaryCrossentropy为大写,区别就是小写的必须传入参数,如下。
tf.keras.losses.binary_crossentropy([0., 0., 1., 1.], [1., 1., 1., 1.])
17、定义优化器、平均损失、正确率等
optimizer = tf.keras.optimizers.Adam(learning_rate = 0.0001)#learning_rate = 0.01
epoch_loss_avg = tf.keras.metrics.Mean('train_loss') # 计算平均损失值
train_accuracy = tf.keras.metrics.Accuracy() #计算正确率
epoch_loss_avg_test = tf.keras.metrics.Mean('test_loss') # 计算平均损失值
test_accuracy = tf.keras.metrics.Accuracy() #计算正确率
18、定义每一步的训练函数
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)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
epoch_loss_avg(loss_step)
train_accuracy(labels, tf.cast(pred>0, tf.int32))
19、定义每一步的测试函数
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)
test_accuracy(labels, tf.cast(pred>0, tf.int32))
20、训练
num_epochs = 30
for epoch in range(num_epochs):
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_accuracy.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_accuracy.result())
print('Epoch:{}: loss: {:.3f}, accuracy: {:.3f}, test_loss: {:.3f}, test_accuracy: {:.3f}'.format(
epoch + 1,
epoch_loss_avg.result(),
train_accuracy.result(),
epoch_loss_avg_test.result(),
test_accuracy.result()
))
epoch_loss_avg.reset_states()
train_accuracy.reset_states()
epoch_loss_avg_test.reset_states()
test_accuracy.reset_states()
下面是部分的输出结果
可以看出,随着训练次数的不断增多,在训练数据集和测试数据集上的识别准确率在不断的提高。