keras - evaluate_generator 使用相同的训练数据产生不同的准确率

keras - evaluate_generator yielding different accuracy rates with same training data

TL;DR 我的模型训练了 1 个时期 - 用于测试目的。然而,当多次评估时,每次我 运行 evaluate_generator 方法 使用相同的训练数据 时,它都会产生不同的准确度。为什么会发生这种情况,有什么方法可以在同一模型上多次评估相同的训练数据时获得相同的准确率?


我正在研究对话行为分类的语言问题,我的模型基于 this 论文。使用 keraskeras_contrib 存储库提供的工具,我正在复制确切的模型,但我对为什么评估给出不同的准确率有疑问。

作为参考,我训练了单个 epoch 的模型,然后使用 keras_contrib 模块提供的实用程序 save_load_utils 将训练后的模型保存在文件中。但是,每当我 运行 具有 那些 权重的模型(针对单个时期进行训练)时,我都会得到不同的准确率。我试了5-10次,都在68%到74%之间,比较大。当我加载预训练(即 1 个时期)模型权重时,我期望获得相同的精度。 (即缺少浮点数的任何精度差异)但是,此速率的结果差异表明我可能做错了什么。

有谁知道为什么 model.evaluate_generator 方法每次使用相同的权重 运行 时生成的结果都如此不同,即使我使用相同的 1-epoch -训练模型的权重来评估它?有什么方法可以修复我的评估代码,以便每次评估时为同一训练模型获得的准确度都相同吗? (即考虑到浮点运算引起的任何微小差异)

下面是所有相关代码。与标准 Whosebug 问题相比,代码示例 稍长一些,但我想包括代码的所有相关部分。就代码的长度向 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))

您可以导航 here 以更全面地了解我正在从事的研究生论文项目,但我试图提供所有可以提供帮助的人所需的所有相关功能。

发生这种情况的原因有很多种,每一种都源于大多数 DL 模型中随机化过程的某些方面的策略。大多数输入例程将包括一个 shuffle 操作,随机化输入集的顺序作为平衡早期训练的一种方式。许多模型类型依赖于以某种方式区分的初始权重(以允许对感知器进行差异化训练),这通常是通过一些随机化函数完成的。

以上任何一种情况都会导致训练结果不同,尤其是早期训练。理想情况下,所有训练运行将 收敛 到一个非常小的准确度范围,但早期结果会有所不同。这不被认为是一个问题。我们无法判断这是否是您的问题,因为您没有提供任何执行跟踪,例如转储所有初始权重或检查输入顺序。

如果您需要可重现的结果,那么您需要深入研究您的支持代码,确定使用 RNG(随机数生成器)的位置,然后执行以下两项操作之一:

  • 用您自己的静态函数替换每个 RNG 调用,
  • 在第一次调用之前强制一个常数随机种子

在我的职业生涯中,我们通常会设置随机种子,因为当我们不再是控制狂时,很容易注释掉。 :-) 我们不得不多次深入研究代码,因为我们代码的不同层有时会使用多个 RNG;我们不得不为他们每个人强制种子。在 Python 和大多数其他语言中,调用很简单,例如

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

我深入研究了 keras 的 Github 问题,并在 this 评论中找到了错误的可能原因。

显然,与批量归一化层类似,使用 Dropout 层会导致我在问题中描述的变化。 Dropout 层导致神经元在训练过程中被丢弃。所以,当模型训练完成后,最初编译模型中的神经元并不是全部存在。

如果使用函数 keras_contrib.save_load_utils.save_all_weights 保存模型权重,则保存模型的权重。但是,一旦您在不保存最终神经元配置(不仅仅是权重)的情况下终止该过程,模型的最终配置就会丢失。如heresave_all_weights,这是我用来保存模型的功能,不保存模型本身的配置。

因此,如果您在不同的进程中编译模型,并加载使用 keras_contrib.save_load_utils.load_all_weights 保存的权重,即使您使用与之前 [=] 中测试的数据相同的数据测试模型35=],新编译的模型有一些额外的神经元在原始模型的训练过程中被丢弃。这种配置上的差异,加上它们可能(在本例中 )用随机权重初始化的事实,导致评估每次都给出不同的准确率 运行.

解决方案似乎不仅要记录权重,还要记录所有配置。这可以简单地通过使用模型实例的 save 方法而不是 keras_contrib.save_load_utils.save_all_weights 来完成。显然,要在不同的进程中加载​​整个模型,应该使用 keras.models.load_model 而不是 keras_contrib.save_load_utils.load_all_weights