简体   繁体   English

TensorFlow 2.0 Keras:如何为 TensorBoard 编写图像摘要

[英]TensorFlow 2.0 Keras: How to write image summaries for TensorBoard

I'm trying to setup an image recognition CNN with TensorFlow 2.0.我正在尝试使用 TensorFlow 2.0 设置图像识别 CNN。 To be able to analyze my image augmentation I'd like to see the images I feed into the network in tensorboard.为了能够分析我的图像增强,我想在张量板中查看我输入网络的图像。

Unfortunately, I cannot figure out, how to do this with TensorFlow 2.0 and Keras.不幸的是,我不知道如何使用 TensorFlow 2.0 和 Keras 来做到这一点。 I also didn't really find documentation on this.我也没有真正找到这方面的文档。

For simplicity, I'm showing the code of an MNIST example.为简单起见,我展示了一个 MNIST 示例的代码。 How would I add the image summary here?我将如何在此处添加图像摘要?

import tensorflow as tf
(x_train, y_train), _ = tf.keras.datasets.mnist.load_data()

def scale(image, label):
    return tf.cast(image, tf.float32) / 255.0, label

def augment(image, label):
    return image, label  # do nothing atm

dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
dataset = dataset.map(scale).map(augment).batch(32)

model = tf.keras.models.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Dense(10, activation='softmax')
])

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(dataset, epochs=5, callbacks=[tf.keras.callbacks.TensorBoard(log_dir='D:\\tmp\\test')])

Except providing an answer to your question I will make the code more TF2.0 -like.除了为您的问题提供答案外,我将使代码更像TF2.0 If you have any questions/need clarification, please post a comment down below.如果您有任何问题/需要澄清,请在下方发表评论。

1. Loading data 1.加载数据

I would advise to use Tensorflow Datasets library.我建议使用Tensorflow Datasets库。 There is absolutely no need to load data in numpy and transform it to tf.data.Dataset if one can do it in a single line:如果可以在一行中完成,绝对不需要在numpy加载数据并将其转换为tf.data.Dataset

import tensorflow_datasets as tfds

dataset = tfds.load("mnist", as_supervised=True, split=tfds.Split.TRAIN)

Line above will only return TRAIN split (read more about those here ).上面的行只会返回TRAIN split(在此处阅读有关这些内容的更多信息)。

2. Define Augmentations and Summaries 2. 定义增强和总结

In order to save images, one has to keep tf.summary.SummaryWriter object throughout each pass.为了保存图像,必须在每次传递中保留tf.summary.SummaryWriter对象。

I have created a convenient wrapping class with __call__ method for easy usage with tf.data.Dataset 's map capabilities:我用__call__方法创建了一个方便的包装类,以便与tf.data.Datasetmap功能一起使用:

import tensorflow as tf

class ExampleAugmentation:
    def __init__(self, logdir: str, max_images: int, name: str):
        self.file_writer = tf.summary.create_file_writer(logdir)
        self.max_images: int = max_images
        self.name: str = name
        self._counter: int = 0

    def __call__(self, image, label):
        augmented_image = tf.image.random_flip_left_right(
            tf.image.random_flip_up_down(image)
        )
        with self.file_writer.as_default():
            tf.summary.image(
                self.name,
                augmented_image,
                step=self._counter,
                max_outputs=self.max_images,
            )

        self._counter += 1
        return augmented_image, label

name will be the name under which each part of images will be saved. name将是图像的每个部分将被保存的名称。 Which part you may ask - the part defined by max_outputs .您可能会问哪个部分 - max_outputs定义的部分。

Say image in __call__ will have shape (32, 28, 28, 1) , where the first dimension is batch, second width, third height and last channels (in case of MNIST only onel but this dimension is needed in tf.image augmentations).假设__call__ image将具有形状(32, 28, 28, 1) ,其中第一个维度是批次、第二个宽度、第三个高度和最后一个通道(在 MNIST 的情况下只有一个,但在tf.image增强中需要这个维度) . Furthermore, let's say max_outputs is specified as 4 .此外,假设max_outputs被指定为4 In this case, only 4 first images from batch will be saved.在这种情况下,将只保存批次中的 4 张第一张图像。 Default value is 3 , so you may set it as BATCH_SIZE to save every image.默认值为3 ,因此您可以将其设置为BATCH_SIZE以保存每个图像。

In Tensorboard , each image will be a separate sample over which you can iterate at the end.Tensorboard ,每个图像都是一个单独的样本,您可以在最后对其进行迭代。

_counter is needed so the images will not be overwritten (I think, not really sure, clarification from someone else would be nice). _counter是必需的,因此图像不会被覆盖(我认为,不太确定,其他人的澄清会很好)。

Important: You may want to rename this class to something like ImageSaver when doing more serious buisness and move augmentation to separate functors/lambda functions.重要提示:在进行更严肃的ImageSaver时,您可能希望将此类重命名为ImageSaver类的名称,并将扩充移至单独的函子/lambda 函数。 It suffices for presentation purposes I guess.我想这足以用于演示目的。

3. Setup global variables 3. 设置全局变量

Please do not mix function declaration, global variables, data loading and others (like loading data and creating function afterwards).请不要混合使用函数声明、全局变量、数据加载等(如加载数据和创建函数)。 I know TF1.0 encouraged this type of programming but they are trying to get away from it and you might want to follow the trend.我知道TF1.0鼓励这种类型的编程,但他们正试图摆脱它,你可能想跟上潮流。

Below I have defined some global variables which will be used throughout next parts, pretty self-explanatory I guess:下面我定义了一些全局变量,这些变量将在接下来的部分中使用,我想这是不言自明的:

BATCH_SIZE = 32
DATASET_SIZE = 60000
EPOCHS = 5

LOG_DIR = "/logs/images"
AUGMENTATION = ExampleAugmentation(LOG_DIR, max_images=4, name="Images")

4. Dataset augmentation 4. 数据集扩充

Similar to yours but with a little twist:类似于你的,但有一点点扭曲:

dataset = (
    dataset.map(
        lambda image, label: (
            tf.image.convert_image_dtype(image, dtype=tf.float32),
            label,
        )
    )
    .batch(BATCH_SIZE)
    .map(AUGMENTATION)
    .repeat(EPOCHS)
)
  • repeat is needed as the loaded dataset is a generator需要repeat ,因为加载的数据集是一个生成器
  • tf.image.convert_image_dtype - better and more readable option than explicit tf.cast mixed with division by 255 (and ensures proper image format) tf.image.convert_image_dtype - 比显式tf.cast与除以255混合更好、更易读的选项(并确保正确的图像格式)
  • batching done before augmentation just for the sake of presentation在增强之前进行批处理只是为了演示

5. Define model, compile, train 5.定义模型,编译,训练

Almost as you did in your example, but I have provided additional steps_per_epoch , so fit knows how many batches constitute an epoch:几乎就像你在你的例子中所做的那样,但我提供了额外的steps_per_epoch ,所以fit知道有多少批次构成一个纪元:

model = tf.keras.models.Sequential(
    [
        tf.keras.layers.Flatten(input_shape=(28, 28, 1)),
        tf.keras.layers.Dense(128, activation="relu"),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(10, activation="softmax"),
    ]
)

model.compile(
    optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]
)
model.fit(
    dataset,
    epochs=EPOCHS,
    steps_per_epoch=DATASET_SIZE // BATCH_SIZE,
    callbacks=[tf.keras.callbacks.TensorBoard(log_dir=LOG_DIR)],
)

Not much to explain other than that I think.除了我认为的之外,没有太多解释。

6. Run Tensorboard 6. 运行 Tensorboard

Since TF2.0 one can do it inside colab using %tensorboard --logdir /logs/images , just wanted to add this for others who may visit this issue.由于TF2.0可以在 colab 中使用%tensorboard --logdir /logs/images ,因此只想为可能访问此问题的其他人添加此内容。 Do it however you like, anyways you know how to do it for sure.随心所欲地做,反正你肯定知道怎么做。

Images should be inside IMAGES and each sample named by name provided to AUGMENTATION object.图像应该在IMAGES并且每个以name命名的样本都提供给AUGMENTATION对象。

7. Whole code (to make everyone's life easier) 7. 完整代码(让大家的生活更轻松)

import tensorflow as tf
import tensorflow_datasets as tfds


class ExampleAugmentation:
    def __init__(self, logdir: str, max_images: int, name: str):
        self.file_writer = tf.summary.create_file_writer(logdir)
        self.max_images: int = max_images
        self.name: str = name
        self._counter: int = 0

    def __call__(self, image, label):
        augmented_image = tf.image.random_flip_left_right(
            tf.image.random_flip_up_down(image)
        )
        with self.file_writer.as_default():
            tf.summary.image(
                self.name,
                augmented_image,
                step=self._counter,
                max_outputs=self.max_images,
            )

        self._counter += 1
        return augmented_image, label


if __name__ == "__main__":

    # Global settings

    BATCH_SIZE = 32
    DATASET_SIZE = 60000
    EPOCHS = 5

    LOG_DIR = "/logs/images"
    AUGMENTATION = ExampleAugmentation(LOG_DIR, max_images=4, name="Images")

    # Dataset

    dataset = tfds.load("mnist", as_supervised=True, split=tfds.Split.TRAIN)

    dataset = (
        dataset.map(
            lambda image, label: (
                tf.image.convert_image_dtype(image, dtype=tf.float32),
                label,
            )
        )
        .batch(BATCH_SIZE)
        .map(AUGMENTATION)
        .repeat(EPOCHS)
    )

    # Model and training

    model = tf.keras.models.Sequential(
        [
            tf.keras.layers.Flatten(input_shape=(28, 28, 1)),
            tf.keras.layers.Dense(128, activation="relu"),
            tf.keras.layers.Dropout(0.2),
            tf.keras.layers.Dense(10, activation="softmax"),
        ]
    )

    model.compile(
        optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]
    )
    model.fit(
        dataset,
        epochs=EPOCHS,
        steps_per_epoch=DATASET_SIZE // BATCH_SIZE,
        callbacks=[tf.keras.callbacks.TensorBoard(log_dir=LOG_DIR)],
    )

You could do something like this to add input image to tensorboard你可以做这样的事情来将输入图像添加到张量板

def scale(image, label):
    return tf.cast(image, tf.float32) / 255.0, label


def augment(image, label):
    return image, label  # do nothing atm


file_writer = tf.summary.create_file_writer(logdir + "/images")


def plot_to_image(figure):
    buf = io.BytesIO()
    plt.savefig(buf, format='png')
    plt.close(figure)
    buf.seek(0)
    image = tf.image.decode_png(buf.getvalue(), channels=4)
    image = tf.expand_dims(image, 0)
    return image


def image_grid():
    """Return a 5x5 grid of the MNIST images as a matplotlib figure."""
    # Create a figure to contain the plot.
    figure = plt.figure(figsize=(10, 10))
    for i in range(25):
        # Start next subplot.
        plt.subplot(5, 5, i + 1, title=str(y_train[i]))
        plt.xticks([])
        plt.yticks([])
        plt.grid(False)
        image, _ = scale(x_train[i], y_train[i])
        plt.imshow(x_train[i], cmap=plt.cm.binary)

    return figure


# Prepare the plot
figure = image_grid()
# Convert to image and log
with file_writer.as_default():
    tf.summary.image("Training data", plot_to_image(figure), step=0)

dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
dataset = dataset.map(scale).map(augment).batch(32)

model = tf.keras.models.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Dense(10, activation='softmax')
])

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

model.fit(dataset, epochs=5, callbacks=[tf.keras.callbacks.TensorBoard(log_dir=logdir)])

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM