Прежде всего, невозможно "извлечь массив numpy из y_true
и y_pred
" в функциях потери Keras. Вы должны работать с тензорами с внутренними функциями Keras (или функциями TF) для расчета потерь.
Другими словами, было бы лучше подумать о «векторизованном» способе расчета потерь, без использования if-else и циклов.
Ваша функция потерь может быть вычислена в следующих шагах:
- Генерация матрицы парных евклидовых расстояний между всеми парами векторов в
encodings
.
- Генерация матрицы
I
, элемент I_ij
которой равен 1, если user_i == user_j
, и -1, если user_i != user_j
.
- Поэлементно умножьте две матрицы и суммируйте элементы, чтобы получить окончательную потерю.
Вот реализация:
def custom_loss_keras(user_id, encodings):
# calculate pairwise Euclidean distance matrix
pairwise_diff = K.expand_dims(encodings, 0) - K.expand_dims(encodings, 1)
pairwise_squared_distance = K.sum(K.square(pairwise_diff), axis=-1)
# add a small number before taking K.sqrt for numerical safety
# (K.sqrt(0) sometimes becomes nan)
pairwise_distance = K.sqrt(pairwise_squared_distance + K.epsilon())
# this will be a pairwise matrix of True and False, with shape (batch_size, batch_size)
pairwise_equal = K.equal(K.expand_dims(user_id, 0), K.expand_dims(user_id, 1))
# convert True and False to 1 and -1
pos_neg = K.cast(pairwise_equal, K.floatx()) * 2 - 1
# divide by 2 to match the output of `custom_loss_numpy`, but it's not really necessary
return K.sum(pairwise_distance * pos_neg, axis=-1) / 2
Я предположил, что user_id
являются целыми числами в коде выше. Хитрость здесь в том, чтобы использовать K.expand_dims
для реализации парных операций. На первый взгляд это, наверное, немного сложно понять, но это весьма полезно.
Это должно дать примерно то же значение потерь, что и custom_loss_numpy
(из-за K.epsilon()
будет небольшая разница):
encodings = np.random.rand(32, 10)
user_id = np.random.randint(10, size=32)
print(K.eval(custom_loss_keras(K.variable(user_id), K.variable(encodings))).sum())
-478.4245
print(custom_loss_numpy(pd.DataFrame(encodings), pd.Series(user_id)))
-478.42953553795815
Я допустил ошибку в функции потерь.
Когда эта функция используется в обучении, поскольку Keras автоматически меняет y_true
на минимум 2D, аргумент user_id
больше не является 1D-тензором. Форма его будет (batch_size, 1)
.
Чтобы использовать эту функцию, необходимо удалить лишнюю ось:
def custom_loss_keras(user_id, encodings):
pairwise_diff = K.expand_dims(encodings, 0) - K.expand_dims(encodings, 1)
pairwise_squared_distance = K.sum(K.square(pairwise_diff), axis=-1)
pairwise_distance = K.sqrt(pairwise_squared_distance + K.epsilon())
user_id = K.squeeze(user_id, axis=1) # remove the axis added by Keras
pairwise_equal = K.equal(K.expand_dims(user_id, 0), K.expand_dims(user_id, 1))
pos_neg = K.cast(pairwise_equal, K.floatx()) * 2 - 1
return K.sum(pairwise_distance * pos_neg, axis=-1) / 2