赞
踩
深度学习的迁移学习是一种技术,它允许将一个任务上学到的知识或模型应用到另一个任务中。其核心思想是将一种任务中学习的特征或模型权重用于另一种任务,以实现知识的迁移和模型的优化。
迁移学习在深度学习中具有广泛的应用,特别是在数据量较少的情况下。通过利用在源领域(source domain)上学习到的知识,迁移学习可以帮助目标领域(target domain)上的学习任务。迁移学习的主要类型包括特征提取、微调和共享参数。
迁移学习在深度学习中的应用场景非常广泛,包括图像分类、自然语言处理、语音识别和游戏AI等领域。通过迁移学习,我们可以减少训练数据量、节省训练时间并提高模型性能。
例如,在图像分类任务中,当源领域上有大量标注数据而目标领域上的数据较少时,我们可以使用迁移学习将源领域上的模型应用到目标领域上,从而提升目标领域的分类性能。同样地,在自然语言处理任务中,迁移学习也可以帮助我们利用在大规模语料库上预训练的模型来加速新任务的训练过程并提高性能。
迁移学习通常用于那些数据集太小、无法从头开始训练一个完整模型的任务。
在深度学习的背景下,迁移学习的最常见形式是以下工作流程:
首先,我们将详细介绍Keras的可训练API,它是大多数迁移学习和微调工作流程的基础。
然后,我们将通过在一个预训练于ImageNet数据集的模型上,并在Kaggle的“猫狗分类”数据集上重新训练它来展示典型的工作流程。
层和模型具有三个权重属性:
Dense 层是深度学习模型中的一个常见层,用于实现全连接层(也称为密集层或线性层)。它包含两个主要的可训练权重:
示例:Dense 层有两个可训练的权重(kernel(核)和 bias(偏置))。
layer = keras.layers.Dense(3)
layer.build((None, 4)) # Create the weights
print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2
trainable_weights: 2
non_trainable_weights: 0
在深度学习中,大多数层的权重默认都是可训练的(trainable),这意味着在训练过程中,这些权重会通过梯度下降等优化算法进行更新,以最小化损失函数。然而,也有一些特殊的层,如BatchNormalization
层,它包含非训练权重(non-trainable weights)。
BatchNormalization
层在训练过程中执行两个主要操作:标准化(normalization)和缩放/平移(scaling/shifting)。标准化是通过对输入数据进行变换,使其具有零均值和单位方差来实现的。这个变换的参数(即均值和方差)是在每个训练批次上计算的,并用于标准化当前批次的输入。这些均值和方差在训练过程中是不断更新的,但它们不是通过梯度下降来优化的,因此它们被视为非训练权重。
另一方面,BatchNormalization
层还有另外两个可训练权重:缩放因子(gamma)和平移项(beta)。这些权重在训练过程中通过梯度下降进行更新,用于控制标准化后的数据应该如何被缩放和平移。这些权重对于调整模型的输出分布非常重要,可以帮助模型更好地拟合训练数据。
因此,BatchNormalization
层通常包含两个可训练权重(gamma和beta)和两个非训练权重(输入数据的均值和方差)。
如果你想在自己的自定义层中使用非训练权重,你可以通过在定义层时创建tf.Variable
对象并设置其trainable
属性为False
来实现。这样,这些权重就不会在训练过程中被优化算法更新。但是,请注意,在大多数情况下,你不需要直接使用非训练权重,除非你的层具有类似于BatchNormalization
层的特殊需求。
layer = keras.layers.BatchNormalization()
layer.build((None, 4)) # Create the weights
print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 4
trainable_weights: 2
non_trainable_weights: 2
可训练属性设置为不可训练
将trainable设置为False
layer = keras.layers.Dense(3)
layer.build((None, 4)) # Create the weights
layer.trainable = False # Freeze the layer
print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2
trainable_weights: 0
non_trainable_weights: 2
不可训练属性的更新
当一个可训练权重变为非训练权重时,其值在训练过程中将不再被更新。
# Make a model with 2 layers layer1 = keras.layers.Dense(3, activation="relu") layer2 = keras.layers.Dense(3, activation="sigmoid") model = keras.Sequential([keras.Input(shape=(3,)), layer1, layer2]) # Freeze the first layer layer1.trainable = False # Keep a copy of the weights of layer1 for later reference initial_layer1_weights_values = layer1.get_weights() # Train the model model.compile(optimizer="adam", loss="mse") model.fit(np.random.random((2, 3)), np.random.random((2, 3))) # Check that the weights of layer1 have not changed during training final_layer1_weights_values = layer1.get_weights() np.testing.assert_allclose( initial_layer1_weights_values[0], final_layer1_weights_values[0] ) np.testing.assert_allclose( initial_layer1_weights_values[1], final_layer1_weights_values[1]
1/1 ━━━━━━━━━━━━━━━━━━━━ 1s 766ms/step - loss: 0.0615
递归设置可训练属性
如果你在一个模型或任何具有子层的层上设置trainable = False,那么所有的子层也将变得不可训练。
inner_model = keras.Sequential( [ keras.Input(shape=(3,)), keras.layers.Dense(3, activation="relu"), keras.layers.Dense(3, activation="relu"), ] ) model = keras.Sequential( [ keras.Input(shape=(3,)), inner_model, keras.layers.Dense(3, activation="sigmoid"), ] ) model.trainable = False # Freeze the outer model assert inner_model.trainable == False # All layers in `model` are now frozen assert inner_model.layers[0].trainable == False # `trainable` is propagated recursively
本节我们将讨论如何在Keras中实现典型的迁移学习工作流程:
trainable = False
来冻结基础模型中的所有层。注意,还有一个更轻量级的替代工作流程可能是:
第二种工作流程的一个关键优势是,只需要在数据集上运行一次基础模型,而不是在训练的每个周期中都运行。因此,它更快也更便宜。
然而,第二种工作流程的一个问题是,它不允许程序员在训练过程中动态修改新模型的输入数据,但这在执行数据增强时是必需的。迁移学习通常用于当程序员的新数据集数据量太小,无法从头开始训练一个全尺寸模型的任务,而在这种场景下,数据增强非常重要。因此,接下来我们将重点关注第一种工作流程。
设置
import numpy as np
import keras
from keras import layers
import tensorflow_datasets as tfds
import matplotlib.pyplot as plt
基础模型的实例化
首先,使用预训练的权重实例化一个基础模型。
base_model = keras.applications.Xception(
weights='imagenet', # Load weights pre-trained on ImageNet.
input_shape=(150, 150, 3),
include_top=False) # Do not include the ImageNet classifier at the top.
冻结基础模型
base_model.trainable = False
建立新的模型
inputs = keras.Input(shape=(150, 150, 3))
# We make sure that the base_model is running in inference mode here,
# by passing `training=False`. This is important for fine-tuning, as you will
# learn in a few paragraphs.
x = base_model(inputs, training=False)
# Convert features of shape `base_model.output_shape[1:]` to vectors
x = keras.layers.GlobalAveragePooling2D()(x)
# A Dense classifier with a single unit (binary classification)
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)
训练模型
使用数据集对新建立的模型进行训练
model.compile(optimizer=keras.optimizers.Adam(),
loss=keras.losses.BinaryCrossentropy(from_logits=True),
metrics=[keras.metrics.BinaryAccuracy()])
model.fit(new_dataset, epochs=20, callbacks=..., validation_data=...)
如果新建立的模型在新数据上收敛,程序员就可以尝试解冻基础模型的全部或部分层,并使用非常低的学习率对整个模型进行端到端的重新训练。
这是一个可选的最后步骤,可能会给程序员带来一些额外的改进。但这也可能导致快速过拟合。
重要的是,只有在模型带有冻结的层已经训练到收敛之后,才执行这一步。如果程序员将随机初始化的可训练层与包含预训练特征的可训练层混合在一起,那么随机初始化的层在训练过程中会产生非常大的梯度更新,这会破坏预训练特征。
同样重要的是,在这个阶段使用非常低的学习率,因为此时程序员通常正在一个通常非常小的数据集上训练一个比第一轮训练时大得多的模型。因此,如果应用大的权重更新,将会很快面临过拟合的风险。在这里,程序员只想以增量的方式重新调整预训练的权重。
以下是实现整个基础模型微调的方法:
trainable
属性为True
。通过这种方法,程序员可以利用预训练的模型权重,同时调整这些权重以适应程序员的特定任务,从而可能获得更好的性能。但是,请注意监控过拟合的迹象,并考虑使用如早停(early stopping)、正则化(regularization)等技术来防止过拟合。
# Unfreeze the base model
base_model.trainable = True
# It's important to recompile your model after you make any changes
# to the `trainable` attribute of any inner layer, so that your changes
# are take into account
model.compile(optimizer=keras.optimizers.Adam(1e-5), # Very low learning rate
loss=keras.losses.BinaryCrossentropy(from_logits=True),
metrics=[keras.metrics.BinaryAccuracy()])
# Train end-to-end. Be careful to stop before you overfit!
model.fit(new_dataset, epochs=10, callbacks=..., validation_data=...)
在模型上调用compile()
方法意味着“冻结”该模型的行为。这意味着在模型编译时设置的trainable
属性值应该在该模型的整个生命周期中被保留,直到再次调用compile()
。因此,如果程序员改变了任何trainable
值,请确保再次在模型上调用compile()
以使更改生效。
许多图像模型都包含BatchNormalization层。这个层在可想象的每个方面都是一个特殊情况。以下是需要注意的几点:
BatchNormalization包含两个在训练过程中更新的非可训练权重。这些变量跟踪输入的均值和方差。
当设置bn_layer.trainable = False
时,BatchNormalization层将在推理模式下运行,并且不会更新其均值和方差统计信息。这通常不是其他层的情况,因为权重可训练性和推理/训练模式是两个正交的概念。但在BatchNormalization层的情况下,两者是相关的。
当程序员解冻包含BatchNormalization层的模型以进行微调时,应该在调用基础模型时传递training=False
来保持BatchNormalization层在推理模式下运行。否则,对非可训练权重的更新会突然破坏模型所学到的东西。
在实际操作中,当程序员从冻结的模型过渡到微调阶段时,你通常会保持基础模型的BatchNormalization层在推理模式下(即training=False
),同时只让新添加的层的权重可训练。这有助于保持预训练特征的稳定性,同时允许程序员微调新添加的层以更好地适应新任务。
为了加深对前面讨论概念的理解,本节通过一个具体的迁移学习和微调操作的示例来展示相应的知识点。我们将加载在ImageNet上预训练的Xception模型,并将其应用于Kaggle的“猫狗”分类数据集。
首先,让我们使用TFDS(TensorFlow数据集)来获取猫狗数据集。如果你有自己的数据集,你可能会想使用keras.utils.image_dataset_from_directory
这个工具来从磁盘上按类别文件夹组织的图像集中生成类似的带标签数据集对象。
迁移学习在处理非常小的数据集时最为有用。为了保持数据集的小规模,我们将使用原始训练数据的40%(25,000张图片)进行训练,10%用于验证,10%用于测试。
tfds.disable_progress_bar()
train_ds, validation_ds, test_ds = tfds.load(
"cats_vs_dogs",
# Reserve 10% for validation and 10% for test
split=["train[:40%]", "train[40%:50%]", "train[50%:60%]"],
as_supervised=True, # Include labels
)
print(f"Number of training samples: {train_ds.cardinality()}")
print(f"Number of validation samples: {validation_ds.cardinality()}")
print(f"Number of test samples: {test_ds.cardinality()}")
我们的原始图像具有各种大小。此外,每个像素由0到255之间的3个整数值(RGB级别值)组成。这不太适合直接输入到神经网络中。我们需要做两件事:
通常,开发以原始数据作为输入的模型是一个好习惯,而不是以已经预处理过的数据作为输入的模型。原因是,如果你的模型期望预处理过的数据,那么每次你将模型导出到其他地方使用(如在浏览器中、在移动应用中)时,你都需要重新实现完全相同的预处理流程。这很快就会变得非常棘手。因此,我们应该在模型处理之前尽可能少地进行预处理。
在这里,我们将在数据管道中调整图像大小(因为深度神经网络只能处理连续的数据批次),并且当我们创建模型时,我们将输入值缩放作为模型的一部分来实现。
让我们将图像大小调整为150x150:
resize_fn = keras.layers.Resizing(150, 150)
train_ds = train_ds.map(lambda x, y: (resize_fn(x), y))
validation_ds = validation_ds.map(lambda x, y: (resize_fn(x), y))
test_ds = test_ds.map(lambda x, y: (resize_fn(x), y))
当你没有大型图像数据集时,一个很好的做法是对训练图像应用随机但现实的变换来人为地引入样本多样性,例如随机水平翻转或小随机旋转。这有助于让模型接触到训练数据的不同方面,同时减缓过拟合。
augmentation_layers = [
layers.RandomFlip("horizontal"),
layers.RandomRotation(0.1),
]
def data_augmentation(x):
for layer in augmentation_layers:
x = layer(x)
return x
train_ds = train_ds.map(lambda x, y: (data_augmentation(x), y))
对数据进行批处理并使用预取功能来优化加载速度。
from tensorflow import data as tf_data
batch_size = 64
train_ds = train_ds.batch(batch_size).prefetch(tf_data.AUTOTUNE).cache()
validation_ds = validation_ds.batch(batch_size).prefetch(tf_data.AUTOTUNE).cache()
test_ds = test_ds.batch(batch_size).prefetch(tf_data.AUTOTUNE).cache()
按照之前解释的蓝图来构建一个模型:
training=False
,以便它在推理模式下运行,这样即使在微调时解冻基础模型,批归一化(batch normalization)的统计信息也不会被更新。base_model = keras.applications.Xception( weights="imagenet", # Load weights pre-trained on ImageNet. input_shape=(150, 150, 3), include_top=False, ) # Do not include the ImageNet classifier at the top. # Freeze the base_model base_model.trainable = False # Create new model on top inputs = keras.Input(shape=(150, 150, 3)) # Pre-trained Xception weights requires that input be scaled # from (0, 255) to a range of (-1., +1.), the rescaling layer # outputs: `(inputs * scale) + offset` scale_layer = keras.layers.Rescaling(scale=1 / 127.5, offset=-1) x = scale_layer(inputs) # The base model contains batchnorm layers. We want to keep them in inference mode # when we unfreeze the base model for fine-tuning, so we make sure that the # base_model is running in inference mode here. x = base_model(x, training=False) x = keras.layers.GlobalAveragePooling2D()(x) x = keras.layers.Dropout(0.2)(x) # Regularize with dropout outputs = keras.layers.Dense(1)(x) model = keras.Model(inputs, outputs) model.summary(show_trainable=True)
model.compile(
optimizer=keras.optimizers.Adam(),
loss=keras.losses.BinaryCrossentropy(from_logits=True),
metrics=[keras.metrics.BinaryAccuracy()],
)
epochs = 2
print("Fitting the top layer of the model")
model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
最后,解冻基础模型并使用较低的学习率对整个模型进行端到端的微调。
重要的是,尽管基础模型变得可训练,但由于我们在构建模型时调用它时传递了training=False
,因此它仍然处于推理模式。这意味着其中的批归一化层不会更新其批统计信息。如果它们更新了,那么它们会破坏模型到目前为止学习的特征表示。
# Unfreeze the base_model. Note that it keeps running in inference mode # since we passed `training=False` when calling it. This means that # the batchnorm layers will not update their batch statistics. # This prevents the batchnorm layers from undoing all the training # we've done so far. base_model.trainable = True model.summary(show_trainable=True) model.compile( optimizer=keras.optimizers.Adam(1e-5), # Low learning rate loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()], ) epochs = 1 print("Fitting the end-to-end model") model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
迁移学习和微调(也称为精细调整)是深度学习中两种强大的技术,它们允许模型在特定任务上快速有效地学习,特别是当目标数据集相对较小或标注数据稀缺时。以下是关于迁移学习和微调的总结:
迁移学习
迁移学习是一种机器学习方法,其中在一个任务上学到的知识被用来改进另一个不同但相关的任务上的学习。在深度学习中,这通常意味着使用在一个大型数据集上预训练的模型(如ImageNet上的模型)作为起点,然后将这些学到的特征或模型权重迁移到一个新的、较小或特定的数据集上。这种方法的关键在于,预训练模型已经学会了识别图像中的通用特征,这些特征在新任务上也可能是有用的。
微调
微调是迁移学习中的一种特殊技术,其中预训练模型的部分或全部层被解冻(变得可训练),然后使用新数据集上的数据重新训练这些层。在微调过程中,通常会使用比预训练时更低的学习率,以防止破坏预训练模型中学到的有用特征。微调允许模型学习针对新任务进行特定优化的特征,从而在新数据集上获得更好的性能。
迁移学习和微调是深度学习实践中广泛使用的技术,特别是在处理标注数据稀缺或计算资源有限的情况时。通过利用预训练模型中的通用特征,迁移学习能够显著加速模型在新任务上的训练过程。而微调则进一步允许模型针对新任务进行特定优化,从而提高其在新数据集上的性能。这两种技术的结合使用,使得深度学习模型能够更有效地处理各种复杂任务,并在各种应用场景中取得优异的结果。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。