Модель Кераса для сиамской сети не учится и всегда предсказывает один и тот же результат

Я пытаюсь обучить сиамскую нейронную сеть с помощью Keras, чтобы определить, принадлежат ли 2 изображения к одному классу или нет. Мои данные перемешаны и содержат равное количество положительных и отрицательных примеров. Моя модель ничего не изучает и всегда предсказывает один и тот же результат. Каждый раз я получаю одни и те же потери, точность проверки и потерю проверки.

Результаты обучения

def convert(row):
    return imread(row)

def contrastive_loss(y_true, y_pred):
    margin = 1
    square_pred = K.square(y_pred)
    margin_square = K.square(K.maximum(margin - y_pred, 0))
    return K.mean(y_true * square_pred + (1 - y_true) * margin_square)

def SiameseNetwork(input_shape):
    top_input = Input(input_shape)

    bottom_input = Input(input_shape)

    # Network
    model = Sequential()
    model.add(Conv2D(96,(7,7),activation='relu',input_shape=input_shape))
    model.add(MaxPooling2D())
    model.add(Conv2D(256,(5,5),activation='relu',input_shape=input_shape))
    model.add(MaxPooling2D())
    model.add(Conv2D(256,(5,5),activation='relu',input_shape=input_shape))
    model.add(MaxPooling2D())
    model.add(Flatten())
    model.add(Dense(4096,activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(1024,activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(512,activation='relu'))
    model.add(Dropout(0.5))

    encoded_top = model(top_input)
    encoded_bottom = model(bottom_input)

    L1_layer = Lambda(lambda tensors:K.abs(tensors[0] - tensors[1]))    
    L1_distance = L1_layer([encoded_top, encoded_bottom])

    prediction = Dense(1,activation='sigmoid')(L1_distance)
    siamesenet = Model(inputs=[top_input,bottom_input],outputs=prediction)
    return siamesenet

data = pd.read_csv('shuffleddata.csv')
print('Converting X1....')

X1 = [convert(x) for x in data['X1']]

print('Converting X2....')

X2 = [convert(x) for x in data['X2']]

print('Converting Y.....')
Y = [0 if data['Y'][i] == 'Negative' else 1 for i in range(len(data['Y']))]

input_shape = (53,121,3,)
model = SiameseNetwork(input_shape)
model.compile(loss=contrastive_loss,optimizer='sgd',metrics=['accuracy'])
print(model.summary())
model.fit(X1,Y,batch_size=32,epochs=20,shuffle=True,validation_split = 0.2)
model.save('Siamese.h5')


person S.Hemanth    schedule 22.12.2019    source источник
comment
Вы пробовали с меньшим размером шага (или даже с другим оптимизатором)? Вы пробовали переобучить небольшую часть своего набора данных?   -  person Zaccharie Ramzi    schedule 22.12.2019
comment
Да, я пробовал использовать меньший размер шага и с другими оптимизаторами и функциями потерь. Я также попытался переобучить небольшие данные, но модель ничего не узнает. Не могли бы вы проверить, правильно ли я ввожу информацию?   -  person S.Hemanth    schedule 22.12.2019
comment
Хм, слой, который вы называете L1_distance, я предполагаю, должен быть расстоянием между двумя выходами сиамской сети, но здесь это карта ошибок. Вам нужно вычислить среднюю разницу l1, например: Lambda(lambda tensors: K.mean(K.abs(tensors[0] - tensors[1]))). Я также удивлен, что у вас есть плотный слой после этого слоя. Разве результат не должен быть просто расстоянием?   -  person Zaccharie Ramzi    schedule 22.12.2019
comment
Я попытался использовать вашу версию расстояния L1, а также удалил плотный слой, но это не сработало, и, кстати, мне нужна вероятность того, насколько похожи данные 2 изображения, не имеют никакого расстояния между изображениями   -  person S.Hemanth    schedule 23.12.2019
comment
Конечно, имеет смысл. Я просто подумал, что это может быть проще для начала, и вы могли бы удалить слой Dense и иметь сигмоид в своей контрастной потере, но я видел реализацию, которую вы использовали, и понимаю, что вы хотите ее придерживаться.   -  person Zaccharie Ramzi    schedule 24.12.2019
comment
Что касается переобучения, пробовали ли вы использовать одно изображение, сформировать пару, а затем переобучить это? Сеть всегда должна выводить одно значение, тогда   -  person Zaccharie Ramzi    schedule 24.12.2019
comment
Этот вопрос до некоторой степени решен. Я обнаружил, что использую очень мало данных для обучения этой модели. Эта модель отлично работает для других стандартных наборов данных, и даже для них, если я использую меньше данных, проблема возвращается. Думаю, мне следует использовать больше данных   -  person S.Hemanth    schedule 25.12.2019


Ответы (3)


Упоминание решения этой проблемы в этом разделе (даже если оно присутствует в разделе комментариев) для пользы сообщества.

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

person Tensorflow Support    schedule 30.12.2019
comment
Спасибо за ответ. но я думаю, что сиамский язык предназначен для работы с небольшим объемом данных, а также для получения хороших результатов. Насколько я знаю, он используется в обучении за несколько выстрелов, требующем очень небольшого количества данных. Так в чем же проблема с данными? У меня такая же проблема, когда я тренируюсь на наборе данных AT&T, в нем 400 изображений для 40 человек. Я обучил модель с этими данными с помощью Pytorch, и модель обучалась и дала мне хорошие результаты. когда я тренировался на том же наборе данных с использованием модели keras, он ничего не узнал. Вы знаете об этом? - person mohamed_abdullah; 06.01.2021

Модель отлично работает с большим количеством данных, как указано в комментариях и в ответе службы поддержки Tensorflow. Также работает небольшая настройка модели. Изменение количества фильтров во 2-м и 3-м сверточных слоях с 256 до 64 уменьшает количество обучаемых параметров на большое количество, и поэтому модель начала обучение.

person S.Hemanth    schedule 31.12.2019

Я хочу упомянуть здесь несколько вещей, которые могут быть полезны другим:

1) Стратификация данных / случайная выборка

При использовании validation_split Keras использует последние x процентов данных в качестве данных проверки. Это означает, что если данные упорядочены по классам, например поскольку пары или тройки создаются в последовательности, данные проверки будут поступать только из классов (или класса), содержащихся в последних x процентах данных. В этом случае набор проверки будет бесполезен. Таким образом, важно заполнить входные данные, чтобы убедиться, что набор для проверки содержит случайные выборки из каждого класса.

Документы для validation_split говорят:

Плавайте в диапазоне от 0 до 1. Часть обучающих данных, которая будет использоваться в качестве данных проверки. Модель выделит эту часть обучающих данных, не будет обучаться на ней и будет оценивать потери и любые метрики модели на этих данных в конце каждой эпохи. Данные проверки выбираются из последних выборок в предоставленных данных x и y перед перемешиванием

2) Выбор оптимизатора

В model.compile() выбор optimizer='sgd' может быть не лучшим подходом, поскольку sgd может застрять в локальных минимумах и т. Д. Adam (см. docs) кажется хорошим выбором для начала, так как он ...

[...] сочетает в себе преимущества [...] AdaGrad для работы с разреженными градиентами и способность RMSProp справляться с нестационарными задачами.

согласно Kingma and Ba (2014 г., стр. 10).

from keras.optimizers import Adam
...
model.compile(loss=contrastive_loss, optimizer=keras.optimizers.Adam(lr=0.0001))

3) Ранняя остановка / скорость обучения

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

from keras.callbacks import EarlyStopping
from keras.callbacks import ReduceLROnPlateau
...
early_stopping = EarlyStopping(monitor='val_loss', patience=50, mode='auto', restore_best_weights=True)
reduce_on_plateau = ReduceLROnPlateau(monitor="val_loss", factor=0.8, patience=15, cooldown=5, verbose=0)
...
hist = model.fit([img_1, img_2], y, 
            validation_split=.2, 
            batch_size=128, 
            verbose=1, 
            epochs=9999,
            callbacks=[early_stopping])

4) Инициализация ядра

Также может быть полезна инициализация ядра (с небольшой SD).

# Layer 1
    seq.add(Conv2D(8, (5,5), input_shape=input_shape, 
        kernel_initializer=keras.initializers.TruncatedNormal(mean=0.0, stddev=0.01, seed=None), 
        data_format="channels_first"))
    seq.add(Activation('relu'))
    seq.add(MaxPooling2D(pool_size=(2, 2))) 
    seq.add(Dropout(0.1))

5) Переоснащение

Я заметил, что вместо того, чтобы бороться с переобучением, добавление шума может оказаться весьма полезным. В этом случае просто добавьте немного GaussianNoise в начало сети.

person Peter    schedule 17.04.2021