siamese-net中的自定义组合铰链/ kb-发散损失功能无法生成有意义的扬声器嵌入

Flu*_*mer 12 python machine-learning deep-learning keras tensorflow

我目前正在尝试在Keras实现一个暹罗网,我必须实现以下丢失功能:

loss(p ? q) = Is · KL(p ? q) + Ids · HL(p ? q)
Run Code Online (Sandbox Code Playgroud)

纸张损失函数的详细描述

KL是Kullback-Leibler分歧,HL是铰链损失.

在训练期间,我将同一个扬声器对标记为1,将不同的扬声器标记为0.

目标是使用训练好的网络从频谱图中提取嵌入.频谱图是二维numpy阵列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
Run Code Online (Sandbox Code Playgroud)

单个谱图将被馈送到基础网络的分支中,暹罗网络由两个这样的分支组成,因此两个谱图同时被馈送,并且在距离层中被连接.基础网络的输出是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
Run Code Online (Sandbox Code Playgroud)

然后,我建立一个暹罗网如下:

    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'])
Run Code Online (Sandbox Code Playgroud)

最后,我构建了一个只有一个输入的相同架构的网络,并试图提取嵌入,然后在它们上构建均值,其中嵌入应该作为扬声器的表示,在聚类期间使用:

utterance_embedding = np.mean(embedding_extractor.predict_on_batch(spectrogram), axis=0)
Run Code Online (Sandbox Code Playgroud)

我们在voxceleb扬声器组上训练网.

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

我想知道我是否做了任何错误的假设以及如何提高我的准确性.

Dan*_*ler 5

准确性问题

请注意,在您的模型中:

  • y_true =标签
  • y_pred = kullback-leibler分歧

这两个无法比较,请看这个例子:

为了得到正确的结果,当y_true == 1(同一个说话者)时,Kullback-Leibler y_pred == 0(没有发散).

因此,完全可以预期指标将无法正常运行.

然后,您要么创建自定义指标,要么只计算评估损失.
此自定义指标需要进行一些调整才能实现,如下所述.

损失可能存在的问题

剪裁

可能是个问题

首先,请注意您正在使用clipKullback-Leibler的值.这可能是错误的,因为剪辑会丢失剪裁区域中的渐变.并且由于您的激活是a PRelu,因此您的值小于零且大于1.那么此处和那里肯定存在零梯度情况,存在冻结模型的风险.

因此,您可能不想剪切这些值.并且为了避免使用负值PRelu,您可以尝试使用'softplus'激活,这是一种没有负值的软relu.你也可以"总结"一个epsilon以避免麻烦,但是留下大于1的值是没有问题的:

#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)
Run Code Online (Sandbox Code Playgroud)

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])
Run Code Online (Sandbox Code Playgroud)

极低的边距和夹紧铰链

可能是个问题

最后,看到铰链损耗也会将值限制在零以下!
由于Kullback-Leibler不限于1,因此这种损失可能无法控制具有高度发散性的样本.不确定这是否真的是一个问题,但您可能想要:

  • 增加保证金
  • 在Kullback-Leibler中,使用mean而不是sum
  • 使用softplus铰链代替a max,以避免失去渐变.

看到:

MARGIN = someValue
hinge = ks.backend.mean(ks.backend.softplus(MARGIN - y_pred), axis=-1)
Run Code Online (Sandbox Code Playgroud)

现在我们可以想到自定义精度

这不是很容易,因为我们对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)
Run Code Online (Sandbox Code Playgroud)