Keras-tuning 找不到回调

Keras-tuning can't find callback

我正在使用 keras-tuner 以获得最适合我的模型的超参数集。 我可以针对随机数据集重现我的问题:

def generate_data(n_windows, n_timesteps):
    feature_vector_list = []
    label_list = []
    for i in range(10):
        x = tf.random.normal((n_windows, n_timesteps))
        y = tf.random.normal((n_windows, n_timesteps))
        z = tf.random.normal((n_windows, n_timesteps))
        feature_vector = [x, y, z]
        choices = [np.array([1, 0, 0, 0]), np.array([0, 1, 0, 0]),
                   np.array([0, 0, 1, 0]), np.array([0, 0, 0, 1])]
        labels = np.array([random.choice(choices) for i in range(n_windows)])
        feature_vector_list.append(feature_vector)
        label_list.append(labels)
    return feature_vector_list, label_list


def custom_generator(feat_vector_list, label_list):
    assert len(feat_vector_list) == len(label_list), \
        "Number of feature vectors inconsistent with the number of labels"
    counter = 0
    while True:
        feat_vec = feat_vector_list[counter]
        list_labels = label_list[counter]
        counter = (counter + 1) % len(feat_vector_list)
        yield feat_vec, list_labels

这是模型:

def model_builder(hp):

    n_timesteps, n_features, n_outputs = 188, 1, 4

    hp_units = hp.Int("units", min_value=50, max_value=500, step=50)
    hp_filters = hp.Int("filters", 4, 32, step=4, default=8)
    hp_kernel_size = hp.Int("kernel_size", 3, 50, step=1)
    hp_pool_size = hp.Int("pool_size", 2, 8, step=1)
    hp_dropout = hp.Float("dropout", 0.1, 0.5, step=0.1)

    # head 1
    input1 = Input(shape=(n_timesteps, n_features))
    conv1 = Conv1D(filters=hp_filters,
                   kernel_size=hp_kernel_size,
                   activation='relu')(input1)
    drop1 = Dropout(hp_dropout)(conv1)
    if hp.Choice("pooling", ["max", "avg"]) == "max":
        pool1 = MaxPooling1D(pool_size=hp_pool_size)(drop1)
    else:
        pool1 = AveragePooling1D(pool_size=hp_pool_size)(drop1)
    flatten1 = Flatten()(pool1)
    # head 2
    input2 = Input(shape=(n_timesteps, n_features))
    conv2 = Conv1D(filters=hp_filters,
                   kernel_size=hp_kernel_size,
                   activation='relu')(input2)
    drop2 = Dropout(hp_dropout)(conv2)
    if hp.Choice("pooling", ["max", "avg"]) == "max":
        pool2 = MaxPooling1D(pool_size=hp_pool_size)(drop2)
    else:
        pool2 = AveragePooling1D(pool_size=hp_pool_size)(drop2)
    flatten2 = Flatten()(pool2)
    # head 3
    input3 = Input(shape=(n_timesteps, n_features))
    conv3 = Conv1D(filters=hp_filters,
                   kernel_size=hp_kernel_size,
                   activation='relu')(input3)
    drop3 = Dropout(hp_dropout)(conv3)
    if hp.Choice("pooling", ["max", "avg"]) == "max":
        pool3 = MaxPooling1D(pool_size=hp_pool_size)(drop3)
    else:
        pool3 = AveragePooling1D(pool_size=hp_pool_size)(drop3)
    flatten3 = Flatten()(pool3)
    # Merge
    merged = concatenate([flatten1, flatten2, flatten3])
    # hidden layers
    dense1 = Dense(hp_units, activation='relu')(merged)
    outputs = Dense(n_outputs, activation='softmax')(dense1)
    model = Model(inputs=[input1, input2, input3], outputs=outputs)
    model.compile(loss='categorical_crossentropy',
                  optimizer=tf.keras.optimizers.Adam(learning_rate=hp.Float("learning_rate",
                                                                            0.01,
                                                                            0.1,
                                                                            step=0.2)),
                  metrics=['accuracy'])
    return model

这是训练脚本:

if __name__ == '__main__':
    N_WINDOWS = 100
    N_TIMESTEPS = 188
    x_train, y_train = generate_data(N_WINDOWS, N_TIMESTEPS)
    x_val, y_val = generate_data(75, N_TIMESTEPS)
    training_generator = custom_generator(x_train, y_train)
    validation_generator = custom_generator(x_val, y_val)
    tuner = kt.Hyperband(
        model_builder,
        objective="val_accuracy",
        max_epochs=70,
        factor=3,
        directory="Results",
        project_name="cnn_tunning"
    )
    stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss',
                                                  patience=5,
                                                  min_delta=0.002)

    print("********** Begin search **********")
    tuner.search(
        training_generator,
        steps_per_epoch=N_WINDOWS,
        validation_data=validation_generator,
        validation_steps=75,
        callbacks=[stop_early],
    )
    # grab the best hyperparameters
    print("********** End of search **********")

现在我发现,在 hyperband 开始使用相当数量的迭代并且我设置的回调应该开始起作用后,我得到了这个错误:

W tensorflow/core/framework/op_kernel.cc:1733] INVALID_ARGUMENT: ValueError: Could not find callback with key=pyfunc_530 in the registry.
Traceback (most recent call last):

  File "/home/diogomota/.cache/pypoetry/virtualenvs/WUAle-Z1-py3.7/lib/python3.7/site-packages/tensorflow/python/ops/script_ops.py", line 259, in __call__
    raise ValueError(f"Could not find callback with key={token} in the "

ValueError: Could not find callback with key=pyfunc_530 in the registry.


W tensorflow/core/kernels/data/generator_dataset_op.cc:107] Error occurred when finalizing GeneratorDataset iterator: INVALID_ARGUMENT: ValueError: Could not find callback with key=pyfunc_530 in the registry.
Traceback (most recent call last):

  File "/home/diogomota/.cache/pypoetry/virtualenvs/WUAle-Z1-py3.7/lib/python3.7/site-packages/tensorflow/python/ops/script_ops.py", line 259, in __call__
    raise ValueError(f"Could not find callback with key={token} in the "

ValueError: Could not find callback with key=pyfunc_530 in the registry.

然而它只是进行到下一次试验,所以我不确定发生了什么,有人可以解释为什么它找不到回调吗?

我正在使用 tensorflow 2.8keras-tuner 1.1.2

我在网上只找到一个地方有类似的问题,但没有提供解决方案:https://issuemode.com/issues/tensorflow/tensorflow/72982126

编辑:

  1. 提供了完整的错误信息
  2. 进一步调试后,问题完全来自使用生成器作为 .search() 的输入。我不知道这是一个问题的原因。使用 .fit() 进行定期训练没有任何问题
  3. 为再现性添加了数据集生成代码

在调用 _internal_py_func 时查看 source code of the error, and reviewing the similar error provided, it looks like this issue is not due to the actual model callback (tf.keras.callbacks.EarlyStoppingCallback). The error occurs in the FuncRegistry class, which is a helper that maintains a map of unique tokens to registered python functions, and it looks like in both cases, the token (pyfunc_XXX) does not map to a function. Functions are inserted here,同时包装 Python 函数(作为急切的 Tensorflow 操作执行)或计算急切的梯度功能。函数令牌的全局注册表(FuncRegistry 对象)提供给 initialize_py_trampoline,它通过 PyBind 绑定到 C++ 中的 InitializePyTrampoline 函数,因此令牌对函数映射的引用是在 C++ 运行 的时候也是如此。

在那个级别,从日志中追踪到 C++ source code 的错误,它发生在内部 class Iterator 的析构函数中,GeneratorDatasetOp 的一个字段。当对象超出范围或明确删除时调用析构函数 - 这意味着它会在生成器完成其任务时调用,这听起来可能与您在发生错误时所做的观察一致。

总而言之,如果没有数据集就无法进一步探测,听起来自定义生成器可能存在问题。我建议尝试在没有 keras-tuner 和相同生成器实现的情况下进行训练,以确定问题是否与其他观察链接一致,因为他们没有使用 keras-tuner 但他们使用的是自定义生成器。如果错误仍然存​​在,也值得评估以前的版本(例如 Tensorflow 2.7 或更低版本)是否与生成器存在相同的问题。如果它一直失败,可能需要向 Tensorflow Github 存储库提交实际问题,因为它实际上可能是一个需要进一步探索的核心错误。

此外,如果您不需要使用生成器(例如,数据可以放入内存),我建议您尝试直接提供数据集(使用 numpy 列表调用 fit数组或 numpy 数组而不是提供生成器函数),因为该路径不会触及当前失败的 DatasetGenerator 代码,并且不应影响您的超参数搜索。

更新

感谢您提供更多信息以及复制生成器函数的代码。我能够在 CPU 上的 Python 3.7/Tensorflow 2.8/keras-tuner 1.1.2 中重现该问题。如果您检查 _funcs(全局注册表中的字段,该字段维护对函数的弱引用的标记字典),它实际上是空的。经过进一步检查,看起来每次开始新的试验时,_funcs 都会被清除并重新填充,如果 keras-tuner 每次都在创建一个新的图形(模型),这是一致的(尽管相同的注册表 FuncRegistry 贯穿始终)。

如果省略 EarlyStopping 回调,则不会发生错误,因此您说错误与回调有关是正确的。它也显示错误是 non-deterministic,因为试验和出现的时代因 运行.

而异

随着错误原因的缩小,另一个人从文档中遇到 the same issue, and their observations were the cause of the error being related to explicitly setting the min_delta parameter in the callback, as you are doing as well, which no other keras-tuner example does (e.g; in this example and this example,他们只有 monitor and/or patience set).

EarlyStopping回调中设置min_delta的影响,默认设置为0,可见here。具体来说,当 min_delta 设置为某个 non-zero 值时,_is_improvement 计算为 True 的频率会降低:

    if self._is_improvement(current, self.best):
      self.best = current
      self.best_epoch = epoch
      if self.restore_best_weights:
        self.best_weights = self.model.get_weights()
      # Only restart wait if we beat both the baseline and our previous best.
      if self.baseline is None or self._is_improvement(current, self.baseline):
        self.wait = 0

  def _is_improvement(self, monitor_value, reference_value):
    return self.monitor_op(monitor_value - self.min_delta, reference_value)

请注意,在您的情况下,self.monitor_opnp.less,因为您正在监控的指标是 val_loss:

      if (self.monitor.endswith('acc') or self.monitor.endswith('accuracy') or
          self.monitor.endswith('auc')):
        self.monitor_op = np.greater
      else:
        self.monitor_op = np.less

self._is_improvement 的评估频率较低时,patience 标准 (self.wait >= self.patience) 将更频繁地得到满足,因为 self.wait 重置的频率较低(因为 self.baseline 默认为 None):

if self.wait >= self.patience and epoch > 0:
      self.stopped_epoch = epoch
      self.model.stop_training = True
      if self.restore_best_weights and self.best_weights is not None:
        if self.verbose > 0:
          io_utils.print_msg(
              'Restoring model weights from the end of the best epoch: '
              f'{self.best_epoch + 1}.')
        self.model.set_weights(self.best_weights)

随着这个范围的缩小,它似乎与模型更频繁地停止训练有关,并且当 keras-tuner 正在 运行 进行试验时,图中不再存在对操作的引用.

简单来说,这似乎是 keras-tuner 中的一个错误,需要提交,我 here 提交了此回复中的所有详细信息。为了在此期间继续进行,如果 min_delta 条件不是必需的,我建议从 EarlyStopping 中删除该参数并再次 运行ning 脚本以查看您是否仍然点击问题。

更新 2

感谢您提供更多信息。如果不使用生成器,我能够重现成功的 运行,并且看起来 other case I referenced 也在使用生成器与 EarlyStoppingmin_delta 结合使用已提供。

经过进一步检查,在注册表中找不到的函数是 finalize_py_func,因为在清除 _funcs 之前导致错误映射到 finalize_py_func 的每个标记中。 finalize_py_funcinner function wrapped by script_ops.numpy_function, which wraps a python function to be used as Tensorflow op. The function where finalize_py_func is defined and returned as Tensorflow op, finalize_fn, is supplied when constructing a generator, as can be seen here. Looking at the documentation of the finalize function in the generator here,它表示“在销毁此数据集上的 C++ 迭代器之前,将根据 init_func` 的结果立即调用的 TensorFlow 函数。”。

总的来说,错误与生成器有关,而不是min_delta参数。虽然设置 min_delta 可以加快错误发生的速度,但如果 patience 降低到足以强制提前停止回调更频繁地触发,即使省略 min_delta 也会发生错误。使用你的例子,如果你t patience 到 1 并删除 min_delta,错误很快出现。

我修改了 github issue 以包含该细节。看起来这个错误在 Tensorflow 2.7 中仍然存在,但是如果你降级到 Tensorflow 2.6(和 Keras 2.6),这个错误就不会出现了。如果可以降级,那么在问题得到解决之前,这可能是继续进行的最佳选择。