简而言之 我的模型训练了1个周期——用于测试目的。然而,当多次评估时,每次运行evaluate_generator
方法时,即使使用相同训练数据,它也会产生不同的准确率。这是为什么呢?有没有什么方法可以在同一个模型上多次评估相同训练数据时获得相同的准确率?
我正在研究对话行为分类的语言学问题,我的模型基于这篇论文。使用keras
和keras_contrib
库提供的工具,我正在复制相同的模型,但我有一个关于为什么评估会产生不同准确率的问题。
为了参考,我训练了模型一个周期,然后使用keras_contrib
模块提供的save_load_utils
工具将训练好的模型保存到文件中。然而,每当我用这些权重运行模型时,这些权重是为一个周期训练的,我得到的准确率每次都不一样。我尝试了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): #超参数 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] # 定义输入 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) # 形成等长度对话的迷你批次 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 ] # 强制对之前形成的迷你批次实施最大批次大小 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_listdef 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) # 打乱批次顺序 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))
您可以访问这里以了解我正在进行的研究生论文项目的更完整视角,但我尽量提供了任何能够帮助的人所需的所有相关功能部分。
回答:
我深入研究了keras
的Github问题,并在这个评论中找到了可能的错误原因。
显然,类似于批量归一化层,使用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
。