深度学习神经网络的一个有趣优势是它们可以重用于相关的其他问题。
迁移学习是指一种技术,用于在不同但有些相似的问题上进行预测建模,然后可以部分或全部地重用该技术,以加速训练并提高模型在目标问题上的性能。
在深度学习中,这意味着将预训练模型的一个或多个层的权重重用于新模型中,并在训练模型时保持权重固定、进行微调或完全适应权重。
在本教程中,您将使用 Keras 在 Python 中了解如何利用迁移学习来提高深度学习神经网络的性能。
完成本教程后,您将了解:
- 迁移学习是一种重用在相关预测建模问题上训练过的模型的方法。
- 迁移学习可以用作权重初始化方案或特征提取方法,以加速神经网络的训练。
- 如何使用迁移学习来提高 MLP 在多类别分类问题上的性能。
用我的新书《更好的深度学习》来启动你的项目,书中包含分步教程和所有示例的 Python 源代码文件。
让我们开始吧。
- 2019 年 10 月更新:更新至 Keras 2.3 和 TensorFlow 2.0。
- **2020年1月更新**:已针对 scikit-learn v0.22 API 的变更进行更新。

如何通过迁移学习提高深度学习神经网络的性能
照片由 Damian Gadal 拍摄,部分权利保留。
教程概述
本教程分为六个部分;它们是:
- 什么是迁移学习?
- Blobs 多类别分类问题
- 问题 1 的多层感知机模型
- 问题 2 的独立 MLP 模型
- 问题 2 的迁移学习 MLP
- 问题 2 模型对比
什么是迁移学习?
迁移学习通常是指将一个模型在某个问题上训练后,以某种方式用于第二个相关问题。
迁移学习和领域自适应指的是,在一种设置(即分布 P1)中学到的知识被用于改进另一种设置(例如分布 P2)中的泛化能力。
— 第 536 页,《深度学习》,2016 年。
在深度学习中,迁移学习是一种技术,其中神经网络模型首先在一个与正在解决的问题相似的问题上进行训练。然后,将训练模型的一个或多个层用于在新模型中,该新模型针对目标问题进行训练。
这通常在监督学习的背景下进行理解,其中输入相同,但目标可能性质不同。例如,我们可能在第一个设置中学习一组视觉类别,例如猫狗,然后在第二个设置中学习另一组视觉类别,例如蚂蚁和黄蜂。
— 第 536 页,《深度学习》,2016 年。
迁移学习的优势在于缩短神经网络模型的训练时间,并降低泛化误差。
实现迁移学习主要有两种方法;它们是:
- 权重初始化。
- 特征提取。
重用层中的权重可以用作训练过程的起点,并根据新问题进行调整。这种用法将迁移学习视为一种权重初始化方案。当第一个相关问题比目标问题拥有更多标记数据,并且问题结构的相似性在两种情况下都有用时,这可能会很有用。
……目标是利用第一种设置的数据来提取在学习过程中甚至在第二种设置中直接进行预测时可能有用信息。
— 第 538 页,《深度学习》,2016。
或者,网络权重可能不会根据新问题进行调整,而只有在重用层之后的层才能训练以解释它们的输出。这种用法将迁移学习视为一种特征提取方案。这种方法的一个例子是重用为照片分类训练的深度卷积神经网络模型,作为开发照片字幕模型时的特征提取器。
这些用法的变体可能包括最初不在新问题上训练模型权重,但稍后使用小的学习率对所有学习到的模型权重进行微调。
想要通过深度学习获得更好的结果吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
Blobs 多类别分类问题
我们将使用一个小的多类别分类问题作为演示迁移学习的基础。
scikit-learn 类提供了 make_blobs() 函数,可用于创建具有指定样本数、输入变量、类别和类别内样本方差的多类别分类问题。
我们可以将问题配置为有两个输入变量(代表点的 *x* 和 *y* 坐标)以及每个组内点的 2.0 标准差。我们将使用相同的随机状态(伪随机数生成器的种子)来确保我们始终获得相同的数据点。
1 2 |
# 生成二维分类数据集 X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=1) |
结果是我们可以建模的数据集的输入和输出元素。
“random_state” 参数可以更改以产生问题的不同版本(不同的聚类中心)。我们可以使用它来生成两个不同问题的样本:在一个问题上训练模型,并重用权重以更好地学习第二个问题的模型。
具体来说,我们将 *random_state=1* 称为问题 1,将 *random_state=2* 称为问题 2。
- 问题 1。具有两个输入变量和三个类别的 Blobs 问题,*random_state* 参数设置为一。
- 问题 2。具有两个输入变量和三个类别的 Blobs 问题,*random_state* 参数设置为二。
为了了解问题的复杂性,我们可以在二维散点图上绘制每个点,并根据类别值对每个点进行着色。
完整的示例如下所示。
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 |
# 问题 1 和 2 的 blobs 多类别分类问题图 from sklearn.datasets import make_blobs from numpy import where from matplotlib import pyplot # 使用给定的随机种子生成 blobs 问题的样本 def samples_for_seed(seed): # 生成样本 X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed) 返回 X, y # 绘制按类别值着色的点散点图 def plot_samples(X, y, classes=3): # 绘制每个类别的点 for i in range(classes): # 选择具有每个类别标签的点索引 samples_ix = where(y == i) # 绘制此类别的点,并指定颜色 pyplot.scatter(X[samples_ix, 0], X[samples_ix, 1]) # 生成多个问题 n_problems = 2 for i in range(1, n_problems+1): # 指定子图 pyplot.subplot(210 + i) # 生成样本 X, y = samples_for_seed(i) # 绘制样本的散点图 plot_samples(X, y) # 绘制图形 pyplot.show() |
运行该示例会为问题 1 和问题 2 生成 1000 个样本,并为每个样本创建散点图,按类别值对数据点进行着色。

问题 1 和问题 2 的 Blobs 数据集散点图,具有三个类别,并按类别值对点进行着色
这为迁移学习奠定了良好的基础,因为每个问题版本都有相似的输入数据和相似的尺度,但目标信息不同(例如,聚类中心)。
我们预期,在一种 blobs 问题(例如问题 1)上拟合的模型方面,在为另一种 blobs 问题(例如问题 2)拟合模型时会很有用。
问题 1 的多层感知机模型
在本节中,我们将为问题 1 开发一个多层感知机(MLP)模型,并将模型保存到文件,以便稍后重用权重。
首先,我们将开发一个函数来准备好用于建模的数据集。在调用具有给定随机种子的 make_blobs() 函数(在本例中为问题 1 的一个)后,必须对目标变量进行独热编码,以便我们能够开发一个模型来预测给定样本属于每个目标类别的概率。
然后,可以将准备好的样本分成两半,每部分包含 500 个样本,用于训练集和测试集。下面的 *samples_for_seed()* 函数实现了这一点,它为给定的随机数种子准备数据集,并将训练集和测试集拆分为输入和输出组件。
1 2 3 4 5 6 7 8 9 10 11 |
# 使用给定的随机种子准备 blobs 样本 def samples_for_seed(seed): # 生成样本 X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed) # 对输出变量进行独热编码 y = to_categorical(y) # 分割为训练集和测试集 n_train = 500 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] return trainX, trainy, testX, testy |
我们可以如下调用此函数来准备问题 1 的数据集。
1 2 |
# 准备数据 trainX, trainy, testX, testy = samples_for_seed(1) |
接下来,我们可以定义模型并在训练数据集上拟合它。
模型将需要两个输入,对应于数据中的两个变量。模型将有两个隐藏层,每个隐藏层有五个节点,并使用 ReLU 激活函数。这两个层可能不是必需的,尽管我们对模型学习可以跨问题实例重用的某些深度结构感兴趣。输出层有三个节点,每个节点对应目标变量中的一个类别,并使用 softmax 激活函数。
1 2 3 4 5 |
# 定义模型 model = Sequential() model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(5, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(3, activation='softmax')) |
鉴于该问题是一个多类别分类问题,将最小化分类交叉熵损失函数,并使用具有默认学习率和动量为零的随机梯度下降来学习该问题。
1 2 |
# 编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) |
模型将在训练数据集上训练 100 个 epoch,并将测试集用作训练期间的验证数据集,在每个 epoch 结束时评估两个数据集的性能,以便我们可以绘制学习曲线。
1 |
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0) |
fit_model() 函数将这些元素结合起来,将训练集和测试集作为参数,并返回拟合的模型和训练历史。
1 2 3 4 5 6 7 8 9 10 11 12 |
# 在训练数据集上定义和拟合模型 def fit_model(trainX, trainy, testX, testy): # 定义模型 model = Sequential() model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(5, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(3, activation='softmax')) # 编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # 拟合模型 history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0) return model, history |
我们可以调用此函数并传入准备好的数据集来获得拟合的模型和训练过程中收集的历史记录。
1 2 |
# 在训练数据集上拟合模型 model, history = fit_model(trainX, trainy, testX, testy) |
最后,我们可以总结模型的性能。
可以评估模型在训练集和测试集上的分类准确率。
1 2 3 4 |
# 评估模型 _, train_acc = model.evaluate(trainX, trainy, verbose=0) _, test_acc = model.evaluate(testX, testy, verbose=0) print('Train: %.3f, Test: %.3f' % (train_acc, test_acc)) |
训练期间收集的历史记录可用于创建折线图,显示模型在每个训练 epoch 的训练集和测试集上的损失和分类准确率,从而提供学习曲线。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 绘制训练过程中的损失 pyplot.子图(211) pyplot.title('Loss') pyplot.plot(history.history['loss'], label='train') pyplot.plot(history.history['val_loss'], label='test') pyplot.legend() # 绘制训练期间的准确率 pyplot.子图(212) pyplot.title('Accuracy') pyplot.plot(history.history['accuracy'], label='train') pyplot.plot(history.history['val_accuracy'], label='test') pyplot.legend() pyplot.show() |
summarize_model() 函数下面实现了这一点,它将拟合的模型、训练历史和数据集作为参数,并打印模型性能以及创建模型学习曲线的图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 总结拟合模型的性能 def summarize_model(model, history, trainX, trainy, testX, testy): # 评估模型 _, train_acc = model.evaluate(trainX, trainy, verbose=0) _, test_acc = model.evaluate(testX, testy, verbose=0) print('Train: %.3f, Test: %.3f' % (train_acc, test_acc)) # 绘制训练期间的损失 pyplot.subplot(211) pyplot.title('Loss') pyplot.plot(history.history['loss'], label='train') pyplot.plot(history.history['val_loss'], label='test') pyplot.legend() # 绘制训练期间的准确率 pyplot.subplot(212) pyplot.title('Accuracy') pyplot.plot(history.history['accuracy'], label='train') pyplot.plot(history.history['val_accuracy'], label='test') pyplot.legend() pyplot.show() |
我们可以调用此函数并传入拟合的模型和准备好的数据。
1 2 |
# 评估模型行为 summarize_model(model, history, trainX, trainy, testX, testy) |
在运行结束时,我们可以将模型保存到文件,以便稍后加载它并将其用作一些迁移学习实验的基础。
请注意,将模型保存到文件需要安装 h5py 库。此库可以通过 pip 安装,如下所示
1 |
sudo pip install h5py |
可以通过调用模型上的 save() 函数来保存拟合模型。
1 2 |
# 将模型保存到文件 model.save('model.h5') |
将这些元素组合在一起,下面列出了在问题 1 上拟合 MLP、汇总模型性能并将模型保存到文件的完整示例。
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 |
# 在问题 1 上拟合 MLP 模型并将其保存到文件 from sklearn.datasets import make_blobs from keras.layers import Dense from keras.models import Sequential from keras.optimizers import SGD from keras.utils import to_categorical from matplotlib import pyplot # 使用给定的随机种子准备 blobs 样本 def samples_for_seed(seed): # 生成样本 X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed) # 对输出变量进行独热编码 y = to_categorical(y) # 分割为训练集和测试集 n_train = 500 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] return trainX, trainy, testX, testy # 在训练数据集上定义和拟合模型 def fit_model(trainX, trainy, testX, testy): # 定义模型 model = Sequential() model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(5, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(3, activation='softmax')) # 编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # 拟合模型 history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0) return model, history # 总结拟合模型的性能 def summarize_model(model, history, trainX, trainy, testX, testy): # 评估模型 _, train_acc = model.evaluate(trainX, trainy, verbose=0) _, test_acc = model.evaluate(testX, testy, verbose=0) print('Train: %.3f, Test: %.3f' % (train_acc, test_acc)) # 绘制训练期间的损失 pyplot.subplot(211) pyplot.title('Loss') pyplot.plot(history.history['loss'], label='train') pyplot.plot(history.history['val_loss'], label='test') pyplot.legend() # 绘制训练期间的准确率 pyplot.subplot(212) pyplot.title('Accuracy') pyplot.plot(history.history['accuracy'], label='train') pyplot.plot(history.history['val_accuracy'], label='test') pyplot.legend() pyplot.show() # 准备数据 trainX, trainy, testX, testy = samples_for_seed(1) # 在训练数据集上拟合模型 model, history = fit_model(trainX, trainy, testX, testy) # 评估模型行为 summarize_model(model, history, trainX, trainy, testX, testy) # 将模型保存到文件 model.save('model.h5') |
运行示例会拟合并评估模型的性能,打印训练集和测试集上的分类准确率。
注意:您的结果可能会有所不同,这取决于算法或评估过程的随机性,或者数值精度的差异。考虑运行几次示例并比较平均结果。
在这种情况下,我们可以看到模型在问题 1 上表现良好,在训练集和测试集上的分类准确率均约为 92%。
1 |
训练:0.916,测试:0.920 |
还会创建一个图表,总结模型的学习曲线,显示每个训练 epoch 结束时模型在训练集(蓝色)和测试集(橙色)上的损失(顶部)和准确率(底部)。
您的图可能不完全相同,但预期会显示相同的总体行为。如果不一致,请尝试运行几次示例。
在这种情况下,我们可以看到模型相对较快且很好地学习了问题,大约在 40 个 epoch 收敛,并且在两个数据集上都保持相对稳定。

问题 1 上 MLP 在训练集和测试集上的损失和准确率学习曲线
现在我们已经看到了如何为问题 1 的 blobs 开发一个独立的 MLP,我们可以看看如何为问题 2 做同样的事情,它可以用作基线。
问题 2 的独立 MLP 模型
上一节中的示例可以更新为在问题 2 上拟合 MLP 模型。
首先,了解独立模型在问题 2 上的性能和学习动态很重要,因为这将提供一个性能基线,用于与使用迁移学习在同一问题上拟合的模型进行比较。
只需要一项更改,即将 samples_for_seed() 的调用更改为使用种子为 2 的伪随机数生成器,而不是 1。
1 2 |
# 准备数据 trainX, trainy, testX, testy = samples_for_seed(2) |
为完整起见,下面列出了包含此更改的完整示例。
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 |
# 在问题 2 上拟合 MLP 模型并将其保存到文件 from sklearn.datasets import make_blobs from keras.layers import Dense from keras.models import Sequential from keras.optimizers import SGD from keras.utils import to_categorical from matplotlib import pyplot # 使用给定的随机种子准备 blobs 样本 def samples_for_seed(seed): # 生成样本 X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed) # 对输出变量进行独热编码 y = to_categorical(y) # 分割为训练集和测试集 n_train = 500 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] return trainX, trainy, testX, testy # 在训练数据集上定义和拟合模型 def fit_model(trainX, trainy, testX, testy): # 定义模型 model = Sequential() model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(5, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(3, activation='softmax')) # 编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # 拟合模型 history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0) return model, history # 总结拟合模型的性能 def summarize_model(model, history, trainX, trainy, testX, testy): # 评估模型 _, train_acc = model.evaluate(trainX, trainy, verbose=0) _, test_acc = model.evaluate(testX, testy, verbose=0) print('Train: %.3f, Test: %.3f' % (train_acc, test_acc)) # 绘制训练期间的损失 pyplot.subplot(211) pyplot.title('Loss') pyplot.plot(history.history['loss'], label='train') pyplot.plot(history.history['val_loss'], label='test') pyplot.legend() # 绘制训练期间的准确率 pyplot.subplot(212) pyplot.title('Accuracy') pyplot.plot(history.history['accuracy'], label='train') pyplot.plot(history.history['val_accuracy'], label='test') pyplot.legend() pyplot.show() # 准备数据 trainX, trainy, testX, testy = samples_for_seed(2) # 在训练数据集上拟合模型 model, history = fit_model(trainX, trainy, testX, testy) # 评估模型行为 summarize_model(model, history, trainX, trainy, testX, testy) |
运行示例会拟合并评估模型的性能,打印训练集和测试集上的分类准确率。
注意:您的结果可能会有所不同,这取决于算法或评估过程的随机性,或者数值精度的差异。考虑运行几次示例并比较平均结果。
在这种情况下,我们可以看到模型在问题 2 上的表现尚可,但不如在问题 1 上看到的那样好,在训练集和测试集上的分类准确率都约为 79%。
1 |
训练:0.794,测试:0.794 |
还会创建一个图表来总结模型的学习曲线。您的图可能不完全相同,但预期会显示相同的总体行为。如果不一致,请尝试运行几次示例。
在这种情况下,我们可以看到模型比上一节中的问题 1 收敛得更慢。这表明此版本的问题可能稍微更具挑战性,至少对于所选的模型配置而言。

问题 2 上 MLP 在训练集和测试集上的损失和准确率学习曲线
现在我们有了问题 2 上 MLP 的性能和学习动态基线,我们可以看看迁移学习的添加如何影响此问题上的 MLP。
问题 2 的迁移学习 MLP
在问题 1 上拟合的模型可以被加载,其权重可以用作在问题 2 上拟合的模型的初始权重。
这是一种迁移学习,其中在不同但相关的问题上学习被用作一种权重初始化方案。
这要求更新 fit_model() 函数以加载模型并将其重新拟合到问题 2 的示例上。
可以使用 Keras 的 load_model() 函数加载保存在 ‘model.h5’ 中的模型。
1 2 |
# 加载模型 model = load_model('model.h5') |
加载后,模型可以像往常一样进行编译和拟合。
下面列出了包含此更改的更新后的 fit_model()。
1 2 3 4 5 6 7 8 9 |
# 加载模型并重新拟合到训练数据集 def fit_model(trainX, trainy, testX, testy): # 加载模型 model = load_model('model.h5') # 编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # 重新拟合模型 history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0) return model, history |
我们预计,使用在不同但相关问题上拟合的模型权重 Thus 的模型将在学习曲线上更快地学习问题,并可能产生更低的泛化误差,尽管这些方面将取决于问题和模型的选择。
为完整起见,下面列出了包含此更改的完整示例。
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 |
# 在问题 2 上使用 MLP 模型进行迁移学习 from sklearn.datasets import make_blobs from keras.layers import Dense from keras.models import Sequential from keras.optimizers import SGD from keras.utils import to_categorical from keras.models import load_model from matplotlib import pyplot # 使用给定的随机种子准备 blobs 样本 def samples_for_seed(seed): # 生成样本 X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed) # 对输出变量进行独热编码 y = to_categorical(y) # 分割为训练集和测试集 n_train = 500 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] return trainX, trainy, testX, testy # 加载模型并重新拟合到训练数据集 def fit_model(trainX, trainy, testX, testy): # 加载模型 model = load_model('model.h5') # 编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # 重新拟合模型 history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0) return model, history # 总结拟合模型的性能 def summarize_model(model, history, trainX, trainy, testX, testy): # 评估模型 _, train_acc = model.evaluate(trainX, trainy, verbose=0) _, test_acc = model.evaluate(testX, testy, verbose=0) print('Train: %.3f, Test: %.3f' % (train_acc, test_acc)) # 绘制训练期间的损失 pyplot.subplot(211) pyplot.title('Loss') pyplot.plot(history.history['loss'], label='train') pyplot.plot(history.history['val_loss'], label='test') pyplot.legend() # 绘制训练期间的准确率 pyplot.subplot(212) pyplot.title('Accuracy') pyplot.plot(history.history['accuracy'], label='train') pyplot.plot(history.history['val_accuracy'], label='test') pyplot.legend() pyplot.show() # 准备数据 trainX, trainy, testX, testy = samples_for_seed(2) # 在训练数据集上拟合模型 model, history = fit_model(trainX, trainy, testX, testy) # 评估模型行为 summarize_model(model, history, trainX, trainy, testX, testy) |
运行示例会拟合并评估模型的性能,打印训练集和测试集上的分类准确率。
注意:您的结果可能会有所不同,这取决于算法或评估过程的随机性,或者数值精度的差异。考虑运行几次示例并比较平均结果。
在这种情况下,我们可以看到模型实现了更低的泛化误差,在问题 2 的测试集上达到了约 81% 的准确率,而独立模型约为 79% 的准确率。
1 |
训练:0.786,测试:0.810 |
还会创建一个图表来总结模型的学习曲线。您的图可能不完全相同,但预期会显示相同的总体行为。如果不一致,请尝试运行几次示例。
在这种情况下,我们可以看到模型确实显示出相似的学习曲线,尽管我们确实看到了测试集(橙色线)的学习曲线的明显改进,无论是在更早的时期(从 epoch 20 开始)更好的性能,还是在性能上超过了模型在训练集上的性能。

问题 2 上带有迁移学习的 MLP 在训练集和测试集上的损失和准确率学习曲线
我们只查看了独立 MLP 模型和具有迁移学习的 MLP 模型的单次运行。
神经网络算法是随机的,因此需要对多次运行的性能进行平均,以确定观察到的行为是真实的还是统计上的巧合。
问题 2 模型对比
为了确定使用迁移学习用于 blobs 多类分类问题是否具有实际效果,我们必须重复每个实验多次,并分析重复实验的平均性能。
我们将比较在问题 2 上训练的独立模型与使用迁移学习的模型在 30 次重复实验中的平均性能。
此外,我们将研究保持某些层权重固定是否能提高模型性能。
在问题 1 上训练的模型有两个隐藏层。通过保持第一个隐藏层或第一个和第二个隐藏层固定,没有可更改权重的层将充当特征提取器,并可能提供使学习问题 2 更容易的特征,从而影响学习速度和/或模型在测试集上的准确率。
作为第一步,我们将简化 fit_model() 函数以拟合模型并丢弃任何训练历史,以便我们可以专注于训练模型的最终准确率。
1 2 3 4 5 6 7 8 9 10 11 12 |
# 在训练数据集上定义和拟合模型 def fit_model(trainX, trainy): # 定义模型 model = Sequential() model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(5, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(3, activation='softmax')) # 编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # 拟合模型 model.fit(trainX, trainy, epochs=100, verbose=0) return model |
接下来,我们可以开发一个函数,该函数将对训练数据集上的问题 2 重复拟合一个新的独立模型,并评估测试集上的准确率。
下面的 eval_standalone_model() 函数实现了这一点,它将训练集和测试集作为参数,以及重复次数,并返回测试数据集上模型的准确率分数列表。
1 2 3 4 5 6 7 8 9 10 |
# 独立模型的重复评估 def eval_standalone_model(trainX, trainy, testX, testy, n_repeats): scores = list() for _ in range(n_repeats): # 在训练数据集上定义并拟合一个新模型 model = fit_model(trainX, trainy) # 在测试数据集上评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) scores.append(test_acc) return scores |
总结此函数返回的准确率分数分布将有助于了解所选独立模型在问题 2 上的表现。
1 2 3 |
# 独立模型的重复评估 standalone_scores = eval_standalone_model(trainX, trainy, testX, testy, n_repeats) print('Standalone %.3f (%.3f)' % (mean(standalone_scores), std(standalone_scores))) |
接下来,我们需要一个用于评估迁移学习模型的等效函数。
在每个循环中,必须从文件加载在问题 1 上训练的模型,将其拟合到问题 2 的训练数据集,然后评估问题 2 的测试集。
此外,我们将配置加载模型中的 0、1 或 2 个隐藏层以保持固定。保持 0 个隐藏层固定意味着模型中的所有权重将在学习问题 2 时被调整,将迁移学习用作权重初始化方案。而保持两个(2 个)隐藏层固定意味着在训练期间只有模型的输出层将被调整,将迁移学习用作特征提取方法。
下面的 eval_transfer_model() 函数实现了这一点,它将问题 2 的训练和测试数据集作为参数,以及在加载的模型中要保持固定的隐藏层数和重复实验的次数。
该函数返回一个测试准确率分数列表,对该分布进行汇总将有助于合理地了解所选类型的迁移学习模型在问题 2 上的表现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 迁移学习模型的重复评估 def eval_transfer_model(trainX, trainy, testX, testy, n_fixed, n_repeats): scores = list() for _ in range(n_repeats): # 加载模型 model = load_model('model.h5') # 将层权重标记为固定或不可训练 for i in range(n_fixed): model.layers[i].trainable = False # 重新编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # 在训练数据集上拟合模型 model.fit(trainX, trainy, epochs=100, verbose=0) # 在测试数据集上评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) scores.append(test_acc) return scores |
我们可以通过在循环中将 n_fixed 设置为 0、1、2 来反复调用此函数,并在此过程中汇总性能;例如
1 2 3 4 5 |
# 迁移学习模型的重复评估,固定层变化 n_fixed = 3 for i in range(n_fixed): scores = eval_transfer_model(trainX, trainy, testX, testy, i, n_repeats) print('Transfer (fixed=%d) %.3f (%.3f)' % (i, mean(scores), std(scores))) |
除了报告每个模型的平均值和标准差外,我们还可以收集所有分数并创建箱须图来汇总和比较模型分数的分布。
将所有这些元素组合在一起,下面列出了完整的示例。
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 |
# 比较独立 MLP 模型性能与迁移学习 from sklearn.datasets import make_blobs from keras.layers import Dense from keras.models import Sequential from keras.optimizers import SGD from keras.utils import to_categorical from keras.models import load_model from matplotlib import pyplot from numpy import mean from numpy import std # 使用给定的随机种子准备 blobs 样本 def samples_for_seed(seed): # 生成样本 X, y = make_blobs(n_samples=1000, centers=3, n_features=2, cluster_std=2, random_state=seed) # 对输出变量进行独热编码 y = to_categorical(y) # 分割为训练集和测试集 n_train = 500 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] return trainX, trainy, testX, testy # 在训练数据集上定义和拟合模型 def fit_model(trainX, trainy): # 定义模型 model = Sequential() model.add(Dense(5, input_dim=2, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(5, activation='relu', kernel_initializer='he_uniform')) model.add(Dense(3, activation='softmax')) # 编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # 拟合模型 model.fit(trainX, trainy, epochs=100, verbose=0) return model # 独立模型的重复评估 def eval_standalone_model(trainX, trainy, testX, testy, n_repeats): scores = list() for _ in range(n_repeats): # 在训练数据集上定义并拟合一个新模型 model = fit_model(trainX, trainy) # 在测试数据集上评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) scores.append(test_acc) 返回 分数 # 迁移学习模型的重复评估 def eval_transfer_model(trainX, trainy, testX, testy, n_fixed, n_repeats): scores = list() for _ in range(n_repeats): # 加载模型 model = load_model('model.h5') # 将层权重标记为固定或不可训练 for i in range(n_fixed): model.layers[i].trainable = False # 重新编译模型 model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # 在训练数据集上拟合模型 model.fit(trainX, trainy, epochs=100, verbose=0) # 在测试数据集上评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) scores.append(test_acc) 返回 分数 # 准备问题 2 的数据 trainX, trainy, testX, testy = samples_for_seed(2) n_repeats = 30 dists, dist_labels = list(), list() # 独立模型的重复评估 standalone_scores = eval_standalone_model(trainX, trainy, testX, testy, n_repeats) print('Standalone %.3f (%.3f)' % (mean(standalone_scores), std(standalone_scores))) dists.append(standalone_scores) dist_labels.append('standalone') # 迁移学习模型的重复评估,固定层变化 n_fixed = 3 for i in range(n_fixed): scores = eval_transfer_model(trainX, trainy, testX, testy, i, n_repeats) print('Transfer (fixed=%d) %.3f (%.3f)' % (i, mean(scores), std(scores))) dists.append(scores) dist_labels.append('transfer f='+str(i)) # 分数分布的箱须图 pyplot.boxplot(dists, labels=dist_labels) pyplot.show() |
运行示例首先报告每个模型在测试数据集上的分类准确率的平均值和标准差。
注意:您的结果可能会有所不同,这取决于算法或评估过程的随机性,或者数值精度的差异。考虑运行几次示例并比较平均结果。
在这种情况下,我们可以看到独立模型在问题 2 上达到了约 78% 的准确率,标准差很大,为 10%。相比之下,所有迁移学习模型的分布范围都小得多,从约 0.05% 到 1.5%。
测试准确率分数的标准差差异表明了迁移学习可以为模型带来的稳定性,减少了通过随机学习算法引入的最终模型性能的方差。
通过比较模型的平均测试准确率,我们可以看到,使用模型作为权重初始化方案的迁移学习(fixed=0)比独立模型取得了更好的性能,准确率约为 80%。
保持所有隐藏层固定(fixed=2)并将其作为特征提取方案,平均而言,性能比独立模型差。这表明在此情况下,该方法过于严格。
有趣的是,当我们保持第一个隐藏层固定(fixed=1)而第二个隐藏层适应问题时,我们看到了最佳性能,测试分类准确率约为 81%。这表明在这种情况下,问题受益于迁移学习的特征提取和权重初始化属性。
查看最后一种方法的結果与使用随机数重新初始化第二个隐藏层(以及可能的输出层)权重的相同模型进行比较可能很有趣。此比较将证明迁移学习的特征提取属性本身或特征提取和权重初始化属性兼而有其益。
1 2 3 4 |
独立 0.787 (0.101) 迁移 (fixed=0) 0.805 (0.004) 迁移 (fixed=1) 0.817 (0.005) 迁移 (fixed=2) 0.750 (0.014) |
创建了一个图表,显示了四个箱须图。盒子显示了每个数据分布的中间 50%,橙色线显示了中位数,点显示了异常值。
独立模型的箱须图显示了许多异常值,表明平均而言,模型表现良好,但也有可能表现非常差。
相反,我们看到迁移学习模型的行为更加稳定,表现出更紧密的性能分布。

箱须图比较独立模型和迁移学习模型通过测试集准确率在 Blobs 多类分类问题上的表现
扩展
本节列出了一些您可能希望探索的扩展本教程的想法。
- 反向实验。训练并保存问题 2 的模型,看看它在问题 1 的迁移学习中是否能提供帮助。
- 添加隐藏层。更新示例以保持两个隐藏层固定,但在输出层之前在固定层之后添加一个具有随机初始化权重的新的隐藏层,并比较性能。
- 随机初始化层。更新示例以随机初始化第二个隐藏层和输出层的权重,并比较性能。
如果您探索了这些扩展中的任何一个,我很想知道。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
文章
论文
- 深度学习表征的无监督和迁移学习, 2011.
- 大规模情感分类的域自适应:一种深度学习方法, 2011.
- 学习第 n 件事是否比学习第一件事更容易?, 1996.
书籍
- 第 15.2 节 迁移学习与域自适应,深度学习,2016。
文章
总结
在本教程中,您将学习如何使用 Keras 在 Python 中使用迁移学习来提高深度学习神经网络的性能。
具体来说,你学到了:
- 迁移学习是一种重用在相关预测建模问题上训练过的模型的方法。
- 迁移学习可以用作权重初始化方案或特征提取方法,以加速神经网络的训练。
- 如何使用迁移学习来提高 MLP 在多类别分类问题上的性能。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
嗨,Jason,
谢谢您的教程。
1-为什么您必须在加载模型后(重新)编译用于迁移学习的模型?(设置相同)
2-使用迁移学习,我们不是应该期望损失和准确率的收敛速度更快吗?在您的图中似乎确实如此。
Keras 倾向于在使用模型之前对其进行编译,以便它能够确定要应用的变换的形状。
这可能取决于问题类型。
我也不明白这一点,如果你必须编译,并且再次将先前模型拟合到数据上,你如何使用先前的权重?
你只是将先前的权重用作第二个模型的初始权重吗?
是的。来自先前模型的权重可按原样使用并冻结(不更改),或用作起点。我们探讨了这两种方法。
Jason 您好!很棒的教程。您能否进一步解释一下您是如何得出箱须图和异常值的?我不太明白具体哪些数据会导致这种表示(例如,在这里什么是异常值),以及这些数据是如何生成的。感谢您的关注。
是的,数据是用箱须图绘制的
https://matplotlib.net.cn/3.1.1/api/_as_gen/matplotlib.pyplot.boxplot.html
有关箱须图工作原理的更多信息
https://en.wikipedia.org/wiki/Box_plot
您好!感谢您的回答。抱歉我没说清楚,但我知道什么是箱须图。我想了解的是正在绘制什么数据。我们如何为每个模型获得多个准确率?
每个模型都会被评估多次。您看到的是由于学习算法的随机性以及
感谢您的教程,Jason!
顺便说一句,我找不到“第 5.2 节 迁移学习与域自适应,深度学习,2016”作为您的书籍引用,但我可以在“15.2”而不是“5.2”处找到该节。请检查!
是的,那是一个错字,谢谢,已修复!
布朗利博士,您好,
我阅读了这篇帖子,但仍然有一些问题。我本人正在应用这种迁移学习方法,但由于一些疑问而无法继续。
我有一个训练好的多类文本分类模型,包括 Embedding、Bi-LSTM、输出层(Dense 层)。现在我想添加更多数据和更多类。我的问题是如何在这种情况下进行迁移学习。我冻结了 Embedding 层和 Bi LSTM 层的权重,但没有冻结输出层的权重,因此其权重是随机初始化的。我没有取得任何好结果。准确率看起来不错,但结果毫无意义。我也找不到关于 NLP 迁移学习的好帖子,其中大多数是关于使用 Imagenet、Mobelnet 等分类器的图像。
如果您有任何建议,请告诉我。感谢您的帮助。
谢谢!
Rishabh Sahrawat
也许先在一个数据集上拟合模型,然后迁移到新数据集,并使用非常小的学习率来调整模型以适应新数据?
Hi Jason,又一篇很棒的文章。我们可以通过向训练好的模型添加新层来应用迁移学习吗?如果可以,如何添加?您能否在此提供帮助。
谢谢!!
是的。您可以使用 Keras Functional API 将新层添加到现有模型。
在通过迁移学习训练数据集时,loss 和 val_loss 降低到大约 25 并且不再变化。我能问一下原因是什么以及如何解决吗?
是的,这里的教程将帮助您诊断学习动态并提供改进学习的技术。
https://machinelearning.org.cn/start-here/#better
你好,
我一直想知道为什么预训练模型的权重文件会那么大!我的意思是,归根结底它们只是包含大量参数的数字文件!但是为什么这些预训练的权重文件通常那么大(>250 MB)?
祝好!
好问题。
也许只是有很多 64 位数字。
嗨 Jason
精彩的迁移学习教程!祝贺!
我的问题
1.) 您在教程介绍中提到了微调。但在后续的研究案例中不再提及。
据我理解,任何时候您加载来自训练问题 1 的模型权重,以解决问题 2,并允许解冻迁移模型的一些层……您都在进行某种“神经外科”的微调。所以您应该提及它。您是否同意,即使您不减慢学习率或应用其他技术,您也在进行微调?
2.) 在您的教程中不是这种情况,但试想一下您正在持续训练一个模型并将结果以训练权重的形式保存。
每次您获得新的或增加您的(训练)数据集……您的策略会是什么……只用新的“增量”数据集训练模型,但以某种微调(慢学习率、使用平滑 SGD 作为优化器等)的方式加载先前训练的模型,但只应用于新的增量数据集或整个训练数据集,或者只是从头开始训练整个数据集(即使用随机权重初始化等)?
这三种方法中,您会选择哪一种作为最佳策略?
非常感谢您的宝贵课程!
谢谢。
是的,尽管“微调”通常有特定含义——在更多数据上训练预训练权重并使用非常小的学习率。
与其猜测,不如使用对照实验来发现最适合数据/域的更新策略。
亲爱的 Jason,
感谢您在这篇文章中所付出的努力。
我想知道微调是否与权重初始化具有相同的含义?
我无法理解微调和权重初始化的区别?
谢谢您的回答。
微调在迁移学习之后进行,用于更改新数据的模型权重。
权重初始化在训练模型之前将权重设置为小的随机值。
尊敬的布朗利博士,
我读了这篇文章,但仍然有些疑问。我直接在我的电脑上运行了你的代码,但得到了不同的结果。
1. 多层感知机模型用于问题1:训练:0.926,测试:0.928
2. 独立MLP模型用于问题2:训练:0.808,测试:0.812
3. 迁移学习MLP用于问题2:训练:0.714,测试:0.770
看起来迁移学习是没用的。
这里我们展示了如何进行迁移学习,具体的应用只是为了理解方法的上下文。
嗨,Jason,
赞赏这篇非常有帮助的文章。
我的问题与model.fit()方法有关。当尝试将预训练模型拟合到新数据时,model.fit()和model.evaluate()有什么区别?
model.fit()不是直接在用旧模型的权重来训练新数据,而是重新训练模型吗?
感谢您的回复。谢谢。
fit() 将会训练模型。
evaluate() 将会使用模型进行预测并计算这些预测的误差。
嗨,Jason,
非常感谢您的精彩教程,
我正试图重现一篇论文中的一些结果,这需要使用你在帖子中描述的权重重用方案,但适用于一个具有单个隐藏层的全连接网络,该网络每次使用不同数量的隐藏单元进行训练!我一直在寻找一种方法,将权重从较小的网络传递到较大的网络,并随机初始化额外的隐藏单元??
那么,有没有一种方法可以将迁移学习应用于一个新网络,而不是具有额外隐藏
层,而是具有额外隐藏单元??
非常感谢您的时间和您所有帖子中提供的帮助。
改变网络的形状在重用权重时很可能会无效。
除非你发挥创意,添加带有随机权重的额外节点,并以某种方式冻结带有预训练权重的节点。可能需要自定义代码。
非常感谢您的精彩帖子。我有两个问题
1. 我能否使用为分类问题训练的模型,通过迁移学习技术将其作为回归问题的预训练模型?如果可以,如何操作?
2. 我训练了几个模型并将它们集成起来成为一个新模型。我能否将这个新模型作为预训练模型进行迁移学习?
非常感谢
不客气!
可以。保存模型,加载模型,在新的数据上训练部分或全部层。上面的教程将直接帮助你入门。
是的,如果你愿意,可以使用模型的集成进行迁移学习。
嗨,Jason,
在你的代码中,fixed = 0 实际上意味着你固定了第一个层,因为索引是从0开始的。
所以,当fixed = 0 时,你基本上锁定了你的神经网络的第一层。我说的对吗?
是的,0是第一个隐藏层。不是输入层。
那么,fixed = 0 也是特征提取方案而不是权重初始化方案的一部分,对吧?
不完全是,fixed=0意味着所有权重都会被更新。
Fixed = 0 意味着第一个隐藏层的所有权重都被固定。其余隐藏层的权重会被更新。对吗?
根据教程
嗨,Jason,
感谢这篇有趣的教程。我的问题是,为什么你认为迁移学习对于这个带有感知机模型(MLP)的简单问题有效?
我有点理解为什么迁移学习在图像任务上有效,因为较低的卷积层学习基本的图像特征和模式,这些对于任何类型的图像任务都仍然有用。因此,我们可以只重新训练最后几层,或者在后面添加几个全连接层来适应特定任务。
在这个例子中,你认为迁移学习是通过作为一种正则化来提高结果的吗?
这里的例子只是为了帮助解释迁移学习的概念。总的来说,深度学习有许多感知机层。通常前几层是用来从输入中提取有用特征的。如果你的输入是相似类型的,我们可以预期特征是相同的,即使后面的层会以不同的方式使用它们。这就是迁移学习有帮助的原因。
您好,尊敬的Jason博士。我有一个关于CNN准确性的问题。我有一个多类别数据集,有10个类别,是关于一个零售网站的商品。每个类别中有许多不同的商品,基于一个类别,例如相机、笔记本电脑、电池都属于类别1……这个包含许多具有一些共同属性的不同事物的排序
是否会影响问题?我是否应该考虑一个特殊的网络或改变数据集的某些东西?
我尝试了许多网络……正则化技术……数据增强……但我无法达到高验证准确率……我得到了两种结果:1)训练准确率高……验证准确率低(过拟合)和2)训练和验证准确率均为50%……我无法达到更高的
准确率。另外,非常感谢您提供的这个很棒的网站,我从您那里学到了很多东西。致以最诚挚的问候。
你好 Sina……感谢您的反馈和好话!虽然我无法直接谈论您的具体应用,但总的来说,如果数据经过归一化并且不被视为“时间序列”数据,顺序不应该是一个主要问题。如果数据被视为时间序列,那么它绝对很重要,您甚至可以通过使用双向LSTM来获得进一步的优势。
https://machinelearning.org.cn/develop-bidirectional-lstm-sequence-classification-python-keras/
你好,再次感谢你的回复……也许我在问题中用了错误的词。
主要问题是,与其他可用数据集相比,我的数据集的每个类别中都有多种商品,正如我之前所说。
而且我认为还有一些噪声。例如,一些不相关的图像混在每个类别中。
我正在尝试使用CNN进行图像识别,那么您对我如何提高验证数据的
准确率有什么建议?
有没有办法来处理这个数据集?
另外,这个数据集属于一家公司的竞赛。
我尝试了很多方法……但我发现可能是训练数据集被分类的方式有问题。
感谢您的教程。
但是我们可以将迁移学习应用于像
地震震级预测这样的回归问题吗?如果有人有模型,请帮帮我。
你好 Ewnetu……以下资源可能对您感兴趣
https://link.springer.com/chapter/10.1007/978-3-030-66763-4_4
你好,James,
迁移学习的绝佳入门介绍。感谢您为此教程付出的时间和努力。
我有一个问题。您是否尝试过本帖中提到的迁移技术用于回归任务?迁移学习在图像分类任务中更为常见,而我找不到太多关于迁移学习技术用于回归任务的信息。
据您所知,迁移学习在回归任务上表现如何?
谢谢!
你好 Sandesh……非常感谢!这是一个很好的问题,您说得对,大多数参考资料都考虑了分类。
以下资源可能对您感兴趣
https://arxiv.org/abs/2102.09504
https://www.quora.com/Can-transfer-learning-be-applied-for-regression-tasks-and-are-there-other-ways-of-using-transfer-learning-other-than-through-neural-networks
你好,James,
感谢您提供如此出色的迁移学习教程。我刚接触它,并且正在处理一个回归问题。在我的情况下,输入维度与基础模型不同,我使用的是ANN。请指导我。
你好,James,
感谢您提供如此出色的迁移学习教程。我刚接触它,并且正在处理一个回归问题。在我的情况下,输入维度与基础模型不同,我使用的是ANN。请指导我。
布朗利博士您好,
非常感谢您提供如此出色的迁移学习教程。我目前正在处理迁移学习的回归任务。我的问题是我的输入维度与基础模型不同。您能帮我解决这个问题吗?
你好 Shaggy……非常欢迎!以下资源是迁移学习应用的一个很好的起点
https://machinelearning.org.cn/transfer-learning-for-deep-learning/
嗨,Jason,
感谢您精彩的解释。我想问一下,您冻结MLP的第0、1、2层的部分,您是否选择了要冻结的第一、第二和第三层,而不是根本不冻结、冻结一层和冻结两层?
另外,即使我们冻结了所有层,为什么会有少许方差呢?冻结层意味着不再有适应,没有权重随机化,没有SGD,那么方差从何而来?
非常感谢
你好 Apollonia……非常欢迎!以下资源可能对您感兴趣。
https://analyticsindiamag.com/what-does-freezing-a-layer-mean-and-how-does-it-help-in-fine-tuning-neural-networks/