第5章关系网络与匹配网络 本章将讲解另一个有趣的单样本学习算法———关系网络。它是最简单、最 有效的单样本学习算法之一。元学习旨在解决从少量样本中快速学习和泛化的 问题,关系网络是为了实现这一目标而设计的。它的核心思想是学习一个关系 评分函数,用于比较输入样本之间的相似性,从而进行快速的分类或回归任务。 本章将探讨如何在单样本、少样本与零样本学习场景中使用关系网络。 本章内容: ● 关系网络。 ● 单样本学习中的关系网络。 ● 少样本学习中的关系网络。 ● 零样本学习中的关系网络。 ● 匹配网络。 5.关系网络 1 在2018 年的论文Learningtocompare:relationnetworkforfew-shot learning[1]中,FloodSung等提出了一种名为关系网络(relationnetwork,RN) 的小样本学习算法。这种算法基于元学习理念,采用有监督学习来估计样本点 之间的距离。通过比较新样本点与过去样本点之间的距离,使RN 算法可以对 新样本点进行分类。虽然它主要适用于小样本分类问题,但其实这种思路对于 任何分类问题都是有效的。 关系网络算法的结构和思路简洁明了,无论是在小样本分类还是大样本分 类问题上,其表现都相当出色。特别是在小样本分类问题上,它超越了之前表现 最好的方法,成为了该算法的一大亮点。 RN 的基本结构包括以下两个主要部分。 (1)特征提取器:对输入样本进行特征提取,通常采用神经网络(如卷积神 经网络、循环神经网络或Transformer等)。这一阶段的目的是将输入样本映射 到一个特征空间,以便更好地衡量相似性。 (2)关系模块:在特征空间中计算输入样本之间的关系评分。关系模块通 常由多层神经网络组成,输入是特征提取器提取的两个样本的特征向量(可以是 连接、拼接、相减等操作后的向量), 输出是一个关系评分,表示两个样本之间的 相似性。 在RN 算法中,特征提取器和距离度量都包含可训练的参数,这意味着它们 是通过监督学习得到的。通过这种方式可以获得一个更好地反映数据特性的特 征提取器和距离度量,从而更准确地对任务特性建模。 通常情况下,距离度量采用的是各种常见的度量方法,如欧氏距离、余弦距 离等。然而,在RN 算法中,距离度量中的参数是可训练的。这意味着在整个元 学习框架中,优化目标函数时,会同时估计特征提取器和距离度量中的参数。这 样有助于提高元学习模型的性能,并取得更好的模型效果。 在训练阶段,RN 通过最小化预测关系评分与真实类别之间的损失来优化 参数。在测试阶段,RN 可以处理新的少样本类别,实现高效的分类或回归 任务。 RN 在许多元学习任务中表现出色,例如图像分类、自然语言处理等。通过 学习比较输入样本之间的相似性,RN 能够在少量样本的情况下实现快速泛化。 1.单样本学习中的关系网络 5.1 在单样本学习任务中,关系网络的训练和应用方式与元学习中略有不同。 因为单样本学习意味着每个类别只有一个样本可用,所以在训练阶段,需要针对 每个类别的单个样本进行特征提取和关系度量的学习。在测试阶段,关系网络 74 第 5 章关系网络与匹配网络 可以处理新的单样本类别,实现高效的分类或回归任务。 众所周知,在单样本学习中,每个类只有一个示例。例如,假设支持集包含 3个类,每个类1个示例。如图5-1所示,有一个包含3个类别的支持集,即{昆 虫,鸟,狗}。 假设有一个查询图像xj ,如图5-2所示,希望预测该查询图像的类,图像如 下所示。 图5- 1 支持 集 图5- 2 待查询图像示例 i], Φ](j]), 以提取特征。由于支持集包含图像,因此可以使用卷积网络作为嵌入函数来学习 嵌入。嵌入函数将提供支持集中每个数据点的特征向量。类似地,将把查询图像 x[x[ 首先,从支持集中获取每个图像x[并将其传递给嵌入函数f[x[ j] 传递给嵌入函数f[Φ](j]) 来学习其嵌入。 因此,一旦有了支持集f[x[和查询集f[x[的特征向量, Φ](i]) Φ](j]) 就 可以使用运算符 Z 组合它们。 Z 可以是任何组合运算符;使用连接作为运算 符,以合并支持集和查询集的特征向量, f[x[f[x[ 即Z(Φ](i]),Φ](j]))。 如图5-3所示, Φ](i]) Φ](j]) 我们将合并支持集f[x[和查询集f[x[的特 征向量。但是这样的组合有什么用呢? 这将帮助我们理解支持集中图像的特征 向量与查询图像的特征向量之间的关系。在示例中,它将帮助我们理解昆虫、鸟 和狗的图像的特征向量与查询图像的特征向量之间的关系,如图5-3所示。 但是如何衡量这种关联性呢? 这就是为什么使用关系函数g(Φ)的原因。 75 图5- 3 关系图 将这些组合的特征向量传递给关系函数,该函数将生成从0到1的关系得分,代 表支持集x[中的样本与查询集x[中的样本之间的相似性。 i] j] 以下等式说明了如何计算关系网络中的关系得分。 r[j]=gΦ (fΦ (xi),fΦ (xj))) (5-1) i,Z( 在该等式中,i,表示在支持集中的每个类别和查询图像之间的相似性 r[j] 的关系分数。由于支持集中有3个类别,在查询集中有1个图像,因此将获得3 个分数,表明支持集中的所有3个类别与查询图像的相似程度。 图5-4显示了在单样本学习设置中关系网络的整体表示。 图5- 4 关系网络的整体表示 76 第 5 章关系网络与匹配网络 1.少样本学习中的关系网络 5.2 在少样本学习任务中,关系网络的训练方式通常采用N-wayK-shot的设 置。这意味着在每个训练任务中,从 N 个类别中每个类别选择 K 个样本作为支 持集。关系网络根据支持集中的样本学习类别之间的相似性度量。在测试阶 段,关系网络需要对新的样本进行分类,这些样本可能来自训练中未见过的类 别。关系网络将新样本与支持集中的样本进行关系度量比较,根据相似性对新 样本进行分类[7-10]。 ( 关系网络在少样本学习任务中的具体流程主要分为以下几个步骤。 1)数据集划分。将数据集划分为训练集、验证集和测试集。训练集用于 训练关系网络模型,验证集用于调整模型超参数,测试集用于评估模型在未知数 据上的性能。 (2)任务构建。在少样本学习中,通常使用N-wayK-shot的设置。对于每 个训练任务,从训练集中随机选择 N 个类别,然后从每个类别中选择 K 个样本 作为支持集。此外,从每个类别中选择一个或多个样本作为查询集,用于计算损 失和更新模型参数。 (3)特征提取。利用特征提取器(如卷积神经网络、循环神经网络或 Transformer等)对支持集和查询集中的样本进行特征提取。这一阶段的目的 是将输入样本映射到一个特征空间,以便更好地衡量相似性。 (4)关系度量。在特征空间中计算查询集中每个样本与支持集中所有样本 之间的关系评分。关系模块通常由多层神经网络组成,输入是特征提取器提取 的两个样本的特征向量(可以是连接、拼接、相减等操作后的向量), 输出是一个 关系评分,表示两个样本之间的相似性。 (5)损失计算与参数更新。根据查询集中样本的关系评分计算损失函数 (如交叉熵损失), 然后使用梯度下降法(如SGD 、Adam 等优化器)更新模型 参数 ( 。 6)验证与模型选择。在验证集上进行类似的任务构建和关系网络应用过 程,根据验证集上的性能调整模型超参数和选择最优模型。 77 (7)测试与性能评估。在测试集上应用训练好的关系网络,评估模型在未 知数据上的泛化性能。 1.零样本学习中的关系网络 5.3 零样本学习(zero-shotlearning,ZSL)是一种特殊的少样本学习任务,要求 模型在训练过程中没有看到目标类别的任何样本。在零样本学习中,关系网络 可以通过学习类别属性(如语义信息、视觉特征等)之间的关系来推断未见过的 类别。 在零样本学习中使用关系网络的具体流程如下。 (1)数据集划分。将数据集划分为训练集、验证集和测试集。训练集用于 训练关系网络模型,验证集用于调整模型超参数,测试集用于评估模型在未知数 据上的性能。 (2)任务构建。与传统的少样本学习任务不同,零样本学习中训练集包含 已知类别的样本,而测试集包含未知类别的样本。这些未知类别没有出现在训 练过程中。 (3)属性信息。为每个类别分配一个属性向量,通常采用语义嵌入(如 Word2Vec、Glove等)表示类别的语义信息。属性向量可以帮助关系网络理解 类别之间的关系,从而实现对未见过类别的泛化。 (4)特征提取。利用特征提取器(如卷积神经网络、循环神经网络或 Transformer等)对训练集中的样本进行特征提取。 (5)关系模块训练。在特征空间中计算训练集中样本与对应属性向量之间 的关系评分。通过最小化训练集上的损失函数(如交叉熵损失)来训练关系 模块 ( 。 6)验证与模型选择。在验证集上进行类似的任务构建和关系网络应用过 程,根据验证集上的性能调整模型超参数和选择最优模型。 (7)测试与性能评估。在测试集上应用训练好的关系网络,利用属性向量 为未知类别的样本计算关系评分。根据关系评分对未知类别的样本进行分类, 并评估模型在未知数据上的泛化性能。 78 第5 章 关系网络与匹配网络 以下是一个基于RelationNetwork的单样本学习示例代码,数据集是常用 的mini-ImageNet。 (1)导入所需依赖。 import tensorflow as tf from tensorflow.keras import layers import task_generator as tg import math from PIL import Image import matplotlib.pyplot as plt import os import argparse import numpy as np import scipy as sp import scipy.stats (2)定义超参数,可以根据实际需要更改。 parser = argparse.ArgumentParser(description="One Shot Visual Recognition") parser.add_argument("-f","--feature_dim",type = int, default = 64) parser.add_argument("-r","--relation_dim",type = int, default = 8) parser.add_argument("-w","--class_num",type = int, default = 5) parser.add_argument("-s","--sample_num_per_class",type = int, default = 1) parser.add_argument("-b","--batch_num_per_class",type = int, default = 15) parser.add_argument("-e","--episode",type = int, default= 500000) parser.add_argument("-t","--test_episode", type = int, default = 600) parser.add_argument("-l","--learning_rate", type = float, default = 0.001) parser.add_argument("-u","--hidden_unit",type=int,default=10) args = parser.parse_args() #Hyper Parameters FEATURE_DIM = args.feature_dim RELATION_DIM = args.relation_dim CLASS_NUM = args.class_num SAMPLE_NUM_PER_CLASS = args.sample_num_per_class BATCH_NUM_PER_CLASS = args.batch_num_per_class EPISODE = args.episode TEST_EPISODE = args.test_episode LEARNING_RATE = args.learning_rate HIDDEN_UNIT = args.hidden_unit 79 (3)定义函数,计算测试准确率的平均值和置信区间。 def mean_confidence_interval(data, confidence=0.95): a = 1.0*np.array(data) n = len(a) m, se = np.mean(a), scipy.stats.sem(a) h = se * sp.stats.t._ppf((1+confidence)/2., n-1) return m,h (4)定义两个神经网络模型:CNNEncoder和RelationNetwork。CNNEncoder 用于提取特征,RelationNetwork用于比较两个样本之间的相似性。 class CNNEncoder(tf.keras.Model): def __init__(self): super(CNNEncoder, self).__init__(name='CNNEncoder') self.conv2a = layers.Conv2D(filters= 64,input_shape=(84,84,3),kernel_ size=(3,3),padding='valid',bias_initializer='glorot_uniform') self.bn2a = layers.BatchNormalization(axis=3,momentum=0.0, epsilon=1e-05,fused=False) self.pool2a = layers.MaxPool2D(pool_size=(2,2)) self.conv2b = layers.Conv2D(filters=64,kernel_size=(3,3), padding='valid',bias_initializer='glorot_uniform') self.bn2b = layers.BatchNormalization(axis=3,momentum=0.0, epsilon=1e-05,fused=False) self.pool2b = layers.MaxPool2D(pool_size=(2,2)) self.conv2c = layers.Conv2D(filters=64,kernel_size=(3,3), padding='same',bias_initializer='glorot_uniform') self.bn2c = layers.BatchNormalization(axis=3,momentum=0.0, epsilon=1e-05,fused=False) self.conv2d = layers.Conv2D(filters=64,kernel_size=(3,3), padding='same',bias_initializer='glorot_uniform') self.bn2d = layers.BatchNormalization(axis=3,momentum=0.0, epsilon=1e-05,fused=False) def call(self, input_tensor, training=False): x = self.conv2a(input_tensor) x = self.bn2a(x,training) x = tf.nn.relu(x) x = self.pool2a(x) x = self.conv2b(x) x = self.bn2b(x,training) 80 第5 章 关系网络与匹配网络 x = tf.nn.relu(x) x = self.pool2b(x) x = self.conv2c(x) x = self.bn2c(x,training) x = tf.nn.relu(x) x = self.conv2d(x) x = self.bn2d(x,training) return tf.nn.relu(x) class RelationNetwork(tf.keras.Model): def __init__(self): super(RelationNetwork, self).__init__(name='RelationNetwork') self.conv2a = layers.Conv2D(filters=64,input_shape=(19,19,128), kernel_size=(3,3),padding='valid',bias_initializer='glorot_uniform') self.bn2a = layers.BatchNormalization(axis=3,momentum=0.0, epsilon=1e-05,fused=False) self.pool2a = layers.MaxPool2D(pool_size=(2,2)) self.conv2b = layers.Conv2D(filters=64,kernel_size=(3,3),padding= 'valid',bias_initializer='glorot_uniform') self.bn2b = layers.BatchNormalization(axis=3,momentum=0.0, epsilon=1e-05,fused=False) self.pool2b = layers.MaxPool2D(pool_size=(2,2)) self.fc1 = layers.Dense(8,activation='relu') self.fc2 = layers.Dense(1,activation='sigmoid') def call(self, input_tensor, training=False): x = self.conv2a(input_tensor) x = self.bn2a(x,training) x = tf.nn.relu(x) x = self.pool2a(x) x = self.conv2b(x) x = self.bn2b(x,training) x = tf.nn.relu(x) x = self.pool2b(x) x = layers.Flatten()(x) x = self.fc1(x) x = self.fc2(x) return x (5)使用TensorFlow2.x中的@tf.function 装饰器来构建计算图。通过 train_one_step()函数实现单步训练的计算图,使用MSE 损失函数来计算训练 81 误差,并通过反向传播算法更新网络参数。通过test()函数实现测试的计算图, 用于评估网络在测试集上的分类精度。 @tf.function def train_one_step(feature_encoder, relation_network, feature_encoder_optim, relation_network_optim, samples, sample_labels, batches, batch_labels): with tf.GradientTape() as feature_encoder_tape, tf.GradientTape() as relation_network_tape: sample_features = feature_encoder(samples,True) batch_features = feature_encoder(batches,True) sample_features_ext = tf.repeat(tf.expand_dims(sample_features, 0),BATCH_NUM_PER_CLASS*CLASS_NUM,axis=0) batch_features_ext = tf.repeat(tf.expand_dims(batch_features,0), CLASS_NUM,axis=0) batch_features_ext = tf.transpose(batch_features_ext,(1,0,2,3,4)) relation_pairs = tf.reshape(tf.concat([sample_features_ext,batch_ features_ext],axis=4),(-1,19,19,FEATURE_DIM*2)) relations = tf.reshape(relation_network(relation_pairs,True),(-1, CLASS_NUM)) mse = tf.keras.losses.MeanSquaredError() one_hot_labels = tf.squeeze(batch_labels) loss = mse(relations,one_hot_labels) grads_feature_encoder = feature_encoder_tape.gradient(loss, feature_encoder.trainable_variables) grads_relation_network = relation_network_tape.gradient(loss, relation_network.trainable_variables) feature_encoder_optim.apply_gradients(zip(grads_feature_encoder, feature_encoder.trainable_variables)) relation_network_optim.apply_gradients(zip(grads_relation_network, relation_network.trainable_variables)) return loss @tf.function def test(feature_encoder, relation_network, samples, sample_labels, batches, batch_labels): sample_features = feature_encoder(samples,True) batch_features = feature_encoder(batches,True) sample_features_ext = tf.repeat(tf.expand_dims(sample_features,0), 3*CLASS_NUM,axis=0) 82