【问题标题】:Create keras callback to save model predictions and targets for each batch during training创建 keras 回调以在训练期间保存每个批次的模型预测和目标
【发布时间】:2018-04-15 04:51:14
【问题描述】:

我正在 Keras(tensorflow 后端)中构建一个简单的序列模型。在训练期间,我想检查单个训练批次和模型预测。因此,我正在尝试创建一个自定义Callback,以保存每个训练批次的模型预测和目标。但是,模型不是使用当前批次进行预测,而是使用整个训练数据。

如何仅将当前训练批次移交给Callback

我如何访问Callback 保存在 self.predhis 和 self.targets 中的批次和目标?

我目前的版本如下:

callback_list = [prediction_history((self.x_train, self.y_train))]

self.model.fit(self.x_train, self.y_train, batch_size=self.batch_size, epochs=self.n_epochs, validation_data=(self.x_val, self.y_val), callbacks=callback_list)

class prediction_history(keras.callbacks.Callback):
    def __init__(self, train_data):
        self.train_data = train_data
        self.predhis = []
        self.targets = []

    def on_batch_end(self, epoch, logs={}):
        x_train, y_train = self.train_data
        self.targets.append(y_train)
        prediction = self.model.predict(x_train)
        self.predhis.append(prediction)
        tf.logging.info("Prediction shape: {}".format(prediction.shape))
        tf.logging.info("Targets shape: {}".format(y_train.shape))

【问题讨论】:

    标签: tensorflow callback keras


    【解决方案1】:

    注意:此答案已过时,仅适用于 TF1。查看@bers 的answer 以获得在TF2 上测试的解决方案。


    模型编译后,y_true 的占位符张量在model.targets 中,y_predmodel.outputs 中。

    要在每批中保存这些占位符的值,您可以:

    1. 首先将这些张量的值复制到变量中。
    2. 评估on_batch_end 中的这些变量,并存储结果数组。

    现在第 1 步有点复杂,因为您必须将 tf.assign 操作添加到训练函数 model.train_function。使用当前的 Keras API,这可以通过在构造训练函数时向 K.function() 提供 fetches 参数来完成。

    model._make_train_function(),有一行:

    self.train_function = K.function(inputs,
                                     [self.total_loss] + self.metrics_tensors,
                                     updates=updates,
                                     name='train_function',
                                     **self._function_kwargs)
    

    包含tf.assign 操作的fetches 参数可以通过model._function_kwargs 提供(仅在Keras 2.1.0 之后有效)。

    举个例子:

    from keras.layers import Dense
    from keras.models import Sequential
    from keras.callbacks import Callback
    from keras import backend as K
    import tensorflow as tf
    import numpy as np
    
    class CollectOutputAndTarget(Callback):
        def __init__(self):
            super(CollectOutputAndTarget, self).__init__()
            self.targets = []  # collect y_true batches
            self.outputs = []  # collect y_pred batches
    
            # the shape of these 2 variables will change according to batch shape
            # to handle the "last batch", specify `validate_shape=False`
            self.var_y_true = tf.Variable(0., validate_shape=False)
            self.var_y_pred = tf.Variable(0., validate_shape=False)
    
        def on_batch_end(self, batch, logs=None):
            # evaluate the variables and save them into lists
            self.targets.append(K.eval(self.var_y_true))
            self.outputs.append(K.eval(self.var_y_pred))
    
    # build a simple model
    # have to compile first for model.targets and model.outputs to be prepared
    model = Sequential([Dense(5, input_shape=(10,))])
    model.compile(loss='mse', optimizer='adam')
    
    # initialize the variables and the `tf.assign` ops
    cbk = CollectOutputAndTarget()
    fetches = [tf.assign(cbk.var_y_true, model.targets[0], validate_shape=False),
               tf.assign(cbk.var_y_pred, model.outputs[0], validate_shape=False)]
    model._function_kwargs = {'fetches': fetches}  # use `model._function_kwargs` if using `Model` instead of `Sequential`
    
    # fit the model and check results
    X = np.random.rand(10, 10)
    Y = np.random.rand(10, 5)
    model.fit(X, Y, batch_size=8, callbacks=[cbk])
    

    除非样本数可以除以批次大小,否则最终批次的大小将与其他批次不同。所以K.variable()K.update()不能在这种情况下使用。您必须改用tf.Variable(..., validate_shape=False)tf.assign(..., validate_shape=False)


    为了验证保存数组的正确性,可以在training.py中添加一行,打印出打乱后的索引数组:

    if shuffle == 'batch':
        index_array = _batch_shuffle(index_array, batch_size)
    elif shuffle:
        np.random.shuffle(index_array)
    
    print('Index array:', repr(index_array))  # Add this line
    
    batches = _make_batches(num_train_samples, batch_size)
    

    在拟合过程中应该打印出洗牌的索引数组:

    纪元 1/1 索引数组:array([8, 9, 3, 5, 4, 7, 1, 0, 6, 2]) 10/10 [===============================] - 0s 23ms/步 - 损失:0.5670

    您可以检查cbk.targets是否与Y[index_array]相同:

    index_array = np.array([8, 9, 3, 5, 4, 7, 1, 0, 6, 2])
    print(Y[index_array])
    [[ 0.75325592  0.64857277  0.1926653   0.7642865   0.38901153]
     [ 0.77567689  0.13573623  0.4902501   0.42897559  0.55825652]
     [ 0.33760938  0.68195038  0.12303088  0.83509441  0.20991668]
     [ 0.98367778  0.61325065  0.28973401  0.28734073  0.93399794]
     [ 0.26097574  0.88219054  0.87951941  0.64887846  0.41996446]
     [ 0.97794604  0.91307569  0.93816428  0.2125808   0.94381495]
     [ 0.74813435  0.08036688  0.38094272  0.83178364  0.16713736]
     [ 0.52609421  0.39218962  0.21022047  0.58569125  0.08012982]
     [ 0.61276627  0.20679494  0.24124858  0.01262245  0.0994412 ]
     [ 0.6026137   0.25620512  0.7398164   0.52558182  0.09955769]]
    
    print(cbk.targets)
    [array([[ 0.7532559 ,  0.64857274,  0.19266529,  0.76428652,  0.38901153],
            [ 0.77567691,  0.13573623,  0.49025011,  0.42897558,  0.55825651],
            [ 0.33760938,  0.68195039,  0.12303089,  0.83509439,  0.20991668],
            [ 0.9836778 ,  0.61325067,  0.28973401,  0.28734073,  0.93399793],
            [ 0.26097575,  0.88219053,  0.8795194 ,  0.64887846,  0.41996446],
            [ 0.97794604,  0.91307569,  0.93816429,  0.2125808 ,  0.94381493],
            [ 0.74813437,  0.08036689,  0.38094273,  0.83178365,  0.16713737],
            [ 0.5260942 ,  0.39218962,  0.21022047,  0.58569127,  0.08012982]], dtype=float32),
     array([[ 0.61276627,  0.20679495,  0.24124858,  0.01262245,  0.0994412 ],
            [ 0.60261369,  0.25620511,  0.73981643,  0.52558184,  0.09955769]], dtype=float32)]
    

    如您所见,cbk.targets 中有两批(一个“全批”大小为 8,最后一批大小为 2),行顺序与Y[index_array] 相同。

    【讨论】:

    • 但是这些真的是训练时内部使用的目标和训练批次吗?像这样使用它时,y_train 批处理的形状为(20,)。然而,当使用 Keras 的 model.fit() 函数并查看精度等指标时,y_true 的形状为 (TensorShape([Dimension(None), Dimension(None)])
    • 你在比较两个不同的东西。 y_train 是一个 numpy 数组,但 y_true 是一个 TF 占位符。在模型拟合期间,numpy 数组的值被输入到y_true
    • 但是如果y_train 被输入到占位符中,它们的尺寸应该一致
    • 他们会的。您可能以错误的方式进行测试。请参阅我的编辑以测试值是否相等。
    • 对于那些遇到与我上面的评论相同的问题的人,你想在加载模型之后设置model.train_function = None,在设置model._function_kwargs = {'fetches': fetches}之后,但在model.fit()之前,因为model._function_kwargs值不会保存在检查点中。 model.fit() 如果model.train_function = None,则“重新加载”此内容。更多详情,请查看training.py中的_make_train_function函数
    【解决方案2】:

    长时间编辑(几乎是一个新答案),原因如下:

    • Yu-Yang 的 2017 answer 依赖于私有的 _make_train_function_function_kwargs API,它们仅在 TF1 中工作(并且可能在 TF1 兼容性下,即所谓的非急切模式)。
    • 同样,Binyan Hu 的 2020 answer 依赖于_make_test_function,在 TF2 中默认不工作(同样需要 non-eager 模式)。
    • 我自己的 2020 年 1 月 answer,它已经受制于几个必需的配置设置,似乎已停止使用(或之前)TF 2.5,并且我无法使 model.inputsmodel.outputs 正常工作更长。
    • 最后,这个答案的早期版本需要潜在的昂贵模型评估来获得每个批次的预测。类似的解决方案to obtain activation histograms 甚至会在重复训练不同模型时导致 OOM 问题。

    因此,我着手寻找一种方法来获取所有可能的数量(输入、目标、预测、激活),批量,而不使用任何私有 API。目的是能够在预期数量上调用.numpy(),因此 Keras 回调可以运行普通 Python 代码以简化调试(我想这就是这个问题的主要内容 - 为了获得最大性能,人们可能会尝试集成为无论如何,在 TensorFlow 的图形操作中进行尽可能多的计算)。

    这是所有解决方案的通用基础模型:

    """Demonstrate batch data access."""
    import tensorflow as tf
    from tensorflow import keras
    
    
    class DataCallback(keras.callbacks.Callback):
        """This class is where all implementations differ."""
    
    
    def tf_nan(dtype):
        """Create NaN variable of proper dtype and variable shape for assign()."""
        return tf.Variable(float("nan"), dtype=dtype, shape=tf.TensorShape(None))
    
    
    def main():
        """Run main."""
        model = keras.Sequential([keras.layers.Dense(1, input_shape=(2,))])
    
        callback = DataCallback()
    
        model.compile(loss="mse", optimizer="adam")
        model.fit(
            x=tf.transpose(tf.range(7.0) + [[0.2], [0.4]]),
            y=tf.transpose(tf.range(7.0) + 10 + [[0.5]]),
            validation_data=(
                tf.transpose(tf.range(11.0) + 30 + [[0.6], [0.7]]),
                tf.transpose(tf.range(11.0) + 40 + [[0.9]]),
            ),
            shuffle=False,
            batch_size=3,
            epochs=2,
            verbose=0,
            callbacks=[callback],
        )
        model.save("tmp.tf")
    
    
    if __name__ == "__main__":
        main()
    

    以下三个 sn-ps 分别展示了一种可能的解决方案,每个都有自己的优缺点。核心技巧始终相同:分配tf.Variable 并使用tf.Variable.assign 将预期数量从一些以图形模式运行的Keras 代码导出到回调中。这些方法在回调初始化和(在一种情况下)模型编译方面略有不同,最重要的是它们可以访问的数量,这就是我在每个 sn-p 上方总结它们的原因。


    自定义指标

    使用自定义(假)指标(类似于我 2020 年 1 月的回答),虽然我们似乎无法再访问 model.inputsmodel.outputs(并且 model.(_)targets 甚至不再存在),但我们 可以访问代表模型目标和输出的y_truey_pred

    [ ] Inputs/Samples (x)
    [ ] Weights (w)
    [+] Targets/Labels (y_true)
    [+] Outputs/Predictions (y_pred)
    [ ] All layers (or only final input/output layers)
    
    """Demonstrate batch data access using a custom metric."""
    import tensorflow as tf
    from tensorflow import keras
    
    
    class DataCallback(keras.callbacks.Callback):  # diff
        """Callback to operate on batch data from metric."""
    
        def __init__(self):
            """Offer a metric to access batch data."""
            super().__init__()
    
            self.y_true = None
            self.y_pred = None
    
        def set_model(self, model):
            """Initialize variables when model is set."""
            self.y_true = tf_nan(model.output.dtype)
            self.y_pred = tf_nan(model.output.dtype)
    
        def metric(self, y_true, y_pred):
            """Fake metric."""
            self.y_true.assign(y_true)
            self.y_pred.assign(y_pred)
    
            return 0
    
        def on_train_batch_end(self, _batch, _logs=None):
            """See keras.callbacks.Callback.on_train_batch_end."""
            print("y_true =", self.y_true.numpy())
            print("y_pred =", self.y_pred.numpy())
    
        def on_train_end(self, _logs=None):
            """Clean up."""
            del self.y_true, self.y_pred
    
    
    def tf_nan(dtype):
        """Create NaN variable of proper dtype and variable shape for assign()."""
        return tf.Variable(float("nan"), dtype=dtype, shape=tf.TensorShape(None))
    
    
    def main():
        """Run main."""
        model = keras.Sequential([keras.layers.Dense(1, input_shape=(2,))])
    
        callback = DataCallback()
    
        model.compile(loss="mse", optimizer="adam", metrics=[callback.metric])  # diff
        model.fit(
            x=tf.transpose(tf.range(7.0) + [[0.2], [0.4]]),
            y=tf.transpose(tf.range(7.0) + 10 + [[0.5]]),
            validation_data=(
                tf.transpose(tf.range(11.0) + 30 + [[0.6], [0.7]]),
                tf.transpose(tf.range(11.0) + 40 + [[0.9]]),
            ),
            shuffle=False,
            batch_size=3,
            epochs=2,
            verbose=0,
            callbacks=[callback],
        )
        model.save("tmp.tf")
    
    
    if __name__ == "__main__":
        main()
    

    自定义训练步骤

    自定义训练步骤是我在此答案的早期版本中使用的。这个想法原则上仍然有效,但y_pred 可能会很昂贵,如果需要,使用自定义指标(见上文)可能是有意义的。

    [+] Inputs/Samples (x)
    [+] Weights (w)
    [+] Targets/Labels (y_true)
    [~] Outputs/Predictions (y_pred) [expensive!]
    [ ] All layers (or only final input/output layers)
    
    """Demonstrate batch data access using a custom training step."""
    import tensorflow as tf
    from tensorflow import keras
    
    
    class DataCallback(keras.callbacks.Callback):  # diff
        """Callback to operate on batch data from training step."""
    
        def __init__(self):
            """Initialize tf.Variables."""
            super().__init__()
    
            self.x = None
            self.w = None
            self.y_true = None
            self.y_pred = None
    
        def set_model(self, model):
            """Wrap the model.train_step function to access training batch data."""
            self.x = tf_nan(model.input.dtype)
            # pylint:disable=protected-access (replace by proper dtype if you know it)
            if model.compiled_loss._user_loss_weights is not None:
                self.w = tf_nan(model.compiled_loss._user_loss_weights.dtype)
            self.y_true = tf_nan(model.output.dtype)
            self.y_pred = tf_nan(model.output.dtype)
    
            model_train_step = model.train_step
    
            def outer_train_step(data):
                # https://github.com/keras-team/keras/blob/v2.7.0/keras/engine/training.py
                x, y_true, w = keras.utils.unpack_x_y_sample_weight(data)
    
                self.x.assign(x)
                if w is not None:
                    self.w.assign(w)
                self.y_true.assign(y_true)
    
                result = model_train_step(data)
    
                y_pred = model(x)
                self.y_pred.assign(y_pred)
    
                return result
    
            model.train_step = outer_train_step
    
        def on_train_batch_end(self, _batch, _logs=None):
            """See keras.callbacks.Callback.on_train_batch_end."""
            print("x =", self.x.numpy())
            if self.w is not None:
                print("w =", self.w.numpy())
            print("y_true =", self.y_true.numpy())
            print("y_pred =", self.y_pred.numpy())
    
        def on_train_end(self, _logs=None):
            """Clean up."""
            del self.x, self.w, self.y_true, self.y_pred
    
    
    def tf_nan(dtype):
        """Create NaN variable of proper dtype and variable shape for assign()."""
        return tf.Variable(float("nan"), dtype=dtype, shape=tf.TensorShape(None))
    
    
    def main():
        """Run main."""
        model = keras.Sequential([keras.layers.Dense(1, input_shape=(2,))])
    
        callback = DataCallback()
    
        model.compile(loss="mse", optimizer="adam")
        model.fit(
            x=tf.transpose(tf.range(7.0) + [[0.2], [0.4]]),
            y=tf.transpose(tf.range(7.0) + 10 + [[0.5]]),
            validation_data=(
                tf.transpose(tf.range(11.0) + 30 + [[0.6], [0.7]]),
                tf.transpose(tf.range(11.0) + 40 + [[0.9]]),
            ),
            shuffle=False,
            batch_size=3,
            epochs=2,
            verbose=0,
            callbacks=[callback],
        )
        model.save("tmp.tf")
    
    
    if __name__ == "__main__":
        main()
    

    自定义层调用

    自定义层调用是访问每个层的输入和输出的一种超级灵活的方式。回调处理层列表的call 函数的修补。虽然我们无法访问权重和目标(因为这些数量在单个层级别上没有意义),但它允许我们访问单个层激活,这对于诸如How does one log activations using `tf.keras.callbacks.TensorBoard`? 之类的问题非常方便。

    [+] Inputs/Samples (x)
    [ ] Weights (w)
    [ ] Targets/Labels (y_true)
    [+] Outputs/Predictions (y_pred)
    [+] All layers (or only final input/output layers)
    
    """Demonstrate batch data access using custom layer calls."""
    import tensorflow as tf
    from tensorflow import keras
    
    
    class DataCallback(keras.callbacks.Callback):  # diff
        """Callback to operate on batch data from selected (to be wrapped) layers."""
    
        def __init__(self, layers):
            """Wrap the calls of an iterable of model layers to access layer batch data."""
            super().__init__()
    
            self.data = {}
            self.inner_calls = {}
            self.outer_calls = {}
    
            for layer in layers:
                self.data[layer] = {
                    "inputs": tf_nan(layer.input.dtype),
                    "outputs": tf_nan(layer.output.dtype),
                }
    
                self.inner_calls[layer] = layer.call
    
                def outer_call(inputs, layer=layer, layer_call=layer.call):
                    self.data[layer]["inputs"].assign(inputs)
                    outputs = layer_call(inputs)
                    self.data[layer]["outputs"].assign(outputs)
                    return outputs
    
                self.outer_calls[layer] = outer_call
    
        def on_train_batch_begin(self, _epoch, _logs=None):
            """Wrap layer calls during each batch."""
            for layer, call in self.outer_calls.items():
                layer.call = call
    
        def on_train_batch_end(self, _epoch, _logs=None):
            """Restore original layer calls for ModelCheckpoint, model.save, ..."""
            for layer, call in self.inner_calls.items():
                layer.call = call
    
            for layer, data in self.data.items():
                print("Layer =", layer)
                print("Inputs =", data["inputs"].numpy())
                print("Outputs =", data["outputs"].numpy())
    
    
    def tf_nan(dtype):
        """Create NaN variable of proper dtype and variable shape for assign()."""
        return tf.Variable(float("nan"), dtype=dtype, shape=tf.TensorShape(None))
    
    
    def main():
        """Run main."""
        model = keras.Sequential([keras.layers.Dense(1, input_shape=(2,))])
    
        callback = DataCallback(model.layers)  # diff
    
        model.compile(loss="mse", optimizer="adam")
        model.fit(
            x=tf.transpose(tf.range(7.0) + [[0.2], [0.4]]),
            y=tf.transpose(tf.range(7.0) + 10 + [[0.5]]),
            validation_data=(
                tf.transpose(tf.range(11.0) + 30 + [[0.6], [0.7]]),
                tf.transpose(tf.range(11.0) + 40 + [[0.9]]),
            ),
            shuffle=False,
            batch_size=3,
            epochs=2,
            verbose=0,
            callbacks=[callback],
        )
        model.save("tmp.tf")
    
    
    if __name__ == "__main__":
        main()
    

    什么时候使用和打开待办事项

    我认为每个解决方案上面的 sn-ps 很好地总结了每种方法的能力。一般来说,

    • 自定义训练步骤将是访问模型输入的理想选择,例如批量数据集生成器、改组效果等;
    • 自定义层调用是访问模型中间层的理想选择;和
    • 自定义指标是访问模型输出的理想选择。

    我相当肯定(但没有尝试过)可以结合所有方法来同时访问所有批次数量。除了训练模式,我没有测试过任何东西——每种方法在测试或预测模式中的有用性方面都有进一步的优缺点。最后,我假设,但也没有测试,它们应该只是tf.keraskeras 之间的微小差异。在 TF2.8.rc1 和 Keras 2.8.0 上测试了这段代码,将tf.keras 代码移回了keras pip 包,并且没有使用任何私有 API,我相信这个假设是合理的。

    如果这种方法可以扩展到再次访问model.inputsmodel.outputs,那就太好了。目前,我遇到了这样的错误:

    TypeError: 您将 KerasTensor(...)(一个中间 Keras 符号输入/输出)传递给不允许注册自定义调度程序的 TF API,例如 tf.condtf.function、渐变磁带或 @ 987654358@。 Keras 函数模型构建仅支持 确实 支持调度的 TF API 调用,例如 tf.math.addtf.reshape。不能在符号 Keras 输入/输出上直接调用其他 API。您可以通过将操作放入自定义 Keras 层 call 并在此符号输入/输出上调用该层来解决此限制。


    上一个答案

    从 TF 2.2 开始,您可以使用自定义训练步骤而不是回调来实现您想要的。这是一个使用tensorflow==2.2.0rc1 的演示,使用继承来改进keras.Sequential 模型。在性能方面,这并不理想,因为预测进行了两次,一次在 self(x, training=True) 中,一次在 super().train_step(data) 中。但你明白了。

    这可以在 Eager 模式下工作,并且不使用私有 API,因此它应该非常稳定。一个警告是你必须使用tf.keras(独立的keras不支持Model.train_step),但我觉得独立的keras无论如何都越来越被弃用了。 (其实tf.keras在TF2.8中迁移到keras。)

    """Demonstrate access to Keras batch tensors in a tf.keras custom training step."""
    import numpy as np
    from tensorflow import keras
    from tensorflow.keras import backend as K
    from tensorflow.python.keras.engine import data_adapter
    
    in_shape = (2,)
    out_shape = (1,)
    batch_size = 3
    n_samples = 7
    
    
    class SequentialWithPrint(keras.Sequential):
        def train_step(self, original_data):
            # Basically copied one-to-one from https://git.io/JvDTv
            data = data_adapter.expand_1d(original_data)
            x, y_true, w = data_adapter.unpack_x_y_sample_weight(data)
            y_pred = self(x, training=True)
    
            # this is pretty much like on_train_batch_begin
            K.print_tensor(w, "Sample weight (w) =")
            K.print_tensor(x, "Batch input (x) =")
            K.print_tensor(y_true, "Batch output (y_true) =")
            K.print_tensor(y_pred, "Prediction (y_pred) =")
    
            result = super().train_step(original_data)
    
            # add anything here for on_train_batch_end-like behavior
    
            return result
    
    
    # Model
    model = SequentialWithPrint([keras.layers.Dense(out_shape[0], input_shape=in_shape)])
    model.compile(loss="mse", optimizer="adam")
    
    # Example data
    X = np.random.rand(n_samples, *in_shape)
    Y = np.random.rand(n_samples, *out_shape)
    
    model.fit(X, Y, batch_size=batch_size)
    print("X: ", X)
    print("Y: ", Y)
    

    最后,这是一个没有继承的更简单的例子:

    """Demonstrate access to Keras batch tensors in a tf.keras custom training step."""
    import tensorflow as tf
    
    IN_SHAPE = (2,)
    OUT_SHAPE = (1,)
    BATCH_SIZE = 3
    N_SAMPLES = 7
    
    
    def make_print_data_and_train_step(keras_model):
        """Return a train_step function that prints data batches."""
        original_train_step = keras_model.train_step
    
        def print_data_and_train_step(data):
            # Adapted from https://git.io/JvDTv, skipping data_adapter.expand_1d
            x, y_true, w = tf.keras.utils.unpack_x_y_sample_weight(data)
            y_pred = keras_model(x, training=True)
    
            # this is pretty much like on_train_batch_begin
            tf.keras.backend.print_tensor(w, "Sample weight (w) =")
            tf.keras.backend.print_tensor(x, "Batch input (x) =")
            tf.keras.backend.print_tensor(y_true, "Batch output (y_true) =")
            tf.keras.backend.print_tensor(y_pred, "Prediction (y_pred) =")
    
            result = original_train_step(data)
    
            # add anything here for on_train_batch_end-like behavior
    
            return result
    
        return print_data_and_train_step
    
    
    # Model
    model = tf.keras.Sequential([tf.keras.layers.Dense(OUT_SHAPE[0], input_shape=IN_SHAPE)])
    model.train_step = make_print_data_and_train_step(model)
    model.compile(loss="mse", optimizer="adam")
    
    # Example data
    X = tf.random.normal((N_SAMPLES, *IN_SHAPE))
    Y = tf.random.normal((N_SAMPLES, *OUT_SHAPE))
    
    model.fit(X, Y, batch_size=BATCH_SIZE)
    print("X: ", X)
    print("Y: ", Y)
    

    【讨论】:

      【解决方案3】:

      更新:此方法已停止工作。请参阅my other answer 一些与 TF2.8 兼容的解决方案(并希望超越)。

      @Yu-Yang 的解决方案的一个问题是它依赖于model._function_kwargs,由于它不是 API 的一部分,因此无法保证能够正常工作。特别是,在具有急切执行的 TF2 中,会话 kwarg 似乎要么根本不被接受,要么由于急切模式而抢先运行。

      因此,这是我在 tensorflow==2.1.0 上测试的解决方案。诀窍是用 Keras 度量替换 fetches,其中来自 fetches 的分配操作是在训练期间进行的。

      如果批量大小除以样本数量,这甚至可以启用仅 Keras 的解决方案;否则,在使用 None 形状初始化 TensorFlow 变量时必须应用另一个技巧,类似于早期解决方案中的 validate_shape=False(比较 https://github.com/tensorflow/tensorflow/issues/35667)。

      重要的是,tf.keras 的行为与 keras 不同(有时只是忽略赋值,或将变量视为 Keras 符号张量),因此这个更新的解决方案同时兼顾了两种实现(Keras==2.3.1tensorflow==2.1.0)。

      """Demonstrate access to Keras symbolic tensors in a (tf.)keras.Callback."""
      
      import numpy as np
      import tensorflow as tf
      
      use_tf_keras = True
      if use_tf_keras:
          from tensorflow import keras
          from tensorflow.keras import backend as K
      
          tf.config.experimental_run_functions_eagerly(False)
          compile_kwargs = {"run_eagerly": False, "experimental_run_tf_function": False}
      
      else:
          import keras
          from keras import backend as K
      
          compile_kwargs = {}
      
      
      in_shape = (2,)
      out_shape = (1,)
      batch_size = 3
      n_samples = 7
      
      
      class CollectKerasSymbolicTensorsCallback(keras.callbacks.Callback):
          """Collect Keras symbolic tensors."""
      
          def __init__(self):
              """Initialize intermediate variables for batches and lists."""
              super().__init__()
      
              # Collect batches here
              self.inputs = []
              self.targets = []
              self.outputs = []
      
              # # For a pure Keras solution, we need to know the shapes beforehand;
              # # in particular, batch_size must divide n_samples:
              # self.input = K.variable(np.empty((batch_size, *in_shape)))
              # self.target = K.variable(np.empty((batch_size, *out_shape)))
              # self.output = K.variable(np.empty((batch_size, *out_shape)))
      
              # If the shape of these variables will change (e.g., last batch), initialize
              # arbitrarily and specify `shape=tf.TensorShape(None)`:
              self.input = tf.Variable(0.0, shape=tf.TensorShape(None))
              self.target = tf.Variable(0.0, shape=tf.TensorShape(None))
              self.output = tf.Variable(0.0, shape=tf.TensorShape(None))
      
          def on_batch_end(self, batch, logs=None):
              """Evaluate the variables and save them into lists."""
              self.inputs.append(K.eval(self.input))
              self.targets.append(K.eval(self.target))
              self.outputs.append(K.eval(self.output))
      
          def on_train_end(self, logs=None):
              """Print all variables."""
              print("Inputs: ", *self.inputs)
              print("Targets: ", *self.targets)
              print("Outputs: ", *self.outputs)
      
      
      @tf.function
      def assign_keras_symbolic_tensors_metric(_foo, _bar):
          """
          Return the assignment operations as a metric to have them evaluated by Keras.
      
          This replaces `fetches` from the TF1/non-eager-execution solution.
          """
          # Collect assignments as list of (dest, src)
          assignments = (
              (callback.input, model.inputs[0]),
              (callback.target, model._targets[0] if use_tf_keras else model.targets[0]),
              (callback.output, model.outputs[0]),
          )
          for (dest, src) in assignments:
              dest.assign(src)
      
          return 0
      
      
      callback = CollectKerasSymbolicTensorsCallback()
      metrics = [assign_keras_symbolic_tensors_metric]
      
      # Example model
      model = keras.Sequential([keras.layers.Dense(out_shape[0], input_shape=in_shape)])
      model.compile(loss="mse", optimizer="adam", metrics=metrics, **compile_kwargs)
      
      # Example data
      X = np.random.rand(n_samples, *in_shape)
      Y = np.random.rand(n_samples, *out_shape)
      
      model.fit(X, Y, batch_size=batch_size, callbacks=[callback])
      print("X: ", X)
      print("Y: ", Y)
      

      【讨论】:

        【解决方案4】:

        受 tf.keras.callbacks.TesnsorBoard 保存 v1(图形)摘要的方式启发。

        没有变量分配,也没有多余的指标。

        用于 tensorflow>=2.0.0,在评估期间绘制(禁用 Eager)模式。

        可以通过覆盖 SavePrediction._pred_callback 来实现对 numpy 预测的广泛操作。

        import numpy as np
        import tensorflow as tf
        from tensorflow import keras
        
        tf.compat.v1.disable_eager_execution()
        
        in_shape = (2,)
        out_shape = (1,)
        batch_size = 2
        n_samples = 32
        
        
        class SavePrediction(keras.callbacks.Callback):
            def __init__(self):
                super().__init__()
                self._get_pred = None
                self.preds = []
        
            def _pred_callback(self, preds):
                self.preds.append(preds)
        
            def set_model(self, model):
                super().set_model(model)
                if self._get_pred is None:
                    self._get_pred = self.model.outputs[0]
        
            def on_test_begin(self, logs):
                # pylint: disable=protected-access
                self.model._make_test_function()
                # pylint: enable=protected-access
                if self._get_pred not in self.model.test_function.fetches:
                    self.model.test_function.fetches.append(self._get_pred)
                    self.model.test_function.fetch_callbacks[self._get_pred] = self._pred_callback
        
            def on_test_end(self, logs):
                if self._get_pred in self.model.test_function.fetches:
                    self.model.test_function.fetches.remove(self._get_pred)
                if self._get_pred in self.model.test_function.fetch_callbacks:
                    self.model.test_function.fetch_callbacks.pop(self._get_pred)
        
                print(self.preds)
        
        
        model = keras.Sequential([
            keras.layers.Dense(out_shape[0], input_shape=in_shape)
        ])
        model.compile(loss="mse", optimizer="adam")
        
        X = np.random.rand(n_samples, *in_shape)
        Y = np.random.rand(n_samples, *out_shape)
        
        model.evaluate(X, Y,
                       batch_size=batch_size,
                       callbacks=[SavePrediction()])
        

        【讨论】:

        • _make_test_function 是否记录在某处?这看起来像是另一个私有 API,不确定是否会长期保留在代码库中......(我猜,fetches 的问题相同。)我认为这只是因为在内部,tf.compat.v1.disable_eager_execution() 切换了很多 的事情到v1
        猜你喜欢
        • 2018-04-15
        • 2017-06-05
        • 2017-05-29
        • 2023-03-13
        • 1970-01-01
        • 2018-07-31
        • 1970-01-01
        • 1970-01-01
        • 2019-02-02
        相关资源
        最近更新 更多