2.md 50.8 KB
Newer Older
W
wizardforcel 已提交
1 2
# 基于指标的方法

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

W
wizardforcel 已提交
5
在本章中,我们将学习可以从较小的数据集中学习正确的数学表示形式的深度学习架构。 总体而言,我们的目标是创建一种无需大量数据收集或训练过程即可概括不熟悉类别的架构。
W
wizardforcel 已提交
6 7 8 9 10 11 12 13 14 15 16 17

本章将涵盖以下主题:

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

# 技术要求

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

W
wizardforcel 已提交
18 19
*   Python
*   Anaconda
W
wizardforcel 已提交
20
*   Jupyter 笔记本
W
wizardforcel 已提交
21
*   PyTorch
W
wizardforcel 已提交
22 23 24
*   Matplotlib
*   scikit 学习

W
wizardforcel 已提交
25
[您可以在该书的 GitHub 存储库中找到该章的代码文件](https://github.com/PacktPublishing/Hands-On-One-shot-Learning-with-Python)
W
wizardforcel 已提交
26

W
wizardforcel 已提交
27
# 参数方法概述
W
wizardforcel 已提交
28 29 30

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

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

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

![](img/010cceaa-cf25-4883-a25d-5800e070dc87.png)

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

W
wizardforcel 已提交
39
# 神经网络学习器
W
wizardforcel 已提交
40

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

![](img/47a48789-8ff8-42cf-a648-96d68b1df236.png)

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

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

W
wizardforcel 已提交
50
在下一节中,我们将了解这些损失函数如何帮助架构学习不同的图像特征,以及如何结合这些特征来为其他各种目标训练模型。
W
wizardforcel 已提交
51 52 53 54 55 56 57

# 可视化参数

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

![](img/2ea3be34-ced7-4c60-a302-98d1f0c35e3e.jpeg)

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

W
wizardforcel 已提交
60
# 了解连体网络
W
wizardforcel 已提交
61

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

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

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

![](img/0d94a582-9d00-4f5b-a28f-7e4df967ea31.png)

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

W
wizardforcel 已提交
72
# 构建
W
wizardforcel 已提交
73

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

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

W
wizardforcel 已提交
78
我们不单独训练层的主要原因有两个:
W
wizardforcel 已提交
79 80 81 82 83 84

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

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

W
wizardforcel 已提交
85
下图说明了一个示例连体网络架构:
W
wizardforcel 已提交
86 87 88

![](img/be19cfde-bc78-4b64-92a9-85e89e1df3ce.png)

W
wizardforcel 已提交
89
如我们所见,前面的图是简单明了的。 现在,我们将讨论训练连体网络所需的预处理步骤。
W
wizardforcel 已提交
90 91 92

# 预处理

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

W
wizardforcel 已提交
95 96
*   相似的图像对
*   不同的图像对
W
wizardforcel 已提交
97

W
wizardforcel 已提交
98
下图说明了 Omniglot 的连体网络目标的示例:
W
wizardforcel 已提交
99 100 101

![](img/67555406-abd0-4471-b5e1-deb9c87c2813.jpg)

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

# 对比损失函数

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

![](img/aaedf542-dd84-4589-8774-94190df0b2f6.png)

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

W
wizardforcel 已提交
112
让我们进一步求解损失方程。 以`Y = 1`表示相似的货币对:
W
wizardforcel 已提交
113 114 115

![](img/029593cf-8b9d-4162-8c8f-4b58a97c679d.png)

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

W
wizardforcel 已提交
118
类似地,对于不相似对的`Y = 0`,这将产生以下结果:
W
wizardforcel 已提交
119 120 121

![](img/50d96d9a-18f2-41b9-a4af-0df76984984e.png)

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

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

W
wizardforcel 已提交
126
# 三重损失函数
W
wizardforcel 已提交
127 128 129 130 131

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

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

W
wizardforcel 已提交
132
*   **锚点**`A`):主数据点
W
wizardforcel 已提交
133 134
*   **正例**`P`):类似于锚点的数据点
*   **负例**`N`):与锚点不同的数据点
W
wizardforcel 已提交
135

W
wizardforcel 已提交
136
考虑到`f(X)`是连体网络的输出,理想情况下,我们可以假设以下内容:
W
wizardforcel 已提交
137 138 139 140 141 142 143

![](img/73760b22-b575-40f1-85be-396c6822a5d5.png)

用距离函数的术语,我们可以说以下几点:

![](img/e89f8c0f-2387-40bf-9cd0-b346ff3fdc52.png)

W
wizardforcel 已提交
144
由于我们不希望连体网络学习`f(X) = 0, X ∈ R`,因此我们将添加余量,类似于对比损失函数:
W
wizardforcel 已提交
145 146 147 148 149 150 151 152 153 154 155

![](img/aa0e72e6-1a0c-4888-a75d-1e4276061bb4.png)

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

![](img/5c387438-e9c3-499e-b6ed-5c92097ee9a0.png)

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

![](img/8d24886d-e1ca-4322-848e-32251f118edd.png)

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

# 应用领域

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

# 了解匹配网络

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

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

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

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

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

# 模型架构

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

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

![](img/3118bc82-3dc7-473f-8e77-f57c6f17b84e.png)

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

W
wizardforcel 已提交
188
# 训练器
W
wizardforcel 已提交
189

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

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

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

W
wizardforcel 已提交
196
匹配网络将支持集(`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)`的最简单形式,它将是支持集标签的线性组合形式:
W
wizardforcel 已提交
197 198 199

![](img/94576b62-c681-443b-ba09-e6e280e2407f.png)

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

W
wizardforcel 已提交
202
例如,如果我们有 2 个类,分别为 0 和 1,则 2 个示例(`k = 2`)如下:`y = (0, 1)`
W
wizardforcel 已提交
203

W
wizardforcel 已提交
204
通过将`y`变成单热编码向量,我们将获得以下信息:
W
wizardforcel 已提交
205 206 207 208 209 210 211

![](img/725b5feb-44c8-43ed-8a13-b4c530b590e8.png)

它们各自的内核值如下:

![](img/8c500245-cdfc-485c-8f8b-16692f891db5.png)

W
wizardforcel 已提交
212
通过引入`a``y`的值,我们将获得以下方程式:
W
wizardforcel 已提交
213 214 215 216 217 218 219

![](img/0b1398a4-325e-475e-84e6-a59cdc029dfd.png)

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

![](img/d3c88db6-018b-4740-a01d-e40f57052f55.png)

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

![](img/6d449b9b-9813-4ed7-9f19-37f2889465e4.png)

W
wizardforcel 已提交
224
在此,`c`是训练集和测试数据点的嵌入之间的余弦相似度函数。
W
wizardforcel 已提交
225

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

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

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

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

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

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

W
wizardforcel 已提交
238
**解决方案**:匹配网络使用 LSTM,并且具有对支持集`S`的注意。
W
wizardforcel 已提交
239 240 241

![](img/d8c095d5-729c-49ac-8834-ef3b2c1f861e.png)

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

W
wizardforcel 已提交
244
下图说明了匹配的网络架构:
W
wizardforcel 已提交
245 246 247

![](img/5840cf24-bfa3-424f-a151-23cf76511e2c.png)

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

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

W
wizardforcel 已提交
256
6.  然后,从`g(x[i])``f'(x)`获得的输出用于检查查询图像属于哪个类别:
W
wizardforcel 已提交
257 258 259

![](img/61fbf48b-9b8f-4de8-96b1-487001701281.png)

W
wizardforcel 已提交
260
在此等式中,`y_hat`是支持集中标签的加权和。
W
wizardforcel 已提交
261

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

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

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

W
wizardforcel 已提交
268
在下一部分中,我们将使用著名的 MNIST 和 Omniglot 数据集探索连体网络的实现和匹配网络架构。
W
wizardforcel 已提交
269 270 271

# 编码练习

W
wizardforcel 已提交
272
在本节中,我们将学习连体网络和匹配网络的实现。
W
wizardforcel 已提交
273

W
wizardforcel 已提交
274
让我们从连体网络开始。
W
wizardforcel 已提交
275 276 277 278 279

# 连体网络-MNIST 数据集

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

W
wizardforcel 已提交
280
1.  数据预处理:创建偶对
W
wizardforcel 已提交
281
2.  创建连体网络架构
W
wizardforcel 已提交
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
3.  使用小型 MNIST 数据集对其进行训练
4.  可视化嵌入

执行以下步骤进行练习:

1.  首先,使用以下代码导入所需的所有库:

```py
# -*- 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
```

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

W
wizardforcel 已提交
307 308
*   相似对;`y = 1`
*   不同对;`y = 0`
W
wizardforcel 已提交
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368

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

2.  要预处理数据并为模型创建迭代器,请首先创建`Dataset`类:

```py
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
```

3.  在创建迭代器之前,让我们创建`pairs`函数并对其进行预处理:

```py
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
```

W
wizardforcel 已提交
369
4.  然后,创建`iterator`函数。 为了我们的训练目的,这将返回给定的`batchsize`参数集:
W
wizardforcel 已提交
370 371 372 373 374 375 376 377 378

```py
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
```

W
wizardforcel 已提交
379
5.  然后,创建`loss`函数。 众所周知,`contrastive_loss_function`包含两个部分:
W
wizardforcel 已提交
380

W
wizardforcel 已提交
381 382
*   对于类似的点:`(1-y)*(distance_function)^2`
*   对于不同的点: `y*{max(0,(m-distance_function^2)}`
W
wizardforcel 已提交
383

W
wizardforcel 已提交
384
4.  在这里,`distance_function`被视为欧几里得距离,也称为**均方根**
W
wizardforcel 已提交
385 386 387 388 389 390 391 392 393 394 395 396 397 398

```py
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
```

W
wizardforcel 已提交
399
6.  接下来,创建连体网络架构。 为此,我们首先创建一个带有两个函数的名为`SiameseNetwork`的类:
W
wizardforcel 已提交
400 401 402 403

*   `forward_once`:在`forward_once`中,训练数据将穿过所有层并返回输出的嵌入。
*   `forward`:在`forward`中,对于给定的输入对,将两次调用`forward_once`,这将返回获得的嵌入的 NumPy 数组。

W
wizardforcel 已提交
404
正如在连体网络的理论部分所讨论的那样,我们与两个并行层共享参数,因此我们不需要显式创建两个分支,我们只需创建一个分支即可:
W
wizardforcel 已提交
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442

```py
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
```

7.  减少`MNIST`数据集并选择`2000`随机点,将`batchsize`设置为 2 的任意幂(例如`128`),然后导入`MNIST`数据集:

```py
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)
```

W
wizardforcel 已提交
443
8.  我们在“步骤 4”中创建了一个迭代器,我们将使用它来创建训练和测试集迭代器:
W
wizardforcel 已提交
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498

```py
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)
```

9.  然后,我们训练模型一定数量的时间并打印结果:

```py
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()))
```

这将给出以下输出:

```py
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
```

W
wizardforcel 已提交
499
0.  现在,让我们创建所有用于绘制嵌入的函数和一个损失函数:
W
wizardforcel 已提交
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518

```py
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)
```

W
wizardforcel 已提交
519
1.  使用以下代码绘制`loss`函数:
W
wizardforcel 已提交
520 521 522 523 524 525 526 527 528

```py
plot_loss(train_loss)
```

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

![](img/aba64cc3-f3b4-4d14-a3bf-8d0ae4a62c36.png)

W
wizardforcel 已提交
529
2.  然后,我们将定义`test_model``testing_plots`来绘制`MNIST`数据集的测试集嵌入:
W
wizardforcel 已提交
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554

```py
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)
```

W
wizardforcel 已提交
555
3.  然后,绘制`testing_plots`
W
wizardforcel 已提交
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570

```py
testing_plots(model)
```

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

![](img/9b174385-0416-496d-b96a-6eee5e7b5f61.png)

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

# 匹配网络– Omniglot 数据集

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

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

W
wizardforcel 已提交
573
您可以从[这里](https://github.com/brendenlake/omniglot)下载 Omniglot 数据集。
W
wizardforcel 已提交
574

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

W
wizardforcel 已提交
577
*   嵌入提取器`g`
W
wizardforcel 已提交
578 579 580
*   完全上下文嵌入和双向 LSTM `f`
*   余弦相似距离函数`c`
*   注意模型,`softmax(c)`
W
wizardforcel 已提交
581 582
*   损失函数,交叉熵损失

W
wizardforcel 已提交
583
现在,我们将遍历匹配网络的每个部分并实现它:
W
wizardforcel 已提交
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601

1.  导入所有库:

```py
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
```

W
wizardforcel 已提交
602
2.  我们将加载`omniglot`数据集,该数据集将使用帮助程序脚本转换为`.npy`格式。 在帮助程序脚本中,我们仅以以下大小格式加载数据:`[total_number, character, 28,28]`(有关更多详细信息,请通过本书的 GitHub 存储库中的`helper.py`脚本进行操作):
W
wizardforcel 已提交
603 604 605 606 607 608 609 610 611 612 613 614

```py
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
```

W
wizardforcel 已提交
615
如果您想了解有关数据加载方法的更多信息,[可以参考 GitHub 上的`helper.py`文件](https://github.com/PacktPublishing/Hands-On-One-shot-Learning-with-Python/tree/master/Chapter02)
W
wizardforcel 已提交
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631

3.  使用规范化方法预处理图像:

```py
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}
```

W
wizardforcel 已提交
632
4.  现在,运行以下代码以可视化由 20 个人编写的一个字符的第`0`个示例:
W
wizardforcel 已提交
633 634 635 636 637 638 639 640 641 642 643 644 645 646

```py
temp = x_train[0,:,:,:,:] 
for i in range(0,20):
    plt.figure()
    plt.imshow(temp[i,:,:,0])
```

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

![](img/fd4f59b6-874c-4f35-b486-0072c2a2426a.png)

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

W
wizardforcel 已提交
647
要加载 Omniglot 数据集并准备将其用于匹配的网络架构,我们需要创建以下内容:
W
wizardforcel 已提交
648 649 650 651 652 653 654

*   标签集:`choose_label`
*   支持集:`support_set_x``support_set_y`
*   支持集示例中的一批

我们将执行以下步骤:

W
wizardforcel 已提交
655
1.  首先,创建一个可以提供支持集和目标集的批量:
W
wizardforcel 已提交
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716

```py
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
```

W
wizardforcel 已提交
717
如果您还记得,在匹配的网络架构中,网络有四个主要部分:
W
wizardforcel 已提交
718

W
wizardforcel 已提交
719 720
*   嵌入提取器(`g`
*   全文上下文嵌入(`f`
W
wizardforcel 已提交
721
*   注意模型(`a`
W
wizardforcel 已提交
722
*   距离函数(`c`
W
wizardforcel 已提交
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777

2.  创建一个分类器:

```py
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
```

W
wizardforcel 已提交
778
3.  在分类器之后创建注意力模型。`a(x, x_hat)`为余弦相似度的 softmax:
W
wizardforcel 已提交
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918

```py
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
```

4.  创建一个距离网络,该距离网络将从测试图像和训练嵌入中获取输出以计算距离。 找到支持集和`input_test_image`之间的余弦相似度:

```py
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()
```

5.  创建`BidirectionalLSTM`,它将从测试图像获取输入和输出,并将它们放在相同的嵌入空间中。 如果我们希望使用全上下文嵌入,则匹配网络为此引入了双向 LSTM:

```py
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

```

6.  现在,让我们结合所有已制成的小模块并创建一个匹配的网络:

```py
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
```

W
wizardforcel 已提交
919
7.  创建一个数据集加载器。 对于我们的案例,当我们使用 Omniglot 数据集时,它将创建一个 Omniglot 构建器,该构建器将调用匹配的网络并运行其周期以进行训练,测试和验证:
W
wizardforcel 已提交
920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032

```py
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
```

8.  设置实验变量:

```py
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
```

9.  现在,运行实验:

```py
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))
```

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

```py
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
```

W
wizardforcel 已提交
1033
0.  现在,通过运行以下代码块来获得测试的准确率:
W
wizardforcel 已提交
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045

```py
total_test_c_loss, total_test_accuracy = run_epoch(total_test_batches,'test')
print("test_accuracy:{}%".format(total_test_accuracy*100))
```

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

```py
test_accuracy:86.0%
```

W
wizardforcel 已提交
1046
1.  让我们可视化我们的结果:
W
wizardforcel 已提交
1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060

```py
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/97d99dae-968d-45c3-9d2f-13cf0b663c2b.png)

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

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

W
wizardforcel 已提交
1065
# 总结
W
wizardforcel 已提交
1066

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

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

# 问题

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

# 进一步阅读

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

W
wizardforcel 已提交
1082 1083
*   [《用于一次图像识别的连体神经网络》](https://www.cs.cmu.edu/~rsalakhu/papers/oneshot1.pdf)
*    [《一次学习的匹配网络》](https://arxiv.org/pdf/1606.04080.pdf)