Оценщик Tensorflow не сходится на модели, преобразованной из Keras (при использовании binary_crossentropy)
Я застрял на долгое время, используя функциональность model_to_estimator в Оценщиках Tensorflow. Проблема, кажется, в том, что Керас позволяет binary_crossentropy
потеря на одном нейроне Плотный выход.
В моем случае я передаю последовательные данные RNN и хочу выяснить, приводит ли последовательность к преобразованию или нет. Код (также можно найти по адресу https://colab.research.google.com/drive/194Puigi-LdzxZup6LNREk47l9uP0_Dx9) для этого будет
import numpy as np
import pandas as pd
import tensorflow as tf
np.random.seed(2)
data = np.random.randint(1,500,size=(10000, 50)) # create something like 50 words out of a vocab of 500
#split
train = data[:7999]
val = data[8000:]
def _input_fn2(arr, batch_size=500, shuffle=False):
arr_copy = arr.copy()
def _parse_func(features):
sum = tf.math.reduce_sum(features)
label = tf.cond(sum >= 15000, lambda: np.array([1]), lambda: np.array([0])) # label=true if sum is larger 15000, gives about 1% true
return (features, label)
dataset = tf.data.Dataset.from_tensor_slices(arr_copy)
dataset = dataset.map(_parse_func)
dataset = dataset.shuffle(200)
dataset = dataset.batch(batch_size)
dataset = dataset.repeat()
return dataset
from tensorflow.keras.layers import Dense, Input, CuDNNGRU, Embedding
import tensorflow.keras.backend as K
inputs = Input(shape=(50,))
embedding = Embedding(
output_dim=5,
input_dim=500,
input_length=50)(inputs)
lstm = CuDNNGRU(
units=5,
input_shape=((5,1)),
return_sequences=False,
)(embedding)
outputs = Dense(1, activation='sigmoid',name='final')(lstm)
model = tf.keras.Model(inputs, outputs)
def true_positives(y_true, y_pred):
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
return true_positives
def false_positives(y_true, y_pred):
true_positives = K.sum(K.round(K.clip((1 - y_true) * y_pred, 0, 1)))
return true_positives
def true_negatives(y_true, y_pred):
true_positives = K.sum(K.round(K.clip((1 - y_true) * (1 - y_pred), 0, 1)))
return true_positives
def false_negatives(y_true, y_pred):
true_positives = K.sum(K.round(K.clip(y_true * (1 - y_pred), 0, 1)))
return true_positives
def recall(y_true, y_pred):
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
recall = true_positives / (possible_positives + K.epsilon())
return recall
def precision(y_true, y_pred):
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
precision = true_positives / (predicted_positives + K.epsilon())
return precision
model.compile(
optimizer=tf.keras.optimizers.Adam(),
loss='binary_crossentropy',
metrics=[
'acc',
true_positives,
true_negatives,
false_positives,
false_negatives,
recall,
precision
]
)
print(model.summary())
train_ds = _input_fn2(train, shuffle=True)
val_ds = _input_fn2(val)
model.fit(
train_ds,
steps_per_epoch=50 ,
epochs=100,
validation_data=val_ds,
validation_steps=10,
verbose=2
)
Это работает хорошо, модель сходится и начинает учиться.
Эпоха 100/100 - 2 с - потеря: 3,5754e-04 - в соотв. 1,0000 - истина_позитивов: 3,2000 - истина_отрицательных: 496,7400 - ложь_позитивов: 0,0000e+00 - ложь_отрицательных: 0,0000e+00 - отзыв: 0,9400 - точность: 0,9400 - val_loss: 0.1281 - val_acc: 0.9806 - val_true_positives: 0.0000e+00 - val_true_negatives: 490.3000 - val_false_positives: 4.5000 - val_false_negatives: 5.2000 - val_recall: 0.0000e+00 - val_precision: 0.0000e+00
Вы можете видеть, что в большинстве случаев он предполагает отрицательный результат, это связано с дисбалансом в наборе данных и, вероятно, правильным решением.
Теперь преобразование этого в модель оценщика, как это
from tensorflow.keras.estimator import model_to_estimator
from tensorflow.estimator import train_and_evaluate, RunConfig
from tensorflow.estimator import TrainSpec, EvalSpec
from tensorflow import metrics
from tensorflow.contrib.estimator import add_metrics
run_config = RunConfig(
save_checkpoints_secs=5,
keep_checkpoint_max=10
)
def eval_metrics(features, labels, predictions):
return {
'precision_streaming': metrics.precision(labels=labels, predictions=predictions['final']),
'recall_streaming': metrics.recall(labels=labels, predictions=predictions['final']),
'true_positives_streaming': metrics.true_positives(labels=labels, predictions=predictions['final']),
'true_negatives_streaming': metrics.true_negatives(labels=labels, predictions=predictions['final']),
'false_positives_streaming': metrics.false_positives(labels=labels, predictions=predictions['final']),
'false_negatives_streaming': metrics.false_negatives(labels=labels, predictions=predictions['final'])
}
estimator = model_to_estimator(keras_model=model, config=run_config)
estimator = add_metrics(estimator, eval_metrics) #took out these metrics for showcase
train_spec = TrainSpec(
input_fn=lambda: _input_fn2(train, shuffle=True), max_steps=2000
)
eval_spec = EvalSpec(input_fn=lambda: _input_fn2(val), steps=4)
score = train_and_evaluate(estimator, train_spec, eval_spec)
print(score)
После сброса модели и обучения версии, основанной на оценщике, модель не сходится, а только кажется, что предсказывает истины
({"binary_accuracy": 0,9865, "false_negatives_streaming": 0,0, "false_positives_streaming": 1979,0, "Precision_streaming": 0,0105, "rec_streaming": 1,0, "true_negatives_streaming": 0,0, "true_positives_streaming": 21,0, "global_step"), []
Теперь мне удалось заставить это работать, используя последний слой Dense(2), горячее кодирование метки и переключение функции потерь на sparse_categorical_crossentropy
... но я бы действительно предпочел сохранить единый выходной класс, так как он облегчает мои вычисления f1-оценки и много чего еще.
Вдохновленное предположение состоит в том, что Оценщик не может распределить потери по одному плотному выходному слою, Керасу как-то удается это сделать.
Любая помощь будет принята с благодарностью
Bests wirtsi