Tensorflow2.0之迁移学习

Posted by Fangjuntao on 2020-04-12

简介

(参考:百度百科)

简单介绍

迁移学习是一种机器学习方法,就是把为任务 A 开发的模型作为初始点,重新使用在为任务 B 开发模型的过程中。深度学习中在计算机视觉任务和自然语言处理任务中将预训练的模型作为新模型的起点是一种常用的方法,通常这些预训练的模型在开发神经网络的时候已经消耗了巨大的时间资源和计算资源,迁移学习可以将已习得的强大技能迁移到相关的的问题上。

为什么需要迁移学习

(参考:mantch大佬http://blog.itpub.net/69942346/viewspace-2654034/)

  1. 大数据与少标注的矛盾:虽然有大量的数据,但往往都是没有标注的,无法训练机器学习模型。人工进行数据标定太耗时。
  2. 大数据与弱计算的矛盾:普通人无法拥有庞大的数据量与计算资源。因此需要借助于模型的迁移。
  3. 普适化模型与个性化需求的矛盾:即使是在同一个任务上,一个模型也往往难以满足每个人的个性化需求,比如特定的隐私设置。这就需要在不同人之间做模型的适配。
  4. 特定应用(如冷启动)的需求。

方法

开发模型的方法:

  1. 选择源任务。你必须选择一个具有丰富数据的相关的预测建模问题,原任务和目标任务的输入数据、输出数据以及从输入数据和输出数据之间的映射中学到的概念之间有某种关系,
  2. 开发源模型。然后,你必须为第一个任务开发一个精巧的模型。这个模型一定要比普通的模型更好,以保证一些特征学习可以被执行。
  3. 重用模型。然后,适用于源任务的模型可以被作为目标任务的学习起点。这可能将会涉及到全部或者部分使用第一个模型,这依赖于所用的建模技术。
  4. 调整模型。模型可以在目标数据集中的输入-输出对上选择性地进行微调,以让它适应目标任务。

预训练模型方法

  1. 选择源模型。一个预训练的源模型是从可用模型中挑选出来的。很多研究机构都发布了基于超大数据集的模型,这些都可以作为源模型的备选者。
  2. 重用模型。选择的预训练模型可以作为用于第二个任务的模型的学习起点。这可能涉及到全部或者部分使用与训练模型,取决于所用的模型训练技术。
  3. 调整模型。模型可以在目标数据集中的输入-输出对上选择性地进行微调,以让它适应目标任务。

第二种类型的迁移学习在深度学习领域比较常用

基于Tensorflow2.0实战

(参考:https://blog.csdn.net/weixin_45906054/article/details/103535499

前言

  1. 数据集:“Cats vs Dogs”的 数据集。这个数据集包含了 23,262 张猫和狗的图像。
    (可以在 Tensorflow Datasets 中获取这个数据集)
  2. 预训练模型:在 ImageNet 数据集上训练得到的模型。这些模型可以在 tensorflow.keras.applications 模块里找到
  3. 个人环境:
  • python3.5
  • tensorflow2.0
  • pycharm community
  1. 安装:
1
pip install tensorflow-datasets

实现

  1. 思路:这个实现分成了几个 部分。首先,我们实现了一个类,其负责 载入 数据和准备数据。然后,我们 导入 预训练模型,构建一个 类, 用于修改最顶端的几层网络。最后,我们把 训练 过程运行起来,并进行 评估
    当然,在这之前,我们必须导入一些代码库,定义一些全局常量

  2. 导入库,定义相关常量:

1
2
3
4
5
6
7
8
9
10
import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf
import tensorflow_datasets as tfds

IMG_SIZE = 160
BATCH_SIZE = 32
SHUFFLE_SIZE = 1000
IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3)
  1. 实现一个类:数据载入器
  • 这个类负责载入数据和准备数据,用于后续的数据处理。
  • 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#数据载入器
#这个类负责载入数据和 准备 数据,用于后续的数据处理。
class DataLoader(object):
def __init__(self, image_size, batch_size):

self.image_size = image_size
self.batch_size = batch_size

# 80% train data, 10% validation data, 10% test data

(self.train_data_raw, self.validation_data_raw, self.test_data_raw), self.metadata = tfds.load(
'cats_vs_dogs', split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
with_info=True,
as_supervised=True, )



# Get the number of train examples
self.num_train_examples = self.metadata.splits['train'].num_examples*80/100
self.get_label_name = self.metadata.features['label'].int2str

# Pre-process data
self._prepare_data()
self._prepare_batches()

# Resize all images to image_size x image_size
#内部方法,用于缩放和归一化数据集里的图像。构造函数需要用到该函数。
def _prepare_data(self):
self.train_data = self.train_data_raw.map(self._resize_sample)
self.validation_data = self.validation_data_raw.map(self._resize_sample)
self.test_data = self.test_data_raw.map(self._resize_sample)

# Resize one image to image_size x image_size
#内部方法,用于缩放单张图像。
def _resize_sample(self, image, label):
image = tf.cast(image, tf.float32)
image = (image/127.5) - 1
image = tf.image.resize(image, (self.image_size, self.image_size))
return image, label

#内部方法,用于将图像打包创建为 batches。创建 train_batches、validation_batches 和 test_batches,分别用于训练、评估过程。
def _prepare_batches(self):
self.train_batches = self.train_data.shuffle(1000).batch(self.batch_size)
self.validation_batches = self.validation_data.batch(self.batch_size)
self.test_batches = self.test_data.batch(self.batch_size)


# Get defined number of not processed images
#这个方法用于从原始的、没有经过处理的数据中随机获取固定数量的图像。
def get_random_raw_images(self, num_of_images):
random_train_raw_data = self.train_data_raw.shuffle(1000)
return random_train_raw_data.take(num_of_images)
  • 分析:看 __ init__
1
首先我们通过传入参数 定义 了图像大小和 batch 大小。然后,由于该数据集本身没有区分训练集和测试集,我们通过划分权值对数据进行划分。这真是 Tensorflow Dataset 引入的非常棒的功能,因为我们可以 留在Tensorflow生态系统 中做这件事,我们不用引入其他的库(比如 Pandas 或者 Scikit Learn)。一旦我们执行了数据划分,我们就开始 计算 训练样本数量,然后调用辅助函数来为训练 准备 数据。在这之后,我们需要做的仅仅是实例化这个类的 对象, 然后载入数据即可。
  1. 实例化这个类的对象,并显示图片进行查看
1
2
3
4
5
6
7
8
9
10
11
12
data_loader = DataLoader(IMG_SIZE, BATCH_SIZE)
plt.figure(figsize=(10, 8))
i = 0
for img, label in data_loader.get_random_raw_images(20):
plt.subplot(4, 5, i+1)
plt.imshow(img)
plt.title("{} - {}".format(data_loader.get_label_name(label), img.shape))
plt.xticks([])
plt.yticks([])
i += 1
plt.tight_layout()
plt.show()

以下是输出结果:
输出结果

  1. 载入 预训练模型
  • 这些模型位于 tensorflow.kearas.applications。我们可以用下面的语句直接载入它们:
1
2
3
vgg16_base = tf.keras.applications.VGG16(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
googlenet_base = tf.keras.applications.InceptionV3(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
resnet_base = tf.keras.applications.ResNet101V2(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
  • 这段代码就是我们创建上述三种网络结构 基础模型 的方式。注意,每个模型构造函数的 include_top参数 传入的是 false。这意味着这些模型是用于 提取特征 的,这些层的参数冻结。我们一旦创建了这些模型,我们就需要修改这些模型 顶部的网络层,使之适用于我们的具体 问题。我们使用 Wrapper 类来完成这个步骤。这个类接收 预训练模型,然后添加一个 Global Average Polling Layer 和一个 Dense Layer。本质上,这最后的 Dense Layer 会用于我们的二分类问题(猫或狗)。Wrapper 类把所有这些元素都放到了一起,放在了同一个 模型
  1. Wrapper类:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Wrapper(tf.keras.Model):
def __init__(self, base_model):
super(Wrapper, self).__init__()

self.base_model = base_model
self.average_pooling_layer = tf.keras.layers.GlobalAveragePooling2D()
self.output_layer = tf.keras.layers.Dense(1)

def call(self, inputs):
x = self.base_model(inputs)
x = self.average_pooling_layer(x)
output = self.output_layer(x)
return output
  1. 最后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
base_learning_rate = 0.0001

vgg16_base.trainable = False
vgg16 = Wrapper(vgg16_base)
vgg16.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
loss='binary_crossentropy',
metrics=['accuracy'])

googlenet_base.trainable = False
googlenet = Wrapper(googlenet_base)
googlenet.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
loss='binary_crossentropy',
metrics=['accuracy'])

resnet_base.trainable = False
resnet = Wrapper(resnet_base)
resnet.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
loss='binary_crossentropy',
metrics=['accuracy'])

PS: 我们标记了基础模型是 不参与训练的,这意味着在训练过程中,我们只会训练新添加到顶部的网络层,而在网络底部的权重值不会发生变化。

  1. 在开始完整训练前进行实验:这些模型的大部头其实已经 被训练过 了。所以,我们可以执行评估过程来看看评估结果如何:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
steps_per_epoch = round(data_loader.num_train_examples)//BATCH_SIZE
validation_steps = 20

loss1, accuracy1 = vgg16.evaluate(data_loader.validation_batches, steps = 20)
loss2, accuracy2 = googlenet.evaluate(data_loader.validation_batches, steps = 20)
loss3, accuracy3 = resnet.evaluate(data_loader.validation_batches, steps = 20)

print("--------VGG16---------")
print("Initial loss: {:.2f}".format(loss1))
print("Initial accuracy: {:.2f}".format(accuracy1))
print("---------------------------")

print("--------GoogLeNet---------")
print("Initial loss: {:.2f}".format(loss2))
print("Initial accuracy: {:.2f}".format(accuracy2))
print("---------------------------")

print("--------ResNet---------")
print("Initial loss: {:.2f}".format(loss3))
print("Initial accuracy: {:.2f}".format(accuracy3))
print("---------------------------")

有意思的是,这些模型在没有预先 训练 的情况下,我们得到的结果也还过得去(50% 的精确度):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
———VGG16———
Initial loss: 5.30
Initial accuracy: 0.51
—————————-

——GoogLeNet—–
Initial loss: 7.21
Initial accuracy: 0.51
—————————-

——–ResNet———
Initial loss: 6.01
Initial accuracy: 0.51
—————————-
  1. 开始训练:
  • vgg16
1
2
3
history = vgg16.fit(data_loader.train_batches,
epochs=10,
validation_data=data_loader.validation_batches)
  • googlenet
1
2
3
history = googlenet.fit(data_loader.train_batches,
epochs=10,
validation_data=data_loader.validation_batches)
  • resnet
1
2
3
history = resnet.fit(data_loader.train_batches,
epochs=10,
validation_data=data_loader.validation_batches)
  1. 评估:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
loss1, accuracy1 = vgg16.evaluate(data_loader.test_batches, steps = 20)
loss2, accuracy2 = googlenet.evaluate(data_loader.test_batches, steps = 20)
loss3, accuracy3 = resnet.evaluate(data_loader.test_batches, steps = 20)

print("--------VGG16---------")
print("Loss: {:.2f}".format(loss1))
print("Accuracy: {:.2f}".format(accuracy1))
print("---------------------------")

print("--------GoogLeNet---------")
print("Loss: {:.2f}".format(loss2))
print("Accuracy: {:.2f}".format(accuracy2))
print("---------------------------")

print("--------ResNet---------")
print("Loss: {:.2f}".format(loss3))
print("Accuracy: {:.2f}".format(accuracy3))
print("---------------------------")

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
——–VGG16———
Loss: 0.25
Accuracy: 0.93
—————————

——–GoogLeNet———
Loss: 0.54
Accuracy: 0.95
—————————
——–ResNet———
Loss: 0.40
Accuracy: 0.97
—————————
  1. 完整源代码:
    ++PS:由于时间关系,最终只下载了VGG16的参数,所以只实现了VGG16网络的迁移学习,若想迁移其它网络,同理++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf
import tensorflow_datasets as tfds

IMG_SIZE = 160
BATCH_SIZE = 32
SHUFFLE_SIZE = 1000
IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3)

#数据载入器
#这个类负责载入数据和 准备 数据,用于后续的数据处理。
class DataLoader(object):
def __init__(self, image_size, batch_size):

self.image_size = image_size
self.batch_size = batch_size

# 80% train data, 10% validation data, 10% test data

(self.train_data_raw, self.validation_data_raw, self.test_data_raw), self.metadata = tfds.load(
'cats_vs_dogs', split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
with_info=True,
as_supervised=True, )



# Get the number of train examples
self.num_train_examples = self.metadata.splits['train'].num_examples*80/100
self.get_label_name = self.metadata.features['label'].int2str

# Pre-process data
self._prepare_data()
self._prepare_batches()

# Resize all images to image_size x image_size
#内部方法,用于缩放和归一化数据集里的图像。构造函数需要用到该函数。
def _prepare_data(self):
self.train_data = self.train_data_raw.map(self._resize_sample)
self.validation_data = self.validation_data_raw.map(self._resize_sample)
self.test_data = self.test_data_raw.map(self._resize_sample)

# Resize one image to image_size x image_size
#内部方法,用于缩放单张图像。
def _resize_sample(self, image, label):
image = tf.cast(image, tf.float32)
image = (image/127.5) - 1
image = tf.image.resize(image, (self.image_size, self.image_size))
return image, label

#内部方法,用于将图像打包创建为 batches。创建 train_batches、validation_batches 和 test_batches,分别用于训练、评估过程。
def _prepare_batches(self):
self.train_batches = self.train_data.shuffle(1000).batch(self.batch_size)
self.validation_batches = self.validation_data.batch(self.batch_size)
self.test_batches = self.test_data.batch(self.batch_size)


# Get defined number of not processed images
#这个方法用于从原始的、没有经过处理的数据中随机获取固定数量的图像。
def get_random_raw_images(self, num_of_images):
random_train_raw_data = self.train_data_raw.shuffle(1000)
return random_train_raw_data.take(num_of_images)




data_loader = DataLoader(IMG_SIZE, BATCH_SIZE)
plt.figure(figsize=(10, 8))
i = 0
for img, label in data_loader.get_random_raw_images(20):
plt.subplot(4, 5, i+1)
plt.imshow(img)
plt.title("{} - {}".format(data_loader.get_label_name(label), img.shape))
plt.xticks([])
plt.yticks([])
i += 1
plt.tight_layout()
plt.show()

#载入 预训练模型 了,这些模型位于 tensorflow.kearas.applications
vgg16_base = tf.keras.applications.VGG16(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
vgg16_base.summary()
#googlenet_base = tf.keras.applications.InceptionV3(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
#resnet_base = tf.keras.applications.ResNet101V2(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')

#顶部的网络层,
class Wrapper(tf.keras.Model):
def __init__(self, base_model):
super(Wrapper, self).__init__()

self.base_model = base_model
self.average_pooling_layer = tf.keras.layers.GlobalAveragePooling2D()
self.output_layer = tf.keras.layers.Dense(1)

def call(self, inputs):
x = self.base_model(inputs)
x = self.average_pooling_layer(x)
output = self.output_layer(x)
return output


base_learning_rate = 0.0001

vgg16_base.trainable = False
vgg16 = Wrapper(vgg16_base)
vgg16.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
loss='binary_crossentropy',
metrics=['accuracy'])

# googlenet_base.trainable = False
# googlenet = Wrapper(googlenet_base)
# googlenet.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
# loss='binary_crossentropy',
# metrics=['accuracy'])

# resnet_base.trainable = False
# resnet = Wrapper(resnet_base)
# resnet.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate),
# loss='binary_crossentropy',
# metrics=['accuracy'])
steps_per_epoch = round(data_loader.num_train_examples) // BATCH_SIZE
validation_steps = 20

loss1, accuracy1 = vgg16.evaluate(data_loader.validation_batches, steps=20)
#loss2, accuracy2 = googlenet.evaluate(data_loader.validation_batches, steps=20)
#loss3, accuracy3 = resnet.evaluate(data_loader.validation_batches, steps=20)

print("--------VGG16---------")
print("Initial loss: {:.2f}".format(loss1))
print("Initial accuracy: {:.2f}".format(accuracy1))
print("---------------------------")

history = vgg16.fit(data_loader.train_batches,
epochs=10,
validation_data=data_loader.validation_batches)
loss1, accuracy1 = vgg16.evaluate(data_loader.test_batches, steps = 20)
print("--------VGG16---------")
print("Loss: {:.2f}".format(loss1))
print("Accuracy: {:.2f}".format(accuracy1))
print("---------------------------")

错误记录:

由于网络原因下载预训练网络权重失败:

  1. 图片

  2. 解决方案:
    参考:https://blog.csdn.net/icurious/article/details/80077035

1
2
3
Linux: 将下载好的*.h5文件下载到用户目录下的~/.keras/models

Windows: 在电脑的地址栏中输入%userprofile%,将下载好的*.h5文件放入.keras/models文件夹下

法二:
为了避免c盘占用过大,使用相对路径加载也可以

1
2
3
path='./[权重文件存放路径]/vgg16_weights_tf_dim_ordering_tf_kernels.h5'  
vgg16 = VGG16(weights=path, include_top=True)
vgg16.summary()