Вопрос или проблема
У меня есть задача бинарной классификации. Я разработал модель с свёрточными ядрами на первых слоях, а затем плотными слоями. В качестве выходного слоя я использовал 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).
Рекомендации по исправлению
Для исправления возникших проблем можно предложить следующий алгоритм изменения вашей модели.
- Модель
- Измените выходной слой на один нейрон с сигмоидной активацией, которая будет выдавать значение в диапазоне от 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
- Предобработка данных
- Измените код предобработки таким образом, чтобы метки 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
Заключение
Проблема с равенством метрик во время обучения может быть исправлена путем внесения изменений в вашу модель и способ представления меток. Перепишите модель с одним выходным нейроном и используйте соответствующую функцию потерь. Это должно значительно улучшить ваши результаты и устранить странные закономерности в метриках. Если у вас возникнут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться за поддержкой.