Реализация пользовательской функции потерь в кератах с условием

Мне нужна помощь с функцией потери 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 и циклов.

Ваша функция потерь может быть вычислена в следующих шагах:

  1. Генерация матрицы парных евклидовых расстояний между всеми парами векторов в encodings,
  2. Генерация матрицы I чей элемент I_ij 1, если user_i == user_jи -1 если user_i != user_j,
  3. Поэлементно умножьте две матрицы и суммируйте элементы, чтобы получить окончательную потерю.

Вот реализация:

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. Во-первых, написание метода для коэффициента / метрики. Во-вторых, написание функции-обертки для форматирования вещей так, как это нужно Керасу.

  1. На самом деле немного чище использовать бэкэнд 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)
    
  1. Теперь для сложной части. Функции потери 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)
Другие вопросы по тегам