siamese网络中自定义的组合铰链/kb发散损失函数无法生成有意义的说话人嵌入



我目前正试图在Keras实现一个siamese网络,在那里我必须实现以下损失函数:

loss(p ∥ q) = Is · KL(p ∥ q) + Ids · HL(p ∥ q)

纸张损失函数的详细描述

其中KL是Kullback-Leibler发散,HL是Hinge损失。

在训练过程中,我将相同的扬声器对标记为1,将不同的扬声器标记为0。

目标是使用经过训练的网络从频谱图中提取嵌入。频谱图是二维数字阵列40x128(时间x频率(

问题是,我从来没有超过0.5的准确度,当对扬声器嵌入进行聚类时,结果显示嵌入和扬声器之间似乎没有相关性

我实现了kb散度作为距离度量,并相应地调整了铰链损耗:

def kullback_leibler_divergence(vects):
x, y = vects
x = ks.backend.clip(x, ks.backend.epsilon(), 1)
y = ks.backend.clip(y, ks.backend.epsilon(), 1)
return ks.backend.sum(x * ks.backend.log(x / y), axis=-1)

def kullback_leibler_shape(shapes):
shape1, shape2 = shapes
return shape1[0], 1

def kb_hinge_loss(y_true, y_pred):
"""
y_true: binary label, 1 = same speaker
y_pred: output of siamese net i.e. kullback-leibler distribution
"""
MARGIN = 1.
hinge = ks.backend.mean(ks.backend.maximum(MARGIN - y_pred, 0.), axis=-1)
return y_true * y_pred + (1 - y_true) * hinge

一个频谱图将被馈送到基础网络的一个分支中,暹罗网由两个这样的分支组成,因此两个频谱图被同时馈送,并在距离层中连接。基本网络的输出为1 x 128。距离层计算kullback-leibler散度,其输出被馈送到kb_hinge_loss中。基础网络的架构如下:

def create_lstm(units: int, gpu: bool, name: str, is_sequence: bool = True):
if gpu:
return ks.layers.CuDNNLSTM(units, return_sequences=is_sequence, input_shape=INPUT_DIMS, name=name)
else:
return ks.layers.LSTM(units, return_sequences=is_sequence, input_shape=INPUT_DIMS, name=name)

def build_model(mode: str = 'train') -> ks.Model:
topology = TRAIN_CONF['topology']
is_gpu = tf.test.is_gpu_available(cuda_only=True)
model = ks.Sequential(name='base_network')
model.add(
ks.layers.Bidirectional(create_lstm(topology['blstm1_units'], is_gpu, name='blstm_1'), input_shape=INPUT_DIMS))
model.add(ks.layers.Dropout(topology['dropout1']))
model.add(ks.layers.Bidirectional(create_lstm(topology['blstm2_units'], is_gpu, is_sequence=False, name='blstm_2')))
if mode == 'extraction':
return model
num_units = topology['dense1_units']
model.add(ks.layers.Dense(num_units, name='dense_1'))
model.add(ks.layers.advanced_activations.PReLU(init='zero', weights=None))
model.add(ks.layers.Dropout(topology['dropout2']))
num_units = topology['dense2_units']
model.add(ks.layers.Dense(num_units, name='dense_2'))
model.add(ks.layers.advanced_activations.PReLU(init='zero', weights=None))
num_units = topology['dense3_units']
model.add(ks.layers.Dense(num_units, name='dense_3'))
model.add(ks.layers.advanced_activations.PReLU(init='zero', weights=None))
num_units = topology['dense4_units']
model.add(ks.layers.Dense(num_units, name='dense_4'))
model.add(ks.layers.advanced_activations.PReLU(init='zero', weights=None))
return model

然后我构建了一个暹罗网,如下所示:

base_network = build_model()
input_a = ks.Input(shape=INPUT_DIMS, name='input_a')
input_b = ks.Input(shape=INPUT_DIMS, name='input_b')
processed_a = base_network(input_a)
processed_b = base_network(input_b)
distance = ks.layers.Lambda(kullback_leibler_divergence,
output_shape=kullback_leibler_shape,
name='distance')([processed_a, processed_b])
model = ks.Model(inputs=[input_a, input_b], outputs=distance)
adam = build_optimizer()
model.compile(loss=kb_hinge_loss, optimizer=adam, metrics=['accuracy'])

最后,我只使用一个输入构建了一个具有相同架构的网络,并尝试提取嵌入,然后在它们之上构建均值,其中嵌入应该作为说话者的表示,在聚类过程中使用:

utterance_embedding = np.mean(embedding_extractor.predict_on_batch(spectrogram), axis=0)

我们在voxceleb扬声器上训练网络。

完整的代码可以在这里看到:GitHub repo

我正在努力弄清楚我是否做出了错误的假设,以及如何提高我的准确性。

问题准确

请注意,在您的模型中:

  • y_true=标签
  • y_pred=kullback-leibler散度

这两者无法比较,请参见以下示例:

对于正确的结果,当y_true == 1(相同扬声器(,Kullback-Leibler是y_pred == 0(无分歧(。

因此,完全预计指标将无法正常工作。

然后,要么创建一个自定义度量,要么只计算评估的损失
如下文所述,此自定义指标需要进行一些调整才能可行。

损失可能存在的问题

剪裁

可能是的问题

首先,请注意,在Kullback-Leibler的值中使用了clip。这可能很糟糕,因为剪辑会丢失剪辑区域中的渐变。由于您的激活是PRelu,因此您的值小于零,大于1。当然,这里和那里都有零梯度的情况,有冻结模型的风险。

因此,您可能不希望剪裁这些值。为了避免PRelu具有负值,您可以尝试使用'softplus'激活,这是一种没有负值的软relu。你也可以"求和"一个epsilon来避免麻烦,但留下大于一的值是没有问题的:

#considering you used 'softplus' instead of 'PRelu' in speakers
def kullback_leibler_divergence(speakers):
x, y = speakers
x = x + ks.backend.epsilon()
y = y + ks.backend.epsilon()
return ks.backend.sum(x * ks.backend.log(x / y), axis=-1)

Kullback-Leibler的同化

这是一个问题

还要注意,Kullback-Leibler不是一个对称函数,它的最小值也不为零!!完美匹配是零,但不匹配的值可能更低,这对损失函数来说是不好的,因为它会导致发散。

请参阅这张显示KB图形的图片

你的论文说你应该把两个损失加起来:(p||q(和(q||p(
这消除了辅助测量和负值。

因此:

distance1 = ks.layers.Lambda(kullback_leibler_divergence,
name='distance1')([processed_a, processed_b])
distance2 = ks.layers.Lambda(kullback_leibler_divergence,
name='distance2')([processed_b, processed_a])
distance = ks.layers.Add(name='dist_add')([distance1,distance2])

非常低的余量和夹式铰链

可能是的问题

最后,请注意铰链损耗也会将值夹在零以下
由于Kullback-Leibler不限于1,因此具有高发散性的样本可能不会受到这种损失的控制。不确定这是否真的是一个问题,但你可能想:

  • 增加利润
  • 在Kullback Leibler中,使用mean而不是sum
  • 在铰链中使用softplus而不是max以避免丢失梯度

参见:

MARGIN = someValue
hinge = ks.backend.mean(ks.backend.softplus(MARGIN - y_pred), axis=-1)

现在我们可以考虑自定义精度

这不是很容易,因为我们没有明确的KB限制,告诉我们"正确/不正确">

你可以随机尝试一个,但你需要调整这个threshold参数,直到你找到一个代表现实的好东西。例如,您可以使用验证数据来找到具有最佳准确性的阈值。

def customMetric(y_true_targets, y_pred_KBL):
isMatch = ks.backend.less(y_pred_KBL, threshold)
isMatch = ks.backend.cast(isMatch, ks.backend.floatx())
isMatch = ks.backend.equal(y_true_targets, isMatch)
isMatch = ks.backend.cast(isMatch, ks.backend.floatx())
return ks.backend.mean(isMatch)

最新更新