Tensorflow2.0でtensorboardにmatplotlabのイメージをベタに書き出す

Tensorflow 2.0自体がまだalpha版なので、変更される可能性はある。

1.xの書き方が一部できなくなっているようで、`tf.summary.Summary.Image`でPNGデータをそのまま渡す、という事が出来なくなったりして混乱した。互換性のためのパッケージがあるのかどうかは調べてない。

とにかく動くコードが欲しかったのでQiitaに書いておく。

### 事前準備：SummaryのWriterを作成

``` python
import datetime
import tensorflow as tf

# 実行毎にタイムスタンプでディレクトリを分ける
TIMESTAMP = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
LOGDIR= f'./logs/{TIMESTAMP}' 

summary_writer = tf.summary.create_file_writer(LOGDIR)

# tensorboard --logdir ./logs でtimestamp毎にまとめて見れるようになる。
```

### matplotlibでイメージ生成し、tf.summary.imageに貼り付け

ちょっと長いけど、tensorflowが絡む箇所はごく一部。

``` python
import numpy as np
import matplotlib.pyplot as plt
import io

# ヘルパ関数
def plot_tf_summary_image(name, x_imgs, y_preds, batch_size, step=None, description=None):

    # matplotlibのfigureを用意してnumpyを駆使して書き出す（tf.Tensorの場合は`tensor_val.numpy()`などで変換する）
    # batch_size=16 なら、縦16,横2の領域を作る
    fig=plt.figure(figsize=(2, batch_size))
    for i in range(batch_size):
        fig.add_subplot(batch_size, 2, i*2+1)
        plt.imshow(x_imgs[i])
        fig.add_subplot(batch_size, 2, i*2+2)
        mono = y_preds[0][i].reshape([7,7]) # 適当
        plt.imshow(mono, interpolation='nearest', vmin=0, vmax=1, cmap='jet')
    # バッファにfigureをPNGファイルとして書き出す
    buf = io.BytesIO()
    fig.savefig(buf, format="png")
    buf.seek(0)
    
    ### ここからがTensorflow要素 ###
    
    # バッファからPNGファイルを読み取り、Tensorに変換
    tf_image = tf.image.decode_png(buf.getvalue())
    # 書き出す。
    tf.summary.image(
        name=name,
        data=tf.expand_dims(tf_image, 0), # [k, width, height, channel]形式である必要があるので、次元を1つ拡張
        max_outputs=1, # max_outputsでdataの中からいくつ書き出すか指定もできる。今回は1つ
        step=step,
        description=description,
    )

# テストデータ VGGと重みデータを取得して書き出すイメージ
images = np.asarray([
    np.random.rand(224,224,3),
    np.random.rand(224,224,3),
    np.random.rand(224,224,3),
])

y_preds = np.asarray([
    np.random.rand(512,7,7,1),
    np.random.rand(512,7,7,1),
    np.random.rand(512,7,7,1),
])

# tensorboardへ書き出す
with summary_writer.as_default():
    plot_tf_summary_image("sample", images, y_preds, 3, step=1)
```


雑だけどtensorboard上はこんな感じに出る。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/52527/599a6af4-9229-9683-445a-7781c7f7af7f.png)


おまけ
------------------

### 学習実行時にSummary File Writerを指定

``` python
with summary_writer.as_default():
    train(model, optimizer, dataset)
```

### メトリック採取

``` python
train_loss = tf.keras.metrics.Mean()

# 中略

with summary_writer.as_default(): # 上記のようにtrain関数中で行うならこの行は不要
    tf.summary.scalar('loss', train_loss.result(), step=1)
```


### tensorboard起動時に`OSError: [Errno 22] Invalid argument`

```
  File "...\python36\lib\site-packages\tensorboard\manager.py", line 51, in <lambda>
    (dt - datetime.datetime.fromtimestamp(0)).total_seconds()),
OSError: [Errno 22] Invalid argument
```

修正されるらしいが、現時点の最新版はまだ反映されていなかった。

https://github.com/tensorflow/tensorboard/issues/2017

Issueは上がっているけど、具体的な原因はわかってない。多分経過秒を取りたいだけ？のようで、雑だけどすぐ動かしたかったので、こうした。

``` diff:tensorboard/manager.py
_type_timestamp = _FieldType(
    serialized_type=int,  # seconds since epoch
    runtime_type=datetime.datetime,  # microseconds component ignored
    serialize=lambda dt: int(
-        (dt - datetime.datetime.fromtimestamp(0)).total_seconds()),
+        (dt - datetime.datetime.strptime("1970-01-01", '%Y-%m-%d')).total_seconds()),
    deserialize=lambda n: datetime.datetime.fromtimestamp(n),
)
```

参考
--------

https://www.tensorflow.org/alpha/guide/effective_tf2#use_tfmetrics_to_aggregate_data_and_tfsummary_to_log_it
https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/summary
https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/io/decode_png