Странные результаты от CNN в Keras

Вопрос или проблема

У меня есть задача бинарной классификации. Я разработал модель с свёрточными ядрами на первых слоях, а затем плотными слоями. В качестве выходного слоя я использовал softmax слой размером 2, а затем применил one-hot кодирование к своим меткам. Это означает, что мои метки выглядят как 2-битное число: либо 01, либо 10.

Также я сообщаю следующие метрики: fp, fn, tp, tn, recall, precision и accuracy.

Моя проблема заключается в том, что я получаю действительно странные результаты во время обучения, как показано ниже. Accuracy, precision и recall всегда равны. Более конкретно: fp=fn, tp=tn, recall=precision=accuracy!!

Может кто-то объяснить это? И как это исправить?

Вот мой код для предварительной обработки данных, построения модели и обучения.

def preprocess_data(X_train, y_train, X_test, y_test):
    X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], X_train.shape[2], 1)
    X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], X_train.shape[2], 1)

    X_train = X_train.astype('float32')
    X_test = X_test.astype('float32')

    # нормализация
    max_signal_value = max(X_train.max(), X_test.max())
    X_train = X_train/max_signal_value 
    X_test = X_test/max_signal_value 

    # бинаризация
    y_train = np.array(list(map(lambda x:int(x==1), y_train)))
    y_test = np.array(list(map(lambda x:int(x==1), y_test)))

    # One-hot кодирование метки
    y_train = to_categorical(y_train)
    y_test = to_categorical(y_test)

    return X_train, y_train, X_test, y_test

metrics = [
    keras.metrics.FalseNegatives(name="fn"),
    keras.metrics.FalsePositives(name="fp"),
    keras.metrics.TrueNegatives(name="tn"),
    keras.metrics.TruePositives(name="tp"),
    keras.metrics.Precision(name="precision"),
    keras.metrics.Recall(name="recall"),
    'accuracy'
]

def build_model():
    model = Sequential()

    model.add(Conv2D(filters = 6, kernel_size = (5,5), padding = 'same',
                   activation = 'relu', input_shape = (28,28,1)))

    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(filters=16, kernel_size=(5, 5), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())

    model.add(Dense(20, activation='relu'))
    model.add(Dense(2, activation='softmax'))

    opt = Adam(learning_rate=0.001)
    model.compile(loss=categorical_crossentropy, optimizer=opt, metrics=metrics)

    return model

LeNet_model = build_model()
LeNet_model.summary()

X_train, y_train, X_test, y_test = preprocess_data(X_train, y_train, X_test, y_test)

history = LeNet_model.fit(X_train, y_train, epochs=50, batch_size=1,
                        steps_per_epoch=X_train.shape[0], validation_data=(X_test, y_test),
                        validation_steps=X_test.shape[0], verbose=1)

Результат выглядит следующим образом:

74008/74008 [=======] - loss: 0.5173 - fn: 16423.0000 - fp: 16423.0000 - tn: 57585.0000 - tp: 57585.0000 - precision: 0.7781 - recall: 0.7781 - accuracy: 0.7781 - cat_accuracy: 0.7781 - val_loss: 0.5173 - val_fn: 4643.0000 - val_fp: 4643.0000 - val_tn: 15487.0000 - val_tp: 15487.0000 - val_precision: 0.7693 - val_recall: 0.7693 - val_accuracy: 0.7693 - val_cat_accuracy: 0.7693
Epoch 2/50

74008/74008 [=======] - loss: 0.4986 - fn: 16404.0000 - fp: 16404.0000 - tn: 57604.0000 - tp: 57604.0000 - precision: 0.7783 - recall: 0.7783 - accuracy: 0.7783 - cat_accuracy: 0.7783 - val_loss: 0.5200 - val_fn: 4644.0000 - val_fp: 4644.0000 - val_tn: 15486.0000 - val_tp: 15486.0000 - val_precision: 0.7693 - val_recall: 0.7693 - val_accuracy: 0.7693 - val_cat_accuracy: 0.7693```

Это бинарная классификация, вам нужно изменить последние слои на 1 единицу с сигмоидной активацией. Функция потерь также должна быть изменена на binary_crossentropy.

Кроме того, для y, просто оставьте его значение как 0 или 1, нет необходимости выполнять One-Hot Кодирование, чтобы оно стало 01 или 10. Это избыточно и не рекомендуется.

Вы можете попробовать этот код для построения модели:

def build_model():
    model = Sequential()

    model.add(Conv2D(filters = 6, kernel_size = (5,5), padding = 'same',
                   activation = 'relu', input_shape = (28,28,1)))

    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(filters=16, kernel_size=(5, 5), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())

    model.add(Dense(20, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))

    opt = Adam(learning_rate=0.001)
    model.compile(loss="binary_crossentropy", optimizer=opt, metrics=metrics)

return model

Надеюсь, это поможет.

Ответ или решение

Странные результаты от свёрточной нейронной сети (CNN) в Keras: анализ и рекомендации

В данной ситуации вы столкнулись с проблемой в реализации модели для решения задачи бинарной классификации. Ваш подход использует свёрточные слои, за которыми следуют полносвязные слои, а также выходной слой с функцией активации softmax и размерностью 2. Однако, как оказалось, это не является оптимальным выбором для вашей задачи.

Проблемы с текущей реализацией

1. Использование Softmax для бинарной классификации

Вы используете выходной слой с двумя нейронами и функцией активации softmax. Это приводит к тому, что вы интерпретируете предсказания как вероятности для двух классов, что некорректно в контексте бинарной классификации. Правильнее было бы использовать один нейрон с функцией активации sigmoid.

2. Однократное кодирование классов

Метод one-hot кодирования для меток 0 и 1 в вашем случае является излишним. Для бинарной классификации достаточно сохранять просто метки 0 или 1. Однократное кодирование приводит к путанице и может способствовать странным результатам таких, как равенство всех ваших метрик (accuracy, precision и recall).

Рекомендации по исправлению

Для исправления возникших проблем можно предложить следующий алгоритм изменения вашей модели.

  1. Модель
    • Измените выходной слой на один нейрон с сигмоидной активацией, которая будет выдавать значение в диапазоне от 0 до 1, указывая вероятность принадлежности к положительному классу.
    • Используйте функцию потерь binary_crossentropy, подходящую для задач бинарной классификации.

Ваш обновлённый код для построения модели может выглядеть следующим образом:

def build_model():
    model = Sequential()

    model.add(Conv2D(filters=6, kernel_size=(5, 5), padding='same',
                     activation='relu', input_shape=(28, 28, 1)))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(filters=16, kernel_size=(5, 5), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())
    model.add(Dense(20, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))  # Один нейрон с сигмоидной активацией

    opt = Adam(learning_rate=0.001)
    model.compile(loss='binary_crossentropy', optimizer=opt, metrics=metrics)  # Функция потерь для бинарной классификации

    return model
  1. Предобработка данных
    • Измените код предобработки таким образом, чтобы метки y оставались в формате 0 и 1, без использования one-hot кодирования.

Ваш обновлённый код предобработки может выглядеть так:

def preprocess_data(X_train, y_train, X_test, y_test):
    X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], X_train.shape[2], 1)
    X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], X_train.shape[2], 1)

    X_train = X_train.astype('float32')
    X_test = X_test.astype('float32')

    # Нормализация
    max_signal_value = max(X_train.max(), X_test.max())
    X_train = X_train / max_signal_value
    X_test = X_test / max_signal_value

    # Перепроверьте метки
    y_train = np.array(y_train).astype(int)  # Конвертируйте метки в 0 и 1
    y_test = np.array(y_test).astype(int)

    return X_train, y_train, X_test, y_test

Заключение

Проблема с равенством метрик во время обучения может быть исправлена путем внесения изменений в вашу модель и способ представления меток. Перепишите модель с одним выходным нейроном и используйте соответствующую функцию потерь. Это должно значительно улучшить ваши результаты и устранить странные закономерности в метриках. Если у вас возникнут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться за поддержкой.

Оцените материал
Добавить комментарий

Капча загружается...