Python 元学习实用指南:1~5(1)https://developer.aliyun.com/article/1426919
最后,我们将x_genuine_pair
和x_imposite
都连接到X
以及y_genuine
和y_imposite
都连接到Y
:
size = 2 total_sample_size = 10000 def get_data(size, total_sample_size): #read the image image = read_image('data/orl_faces/s' + str(1) + '/' + str(1) + '.pgm', 'rw+') #reduce the size image = image[::size, ::size] #get the new size dim1 = image.shape[0] dim2 = image.shape[1] count = 0 #initialize the numpy array with the shape of [total_sample, no_of_pairs, dim1, dim2] x_geuine_pair = np.zeros([total_sample_size, 2, 1, dim1, dim2]) # 2 is for pairs y_genuine = np.zeros([total_sample_size, 1]) for i in range(40): for j in range(int(total_sample_size/40)): ind1 = 0 ind2 = 0 #read images from same directory (genuine pair) while ind1 == ind2: ind1 = np.random.randint(10) ind2 = np.random.randint(10) # read the two images img1 = read_image('data/orl_faces/s' + str(i+1) + '/' + str(ind1 + 1) + '.pgm', 'rw+') img2 = read_image('data/orl_faces/s' + str(i+1) + '/' + str(ind2 + 1) + '.pgm', 'rw+') #reduce the size img1 = img1[::size, ::size] img2 = img2[::size, ::size] #store the images to the initialized numpy array x_geuine_pair[count, 0, 0, :, :] = img1 x_geuine_pair[count, 1, 0, :, :] = img2 #as we are drawing images from the same directory we assign label as 1\. (genuine pair) y_genuine[count] = 1 count += 1 count = 0 x_imposite_pair = np.zeros([total_sample_size, 2, 1, dim1, dim2]) y_imposite = np.zeros([total_sample_size, 1]) for i in range(int(total_sample_size/10)): for j in range(10): #read images from different directory (imposite pair) while True: ind1 = np.random.randint(40) ind2 = np.random.randint(40) if ind1 != ind2: break img1 = read_image('data/orl_faces/s' + str(ind1+1) + '/' + str(j + 1) + '.pgm', 'rw+') img2 = read_image('data/orl_faces/s' + str(ind2+1) + '/' + str(j + 1) + '.pgm', 'rw+') img1 = img1[::size, ::size] img2 = img2[::size, ::size] x_imposite_pair[count, 0, 0, :, :] = img1 x_imposite_pair[count, 1, 0, :, :] = img2 #as we are drawing images from the different directory we assign label as 0\. (imposite pair) y_imposite[count] = 0 count += 1 #now, concatenate, genuine pairs and imposite pair to get the whole data X = np.concatenate([x_geuine_pair, x_imposite_pair], axis=0)/255 Y = np.concatenate([y_genuine, y_imposite], axis=0) return X, Y
现在,我们生成数据并检查数据大小。 如您所见,我们有 20,000 个数据点,其中 10,000 个是真实对,而 10,000 个是非对:
X, Y = get_data(size, total_sample_size) X.shape (20000, 2, 1, 56, 46) Y.shape (20000, 1)
接下来,我们将训练和测试的数据划分为 75% 的训练和 25% 的测试比例:
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=.25)
现在我们已经成功地生成了数据,我们就建立了连体网络。 首先,我们定义基础网络,该网络基本上是用于特征提取的卷积网络。 我们使用 ReLU 激活和最大池化以及一个平坦层来构建两个卷积层:
def build_base_network(input_shape): seq = Sequential() nb_filter = [6, 12] kernel_size = 3 #convolutional layer 1 seq.add(Convolution2D(nb_filter[0], kernel_size, kernel_size, input_shape=input_shape, border_mode='valid', dim_ordering='th')) seq.add(Activation('relu')) seq.add(MaxPooling2D(pool_size=(2, 2))) seq.add(Dropout(.25)) #convolutional layer 2 seq.add(Convolution2D(nb_filter[1], kernel_size, kernel_size, border_mode='valid', dim_ordering='th')) seq.add(Activation('relu')) seq.add(MaxPooling2D(pool_size=(2, 2), dim_ordering='th')) seq.add(Dropout(.25)) #flatten seq.add(Flatten()) seq.add(Dense(128, activation='relu')) seq.add(Dropout(0.1)) seq.add(Dense(50, activation='relu')) return seq
接下来,我们将图像对馈送到基础网络,该基础网络将返回嵌入,即特征向量:
input_dim = x_train.shape[2:] img_a = Input(shape=input_dim) img_b = Input(shape=input_dim) base_network = build_base_network(input_dim) feat_vecs_a = base_network(img_a) feat_vecs_b = base_network(img_b)
feat_vecs_a
和feat_vecs_b
是我们图像对的特征向量。 接下来,我们将这些特征向量馈入能量函数以计算它们之间的距离,然后使用欧几里得距离作为能量函数:
def euclidean_distance(vects): x, y = vects return K.sqrt(K.sum(K.square(x - y), axis=1, keepdims=True)) def eucl_dist_output_shape(shapes): shape1, shape2 = shapes return (shape1[0], 1) distance = Lambda(euclidean_distance, output_shape=eucl_dist_output_shape)([feat_vecs_a, feat_vecs_b])
现在,我们将周期长度设置为13
,并使用 RMS 属性进行优化并定义我们的模型:
epochs = 13 rms = RMSprop() model = Model(input=[input_a, input_b], output=distance)
接下来,我们将损失函数定义为contrastive_loss
函数并编译模型:
def contrastive_loss(y_true, y_pred): margin = 1 return K.mean(y_true * K.square(y_pred) + (1 - y_true) * K.square(K.maximum(margin - y_pred, 0))) model.compile(loss=contrastive_loss, optimizer=rms)
现在,我们训练模型:
img_1 = x_train[:, 0] img_2 = x_train[:, 1] model.fit([img_1, img_2], y_train, validation_split=.25, batch_size=128, verbose=2, nb_epoch=epochs)
您会看到损失随着时间的推移而减少:
Train on 11250 samples, validate on 3750 samples Epoch 1/13 - 60s - loss: 0.2179 - val_loss: 0.2156 Epoch 2/13 - 53s - loss: 0.1520 - val_loss: 0.2102 Epoch 3/13 - 53s - loss: 0.1190 - val_loss: 0.1545 Epoch 4/13 - 55s - loss: 0.0959 - val_loss: 0.1705 Epoch 5/13 - 52s - loss: 0.0801 - val_loss: 0.1181 Epoch 6/13 - 52s - loss: 0.0684 - val_loss: 0.0821 Epoch 7/13 - 52s - loss: 0.0591 - val_loss: 0.0762 Epoch 8/13 - 52s - loss: 0.0526 - val_loss: 0.0655 Epoch 9/13 - 52s - loss: 0.0475 - val_loss: 0.0662 Epoch 10/13 - 52s - loss: 0.0444 - val_loss: 0.0469 Epoch 11/13 - 52s - loss: 0.0408 - val_loss: 0.0478 Epoch 12/13 - 52s - loss: 0.0381 - val_loss: 0.0498 Epoch 13/13 - 54s - loss: 0.0356 - val_loss: 0.0363
现在,我们使用测试数据进行预测:
pred = model.predict([x_test[:, 0], x_test[:, 1]])
接下来,我们定义一个用于计算精度的函数:
def compute_accuracy(predictions, labels): return labels[predictions.ravel() < 0.5].mean()
现在,我们对模型的准确率:
compute_accuracy(pred, y_test) 0.9779092702169625
使用连体网络构建音频识别模型
在上一教程中,我们了解了如何使用连体网络识别人脸。 现在,我们将看到如何使用连体网络来识别音频。 我们将训练我们的网络,以区分狗的声音和猫的声音。 可以从此处下载猫和狗音频的数据集。
下载数据后,我们将数据分成三个文件夹:Dogs
,Sub_dogs
和Cats
。 在Dogs
和Sub_dogs
中,放置狗的吠叫音频,在Cats
文件夹中,放置猫的音频。 我们网络的目标是识别音频是狗的吠叫还是其他声音。 众所周知,对于连体网络,我们需要成对输入输入。 我们从Dogs
和Sub_dogs
文件夹中选择一个音频并将其标记为真正对,并从Dogs
和Cats
文件夹中选择一个音频并将它们标记为非对。 即,(Dogs, Sub_dogs)
是真正的对,(Dogs, Cats)
是非配对的。
现在,我们将逐步展示如何训练连体网络以识别音频是狗的吠叫声还是其他声音。
为了更好地理解,您可以检查完整的代码,该代码可以在 Jupyter 笔记本中找到,并在此处进行解释。
首先,我们将加载所有必需的库:
#basic imports import glob import IPython from random import randint #data processing import librosa import numpy as np #modelling from sklearn.model_selection import train_test_split from keras import backend as K from keras.layers import Activation from keras.layers import Input, Lambda, Dense, Dropout, Flatten from keras.models import Model from keras.optimizers import RMSprop
在继续之前,我们加载并收听音频片段:
IPython.display.Audio("data/audio/Dogs/dog_barking_0.wav") IPython.display.Audio("data/audio/Cats/cat_13.wav")
那么,如何将这些原始音频馈送到我们的网络? 我们如何从原始音频中提取有意义的特征? 众所周知,神经网络仅接受向量化输入,因此我们需要将音频转换为特征向量。 我们该怎么做? 嗯,我们可以通过几种机制生成音频的嵌入。 这样的流行机制之一是梅尔频率倒谱系数(MFCC)。 MFCC 使用对数功率谱在频率的非线性梅尔尺度上的线性余弦变换来转换音频的短期功率谱。 要了解有关 MFCC 的更多信息,请查看此不错的教程。
我们将使用librosa
库中的 MFCC 函数来生成音频嵌入。 因此,我们定义了一个名为audio2vector
的函数,该函数在给定音频文件的情况下返回音频嵌入:
def audio2vector(file_path, max_pad_len=400): #read the audio file audio, sr = librosa.load(file_path, mono=True) #reduce the shape audio = audio[::3] #extract the audio embeddings using MFCC mfcc = librosa.feature.mfcc(audio, sr=sr) #as the audio embeddings length varies for different audio, we keep the maximum length as 400 #pad them with zeros pad_width = max_pad_len - mfcc.shape[1] mfcc = np.pad(mfcc, pad_width=((0, 0), (0, pad_width)), mode='constant') return mfcc
我们将加载一个音频文件并查看嵌入内容:
audio_file = 'data/audio/Dogs/dog_barking_0.wav' audio2vector(audio_file) array([[-297.54905127, -288.37618855, -314.92037769, ..., 0\. , 0\. , 0\. ], [ 23.05969394, 9.55913148, 37.2173831 , ..., 0\. , 0\. , 0\. ], [-122.06299523, -115.02627567, -108.18703056, ..., 0\. , 0\. , 0\. ], ..., [ -6.40930836, -2.8602708 , -2.12551478, ..., 0\. , 0\. , 0\. ], [ 0.70572914, 4.21777791, 4.62429301, ..., 0\. , 0\. , 0\. ], [ -6.08997702, -11.40687886, -18.2415214 , ..., 0\. , 0\. , 0\. ]])
现在我们已经了解了如何生成音频嵌入,我们需要为我们的连体网络创建数据。 众所周知,连体网络可以成对接受数据,因此我们定义了获取数据的函数。 我们将创建一个真正的对(Dogs
,Sub_dogs
),并将标签指定为1
,将非正当对创建为(Dogs
,Cats
),并将标签指定为0
:
def get_data(): pairs = [] labels = [] Dogs = glob.glob('data/audio/Dogs/*.wav') Sub_dogs = glob.glob('data/audio/Sub_dogs/*.wav') Cats = glob.glob('data/audio/Cats/*.wav') np.random.shuffle(Sub_dogs) np.random.shuffle(Cats) for i in range(min(len(Cats),len(Sub_dogs))): #imposite pair if (i % 2) == 0: pairs.append([audio2vector(Dogs[randint(0,3)]),audio2vector(Cats[i])]) labels.append(0) #genuine pair else: pairs.append([audio2vector(Dogs[randint(0,3)]),audio2vector(Sub_dogs[i])]) labels.append(1) return np.array(pairs), np.array(labels) X, Y = get_data("/home/sudarshan/sudarshan/Experiments/oneshot-audio/data/")
接下来,我们将训练和测试的数据划分为 75% 的训练和 25% 的测试比例:
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.2)
现在我们已经成功地生成了数据,我们就建立了连体网络。 我们定义了用于特征提取的基本网络,我们使用了三个密集层,中间有一个丢弃层:
def build_base_network(input_shape): input = Input(shape=input_shape) x = Flatten()(input) x = Dense(128, activation='relu')(x) x = Dropout(0.1)(x) x = Dense(128, activation='relu')(x) x = Dropout(0.1)(x) x = Dense(128, activation='relu')(x) return Model(input, x)
接下来,我们将音频对馈送到基础网络,基础网络将返回特征:
input_dim = X_train.shape[2:] audio_a = Input(shape=input_dim) audio_b = Input(shape=input_dim) base_network = build_base_network(input_dim) feat_vecs_a = base_network(audio_a) feat_vecs_b = base_network(audio_b)
feat_vecs_a
和feat_vecs_b
是我们音频对的特征向量。 接下来,我们将这些特征向量馈入能量函数以计算它们之间的距离,然后使用欧几里得距离作为能量函数:
def euclidean_distance(vects): x, y = vects return K.sqrt(K.sum(K.square(x - y), axis=1, keepdims=True)) def eucl_dist_output_shape(shapes): shape1, shape2 = shapes return (shape1[0], 1) distance = Lambda(euclidean_distance, output_shape=eucl_dist_output_shape)([feat_vecs_a, feat_vecs_b])
接下来,我们将周期长度设置为13
,并使用 RMS 属性进行优化:
epochs = 13 rms = RMSprop() model = Model(input=[audio_a, audio_b], output=distance)
最后,我们将损失函数定义为contrastive_loss
并编译模型:
def contrastive_loss(y_true, y_pred): margin = 1 return K.mean(y_true * K.square(y_pred) + (1 - y_true) * K.square(K.maximum(margin - y_pred, 0))) model.compile(loss=contrastive_loss, optimizer=rms)
现在,我们训练模型:
audio1 = X_train[:, 0] audio2 = X_train[:, 1] model.fit([audio_1, audio_2], y_train, validation_split=.25, batch_size=128, verbose=2, nb_epoch=epochs)
您可以了解历代的损失:
Train on 8 samples, validate on 3 samples Epoch 1/13 - 0s - loss: 23594.8965 - val_loss: 1598.8439 Epoch 2/13 - 0s - loss: 62360.9570 - val_loss: 816.7302 Epoch 3/13 - 0s - loss: 17967.6230 - val_loss: 970.0378 Epoch 4/13 - 0s - loss: 20030.3711 - val_loss: 358.9078 Epoch 5/13 - 0s - loss: 11196.0547 - val_loss: 339.9991 Epoch 6/13 - 0s - loss: 3837.2898 - val_loss: 381.9774 Epoch 7/13 - 0s - loss: 2037.2965 - val_loss: 303.6652 Epoch 8/13 - 0s - loss: 1434.4321 - val_loss: 229.1388 Epoch 9/13 - 0s - loss: 2553.0562 - val_loss: 215.1207 Epoch 10/13 - 0s - loss: 1046.6870 - val_loss: 197.1127 Epoch 11/13 - 0s - loss: 569.4632 - val_loss: 183.8586 Epoch 12/13 - 0s - loss: 759.0131 - val_loss: 162.3362 Epoch 13/13 - 0s - loss: 819.8594 - val_loss: 120.3017
总结
在本章中,我们学习了什么是连体网络,以及如何使用连体网络构建人脸和音频识别模型。 我们探索了连体网络的架构,该网络基本上由两个相同的神经网络组成,它们具有相同的权重和架构,并且将这些网络的输出插入到一些能量函数中以了解相似性。
在下一章中,我们将学习原型网络及其变种,例如高斯原型网络和半原型网络。 我们还将看到如何使用原型网络进行全方位字符集分类。
问题
- 什么是连体网络?
- 什么是对比损失函数?
- 能量函数是什么?
- 连体网络所需的数据格式是什么?
- 连体网络有哪些应用?
进一步阅读
三、原型网络及其变体
在上一章中,我们了解了什么是连体网络以及如何将它们用于执行少量学习任务。 我们还探讨了如何使用连体网络进行人脸和音频识别。 在本章中,我们将介绍另一种有趣的少样本学习算法,称为原型网络,该算法能够将其推广到训练集中没有的类。 我们将从了解什么是原型网络开始,然后我们将了解如何使用原型网络在 omniglot 数据集中执行分类任务。 然后,我们将看到原型网络的不同变体,例如高斯原型网络和半原型网络。
在本章中,您将了解以下内容:
- 原型网络
- 原型网络算法
- 将原型网络用于分类
- 高斯原型网络
- 高斯原型网络算法
- 半原型网络
原型网络
原型网络是另一种简单,高效,很少的镜头学习算法。 像连体网络一样,原型网络尝试学习度量空间以执行分类。 原型网络的基本思想是创建每个类的原型表示形式,并根据类原型与查询点之间的距离对查询点(即新点)进行分类。
假设我们有一个包含狮子,大象和狗的图像的支持集,如下图所示:
因此,我们分为三类: {Lion, Eleph, Dog}
。 现在,我们需要为这三个类中的每一个创建一个原型表示。 我们如何构建这三个类的原型? 首先,我们将使用嵌入函数来学习每个数据点的嵌入。 嵌入函数f[φ]()
可以是可用于提取特征的任何函数。 由于我们的输入是图像,因此我们可以使用卷积网络作为嵌入函数,该函数将从输入图像中提取特征:
一旦了解了每个数据点的嵌入,就可以将每个类中数据点的均值嵌入并形成类原型,如下图所示。 因此,类原型基本上就是在类中数据点的平均嵌入:
同样,当有新的数据点(即我们要为其预测标签的查询点)进入时,我们将使用与创建类原型相同的嵌入函数为该新数据点生成嵌入。 是,我们使用卷积网络为查询点生成嵌入:
对查询点进行嵌入后,我们将比较类原型和查询点嵌入之间的距离,以查找查询点所属的类。 我们可以使用欧几里得距离作为查找类原型与查询点嵌入之间距离的度量,如下所示:
在找到类原型与查询点嵌入之间的距离后,我们将 softmax 应用于该距离并获得概率。 由于我们有狮子,大象和狗这三个类,因此我们将获得三个概率。 因此,概率最高的类别将是我们查询点的类别。
由于我们希望网络从几个数据点中学习,也就是说,我们希望执行几次快照学习,因此我们以相同的方式训练网络。 因此,我们使用了间歇式训练-对于每个剧集,我们从数据集中的每个类随机采样一些数据点,我们称其为支持集,仅使用支持集而不是整个数据集来训练网络。 同样,我们从数据集中随机抽取一个点作为查询点,并尝试预测其类别。 因此,通过这种方式,我们的网络受到了如何从较小的数据点集中学习的训练。
下图显示了我们原型网络的整体流程。 如您所见,首先,我们将为支持集中的所有数据点生成嵌入,并通过在类中获取数据点的平均嵌入来构建类原型。 我们还为查询点生成嵌入。 然后,我们计算类原型与查询点嵌入之间的距离。 我们使用欧几里得距离作为距离度量。 然后,我们将 softmax 应用于此距离并获得概率。 如下图所示,由于我们的查询点是狮子,因此狮子的概率很高,为 0.9:
原型网络不仅用于单样本/少样本学习,而且还用于零样本学习。 考虑以下情况:每个类没有数据点,但是您具有包含每个类的高级描述的元信息。 因此,在这些情况下,我们从每个类的元信息中学习嵌入,以形成类原型,然后使用该类原型进行分类。
算法
原型网络的算法如下所示:
- 假设我们有数据集
D
,其中包含{(x1, y1), (x2, y2), ..., (xn, yn)}
其中x
是特征,y
是类别标签。 - 由于我们进行了间歇式训练,因此我们从数据集中
D
中随机抽取每个类别的n
个数据点数,并准备了支持集S
。 - 同样,我们选择
n
个数据点,并准备我们的查询集Q
。 - 我们使用嵌入函数
f[∅]
来学习数据点在支持集中的嵌入。 嵌入函数可以是任何特征提取器,例如,用于图像的卷积网络和用于文本的 LSTM 网络。 - 一旦获得每个数据点的嵌入,就可以通过获取每个类下数据点的平均嵌入来计算每个类的原型:
- 同样,我们学习查询集嵌入。
- 我们计算查询集嵌入和类原型之间的欧几里德距离
d
。 - 我们通过在距离
d
上应用 softmax 来预测查询集类别的概率p [∅](y = k | x)
:
- 我们将损失函数
J(∅)
计算为负对数概率J(∅) = -logp[∅](y = k | x)
,我们尝试使用随机梯度下降法将损失降到最低。
使用原型网络执行分类
现在,我们将看到如何使用原型网络执行分类任务。 我们使用 omniglot 数据集进行分类。 该数据集包含来自 50 个不同字母的 1,623 个手写字符,每个字符都有 20 个不同的示例,这些示例是由不同的人编写的。 由于我们希望我们的网络从数据中学习,因此我们以相同的方式对其进行训练。 我们从每个类中采样五个示例,并将其用作我们的支持集。 我们使用四个卷积块作为编码器来学习支持集的嵌入,并构建类原型。 同样,我们从每个类中为我们的查询集采样五个示例,学习查询集嵌入,并通过比较查询集嵌入和类原型之间的欧式距离来预测查询集类。 让我们逐步了解它会更好地理解这一点。
您还可以在此处查看 Jupyter 笔记本中可用的代码并进行解释。
首先,我们导入所有必需的库:
import os import glob from PIL import Image import numpy as np import tensorflow as tf
现在,我们将探索并查看我们从数据中得到的结果。 众所周知,我们有不同字母的不同字符,每个字符有二十种不同的字母,由不同的人书写。 让我们绘制并检查其中的一些。
让我们从日语字母中绘制一个字符:
Image.open('dahttps://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-meta-learn-py/img/Japanese_(katakana)/character13/0608_01.png')
相同字母的不同变化:
Image.open('dahttps://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-meta-learn-py/img/Japanese_(katakana)/character13/0608_13.png')
让我们看一下梵文字母中的一个字符:
Image.open('dahttps://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-meta-learn-py/img/Sanskrit/character13/0863_09.png')
Image.open('dahttps://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-meta-learn-py/img/Sanskrit/character13/0863_13.png')
我们如何将图像转换为数组? 我们可以使用np.array
将这些图像转换为数组并将其重塑为 28 x 28:
image_name = 'dahttps://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-meta-learn-py/img/Sanskrit/character13/0863_13.png' alphabet, character, rotation = 'Sanskrit/character13/rot000'.split('/') rotation = float(rotation[3:])
您可以看到如下输出:
array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 0., 1., 1., 1., 1., 1., 1., 0., 1., 1., 1., 0., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]],dtype=float32)
现在我们已经了解了数据集中的内容,我们将加载数据集:
root_dir = 'data/'
我们在/data/omniglot/splits/train.txt
文件中有拆分的详细信息,该文件中的语言名称,字符号,旋转信息和/data/omniglot/data/
中的图像是:
train_split_path = os.path.join(root_dir, 'splits', 'train.txt') with open(train_split_path, 'r') as train_split: train_classes = [line.rstrip() for line in train_split.readlines()]
我们发现类的数量如下:
#number of classes no_of_classes = len(train_classes)
现在,我们将示例数量设置为 20,因为我们的数据集中每个类有 20 个示例,并将图像的宽度和高度设置为28 x 28
:
#number of examples num_examples = 20 #image width img_width = 28 #image height img_height = 28 channels = 1
接下来,我们将训练数据集的形状初始化为多个类,示例数以及图像的高度和宽度:
train_dataset = np.zeros([no_of_classes, num_examples, img_height, img_width], dtype=np.float32)
现在,我们读取所有图像,将它们转换为 NumPy 数组,并将它们的标签和值(即train_dataset = [label, values]
)存储在train_dataset
数组中:
for label, name in enumerate(train_classes): alphabet, character, rotation = name.split('/') rotation = float(rotation[3:]) img_dir = os.path.join(root_dir, 'data', alphabet, character) img_files = sorted(glob.glob(os.path.join(img_dir, '*.png'))) for index, img_file in enumerate(img_files): values = 1\. - np.array(Image.open(img_file).rotate(rotation).resize((img_width, img_height)), np.float32, copy=False) train_dataset[label, index] = values
训练数据的形状如下:
train_dataset.shape (4112, 20, 28, 28)
现在我们已经加载了训练数据,我们需要为它们创建嵌入。 我们使用卷积运算生成嵌入,因为我们的输入值是图像。 因此,我们定义了一个具有 64 个过滤器的卷积块,其中批量标准化和 ReLU 作为激活函数。 接下来,我们执行最大池化操作:
def convolution_block(inputs, out_channels, name='conv'): conv = tf.layers.conv2d(inputs, out_channels, kernel_size=3, padding='SAME') conv = tf.contrib.layers.batch_norm(conv, updates_collections=None, decay=0.99, scale=True, center=True) conv = tf.nn.relu(conv) conv = tf.contrib.layers.max_pool2d(conv, 2) return conv
现在,我们定义嵌入函数,该函数为我们提供了包含四个卷积块的嵌入:
def get_embeddings(support_set, h_dim, z_dim, reuse=False): net = convolution_block(support_set, h_dim) net = convolution_block(net, h_dim) net = convolution_block(net, h_dim) net = convolution_block(net, z_dim) net = tf.contrib.layers.flatten(net) return net
请记住,我们不会使用整个数据集进行训练; 由于我们使用的是单样本学习,因此我们从每个类中抽取一些数据点作为支持集,并以情景方式使用支持集训练网络。
现在,我们定义一些重要的变量-我们考虑 50 路五样本学习场景:
#number of classes num_way = 50 #number of examples per class in a support set num_shot = 5 #number of query points for query set num_query = 5 #number of examples num_examples = 20 h_dim = 64 z_dim = 64
接下来,我们为支持和查询集初始化占位符:
support_set = tf.placeholder(tf.float32, [None, None, img_height, img_width, channels]) query_set = tf.placeholder(tf.float32, [None, None, img_height, img_width, channels])
并且我们分别在support_set_shape
和query_set_shape
中存储支持和查询集的形状:
support_set_shape = tf.shape(support_set) query_set_shape = tf.shape(query_set)
我们获得了用于初始化我们的支持和查询集的类数,支持集中的数据点数以及查询集中的数据点数:
num_classes, num_support_points = support_set_shape[0], support_set_shape[1] num_query_points = query_set_shape[1]
接下来,我们为标签定义占位符:
y = tf.placeholder(tf.int64, [None, None]) #convert the label to one hot y_one_hot = tf.one_hot(y, depth=num_classes)
现在,我们使用嵌入函数为支持集生成嵌入:
support_set_embeddings = get_embeddings(tf.reshape(support_set, [num_classes * num_support_points, img_height, img_width, channels]), h_dim, z_dim)
我们计算每个类的原型,这是该类支持集嵌入的均值向量:
embedding_dimension = tf.shape(support_set_embeddings)[-1] class_prototype = tf.reduce_mean(tf.reshape(support_set_embeddings, [num_classes, num_support_points, embedding_dimension]), axis=1)
接下来,我们使用相同的嵌入函数来获取查询集的嵌入:
query_set_embeddings = get_embeddings(tf.reshape(query_set, [num_classes * num_query_points, img_height, img_width, channels]), h_dim, z_dim, reuse=True)
现在我们有了类原型和查询集嵌入,我们定义了一个距离函数,该距离函数为我们提供了类原型和查询集嵌入之间的距离:
def euclidean_distance(a, b): N, D = tf.shape(a)[0], tf.shape(a)[1] M = tf.shape(b)[0] a = tf.tile(tf.expand_dims(a, axis=1), (1, M, 1)) b = tf.tile(tf.expand_dims(b, axis=0), (N, 1, 1)) return tf.reduce_mean(tf.square(a - b), axis=2)
我们计算类原型与查询集嵌入之间的距离:
distance = euclidean_distance(class_prototype,query_set_embeddings)
接下来,我们将每个类别的概率作为距离的 softmax:
predicted_probability = tf.reshape(tf.nn.log_softmax(-distance), [num_classes, num_query_points, -1])
然后,我们计算损失:
loss = -tf.reduce_mean(tf.reshape(tf.reduce_sum(tf.multiply(y_one_hot, predicted_probability), axis=-1), [-1]))
我们计算精度如下:
accuracy = tf.reduce_mean(tf.to_float(tf.equal(tf.argmax(predicted_probability, axis=-1), y)))
然后,我们使用 Adam 优化器将损失降到最低:
train = tf.train.AdamOptimizer().minimize(loss)
现在,我们开始 TensorFlow 会话并训练模型:
sess = tf.InteractiveSession() init = tf.global_variables_initializer() sess.run(init)
我们定义周期数和剧集数:
num_epochs = 20 num_episodes = 100
接下来,我们开始进行情景式训练-也就是说,对于每个剧集,我们都对数据点进行采样,构建支持和查询集,并训练模型:
for epoch in range(num_epochs): for episode in range(num_episodes): # select 60 classes episodic_classes = np.random.permutation(no_of_classes)[:num_way] support = np.zeros([num_way, num_shot, img_height, img_width], dtype=np.float32) query = np.zeros([num_way, num_query, img_height, img_width], dtype=np.float32) for index, class_ in enumerate(episodic_classes): selected = np.random.permutation(num_examples)[:num_shot + num_query] support[index] = train_dataset[class_, selected[:num_shot]] # 5 querypoints per classs query[index] = train_dataset[class_, selected[num_shot:]] support = np.expand_dims(support, axis=-1) query = np.expand_dims(query, axis=-1) labels = np.tile(np.arange(num_way)[:, np.newaxis], (1, num_query)).astype(np.uint8) _, loss_, accuracy_ = sess.run([train, loss, accuracy], feed_dict={support_set: support, query_set: query, y:labels}) if (episode+1) % 20 == 0: print('Epoch {} : Episode {} : Loss: {}, Accuracy: {}'.format(epoch+1, episode+1, loss_, accuracy_))
高斯原型网络
现在,我们将研究一种原型网络的变体,称为高斯原型网络。 我们刚刚学习了原型网络如何学习数据点的嵌入以及如何通过获取每个类的均值嵌入并使用类原型进行分类来构建类原型的。
在高斯原型网络中,连同为数据点生成嵌入,我们在它们周围添加一个以高斯协方差矩阵为特征的置信区域。 拥有置信度区域有助于表征单个数据点的质量,并且在嘈杂且不太均匀的数据中很有用。
因此,在高斯原型网络中,编码器的输出将是嵌入以及协方差矩阵。 除了使用完整的协方差矩阵之外,我们还包括来自协方差矩阵的半径或对角线分量以及嵌入:
- 半径分量:如果我们使用协方差矩阵的半径分量,则我们的协方差矩阵的维数将为 1,因为半径只是一个整数。
- 对角分量:如果我们使用协方差矩阵的对角分量,则我们的协方差矩阵的维数将与嵌入矩阵的维数相同。
此外,我们使用协方差矩阵的逆矩阵来代替直接使用协方差矩阵。 我们可以使用以下任何一种方法将原始协方差矩阵转换为逆协方差矩阵。 令S_ori
为协方差矩阵,S
为逆协方差矩阵:
S = 1 + Softplus(S_ori)
S = 1 + Sigmoid(S_ori)
S = 1 + 4 * Sigmoid(S_ori)
S = Bias + Scale * softplus(S_ori)
,其中Bias
和Scale
是可训练的参数
因此,编码器,以及为输入生成嵌入,还返回协方差矩阵。 我们使用协方差矩阵的对角线或半径分量。 同样,我们使用逆协方差矩阵代替直接使用协方差矩阵。
但是将协方差矩阵与嵌入一起使用有什么用? 如前所述,它在数据点周围添加了置信区域,在嘈杂的数据中非常有用。 看下图。 假设我们有两个类,A
和B
。 黑点表示数据点的嵌入,黑点周围的圆圈表示协方差矩阵。 大的虚线圆表示一个类的整体协方差矩阵。 中间的星星表示类的原型。 如您所见,在嵌入周围有这个协方差矩阵,这给了我们围绕数据点和类原型的置信度区域:
让我们通过查看代码更好地理解这一点。 假设我们有一个图像X
,我们想为该图像生成嵌入。 让我们用 sigma 表示协方差矩阵。 首先,我们选择要使用协方差矩阵的哪个分量,即我们要使用对角分量还是半径分量。 如果我们使用半径分量,那么我们的协方差矩阵维将仅为 1。 如果我们选择对角线分量,则协方差矩阵的大小将与嵌入维数相同:
if component =='radius': covariance_matrix_dim = 1 else: covariance_matrix_dim = embedding_dim
现在,我们定义编码器。 由于我们的输入是图像,因此我们使用卷积块作为编码器。 因此,我们定义了过滤器的大小,过滤器的数量以及池化层的大小:
filters = [3,3,3,3] num_filters = [64,64,64,embedding_dim +covariance_matrix_dim] pools = [2,2,2,2]
我们将嵌入初始化为我们的图片X
:
previous_channels = 1 embeddings = X weight = [] bias = [] conv_relu = [] conv = [] conv_pooled = []
然后,我们执行卷积运算并获得嵌入:
for i in range(len(filters)): filter_size = filters[i] num_filter = num_filters[i] pool = pools[i] weight.append(tf.get_variable("weights_"+str(i), shape=[filter_size, filter_size, previous_channels, num_filter]) bias.append(tf.get_variable("bias_"+str(i), shape=[num_filter])) conv.append(tf.nn.conv2d(embeddings, weight[i], strides=[1,1,1,1], padding='SAME') + bias[i]) conv_relu.append(tf.nn.relu(conv[i])) conv_pooled.append(tf.nn.max_pool(conv_relu[i], ksize = [1,pool,pool,1], strides=[1,pool,pool,1], padding = "VALID")) previous_channels = num_filter embeddings = conv_pooled [i]
我们将最后一个卷积层的输出作为我们的嵌入,并对结果进行整形以具有嵌入以及协方差矩阵:
X_encoded = tf.reshape(embeddings,[-1,embedding_dim + covariance_matrix_dim ])
现在,我们将嵌入和原始协方差矩阵拆分,因为我们需要将原始协方差矩阵转换为逆协方差矩阵:
embeddings, raw_covariance_matrix = tf.split(X_encoded, [embedding_dim, covariance_matrix_dim], 1)
接下来,我们使用任何讨论的方法来计算协方差矩阵的逆:
if inverse_transform_type == "softplus": offset = 1.0 scale = 1.0 inv_covariance_matrix = offset + scale * tf.nn.softplus(raw_covariance_matrix) elif inverse_transform_type == "sigmoid": offset = 1.0 scale = 1.0 inv_covariance_matrix = offset + scale * tf.sigmoid(raw_covariance_matrix) elif inverse_transform_type == "sigmoid_2": offset = 1.0 scale = 4.0 inv_covariance_matrix = offset + scale * tf.sigmoid(raw_covariance_matrix) elif inverse_transform_type == "other": init = tf.constant(1.0) scale = tf.get_variable("scale", initializer=init) div = tf.get_variable("div", initializer=init) offset = tf.get_variable("offset", initializer=init) inv_covariance_matrix = offset + scale * tf.nn.softplus(raw_covariance_matrix/div)
到目前为止,我们已经看到我们可以计算协方差矩阵以及输入的嵌入。 下一步是什么? 我们如何计算类原型? 类原型p[c]
可以如下计算:
在该方程式中,s[i]^c
是逆协方差矩阵的对角线,x[i]^c
表示嵌入,上标c
表示类别。
Python 元学习实用指南:1~5(3)https://developer.aliyun.com/article/1426921