TensorFlow 2 和 Keras 高级深度学习:11~13(3)https://developer.aliyun.com/article/1426965
4. 用于无监督聚类的编码器网络
图 13.4.1 中显示了用于无监督聚类的编码器网络实现。 它是一种编码器,具有类似 VGG 的[2]主干和Dense
层,并具有 softmax 输出。 最简单的 VGG-11 具有主干,如“图 13.4.2”所示。
对于 MNIST,使用最简单的 VGG-11 骨干将特征映射大小从MaxPooling2D
操作的 5 倍减至零。 因此,当在 Keras 中实现时,将使用按比例缩小的 VGG-11 主干版本,如图“图 13.4.3”所示。 使用同一组过滤器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JeAeSS3h-1681704403427)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_03.png)]
图 13.4.1 IIC 编码器网络E
的网络实现。 输入的 MNIST 图像被中心裁剪为24 x 24
像素。 在此示例中,X_bar = G(X)
是随机的24 x 24
像素裁剪操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rmDzEiRr-1681704403427)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_04.png)]
图 13.4.2 VGG-11 分类器主干
在“图 13.4.3”中,有 4 个Conv2D-BN-ReLU Activation-MaxPooling2D
层,其过滤器大小为(64, 128, 256, 512)
。 最后的Conv2D
层不使用MaxPooling2D
。 因此,最后的Conv2D
层针对24 x 24 x 1
裁剪的 MNIST 输入输出(3, 3, 512)
特征映射。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BzXTb8xu-1681704403428)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_05.png)]
图 13.4.3 缩小的 VGG 用作编码器主干
“图 13.4.4”显示了“图 13.4.1”的 Keras 模型图。 为了提高性能,IIC 执行了超集群。 两个或更多编码器用于生成两个或更多个边际分布P(Z)
和P(Z_bar)
。 生成相应的联合分布。 就网络模型的而言,这是由具有两个或更多头的编码器实现的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLPVHb7d-1681704403428)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_06.png)]
图 13.4.4 Keras 中 IIC 编码器E
的网络实现
“图 13.4.4”是单头编码器,而“图 13.4.5”是双头编码器。 请注意,两个头共享相同的 VGG 主干。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8zWn8Ch-1681704403428)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_07.png)]
图 13.4.5 Keras 中的两头编码器网络E
在以下两个部分的中,我们将研究[II]网络模型是如何实现,训练和评估的。 我们还将研究线性分配问题,作为为每个聚类指定标签的工具。
5. Keras 中的无监督聚类实现
Keras 中用于无监督聚类的网络模型实现在“列表 13.5.1”中显示。 仅显示初始化。 网络超参数存储在args
中。 VGG 主干对象在初始化期间提供。 给定骨干,模型实际上只是具有 softmax 激活的Dense
层,如build_model()
方法所示。 有创建多个头的选项。
与“第 11 章”,“对象检测”相似,我们实现了DataGenerator
类以多线程方式有效地提供输入数据。 DataGenerator
对象生成由输入图像X
及其变换后的图像X_bar
组成的所需配对训练输入数据(即,连体输入图像)。 DataGenerator
类中最关键的方法__data_generation()
显示在“列表 13.5.2”中。 输入图像X
从原始输入图像中央裁切。 对于 MNIST,这是24 x 24
像素中心裁剪。 变换后的输入图像X_bar
可以随机旋转±20
范围内的某个角度,也可以从图像的任何部分随机裁剪16 x 16
、18 x 18
或20 x 20
像素,然后将其调整为24 x 24
像素。 作物尺寸存储在crop_sizes
列表中。
注意,仅输入图像和变换图像在DataGenerator
对象生成的数据中很重要。 同样,损失函数所需的配对数据沿批量轴连接。 这将使我们能够在单批配对数据中计算损失函数。
“列表 13.5.1”:iic-13.5.1.py
。 显示初始化和模型创建的 IIC 类:IIC 类:
def __init__(self, args, backbone): """Contains the encoder model, the loss function, loading of datasets, train and evaluation routines to implement IIC unsupervised clustering via mutual information maximization
Arguments: args : Command line arguments to indicate choice of batch size, number of heads, folder to save weights file, weights file name, etc backbone (Model): IIC Encoder backbone (eg VGG) """ self.args = args self.backbone = backbone self._model = None self.train_gen = DataGenerator(args, siamese=True) self.n_labels = self.train_gen.n_labels self.build_model() self.load_eval_dataset() self.accuracy = 0
def build_model(self): """Build the n_heads of the IIC model """ inputs = Input(shape=self.train_gen.input_shape, name='x') x = self.backbone(inputs) x = Flatten()(x) # number of output heads outputs = [] for i in range(self.args.heads): name = "z_head%d" % i outputs.append(Dense(self.n_labels, activation='softmax', name=name)(x)) self._model = Model(inputs, outputs, name='encoder') optimizer = Adam(lr=1e-3) self._model.compile(optimizer=optimizer, loss=self.mi_loss)
“列表 13.5.2”:data_generator.py
。 用于生成成对的输入数据以训练 IIC 编码器的DataGenerator
类方法:
def __data_generation(self, start_index, end_index): """Data generation algorithm. The method generates a batch of pair of images (original image X and transformed imaged Xbar). The batch of Siamese images is used to trained MI-based algorithms: 1) IIC and 2) MINE (Section 7)
Arguments: start_index (int): Given an array of images, this is the start index to retrieve a batch end_index (int): Given an array of images, this is the end index to retrieve a batch """
d = self.crop_size // 2 crop_sizes = [self.crop_size*2 + i for i in range(0,5,2)] image_size = self.data.shape[1] - self.crop_size x = self.data[self.indexes[start_index : end_index]] y1 = self.label[self.indexes[start_index : end_index]]
target_shape = (x.shape[0], *self.input_shape) x1 = np.zeros(target_shape) if self.siamese: y2 = y1 x2 = np.zeros(target_shape)
for i in range(x1.shape[0]): image = x[i] x1[i] = image[d: image_size + d, d: image_size + d] if self.siamese: rotate = np.random.randint(0, 2) # 50-50% chance of crop or rotate if rotate == 1: shape = target_shape[1:] x2[i] = self.random_rotate(image, target_shape=shape) else: x2[i] = self.random_crop(image, target_shape[1:], crop_sizes)
# for IIC, we are mostly interested in paired images # X and Xbar = G(X) if self.siamese: # If MINE Algorithm is chosen, use this to generate # the training data (see Section 9) if self.mine: y = np.concatenate([y1, y2], axis=0) m1 = np.copy(x1) m2 = np.copy(x2) np.random.shuffle(m2) x1 = np.concatenate((x1, m1), axis=0) x2 = np.concatenate((x2, m2), axis=0) x = (x1, x2) return x, y
x_train = np.concatenate([x1, x2], axis=0) y_train = np.concatenate([y1, y2], axis=0) y = [] for i in range(self.args.heads): y.append(y_train) return x_train, y
return x1, y1
为了实现 VGG 骨干,在 Keras 中实现了VGG
类,如“列表 13.5.3”所示。 VGG
类的灵活性在于可以用不同的方式(或 VGG 的不同样式)进行配置。 显示了用于 IIC VGG 主干配置cfg
的选项’F’。 我们使用一个辅助函数来生成Conv2D-BN-ReLU-MaxPooling2D
层。
“列表 13.5.3”:vgg.py
。
Keras 中的VGG backbone
类方法:
cfg = { 'F': [64, 'M', 128, 'M', 256, 'M', 512], }
class VGG: def __init__(self, cfg, input_shape=(24, 24, 1)): """VGG network model creator to be used as backbone feature extractor
Arguments: cfg (dict): Summarizes the network configuration input_shape (list): Input image dims """ self.cfg = cfg self.input_shape = input_shape self._model = None self.build_model()
def build_model(self): """Model builder uses a helper function make_layers to read the config dict and create a VGG network model """ inputs = Input(shape=self.input_shape, name='x') x = VGG.make_layers(self.cfg, inputs) self._model = Model(inputs, x, name='VGG')
@property def model(self): return self._model
@staticmethod def make_layers(cfg, inputs, batch_norm=True, in_channels=1): """Helper function to ease the creation of VGG network model
Arguments: cfg (dict): Summarizes the network layer configuration inputs (tensor): Input from previous layer batch_norm (Bool): Whether to use batch norm between Conv2D and ReLU in_channel (int): Number of input channels """ x = inputs for layer in cfg: if layer == 'M': x = MaxPooling2D()(x) elif layer == 'A': x = AveragePooling2D(pool_size=3)(x) else: x = Conv2D(layer, kernel_size=3, padding='same', kernel_initializer='he_normal' )(x) if batch_norm: x = BatchNormalization()(x) x = Activation('relu')(x)
return x
回到IIC
类,IIC
的关键算法是使负 MI 最小的损失函数。 此方法显示在“列表 13.5.4”中。 为了评估单个批量中的损失,我们研究了y_pred
,并将其分为上下两半,分别对应于输入图像X
及其变换后的图像X_bar
的编码器输出的。 回想一下,配对数据是通过将一批图像X
和一批其变换后的图像X_bar
连接在一起而制成的。
y_pred
的下半部分为Z
,而上半部分为Z_bar
遵循“公式 10.3.2”至“公式 10.3.7”,联合分布P(Z, Z_bar)
和边际分布被计算。 最后,返回负数 MI。 注意,每个头对总损失函数的贡献均等。 因此,损失是根据头部的数量来缩放的。
“列表 13.5.4”:iic-13.5.1.py
。
Keras 中的IIC
类损失函数。 损失函数使负 MI 最小化(即,使 MI 最大化):
def mi_loss(self, y_true, y_pred): """Mutual information loss computed from the joint distribution matrix and the marginals
Arguments: y_true (tensor): Not used since this is unsupervised learning y_pred (tensor): stack of softmax predictions for the Siamese latent vectors (Z and Zbar) """ size = self.args.batch_size n_labels = y_pred.shape[-1] # lower half is Z Z = y_pred[0: size, :] Z = K.expand_dims(Z, axis=2) # upper half is Zbar Zbar = y_pred[size: y_pred.shape[0], :] Zbar = K.expand_dims(Zbar, axis=1) # compute joint distribution (Eq 10.3.2 & .3) P = K.batch_dot(Z, Zbar) P = K.sum(P, axis=0) # enforce symmetric joint distribution (Eq 10.3.4) P = (P + K.transpose(P)) / 2.0 # normalization of total probability to 1.0 P = P / K.sum(P) # marginal distributions (Eq 10.3.5 & .6) Pi = K.expand_dims(K.sum(P, axis=1), axis=1) Pj = K.expand_dims(K.sum(P, axis=0), axis=0) Pi = K.repeat_elements(Pi, rep=n_labels, axis=1) Pj = K.repeat_elements(Pj, rep=n_labels, axis=0) P = K.clip(P, K.epsilon(), np.finfo(float).max) Pi = K.clip(Pi, K.epsilon(), np.finfo(float).max) Pj = K.clip(Pj, K.epsilon(), np.finfo(float).max) # negative MI loss (Eq 10.3.7) neg_mi = K.sum((P * (K.log(Pi) + K.log(Pj) - K.log(P)))) # each head contribute 1/n_heads to the total loss return neg_mi/self.args.heads
IIC 网络训练方法显示在“列表 13.5.5”中。 由于我们使用的是从Sequence
类派生的DataGenerator
对象,因此可以使用 Keras fit_generator()
方法来训练模型。
我们使用学习率调度器,每 400 个周期将学习率降低 80%。 AccuracyCallback
调用eval()
方法,因此我们可以在每个周期之后记录网络的表现。
可以选择保存表现最佳的模型的权重。 在eval()
方法中,我们使用线性分类器为每个聚类分配标签。 线性分类器unsupervised_labels()
是一种匈牙利算法,它以最小的成本将标签分配给群集。
最后一步将无监督的聚类转换为无监督的分类。 unsupervised_labels()
函数在“列表 13.5.6”中显示。
“列表 13.5.5”:iic-13.5.1.py
。
IIC 网络训练和评估:
def train(self): """Train function uses the data generator, accuracy computation, and learning rate scheduler callbacks """ accuracy = AccuracyCallback(self) lr_scheduler = LearningRateScheduler(lr_schedule, verbose=1) callbacks = [accuracy, lr_scheduler] self._model.fit_generator(generator=self.train_gen, use_multiprocessing=True, epochs=self.args.epochs, callbacks=callbacks, workers=4, shuffle=True)
def eval(self): """Evaluate the accuracy of the current model weights """ y_pred = self._model.predict(self.x_test) print("") # accuracy per head for head in range(self.args.heads): if self.args.heads == 1: y_head = y_pred else: y_head = y_pred[head] y_head = np.argmax(y_head, axis=1) accuracy = unsupervised_labels(list(self.y_test), list(y_head), self.n_labels, self.n_labels) info = "Head %d accuracy: %0.2f%%" if self.accuracy > 0: info += ", Old best accuracy: %0.2f%%" data = (head, accuracy, self.accuracy) else: data = (head, accuracy) print(info % data) # if accuracy improves during training, # save the model weights on a file if accuracy > self.accuracy \ and self.args.save_weights is not None: self.accuracy = accuracy folder = self.args.save_dir os.makedirs(folder, exist_ok=True) path = os.path.join(folder, self.args.save_weights) print("Saving weights... ", path) self._model.save_weights(path)
“列表 13.5.6”:utils.py
。
匈牙利语算法将标签分配给具有最低成本的集群:
from scipy.optimize import linear_sum_assignment def unsupervised_labels(y, yp, n_classes, n_clusters): """Linear assignment algorithm Arguments: y (tensor): Ground truth labels yp (tensor): Predicted clusters n_classes (int): Number of classes n_clusters (int): Number of clusters """ assert n_classes == n_clusters # initialize count matrix C = np.zeros([n_clusters, n_classes]) # populate count matrix for i in range(len(y)): C[int(yp[i]), int(y[i])] += 1 # optimal permutation using Hungarian Algo # the higher the count, the lower the cost # so we use -C for linear assignment row, col = linear_sum_assignment(-C) # compute accuracy accuracy = C[row, col].sum() / C.sum() return accuracy * 100
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-md0ViQ0q-1681704403428)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_08.png)]
图 13.5.1 在三个群集的简单场景中说明的线性分配算法,可以将其最佳地分配给三个类别
如图“图 13.5.1”所示,线性分配问题最好使用将三个群集分配给三个类别的简化方案来解释。 线性分配问题找到了类对类的一对一分配,从而使总成本最小。 在“图 13.5.1*”的左侧,显示了聚类结果和真实情况标签。
线性分配问题可以找到每个群集的类或类别,或者如何为每个群集分配标签。 还显示了成本矩阵C
。 对于每个聚类-真实情况对,成本矩阵像元递减 1。该像元的行-列索引是聚类编号-真实情况标签索引。 使用成本矩阵,线性分配问题的工作是找到导致总成本最小的最优矩阵X
:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TzSA77It-1681704403429)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_080.png)] (Equation 13.5.1)
其中c[ij]
和x[ij]
分别是矩阵C
和X
的元素 。i
和j
是索引。X
的元素受的以下约束:
x[ij] ∈ {0, 1}
Σ[j] x[ij] = 1
对于i = 1, 2, ..., N
Σ[i] x[ij] = 1
对于j = 1, 2, ..., N
X
是一个二进制矩阵。 每行仅分配给一列。 因此,线性分配问题是组合问题。 最佳解决方案的详细信息超出了本书的范围,此处不再讨论。
最佳权重矩阵X
显示在“图 13.5.1”中。 群集 0 被分配了标签 1。群集 1 被分配了标签 2。群集 2 被分配了标签 0。这可以从成本矩阵中直观地进行验证,因为这导致最低成本为 -4,同时确保每行仅分配给一列。
使用此矩阵,群集类的分配显示在最右边的表中。 使用群集类分配时,第四行上只有一个错误。 结果精度为五分之四,即 80%。
我们可以将的线性分配问题扩展到为 10 个 MNIST 集群分配标签的问题。 我们在scipy
包中使用linear_sum_assignment()
函数。 该函数基于匈牙利算法。“列表 13.5.6”显示了群集标记过程的实现。 有关linear_sum_assignment()
函数的更多详细信息,请参见这里。
要训练 1 头情况下的 IIC 模型,请执行:
python3 iic-13.5.1.py --heads=1 --train --save-weights=head1.h5
对于其他数量的打印头,应相应地修改选项--heads
和--save-weights
。 在下一部分中,我们将检查 IIC 作为 MNIST 分类器的表现。
6. 将 MNIST 用于验证
在本节中,我们将研究使用 MNIST 测试数据集对 IIC 进行验证之后的结果。 在测试数据集上运行聚类预测后,线性分配问题为每个聚类分配标签,从本质上将聚类转换为分类。 我们计算了分类精度,如“表 13.6.1”所示。 IIC 的准确率高于论文中报告的 99.3%。 但是,应该注意的是,并非每次训练都会导致高精度分类。
有时,由于优化似乎停留在局部最小值中,我们不得不多次运行训练。 此外,在多头 IIC 模型中,对于所有头部,我们都无法获得相同水平的表现。“表 13.6.1”报告了最佳表现的头部。
头部数 | 1 |
2 |
3 |
4 |
5 |
精度,% | 99.49 | 99.47 | 99.54 | 99.52 | 99.53 |
表 13.6.1 不同头数的 IIC 精度
权重在 GitHub 上可用。 例如,要在单头 IIC 上运行验证:
python3 iic-13.5.1.py --heads=1 --eval --restore-weights=head1-best.h5
总之,我们可以看到可以执行无监督分类。 结果实际上比我们在“第 2 章”,“深度神经网络”中检查的监督分类更好。 在以下各节中,我们将把注意力转向对连续随机变量的无监督学习。
7. 通过最大化连续随机变量的互信息进行无监督学习
在前面的章节中,我们了解到可以很好地估计离散随机变量的 MI。 我们还证明了借助线性分配算法,通过最大化 MI 来执行聚类的网络可以得出准确的分类器。
如果 IIC 是离散随机变量 MI 的良好估计者,那么连续随机变量又如何呢? 在本节的中,我们讨论 Belghazi 等人的互信息网络估计器(MINE)。 [3]作为连续随机变量 MI 的估计量。
MINE 在“公式 13.1.1”中提出了 KL 散度的另一种表示形式,以使用神经网络实现 MI 估计器。 在 MINE 中,使用 KL 散度的 Donsker-Varadhan(DV)表示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cgWGoj6-1681704403429)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_085.png)] (Equation 13.7.1)
在函数T
的整个空间中占据最高位的位置。T
是从输入空间(例如图像)映射到实数的任意函数。 回想一下,最高被粗略地解释为最大值。 对于T
,我们可以从θ ∈ Θ
参数化的函数T[θ] = X x Y -> R
系列中进行选择。 因此,我们可以用估计 KL 散度的深度神经网络表示T[θ]
,因此代表T
。
给定作为 MI 的精确(但难处理)表示I(X; Y)
及其参数化的估计值I[θ](X; Y)
作为易于处理的下限,我们可以安全地说:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DPT5Zlq1-1681704403429)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_091.png)] (Equation 13.7.2)
其中参数化的 MI 估计为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CVHlrzDf-1681704403429)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_092.png)] (Equation 13.7.3)
I[θ](X; Y)
也称为神经信息测度。 在第一个期望中,样本(x, y) ~ P(X, Y)
从联合分布P
(X
,Y
)中获取。 在第二个期望中,样本x ~ P(X), y ~ P(Y)
来自边际分布P(X)
和P(Y)
。
“算法 13.7.1”:MINE
。
初始化所有网络参数θ
。
θ
尚未收敛时,请执行:
- 从联合分布
{(x^(1), y^(1)), (x^(2), y^(2)), ..., (x^(b), y^(b))} ~ P(X, Y)
中抽取一个小批量的b
- 从边际分布
{x^(1), x^(2), ..., x^(b)} ~ P(X)
和{y^(1), y^(2), ..., y^(b)} ~ P(Y)
中抽取一个小批量的b
。 - 评估下界:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pCRcT8DX-1681704403429)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_101.png)] - 评估偏差校正后的梯度:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fdZw7qMr-1681704403430)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_103.png)] - 更新网络参数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17YY652z-1681704403430)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_102.png)]
其中ε
是学习率。
“算法 13.7.1”总结了 MINE 算法。 来自边际分布的样本是来自联合分布的样本,另一个变量已删除。 例如,样本x
只是简单的样本(x, y)
,变量y
被丢弃。 在降为变量y
的值之后,将x
的样本进行混洗。 对y
执行相同的采样方法。 为了清楚起见,我们使用符号x_bar
和y_bar
从边际分布中识别样本。
在下一部分中,在双变量高斯分布的情况下,我们将使用 MINE 算法估计 MI。 我们将展示使用解析方法估计的 MI 和使用 MINE 估计 MI 的方法。
8. 估计二元高斯的互信息
在本节中,我们将验证 MINE 的二元高斯分布。“图 13.8.1”显示具有均值和协方差的双变量高斯分布:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QIeQTLBi-1681704403430)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_107.png)] (Equation 13.8.1)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-orWcSm46-1681704403430)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_108.png)] (Equation 13.8.2)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IkqchkUb-1681704403430)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_09.png)]
图 13.8.1 具有均值和协方差的二维高斯分布,如公式 13.8.1 和公式 13.8.2 所示
我们的目标是通过近似“公式 13.1.3”来估计 MI。 可以通过获得大量样本(例如 1 百万个)并创建具有大量箱子(例如 100 个箱子)的直方图来进行近似。“列表 13.8.1”显示了使用装仓对二元高斯分布的 MI 进行的手动计算。
“列表 13.8.1”:mine-13.8.1.py
:
def sample(joint=True, mean=[0, 0], cov=[[1, 0.5], [0.5, 1]], n_data=1000000): """Helper function to obtain samples fr a bivariate Gaussian distribution
Arguments: joint (Bool): If joint distribution is desired mean (list): The mean values of the 2D Gaussian cov (list): The covariance matrix of the 2D Gaussian n_data (int): Number of samples fr 2D Gaussian """ xy = np.random.multivariate_normal(mean=mean, cov=cov, size=n_data)
# samples fr joint distribution if joint: return xy y = np.random.multivariate_normal(mean=mean, cov=cov, size=n_data)
# samples fr marginal distribution x = xy[:,0].reshape(-1,1) y = y[:,1].reshape(-1,1) xy = np.concatenate([x, y], axis=1) return xy
def compute_mi(cov_xy=0.5, n_bins=100): """Analytic computation of MI using binned 2D Gaussian
Arguments: cov_xy (list): Off-diagonal elements of covariance matrix n_bins (int): Number of bins to "quantize" the continuous 2D Gaussian """ cov=[[1, cov_xy], [cov_xy, 1]] data = sample(cov=cov) # get joint distribution samples # perform histogram binning joint, edge = np.histogramdd(data, bins=n_bins) joint /= joint.sum() eps = np.finfo(float).eps joint[joint<eps] = eps # compute marginal distributions x, y = margins(joint)
xy = x*y xy[xy<eps] = eps # MI is P(X,Y)*log(P(X,Y)/P(X)*P(Y)) mi = joint*np.log(joint/xy) mi = mi.sum() return mi
运行的结果:
python3 mine-13.8.1.py --gaussian
表示手动计算的 MI:
Computed MI: 0.145158
可以使用--cov_xy
选项更改协方差。 例如:
python3 mine-13.8.1.py --gaussian --cov_xy=0.8
表示手动计算的 MI:
Computed MI: 0.510342
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sxzwuFlk-1681704403431)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_10.png)]
图 13.8.2 一个简单的 MINE 模型,用于估计双变量高斯分布的随机变量X
和Y
的 MI
“列表 13.8.2”:mine-13.8.1.py
。
一个简单的 MINE 模型,用于估计双变量高斯分布的随机变量的 MI:
class SimpleMINE: def __init__(self, args, input_dim=1, hidden_units=16, output_dim=1): """Learn to compute MI using MINE (Algorithm 13.7.1)
Arguments: args : User-defined arguments such as off-diagonal elements of covariance matrix, batch size, epochs, etc input_dim (int): Input size dimension hidden_units (int): Number of hidden units of the MINE MLP network output_dim (int): Output size dimension """ self.args = args self._model = None self.build_model(input_dim, hidden_units, output_dim)
def build_model(self, input_dim, hidden_units, output_dim): """Build a simple MINE model
Arguments: See class arguments. """ inputs1 = Input(shape=(input_dim), name="x") inputs2 = Input(shape=(input_dim), name="y") x1 = Dense(hidden_units)(inputs1) x2 = Dense(hidden_units)(inputs2) x = Add()([x1, x2]) x = Activation('relu', name="ReLU")(x) outputs = Dense(output_dim, name="MI")(x) inputs = [inputs1, inputs2] self._model = Model(inputs, outputs, name='MINE') self._model.summary()
def mi_loss(self, y_true, y_pred): """ MINE loss function
Arguments: y_true (tensor): Not used since this is unsupervised learning y_pred (tensor): stack of predictions for joint T(x,y) and marginal T(x,y) """ size = self.args.batch_size # lower half is pred for joint dist pred_xy = y_pred[0: size, :]
# upper half is pred for marginal dist pred_x_y = y_pred[size : y_pred.shape[0], :] # implentation of MINE loss (Eq 13.7.3) loss = K.mean(pred_xy) \ - K.log(K.mean(K.exp(pred_x_y))) return -loss
def train(self): """Train MINE to estimate MI between X and Y of a 2D Gaussian """ optimizer = Adam(lr=0.01) self._model.compile(optimizer=optimizer, loss=self.mi_loss) plot_loss = [] cov=[[1, self.args.cov_xy], [self.args.cov_xy, 1]] loss = 0. for epoch in range(self.args.epochs): # joint dist samples xy = sample(n_data=self.args.batch_size, cov=cov) x1 = xy[:,0].reshape(-1,1) y1 = xy[:,1].reshape(-1,1) # marginal dist samples xy = sample(joint=False, n_data=self.args.batch_size, cov=cov) x2 = xy[:,0].reshape(-1,1) y2 = xy[:,1].reshape(-1,1)
# train on batch of joint & marginal samples x = np.concatenate((x1, x2)) y = np.concatenate((y1, y2)) loss_item = self._model.train_on_batch([x, y], np.zeros(x.shape)) loss += loss_item plot_loss.append(-loss_item) if (epoch + 1) % 100 == 0: fmt = "Epoch %d MINE MI: %0.6f" print(fmt % ((epoch+1), -loss/100)) loss = 0.
现在,让我们使用 MINE 估计此双变量高斯分布的 MI。“图 13.8.2”显示了一个简单的 2 层 MLP 作为T[θ]
的模型。 输入层从联合分布中接收一批(x
,y
),从边缘分布中接收一批(x_bar, y_bar)
。 该网络在build_model()
中的“列表 13.8.2”中实现。 在同一清单中还显示了此简单 MINE 模型的训练例程。
实现“公式 13.7.3”的损失函数也在“列表 13.8.2”中显示。 请注意,损失函数不使用基本真值。 它只是最小化了 MI 的负估计(从而使 MI 最大化)。 对于此简单的 MINE 模型,未实现移动平均损失。 我们使用“列表 13.8.1”中的相同函数sample()
来获得联合和边际样本。
现在,我们可以使用同一命令来估计双变量高斯分布的 MI:
python3 mine-13.8.1.py --gaussian
“图 13.8.3”显示了 MI 估计(负损失)与历时数的关系。 以下是每隔 100 个特定周期的定量结果。手动和 MINE 计算的结果接近。 这证明了 MINE 是连续随机变量 MI 的良好估计。
Epoch 100 MINE MI: 0.112297 Epoch 200 MINE MI: 0.141723 Epoch 300 MINE MI: 0.142567 Epoch 400 MINE MI: 0.142087 Epoch 500 MINE MI: 0.142083 Epoch 600 MINE MI: 0.144755 Epoch 700 MINE MI: 0.141434 Epoch 800 MINE MI: 0.142480 Epoch 900 MINE MI: 0.143059 Epoch 1000 MINE MI: 0.142186 Computed MI: 0.147247
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RH1A2DZH-1681704403431)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_11.png)]
图 13.8.3 MI 估计作为简单 MINE 模型的函数周期。
到目前为止,我们已经针对二元高斯分布情况证明了 MINE。 在下一节中,我们将使用 MINE 来处理与 IIC 相同的 MNIST 无监督聚类问题。
9. Keras 中的使用连续随机变量的无监督聚类
在 MNIST 数字的无监督分类中,我们使用 IIC,因为可以使用离散的联合和边际分布来计算 MI 。 我们使用线性分配算法获得了良好的准确率。
在此部分中,我们将尝试使用 MINE 进行聚类。 我们将使用来自 IIC 的相同关键思想:从一对图像及其转换后的版本(X, X_bar)
中,最大化对应的编码潜向量(Z, Z_bar)
的 MI。 通过最大化 MI,我们对编码的潜在向量进行聚类。 与 MINE 的不同之处在于,编码后的潜在向量是连续的,而不是 IIC 中使用的单热向量格式。 由于聚类的输出不是单热向量格式,因此我们将使用线性分类器。 线性分类器是没有诸如ReLU
之类的非线性激活层的 MLP。 如果输出不是单热点向量格式,则使用线性分类器替代线性分配算法。
“图 13.9.1”显示了 MINE 的网络模型。 对于 MNIST,从 MNIST 训练数据集中采样了x
。 与 IIC 相似,称为变量y
的其他输入只是图像x
的变换后的版本。 在测试过程中,输入图像x
来自 MNIST 测试数据集。 从本质上讲,数据生成与 IIC 中的相同,如“列表 13.5.2”中所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OyhSg35V-1681704403431)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_12.png)]
图 13.9.1 使用编码器网络E
的 MINE 的网络实现。 输入的 MNIST 图像被中心裁剪为24 x 24
像素。 在此示例中,X_bar = Y = G(X)
是随机的24 x 24
像素裁剪操作。
当在 Keras 中实现时,“图 13.9.1”的编码器网络显示在“图 13.9.2”中。 我们在 Dense 输出中省略了维数,以便我们可以尝试不同的维数(例如 10、16 和 32)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CQZ6ebk5-1681704403431)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_13.png)]
图 13.9.2 编码器网络E
是 VGG 网络,类似于 IIC 中使用的网络
MINE 网络模型显示在“图 13.9.3”中,代码显示在“列表 13.9.1”中。 它在架构中与上一节中实现的简单 MINE 类似,不同之处在于,我们在 MLP 中使用了 1,024 个隐藏单元,而不是 16 个。
“列表 13.9.1”:mine-13.8.1.py
。
MINE 网络模型用于无监督群集:
class MINE: def __init__(self, args, backbone): """Contains the encoder, SimpleMINE, and linear classifier models, the loss function, loading of datasets, train and evaluation routines to implement MINE unsupervised clustering via mutual information maximization
Arguments: args : Command line arguments to indicate choice of batch size, folder to save weights file, weights file name, etc backbone (Model): MINE Encoder backbone (eg VGG) """ self.args = args self.latent_dim = args.latent_dim self.backbone = backbone self._model = None self._encoder = None self.train_gen = DataGenerator(args, siamese=True, mine=True) self.n_labels = self.train_gen.n_labels self.build_model() self.accuracy = 0
def build_model(self): """Build the MINE model unsupervised classifier """ inputs = Input(shape=self.train_gen.input_shape, name="x") x = self.backbone(inputs) x = Flatten()(x) y = Dense(self.latent_dim, activation='linear', name="encoded_x")(x) # encoder is based on backbone (eg VGG) # feature extractor self._encoder = Model(inputs, y, name="encoder") # the SimpleMINE in bivariate Gaussian is used # as T(x,y) function in MINE (Algorithm 13.7.1) self._mine = SimpleMINE(self.args, input_dim=self.latent_dim, hidden_units=1024, output_dim=1) inputs1 = Input(shape=self.train_gen.input_shape, name="x") inputs2 = Input(shape=self.train_gen.input_shape, name="y") x1 = self._encoder(inputs1) x2 = self._encoder(inputs2) outputs = self._mine.model([x1, x2]) # the model computes the MI between # inputs1 and 2 (x and y) self._model = Model([inputs1, inputs2], outputs, name='encoder') optimizer = Adam(lr=1e-3) self._model.compile(optimizer=optimizer, loss=self.mi_loss) self._model.summary() self.load_eval_dataset() self._classifier = LinearClassifier(\ latent_dim=self.latent_dim)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6YwYvsf3-1681704403432)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_14.png)]
图 13.9.3 MINE 网络模型
如“列表 13.9.2”中所示,训练例程类似于 IIC 中的训练例程。 区别在于在每个周期之后执行的评估。 在这种情况下,我们针对个周期训练线性分类器,并将其用于评估聚类的潜在代码向量。 当精度提高时,可以选择保存模型权重。 损失函数和优化器与SimpleMINE
中的类似,如“列表 13.8.2”中所示,此处不再赘述。
“列表 13.9.2”:mine-13.8.1.py
。
矿山训练和评估职能:
def train(self): """Train MINE to estimate MI between X and Y (eg MNIST image and its transformed version) """ accuracy = AccuracyCallback(self) lr_scheduler = LearningRateScheduler(lr_schedule, verbose=1) callbacks = [accuracy, lr_scheduler] self._model.fit_generator(generator=self.train_gen, use_multiprocessing=True, epochs=self.args.epochs, callbacks=callbacks, workers=4, shuffle=True)
def eval(self): """Evaluate the accuracy of the current model weights """ # generate clustering predictions fr test data y_pred = self._encoder.predict(self.x_test) # train a linear classifier # input: clustered data # output: ground truth labels self._classifier.train(y_pred, self.y_test) accuracy = self._classifier.eval(y_pred, self.y_test)
info = "Accuracy: %0.2f%%" if self.accuracy > 0: info += ", Old best accuracy: %0.2f%%" data = (accuracy, self.accuracy) else: data = (accuracy) print(info % data) # if accuracy improves during training, # save the model weights on a file if accuracy > self.accuracy \ and self.args.save_weights is not None: folder = self.args.save_dir os.makedirs(folder, exist_ok=True) args = (self.latent_dim, self.args.save_weights) filename = "%d-dim-%s" % args path = os.path.join(folder, filename) print("Saving weights... ", path) self._model.save_weights(path)
if accuracy > self.accuracy: self.accuracy = accuracy
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-adafe964-1681704403432)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_15.png)]
图 13.9.4 线性分类器模型
线性分类器模型显示在“图 19.3.4”中。 它是一个具有 256 个单元的隐藏层的 MLP。 由于此模型不使用诸如ReLU
之类的非线性激活,因此可以将其用作线性分配算法的近似值,以对 VGG-Dense 编码器E
的输出进行分类。“列表 13.9.3”显示了在 Keras 中实现的线性分类器网络模型构建器。
“列表 13.9.3”:mine-13.8.1.py
。
线性分类器网络:
class LinearClassifier: def __init__(self, latent_dim=10, n_classes=10): """A simple MLP-based linear classifier. A linear classifier is an MLP network without non-linear activations like ReLU. This can be used as a substitute to linear assignment algorithm.
Arguments: latent_dim (int): Latent vector dimensionality n_classes (int): Number of classes the latent dim will be converted to. """ self.build_model(latent_dim, n_classes)
def build_model(self, latent_dim, n_classes): """Linear classifier model builder.
Arguments: (see class arguments) """ inputs = Input(shape=(latent_dim,), name="cluster") x = Dense(256)(inputs) outputs = Dense(n_classes, activation='softmax', name="class")(x) name = "classifier" self._model = Model(inputs, outputs, name=name) self._model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) self._model.summary()
可以通过执行以下命令来训练 MINE 非监督分类器:
python3 mine-13.8.1.py --train --batch-size=1024 --epochs=200
可以根据可用的 GPU 内存来调整批量的大小。 要使用其他潜在尺寸大小(例如 64),请使用--latent-dim
选项:
python3 mine-13.8.1.py --train --batch-size=1024 --latent-dim=64 --epochs=200
在 200 个周期内,MINE 网络具有“图 13.9.5”中所示的精度:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4oD6sSBb-1681704403432)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_13_16.png)]
图 13.9.5 MNIST 分类中的 MINE 准确率
如图“图 13.9.5”所示,在默认潜伏昏暗 10 时,类似于 IIC,使用线性分类器的 MINE 可以达到 93.86% 的精度。 精度随潜在尺寸的值而增加。 由于 MINE 是真实 MI 的近似值,因此预计其精度会低于 IIC。
到此结束本章和书。 无监督学习的领域是新生的。 鉴于人工智能发展的当前障碍之一是人工标签,这是一个巨大的研究机会,这既昂贵又费时。 我们预计在未来几年中,无监督学习将取得突破。
10. 总结
在本章中,我们讨论了 MI 及其在解决无监督任务中有用的方式。 各种在线资源提供了有关 MI 的其他背景信息[4]。 当用于聚类时,最大化 MI 会强制使用线性分配或线性分类器将潜在代码向量聚类在适合轻松标记的区域中。
我们介绍了 MI 的两种度量:IIC 和 MINE。 我们可以通过对离散随机变量使用 IIC 来近似逼近 MI,从而导致分类器以较高的精度执行。 IIC 适用于离散概率分布。 对于连续随机变量,MINE 使用 KL 散度的 Donsker-Varadhan 形式对估计 MI 的深度神经网络进行建模。 我们证明了 MINE 可以近似逼近双变量高斯分布的 MI。 作为一种无监督的方法,MINE 在对 MNIST 数字进行分类时显示出可接受的表现。
11. 参考
Ji, Xu, João F. Henriques, and Andrea Vedaldi. Invariant Information Clustering for Unsupervised Image Classification and Segmentation. International Conference on Computer Vision, 2019.
Simonyan, Karen, and Andrew Zisserman. Very deep convolutional networks for large-scale image recognition. arXiv preprint arXiv:1409.1556 (2014).
Belghazi, Mohamed Ishmael, et al. Mutual Information Neural Estimation. International Conference on Machine Learning. 2018.
https://en.wikipedia.org/wiki/Mutual_information.