model.predict и model.evaluate дают разный результат

Рейтинг: 1Ответов: 1Опубликовано: 03.05.2023

Пишу функции для тестирования нейронных сетей и заметил вот такую странность. Сам код:

import shutil
import os
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

from tensorflow.keras.preprocessing.image import ImageDataGenerator

from sklearn.metrics import confusion_matrix, precision_score, \
                            recall_score, roc_auc_score, ConfusionMatrixDisplay

def model_score_evaluate(path_name, model_name):

    metrics = ['accuracy',
               tf.keras.metrics.Recall(),
               tf.keras.metrics.Precision(),
               tf.keras.metrics.AUC()]

    model = tf.keras.models.load_model(model_name, compile=False)

    model.compile(loss="categorical_crossentropy",
                  optimizer="rmsprop",
                  metrics=metrics)

    datagen = ImageDataGenerator(rescale = 1./255)
                            
    generator = datagen.flow_from_directory(path_name,
                                            target_size=(Pic_Y, Pic_X),
                                            batch_size=64,
                                            class_mode='categorical')
    
    (
    _,
    _,
    test_recall,
    test_precision,
    test_auc
             ) = model.evaluate(generator, steps=len(generator))
    
    print('Test recall:', test_recall)
    print('Test precision:', test_precision)
    print('Test AUC:', test_auc)

    return

def model_score_predict(path_name, model_name):

    model = tf.keras.models.load_model(model_name, compile=False)

    model.compile(loss="categorical_crossentropy",
                  optimizer="rmsprop",
                  metrics=['accuracy'])

    datagen = ImageDataGenerator(rescale = 1./255)
                            
    generator = datagen.flow_from_directory(path_name,
                                            target_size=(Pic_Y, Pic_X),
                                            batch_size=64,
                                            class_mode='categorical')
    
    true_labels = generator.classes

    predictions = model.predict(generator)

    predicted_labels = np.argmax(predictions, axis=1)

    recall = recall_score(true_labels, predicted_labels, average='micro')
    precision = precision_score(true_labels, predicted_labels, average='micro')
    auc = roc_auc_score(true_labels, predictions, average='macro', 
                        multi_class='ovr')

    print('Test recall:', recall)
    print('Test precision:', precision)
    print('Test AUC:', auc)

    cm = confusion_matrix(true_labels, predicted_labels)

    class_names = list(generator.class_indices.keys())

    disp = ConfusionMatrixDisplay(confusion_matrix=cm, 
                                  display_labels=class_names)

    _, ax = plt.subplots(figsize=(10, 10))
    disp.plot(ax=ax, cmap=plt.cm.Blues)

    ax.set_title("Confusion matrix")
    ax.set_xlabel("Predicted label")
    ax.set_ylabel("True label")

    plt.show()

    return

Первая модель определяет перечень метрик при её компиляции. Затем она создает генератор из предложенных данных. Вызывается метод evaluate() и выводятся на печать метрики.

Вторая модель компилируется без дополнительного перечня метрик. У модели вызывается метод predict(), а у генератора - classes. Метрики рассчитываются с помощью библиотеки sklearn. Ну и бонусом вторая функция строит матрицу неточностей.

Приведу результаты работы функций на одних и тех де данных:

path_name = 'to_test'
model_name = 'convnet_from_scratch.h5'

model_score_evaluate(path_name, model_name)

Результат:
    Test recall: 0.9272338151931763
    Test precision: 0.932723343372345
    Test AUC: 0.9822500944137573

model_score_predict(path_name, model_name)

Результат:
    Test recall: 0.13846976993044408
    Test precision: 0.13846976993044408
    Test AUC: 0.4982606769303777

Матрицу неточностей не привожу. Смотреть вообще не на что при таком AUC... Почему такой разный результат?

UPD: Пощелкал метод model.predict. Формат выходных данных ожидаем:

img = Image.open('to_test/03/55907903_b524_c1222_user_652_T.jpg')
img = np.array(img.resize((Pic_X, Pic_Y))) / 255
img = np.expand_dims(img, axis=0)
model.predict(img)

Результат:
    array([[9.648740e-36, 0.000000e+00, 1.000000e+00, 0.000000e+00,
        7.593448e-24, 0.000000e+00, 1.165523e-33, 0.000000e+00,
        0.000000e+00]], dtype=float32)

Изображение взято из третьей по счёту стопки (с индексом 2). Степень уверенности модели очень высока, как видно. Так и с прочими, которые я проверил руками.

Содержимое generator.classes без сюрпризов. Данные не перемешиваются, по порядку цифры от 0 до 8 (всего 9 классов).

Ответы

▲ 1Принят

Всё дело оказалось в том, что метод flow_from_directory имеет атрибут shuffle, который по умолчанию равен True. Из-за этого вектор предсказаний был перемешан относительно статично определенных меток классов у тестовых данных.

generator = datagen.flow_from_directory(path_name,
                                            target_size=(Pic_Y, Pic_X),
                                            batch_size=64,
                                            class_mode='categorical',
                                            shuffle=False)

После этого оценки, измеренные обоими методами, примерно совпали. Их различия уже связаны только с методом усреднения при многоклассовой классификации.