Как реализовать пользовательскую функцию потери Keras для LSTM

#python #tensorflow #keras #lstm #loss-function

Вопрос:

У меня есть проблема многоклассовой классификации, и я использую LSTM для ее решения. Я тренировал свою модель с помощью categorical_crossentropy . Но когда дело доходит до проверки качества модели (после обучения) Я должен использовать эту пользовательскую метрику, где A есть 2D-матрица штрафов:

 def score(y_true, y_pred):
    S = 0.0
    y_true = y_true.astype(int)
    y_pred = y_pred.astype(int)
    for i in range(0, y_true.shape[0]):
        S -= A[y_true[i], y_pred[i]]
    return S/y_true.shape[0]
 

Такая пользовательская метрика может приниматься в качестве входных y_true данных и y_pred в качестве Series объектов Pandas, и она выводит отрицательное число, которое чем ближе к нулю, тем лучше.

Я хотел бы заменить текущую categorical_crossentropy функцию потерь пользовательской потерей, поведение которой аналогично приведенной выше пользовательской метрике, то есть учитывает матрицу A штрафов.

Проблемы, с которыми я сталкиваюсь, заключаются в том, что входы функции потерь являются Tensor объектами, а Series не объектами Панд, с которыми я совершенно незнаком. Не только это, но, поскольку я имею дело с LSTM, форма моих входных тензоров находится в 3D:

 y_true: Tensor("IteratorGetNext:1", shape=(1, 25131, 12), dtype=uint8)
type(y_true): <class 'tensorflow.python.framework.ops.Tensor'>
y_pred: Tensor("sequential_26/time_distributed_26/Reshape_1:0", shape=(1, 25131, 12), dtype=float32)
type(y_pred): <class 'tensorflow.python.framework.ops.Tensor'>
 

Если это поможет, это моя архитектура:

 callbacks = [EarlyStopping(monitor='val_loss', patience=25)]

model = Sequential()
model.add(Masking(mask_value = 0.))
model.add(Bidirectional(LSTM(64, return_sequences=True, activation = "tanh")))
model.add(Dropout(0.3))
model.add(TimeDistributed(Dense(12, activation='softmax')))
adam = adam_v2.Adam(learning_rate=0.002)

model.compile(optimizer=adam, loss=score, metrics=['accuracy'])

history = model.fit(X_train, y_train, epochs=150, batch_size=1, shuffle=False,
                    validation_data=(X_test, y_test), verbose=2, callbacks=[callbacks])
 

Это формы моих входных данных для модели, всего у меня 12 классов:

 print(f'{X_train.shape} {X_test.shape} {y_train.shape} {y_test.shape}')
(73, 25131, 29) (25, 23879, 29) (73, 25131, 12) (25, 23879, 12)
 

Это матрица A штрафов размером 12×12, которые представляют собой количество классов задачи многоклассовой классификации:

введите описание изображения здесь

И это соревнование, для которого я создаю модель:

https://xeek.ai/challenges/force-well-logs/overview

https://github.com/bolgebrygg/Force-2020-Machine-Learning-competition/tree/master/lithology_competition

Комментарии:

1. можем ли мы предположить, что A имеет форму (X_train.shape[0], X_train.shape[0]) ?

2. Штрафная матрица A имеет форму 12×12 (которые являются классами). Я добавлю это к вопросу.

Ответ №1:

Поскольку вы хотите провести оценку модели, вам нужна метрика, а не убытки.

Чтобы преобразовать функцию в пользовательскую метрику в tensorflow:

  1. Приведение y_true и y_pred чтобы int не получить то, что вы хотите, вам нужно использовать tf.argmax вместо этого, предполагая, что y_true это однократно закодированные метки и y_pred вероятности. (потому что вы использовали categorical_crossentropy для обучения и softmax в качестве функции активации на выходном уровне)
  2. Определите пользовательский класс метрик

Примеры Кодов:

 class Score(tf.keras.metrics.Metric):
  def __init__(self, A, name="score", **kwargs):
    super().__init__(name=name, **kwargs)
    self.A = tf.constant(A,dtype=tf.float32)
    self.S = self.add_weight(name="S", initializer="zeros")
    self.num_elems = self.add_weight(name="num_elems", initializer="zeros")

  def update_state(self, y_true, y_pred, sample_weight=None):
    #convert y_true and y_pred to indices and flatten them
    y_true = tf.reshape(tf.argmax(y_true,-1),(-1,))
    y_pred = tf.reshape(tf.argmax(y_pred,-1),(-1,))
    num_elems = y_true.shape[0]
    indices = tf.stack([y_true,y_pred],1)
    #access A using indices and sum
    self.S.assign_add(-tf.reduce_sum(tf.gather_nd(self.A,indices)))
    self.num_elems.assign_add(num_elems)
        
  def result(self):
    return self.S/self.num_elems
 

Кроме того, model.fit используется для обучения модели. Если вы хотите только оценить модель, передайте Score() ее в качестве показателя и используйте model.evaluate .
Примеры Кодов:

 model.compile(optimizer=adam, loss=tf.keras.losses.CategoricalCrossentropy(), 
              metrics=['accuracy',Score(A)])
model.evaluate(X_test, y_test, batch_size=1)