我对迁移学习的简单理解就是,将别人训练好的模型用到自己的程序中,同时根据实际情况,重新训练模型中的部分参数。我认为这样有2个好处,一是由于使用了已知的模型,那么节省训练的时间,二是在充分利用已知的成果,节省精力。下面我使用Jupyter Notebook编辑环境,以猫狗识别为例,对迁移学习进行分析。
1、首先导入需要的包
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import glob
import os
2、为了使代码更加见解,我进行了如下的赋值。
keras = tf.keras
layers = tf.keras.layers
3、读取数据集,获取数据集中每条数据的路径,路径存放到列表中。其中,train_image_path用于存放训练数据集,test_image_path用于存放验证数据集。
train_image_path = glob.glob('/home/haha/资料/人工智能/数据集/dog-cat-2000/train/*/*.jpg')
test_image_path = glob.glob('/home/haha/资料/人工智能/数据集/dog-cat-2000/test/*/*.jpg')
np.random.shuffle(train_image_path)
执行test_image_path[:5],可以看到验证数据集中前5项,如下。
['/home/haha/资料/人工智能/数据集/dog-cat-2000/test/dog/dog.1433.jpg', '/home/haha/资料/人工智能/数据集/dog-cat-2000/test/dog/dog.1091.jpg', '/home/haha/资料/人工智能/数据集/dog-cat-2000/test/dog/dog.1269.jpg', '/home/haha/资料/人工智能/数据集/dog-cat-2000/test/dog/dog.1267.jpg', '/home/haha/资料/人工智能/数据集/dog-cat-2000/test/dog/dog.1043.jpg']
4、获取数据集中的每条数据对应的标签。dog的标签值为0,cat的标签值为1。
train_image_label = [int(p.split('/')[-2] == 'cat') for p in train_image_path]
test_image_label = [int(p.split('/')[-2] == 'cat') for p in test_image_path]
5、定义图片的预处理函数。
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, [256,256]) #将图片大小值为256*256
image = tf.cast(image, tf.float32) #图片像素值的默认类型为uint8,现在需要将数据类型转换为tf.float32
image = image/255 #图片每一像素的值范围是0-255,现在需做归一化处理,所以除以255
#label的形式为[1,2,3],下面代码将label的形式转化为[[1], [2], [3]]
label = tf.reshape(label, [1])
return image, label
6、将图片的路径和标签组合列表中的一项。
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))
7、调用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)
8、我想看一下数据集。执行test_image_ds就可以看一下测试数据集的信息,结果为<ParallelMapDataset shapes: ((256, 256, 3), (1,)), types: (tf.float32, tf.int32)>,表示每张图片的大小为256*256,厚度为3(即彩色图片),对应图片标签的长度为1。
如下代码可以查看一下第一张图片:
for image, label in test_image_ds.take(2):
plt.imshow(image)
显示为:
9、设置数据集的相关属性等操作。训练时,每次从数据集中读取一批(即32条数据)。必须对训练数据集做shuffle(乱序)操作。
BATCH_SIZE = 32
train_count = len(train_image_path)
test_count = len(test_image_path)
train_image_ds = train_image_ds.shuffle(train_count).repeat().batch(BATCH_SIZE)
test_image_ds = test_image_ds.repeat().batch(BATCH_SIZE)
10、迁移学习需要使用已有的模型的参数,那么模型参数从何而来,当然是从网上下载。从网上下载vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5文件,放到本机/home/haha/.keras/datasets目录中。该文件就是vgg16卷集神经网络的权重文件。然后以下代码就是读取该文件。
tf.keras.datasets.mnist.load_data() #该语句的作用是使得程序从本地(/home/wchw/.keras/datasets)读取数据,而不需要从网上下载。
11、获取卷积基。如果省略第9步,那该步骤就需要在线下载权重,现在速度很慢。使用的权重为在别人在imagenet数据集上训练好的权重。include_top = False表示只使用卷积基,而不使用全连接部分。因此,我需要自己训练全连接层的权重。我为什么选择自己训练全连接层,我的理解是卷基层是对特征进行提取,而该特征是cat还是dog,这就需要自己构建全连接层,用我自己的数据集进行训练得出的模型进行判断。
#weights='imagenet'表示使用在imagenet上训练好的权重
#include_top = False表示只使用卷积基,而不使用全连接部分
covn_base = keras.applications.VGG16(weights='imagenet', include_top = False)
12、执行covn_base.summary()可以查看该模型的信息。下面是最后的几部分:
13、下面构建模型。model.add(covn_base)表示使用已有的卷积部分(即迁移学习的核心),后面的就是我自己构建的全连接层部分。
model = keras.Sequential()
model.add(covn_base)
model.add(layers.GlobalAveragePooling2D())
model.add(layers.Dense(512, activation = 'relu'))
model.add(layers.Dense(1, activation = 'sigmoid'))
执行model.summary(),可查看该模型,如下:
问题来了,为什么Total params: 14,977,857。Trainable params: 14,977,857。因为我还需要进行其他的设置,看下一步。
14、设置卷积部分不可训练,也就是使用卷积部分现有的权重。
covn_base.trainable = False
执行model.summary()
可发现:Total params: 14,977,857。Trainable params: 263,169。Non-trainable params: 14,714,688。这是希望看到的。
15、编译模型。
model.compile(optimizer=keras.optimizers.Adam(lr=0.0005),
loss = 'binary_crossentropy',
metrics = ['acc']
)
16、训练模型。
history = model.fit(
train_image_ds,
steps_per_epoch=train_count//BATCH_SIZE,
epochs=15,
validation_data=test_image_ds,
validation_steps=test_count//BATCH_SIZE
)
部分结果如下: