В настоящее время я пытаюсь реализовать сиамскую сеть в Керасе, где я должен реализовать следующую функцию потерь:
loss(p ∥ q) = Is · KL(p ∥ q) + Ids · HL(p ∥ q)
подробное описание функции потерь из бумаги
Где KL - дивергенция Кульбака-Лейблера, а HL - потеря шарнира.
Во время обучения я обозначаю пары одинаковых динамиков как 1, а разные динамики - как 0.
Цель состоит в том, чтобы использовать обученную сеть для извлечения вложений из спектрограмм. Спектрограмма представляет собой двумерную матрицу NumPy 40x128 (время х частота)
Проблема в том, что я никогда не получаю точность более 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
Я пытаюсь выяснить, сделал ли я какие-то неправильные предположения и как улучшить свою точность.