Реализация пользовательской функции потерь в кератах с условием
Мне нужна помощь с функцией потери Keras. Я реализовал пользовательскую функцию потерь на керах с бэкэндом Tensorflow.
Я реализовал пользовательскую функцию потерь в numpy, но было бы здорово, если бы она могла быть преобразована в функцию потерь keras. Функция потерь принимает фрейм данных и серию идентификаторов пользователей. Евклидово расстояние для одного и того же user_id является положительным и отрицательным, если user_id отличается. Функция возвращает суммированное скалярное расстояние кадра данных.
def custom_loss_numpy (encodings, user_id):
# user_id: a pandas series of users
# encodings: a pandas dataframe of encodings
batch_dist = 0
for i in range(len(user_id)):
first_row = encodings.iloc[i,:].values
first_user = user_id[i]
for j in range(i+1, len(user_id)):
second_user = user_id[j]
second_row = encodings.iloc[j,:].values
# compute distance: if the users are same then Euclidean distance is positive otherwise negative.
if first_user == second_user:
tmp_dist = np.linalg.norm(first_row - second_row)
else:
tmp_dist = -np.linalg.norm(first_row - second_row)
batch_dist += tmp_dist
return batch_dist
Я попытался реализовать функцию потери Keras. Я извлек массив numpy из тензорных объектов y_true и y_pred.
def custom_loss_keras(y_true, y_pred):
# session of my program
sess = tf_session.TF_Session().get()
with sess.as_default():
array_pred = y_pred.eval()
print(array_pred)
Но я получаю следующую ошибку.
tensorflow.python.framework.errors_impl.InvalidArgumentError: You must feed a value for placeholder tensor 'dense_1_input' with dtype float and shape [?,102]
[[Node: dense_1_input = Placeholder[dtype=DT_FLOAT, shape=[?,102], _device="/job:localhost/replica:0/task:0/device:CPU:0"]()]]
Буду благодарен за любую помощь.
2 ответа
Прежде всего, невозможно "извлечь массив из 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
Есть два шага в реализации параметризованной пользовательской функции потерь в Keras. Во-первых, написание метода для коэффициента / метрики. Во-вторых, написание функции-обертки для форматирования вещей так, как это нужно Керасу.
На самом деле немного чище использовать бэкэнд Keras вместо tenorflow для простых пользовательских функций потерь, таких как DICE. Вот пример коэффициента, реализованного таким образом:
import keras.backend as K def dice_coef(y_true, y_pred, smooth, thresh): y_pred = y_pred > thresh y_true_f = K.flatten(y_true) y_pred_f = K.flatten(y_pred) intersection = K.sum(y_true_f * y_pred_f) return (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)
Теперь для сложной части. Функции потери Keras должны принимать только
(y_true, y_pred)
в качестве параметров. Поэтому нам нужна отдельная функция, которая возвращает другую функцию:def dice_loss(smooth, thresh): def dice(y_true, y_pred) return -dice_coef(y_true, y_pred, smooth, thresh) return dice
Наконец, вы можете использовать его в Keras следующим образом compile
:
# build model
model = my_model()
# get the loss function
model_dice = dice_loss(smooth=1e-5, thresh=0.5)
# compile model
model.compile(loss=model_dice)