керас - метод оценки_генератора с разными показателями точности при одинаковых данных обучения - PullRequest
1 голос
/ 17 апреля 2019

TL; DR Моя модель обучена в течение 1 эпох - для целей тестирования.Тем не менее, при многократной оценке это дает разную точность каждый раз, когда я запускаю evaluate_generator метод с одними и теми же данными обучения .Почему это происходит, и есть ли способ получить один и тот же показатель точности при оценке одних и тех же обученных данных для одной и той же модели несколько раз?


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

Для справки я обучил модель дляотдельная эпоха, а затем сохраненная обученная модель в файле с помощью утилиты save_load_utils, предоставляемой модулем keras_contrib.Однако всякий раз, когда я запускаю модель с теми весами , которые тренировались в течение одной эпохи, я получаю разную степень точности.Я пробовал это 5-10 раз, и это колеблется от 68% до 74%, что довольно много.Поскольку я загружаю предварительно обученные (то есть для 1 эпохи) веса моделей, я ожидаю получить ту же точность.(т.е. за исключением различий в точности чисел с плавающей запятой). Однако, разница в результатах с такой скоростью предполагает, что я, возможно, сделал что-то неправильно.

Кто-нибудь имеет какое-либо представление о том, почему model.evaluate_generatorметод генерирует результаты, которые настолько различаются каждый раз, когда я запускаю его с одним и тем же весом, даже если я использую одни и те же веса, обученные на одной эпохе, чтобы оценить его?Есть ли способ исправить мой код оценки, чтобы точность, полученная для одной и той же обученной модели, была одинаковой при каждой оценке?(т.е. с учетом любых незначительных различий из-за арифметики с плавающей точкой)

Ниже приведен весь соответствующий код.Пример кода немного длиннее по сравнению со стандартным вопросом StackOverflow, но я хотел включить все соответствующие части кода.Извинения программистам Python за длину кода.Я начинающий программист на Python, и, возможно, я мог бы написать все это более кратким идиоматическим языком Python.

Код подготовки модели:

def prepare_kadjk_model(max_mini_batch_size,
                        max_conversation_length, timesteps, num_word_dimensions,
                        word_to_index, word_vec_dict,
                        num_tags):
    #Hyperparameters
    m = timesteps
    h = timesteps

    model = Sequential()

    dictionary_size = len(word_to_index) + 1

    embedding_weights = numpy.zeros((dictionary_size, num_word_dimensions))
    for word, index in word_to_index.items():
        embedding_weights[index, :] = word_vec_dict[word]

    # define inputs here
    embedding_layer = Embedding(dictionary_size, num_word_dimensions,
                                weights=[embedding_weights],
                                embeddings_regularizer=regularizers.l2(0.0001))
    model.add(TimeDistributed(embedding_layer,
                              input_shape=(max_conversation_length, timesteps)))

    model.add(TimeDistributed(Bidirectional(LSTM(m // 2, return_sequences=True,
                                            kernel_regularizer=regularizers.l2(0.0001)))))
    model.add(TimeDistributed(Dropout(0.2)))
    model.add(TimeDistributed(GlobalMaxPooling1D()))
    model.add(Bidirectional(LSTM(h // 2, return_sequences = True,
                                 kernel_regularizer=regularizers.l2(0.0001)), merge_mode='concat'))
    model.add(Dropout(0.2))
    crf = CRF(num_tags, sparse_target=False, kernel_regularizer=regularizers.l2(0.0001))
    model.add(crf)
    model.compile(optimizer, loss = crf_loss,
                  metrics=[crf_accuracy])
    return model

Функции подготовки пакета:

def form_mini_batches(dataset_x, max_mini_batch_size):
    num_conversations = len(dataset_x)

    # Form mini batches of equal-length conversations
    mini_batches = {}
    for i in range(num_conversations):
        num_utterances = len(dataset_x[i])
        if num_utterances in mini_batches:
            mini_batches[num_utterances].append( i )
        else:
            mini_batches[num_utterances] = [ i ]

    # Enforce max_batch_size on previously formed mini batches
    mini_batch_list = []
    for conversations in mini_batches.values():
        mini_batch_list += [conversations[x: x + max_mini_batch_size] for x in range(0, len(conversations), max_mini_batch_size)]

    return mini_batch_list


def kadjk_batch_generator(dataset_x, dataset_y, tag_indices,
                          mini_batch_list, max_conversation_length,
                          timesteps, num_word_dimensions, num_tags,
                          word_index_to_append, tag_index_to_append):
    num_mini_batches = len(mini_batch_list)

    # Shuffle the order of batches
    index_list = [x for x in range(num_mini_batches)]
    random.shuffle(index_list)

    k = -1
    while True:
        k = (k + 1) % len(index_list)
        index = index_list[k]
        conversation_indices = mini_batch_list[index]

        num_conversations = len(conversation_indices)
        batch_features = numpy.empty(shape = (num_conversations, max_conversation_length, timesteps),
                                     dtype = int)
        label_list = []

        for i in range(num_conversations):
            utterances = dataset_x[conversation_indices[i]]
            labels = copy.deepcopy(dataset_y[conversation_indices[i]])
            num_utterances = len(utterances)
            num_labels_to_append = max(0, max_conversation_length - len(labels))
            labels += [tag_index_to_append] * num_labels_to_append
            tags = to_categorical(labels, num_tags)
            del labels

            for j in range(num_utterances):
                utterance = copy.deepcopy(utterances[j])
                num_to_append = max(0, timesteps - len(utterance))
                if num_to_append > 0:
                    appendage = [word_index_to_append] * num_to_append
                    utterance += appendage

                batch_features[i][j] = utterance
                del utterance

            remaining_space = (max_conversation_length - num_utterances, timesteps)
            batch_features[i][num_utterances:] = numpy.ones(remaining_space) * word_index_to_append
            label_list.append(tags)

        batch_labels = numpy.array(label_list)
        del label_list

        yield batch_features, batch_labels

Функция обучения:

def train_kadjk(model, training, validation, num_epochs_to_train, tag_indices, max_mini_batch_size,
                max_conversation_length, timesteps, num_word_dimensions, num_tags,
                end_of_line_word_index, uninterpretable_label_index):
    training_mini_batch_list = form_mini_batches(training[0], max_mini_batch_size)
    validation_mini_batch_list = form_mini_batches(validation[0], max_mini_batch_size)

    num_training_steps = len(training_mini_batch_list)
    num_validation_steps = len(validation_mini_batch_list)

    early_stop = EarlyStopping(patience = 5)
    change_learning_rate = LearningRateScheduler(learning_rate_scheduler)

    model.fit_generator(kadjk_batch_generator(training[0], training[1], tag_indices,
                                              training_mini_batch_list, max_conversation_length,
                                              timesteps, num_word_dimensions, num_tags,
                                              end_of_line_word_index, uninterpretable_label_index),
                        steps_per_epoch = num_training_steps,
                        epochs = num_epochs_to_train,
                        validation_data = kadjk_batch_generator(validation[0], validation[1],
                                                                tag_indices,
                                                                validation_mini_batch_list, 
                                                                max_conversation_length, timesteps,
                                                                num_word_dimensions, num_tags,
                                                                end_of_line_word_index,
                                                                uninterpretable_label_index),
                        validation_steps = num_validation_steps,
                        callbacks = [early_stop, change_learning_rate])

Функция оценки:

def evaluate_kadjk(model, testing, tag_indices, max_mini_batch_size, max_conversation_length,
                   timesteps, num_word_dimensions, num_tags,
                   end_of_line_word_index, uninterpretable_label_index):
    testing_mini_batch_list = form_mini_batches(testing[0], max_mini_batch_size)
    num_testing_steps = len(testing_mini_batch_list)
    score = model.evaluate_generator(kadjk_batch_generator(testing[0], testing[1],
                                                           tag_indices,
                                                           testing_mini_batch_list, 
                                                           max_conversation_length, timesteps,
                                                           num_word_dimensions, num_tags,
                                                           end_of_line_word_index,
                                                           uninterpretable_label_index),
                                     steps = num_testing_steps)
    print("len(score):" + str(len(score)))
    print("score:" + str(score))

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

Ответы [ 2 ]

0 голосов
/ 09 мая 2019

Я углубился в проблемы Github keras и нашел возможную причину ошибки в этом комментарии.

По-видимому, подобно слоям нормализации партии, использование Dropout слоев вызывает изменения, как я описал в вопросе. Dropout слоев заставляют нейроны быть сброшенными во время тренировки. Таким образом, когда обучение модели завершено, присутствуют не все нейроны в первоначально скомпилированной модели.

Если веса модели сохраняются с помощью функции keras_contrib.save_load_utils.save_all_weights, то веса модели сохраняются. Однако после завершения этого процесса без сохранения окончательной конфигурации нейронов (а не только весов) окончательная конфигурация модели теряется. Как указано здесь , save_all_weights, то есть функция, которую я использовал для сохранения модели, не сохраняет конфигурацию самой модели.

Следовательно, если вы компилируете модель в другом процессе и загружаете веса, которые вы сохранили, используя keras_contrib.save_load_utils.load_all_weights, даже если вы тестируете модель с теми же данными, с которыми вы ее тестировали в предыдущем запуске, вновь скомпилированная модель имеет некоторые дополнительные нейроны, которые выпали во время обучения оригинальной модели. Это различие в конфигурации в сочетании с тем фактом, что они могут (и в этом случае ) инициализируются со случайными весами, заставляет оценку давать разный коэффициент точности при каждом запуске.

Решение, похоже, заключается в регистрации не только весов, но и всей конфигурации. Это можно просто сделать, используя метод save экземпляра модели вместо keras_contrib.save_load_utils.save_all_weights. Очевидно, что для загрузки всей модели обратно в другом процессе следует использовать keras.models.load_model вместо keras_contrib.save_load_utils.load_all_weights.

0 голосов
/ 17 апреля 2019

Существует множество причин, по которым это может происходить, каждая из которых опускается с тактики в большинстве моделей DL для рандомизации определенных аспектов процесса.Большинство входных подпрограмм будут включать в себя операцию shuffle, рандомизирующую порядок входных наборов как способ раннего обучения.Многие типы моделей зависят от того, как начальные веса каким-либо образом дифференцируются (чтобы обеспечить дифференцированное обучение персептронов), что обычно выполняется с использованием некоторой рандомизирующей функции.

Любое из этих обстоятельств приведет к различным результатам в обучении, особеннораннее обучение.В идеале все тренировочные заезды будут сходиться с очень небольшим диапазоном точности, но ранние результаты будут отличаться.Это не считается проблемой.Мы не можем сказать, является ли это вашей проблемой, так как вы не предоставили никакой трассировки выполнения, такой как сброс всех ваших начальных весов или проверка порядка ввода.

Если вам требуются воспроизводимые результаты, то вы будетехотите покопаться в вашем коде поддержки, определить места, где используется ГСЧ (генератор случайных чисел), и сделать одну из двух вещей:

  • заменить каждый вызов ГСЧ своей собственной статической функцией, или
  • форсировать случайное постоянное число seed до того, как будет сделан первый вызов.

В моей карьере мы обычно шли сустановка случайного семени, так как это легко комментировать, когда мы закончим, будучи фанатами контроля.:-) Нам пришлось копаться в коде несколько раз, так как разные уровни нашего кода иногда использовали бы более одного RNG;мы должны были заставить семя для каждого из них.В Python и большинстве других языков вызов прост, например,

random.seed(1)    # Sets 1 as the initial seed for the RNG.
...