这是一个关于孪生网络相当有趣的问题
我正在参考 https://keras.io/examples/mnist_siamese/ 上的示例。我修改后的代码在这个 Google Colab 中
孪生网络接受两个输入(两个手写数字)并输出它们是否为相同数字(1)或不同(0)。
两个输入中的每一个首先由共享的基本网络处理(包含3个密集层和2个中间的Dropout层)。输入input_a被提取为processed_a,input_b被提取为processed_b。
孪生网络的最后一层是两个提取的张量之间的欧几里得距离层:
distance = Lambda(euclidean_distance, output_shape=eucl_dist_output_shape)([processed_a, processed_b])model = Model([input_a, input_b], distance)
我理解在网络的下半部分使用欧几里得距离层的理由:如果特征提取得当,那么相似的输入应该具有相似的特征。
我在想,为什么不在下半部分使用普通的密集层,像这样:
# distance = Lambda(euclidean_distance,# output_shape=eucl_dist_output_shape)([processed_a, processed_b])# model = Model([input_a, input_b], distance)#我的模型subtracted = Subtract()([processed_a, processed_b])out = Dense(1, activation="sigmoid")(subtracted)model = Model([input_a,input_b], out)
我的推理是,如果提取的特征相似,那么Subtract层应该产生一个小的张量,作为提取特征之间的差异。接下来的密集层可以学习到,如果输入较小,则输出1,否则输出0。
因为欧几里得距离层在两个输入相似时输出接近0的值,否则输出1,我还需要反转准确率和损失函数,如下所示:
# 欧几里得距离层的损失和准确率版本# def contrastive_loss(y_true, y_pred):# '''Contrastive loss from Hadsell-et-al.'06# http://yann.lecun.com/exdb/publis/pdf/hadsell-chopra-lecun-06.pdf# '''# margin = 1# square_pred = K.square(y_pred)# margin_square = K.square(K.maximum(margin - y_pred, 0))# return K.mean(y_true * square_pred + (1 - y_true) * margin_square)# def compute_accuracy(y_true, y_pred):# '''Compute classification accuracy with a fixed threshold on distances.# '''# pred = y_pred.ravel() < 0.5# return np.mean(pred == y_true)# def accuracy(y_true, y_pred):# '''Compute classification accuracy with a fixed threshold on distances.# '''# return K.mean(K.equal(y_true, K.cast(y_pred < 0.5, y_true.dtype)))### 我的版本,损失和准确率def contrastive_loss(y_true, y_pred): margin = 1 square_pred = K.square(y_pred) margin_square = K.square(K.maximum(margin - y_pred, 0))# return K.mean(y_true * square_pred + (1-y_true) * margin_square) return K.mean(y_true * margin_square + (1-y_true) * square_pred)def compute_accuracy(y_true, y_pred): '''Compute classification accuracy with a fixed threshold on distances. ''' pred = y_pred.ravel() > 0.5 return np.mean(pred == y_true)def accuracy(y_true, y_pred): '''Compute classification accuracy with a fixed threshold on distances. ''' return K.mean(K.equal(y_true, K.cast(y_pred > 0.5, y_true.dtype)))
旧模型的准确率:* 训练集上的准确率:99.55%* 测试集上的准确率:97.42%这种微小的变化导致模型无法学习任何东西:* 训练集上的准确率:48.64%* 测试集上的准确率:48.29%
所以我的问题是:
1. 使用Subtract + Dense作为孪生网络下半部分的推理有什么问题?
2. 我们能解决这个问题吗? 我有两个潜在的解决方案,但我不太有信心,(1)使用卷积神经网络进行特征提取,(2)在孪生网络的下半部分添加更多的密集层。
回答:
在两个相似的例子中,使用共同/基础特征提取模型提取的两个n维特征向量相减后,你会在大多数位置得到零或接近零的值,这些值将用于下一层/输出密集层。另一方面,我们都知道在ANN模型中,权重是以这样一种方式学习的,即不重要的特征会产生非常小的响应,而突出/有趣的特征会对决策产生高响应。现在你可以理解,我们的减法特征向量是相反的方向,因为当两个例子来自不同类别时,它们会产生高响应,而来自同一类别的例子则相反。此外,输出层只有一个节点(在输出层之前没有额外的隐藏层),对于模型来说,从两个样本属于同一类别时的零值中生成高响应是相当困难的。这可能是解决您问题的一个重要点。
基于上述讨论,您可能想尝试以下想法:
- 转换减法特征向量,以确保当存在相似性时,您会得到高响应,可能通过从1中减去或使用倒数(乘法逆)然后进行归一化。
- 在输出层之前添加更多的密集层。
我不会惊讶,如果使用卷积神经网络而不是堆叠的密集层进行特征提取(正如您所想)并不会显著提高您的准确率,因为这只是另一种进行相同操作(特征提取)的方法。