Python 单样本学习实用指南:1~6 全

2023-04-27 15:25:40 浏览数 (1)

第一部分:单样本学习简介

深度学习给制造业带来了重大变化,无论是制造业,医疗还是人力资源。 通过这一重大革命和概念验证,几乎每个行业都在尝试调整其业务模型以适应深度学习,但是它有一些主要要求,可能并不适合每个业务或行业。 阅读本节后,您将对深度学习的优缺点有适当的了解。

本节包括以下章节:

  • 第 1 章,“单样本学习简介”

一、单样本学习简介

人们可以通过少量示例学习新事物。 当受到刺激时,人类似乎能够快速理解新概念,然后在将来认识到这些概念的变体。 孩子可以从一张图片中学会识别狗,但是机器学习系统需要大量示例来学习狗的特征并在将来识别它们。 作为一个领域,机器学习在各种任务(例如分类和 Web 搜索以及图像和语音识别)上都取得了巨大的成功。 但是,这些模型通常在没有大量数据(示例)可供学习的情况下表现不佳。 本书的主要动机是使用很少的示例来训练模型,而无需进行大量的重新训练就能够将其概括为不熟悉的类别。

深度学习在机器学习的发展中发挥了重要作用,但它也需要大量的数据集。 不同的技术(例如正则化)可以减少在低数据环境中的过拟合,但不能解决较少的训练示例所固有的问题。 此外,大型数据集导致学习缓慢,需要使用梯度下降法进行许多权重更新。 这主要是由于 ML 算法的参数方面,在该方面需要慢慢学习训练示例。 相反,许多已知的非参数模型(例如最近邻居)不需要任何训练,但是表现取决于有时任意选择的距离度量(例如 L2 距离)。 单样本学习是计算机视觉中的对象分类问题。 尽管大多数基于 ML 的对象分类算法都需要数百或数千张图像和非常大的数据集进行训练,但是单样本学习的目的是从一个或仅几个训练图像中学习有关对象类别的信息。 在本章中,我们将学习单样本学习的基础知识,并探索其实际应用。

本章将涵盖以下主题:

  • 人脑概述
  • 机器学习-历史概述
  • 单样本学习-概述
  • 设置环境
  • 编码练习

技术要求

需要使用以下库来学习和执行本章中的项目:

  • Python
  • Anaconda
  • Jupyter 笔记本
  • PyTorch
  • Matplotlib
  • Scikit-Learn

您可以在本书的 GitHub 存储库中找到本章的代码文件。

人脑概述

自文明开始以来,人脑一直是研究的主题。 如果我们研究孩子的成长,我们会发现随着他们的成长,他们的学习能力也会提高。 首先,他们了解食物,然后学习识别面孔。 每当孩子学到东西时,信息就会被编码到大脑的某个部分。 尽管如此,真正的问题仍然存在,信息如何存储在我们的大脑中? 为什么有些信息被硬编码,而其他信息却容易被忘记?

人脑如何学习

关于大脑如何训练自己以处理数据的大多数信息是未知的,但是有各种理论可以对其进行探索。 如下图所示,如果我们查看大脑神经元的结构,则神经元的工作方式类似于收集器,其中它通过树突从其他神经元收集信号。 一旦信号变强,神经元就会通过称为轴突的细链向附近的神经元发出电信号。 在该网络的末端,突触将信号活动转换为激发活动并激活连接的神经元。 大脑神经元通过改变突触的有效性来学习将信号发送到大脑的不同部分,类似于人工神经网络中某些神经元的权重变为接近零的方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a4p8Nc9s-1681785569470)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/30194677-af4a-47bf-9e51-058911b43b19.png)]

有许多理论表明,神经元之间的紧密连接会增加人类的学习能力。 反过来,许多神经科学家认为,随着通过学习和刺激来更多地利用大脑,就会形成密集的树突状连接。 因此,随着学习的越来越多,我们变得越来越聪明。

比较人类神经元和人工神经元

尽管人类神经元一直是创建人工神经网络的灵感,但它们在多种方式上却有所不同。 研究人员正在尝试通过尝试不同的激活(激励)函数和非线性系统来弥合这些差距。 与我们的大脑具有收集和传输从我们的感官接收到的信息的神经元的方式类似,神经网络也由多层(一组神经元)组成,这些层通过跨层传输信息来学习任务。 在某些情况下,我们可以说人造神经元的工作方式类似于大脑中存在的神经元。 让我们看下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TVQiEmkX-1681785569473)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/571d3436-3f5f-46aa-9057-efe3e52dd3c8.png)]

如上图所示,信息流经每个连接,每个连接都有特定的权重,该权重控制数据流。 如果将人脑神经元的活动与人工神经网络进行比较,我们将看到,只要为任务创建神经网络,就如同创建新的脑神经元。 如果环顾四周,我们已经开始依靠计算机做出决策,例如在信用卡欺诈,垃圾邮件/非垃圾邮件和推荐系统方面。 就像我们为周围的小任务创造了新的大脑。 仍然存在问题,人与人工神经网络之间有什么区别? 让我们找出:

  • 主要区别之一是所需的学习数据量。 要学习神经网络,我们需要大量数据,而人脑可以学习的数据更少。 如果我们希望拥有与人脑相似的神经网络,则需要改进现有的优化算法。
  • 另一个关键区别是速度。 通常,神经网络比人类更快地处理数据和信息。

机器学习–历史概述

机器学习是一个程序,在给定任务(损失函数)的情况下,可以通过经验(训练数据)进行学习。 凭着经验,该程序将学会按照给定的标准执行给定的任务。 在 1960 年代,机器学习主要集中于创建不同形式的数据预处理过滤器。 随着图像过滤器的引入,重点逐渐转向计算机视觉,并在 1990 年代和 2000 年代在这一领域进行了重大研究。 在开发了传统机器学习算法方面的一些稳定性之后,研究人员转向了概率领域,因为随着高维数据的引入,它变得更有希望。 深度学习在 2012 年赢得 ImageNet 挑战赛时便开始蓬勃发展,并且自此在数据科学领域中发挥了重要作用。

机器学习可以分为两类:

  • 参数:使用给定训练数据(例如逻辑回归,支持向量机和神经网络)的算法调整数学或统计模型中的参数,即可完成学习。
  • 非参数:通过存储训练数据(记忆)并执行一些降维映射来完成学习,例如, k 最近邻kNN)和决策树。

由于学习参数的要求,参数方法通常需要大量数据。 顺便说一句,如果我们有大量的数据集,则最好使用参数方法,因为非参数方法通常需要存储数据并针对每个查询对其进行处理。

机器学习和深度学习中的挑战

机器学习和深度学习已经彻底改变了计算机科学行业,但它们各有利弊。 我们当前方法面临的一些常见挑战如下:

  • 数据收集:为每个类别收集足够的相关数据以供机器学习非常费力。
  • 数据标记:通常,标记数据需要专家或由于隐私,安全或道德问题而无法进行。
  • 硬件限制:由于数据量大,而且参数模型大,因此训练它们需要昂贵的硬件(GPU 和 TPU)。
  • 结果分析:尽管有某些提供分析参数的开源库,但了解结果也是一项重大挑战。

除了这些挑战之外,机器学习在处理特征选择和高维数据方面也面临挑战。

在下一节中,我们将介绍单样本学习,并学习如何尝试解决机器学习和深度学习所面临的挑战。

单样本学习概述

单样本学习可以看作是一种类似于人类学习方式的机器训练方法。 单样本学习是一种在有限的监督数据的帮助下,借助强大的先验知识来学习新任务的方法。 李菲菲博士最早发表的导致图像分类问题精度高的著作可以追溯到 2000 年代-尽管近年来,研究人员在通过不同的深度学习架构和优化算法(例如, 匹配网络,不可知论元学习模型和记忆增强神经网络。 单样本学习在多个行业中都有很多应用,尤其是在医疗和制造业中。 在医学上,当可用数据有限时,例如在治疗罕见疾病时,我们可以使用单样本学习。 而在制造中,我们可以减少人为误差,例如表壳制造中的缺陷。

单样本学习的先决条件

如果我们进一步讨论如何从有限的数据中学习必要的信息,我们将意识到人脑已经受过训练以提取重要信息的神经元。 例如,如果教导孩子球形物体是球,那么他们的大脑也会处理有关球的大小和纹理的信息,也称为物体的过滤器。 因此,对于任何形式的单样本学习,我们都可以说我们至少需要满足以下条件之一:

  • 先前训练过的过滤器和预定架构
  • 正确的数据分布假设
  • 一种确定形式的分类法,用于存储或收集的信息

在某些情况下,我们观察到只能具有非常低的特征提取水平。 在这些情况下,我们只能依靠非参数或概率方法,因为要学习参数,我们需要足够数量的数据。 即使我们以某种方式强迫神经网络几乎不学习任何数据,也将导致过拟合。

在下一部分中,我们将进行简短的编码练习,以了解当我们拥有小的数据集时,简单的非参数 kNN 的表现要优于神经网络。 不幸的是,它在现实世界中可能无法很好地工作,因为我们仍然存在学习良好的特征表示并选择合适的距离函数的问题。

单样本学习的类型

解决单样本学习的方法多种多样。 粗略地说,它们可以分为五个主要类别:

  • 数据扩充方法
  • 基于模型的方法
  • 基于指标的方法
  • 基于优化的方法
  • 基于生成建模的方法

下图显示了单样本学习的类别:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BvRcaoUT-1681785569473)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/7da6c666-88ab-45dc-ada6-3081219fd02c.png)]

数据扩充是深度学习社区中最常用的方法,可以为数据增加变化,增加数据大小并平衡数据。 这是通过在数据中添加某种形式的噪声来实现的。 例如,图像可能会被缩放,平移和旋转。 而在自然语言处理任务中,可能会有同义词替换,随机插入和随机交换。

尽管数据扩充方法在预处理中起着至关重要的作用,但本书不会涉及到该主题。 在本书中,我们将着重于单样本学习的算法方法以及如何实现它们。 我们还将在常用的单样本学习数据集上进行实验,例如 Omniglot 数据集和 Mini ImageNet

设置环境

在本节中,我们将使用以下步骤为我们的编码练习和问题设置虚拟环境:

  1. 通过进入您选择的目录并在 Git Bash 命令行中运行以下命令来克隆存储库:
代码语言:javascript复制
git clone https://github.com/Packt-Publishing/Hands-on-One-Shot-Learning.git
  1. 转到克隆的存储库的Chapter01目录:
代码语言:javascript复制
cd Hands-on-One-Shot-Learning/Chapter01
  1. 然后,打开一个终端并使用以下命令安装 Python 版本 3.6 的 Anaconda,并创建一个虚拟环境:
代码语言:javascript复制
conda create --name environment_name python=3.6

在“步骤 3”和4中,可以将environment_name替换为易于记忆的名称,例如one_shot或您选择的名称。

  1. 使用以下命令激活环境:
代码语言:javascript复制
source activate environment_name
  1. 使用以下命令安装requirements.txt
代码语言:javascript复制
pip install -r requirements.txt
  1. 运行以下命令以打开 Jupyter 笔记本:
代码语言:javascript复制
jupyter notebook

现在我们已经建立了环境,让我们继续进行编码练习。

编码练习

在本节中,我们将探索一种基本的单样本学习方法。 作为人类,我们有一种分层的思维方式。 例如,如果我们看到一些未知的东西,我们会寻找它与我们已经知道的对象的相似性。 同样,在本练习中,我们将使用非参数 kNN 方法查找类。 我们还将其表现与基本神经网络架构进行比较。

kNN – 基本的单样本学习

在本练习中,我们将把 kNN 与拥有少量数据集的神经网络进行比较。 我们将使用从scikit-learn库导入的iris数据集。

首先,我们将首先讨论 kNN 的基础知识。 kNN 分类器是一个非参数分类器,它简单地存储训练数据D,并使用对它的k个最近邻居的多数投票对每个新实例进行分类,并使用任何距离函数来计算 。 对于 kNN,我们需要选择距离函数d和邻居数k

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BnjsJ0Ix-1681785569473)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/16ca7a17-01fc-4139-a2af-1e12225d1e1b.png)]

您还可以在以下 GitHub 链接上引用代码文件。

请按照以下步骤将 kNN 与神经网络进行比较:

  1. 使用以下代码导入此练习所需的所有库:
代码语言:javascript复制
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, accuracy_score
from sklearn.model_selection import cross_val_score
from sklearn.neural_network import MLPClassifier
  1. 导入iris数据集:
代码语言:javascript复制
# import small dataset
iris = datasets.load_iris()
X = iris.data
y = iris.target
  1. 为了确保我们使用的数据集非常小,我们将随机选择 30 个点并使用以下代码进行打印:
代码语言:javascript复制
indices=np.random.choice(len(X), 30)
X=X[indices]
y=y[indices]
print (y)

这将是结果输出:

代码语言:javascript复制
[2 1 2 1 2 0 1 0 0 0 2 1 1 0 0 0 2 2 1 2 1 0 0 1 2 0 0 2 0 0]
  1. 为了理解我们的特征,我们将尝试以散点图的形式将它们绘制在 3D 中:
代码语言:javascript复制
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(1, figsize=(20, 15))
ax = Axes3D(fig, elev=48, azim=134)
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=y,
         cmap=plt.cm.Set1, edgecolor='k', s = X[:, 3]*50)

for name, label in [('Virginica', 0), ('Setosa', 1), ('Versicolour', 2)]:
    ax.text3D(X[y == label, 0].mean(),
              X[y == label, 1].mean(),
              X[y == label, 2].mean(), name,
              horizontalalignment='center',
              bbox=dict(alpha=.5, edgecolor='w', facecolor='w'),size=25)

ax.set_title("3D visualization", fontsize=40)
ax.set_xlabel("Sepal Length [cm]", fontsize=25)
ax.w_xaxis.set_ticklabels([])
ax.set_ylabel("Sepal Width [cm]", fontsize=25)
ax.w_yaxis.set_ticklabels([])
ax.set_zlabel("Petal Length [cm]", fontsize=25)
ax.w_zaxis.set_ticklabels([])

plt.show()

下图是输出。 正如我们在 3D 可视化中所看到的,数据点通常在组中找到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uO9QaLd6-1681785569473)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/ea368c60-84e7-477f-87c8-f3f5081638a5.png)]

  1. 首先,我们将首先使用 80:20 拆分将数据集拆分为训练集和测试集。 我们将使用k=3作为最近的邻居:
代码语言:javascript复制
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)
# Instantiate learning model (k = 3)
classifier = KNeighborsClassifier(n_neighbors=3)

# Fitting the model
classifier.fit(X_train, y_train)

# Predicting the Test set results
y_pred = classifier.predict(X_test)

cm = confusion_matrix(y_test, y_pred)

accuracy = accuracy_score(y_test, y_pred)*100
print('Accuracy of our model is equal '   str(round(accuracy, 2))   ' %.')

这将导致以下输出:

代码语言:javascript复制
Accuracy of our model is equal 83.33 %.
  1. 初始化隐藏层的大小和迭代次数:
代码语言:javascript复制
mlp = MLPClassifier(hidden_layer_sizes=(13,13,13),max_iter=10)
mlp.fit(X_train,y_train)

您可能会收到一些警告,具体取决于 scikit-learn 的版本,例如/sklearn/neural_network/multilayer_perceptron.py:562:ConvergenceWarning: Stochastic Optimizer: Maximum iterations (10) reached and the optimization hasn't converged yet. % self.max_iter, ConvergenceWarning)。 这只是表明您的模型尚未收敛。

  1. 我们将预测 kNN 和神经网络的测试数据集,然后将两者进行比较:
代码语言:javascript复制
predictions = mlp.predict(X_test)

accuracy = accuracy_score(y_test, predictions)*100
print('Accuracy of our model is equal '   str(round(accuracy, 2))   ' %.')

以下是结果输出:

代码语言:javascript复制
Accuracy of our model is equal 50.0 %.

对于我们当前的情况,我们可以看到神经网络的准确率不如 kNN。 这可能是由于许多原因造成的,包括数据集的随机性,邻居的选择以及层数。 但是,如果我们运行足够的时间,我们会发现 kNN 总是会存储更好的数据,因为它总是存储数据点,而不是像神经网络那样学习参数。 因此,kNN 可以称为单样本学习方法。

总结

深度学习已经彻底变革了数据科学领域,并且仍在不断进步,但是仍然有一些主要行业尚未体验到深度学习的所有优势,例如医疗和制造业。 人类成就的顶峰将是创造一种可以像人类一样学习并且可以像人类一样成为专家的机器。 但是,成功的深度学习通常需要拥有非常庞大的数据集才能进行工作。 幸运的是,本书重点介绍了可以消除此先决条件的架构。

在本章中,我们了解了人类的大脑以及人工神经网络的结构如何接近我们的大脑结构。 我们介绍了机器学习和深度学习的基本概念及其挑战。 我们还讨论了单样本学习及其各种类型,然后在iris数据集上进行了实验,以比较在数据稀缺情况下的参数方法和非参数方法。 总的来说,我们得出结论,正确的特征表示在确定机器学习模型的效率方面起着重要作用。

在下一章中,我们将学习基于度量的单样本学习方法,并探索单样本学习算法的特征提取领域。

问题

  • 为什么对于单样本学习任务,kNN 比新近训练的人工神经网络更好地工作?
  • 什么是非参数机器学习算法?
  • 决策树是参数算法还是非参数算法?
  • 将其他分类算法作为编码练习进行实验,并比较结果。

第二部分:深度学习架构

单样本学习一直是许多科学家的活跃研究领域,他们试图找到一种在学习方面与人类尽可能接近的认知机器。 由于存在关于人类如何进行单样本学习的各种理论,因此我们可以使用许多不同的深度学习方法来解决这一问题。 本书的这一部分将重点介绍基于度量,基于模型和基于优化的深度学习架构,以解决单样本学习问题及其实现。

本节包括以下章节:

  • 第 2 章,“基于度量的方法”
  • 第 3 章,“基于模型的方法”
  • 第 4 章,“基于优化的方法”

二、基于指标的方法

深度学习已在各种应用中成功实现了最先进的表现,例如图像分类,对象检测,语音识别等。 但是,深度学习架构在被迫对几乎没有监督信息的数据进行预测时常常会失败。 众所周知,数学是所有机器学习和深度学习模型的基础。 我们使用数据的数学表示将数据和目标传达给机器。 这些表示形式可以有多种形式,特别是如果我们想学习复杂的任务(例如疾病检测),或者如果我们希望我们的架构根据不同的目标学习表示形式,例如,计算两个图像之间的相似度,我们可以计算欧几里得距离和余弦相似度。

在本章中,我们将学习可以从较小的数据集中学习正确的数学表示形式的深度学习架构。 总体而言,我们的目标是创建一种无需大量数据收集或训练过程即可概括不熟悉类别的架构。

本章将涵盖以下主题:

  • 参数方法–概述
  • 连体网络
  • 匹配网络
  • 编码练习

技术要求

需要使用以下库来学习和执行本章中的项目:

  • Python
  • Anaconda
  • Jupyter 笔记本
  • PyTorch
  • Matplotlib
  • scikit 学习

您可以在该书的 GitHub 存储库中找到该章的代码文件。

参数方法概述

在上一章中,我们简要讨论了非参数机器学习方法。 本节将主要关注机器学习的参数方法是什么,以及它们实际学习了什么。

简而言之,参数化机器学习算法试图学习数据及其标签的联合概率分布。 我们学习的参数是联合概率分布给出的方程; 例如,众所周知,逻辑回归可以看作是一层神经网络。 因此,考虑到一个单层神经网络,它实际学习的是方程的权重和偏差,以便使P(Y | X)适应Y(标签)。

逻辑回归是判别式分类器的一种形式,在判别式分类器中,我们仅关注P(Y | X),即,我们尝试对Y(标签)的概率分布做出假设,并尝试以某种方式将我们的X(输入)映射到它。 因此,本质上,我们尝试在逻辑回归中进行以下操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPs0fwQp-1681785569474)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/010cceaa-cf25-4883-a25d-5800e070dc87.png)]

在这里,P(Y | X)是分类分布,这意味着我们正在尝试学习可能类别上的分布。 简单来说:给定X,我们将学习Y可以具有的所有可能类别。 由于数据的缘故,这都是可能的-随着数据量的增加,我们对Y的近似值也随之增加。 在下一节中,我们将学习神经网络的学习过程,并了解哪些属性在逼近Y标签中起着重要的作用。

神经网络学习器

众所周知,神经网络通过使用随机梯度下降优化方法使损失函数(或目标函数)最小化来学习。 因此,损失函数是决定神经网络架构目标的主要因素之一。 例如,如果要分类数据点,我们将选择损失函数,例如类别交叉熵0-1 损失铰链损失; 相反,如果我们的目标是回归,我们将选择损失函数,例如均方误差均方根误差Huber 损失。 一些常见的方程式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pxX1cslS-1681785569474)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/47a48789-8ff8-42cf-a648-96d68b1df236.png)]

在知道损失函数对神经网络有重大影响之后,每个人首先想到的是我们需要提出更好的损失函数。 如果您研究最新的研究,您会发现主要的发展是基于更改损失函数的对象检测,图像分割,机器翻译等。 找出新的损失函数可能很棘手,原因有两个:

  • 目标函数本质上必须是凸形的,才能满足随机梯度下降优化的要求。
  • 通常,通过不同函数获得的最小值在数值上相同。

在下一节中,我们将了解这些损失函数如何帮助架构学习不同的图像特征,以及如何结合这些特征来为其他各种目标训练模型。

可视化参数

神经网络通过梯度下降学习,但是它们学到什么呢? 答案是参数,但我们希望了解这些参数的含义。 在下图中,如果我们看一下前几层,将看到简单易懂的提取特征,例如边缘和兴趣点,而更深层的特征则更复杂。 例如,如果我们查看下图中的最后一层,将观察到与初始层特征相比,特征是不可识别的。 这是因为随着我们进入更深的层次,越来越多的信息丰富的特征正在通过各种矩阵运算来提取。 这使得高维信息可以压缩到低维损失函数空间中,并得到训练后的模型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KrhCklNm-1681785569474)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/2ea3be34-ced7-4c60-a302-98d1f0c35e3e.jpeg)]

因此,例如,如果我们查看花朵与汽车等类别,则初始层的特征就足够了。 但是,如果我们有诸如汽车类型之类的类别,则需要更深层次的模型,因为我们需要提取更复杂的特征,这需要更大的数据集。 问题是,什么决定了模型学习的特征或参数的类型,是否有可能在初始层学习这些重要参数? 在下一部分中,我们将探索连体网络,它是一种神经网络架构,可以通过更改损失函数及其架构设计来学习前几层的复杂特征。

了解连体网络

顾名思义,连体网络是一种具有两个并行层的架构。 在此架构中,模型学会了在两个给定的输入之间进行区分,而不是学习使用分类损失函数对其输入进行分类的模型。 它根据相似性度量比较两个输入,并检查它们是否相同。 与任何深度学习架构相似,连体网络也有两个阶段-训练和测试阶段。 但是,对于单样本学习方法(因为我们没有很多数据点),我们将在一个数据集上训练模型架构,并在另一个数据集上对其进行测试。 为了简单起见,我们使用带监督的基于度量的方法使用连体神经网络来学习图像嵌入,然后将该网络的特征重新用于单样本学习,而无需进行微调或重新训练。

机器学习算法的良好特征的提取在确定模型效率方面起着至关重要的作用。 在各种情况下,当可用数据有限时,事实证明它要么计算量大,要么困难。

如下图所示,我们的目标是训练网络以了解两个图像或声音是否相同。 假设第一个图像中有两个足球; 即使背景不同,两者都是足球,所以被认为是相同的。 单词cow的声音也是如此。 在图像和声音不同的地方,例如鳄鱼和足球,它们被标记为不同的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eAUUkAwK-1681785569474)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/0d94a582-9d00-4f5b-a28f-7e4df967ea31.png)]

关键思想听起来可能类似于转学,但有所不同。 连体网络使用对比损失函数来学习这些特征。 其次,连体网络方法仅适用于相似的域,因为它还需要注意域的适应性,也就是说,它需要尝试确保我们的训练和测试数据集在域方面是紧密的。 例如,如果您要创建一个系统来测试两个手写示例是否属于同一个人,则可以在 MNIST 数据集上训练一个连体网络架构,通过该架构,可以学习特定于手写体的特征(例如曲线) 和给定字符的笔划。 在下一节中,我们将研究连体网络的架构并了解其优化。

构建

连体网络由两个相同的神经网络组成,它们共享相似的参数,每个头部获取一个输入数据点。 在中间层,由于权重和偏差相同,因此我们提取了相似的特征。 这些网络的最后一层被馈送到对比损失函数层,,该层计算两个输入之间的相似度。

您可能会遇到的一个问题是,为什么连体网络的层共享参数? 如果我们已经在努力改变损失函数,这是否有助于我们分别训练各层?

我们不单独训练层的主要原因有两个:

  • 对于每一层,我们都添加了数千个参数。 因此,类似于我们在共享参数的卷积神经网络方法中所做的工作,我们可以更快地优化网络。
  • 权重共享确保两个相似的图像不会映射到特征嵌入空间中的不同位置。

特征嵌入是将特征投影到某个更高维度的空间(也称为特征嵌入空间),具体取决于我们要实现的任务。

下图说明了一个示例连体网络架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OAN6kOQv-1681785569475)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/be19cfde-bc78-4b64-92a9-85e89e1df3ce.png)]

如我们所见,前面的图是简单明了的。 现在,我们将讨论训练连体网络所需的预处理步骤。

预处理

为了训练连体网络,我们需要对数据集进行特殊的预处理。 在预处理数据集时,我们必须仔细创建数据点对,如下所示:

  • 相似的图像对
  • 不同的图像对

下图说明了 Omniglot 的连体网络目标的示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1fVbmz6d-1681785569475)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/67555406-abd0-4471-b5e1-deb9c87c2813.jpg)]

我们还需要为相似的数据点(y = 1)和不相似的数据点(y = 0)相应地创建标签; 然后,将每一对馈入连体体系。 在层的最后,连体网络使用损失函数的微分形式来学习各层之间的微分特征。 通常,对于连体网络,我们仅使用两种类型的函数-对比损失函数和三重损失函数。 我们将在下一节中详细了解这些内容。

对比损失函数

使用连体架构的整体思想不是在类别之间进行分类,而是学习区分输入。 因此,它需要损失函数的微分形式,称为对比损失函数。 给出如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eVWw6PXJ-1681785569475)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/aaedf542-dd84-4589-8774-94190df0b2f6.png)]

在该等式中, D[w] = √((f(X1) - f(X2))²)f(X)代表连体神经网络,m代表裕度。

让我们进一步求解损失方程。 以Y = 1表示相似的货币对:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8aGqVHNf-1681785569475)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/029593cf-8b9d-4162-8c8f-4b58a97c679d.png)]

如果两个输入X1X2相同,则意味着连体网络应该能够学习制作D²[w] = 0。 我们在方程式中增加余量m,以使连体网络不使W = 0,从而使D²[w] = 0。 通过强制执行边距,我们确保连体网络学习到良好的决策边界。

类似地,对于不相似对的Y = 0,这将产生以下结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MvFva8yr-1681785569476)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/50d96d9a-18f2-41b9-a4af-0df76984984e.png)]

从数字上讲,对于相同的对情况,仅当D[w] = 0时,损失函数才为零,否则它将表现为回归损失,并尝试学习特征以确保D[w]接近 0。

尽管对比损失函数是学习判别特征的一种好方法,但是在连体网络架构的其他修改版本中,对比损失函数不能非常清楚地学习决策边界。 在这种情况下,我们可以使用称为三重损失的新损失函数,该函数可帮助架构获得更好的结果。

三重损失函数

三重态损失函数是对比损失函数的替代方法。 与对比损失函数相比,它具有收敛优势。

要了解三重态损失函数,首先,我们需要成对定义数据点,如下所示:

  • 锚点A):主数据点
  • 正例P):类似于锚点的数据点
  • 负例N):与锚点不同的数据点

考虑到f(X)是连体网络的输出,理想情况下,我们可以假设以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LAmdKh7G-1681785569476)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/73760b22-b575-40f1-85be-396c6822a5d5.png)]

用距离函数项,我们可以说以下几点:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-saWm77m0-1681785569476)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/e89f8c0f-2387-40bf-9cd0-b346ff3fdc52.png)]

由于我们不希望连体网络学习f(X) = 0, X ∈ R,因此我们将添加余量,类似于对比损失函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iH5PvTjk-1681785569476)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/aa0e72e6-1a0c-4888-a75d-1e4276061bb4.png)]

使用以下等式,我们将定义三重态损失如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PpnHF8kX-1681785569476)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/5c387438-e9c3-499e-b6ed-5c92097ee9a0.png)]

下图表示三元组损失函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMdW5ClQ-1681785569477)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/8d24886d-e1ca-4322-848e-32251f118edd.png)]

三重损失函数的收敛性优于对比损失函数,因为它一次考虑了三个示例,并保持了点之间的距离,如上图所示,从而可以更准确地学习决策边界,而对比损失函数一次只考虑成对示例,因此从某种意义上讲,它更贪婪,这会影响决策边界。

应用领域

通常,可以使用各种方法解决问题。 例如我们手机上的人脸检测。 图像分类是一种需要大量数据点的方法,而如果使用单样本学习的连体网络架构,则仅需几个数据点就可以实现更高的准确率。 连体网络架构已成为软件行业采用的最流行的单样本学习架构之一。 它可用于各种其他应用,例如面部检测,手写检测和垃圾邮件检测。 但是仍然有很多改进的余地,并且各种各样的研究者正在为此努力。 在下一节中,以相似的主题进行工作,我们将学习匹配的网络架构,该架构使用注意力机制和不同的训练过程来学习训练集标签上的概率分布。

了解匹配网络

匹配网络通常会提出一个框架,该框架学习一个网络,该网络映射一个小的训练数据集,并在相同的嵌入空间中测试未标记的示例。 匹配网络旨在学习小型训练数据集的正确嵌入表示,并使用具有余弦相似性度量的可微 kNN 来检查是否已经看到测试数据点。

匹配网络的设计有两个方面:

  • 建模级别:在建模级别,他们提出了匹配网络,该网络利用注意力和记忆力方面的进步实现快速有效的学习。
  • 训练过程:在训练级别上,他们有一个条件-训练和测试集的分布必须相同。 例如,这可能意味着每个类显示一些示例,并将任务从小批量切换到小批量,类似于在展示新任务的一些示例时如何对其进行测试。

匹配网络结合了参数模型和非参数模型的最佳特性,也被称为差分最近邻居。

在下一节中,我们将研究在建模级别由匹配网络做出的贡献,随后我们将经历训练过程的贡献。

模型架构

匹配的网络架构主要受关注模型和基于内存的网络的启发。 在所有这些模型中,都定义了神经注意力机制来访问存储矩阵,该矩阵存储有用的信息来解决手头的任务。 首先,我们需要了解匹配网络中使用的某些术语:

  • 标签集:这是所有可能类别的样本集。 例如,如果我们使用 ImageNet 数据集,它包含数千个类别(例如猫,狗和鸟),但是作为标签集的一部分,我们将仅使用其中的五个类别。
  • 支持集:这是我们标签集类别的采样输入数据点(例如,图像)。
  • 批量:类似于支持集,批量也是由标签集类别的输入数据点组成的采样集。
  • Nk次方法:此处,N是支撑集的大小,或更简单地说,是训练集中可能类别的数量。 例如,在下图中,我们有四种不同类型的狗品种,并且我们计划使用 5 样本学习方法,即每种类别至少有五个示例。 这将使我们的匹配网络架构使用 4 路 5 样本学习,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uwBi4jQk-1681785569477)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/3118bc82-3dc7-473f-8e77-f57c6f17b84e.png)]

匹配网络的关键思想是将图像映射到嵌入空间,该空间也封装了标签分布,然后使用不同的架构在相同的嵌入空间中投影测试图像。 然后,我们以后用余弦相似度来衡量相似度。 让我们看一下匹配网络如何创建其嵌入空间。

训练器

在训练架构方面,匹配网络遵循某种技术:它们尝试在训练阶段复制测试条件。 简而言之,正如我们在上一节中所了解的那样,匹配网络从训练数据中采样标签集,然后它们从同一标签集生成支持集和批量集。 数据预处理之后,匹配网络通过训练模型以将支持集作为训练集,并将批量集作为测试集来最小化误差,从而学习其参数。 通过将支持集作为训练集,将批量集作为测试集的训练过程,可使匹配的网络复制测试条件。

在下一部分中,我们将介绍匹配网络的架构和算法,并学习如何在模型的训练阶段使用批量集(即测试集)。

建模级别–匹配的网络架构

匹配网络将支持集(k示例)映射到分类器C[s](·)的支持集(k示例)S = (x[i], x[i]), i = 1, ..., k。 基本上,匹配网络将映射S -> C[s](·)定义为参数化神经网络P(y_hat | x_hat, S)。 如果我们谈论P(y_hat | x_hat, S)的最简单形式,它将是支持集标签的线性组合形式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G3YQOhWG-1681785569477)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/94576b62-c681-443b-ba09-e6e280e2407f.png)]

在此,a(x_hat, x[i])是 softmax 函数。 从逻辑上看,我们可以看到y_hat正在非参数意义上正确计算。

例如,如果我们有 2 个类,分别为 0 和 1,则 2 个示例(k = 2)如下:y = (0, 1)

通过将y变成单热编码向量,我们将获得以下信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KqQQntfm-1681785569477)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/725b5feb-44c8-43ed-8a13-b4c530b590e8.png)]

它们各自的核值如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ugcAoyIF-1681785569477)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/8c500245-cdfc-485c-8f8b-16692f891db5.png)]

通过引入ay的值,我们将获得以下方程式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kbFiT3mm-1681785569478)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/0b1398a4-325e-475e-84e6-a59cdc029dfd.png)]

解决此问题后,我们将获得以下方程式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6o3GIOvP-1681785569478)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/d3c88db6-018b-4740-a01d-e40f57052f55.png)]

总体而言,我们可以看到y_hat如何成为确定测试输入x_hat属于哪个类别的概率的线性组合。 要将任何形式的函数转换为概率空间,深度学习社区使用的最佳选择是 softmax 函数,使得a(x_hat, x[i])如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fGJD1eHt-1681785569478)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/6d449b9b-9813-4ed7-9f19-37f2889465e4.png)]

在此,c是训练集和测试数据点的嵌入之间的余弦相似度函数。

现在,出现了关于如何从测试集和训练集中提取嵌入的问题。 任何形式的神经网络都可以工作。 对于图像,著名的 VGG16 或 Inception Net 将通过使用迁移学习为测试图像和训练图像提供适当的嵌入; 本质上,这是过去大多数基于度量的方法所做的,但是无法获得人类水平的认知结果。

VGG16 和 Inception Net 是深度学习架构,它们在 ImageNet 数据集上提供了最新的结果。 它们通常用于任何图像的初始特征提取,因为这将为我们的架构适当地初始化训练过程。

匹配网络指出了上述简单化非参数方法的两个问题:

  • 问题 1:即使分类策略P(y_hat | x_hat, S)已设定条件,训练集图像的嵌入也彼此独立,而不认为它们是支持集的一部分。

解决方案:匹配网络使用双向长短期记忆LSTM)在整个支持集范围内启用每个数据点的编码。 通常,LSTM 用于理解数据序列,因为它们能够使用其单元内部的门保持整个数据的上下文。 同样,使用双向 LSTM 可以更好地理解数据序列。 匹配网络使用双向 LSTM 来确保支持集中一幅图像的嵌入将具有所有其他图像嵌入的上下文。

  • 问题 2:如果要计算两个数据点之间的相似度,首先需要将它们放入相同的嵌入空间。 因此,支持集S必须能够有助于提取测试图像嵌入。

解决方案:匹配网络使用 LSTM,并且具有对支持集S的注意。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bymx6VrL-1681785569478)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/d8c095d5-729c-49ac-8834-ef3b2c1f861e.png)]

这里,K是展开步骤的数量,embeddings(x_hat)是通过 VGG16/Inception 网络获得的测试图像嵌入; g(S)是将测试图像嵌入带到同一空间的样本集。

下图说明了匹配的网络架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XYbuWrUG-1681785569478)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/5840cf24-bfa3-424f-a151-23cf76511e2c.png)]

匹配的网络架构解决了通过设置框架在训练模型时复制测试条件的单样本学习问题,如训练过程部分中所述。 匹配网络的架构包含许多子部分。 为了简化和更清楚地了解它,我们将从左到右进行每个过程:

  1. 作为预处理数据的一部分,将创建k示例的支持集S作为(x[i], y[i]), i = 1 ... k
  2. 获取支持集后,它会通过标准特征提取层(g),例如 VGG 或 Inception。
  3. 在提取支持集(S)的嵌入(g层的输出)之后,将它们放入双向 LSTM 架构中。 这有助于模型学习支持集中存在的标签的概率分布。
  4. 与训练集类似,查询图像(即测试图像)的全上下文嵌入提取也经历了组合的双向 LSTM 架构,同时从g(x[i)获得了贡献,从而映射到相同的嵌入空间。
  5. 从这两种架构获得输出后,这些输出将通过 softmax 层(也称为注意核步骤a(h[k-1], g(x[i])))传递。
  6. 然后,从g(x[i])f'(x)获得的输出用于检查查询图像属于哪个类别:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aOgkXl52-1681785569479)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/61fbf48b-9b8f-4de8-96b1-487001701281.png)]

在此等式中,y_hat是支持集中标签的加权和。

在此,注意核是 softmax 函数,其余弦距离的值介于g(x[i])f'(x)之间。 为了训练模型,我们可以使用任何基于分类的损失函数,例如交叉熵损失函数。

匹配网络的关键思想是创建一个即使在训练数据(即支持集)中不存在的类也可以表现良好的架构。

匹配网络因其创新的训练过程和完全上下文嵌入而成为一站式学习的著名方法之一。 如果我们尝试从人类学习的角度理解匹配网络的方法,则它与儿童的教学过程非常相似。 要学习新任务,将为他们提供一系列小型示例,然后是小型测试集,然后重复进行。 使用此程序,并借助人脑的上下文记忆保持功能,孩子们可以学习一项新的任务。

在下一部分中,我们将使用著名的 MNIST 和 Omniglot 数据集探索连体网络的实现和匹配网络架构。

编码练习

在本节中,我们将学习连体网络和匹配网络的实现。

让我们从连体网络开始。

连体网络-MNIST 数据集

在本教程中,我们将按照此处列出的顺序执行以下操作:

  1. 数据预处理:创建偶对
  2. 创建连体网络架构
  3. 使用小型 MNIST 数据集对其进行训练
  4. 可视化嵌入

执行以下步骤进行练习:

  1. 首先,使用以下代码导入所需的所有库:
代码语言:javascript复制
# -*- encoding: utf-8 -*-
import argparse
import torch
import torchvision.datasets as dsets
import random
import numpy as np
import time
import matplotlib.pyplot as plt
from torch.autograd import Variable
from torchvision import transforms
import pickle
import torch
import torch.nn as nn

正如我们在理论“了解连体网络”部分中学到的,作为数据预处理的一部分,我们需要创建对:

  • 相似对;y = 1
  • 不同对;y = 0

我们正在使用对比损失函数–这就是为什么我们只有两对的原因。 对于三重损失函数,我们需要其他形式的预处理。

  1. 要预处理数据并为模型创建迭代器,请首先创建Dataset类:
代码语言:javascript复制
class Dataset(object):
    '''
    Class Dataset:
    Input: numpy values
    Output: torch variables.
    '''
    def __init__(self, x0, x1, label):
        self.size = label.shape[0] 
        self.x0 = torch.from_numpy(x0)
        self.x1 = torch.from_numpy(x1)
        self.label = torch.from_numpy(label)

    def __getitem__(self, index):
        return (self.x0[index],
                self.x1[index],
                self.label[index])

    def __len__(self):
        return self.size
  1. 在创建迭代器之前,让我们创建pairs函数并对其进行预处理:
代码语言:javascript复制
def create_pairs(data, digit_indices):
    x0_data = []
    x1_data = []
    label = []
    n = min([len(digit_indices[d]) for d in range(10)]) - 1
    for d in range(10): # for MNIST dataset: as we have 10 digits
        for i in range(n):
            z1, z2 = digit_indices[d][i], digit_indices[d][i   1]
            x0_data.append(data[z1] / 255.) # Image Preprocessing 
                                            Step
            x1_data.append(data[z2] / 255.) # Image Preprocessing 
                                            Step
            label.append(1)
            inc = random.randrange(1, 10)
            dn = (d   inc) % 10
            z1, z2 = digit_indices[d][i], digit_indices[dn][i]
            x0_data.append(data[z1] / 255.) # Image Preprocessing 
                                            Step
            x1_data.append(data[z2] / 255.) # Image Preprocessing 
                                            Step
            label.append(0)

    x0_data = np.array(x0_data, dtype=np.float32)
    x0_data = x0_data.reshape([-1, 1, 28, 28])
    x1_data = np.array(x1_data, dtype=np.float32)
    x1_data = x1_data.reshape([-1, 1, 28, 28])
    label = np.array(label, dtype=np.int32)
    return x0_data, x1_data, label
  1. 然后,创建iterator函数。 为了我们的训练目的,这将返回给定的batchsize参数集:
代码语言:javascript复制
def create_iterator(data, label, batchsize, shuffle=False):
    digit_indices = [np.where(label == i)[0] for i in range(10)]
    x0, x1, label = create_pairs(data, digit_indices)
    ret = Dataset(x0, x1, label)
    return ret
  1. 然后,创建loss函数。 众所周知,contrastive_loss_function包含两个部分:
  • 对于类似的点:(1-y)*(distance_function)^2
  • 对于不同的点: y*{max(0,(m-distance_function^2)}
  1. 在这里,distance_function被视为欧几里得距离,也称为均方根
代码语言:javascript复制
def contrastive_loss_function(x0, x1, y, margin=1.0):
    # euclidean distance
    diff = x0 - x1
    dist_sq = torch.sum(torch.pow(diff, 2), 1)
    dist = torch.sqrt(dist_sq)
    mdist = margin - dist
    dist = torch.clamp(mdist, min=0.0)
    loss = y * dist_sq   (1 - y) * torch.pow(dist, 2)
    loss = torch.sum(loss) / 2.0 / x0.size()[0]
    return loss
  1. 接下来,创建连体网络架构。 为此,我们首先创建一个带有两个函数的名为SiameseNetwork的类:
  • forward_once:在forward_once中,训练数据将穿过所有层并返回输出的嵌入。
  • forward:在forward中,对于给定的输入对,将两次调用forward_once,这将返回获得的嵌入的 NumPy 数组。

正如在连体网络的理论部分所讨论的那样,我们与两个并行层共享参数,因此我们不需要显式创建两个分支,我们只需创建一个分支即可:

代码语言:javascript复制
class SiameseNetwork(nn.Module):
    def __init__(self,flag_kaf=False):
        super(SiameseNetwork, self).__init__()
        self.cnn1 = nn.Sequential(
            nn.Conv2d(1, 20, kernel_size=5),
            nn.MaxPool2d(2, stride=2),
            nn.Conv2d(20, 50, kernel_size=5),
            nn.MaxPool2d(2, stride=2))
        self.fc1 = nn.Sequential(
            nn.Linear(50 * 4 * 4, 500),
            nn.ReLU(inplace=True),
            nn.Linear(500,10),
            nn.Linear(10, 2))

    def forward_once(self, x):
        output = self.cnn1(x)
        output = output.view(output.size()[0], -1)
        output = self.fc1(output)
        return output

    def forward(self, input1, input2):
        output1 = self.forward_once(input1)
        output2 = self.forward_once(input2)
        return output1, output2
  1. 减少MNIST数据集并选择2000随机点,将batchsize设置为 2 的任意幂(例如128),然后导入MNIST数据集:
代码语言:javascript复制
batchsize=128
train = dsets.MNIST(root='../data/',train=True,download=True)
test = dsets.MNIST(root='../data/',train=False,transform=transforms.Compose([transforms.ToTensor(),]))
indices= np.random.choice(len(train.train_labels.numpy()), 2000, replace=False)
indices_test= np.random.choice(len(test.test_labels.numpy()), 100, replace=False)
  1. 我们在“步骤 4”中创建了一个迭代器,我们将使用它来创建训练和测试集迭代器:
代码语言:javascript复制
train_iter = create_iterator(train.train_data.numpy()[indices],train.train_labels.numpy()[indices],batchsize)
test_iter = create_iterator(test.test_data.numpy()[indices_test],test.test_labels.numpy()[indices_test],batchsize)

# call model
model = SiameseNetwork()
learning_rate = 0.01 # learning rate for optimization
momentum = 0.9 # momentum
# Loss and Optimizer
criterion = contrastive_loss_function # we will use contrastive loss function as defined above
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate,momentum=momentum)

# creating a train loader, and a test loader.
train_loader = torch.utils.data.DataLoader(train_iter,batch_size=batchsize, shuffle=True)
test_loader = torch.utils.data.DataLoader(test,batch_size=batchsize, shuffle=True)
  1. 然后,我们训练模型一定数量的时间并打印结果:
代码语言:javascript复制
train_loss = []
epochs =100
for epoch in range(epochs):
    print('Train Epoch:' str(epoch) "------------------>")
    for batch_idx, (x0, x1, labels) in enumerate(train_loader):
        labels = labels.float()
        x0, x1, labels = Variable(x0), Variable(x1), 
        Variable(labels)
        output1, output2 = model(x0, x1)
        loss = criterion(output1, output2, labels)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        train_loss.append(loss.item())
        if batch_idx % batchsize == 0:
            print('Epoch: {} tLoss: {:.6f}'.format(epoch, 
                loss.item()))

这将给出以下输出:

代码语言:javascript复制
Epoch: 0        Loss: 0.269623
Epoch: 1        Loss: 0.164050
Epoch: 2        Loss: 0.109350
Epoch: 3        Loss: 0.118925
Epoch: 4        Loss: 0.108258
...
...
Epoch: 97       Loss: 0.003922
Epoch: 98       Loss: 0.003155
Epoch: 99       Loss: 0.003937
  1. 现在,让我们创建所有用于绘制嵌入的函数和一个损失函数:
代码语言:javascript复制
def plot_loss(train_loss,name="train_loss.png"):
    plt.plot(train_loss, label="train loss")
    plt.legend()
    plt.show()

def plot_mnist(numpy_all, numpy_labels,name="./embeddings_plot.png"):
        c = ['#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff',
             '#ff00ff', '#990000', '#999900', '#009900', '#009999']

        for i in range(10):
            f = numpy_all[np.where(numpy_labels == i)]
            plt.plot(f[:, 0], f[:, 1], '.', c=c[i])
        plt.legend(['0', '1', '2', '3', '4', '5', '6', '7', '8', 
            '9'])
        plt.savefig(name)
  1. 使用以下代码绘制loss函数:
代码语言:javascript复制
plot_loss(train_loss)

这将给出以下图作为结果输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rVUxiGwN-1681785569479)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/aba64cc3-f3b4-4d14-a3bf-8d0ae4a62c36.png)]

  1. 然后,我们将定义test_modeltesting_plots来绘制MNIST数据集的测试集嵌入:
代码语言:javascript复制
def test_model(model):
        model.eval()
        all_ = []
        all_labels = []
        with torch.no_grad():
            for batch_idx, (x, labels) in enumerate(test_loader):
                x, labels = Variable(x), Variable(labels)
                output = model.forward_once(x)
                all_.extend(output.data.cpu().numpy().tolist())
                all_labels.extend(labels.data.cpu().numpy().tolist())

        numpy_all = np.array(all_)
        numpy_labels = np.array(all_labels)
        return numpy_all, numpy_labels

def testing_plots(model):
        dict_pickle={}
        numpy_all, numpy_labels = test_model(model)
        dict_pickle["numpy_all"]=numpy_all
        dict_pickle["numpy_labels"]=numpy_labels
        plot_mnist(numpy_all, numpy_labels)
  1. 然后,绘制testing_plots
代码语言:javascript复制
testing_plots(model)

这将给出以下图作为结果输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5p1fQlYw-1681785569479)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/9b174385-0416-496d-b96a-6eee5e7b5f61.png)]

在上图中,我们可以观察到大多数点都在一个群集中,而其他一些点则不在群集中,可以看作是离群值。

匹配网络– Omniglot 数据集

在本教程中,我们将学习如何创建匹配的网络架构并将其训练在 Omniglot 数据集上。 首先,让我们首先了解什么是 Omniglot 数据集。

Omniglot 数据集旨在开发更多类似于人类的学习算法。 它包含来自 50 个不同字母的 1,623 个不同的手写字符。 1,623 个字符中的每个字符都是由 20 个不同的人通过亚马逊的 Mechanical Turk 在线绘制的。 每个图像都与笔划数据配对,序列[x, y, t]与时间的坐标(t)以毫秒为单位。 有关更多详细信息,请参考这里。

您可以从这里下载 Omniglot 数据集。

我们的匹配网络架构实现包括以下五个重要部分(有关更多详细信息,您可以参考“建模级别-匹配网络架构”部分中的匹配网络架构图):

  • 嵌入提取器g
  • 完全上下文嵌入和双向 LSTM f
  • 余弦相似距离函数c
  • 注意模型,softmax(c)
  • 损失函数,交叉熵损失

现在,我们将遍历匹配网络的每个部分并实现它:

  1. 导入所有库:
代码语言:javascript复制
import numpy as np
import torch
import torch.nn as nn
import math
import numpy as np
import torch.nn.functional as F
from torch.autograd import Variable
import tqdm
import torch.backends.cudnn as cudnn
from torch.optim.lr_scheduler import ReduceLROnPlateau
import matplotlib.pyplot as plt
%matplotlib inline
  1. 我们将加载omniglot数据集,该数据集将使用帮助程序脚本转换为.npy格式。 在帮助程序脚本中,我们仅以以下大小格式加载数据:[total_number, character, 28,28](有关更多详细信息,请通过本书的 GitHub 存储库中的helper.py脚本进行操作):
代码语言:javascript复制
x = np.load('data/data.npy') # Load Data
x = np.reshape(x, newshape=(x.shape[0], x.shape[1], 28, 28, 1)) # expand dimension from (x.shape[0],x.shape[1],28,28)
np.random.shuffle(x) # shuffle dataset
x_train, x_val, x_test = x[:1200], x[1200:1411], x[1411:] # divide dataset in to train, val,ctest
batch_size = 16 # setting batch_size
n_classes = x.shape[0] # total number of classes
classes_per_set = 20 # Number of classes per set
samples_per_class = 1 # as we are choosing it to be one shot learning, so we have 1 sample

如果您想了解有关数据加载方法的更多信息,可以参考 GitHub 上的helper.py文件。

  1. 使用规范化方法预处理图像:
代码语言:javascript复制
def processes_batch(data, mu, sigma):
    return (data - mu) / sigma

# Normalize Dataset
x_train = processes_batch(x_train, np.mean(x_train), np.std(x_train))
x_val = processes_batch(x_val, np.mean(x_val), np.std(x_val))
x_test = processes_batch(x_test, np.mean(x_test), np.std(x_test))

# Defining dictionary of dataset
datatset = {"train": x_train, "val": x_val, "test": x_test}
  1. 现在,运行以下代码以可视化由 20 个人编写的一个字符的第0个示例:
代码语言:javascript复制
temp = x_train[0,:,:,:,:] 
for i in range(0,20):
    plt.figure()
    plt.imshow(temp[i,:,:,0])

通过运行前面的代码,您将获得以下 20 个:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v6rPY2JW-1681785569479)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/fd4f59b6-874c-4f35-b486-0072c2a2426a.png)]

接下来,我们将对训练数据进行一些处理。

要加载 Omniglot 数据集并准备将其用于匹配的网络架构,我们需要创建以下内容:

  • 标签集:choose_label
  • 支持集:support_set_xsupport_set_y
  • 支持集示例中的一批

我们将执行以下步骤:

  1. 首先,创建一个可以提供支持集和目标集的批量:
代码语言:javascript复制
def sample_batch(data):
        """
        Generates sample batch 
        :param : data - one of(train,test,val) our current dataset 
        shape [total_classes,20,28,28,1]
        :return: [support_set_x,support_set_y,target_x,target_y] 
        for Matching Networks
        """
        support_set_x = np.zeros((batch_size, classes_per_set, 
            samples_per_class, data.shape[2], data.shape[3], 
            data.shape[4]), np.float32)
        support_set_y = np.zeros((batch_size, classes_per_set, 
            samples_per_class), np.int32)

        target_x = np.zeros((batch_size, data.shape[2], 
            data.shape[3], data.shape[4]), np.float32)
        target_y = np.zeros((batch_size, 1), np.int32)
        for i in range(batch_size):
            choose_classes = np.random.choice(data.shape[0], 
                size=classes_per_set, replace=False) # choosing 
                random classes
            choose_label = np.random.choice(classes_per_set, 
                size=1) # label set
            choose_samples = np.random.choice(data.shape[1], 
                size=samples_per_class   1, replace=False)
            x_temp = data[choose_classes] # choosing classes
            x_temp = x_temp[:, choose_samples] # choosing sample 
                batch from classes chosen outputs 20X2X28X28X1
            y_temp = np.arange(classes_per_set) # will return 
                [0,1,2,3,...,19]
            support_set_x[i] = x_temp[:, :-1]
            support_set_y[i] = np.expand_dims(y_temp[:], 
                axis=1) # expand dimension
            target_x[i] = x_temp[choose_label, -1]
            target_y[i] = y_temp[choose_label]
        return support_set_x, support_set_y, target_x, target_y 
            # returns support of [batch_size, 20 classes per set, 
            1 sample, 28, 28,1]

def get_batch(dataset_name):
        """
        gen batch while training
        :param dataset_name: The name of dataset(one of 
        "train","val","test")
        :return: a batch images
        """
        support_set_x, support_set_y, target_x, target_y = 
        sample_batch(datatset[dataset_name])
        support_set_x = 
        support_set_x.reshape((support_set_x.shape[0], 
            support_set_x.shape[1] * support_set_x.shape[2],
            support_set_x.shape[3], support_set_x.shape[4], 
            support_set_x.shape[5]))
        support_set_y = 
        support_set_y.reshape(support_set_y.shape[0], 
            support_set_y.shape[1] * support_set_y.shape[2])
        return support_set_x, support_set_y, target_x, target_y

如果您还记得,在匹配的网络架构中,网络有四个主要部分:

  • 嵌入提取器(g
  • 全文上下文嵌入(f
  • 注意模型(a
  • 距离函数(c
  1. 创建一个分类器:
代码语言:javascript复制
def convLayer(in_channels, out_channels, dropout_prob=0.0):
    """
    :param dataset_name: The name of dataset(one of 
    "train","val","test")
    :return: a batch images
    """
    cnn_seq = nn.Sequential(
        nn.Conv2d(in_channels, out_channels, 3, 1, 1),
        nn.ReLU(True),
        nn.BatchNorm2d(out_channels),
        nn.MaxPool2d(kernel_size=2, stride=2),
        nn.Dropout(dropout_prob)
    )
    return cnn_seq

class Embeddings_extractor(nn.Module):
    def __init__(self, layer_size=64, num_channels=1, 
        dropout_prob=0.5, image_size=28):
        super(Embeddings_extractor, self).__init__()
        """
        Build a CNN to produce embeddings
        :param layer_size:64(default)
        :param num_channels:
        :param keep_prob:
        :param image_size:
        """
        self.layer1 = convLayer(num_channels, layer_size, 
            dropout_prob)
        self.layer2 = convLayer(layer_size, layer_size, 
            dropout_prob)
        self.layer3 = convLayer(layer_size, layer_size, 
            dropout_prob)
        self.layer4 = convLayer(layer_size, layer_size, 
            dropout_prob)

        finalSize = int(math.floor(image_size / (2 * 2 * 2 * 2)))
        self.outSize = finalSize * finalSize * layer_size

    def forward(self, image_input):
        """
        :param: Image
        :return: embeddings
        """
        x = self.layer1(image_input)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = x.view(x.size()[0], -1)
        return x
  1. 在分类器之后创建注意力模型。a(x, x_hat)为余弦相似度的 softmax:
代码语言:javascript复制
class AttentionalClassify(nn.Module):
    def __init__(self):
        super(AttentionalClassify, self).__init__()
    def forward(self, similarities, support_set_y):
        """
        Products pdfs over the support set classes for the target 
        set image.
        :param similarities: A tensor with cosine similarites of 
        size[batch_size,sequence_length]
        :param support_set_y:[batch_size,sequence_length,
        classes_num]
        :return: Softmax pdf shape[batch_size,classes_num]
        """
        softmax = nn.Softmax(dim=1)
        softmax_similarities = softmax(similarities)
        preds = softmax_similarities.unsqueeze(1).
        bmm(support_set_y).squeeze()
        return preds
  1. 创建一个距离网络,该距离网络将从测试图像和训练嵌入中获取输出以计算距离。 找到支持集和input_test_image之间的余弦相似度:
代码语言:javascript复制
class DistanceNetwork(nn.Module):
    def __init__(self):
        super(DistanceNetwork, self).__init__()

    def forward(self, support_set, input_image):
        eps = 1e-10
        similarities = []
        for support_image in support_set:
            sum_support = torch.sum(torch.pow(support_image, 2), 1)
            support_manitude = sum_support.clamp(eps, 
                float("inf")).rsqrt()
            dot_product = input_image.unsqueeze(1).
                bmm(support_image.unsqueeze(2)).squeeze()
            cosine_similarity = dot_product * support_manitude
            similarities.append(cosine_similarity)
        similarities = torch.stack(similarities)
        return similarities.t()
  1. 创建BidirectionalLSTM,它将从测试图像获取输入和输出,并将它们放在相同的嵌入空间中。 如果我们希望使用全上下文嵌入,则匹配网络为此引入了双向 LSTM:
代码语言:javascript复制
class BidirectionalLSTM(nn.Module):
    def __init__(self, layer_size, batch_size, vector_dim):
        super(BidirectionalLSTM, self).__init__()
        self.batch_size = batch_size
        self.hidden_size = layer_size[0]
        self.vector_dim = vector_dim
        self.num_layer = len(layer_size)
        self.lstm = nn.LSTM(input_size=self.vector_dim, 
            num_layers=self.num_layer, 
            hidden_size=self.hidden_size, bidirectional=True)
        self.hidden = (Variable(torch.zeros(
            self.lstm.num_layers * 2, self.batch_size, 
            self.lstm.hidden_size),requires_grad=False),
            Variable(torch.zeros(self.lstm.num_layers * 2, 
            self.batch_size, self.lstm.hidden_size),
            requires_grad=False))

    def repackage_hidden(self,h):
        """Wraps hidden states in new Variables, 
        to detach them from their history."""
        if type(h) == torch.Tensor:
            return Variable(h.data)
        else:
            return tuple(self.repackage_hidden(v) for v in h)

    def forward(self, inputs):
        self.hidden = self.repackage_hidden(self.hidden)
        output, self.hidden = self.lstm(inputs, self.hidden)
        return output
  1. 现在,让我们结合所有已制成的小模块并创建一个匹配的网络:
代码语言:javascript复制
class MatchingNetwork(nn.Module):
    def __init__(self, keep_prob, batch_size=32, num_channels=1, 
    learning_rate=1e-3, fce=False, num_classes_per_set=20, 
    num_samples_per_class=1, image_size=28):
        super(MatchingNetwork, self).__init__()
        self.batch_size = batch_size
        self.keep_prob = keep_prob
        self.num_channels = num_channels
        self.learning_rate = learning_rate
        self.num_classes_per_set = num_classes_per_set
        self.num_samples_per_class = num_samples_per_class
        self.image_size = image_size
        # Let's set all peices of Matching Networks Architecture
        self.g = Embeddings_extractor(layer_size=64, 
            num_channels=num_channels, dropout_prob=keep_prob, 
            image_size=image_size)
        self.f = fce # if we are considering full-context 
                        embeddings
        self.c = DistanceNetwork() # cosine distance among 
                        embeddings
        self.a = AttentionalClassify() # softmax of cosine 
                        distance of embeddings
        if self.f: self.lstm = BidirectionalLSTM(layer_size=[32], 
            batch_size=self.batch_size, vector_dim=self.g.outSize)

    def forward(self, support_set_images, support_set_y_one_hot, 
    target_image, target_y):
        # produce embeddings for support set images
        encoded_images = []
        for i in np.arange(support_set_images.size(1)):
            gen_encode = self.g(support_set_images[:, i, :, :])
            encoded_images.append(gen_encode)

        # produce embeddings for target images
        gen_encode = self.g(target_image)
        encoded_images.append(gen_encode)
        output = torch.stack(encoded_images,dim=0)

        # if we are considering full-context embeddings
        if self.f:
            output = self.lstm(output)

        # get similarities between support set embeddings and 
        target
        similarites = self.c(support_set=output[:-1], 
        input_image=output[-1])

        # produce predictions for target probabilities
        preds = self.a(similarites, support_set_y=support_set_y_one_hot)

        # calculate the accuracy
        values, indices = preds.max(1)
        accuracy = torch.mean((indices.squeeze() == target_y).float())
        crossentropy_loss = F.cross_entropy(preds, target_y.long())

        return accuracy, crossentropy_loss
  1. 创建一个数据集加载器。 对于我们的案例,当我们使用 Omniglot 数据集时,它将创建一个 Omniglot 构建器,该构建器将调用匹配的网络并运行其周期以进行训练,测试和验证:
代码语言:javascript复制
def run_epoch(total_train_batches, name='train'):
    """
    Run the training epoch
    :param total_train_batches: Number of batches to train on
    :return:
    """
    total_c_loss = 0.0
    total_accuracy = 0.0
    for i in range(int(total_train_batches)):
            x_support_set, y_support_set, x_target, y_target = 
                get_batch(name)
            x_support_set = Variable(
                torch.from_numpy(x_support_set)).float()
            y_support_set = Variable(torch.from_numpy(y_support_set), 
                requires_grad=False).long()
            x_target = Variable(torch.from_numpy(x_target)).float()
            y_target = Variable(torch.from_numpy(y_target), 
                requires_grad=False).squeeze().long()

            # convert to one hot encoding
            y_support_set = y_support_set.unsqueeze(2)
            sequence_length = y_support_set.size()[1]
            batch_size = y_support_set.size()[0]
            y_support_set_one_hot = Variable(
                torch.zeros(batch_size, sequence_length,
                classes_per_set).scatter_(2,
                y_support_set.data,1), requires_grad=False)

            # reshape channels and change order
            size = x_support_set.size()
            x_support_set = x_support_set.permute(0, 1, 4, 2, 3)
            x_target = x_target.permute(0, 3, 1, 2)
            acc, c_loss = matchNet(x_support_set, 
                y_support_set_one_hot, x_target, y_target)

            # optimize process
            optimizer.zero_grad()
            c_loss.backward()
            optimizer.step()

            iter_out = "tr_loss: {}, tr_accuracy: 
                {}".format(c_loss, acc)
            total_c_loss  = c_loss
            total_accuracy  = acc

    total_c_loss = total_c_loss / total_train_batches
    total_accuracy = total_accuracy / total_train_batches
    return total_c_loss, total_accuracy
  1. 设置实验变量:
代码语言:javascript复制
batch_size=20
num_channels=1
lr=1e-3
image_size=28
classes_per_set=20
samples_per_class=1
keep_prob=0.0
fce=True
optim="adam"
wd=0
matchNet = MatchingNetwork(keep_prob, batch_size, num_channels, lr, 
    fce, classes_per_set, samples_per_class, image_size)
total_iter = 0
total_train_iter = 0
optimizer = torch.optim.Adam(matchNet.parameters(), lr=lr, 
    weight_decay=wd)
scheduler = ReduceLROnPlateau(optimizer, 'min',verbose=True)

# Training setup
total_epochs = 100
total_train_batches = 10
total_val_batches = 5
total_test_batches = 5
  1. 现在,运行实验:
代码语言:javascript复制
train_loss,train_accuracy=[],[]
val_loss,val_accuracy=[],[]
test_loss,test_accuracy=[],[]

for e in range(total_epochs):
    ############################### Training Step ##########################################
    total_c_loss, total_accuracy = 
        run_epoch(total_train_batches,'train')
    train_loss.append(total_c_loss)
    train_accuracy.append(total_accuracy)

    ################################# Validation Step #######################################
    total_val_c_loss, total_val_accuracy = 
        run_epoch(total_val_batches, 'val')
    val_loss.append(total_val_c_loss)
    val_accuracy.append(total_val_accuracy)
    print("Epoch {}: train_loss:{:.2f} train_accuracy:{:.2f} 
        valid_loss:{:.2f} valid_accuracy:{:.2f}".format(e, 
        total_c_loss, total_accuracy, total_val_c_loss, 
        total_val_accuracy))

运行此代码块后,您将看到模型开始训练并打印以下输出:

代码语言:javascript复制
Epoch 0: train_loss:2.99 train_accuracy:0.11 valid_loss:2.98 valid_accuracy:0.22
Epoch 1: train_loss:2.97 train_accuracy:0.20 valid_loss:2.97 valid_accuracy:0.28
Epoch 2: train_loss:2.95 train_accuracy:0.31 valid_loss:2.94 valid_accuracy:0.37
  1. 现在,通过运行以下代码块来获得测试的准确率:
代码语言:javascript复制
total_test_c_loss, total_test_accuracy = run_epoch(total_test_batches,'test')
print("test_accuracy:{}%".format(total_test_accuracy*100))

运行此代码块后,您将看到以下输出:

代码语言:javascript复制
test_accuracy:86.0%
  1. 让我们可视化我们的结果:
代码语言:javascript复制
def plot_loss(train,val,name1="train_loss",name2="val_loss"):
    plt.plot(train, label=name1)
    plt.plot(val, label=name2)
    plt.legend()

plot_loss(train_loss,val_loss)

运行这些单元格后,您将看到如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zFmgUaC-1681785569480)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/97d99dae-968d-45c3-9d2f-13cf0b663c2b.png)]

在本节中,我们探索了使用 MNIST 数据集的连体网络的实现以及使用 Omniglot 数据集的匹配网络架构。 在连体网络编码练习中,我们创建了一个小的卷积层,并由一个全连接层姐妹架构进行了扩展。 训练模型后,我们还绘制了模型获得的二维嵌入图,并观察了某些数字如何聚类在一起。 同样,在匹配网络编码练习中,我们为匹配网络的每个模块实现了小型架构,例如嵌入提取器,注意力模型和完全上下文嵌入。 我们还观察到,仅用 100 个周期,我们就可以达到约 86% 的精度,并绘制了匹配网络架构的精度和损失图。

您可能还观察到某些模型是从头开始训练的-我们可能已经使用了迁移学习架构,或者增加了 LSTM 架构的隐藏大小,或者也许被认为是加权的交叉熵损失函数。 总是有实验和改进的空间。 如果您想进一步尝试使用该模型,建议您访问本书的 GitHub 页面。

总结

在本章中,我们学习了基于指标的单样本学习方法。 我们探索了两种神经网络架构,它们已在研究界和软件行业中用于单样本学习。 我们还学习了如何评估经过训练的模型。 然后,我们使用 MNIST 数据集在连体网络中执行了一个练习。 总之,可以说匹配网络和连体网络架构都已经成功证明,通过更改损失函数或特征表示,我们可以用有限的数据量实现目标。

在下一章中,我们将探索不同的基于优化的方法,并了解它们与基于度量的方法之间的区别。

问题

  1. 什么是相似度指标? 为什么余弦相似度最有效?
  2. 为什么匹配网络使用 LSTM 架构来获取嵌入?
  3. 对比损失函数有哪些缺点,三重损失函数如何帮助解决它?
  4. 维度的诅咒是什么? 我们该如何处理?

进一步阅读

要更深入地了解本章介绍的架构,并探讨它们的工作方式和原因,请阅读以下文章:

  • 《用于一次图像识别的连体神经网络》
  • 《单样本学习的匹配网络》

三、基于模型的方法

在上一章中,我们讨论了两种基于优化的方法。 我们试图用元学习机制来训练模型,这与人类所见相似。 当然,除了学习新事物的能力外,人类在执行任何任务时还可以访问大量内存。 通过回忆过去的经历和经验,这使我们能够更轻松地学习新任务。 遵循相同的思想过程,设计了基于模型的架构,并添加了外部存储器以快速概括单样本学习任务。 在这些方法中,使用存储在外部存储器中的信息,模型仅需几个训练步骤即可收敛。

本章将涵盖以下主题:

  • 了解神经图灵机
  • 记忆增强神经网络
  • 元网络
  • 编码练习

技术要求

您将需要 Python,Anaconda,Jupyter 笔记本,PyTorch 和 Matplotlib 库在本章中学习和执行项目。

您可以在本书的 GitHub 存储库中找到本章的代码文件。

了解神经图灵机

在 AI 的早期,该领域主要由一种象征性的处理方法主导。 换句话说,它依靠使用符号和结构以及操纵它们的规则来处理信息。 直到 1980 年代,人工智能领域才采用了另一种方法-连接主义。 连接主义最有前景的建模技术是神经网络。 但是,他们经常遭到两个严厉的批评:

  • 神经网络仅接受固定大小的输入,这在输入长度可变的现实生活中不会有太大帮助。
  • 神经网络无法将值绑定到我们已知的两个信息系统(人脑和计算机)大量使用的数据结构中的特定位置。 简单来说,在神经网络中,我们无法将特定的权重设置到特定的位置。

第一个问题可以通过在各种任务上实现最先进表现的 RNN 来解决。 通过查看神经图灵机NTM)可以解决第二个问题。 在本节中,我们将讨论 NTM 的总体架构,这是理解记忆增强神经网络MANN)的基础,这些神经网络修改了 NMT 的架构并使之适用于单样本学习任务。

NTM 的架构

在过去的 50 年中,现代计算机发生了很大的变化。 但是,它们仍然由三个系统组成-内存,控制流和算术/逻辑运算。 来自生物学和计算神经科学领域的研究提供了广泛的证据,表明记忆对于快速有效地存储和检索信息至关重要。 从中汲取灵感,NTM 基本上由神经网络组成,该神经网络由控制器和称为存储库(或存储矩阵)的二维矩阵组成。 在每个时间步长,神经网络都会接收一些输入,并生成与该输入相对应的输出。 在这样做的过程中,它还访问内部存储库并对其执行读取和/或写入操作。 从传统的图灵机中汲取灵感,NMT 使用术语头部来指定内存位置。 下图显示了总体架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trrJCU4K-1681785569480)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/29d7adbd-0103-4c43-b5b0-c2a65a121d48.png)]

整体架构看起来不错; 但是,这有一个问题。 如果通过在内存矩阵中指定行索引和列索引来访问内存位置,则无法获取该索引的梯度。 此操作不可反向传播,并且会使用标准的反向传播和基于梯度下降的优化技术来限制 NMT 的训练。 为了解决此问题,NTM 的控制器使用模糊读写操作与内存进行交互,这些操作与内存的所有元素进行不同程度的交互。 更准确地说,控制器以差分方式在所有存储位置上生成权重,这有助于使用基于标准梯度的优化方法从头到尾训练网络。

在下一节中,我们将讨论如何产生这些权重以及如何执行读写操作。

建模

在时间步tM[t])的存储矩阵具有R行和C列。 有一种注意力机制,用于指定注意头应该读取/写入的内存位置。 控制器生成的注意力向量是长度R的向量,称为权重向量w[t]),其中向量w[t](i)的条目是存储库第i行的权重。 权重向量已标准化,这意味着它满足以下条件:*

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7A2AFjWA-1681785569480)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/eed93f02-4abc-465e-b009-2912f63ccb39.png)]

读取

读取头将返回长度为C的向量, r[t],它是存储器行M[t](i)由权重向量缩放:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-83DMFHmv-1681785569480)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/7b4dda93-17d9-4c36-ab06-7af57479b100.png)]

写入

写入是两个步骤的结合:擦除和添加。 为了擦除旧数据,写头使用附加长度C擦除向量e[t]以及权重向量。 以下方程式定义了擦除行的中间步骤:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kistk1um-1681785569480)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/cb6720e7-9a40-4e30-b015-966dd1074fb0.png)]

最后,写入头使用长度C的向量, a[t]以及M_erased[t]根据前面的方程式和权重向量更新存储矩阵的行:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-agR1IydJ-1681785569480)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/6d9c4431-8618-4953-a442-b2320bd662fb.png)]

寻址

读取和写入操作的关键是权重向量,该权重向量指示要从中读取/写入的行。 控制器分四个阶段生成此权重向量。 每个阶段都会产生一个中间向量,该向量将传递到下一个阶段:

  • 第一步是基于内容的寻址,其目的是基于每一行与给定的长度为C的给定关键字向量k[t]的相似度来生成权重向量。 更精确地说,控制器发出向量k[t],并使用余弦相似性度量将其与M[t]的每一行进行比较。 如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XmBkp4HH-1681785569481)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/d338c2ca-2226-4543-abc4-5ab988fc0a28.png)]

内容权重向量尚未规范化,因此可以通过以下操作进行规范化:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rVPXuOk8-1681785569481)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/e9fa7fd5-b836-4dc6-a320-74200df1183a.png)]

  • 第二阶段是基于位置的寻址,其重点是从特定存储位置读取/写入数据,而不是在阶段 1 中完成的位置值。其后,标量参数g[t] ∈ (0, 1)称为插值门,将内容权重向量w[t]^c与前一个时间步的权重向量w[t-1]混合,以产生门控权重w[t]^g。 这使系统能够学习何时使用(或忽略)基于内容的寻址:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzhRclXJ-1681785569481)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/906b96f2-1a9f-4b09-ad23-f551d40f82d5.png)]

  • 在第三阶段,插值后,头部发出归一化的移位加权s[t],以执行R模的移位运算(即,向上或向下移动行)。 这由以下操作定义:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wmwCGGqe-1681785569481)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/8ad09342-cba4-4cfe-8b3e-3536756a491d.png)]

  • 第四个也是最后一个阶段,锐化,用于防止偏移的权重w_tilde[t]模糊。 这是使用标量γ >= 1并应用以下操作完成的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RZscwMJJ-1681785569481)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/f6a0bbfd-bfe5-4d2d-8d0e-015fc55c1642.png)]

所有操作(包括读取,写入和寻址的四个阶段)都是差分的,因此可以使用反向传播和任何基于梯度下降的优化器从头到尾训练整个 NMT 模型。 控制器是一个神经网络,可以是前馈网络,也可以是循环神经网络,例如长短期记忆LSTM)。 它已显示在各种算法任务(例如复制任务)上均具有良好的表现,这些任务将在本章的稍后部分实现。

既然我们了解了 NTM 的架构和工作原理,我们就可以开始研究 MANN,这是对 NMT 的修改,并且经过修改可以在单样本学习中表现出色。

记忆增强神经网络

MANN 的目标是在单样本学习任务中表现出色。 正如我们之前阅读的,NMT 控制器同时使用基于内容的寻址和基于位置的寻址。 另一方面,MANN 控制器仅使用基于内容的寻址。 有两个原因。 原因之一是单样本学习任务不需要基于位置的寻址。 在此任务中,对于给定的输入,控制器可能只需要执行两个操作,并且这两个操作都与内容有关,而与位置无关。 当输入与先前看到的输入非常相似时,将采取一种措施,在这种情况下,我们可以更新内存的当前内容。 当当前输入与以前看到的输入不相似时,将采取另一种操作,在这种情况下,我们不想覆盖最近的信息。 相反,我们写到使用最少的内存位置。 在这种情况下,该存储模块称为最久未使用的访问LRUA)模块。

读取

MANN 的读取操作与 NTM 的读取操作非常相似,唯一的区别是此处的权重向量仅使用基于内容的寻址(NMT 寻址的阶段 -1)。 更准确地说,控制器使用标准化的读取权重向量w[t]^r,将其与M[t]的行一起使用以生成读取向量,r[t]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ZQhEgEW-1681785569482)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/dc63f6b2-8a4b-4774-8ed6-0d720f31bb7b.png)]

读取权重向量w[t]^r由控制器产生,该控制器由以下操作定义:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6LHaCXoZ-1681785569482)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/b947ff46-f23a-45ed-a252-2ceaa0ca82b6.png)]

在此,运算K()是余弦相似度,类似于为 NMT 定义的余弦相似度。

写入

为了写入存储器,控制器在写入最近读取的存储器行和写入最近读取的存储器行之间进行插值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1DYAg09m-1681785569482)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/940cbf9a-4794-43e5-917a-171886d6871a.png)]

通过对 Omniglot 数据集进行一次一次性分类任务,MANN 已显示出令人鼓舞的结果。 由于其基本的模型 NTM,它们表现良好。 NTM 能够快速编码,存储和检索数据。 它们还能够存储长期和短期权重。 可以使用 MANN 的方法添加 NTM,以跟踪最久未使用的存储位置,以执行基于内容的寻址,以读取和写入最久未使用的位置。 它使 MANN 成为少量学习的理想人选。

在下一部分中,我们将学习另一种基于模型的架构,该架构由四个架构的网络组成,并为单样本学习领域做出了重大贡献。

了解元网络

顾名思义,元网络是基于模型的元学习方法的一种形式。 在通常的深度学习方法中,神经网络的权重是通过随机梯度下降来更新的,这需要大量的时间来训练。 众所周知,随机梯度下降法意味着我们将考虑每个训练数据点进行权重更新,因此,如果我们的批量大小为 1,这将导致模型优化非常缓慢-换句话说,较慢的权重更新。

元网络建议通过训练与原始神经网络并行的神经网络来预测目标任务的参数,从而解决权重缓慢的问题。 生成的权重称为快速权重。 如果您还记得的话,LSTM 元学习器(请参阅第 4 章,“基于优化的方法”)也是基于类似的基础来预测使用 LSTM 单元的任务的参数更新 。

与其他元学习方法类似,元网络包含两个级别:

  • 元学习器:元学习器获得有关不同任务的一般知识。 在元网络的情况下,这是一个嵌入函数,用于比较两个不同数据点的特征。
  • 基础学习器:基础学习器尝试学习目标任务(任务目标网络可以是简单的分类器)。

元级学习器的目标是获得有关不同任务的一般知识。 然后可以将知识迁移到基础级学习器,以在单个任务的上下文中提供概括。

如所讨论的,元网络学习权重的两种形式:慢权重和快权重。 要为元学习器(嵌入函数)和基础学习器(分类模型)两者学习这些权重,我们需要两个不同的网络。 这使得元网络成为迄今为止我们在本书中讨论过的最复杂的网络之一。 简而言之,元网络由四种类型的神经网络组成,它们各自的参数要训练。 在下一节中,我们将遍历元网络中存在的每个网络,并了解其架构。

元网络算法

要开始学习元网络,我们首先需要定义以下项:

  • 支持集:训练集中的采样输入数据点(xy)。
  • 测试集:来自训练集的采样数据点(x)。
  • 嵌入函数f[θ]):作为元学习器的一部分,嵌入函数与连体网络非常相似。 经过训练可以预测两个输入是否属于同一类。
  • 基本学习器模型g[φ]):基本学习器模型尝试完成实际的学习任务(例如,分类模型)。
  • θ⁺:嵌入函数的快速权重,(f[θ])。
  • φ⁺:基本学习器模型的快速权重(g[φ])。
  • F[w]:一种 LSTM 架构,用于学习嵌入函数的快速权重θf[θ])的慢速网络。
  • G[v]:通过v学习快速权重φ参数化的神经网络,用于基础学习器g[φ],来自其损失梯度。

下图说明了元网络架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HG064yIu-1681785569482)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/7ee8a46f-a88d-4310-a261-da0df68aa4f7.png)]

如图所示,元学习器基础学习器由较慢的权重(θ, φ)组成。 为了学习快速权重(θ⁺, φ⁺),元网络使用两个不同的网络:

  • LSTM 网络(F[w]),学习嵌入函数的(元学习器)快速权重-即θ⁺
  • 神经网络(G[v]),以学习基本学习器的快速权重,即φ⁺

现在我们已经了解了快速权重和慢速权重的概念和架构,让我们尝试观察整个元网络架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k0J7P1e0-1681785569482)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/d98e350d-6d91-4bff-b79d-6f33824b0e59.png)]

如上图所示,元网络由基本学习器和配备外部存储器的元学习器(嵌入函数)组成。 我们还可以看到快速参数化箭头同时进入元学习器和基础学习器。 这些是元权重的输出,其中包括用于学习快速权重的模型。

现在让我们简单介绍一下训练。 随着训练输入数据的到来,它会同时通过元学习器和基础学习器。 在元学习器中,它用于连续学习(更新参数),而在基础学习器中,对输入进行预处理之后,它将元信息梯度)传递给元 -学习器。 之后,元学习器使用元信息梯度)将参数化更新快速返回给基础学习器,以通过使用慢速权重和快速权重的集成来进行优化(如图所示) 在下图中)。 元网络的基本关键思想是通过处理元信息以快速的方式学习权重以进行快速概括。

在 MetaNet 中,学习器的损失梯度是任务的元信息。 MetaNet 还有一个重要的问题:它如何使用快速权重和慢速权重进行预测?

在 MetaNet 中,将慢速权重和快速权重组合在一起,以在神经网络中进行预测,如下图所示。 在这里,表示元素方式的和:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ewDk3StF-1681785569483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/e3036523-f829-413a-85ad-fe9838ae6684.png)]

在下一节中,我们将逐步介绍算法,提取元信息以及最终模型预测。

算法

元网络也遵循与匹配网络相似的训练过程。 在此,训练数据分为两种类型:支持集S = (x'[i], y'[i])和测试集U = (x[i], y[i])

请记住,目前,我们有四个网络(f[θ], g[φ], F[w], G[v]))和四组要学习的模型参数(θ, ϕ, w, v)。 让我们看一下算法的所有步骤:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nq94NlUA-1681785569483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/6a8a41af-c6a4-471a-8998-920cf7d1d0c7.png)]

以下是算法的步骤:

  1. 支持集S的样本的K个随机对。

对于t = 1, ..., K

  • 通过嵌入函数f[θ]正向传播数据点。
  • 计算交叉熵损失(L_emb)。
  1. 通过 LSTM 网络向前传递数据以计算θ⁺θ⁺ = F[w](▽L_emb)
  2. 接下来,遍历支持集S中的示例,并为每个示例计算快速权重。 同时,使用获得的嵌入内容更新外部存储器。

对于i = 1, ..., K

  • 将基本学习器g[φ](x[i])(例如,分类模型)向前传递,并计算损失函数L_task[i](例如,交叉熵)。
  • 计算基本学习器梯度▽L_task[i],并使用它们来计算示例级快速权重φ⁺[i] = G[v](▽L_task[i])
  • 将基础学习器φ⁺[i]的计算得出的快速权重存储在存储器的M部分的i位置处。
  • 在嵌入网络中使用合并快速权重和缓慢权重。
  • 将支持样本转发通过嵌入网络并获得嵌入r'[i] = f[θ,θ⁺](x[i])
  • r'[i]存储在内存Rk部分的i位置。
  1. 最后,是时候使用测试集U = (x[i], y[i])构建训练损失了。 从L_train = 0开始。

对于j = 1, ..., L

  • 将测试样本转发通过嵌入网络,并获得测试嵌入r'[i] = f[θ,θ⁺](x[j])
  • 计算支持集的存储嵌入R和获得的嵌入r[j]之间的相似度。 您可以使用a[j] = cos(R, r[j])来执行此操作。 在此,R是指存储在外部存储器中的数据。
  • 现在,通过使用支持集样本(M)的快速权重来计算基础学习器的快速权重(φ⁺)。 您可以使用φ⁺[j] = softmax(a[j])^T M来执行此操作。 在此,M是指存储在外部存储器中的数据。
  • 使用最新的φ⁺将测试样本向前传递通过基本学习器,并计算损失函数L_task[i]
  • 使用公式更新训练损失: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-melCfz13-1681785569483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/14423e78-c644-4cec-b7dc-f5879e000a7a.png)]
  1. 使用L_train更新所有参数(θ, ϕ, w, v)

在选择嵌入网络时,元网络使用 LSTM 架构。 如我们所见,匹配网络和 LSTM 元学习器也遵循相同的策略,分别用于提取数据和元信息的上下文嵌入。 这是因为 LSTM 架构倾向于记住历史,这使得元学习器的目标能够跨任务提取重要信息。

例如,假设我们正在训练我们的网络以完成多种任务,例如猫的品种分类和狗的品种分类。 当我们使用 LSTM 元学习器进行训练时,它会学习例如狗品种分类中权重更新的策略,并使用这些学习到的信息以较少的步骤和较少的数据来优化其用于猫品种分类的操作。 使用元网络在 Omniglot 数据集上达到了 95.92% 的准确率,而人类的准确率仅为 95.5%,因此,元网络被认为是最新模型之一。

编码练习

在本节中,我们将首先介绍 NTM 的实现,然后再使用 Omniglot 数据集介绍 MAAN。 所以,让我们开始吧!

本练习不包括代码的某些部分。 如果希望获得可运行的代码,请在这个页面中查看本书的 GitHub 存储库。

NTM 的实现

如上所述,NTM 由两个重要组成部分组成:

  • 神经网络,也称为控制器
  • 称为记忆的二维矩阵

在本教程中,我们将实现两者的简化版本,并尝试展示复制任务。

任务目标如下:

  • NTM 模型显示为T时间步长的随机k维向量。
  • 网络的工作是在每个时间步从零向量输出这些T[k]维随机向量。

执行以下步骤来实现 NTM:

  1. 首先,导入所有必需的库:
代码语言:javascript复制
import torch
from torch import nn
import torch.nn.functional as F
import numpy as np
from time import time
import torchvision.utils as vutils
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
%matplotlib inline
  1. 然后,实现Controller。 作为控制器的一部分,我们将实现以下三个组件:
    • 两层前馈网络
    • 使用 Xavier 方法进行权重初始化
    • Sigmoid 非线性
代码语言:javascript复制
class Controller(nn.Module):
    def __init__(self, input_size, output_size, hidden_size):
        super(Controller, self).__init__()
        self.layer1 = nn.Linear(input_size, hidden_size)
        self.layer2 = nn.Linear(hidden_size, output_size)
        self.intialize_parameters()

    def intialize_parameters(self):
        # Initialize the weights of linear layers
        nn.init.xavier_uniform_(self.layer1.weight, gain=1.4) 
        nn.init.normal_(self.layer1.bias, std=0.01)
        nn.init.xavier_uniform_(self.layer2.weight, gain=1.4)
        nn.init.normal_(self.layer2.bias, std=0.01)

    def forward(self, x, last_read):
        # Forward pass operation, depending on last_read operation
        x = torch.cat((x, last_read), dim=1)
        x = torch.sigmoid(self.layer1(x))
        x = torch.sigmoid(self.layer2(x))
        return x

我们也可以有一个 LSTM 控制器,但是由于简单起见,我们构建了一个两层的全连接控制器。

  1. 接下来,实现Memory模块。 Memory是一个二维矩阵,具有M行和N列:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hsrIEyx3-1681785569483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/3a4d2e1c-a7df-4316-b1cd-5a67640f0bdf.png)]

address()函数执行存储器寻址,它由四个函数组成:

  • similarity
  • interpolate
  • shift
  • sharpen
代码语言:javascript复制
class Memory(nn.Module):
    def __init__(self, M, N, controller_out):
        super(Memory, self).__init__()
        self.N = N
        self.M = M
        self.read_lengths = self.N   1   1   3   1
        self.write_lengths = self.N   1   1   3   1   self.N   
            self.N
        self.w_last = [] # define to keep track of weight_vector 
        at each time step.
        self.reset_memory()

    def address(self, k, beta, g, s, gamma, memory, w_last):
        # Content focus
        wc = self._similarity(k, beta, memory) # CB1 to CB3 
        equations
        # Location focus
        wg = self._interpolate(wc, g, w_last) # CS1 equation
        w_hat = self._shift(wg, s) # CS2 and CS3 equation
        w = self._sharpen(w_hat, gamma) # S1 equation
        return w

    # Implementing Similarity on basis of CB1 followed by CB2 
    and CB3 Equation
    def _similarity(self, k, beta, memory):
        w = F.cosine_similarity(memory, k, -1, 1e-16) # CB1 
        Equation
        w = F.softmax(beta * w, dim=-1) # CB2 and CB3 Equation
        return w # return CB3 equation obtained weights

    # Implementing CS1 Equation. It decides whether to use 
    the weights we obtained
    # at the previous time step w_last or use the weight 
    obtained through similarity(content focus)
    def _interpolate(self, wc, g, w_last):
        return g * wc   (1 - g) * w_last
# .... Rest Code is available at Github......
  1. 接下来,执行read操作。 在这里,我们将定义ReadHead,它可以根据read操作访问和更新内存:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRLV2MNX-1681785569483)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/006a56d0-58c8-429e-a160-c9f33966e585.png)]

代码语言:javascript复制
class ReadHead(Memory):
    # Reading based on R2 equation
    def read(self, memory, w):
        return torch.matmul(w, memory)

    # Use Memory class we formed above to create a ReadHead 
    operation
    def forward(self, x, memory):
        param = self.fc_read(x) # gather parameters
        # initialize necessary parameters k, beta, g, shift, 
        and gamma
        k, beta, g, s, gamma = torch.split(param, 
            [self.N, 1, 1, 3, 1], dim=1)
        k = torch.tanh(k)
        beta = F.softplus(beta)
        g = torch.sigmoid(g)
        s = F.softmax(s, dim=1)
        gamma = 1   F.softplus(gamma) 
        # obtain current weight address vectors from Memory
        w = self.address(k, beta, g, s, gamma, memory, 
            self.w_last[-1])
        # append in w_last function
        self.w_last.append(w)
        mem = self.read(memory, w) 
        return mem, w
  1. read操作类似,在这里我们将执行write操作:
代码语言:javascript复制
class WriteHead(Memory):
    def write(self, memory, w, e, a):
        # Implement write function based on E1 and A1 Equation
        w, e, a = torch.squeeze(w), torch.squeeze(e), 
            torch.squeeze(a)
        erase = torch.ger(w, e)
        m_tilde = memory * (1 - erase) # E1 equation
        add = torch.ger(w, a)
        memory_update = m_tilde   add # A1 equation
        return memory_update

    def forward(self, x, memory):
        param = self.fc_write(x) # gather parameters
        # initialize necessary parameters k, beta, g, shift, 
        and gamma
        k, beta, g, s, gamma, a, e = torch.split(param, 
            [self.N, 1, 1, 3, 1, self.N, self.N], dim=1)
        k = torch.tanh(k)
        beta = F.softplus(beta)
        g = torch.sigmoid(g)
        s = F.softmax(s, dim=-1)
        gamma = 1   F.softplus(gamma)
        a = torch.tanh(a)
        e = torch.sigmoid(e)
        # obtain current weight address vectors from Memory
        w = self.address(k, beta, g, s, gamma, memory, 
            self.w_last[-1])
        # append in w_last function
        self.w_last.append(w)
        # obtain current mem location based on R2 equation
        mem = self.write(memory, w, e, a)
        return mem, w

ReadHeadWriteHead都使用全连接层来生成用于内容编址的参数(kbetagsgamma)。

  1. 实现神经图灵机结构,其中包括:
  • 全连接控制器
  • 读写头部
  • 记忆体参数
  • 在无法训练的记忆上运行的工具函数
代码语言:javascript复制
class NTM(nn.Module):
    def forward(self, X=None):
        if X is None:
            X = torch.zeros(1, self.num_inputs)
        controller_out = self.controller(X, self.last_read) 
        self._read_write(controller_out)
        # use updated last_read to get sequence
        out = torch.cat((X, self.last_read), -1)
        out = torch.sigmoid(self.fc_out(out))

        return out

    def _read_write(self, controller_out):
        # Read Operation
        read, w = self.read_head(controller_out, self.memory)
        self.last_read = read
        # Write Operation
        mem, w = self.write_head(controller_out, self.memory)
        self.memory = mem

forward函数中,X可以是None。 这是因为,在复制任务中,针对一个特定序列,训练分两步进行:

  1. 在第一步中,网络显示为t时间步长的k维输入。
  2. 在第二步(预测步骤)中,网络采用k维零向量来产生预测,该预测对每个时间步执行输入的复制。
  3. 在这里,我们正在为复制任务生成向量的随机序列。 它由 NTM 模型复制:
代码语言:javascript复制
class BinaySeqDataset(Dataset):

    def __init__(self, sequence_length, token_size, 
    training_samples):
        self.seq_len = sequence_length
        self.seq_width = token_size
        self.dataset_dim = training_samples

    def _generate_seq(self):
        # A special token is appened at beginning and end of each
        # sequence which marks sequence boundaries.
        seq = np.random.binomial(1, 0.5, (self.seq_len, self.seq_width))
        seq = torch.from_numpy(seq)
        # Add start and end token
        inp = torch.zeros(self.seq_len   2, self.seq_width)
        inp[1:self.seq_len   1, :self.seq_width] = seq.clone()
        inp[0, 0] = 1.0
        inp[self.seq_len   1, self.seq_width - 1] = 1.0
        outp = seq.data.clone()

        return inp.float(), outp.float()

    def __len__(self):
        return self.dataset_dim

    def __getitem__(self, idx):
        inp, out = self._generate_seq()
        return inp, out
  1. 我们还将实现梯度剪切,因为剪切梯度通常是一个好主意,以使网络在数值上稳定:
代码语言:javascript复制
def clip_grads(net, min_grad=-10,max_grad=10):
    parameters = list(filter(lambda p: p.grad is not None, net.parameters()))
    for p in parameters:
        p.grad.data.clamp_(min_grad,max_grad)
  1. 初始化训练参数:
代码语言:javascript复制
memory_capacity=64
memory_vector_size=128
controller_output_dim=256
controller_hidden_dim=512
learning_rate=1e-2

sequence_length, token_size, training_samples = 2, 10, 99
min_grad, max_grad = -10, 10
  1. 然后,初始化训练模型:
代码语言:javascript复制
# Initialize the dataset
dataset = BinaySeqDataset(sequence_length, token_size, training_samples)
dataloader = DataLoader(dataset, batch_size=1,shuffle=True, num_workers=4)
model = NTM() # Initialize NTM
criterion = torch.nn.BCELoss()
optimizer = torch.optim.RMSprop(model.parameters(), lr=learning_rate)
losses = []
# Train the Model
for e, (X, Y) in enumerate(dataloader):
    tmp = time()
    model.initalize_state()
    optimizer.zero_grad()
    inp_seq_len = sequence_length   2
    out_seq_len = sequence_length
    X.requires_grad = True
    # Forward Pass: Feed the Sequence
    for t in range(0, inp_seq_len):
        model(X[:, t])
    # Predictions: Obtain the already feeded sequence
    y_pred = torch.zeros(Y.size())
    for i in range(0, out_seq_len):
        y_pred[:, i] = model() # Here, X is passed as None
    loss = criterion(y_pred, Y)
    loss.backward()
    clip_grads(model)
    optimizer.step()
    losses  = [loss.item()]
    if (e==0):
        print("iteration: {}, Loss:{} ".format(e, loss.item()))
    if e == 5000:
        break

运行此单元格后,您将看到以下输出:

代码语言:javascript复制
iteration: 0, Loss:0.7466866970062256 
iteration: 10, Loss:0.7099956274032593 
iteration: 20, Loss:0.6183871626853943 
iteration: 30, Loss:0.6750341653823853 
iteration: 40, Loss:0.7050653696060181 
iteration: 50, Loss:0.7188648581504822
  1. 定义一个plot_signal函数并绘制训练损失losses
代码语言:javascript复制
def plot_signal(grid_image, fig_size=(500,100)):
    plt.figure(figsize=fig_size)
    plt.imshow(grid_image.data.permute(2, 1, 0))

plt.plot(losses)
plt.show()
  1. 测试 NTM 模型的复制任务:
代码语言:javascript复制
X, Y = dataset._generate_seq()
X, Y = X.unsqueeze(0), Y.unsqueeze(0)# Add the batch dimension

model.initalize_state()

for t in range(0, inp_seq_len):
    model(X[:, t])

y_pred = torch.zeros(Y.size())
for i in range(0, out_seq_len):
    y_pred[:, i] = model()

grid_img_truth = vutils.make_grid(Y, normalize=True, scale_each=True)
grid_img_pred = vutils.make_grid(y_pred, normalize=True, scale_each=True)

plt.figure(figsize=(200,200))
plt.imshow(grid_img_truth.data.permute(2, 1, 0))

plt.figure(figsize=(200,200))
plt.imshow(grid_img_pred.data.permute(2, 1, 0))

运行前面的代码将给出以下输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GEebHoA4-1681785569484)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/0dc925d0-48d7-4b5c-958f-fe2f911fdb8d.png)]

在这里,我们创建了一个 300 个时间步长的随机信号,并观察了模型复制该信号的程度。 在此步骤中,您观察了复制任务输出。 这两个信号应该非常接近。 如果不是,我们建议您更多地训练模型。

MAAN 的实现

正如我们在上一节中展示的那样,NTM 的控制器能够使用基于内容的寻址,基于位置的寻址或同时使用这两种,而 MANN 则使用纯基于内容的内存写入器来工作。

MANN 还使用一种称为最久未访问的新寻址方案。 该模式背后的思想是,最久未使用的内存位置由读取操作确定,而读取操作由基于内容的寻址执行。 因此,我们基本上执行基于内容的寻址,以读取和写入最久未使用的位置。

在本教程中,我们将实现readwrite操作。

  1. 首先,导入所需的所有库:
代码语言:javascript复制
import torch
from torch import nn
import torch.nn.functional as F
import numpy as np
import copy
  1. 实现类似于 NTM 的Memory模块,并对 MANN 进行一些更改:
代码语言:javascript复制
class Memory(nn.Module):

 def __init__(self, M, N, controller_out):
     super(Memory, self).__init__()
     self.N = N
     self.M = M
     self.read_lengths = self.N   1   1   3   1
     self.write_lengths = self.N   1   1   3   1   self.N   self.N
     self.w_last = [] # define to keep track of weight_vector at 
     each time step
     self.reset_memory()

 def address(self, k, beta, g, s, gamma, memory, w_last):
     # Content focus
     w_r = self._similarity(k, beta, memory)
     return w_r

 # Implementing Similarity
 def _similarity(self, k, beta, memory):
     w = F.cosine_similarity(memory, k, -1, 1e-16) 
     w = F.softmax(w, dim=-1)
     return w # return w_r^t for reading purpose
  1. 定义ReadHead,使其可以根据read操作访问和更新内存:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LaxQm6Z8-1681785569484)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/295304f9-1168-4b66-8274-4550618d7e6e.png)]

ReadHead函数定义如下:

代码语言:javascript复制
class ReadHead(Memory):
    def read(self, memory, w):
        # Calculate Memory Update
        return torch.matmul(w, memory)

    def forward(self, x, memory):
        param = self.fc_read(x) # gather parameters
        # initialize necessary parameters k, beta, g, shift, and 
        gamma
        k, g, s, gamma = torch.split(param, [self.N, 1, 1, 3, 1], 
            dim=1)
        k = torch.tanh(k)
        g = F.sigmoid(g)
        s = F.softmax(s, dim=1)
        gamma = 1   F.softplus(gamma)
        # obtain current weight address vectors from Memory
        w_r = self.address(k, g, s, gamma, memory, self.w_last[-1])
        # append in w_last function to keep track content based 
        locations
        self.w_last.append(w_r)
        # obtain current mem location based on above equations
        mem = self.read(memory, w_r)
        w_read = copy.deepcopy(w_r)
        return mem, w_r
  1. read操作类似,在这里我们将执行write操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6eoF5Q1I-1681785569484)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/1560e10f-2d4c-4532-a273-9d87c961a89e.png)]

write操作的实现如下:

代码语言:javascript复制
class WriteHead(Memory):

    def usage_weight_vector(self, prev_w_u, w_read, w_write, 
    gamma):
        w_u = gamma * prev_w_u   torch.sum(w_read, dim=1)   
            torch.sum(w_write, dim=1)
        return w_u # Equation F2

    def least_used(self, w_u, memory_size=3, n_reads=4):
        _, indices = torch.topk(-1*w_u,k=n_reads)
        wlu_t = torch.sum(F.one_hot(indices, 
            memory_size).type(torch.FloatTensor),dim=1,
            keepdim=True)
        return indices, wlu_t

    def mann_write(self, memory, w_write, a, gamma, prev_w_u, 
    w_read, k):
        w_u = self.usage_weight_vector(prev_w_u, w_read, w_write, 
            gamma)
        w_least_used_weight_t = self.least_used(w_u)
        # Implement write step as per (F3) Equation
        w_write = torch.sigmoid(a)*w_read   
            (1-torch.sigmoid(a))*w_least_used_weight_t
        memory_update = memory   w_write*k # Memory Update 
        as per Equation (F4)

    def forward(self, x, memory):
        param = self.fc_write(x) # gather parameters
        k, beta, g, s, gamma, a, e = torch.split(param, 
            [self.N, 1, 1, 3, 1, self.N, self.N], dim=1)
        k = F.tanh(k)
        beta = F.softplus(beta)
        g = F.sigmoid(g)
        s = F.softmax(s, dim=-1)
        gamma = 1   F.softplus(gamma)
        a = F.tanh(a)
        # obtain current weight address vectors from Memory
        w_write = self.address(k, beta, g, s, gamma, memory, 
            self.w_last[-1])
        # append in w_last function to keep track content 
        based locations
        self.w_last.append(w_write)
        # obtain current mem location based on F2-F4 equations
        mem = self.write(memory, w_write, a, gamma, prev_w_u, 
            w_read, k)
        w_write = copy.deepcopy(w)
        return mem, w

ReadHeadWriteHead都使用全连接层来生成用于内容编址的参数(kbetagsgamma)。

请注意,此练习只是为了展示 MANN 如何受到 NTM 的启发。 如果您想在真实的数据集上探索前面的练习,请参考GitHub 存储库。

总结

在本章中,我们探索了用于单样本学习的不同形式的基于模型的架构。 我们观察到的最常见的事情是使用外部存储器,这对学习神经网络不同阶段的表示形式有什么帮助。 NMT 方法在单样本学习任务中表现良好,但是由于手工设计的内存寻址函数,它们的能力仍然有限,因为它们必须具有差异性。 探索更复杂的函数来处理内存可能很有趣。 在元网络中,我们看到了如何定义一个新网络以实现对原始网络的快速学习,以及如何在元学习器级别上存储有关表示的信息如何在基础级别上微调参数。 尽管基于模型的架构是实现单样本学习的好方法,但它们具有外部存储器的先决条件,因此与其他方法相比,实现基于模型的架构的成本昂贵。

在下一章中,我们将介绍基于优化的方法,例如与模型无关的元学习和 LSTM 元学习。 内存为我们提供了一种方式来存储我们所学到的信息,因此优化策略使我们能够更快地学习事物。 在后面的章节中,我们将探索一些不同形式的优化策略,这些策略可以用来学习目标。

问题

  1. 什么是神经图灵机,它们如何帮助学习?
  2. 记忆矩阵如何帮助模型更快地学习?
  3. 元学习器和基础学习器之间的分裂如何帮助架构学习单样本学习?

进一步阅读

基于模型的方法是您需要学习的更复杂的主题之一,因此,如果您想更深入地研究所涉及的概念,则可以阅读以下论文:

  • 《神经图灵机》
  • 《记忆增强神经网络》
  • 《元网络》

四、基于优化的方法

大多数深度学习模型都是使用梯度下降法来学习目标的。 但是,梯度下降优化需要大量训练样本才能使模型收敛,这使其不适合进行少量学习。 在通用深度学习模型中,我们训练模型以学习实现确定的目标,而人类则训练为学习任何目标。 遵循此观察结果,各种研究人员创建了针对元学习机制的不同优化方法。

换句话说,系统着重于如何收敛任何损失函数(目标),而不是最小化单个损失函数(目标),这使该算法方法任务和域不变。 例如,您不需要训练模型就可以使用交叉熵损失函数来识别花朵的类型。 相反,您可以训练模型以了解任何两个图像之间的差异,从而使模型任务不可知(例如花识别,花检测)和领域不可知(例如猫识别)。

在本章中,我们将介绍以下主题:

  • 梯度下降概述
  • 了解模型不可知的元学习
  • 了解 LSTM 元学习器
  • 编码练习

技术要求

在本章中,将需要 Python,Anaconda,Jupyter 笔记本,Matplotlib 和 Scikit-learn 库来学习和执行该项目。

您可以从本书的 GitHub 存储库中找到本章的代码文件。

梯度下降概述

如果我们研究神经网络架构的学习方法,它通常由很多参数组成,并使用梯度下降算法进行了优化,该算法需要对许多示例进行许多迭代以使其表现良好。 但是,梯度下降算法在其模型中提供了不错的表现,但是在某些情况下,梯度下降优化算法会失败。 让我们在接下来的部分中介绍这种情况。

当给出有限数量的数据时,梯度下降算法无法优化神经网络的主要原因有两个:

  • 对于每个新任务,神经网络必须从其参数的随机初始化开始,这会导致后期收敛。 迁移学习已通过使用预训练的网络来缓解此问题,但由于数据应具有相似的域而受到限制。
  • 即使是梯度下降的权重更新步骤方法(例如 AdaGrad,Adam,RMS 等)的变体也无法在较少的周期内表现良好。 这些算法不能保证收敛,特别是在用于非凸优化时。

真正有用的是学习一些可以在所有域中使用的通用初始化,这是初始化的一个好地方。 梯度下降算法的关键思想是基于下一步的方向,该方向是根据概率分布假设选择的。 因此,如果我们能够以某种方式完全近似概率分布,则仅需几个步骤就可以优化网络。 这是一次/小样本学习基于优化的算法的基本思想。

了解模型不可知的元学习

与模型无关的元学习MAML)尝试通过为每个新任务提供更好的权重初始化来解决梯度下降方法的缺点。 这种方法的关键思想是使用不同的数据集训练模型的参数。 当将其用于新任务时,该模型通过使用已初始化的参数通过一个或多个梯度步骤来微调架构,从而提供更好的表现。 从特征学习的角度来看,这种训练模型参数以使一些梯度步骤可以优化损失函数的方法也可以从构建内部表示的角度来看。 在这种方法中,我们选择通用模型的架构,以便可以将其用于各种任务。 MAML 的主要贡献是一种与模型和任务无关的简单快速学习算法。

了解 MAML 背后的逻辑

MAML 的目的是为模型的参数提供良好的初始化,从而以较少的梯度步骤实现对新任务的最佳快速学习。 它还尝试避免过拟合的情况,这种情况在训练具有较少数据架构的神经网络时会发生。 下图是 MAML 的表示形式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u1nzw41d-1681785569484)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/b3cabe38-594a-4604-a177-7da4089882bb.png)]

如上图所示,θ是模型的参数,粗黑线是元学习阶段。 假设我们有三个不同的新任务,并且为每个任务(带有箭头的灰色线)执行了一个梯度步骤。 我们可以看到参数θ接近三个任务的所有三个最佳参数,这使θ成为可以快速适应不同新任务的最佳参数初始化。 结果,参数θ的很小变化将导致任何任务的损失函数的最佳最小化。 根据这一观察结果,MML 建议我们首先应通过主要数据集学习θ; 在对实际数据集进行微调的同时,我们仅需移动一小步。

顾名思义,与模型无关的元学习可以用于任何形式的模型,无论是分类,回归还是强化学习。 但是对于这本书,我们将只关注 MAML 算法的单样本学习分类方面。 所以,让我们开始吧!

算法

要了解 MAML 的单样本学习/小样本学习,首先,我们需要学习某些项。 这些类似于我们在匹配网络时学到的知识:

  • T:这表示各种任务-例如,我们希望我们的模型学习识别猫,狗,马等,以及T[i]代表一种识别猫的训练模型。 在此,T[i] ∈ T
  • P(T):这表示所有任务之间的概率分布。 我们的目标是通过 MAML 学习P(T)
  • L(T):这表示任务生成的损失函数T数据点。 对于分类,我们可以使用交叉熵损失:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fXeIwkVF-1681785569484)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/db9617c8-2ef5-4a11-8dec-bf17f01a1a38.png)]

假设我们希望训练一个分类模型f[θ],该模型可以学习识别图像中的猫,狗和马。 让我们逐步介绍如何设置 MAML:

  1. 随机初始化模型参数θ ~ N(0, 1)
  2. 重复直到完成。
  3. P(T)中提取T[i]-例如,我们从所有可能的任务中随机抽取识别猫的任务。
  4. 对于从P(T)采样的所有T[i],执行以下操作:
  • T[i]的样本 K 训练数据点D[i] = (x[i], y[i])(对于单样本学习,K = 1)。
  • 前向穿过层(f[θ]),以计算L[T[i]]▽[θ] L[T[i]] f[θ]
  • 使用梯度下降法更新参数。 由于我们正在针对特定任务训练模型,因此我们将学习θ'[i](特定于任务的参数):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vAhxmL2D-1681785569485)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/4848ff3d-a327-43fe-8c92-7d6f69977ac1.png)]

  • 来自T[i]的样本测试数据点D'[i] = (x[i], y[i]),用于元更新。

结束for循环。

  1. 通过使用模型f[θ'[i]]上的采样测试数据点D'[i]计算损失及其梯度来更新θ

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-srwnunb7-1681785569485)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/00a98b32-110e-44d8-9a49-e36078be0d57.png)]

结束repeat循环。

对于 Omniglot 和 mini-ImageNet 数据集,MAML 能够实现比连体网络,匹配网络和内存增强神经网络更好的表现。 由于事实证明 MAML 的表现更好,因此还有许多其他任务可以使用 MAML。 让我们来看一个这样的变体-域自适应元学习DAML)。

MAML 应用–域自适应元学习

当涉及到模仿学习时,机器人需要接收适当的数据,这些数据包括有关动觉变化(有关其身体部位运动的意识),遥距操作(控制)和其他类型输入的信息。 另一方面,人脑只需观看一些视频即可学习。 DAML 尝试通过使用元学习(MAML)解决模仿学习的问题。 它提出了一种仅通过利用从不同任务的数据中提取的强大先验知识(例如,关于动觉学习的信息)就可以从人类的单个视频中学习机器人操纵技能的系统,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAXlPsfQ-1681785569485)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/7f72e13d-4f5d-4cb6-99c3-fd2394d5cc99.png)]

由于无法使用模仿学习损失函数来训练机器人,因此 DAML 提出了行为克隆目标的暂时损失,该目标也充当日志空间中的正则化项。 众所周知,在任何情况下都要进行强正则化对于避免过拟合非常重要,尤其是在单样本学习的情况下。

了解 LSTM 元学习器

LSTM 元学习器是一种元学习。 LSTM 元学习器分为两个阶段:

  • 元学习器:在此阶段,模型着重于学习跨各种任务的常识。
  • 基础学习器:在基础学习器中,模型尝试优化以学习任务特定目标的参数。

LSTM 元学习器的关键思想是训练 LSTM 单元以学习我们原始任务的更新规则。 用元学习框架的术语来说, LSTM 单元将用作元学习器,而特定于任务的目标(例如狗的品种分类)将被用作基础学习器

现在,问题来了,为什么我们要使用 LSTM 单元? LSTM 元学习器的作者做出了一个关键的观察,即 LSTM 中的单元状态更新与反向传播中的基于梯度的更新相似,可用于学习基本学习器目标的更新规则:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5yVKzA4u-1681785569485)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/7f93507c-8eba-4a42-a6f5-8eb229b65be1.png)]

LSTM 在各种门的帮助下存储信息历史记录,如上图所示。 我们还知道,随机梯度下降SGD)有多种变化形式,例如动量,RMSprop,Adam 等,它们实质上存储了有关过去学习的信息(以梯度形式)以实现更好的优化。 因此,从逻辑上讲,可以将 LSTM 单元视为一种更好的优化策略,该策略使模型能够捕获特定任务的短期知识和公共长期知识。

在下一部分中,我们将了解架构,LSTM 单元背后的逻辑以及权重更新算法。

LSTM 元学习器的架构

如果我们研究梯度下降的更新方法,我们将看到一个这样的方程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwY4kVJH-1681785569485)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/5a9a4fca-cdb7-4c9d-9aba-27c879fc4481.png)]

此处, θ[t]是时间步长t的参数,▽L[t]t时的损失梯度,并且α[t]是时间t时的学习率。

另一方面,LSTM 单元的单元更新方程看起来像这样:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HrpVbwvY-1681785569486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/1994255e-a722-4cfe-bfac-9a66d91048e2.png)]

此更新看起来与 LSTM 中单元的更新方式非常相似。 LSTM 元学习器的作者提出,如果将以下值放在单元更新方程中,那么我们将获得梯度下降更新规则:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CeHbwDMy-1681785569486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/b38cc62b-318b-4b9e-ba28-0e5eb9a1106d.png)]

从逻辑上考虑这一点,我们只想学习i[t],因为这与估计梯度下降的学习率基本相似。 因此,LSTM 元学习器将i[t]定义如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q2pu9vkP-1681785569486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/da2cd6b1-bca0-456b-8064-23bdb90ef26c.png)]

本质上,i[t]被定义为具有当前梯度,当前损失和先前学习率i[t-1]的组合的 Sigmoid 函数。

对于f[t],应为 1,但为避免梯度缩小的问题,其定义如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SnZS3OZD-1681785569486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/1d551c2d-de09-4a7b-8657-0c310ee85bbb.png)]

本质上,f[t]被定义为具有当前梯度,当前损失和遗忘门的 Sigmoid 函数。

您可能想知道为什么他们使用 LSTM 单元的这种特定选择? 如果仔细观察,会根据当前梯度和当前损失选择i[t]f[t]。 故意这样做是为了使元学习器能够控制学习率,以便在更少的时间内训练基础学习器。

数据预处理

在一般的深度学习设置中,要在给定数据集D上训练模型,我们将数据集分为三个部分:训练,验证和测试集。 但是在元学习设置中,我们首先将数据集划分为特定于任务的集(例如,猫品种分类和狗品种分类),称为元集,例如D[n]。 对于每个D ∈ D[n]D_n_trainD_n_test组成,因此对于K样本学习,每个D_n_trainK * N个示例组成,其中N是类数。

此后,D_n_train进一步分为三个部分:D_meta_trainD_meta_valD_meta_test。 在这里,目标是使用D_meta_train训练学习算法,该算法可以将任何特定于任务的集合作为训练集D_train并产生更好的分类器(学习器)。

算法–伪代码实现

要训​​练一个单样本学习模型,您需要匹配训练条件以测试时间条件,例如,像在匹配网络中一样,在较少的数据上进行训练,但要进行多个批量。 LSTM 元学习器也遵循与匹配网络相同的概念,并已被证明在特定任务目标上确实表现出色。

要开始理解 LSTM 元学习器,首先,我们需要了解某些项:

  • 基础学习器M):具有特定任务的主要目标,带有参数θ-例如,用于识别猫的分类器
  • 元学习器R):LSTM 单元,带有参数,θ
  • 数据点X, Y):从D_meta_train采样的数据点
  • 损失L):用于调整主要任务特定目标的损失函数,例如,二进制交叉熵损失

让我们开始逐步学习 LSTM 元学习器算法:

  1. 首先,随机初始化 LSTM 单元的初始参数(θ[0])。
  2. 对于D = 1n步骤,请执行以下操作:
  • D_meta_train中随机抽取D_train, D_test
  • 随机初始化基础学习器(分类模型)的初始参数(θ[0])。
  • 对于t = 1T步骤,重复以下步骤:
  • D_train中随机采样输入输出对(X[t], Y[t])
  • 使用L[t] = L(M(X[t], θ[t-1]), Y[t])计算基础学习器(分类模型)的损失。
  • 使用单元格的基本学习器的损失(L[t])及其梯度(▽[θ[t-1]] L[t]),更新单元格状态(c[t]) 状态方程。
  • 将基本学习器(分类模型)参数更新为θ[t] = c[t](请参阅 LSTM 元学习器的“架构”部分)。

结束T步骤循环。

  • 现在,从D_test中随机采样输入输出对(X_test, Y_test)
  • 使用L_test = L(M(X_test, θ[T]), Y_test)计算基础学习器(分类模型)的损失。
  • 使用▽[θ[d-1]] Y_test(请参阅 LSTM 元学习器“架构”部分)更新元学习器(LSTM 单元)参数(θ[t])。

结束n步骤循环。

简而言之,在迭代T步骤的同时,基本学习器参数也会更新。 在T步骤之后,最终的基本学习器参数将用于评估测试集并更新元学习器参数。 有关该算法的图形表示,请参考以下架构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9DlaC1x-1681785569486)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/968555da-ab84-4ab1-9e2a-cd4ba196a50d.png)]

LSTM 元学习器的总体思路非常引人注目。 您可能想知道为什么我们只使用一个 LSTM 单元,为什么 LSTM 元学习器的作者没有像在匹配网络中看到的那样使用整个 LSTM 网络。 您确实可以在元学习器中添加一些复杂的架构,但这要付出大量训练参数的代价。 LSTM 单元的使用使这种元学习架构对于单样本学习变得可行。

在下一部分中,我们将进行 MAML 和 LSTM 元学习器的编码练习,这将有助于我们更全面地了解架构。

练习题

在本节中,我们将首先使用 MAML 进行正弦数据回归的简单练习。

模型无关元学习的简单实现

在本教程中,我们将展示如何应用 MAML 来学习正弦数据的简单曲线。 本教程的第二部分在 GitHub 上可用,我们可以在其中学习如何使用 torch-meta 库在 mini-ImageNet 上训练 MAML。

让我们通过以下步骤开始本教程:

  1. 导入所有库:
代码语言:javascript复制
import math
import random
import torch
from torch import nn
from torch.nn import functional as F
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
%matplotlib inline
  1. 创建一个简单的神经网络架构。 我们将获得随机生成的正弦曲线数据。 我们将使用这个非常小的网络,因为我们不需要大的网络来学习曲线:
代码语言:javascript复制
def net(x, params):
    x = F.linear(x, params[0], params[1])
    x = F.relu(x)

    x = F.linear(x, params[2], params[3])
    x = F.relu(x)

    x = F.linear(x, params[4], params[5])
    return x

params = [
    torch.Tensor(32, 1).uniform_(-1., 1.).requires_grad_(),
    torch.Tensor(32).zero_().requires_grad_(),

    torch.Tensor(32, 32).uniform_(-1./math.sqrt(32), 
        1./math.sqrt(32)).requires_grad_(),
    torch.Tensor(32).zero_().requires_grad_(),

    torch.Tensor(1, 32).uniform_(-1./math.sqrt(32), 
        1./math.sqrt(32)).requires_grad_(),
    torch.Tensor(1).zero_().requires_grad_(),
]
  1. 设置训练参数。 初始化alphabeta,学习率,优化器和循环数的参数:
代码语言:javascript复制
opt = torch.optim.SGD(params, lr=1e-2)
n_inner_loop = 5
alpha = 3e-2
  1. 实现优化算法:
代码语言:javascript复制
for it in range(100000): # training for large number of iterations
    b = 0 if random.choice([True, False]) else math.pi # setting up 
            beta variable randomly
    #### Randomly obtain task 1 sinusoidal data ####
    x = torch.rand(4, 1)`4`math.pi - 2*math.pi
    y = torch.sin(x   b)
    #### Randomly obtain the task 2 sinusoidal data ####
    v_x = torch.rand(4, 1)`4`math.pi - 2*math.pi
    v_y = torch.sin(v_x   b)
    opt.zero_grad() # setup optimizer
    new_params = params # initialize weights for inner loop
    for k in range(n_inner_loop):
        f = net(x, new_params) # re-initialize task 2 neural 
            network with new parameters
        loss = F.l1_loss(f, y) # set loss as L1 Loss
        grads = torch.autograd.grad(loss, new_params, 
            create_graph=True)
        new_params = [(new_params[i] - alpha*grads[i]) for i in
             range(len(params))] # update weights of inner loop
    v_f = net(v_x, new_params) # re-initialize task 1 neural 
        network with new parameters
    loss2 = F.l1_loss(v_f, v_y) # calculate Loss
    loss2.backward() # Backward Pass
    opt.step()

运行此命令后,您将以以下形式查看优化输出:

代码语言:javascript复制
Iteration 0 -- Inner loop 0 -- Loss: 0.3558
Iteration 0 -- Inner loop 1 -- Loss: 0.3815
Iteration 0 -- Inner loop 2 -- Loss: 0.3788
Iteration 0 -- Inner loop 3 -- Loss: 0.3265
Iteration 0 -- Inner loop 4 -- Loss: 0.4066
Iteration 0 -- Outer Loss: 0.7631
Iteration 100 -- Inner loop 0 -- Loss: 0.9611
Iteration 100 -- Inner loop 1 -- Loss: 0.9364
Iteration 100 -- Inner loop 2 -- Loss: 0.9122
Iteration 100 -- Inner loop 3 -- Loss: 0.8883
Iteration 100 -- Inner loop 4 -- Loss: 0.8641
Iteration 100 -- Outer Loss: 1.0115
  1. 绘制获得的结果。 一旦获得正确的参数,我们将首先生成一些随机数据点以对五个数据点进行子采样。 如果将结果绘制成图,我们将看到神经网络能够在正弦数据点上获得正确的曲线:
代码语言:javascript复制
# Randomly generate 5 data points.
t_b = math.pi 
t_x = torch.rand(4, 1)`4`math.pi - 2*math.pi
t_y = torch.sin(t_x   t_b)

opt.zero_grad()

t_params = params
for k in range(n_inner_loop):
    t_f = net(t_x, t_params)
    t_loss = F.l1_loss(t_f, t_y)

    grads = torch.autograd.grad(t_loss, t_params, 
        create_graph=True)
    t_params = [(t_params[i] - alpha*grads[i]) for i 
        in range(len(params))]

test_x = torch.arange(-2*math.pi, 2*math.pi, step=0.01).unsqueeze(1)
test_y = torch.sin(test_x   t_b)

test_f = net(test_x, t_params)

plt.plot(test_x.data.numpy(), test_y.data.numpy(), label='sin(x)')
plt.plot(test_x.data.numpy(), test_f.data.numpy(), label='net(x)')
plt.plot(t_x.data.numpy(), t_y.data.numpy(), 'o', label='Examples')
plt.legend()
plt.savefig('maml_output.png')

运行此命令后,您应该能够获得如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qfIrxeMf-1681785569487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/1255afd0-ad98-404e-8676-0adad4b09236.png)]

如果查看该图,您将看到网络能够大致学习sin(x)曲线。

域自适应元学习的简单实现

在本教程中,我们将使用域自适应元学习来学习正弦数据的简单曲线。 它是模型不可知的元学习的一种变体,但是增加了先验信息,也就是说,已经添加了有关该域的其他相关信息。

让我们开始!

元学习算法优化了模型快速学习新任务的能力。 为此,他们使用跨各种任务收集的数据,并根据其学习新的元测试任务的能力进行评估。 此过程可以形式化为学习数据(一系列任务)的先验知识(即提取重要信息),并且微调过程成为在学习到的先验知识下的推断:

  1. 导入所有库:
代码语言:javascript复制
import math
import random
import sys
import torch # v0.4.1
from torch import nn
from torch.nn import functional as F
from tqdm import tqdm
from time import sleep
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')
  1. 创建一个简单的神经网络架构,将学习正弦曲线。 我们将从正弦曲线中获取随机生成的数据,因此我们将使用这个非常小的网络,因为我们不需要大的网络来学习曲线:
代码语言:javascript复制
def meta_net(x, params): 
    # main network which is suppose to learn our main objective 
    i.e; learn sinusoidal curve family here.
    x = F.linear(x, params[0], params[1])
    x1 = F.relu(x)

    x = F.linear(x1, params[2], params[3])
    x2 = F.relu(x)

    y = F.linear(x2, params[4], params[5])

    return y, x2, x1

params = [
    torch.Tensor(32, 1).uniform_(-1., 1.).requires_grad_(),
    torch.Tensor(32).zero_().requires_grad_(),

    torch.Tensor(32, 32).uniform_(-1./math.sqrt(32), 
        1./math.sqrt(32)).requires_grad_(),
    torch.Tensor(32).zero_().requires_grad_(),

    torch.Tensor(1, 32).uniform_(-1./math.sqrt(32), 
        1./math.sqrt(32)).requires_grad_(),
    torch.Tensor(1).zero_().requires_grad_(),
]
  1. 创建另一个简单的神经网络架构,以添加有关该域的先验信息。 我们将在我们的主要网络中增加一些先验知识; 因此,我们需要创建一个简单的adap_net
代码语言:javascript复制
def adap_net(y, x2, x1, params): 
    # the net takes forward pass from meta_net and provides 
    efficient parameter initializations.
    # It works adapts the meta_net easily to any form of change

    x = torch.cat([y, x2, x1], dim=1)

    x = F.linear(x, params[0], params[1])
    x = F.relu(x)

    x = F.linear(x, params[2], params[3])
    x = F.relu(x)

    x = F.linear(x, params[4], params[5])

    return x

adap_params = [
    torch.Tensor(32, 1 32 32).uniform_(-1./math.sqrt(65), 
        1./math.sqrt(65)).requires_grad_(),
    torch.Tensor(32).zero_().requires_grad_(),

    torch.Tensor(32, 32).uniform_(-1./math.sqrt(32), 
        1./math.sqrt(32)).requires_grad_(),
    torch.Tensor(32).zero_().requires_grad_(),

    torch.Tensor(1, 32).uniform_(-1./math.sqrt(32), 
        1./math.sqrt(32)).requires_grad_(),
    torch.Tensor(1).zero_().requires_grad_(),
]
  1. 设置训练参数。 我们将使用内循环而不是外循环训练,因此,我们需要设置某些参数,例如alphabeta,学习率,优化器和循环数:
代码语言:javascript复制
opt = torch.optim.SGD(params   adap_params, lr=1e-2)
n_inner_loop = 5
alpha = 3e-2
  1. 实现优化算法。 如“域自适应元学习”部分所述,这种方法只能从一个人的视频中学习新技能。 为此,它首先使用人类演示和远程演示来训练元网络,以在元训练阶段建立强大而丰富的先于任务:
代码语言:javascript复制
inner_loop_loss=[]
outer_lopp_loss=[]
# Here, T ∼ p(T ) {or minibatch of tasks} is to learn sinusoidal family curves
with tqdm(total=100000, file=sys.stdout) as pbar:
    for it in range(100000):
        b = 0 if random.choice([True, False]) else math.pi
        #### Randomly obtain the task 2 sinusoidal data ####
        v_x = torch.rand(4, 1)`4`math.pi - 2*math.pi 
        v_y = torch.sin(v_x   b)
        opt.zero_grad()
        new_params = params
        for k in range(n_inner_loop):
            sampled_data = torch.FloatTensor([[random.uniform
                (math.pi/4, math.pi/2) if b == 0 
                else random.uniform(-math.pi/2, -math.pi/4)]]
            # Here, si is adap_net parameters: adap_params and 
            theta is meta_net parameters
            f, f2, f1 = meta_net(sampled_data, new_params) 
            h = adap_net(f, f2, f1, adap_params)     
            adap_loss = F.l1_loss(h, torch.zeros(1, 1)) # Calculate 
            Loss
            grads = torch.autograd.grad(adap_loss, new_params, 
                create_graph=True)
            # Compute policy parameters phi_t(new_params)
            new_params = [(new_params[i] - alpha*grads[i]) for i
                in range(len(params))]
            if it % 100 == 0: 
                inner_loop_loss.append(adap_loss)
        v_f, _, _ = meta_net(v_x, new_params) # forward pass using 
        learned policy parameters 
        loss = F.l1_loss(v_f, v_y) # calculate the loss of meta_net
        loss.backward()   
        opt.step() # optimize the policy parameters(theta and si)
        pbar.update(1)
        if it % 100 == 0: 
            outer_lopp_loss.append(loss)

在此阶段,机器人(meta_net)学习如何使用数据向人类学习。 在元训练阶段之后,机器人可以通过将其学习到的先验知识与执行新技能的人员的一个视频相结合来获得新技能。 此方法包括两个阶段:

  • 在元训练阶段,目标是使用人类和机器人的演示数据获取先验策略(φ
  • 使用学到的知识,然后快速学习如何仅用几个数据点来模仿新任务

一旦运行了上面的代码,您将获得以下输出:

代码语言:javascript复制
Iteration 0 -- Inner loop 0 -- Loss: 0.0211
Iteration 0 -- Inner loop 1 -- Loss: 0.0183
Iteration 0 -- Inner loop 2 -- Loss: 0.0225
Iteration 0 -- Inner loop 3 -- Loss: 0.0180
Iteration 0 -- Inner loop 4 -- Loss: 0.0156
Iteration 0 -- Outer Loss: 0.5667
Iteration 100 -- Inner loop 0 -- Loss: 0.0009
Iteration 100 -- Inner loop 1 -- Loss: 0.0007
Iteration 100 -- Inner loop 2 -- Loss: 0.0003
Iteration 100 -- Inner loop 3 -- Loss: 0.0003
Iteration 100 -- Inner loop 4 -- Loss: 0.0000
Iteration 100 -- Outer Loss: 0.8096 ...
  1. 微调主网。 一旦获得正确的参数,我们将首先生成一些随机数据点以对五个数据点进行子采样,并使用adap_net的损失微调主要的meta_net
代码语言:javascript复制
t_b = math.pi
opt.zero_grad()
t_params = params

for k in range(n_inner_loop):
    # sample the new task data
    new_task_data = torch.FloatTensor([[random.uniform
            (math.pi/4, math.pi/2) if t_b == 0 
            else random.uniform(-math.pi/2, -math.pi/4)]])
    # forward pass through meta_net to extract the input for 
    adap_net
    t_f, t_f2, t_f1 = meta_net(new_task_data, t_params)
    # extract the information from adap_net
    t_h = adap_net(t_f, t_f2, t_f1, adap_params)
    # calculate the loss, here we used true label as 
    torch.zeros(1, 1), because t_b = pi
    t_adap_loss = F.l1_loss(t_h, torch.zeros(1, 1))

    grads = torch.autograd.grad(t_adap_loss, t_params, 
        create_graph=True)
    # learn the policy using the loss of adap_net
    t_params = [(t_params[i] - alpha*grads[i]) for i 
        in range(len(params))]

部署后,机器人可以仅使用人类与这些对象一起执行任务的单个视频,即可适应具有新颖对象的特定任务。

  1. 使用以下代码可视化输出:
代码语言:javascript复制
test_x = torch.arange(-2*math.pi, 2*math.pi, step=0.01).unsqueeze(1)
test_y = torch.sin(test_x   t_b)

test_f, _, _ = meta_net(test_x, t_params) # use the learned parameters

plt.plot(test_x.data.numpy(), test_y.data.numpy(), label='sin(x)')
plt.plot(test_x.data.numpy(), test_f.data.numpy(), label='meta_net(x)')
plt.legend()
plt.savefig('daml-sine.png')

运行代码后,您将看到类似于以下内容的图形:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zoMfmvr2-1681785569487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/e8692140-1570-4eda-add5-c8331e6abf96.png)]

如果您无法获得理想的正弦曲线形状,则将迭代次数加倍。

在这里,您可以看到我们的净模型(橙色线)非常接近真实数据集(蓝色线)。 如果您希望使用真实的数据集探索这些模型,请参考上的 GitHub 存储库 https://github.com/PacktPublishing/Hands-On-One-shot-Learning-with-Python/tree/ master / Chapter04 。 在这里,您会发现使用 Omniglot 和 mini-ImageNet 数据集的其他优化算法。

总结

要求解任何方程,通常我们可以使用很多方法。 同样,为了进行优化(学习神经网络的参数),许多研究人员也公开了许多方法,但是事实证明梯度下降是一种适用于每种情况的通用方法。 如果我们希望处理特定类型的神经网络问题,那么最好探索可能适合我们任务的不同优化技术。

在这一章中,我们研究了两种最著名的单样本学习优化方法:MAML 和 LSTM 元学习器。 我们了解了 MAML 如何通过优化我们的初始参数设置来解决单样本学习问题,从而在几个数据点上进行一个或几个梯度下降步骤可以导致更好的概括。 我们还探讨了 LSTM 元学习器对如何训练 LSTM 单元作为元学习器以预测基础学习器权重更新的见解。

在下一章中,我们将探讨一种著名的 ML 方法贝叶斯学习。 我们将通过用概率模型表示对象类别来观察几个贝叶斯学习框架的发展。 我们将对判别式K样本学习和贝叶斯程序学习及其在现实世界中的应用进行恰当的解释。

问题

  1. 梯度下降优化算法的优缺点是什么?
  2. 梯度下降优化算法有其他选择吗?
  3. 为什么训练神经网络需要那么多的周期?

进一步阅读

有关本章中介绍的一些架构的更多详细信息,建议阅读以下论文:

  • 《与模型无关的元学习》
  • 《作为小样本学习模型的优化》

第三部分:其他方法和结论

深度学习架构已被证明是非常有效的,但是它们仍然不是单样本学习的最佳方法。 贝叶斯编程语言之类的不同贝叶斯方法仍然可以一口气击败人类。 在本节中,我们将学习贝叶斯方法,并讨论该领域的最新进展。 此外,我们将贝叶斯方法与深度学习圈中解决任何问题的众所周知的技术(迁移学习)进行比较。 我们还将学习何时在迁移学习中使用单发方法。

本节包括以下章节:

  • 第 5 章,“基于生成建模的方法”
  • 第 6 章,“结论和其他方法”

五、基于生成建模的方法

当人们对看不见的数据进行推断时,他们会利用有关他们已经看到,听到,触摸或经历过的相关事件的强大先验知识(或归纳偏见)。 例如,与狗一起长大的婴儿可能第一次见到猫,并立即推断出它与家犬的宠物般的性情相似。 当然,猫和狗作为物种和个体是千差万别的。 但是,公平地说,猫比狗更像狗,而不是孩子经历的其他随机事物(例如食物)。 与机器学习模型相反,人类不需要成千上万的猫实例就可以从头开始学习,只要他们已经学会了识别狗就可以识别猫。 人脑具有元学习的固有能力,这与机器学习语言中的迁移学习多任务学习有关。 此能力通过利用从相关任务中学到的知识来加速新概念的学习。

生成模型是概率模型,旨在弥合人类学习与机器学习之间的鸿沟。 这些模型旨在从对象的各个部分学习高级抽象特征,并将这些学习的特征应用于新的但相似的对象类别。 在本章中,我们将研究如何生成这些生成模型,拥有先验知识意味着什么,如何用数学术语构筑先验知识,如何从一些对象中学习高级概念(模型的参数学习) ),以及如何将这些新学到的知识与先验知识相结合,以对新对象做出有意义的决策(推断)。

本章将涵盖以下主题:

  • 贝叶斯学习概述
  • 了解有向图模型
  • 概率方法概述
  • 贝叶斯程序学习
  • 判别式 K 样本学习

技术要求

本章将基于理论,因此没有正式的技术要求,但是需要对贝叶斯建模有基本的了解。

贝叶斯学习概述

在本节中,我们将从数学的角度简要讨论贝叶斯学习背后的思想,这是单样本学习概率模型的核心。 贝叶斯学习的总体目标是在给定训练数据的情况下,对参数θ的分布进行建模,即学习分布p(θ | Data)

在机器学习的概率视图中,我们尝试求解以下方程式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IZeO3ujs-1681785569487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/461dfab6-3cd8-4782-a6de-e092332fa3b6.png)]

在此设置中,我们尝试找到可以解释数据的最佳参数集θ。 因此,我们在θ上最大化给定方程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G2SNvKhF-1681785569487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/59ce0f8b-fa97-4605-98f6-78bf3737c14f.png)]

我们可以对两边取对数,这不会影响优化问题,但会使数学容易且易于处理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RaobFfry-1681785569487)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/f1da3c33-07dc-4b1d-8a75-aa4162729d8c.png)]

我们可以从数据的右侧删除P(data),因为它不依赖于θ来进行优化,因此优化问题如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDd9RnXm-1681785569488)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/d5a1d317-b037-40b4-b612-10d2e168e328.png)]

在非概率视图(也称为期望最大化框架)中,右侧等式中的项p(Data | θ)P(θ),分别成为损失函数和正则化。 在给定的概率设置中,相同的项称为(给定θ的数据的)似然先验(在参数空间中的先验信念)。 这种概率优化称为最大后验MAP)估计,因为我们正在从数据中最大化模型参数的后验分布。 但是,贝叶斯统计不相信 MAP 估计,因为它可能给我们有关最终学习参数的错误结果。 一个不同的数据集很有可能会给我们完全不同的学习参数,这些参数在参数空间中与从原始数据集中学习的参数相距甚远。 这就是贝叶斯学习试图解决的问题。 它显式地模拟参数空间中的不确定性。

考虑给定左撇子和右撇子人数据集的参数分布示例。 下图显示了分布:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MuZD2wuB-1681785569488)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/ad909edc-738b-40ed-a6fb-4d3893f9efc9.png)]

最大化来自数据的参数(θ)概率的 MAP 估计将收敛至点A。但是,大多数概率量都更偏向于惯用右手的人,这与世界上惯用右手的人多于惯用左手的人这一事实相吻合。

因此,在贝叶斯学习中,重点是解决后验参数P(Data | θ),以明确地对参数中的不确定性建模。

了解有向图模型

现在,在深入研究用于单样本学习的概率模型之前,我们将简要研究有向图模型。 有向图模型(也称为贝叶斯网络)由与有向边相连的随机变量定义,如在父子关系中。 下图显示了一个这样的贝叶斯网络:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RjRvCVMY-1681785569488)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/169cea75-5669-4783-a7c4-a4b77f185c91.png)]

此图中SRLWT的随机变量的联合分布,通过一个简单的链式规则可分为多个分布:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ns0qgE1-1681785569488)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/9511985a-1153-4134-8e00-3c8126a5dc83.png)]

前面方程右侧的条件分布具有大量参数。 这是因为每个分布都以许多变量为条件,并且每个条件变量都有其自己的结果空间。 如果我们在有大量条件变量的情况下在图中走得更远,则这种影响会更加突出。 因此,要学习每种条件分布的庞大参数集,我们需要大量的标记数据,这在现代机器学习任务中通常是不可用的。

这是有向图模型进入图片的地方。 它断言了概率的一些条件独立性,从而简化了前面描述的方程式。 有向图模型中的每个变量在条件上独立于给定其父对象的非后代。 有向图模型不过是条件独立性的表示。 更正式地讲,如果X[i]是有向图中的顶点,则V是顶点数,X[pa(t)]都是顶点X[t]的父级,则所有顶点上的联合概率分布可写为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F7mRkCFD-1681785569488)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/4bb469f5-c12f-455b-9a29-06e6eff50baa.png)]

鉴于此,前面公式中定义的联合分布可简化为以下形式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6101q0mF-1681785569489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/8f18500d-a05b-498d-949b-2d311af7bcbd.png)]

这减少了模型中参数的数量,并易于使用相对较少的数据来学习模型。

概率方法概述

人类的概念学习趋向于在两个主要方面不同于机器学习。 在下图中考虑来自大量词汇的手写数字示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SGuay8K2-1681785569489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/06cbc1c2-9840-44aa-b8f3-a41e2abac4c4.png)]

首先,人们倾向于仅从一个或几个示例中学习关于对象的有意义的信息,例如对象边界,并对其进行高精度分类(请参见上图中的i)部分)。 另一方面,深度学习模型需要大量带标签的数据才能在诸如对象识别之类的任务上达到人类水平的表现。

其次,人类从一个示例中学习了绝大多数能力,例如,创建新字符(参见上图中的ii)部分),将对象/字符分解为各个部分和关系(请参见iii)部分),并根据有关现有概念的现有知识开发新的,有意义的概念/字符(请参见上图中的iv)部分)。 相反,深度学习模型针对每个任务都需要特殊的损失函数和架构,由于该任务可用的标记数据非常有限,因此通常不切实际。

人们如何仅从一个示例中学习对对象和概念的如此丰富,强大的表示?

学习理论指出,需要更多的数据(而不是更少的数据)来学习更复杂的,可以很好地概括的模型。 但是人类往往会从非常稀疏的数据中学习到更丰富的表述,这些表述可以很好地概括。

概率模型旨在弥合数据饥渴的机器模型与人类采用的高度健壮的元学习方法之间的鸿沟。 在本章中,我们将讨论两行概率方法,这些方法在从很少的数据中学习各种任务方面已经获得了广泛的成功:

  • 第一种方法是对对象零件,子零件及其之间的关系进行显式建模,以学习概念(对象)。 这可用于从一个或几个示例中对新对象进行分类,并从预定义的零件和子零件列表中绘制新类型的对象。
  • 第二种方法基于深度学习方法,通过从初始的大量训练数据中学习新类别(其中只有一个类别的图像,以及来自其他类别的大量图像)来完成一次分类任务。 这种方法将潜在变量定义为概念(类)的先验。 最初的大量训练数据集有助于学习有关概念的强大先验知识,这些概念随后可用于从一次镜头分类中对新对象进行分类。

贝叶斯程序学习

贝叶斯程序学习BPL)进行三个步骤:

  1. 第一步是生成模型,BPL 通过从“模型”部分的图的 A 侧部分组成(请参考iii)来学习新概念,以学习新概念) ,下图 A 侧的子部分(请参见ii)和它们在下图 A 侧的空间关系(请参见iv)。 例如,它可以从零件和子零件中采样新类型的概念(在这种情况下为手写字符),并以新的方式将它们组合在一起。
  2. 在第二步中,第一步中抽取的概念形成另一个较低层的生成模型,以产生新示例,如 A 侧的v)部分所示。
  3. 最后一步将渲染原始字符级图像。 因此,BPL 是生成模型的生成模型。 下图的 B 侧显示了该生成模型的伪代码。

模型

给定如图 A 所示的有向图模型,类型ψ的联合分布; 一组M个标记, θ[1], ..., θ[M]; 及其相应的原始图像I[1], ..., I[M]分解如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mfziZhnd-1681785569489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/a87b8aac-2728-4f0f-bfb7-9e588762ceca.png)]

这三个生成过程分别是类型生成(P(ψ)),标记生成(P(θ[m] | ψ[m]))和图像生成(P(I[m] | θ[m])),分别在下图中用其伪代码进行了讨论:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1qWjoBOP-1681785569489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/16ff2f50-55be-44fd-8da5-661c7c7ea3a6.png)]

类型生成

手写字符类型(ψ)是字符的各个部分和子部分之间以及它们之间的关系的抽象架构。 反映现实生活中的字符书写过程,字符部分S[i]形成了笔向下移动到笔抬起操作的一个笔触。 这些字符笔划由S[i1], ..., S[in[i]]子部分组成,表示笔的短暂停顿。 生成新字符类型的伪代码显示在上图的 B 侧,其执行过程如下:

  1. 为了生成新的字符类型,模型首先为每个部分采样零件数(k)和子零件数(n[i])。 采样参数来自它们在训练数据集中的经验分布。 训练数据还提供了子部分的预定义原语集。
  2. 每个字符部分都是通过对预定义集合中的子部分进行采样而产生的,因此对下一个子部分进行采样的概率取决于前一个子部分。
  3. 为零件S[i]采样了关系R[i],该关系定义了该零件与先前零件的连接方式。

标记生成

字符标记θ[m]是通过对墨水从笔到纸的流动方式进行建模而由零件和关系生成的。 用于标记生成的伪代码在上图的 B 侧进行了描述。 首先,将噪声(此处称为运动方差)添加到子零件的比例尺和控制点,以定义冲程(或零件)轨迹s[m]。 轨迹的精确开始位置L[m]由关系R[i]决定。 最后,将变换A[m]用于减轻概率推断。

图像生成

使用随机渲染函数生成原始二进制字符图像I[m],该函数将笔划轨迹与灰度墨水映射。 这是通过为每个像素分配独立的伯努利概率来实现的。

BPL 是一种高度直观的模型,可以在贝叶斯框架下使用简单的程序为概念建模。 从训练数据中学习概率分布的参数。 在分类和生成的单次计算机视觉任务上,该模型的表现与人类水平的表现相当,其数据需求远低于某些最新的深度学习模型。 这里研究的概率程序非常基础,适合于相当容易的字符识别任务。 BPL 框架支持设计更复杂的程序,这些程序可以对各种对象的复杂表示进行建模。 例如,可以使用此框架对在零件和关系方面具有清晰直观描述的对象(例如车辆,食品,动物,甚至人脸)进行建模。 为此,BPL 框架甚至支持建模抽象知识,例如自然语言语义和物理理论。 然而,这些概率程序需要对数据及其部分,子部分和关系进行手动标记,这是一个耗时的过程,而深度学习方法则是自己学习这些人类直观特征和深层抽象特征。

判别式 K 样本学习

K 样本学习的一种非常常见的方法是训练具有相关任务的大型模型,而我们为此拥有大型数据集。 然后,通过 K 次特定任务对该模型进行微调。 因此,来自大型数据集的知识被提炼为到模型中,这仅从几个示例中增加了对新相关任务的学习。 2003 年,Bakker 和 Heskes 提出了一种用于 K 样本学习的概率模型,其中所有任务共享一个公共特征提取器,但具有各自的线性分类器,其中仅包含几个特定于任务的参数。

这里讨论的 K 样本学习的概率方法与 Bakker 和 Heskes 引入的方法非常相似。 该方法通过从很少的数据中学习概率模型来解决分类任务(针对图像)。 这个想法是使用一个强大的神经网络,该网络从大量监督数据中学习可靠的特征,并将其与概率模型结合起来。 神经网络最后一层的权重充当以贝叶斯方式规范化 K 次子模型权重的数据。

学习框架包括四个阶段:

  • 表示学习
  • 概念学习
  • K 样本学习
  • K 次测试

下图显示了具有四个阶段的框架。 以下各节将对它们进行更正式的讨论:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UA29kkWo-1681785569489)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/5f07d8c6-f555-4b6f-89a9-72d141ed72f9.png)]

表示学习

在第一阶段(表示学习),使用大型数据集D_tilde训练 CNN(Φ[φ]),该数据集训练参数φ的网络W_tilde。 此后,这些参数φ是固定的,并在以后的阶段中共享。 来自 CNN 最后一层的激活被映射到两组 softmax 层,由W_tildeW参数化。 参数W_tilde对应于大型数据集D_tilde中的C_tilde类,参数W对应于 K 次任务的数据集D中的C类。 如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qoIZuYhe-1681785569490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/f23a6f16-745c-44e0-bc32-0ca3befa7b6a.png)]

权重的概率模型

假设由于最大数据集D_tilde而在第一阶段获知的 softmax 权重W_tilde的不确定性很小。 将此近似值与上图中的图模型结构结合起来,我们可以摆脱原始数据集D_tilde,并使用W_tilde的 MAP 估计值(W_MAP)处于概念学习和 K 样本学习阶段。 完整的概率模型遵循以下步骤:

  1. K 样本学习过程将信息合并到两个数据集D_tildeD中,以在W上生成后验分布:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pH7WtcsP-1681785569490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/3f0ac346-6f71-413c-9364-4da122454208.png)]

  1. 从图模型,在上图中,我们知道给定父级W的情况,D有条件地独立于D_tilde我们有以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IucwwUOw-1681785569490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/93b309a8-288c-4cd2-883f-f166d5f51c7d.png)]

因此,等式 1 变为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dx9JUQPf-1681785569490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/be56381d-889a-404b-95e6-1ebe2a7002bc.png)]

  1. 我们可以将项P(D_tilde)吸收到比例常数中,从而使前面的方程式变为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H5BUaiKt-1681785569490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/a32d4c87-1014-4d11-b7c1-563e0c5bfd97.png)]

主要挑战是在给定初始数据集D_tilde的情况下,计算超参数θ的后验,这会使该模型的推理变得难以处理。 由于使用了较大的初始数据集来学习权重W_tilde,后分布P(W_tilde | D_tilde)可以通过其 MAP 估计值安全地近似,即P(W_tilde | D_tilde) ≈ δ(W - W_MAP)。 因此,我们可以摆脱等式 2 中的D_tilde,并用W_tilde代替。

选择权重模型

给定图模型,我们可以写出概念超参数(θ)和模型权重(WW_tilde)的联合分布如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T7qqu61c-1681785569490)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/cd64030c-1e6b-4a97-bc41-e5043bf94a5e.png)]

做出两个简单但合理的假设以使机器在计算上易于处理:

  • 首先,对于每个类别,从最后一个隐藏层到 softmax 的隐藏权重WW_tilde被视为独立的。
  • 第二,给定θP(w_tilde[c'] | θ)P(w[c] | θ)的权重分布WW_tilde是相同的。

然后,等式 3 中的联合分布简化为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O147xEil-1681785569491)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/0c67309f-24e7-462a-abaa-039674855756.png)]

一个简单的高斯模型用于权重P(w | θ) = N(w | μ, Σ)及其共轭正反 Wishart 先验P(θ) = P(μ, Σ) = NIW(μ[0], κ[0], Λ[0], v[0]),并估计 MAP 解的参数θ_MAP = {μ_MAP, Σ_MAP}

这导致分发简化为以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vASiwi5T-1681785569491)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/4a4c76f8-4524-4ba0-bda8-afec7fa80efe.png)]

K 样本学习(等式 2)期间新权重W的后验分布简化为以下表达式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-drwATmue-1681785569491)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/bd2213cb-753d-470a-994d-2b8cc7b6a7c4.png)]

每个阶段的计算和近似

在讨论了先前模型的机制之后,以下小节总结了判别式 K 次模型的四个阶段中的所有计算和近似。

第一阶段–表示学习

最初,深度学习训练特征提取器Φ[φ]。 在以下阶段中使用输入图像(u)的最后一层的激活x = Φ[φ](u)。 原始数据集中类别的 softmax 权重为 MAP 估计值W_tilde_MAP

第二阶段–概念学习

概率模型直接适合于 MAP 权重P(θ | W_tilde) ∝ P(θ)P(W_tilde | θ)。 对于共轭模型,后验分布是通过解析获得的。 否则,将使用P(θ | W_tilde)的 MAP 估计值。

第三阶段– K 样本学习

在 softmax 权重WP(W | D, W_tilde_MAP) ∝ P(W | W_tilde_MAP) Π[n = 1, ..., N] p(y[n] | x[n], w)上的后缀是难以处理的。 通过使用 MAP 估计W_MAP或通过采样W[m] = P(W | D, W_tilde_MAP)可以近似得出。 必须注意,P(W | W_tilde_MAP) = ∫P(W | θ)P(θ | W_tilde_MAP)dθ是针对共轭模型的分析。 但是,如果在阶段 2 中根据 MAP 估计来估计θ,则如等式 4 中所述使用P(W | W_tilde_MAP) ≈ P(W | θ_MAP)

第四阶段 – K 次测试

K 次测试时间P(y*, x*, D, W_tilde_MAP) = ∫p(y* | x*, W)P(W | D, W_tilde_MAP)dW的推断是难以理解的,因此此处使用近似值。 如果从阶段 3 开始使用W的 MAP 估计值(W_MAP),则P(y*, x*, D, W_tilde_MAP) = P(y* | x*, W_MAP)。 如果在阶段 3 中重新采样,则使用P(y*, x*, D, W_tilde_MAP) = 1/m Σ[m = 1, ..., M] p(y* | x*, W[m])

在 miniImageNet 数据集(由 100 个类组成,每个类中包含 600 个图像)上,此方法可以单样本学习一次和五样本学习获得最先进的结果。 离统一概率模型和深度学习的领域又迈进了一步,将两者结合起来,可以开发出真正强大的模型,从而利用概率领域的强大数学保证和深度学习模型的强大健壮功能。 判别式 K 样本学习方法仍然需要大量带标签的训练数据来训练基于深度学习的特征提取器。 另一方面,贝叶斯程序学习方法利用模型中的归纳偏差和手工设计的特征,因此需要较少的标注训练数据。

总结

在本章中,我们学习了在贝叶斯框架内开发概率模型的方法,该模型可以极大地减少数据需求并达到人类水平的表现。 从前面讨论的手写字符的示例中,我们还观察到概率模型不仅可以学习如何对字符进行分类,还可以学习基本概念,即以新的方式应用获得的知识,例如从集合中仅有的几个字符生成全新的字符,以及将字符解析为部分和关系。

但是,人类学习器需要从具有丰富重叠结构的许多经验中获得的广泛的先前经验来完成新的学习任务。 为了模仿人类学习,图结构需要具有更多的依赖性,并且需要在模型中内置丰富的归纳偏差。 还应注意,人类在很小的时候就对物体的物理特性(形状,运动和其他力学)有很好的认识。 学习模型不会隐式地捕获对象的直观物理特性,也不会显式地将其嵌入对象中。 直观物理(类似于游戏引擎中嵌入的物理)与概率模型和深度学习的集成,是朝着更健壮的单发学习迈出的重要一步。 最后,由于先验知识以强先验和图结构的形式嵌入到概率模型中,因此与必须从头学习任务的深度学习模型相比,它们的数据消耗更少。 但这是以在概率模型中进行有效推理的计算挑战为代价的。 在推断时,这些模型必须搜索巨大的概率空间,这对于现代计算机而言是不实际的。 相反,深度学习模型具有精确且计算上便宜的推断。 最近的工作通过使用前馈映射对摊销概率推理计算来解决图模型中的这一推理挑战,可以使用成对的生成/识别网络来学习。 这提供了另一条有希望的研究领域,使深度学习和概率模型更加接近。

进一步阅读

要了解有关本章涵盖的主题的更多信息,请阅读以下文章:

  • 《通过概率性规划归纳的人类级别概念学习》
  • 《可以无监督单样本学习对象类别的一种贝叶斯方法》
  • 《使用概率模型的判别性 K 样本学习》
  • 《构建像人一样学习和思考的机器》
  • 《简单视觉概念的单样本学习》
  • 《使用分层非参数贝叶斯模型的单样本学习》

六、总结和其他方法

在这本书中,我们了解了用于深度学习的各种形式的架构,以及从手动特征提取到变型贝叶斯框架的各种技术和方法。 单样本学习是一个特别活跃的研究领域,因为它专注于根据人类的神经能力更紧密地建立一种机器意识。 过去 5 年中,随着深度学习社区的进步,我们至少可以说,我们正在开发一种可以像人类一样同时学习多个任务的机器。 在这一章中,我们将看到单样本学习还有哪些其他选择,并讨论本书中未深入探讨的其他方法。

将涵盖以下主题:

  • 最新进展
  • 相关领域
  • 应用领域

最新进展

在深度学习社区中,已经提出了用于单样本学习的各种其他方法,例如使用 GAN 的生成建模,图像变形元网络,基于代表的度量学习等。 到目前为止,我们已经看到了使用单样本学习进行分类的模型,但是在对象检测和语义分割方面也取得了一些进步。 在本节中,我们将介绍一些主要的基于机器学习的会议的最新论文(例如 CVPR,NeurIPS,ICLR 等)。

基于度量的学习是进行单样本学习的较旧方法之一。 尽管该区域较旧,但仍在探索许多方面。 一个很好的例子是关于《为短时学习重新研究基于局部描述符的图像到类度量》主题的研究工作。 在本文中,作者提出了一种卷积神经网络架构,称为 D4N深度最近邻神经网络),该架构可提取图像级特征。 它与其他神经网络架构的主要区别是用基于局部描述符的图像到类度量替代了最后一层。

《通过类别遍历查找与几次任务学习相关的任务相关特征》也为改进度量学习方法做出了贡献,方法是引入一个插件框架。 在本文中,作者讨论了众所周知的度量学习方法(例如连体网络和匹配网络)如何一次只关注一个任务,而不是整个学习所有任务。 类别遍历模块CTM)插件组件通过完成所有支持任务来学习重要的尺寸特征。 CTM 在集中器和投影仪单元的帮助下,为相似类别提取通用特征嵌入,并在不同类别中提取唯一特征。 使用 CTM 的输出,我们可以在元学习器之前添加一个强大的先验,这可以使我们更快更好地进行优化。 通过使用此框架,他们显示了基于度量的学习方法的显着改进。

在对象检测和语义分割领域也有一些显着贡献。 让我们讨论其中的两种方法。

小样本领域中的对象检测

《RepMet:用于分类和几次对象检测的基于代表的度量学习》是一种小样本学习对象检测方法。 在本文中,作者提出了一种用于对象区域提议的特征金字塔网络的变体,并且在其顶部,他们添加了基于度量的分类器,该分类器根据与学习的类代表的距离对建议的区域进行分类。 他们还通过在 ImageNet 数据集上建立了用于少发物体检测任务的基准,为研究界做出了贡献。

同样,《具有共同注意和共同激励的一次目标检测》也可以使用传统的视觉方法,在建议的区域基础上进行过滤。 在这项工作中,作者假设将提供目标图像和查询图像。 例如,如果我们要检测笔架,则目标图像将是笔架,而查询图像将是桌子上的笔架。 在这种方法中,我们首先从目标图像中提取有关对象的空间信息,然后从查询图像中提取上下文对象。 上下文和空间信息在确定对象方面起着重要作用。 例如,如果有一张桌子,出现笔架的可能性就会增加。 这类似于人类使用上下文学习的方式。 该模型还通过将输入传递给注意力模型来利用上下文的帮助。

小样本领域中的图像分割

研究工作《CANet:具有迭代细化和专注的小样本学习的类不可知分割网络》证明了医学影像行业的潜在增长。 在本文中,作者提出了一个用于语义分割的两级框架:密集比较模块DCM)和迭代优化模块IOM)。 DCM 通过使用通用的 ResNet 架构提取特征,在训练集示例和测试集示例之间进行了密集的特征比较,而 IOM 通过残差块加 CNN 和粗糙的空间金字塔池ASPP)模块。

同样, 《PANet:具有原型对齐的几次语义分割》通过以下方式解决了少数镜头分割问题: 度量学习方法。 本文还提出了一种对齐网络,以更好地利用从支持集中提取的信息。 在 PANet 中,最初,网络从特定嵌入空间内的一些支持图像中学习特定于类别的表示,然后通过将每个像素与学习到的特定于类别的表示进行匹配,对查询/目标图像执行分割。 通过使用这种方法,PANet 可以利用支持集中的重要见解,并在几次分割的情况下提供更可靠的概括。

如我们所见,这些解决方案适用于数据有限的情况。 我们如何量化有限的和足够的? 我们需要查看我们希望训练的模型架构的能力以及希望解决的问题的复杂性。 类似于单样本学习,多年来研究人员提出了其他一些方法,也旨在解决数据有限的问题。 在下一节中,我们将学习机器学习的这些领域,以及它们与单样本学习相比的效率。

相关领域

众所周知,单样本学习是机器学习的一个子领域。 有多种不同的相关解决方案与单样本学习方法非常相似,但其解决方案方法略有不同。 这些问题也可以通过使用单样本学习算法来解决。 让我们遍历 ML 的每个相关领域,观察它们与单样本学习问题的相似之处:

  • 半监督学习
  • 学习不平衡
  • 元学习
  • 迁移学习

半监督学习

假设我们有 10,000 个数据点,其中只有 20,000 个被标记,而 80,000 个未被标记。 在这种情况下,我们将采用半监督学习。 在半监督学习中,我们使用未标记的数据来总体上进一步了解人口结构。 半监督学习通过伪标签技术来增加训练集。 也就是说,我们使用 20,000 个带标签的数据集训练模型,并在大小相等的测试数据点上使用该模型为它们创建伪标签。 下图说明了半监督学习架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8F3ksliq-1681785569491)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/59858a66-819e-4871-955e-a0c5ffb64345.png)]

获得伪标签后,我们将实标签与伪标签连接起来,将实特征与伪特征连接在一起。 连接后,我们训练了一个新模型,事实证明该模型比初始模型更准确。 我们一直这样做,直到获得最佳精度。

学习不平衡

在学习不平衡的情况下,我们有一个不平衡的数据集。 也就是说,我们从一个类别中获得的样本要多于从其他类别中获得的样本。 这也被普遍称为偏态分布数据集。让我们看一下处理偏斜数据集的一些流行方法:

  • 度量标准的选择:度量标准的多种形式可以帮助评估模型的准确率,例如混淆矩阵,精度,召回率和 F1 得分。
  • 算法的选择:参数算法通过数据集学习其参数,因此,如果数据集存在偏差,则参数模型最有可能也会受到偏差。 对于有偏数据集,非参数方法(例如 k 最近邻)和集合(例如 AdaBoost,XGBoost 等)被证明是最佳方法。
  • 数据采样方法的选择:也可以考虑进行数据采样以确保数据集不会保持偏斜。

这种方法接近单样本学习,因为我们期望创建的机器学习模型应该能够从一些示例中学习分布。

要了解有关度量形式的更多信息,请参阅第 2 章,“基于度量的方法”。

元学习

元学习最近在研究界引起了很多关注。 本书中讨论的大多数方法都是元学习型方法,例如与模型无关的元学习和元网络。 元学习是一种在不同任务上训练模型,然后针对特定任务使用通常学到的特征的方法。 它帮助模型在许多任务上学习先验,从而帮助模型在有限的数据下达到优化。 用简单的话来说,元学习是一种训练模型以元学习任何目标的方法。

迁移学习

迁移学习是指使用从解决一个问题中获得的知识并将其用于解决另一个问题的技术。 以下是迁移学习方法的简化视图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gRLMQhm6-1681785569492)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-1shot-learn-py/img/d2619e72-ed14-4933-9ca1-7d658c79f2f2.png)]

换句话说,在一个数据集上训练的神经网络模型可以通过对前一个网络进行微调而用于其他数据集,就像我们如何使用在不同域数据集(例如 MNIST 数据集)上训练的连体网络来提取更好的特征,用于签名匹配,手写匹配等。 迁移学习在深度学习领域引起了很多关注,并已被证明对于许多应用非常有用。 但是,由于数据限制,我们无法在制造,医药,化学等非常见领域中使用它。

应用领域

从理论上讲,单样本学习有多种应用,但直到最近才开始在实际场景中使用。 使用单样本学习已取得了最新进展,例如编写 SQL 代码,改进变形的医学图像以及运行签名验证。 还有其他几个领域仍在研究中。 OpenAI,Google,Microsoft 和 Amazon 等公司正在 AI 研究方面投入巨资。 解决单样本学习将意味着创建具有人类能力的机械大脑。 这种进步可以通过多种方式挽救生命:可以为罕见病的发现铺平道路,解决全球粮食危机或优化供应链模型。

0 人点赞