TensorFlow.keras上にオートエンコーダを実装してMNIST画像内に含まれたノイズを除去できるニューラルネットワークを構築する

本記事では、深層学習のモデルのひとつである **「オートエンコーダ」** を使って **画像からノイズを除去する手法** について解説します。今回扱うオートエンコーダは画像処理を行うものであり、 **画像を入力として、画像を出力します。** オートエンコーダによる画像処理では、入力された画像はエンコーダとなるニューラルネットワークによりモデルの内部で「ある特徴量」まで圧縮が行われ、その後、デコーダとなるニューラルネットワークにより特徴量から画像を復元します。ここで用いられる **エンコーダとデコーダを事前の学習で「ノイズが特徴量に含まれないようにニューラルネットワークの調整しておく」ことにより、入力された画像からノイズの特徴量が失われ、デコード後の画像（オートエンコーダを通過した画像）にノイズが含まれないように処理することができます。**

![スクリーンショット 2025-01-18 16.13.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/354aa667-053c-2bb2-381e-6a99ce1d9780.png)

https://jp.mathworks.com/discovery/autoencoder.html

https://www.tensorflow.org/tutorials/generative/autoencoder?hl=ja

#### 本記事で扱うオートエンコーダの概略

今回実装したニューラルネットワークは下記の構造を持ちます。水色のフローがエンコーダの処理であり、灰色のフローがデコーダの処理です。 **それぞれのフローは実データを扱う前に、学習用データセットによりパラメータチューニングをしておく** 必要があります。

![スクリーンショット 2025-01-19 1.49.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/f8658b6f-0a01-c87b-9ba1-85adfd9900bf.png)

## 開発環境の準備

それでは早速、開発の準備を進めていきましょう。

### 開発用ディレクトリとPythonの仮想環境を作成する

まずはじめに開発用ディレクトリを作成し、その中にPythonの仮想環境を定義し、必要なパッケージをインストールします。今回はAIの実装に **TensorFlow.keras** を使います。

```bash
### 作業用ディレクトリを作成します
$ cd ~
$ mkdir autoencoder_example
$ cd autoencoder_example

### AnacondaのPython仮想環境を作成し、ログインします
$ conda create -n autoencoder_run python=3.10
$ conda activate autoencoder_run

### 必要なパッケージをインストールします
$ pip install tensorflow
$ pip install tensorboard
$ pip install pandas
$ pip install matplotlib
$ pip install scikit-learn
$ pip install -U numpy==1.26.4
# Installing collected packages: numpy
#   Attempting uninstall: numpy
#     Found existing installation: numpy 2.0.2
#     Uninstalling numpy-2.0.2:
#       Successfully uninstalled numpy-2.0.2
# Successfully installed numpy-1.26.4
```

https://www.kikagaku.co.jp/kikagaku-blog/python-scikit-learn/

Pythonの仮想環境の構築ができたら、開発・実行用のVisual Studio Codeで作成した仮想環境を選択し、インストールしたパッケージを利用できるようにします。まず、適当な名前のPythonスクリプト (例：`autoencoder_conv.py`) を作成し、次に、 **画面右下に表示されているPythonのバージョンをクリックし、作成した `autoencoder_run` を選択します。**

![スクリーンショット 2025-01-18 12.39.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/6e1dae61-624d-8af2-f0e6-69748a960fa9.png)

以下のimport文をPythonコードへ入力し **「F5」** キーを押下しエラー無く実行できれば、Pythonの仮想環境は正しく読み込めています。

```python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf

from tensorflow.keras import layers, losses
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Model

import os
import datetime
```

## オートエンコーダを実装する

それでは早速、**ノイズを除去する機能を備えたオートエンコーダ** を実装していきましょう。

### 学習用のデータセットを準備する

ノイズ除去の実現には、まずデータセットを準備する必要があります。本記事では、 **手書き数字を含むMNIST(28px x 28px x 1ch(グレースケール))のノイズ除去** について紹介します。まず、扱うMNISTの画像を準備するところからはじめていきましょう。

![スクリーンショット 2025-01-18 16.07.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/c35b4492-da9e-cecf-db3c-678a60861b1e.png)

#### MNISTの中から"5"のラベルのモノのみ抽出する

今回実装するオートエンコーダを学習するために、 **MNIST (手書き数字画像の学習用データセット) から"5"のラベルの付いた画像をデータセットとして抽出** しました。MNISTを分類し、各ラベルごとにグレースケール画像を格納する関数 `get_sorted_mnist` を定義します。MNISTの入手にはkerasが提供する `mnist` インターフェースを利用しました。

https://qiita.com/erdes/items/67aec9ffb5b2afab58d4

https://qiita.com/kzrn3318/items/148cd0accf48e40ddac5

```python
# AutoEncoderで扱う画像を選択する
TARGET_MNIST_LABEL = 5
# 画像サイズを指定する
IMG_W = 28
IMG_H = 28

######
### ソートされたMNISTのデータセットを生成する
######
def get_sorted_mnist(items_per_category=5000, test_select=False):
    # MNISTの学習用データセットを展開する
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    # データセットを Train のものか Test のものか選択する
    x_input = x_train
    y_input = y_train
    if test_select:
        x_input = x_test
        y_input = y_test
    # データフレームを定義する
    df = pd.DataFrame(columns=["label"])
    df["label"] = y_input.reshape([-1])
    # 0〜9までに対応するtrainデータを指定個数ずつ取り出す
    lists_images = []
    for item in range(10):
        # ラベルを指定して対応する箇所のインデックスを算出する
        list = df.loc[df.label==item].sample(n=items_per_category)
        list = list.sort_index()
        list_idx = list.index.values
        # インデックスからtrainデータの同一ラベル箇所を切り出す
        list_train = x_input[list_idx]
        list_train = list_train[np.newaxis,:]
        # リストに追加する
        lists_images.append(list_train)
    # リストを配列に変換する
    lists_images = np.vstack(lists_images)
    return lists_images

# 学習用データセットを生成する
images_train = get_sorted_mnist(items_per_category=5000, test_select=False)
images_test = get_sorted_mnist(items_per_category=100, test_select=True)
# 画像を選出しグレースケール画像のshapeを指定する
imageset_train = images_train[TARGET_MNIST_LABEL]
imageset_train = imageset_train.reshape(-1, IMG_W, IMG_H, 1)
imageset_test = images_test[TARGET_MNIST_LABEL]
imageset_test = imageset_test.reshape(-1, IMG_W, IMG_H, 1)
# 画像をfloat型に変換する
imageset_train = imageset_train.astype('float32') / 255
imageset_test = imageset_test.astype('float32') / 255
```

https://hahaeatora.hateblo.jp/entry/2019/01/29/200000

https://qiita.com/yosshi4486/items/deb49d5a433a2c8a8ed4

https://teratail.com/questions/157841

https://deepage.net/features/numpy-stack.html

#### 28px x 28px x 1chのnumpy配列を画像化する

次に可視化のために **「28px x 28px x 1ch(グレースケール画像)」** をPNG画像として出力する `image_plot` 関数を定義します。これにより学習や評価に利用した画像を記録することや、ノイズ除去で扱ったデータを記録することができるようになります。

![スクリーンショット 2025-01-18 16.07.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/c35b4492-da9e-cecf-db3c-678a60861b1e.png)

```python
# 実行中の画像を出力するディレクトリ
EXPORT_DIR_FOR_AUTOENCODER = "autoencoder_out/"
os.makedirs(os.path.dirname(EXPORT_DIR_FOR_AUTOENCODER), exist_ok=True)
# 入力画像を出力するファイルパス
PATH_SAVE_INPUT_DATASET_TRAIN = "input_dataset_train.png"
PATH_SAVE_INPUT_DATASET_TEST = "input_dataset_test.png"

######
### Numpy配列の先頭8要素を画像へ書き出す
######
def image_plot(imgs, path_savefig):
    output_imgs = imgs[:8,:,:,:]
    # 横長の4x2で画像を保存する
    plt.figure(figsize=(5, 3))
    for k in range(output_imgs.shape[0]):
        plt.subplot(2, 4, k+1)
        plt.imshow(output_imgs[k, :, :, 0], cmap='gray')
    plt.tight_layout()
    # 画像保存先のディレクトリを作成する
    path_savefig = os.path.join(EXPORT_DIR_FOR_AUTOENCODER, path_savefig)
    os.makedirs(os.path.dirname(path_savefig), exist_ok=True)
    # 画像を保存する
    plt.savefig(path_savefig)
    print("Save images to :", path_savefig)
    plt.clf()
    plt.close()

# 画像の一部を出力する
image_plot(imageset_train, PATH_SAVE_INPUT_DATASET_TRAIN)
image_plot(imageset_test, PATH_SAVE_INPUT_DATASET_TEST)
```

### 畳み込みオートエンコーダを定義する

それでは、オートエンコーダを実装してみましょう。今回は画像を扱いますので、畳み込み層と逆畳み込み層を利用したオートエンコーダを定義します。 **エンコード時はstride=2の畳み込み層により画像サイズを圧縮して特徴量を削減し、デコード時には逆畳み込み層を用いて特徴量から画像を復元** します。

![スクリーンショット 2025-01-19 1.49.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/e2f0b63f-c8bf-4590-1eee-8720d482887a.png)

#### ノイズ除去を実現する、ひとつの局所解

画像に均一に含まれているノイズを除去する方法のうち、最悪な方法のひとつが **「有効なデータなどお構いなく、すべてのピクセルを黒で塗りつぶしてしまうこと」** であり、今回実装するオートエンコーダも、そういった局所解に陥ってしまうことがあります。そういった状況に陥った学習の場合、 <font color="red">**早いepochsのうちにLossが大きい値のまま収束してしまい、モデルが一定値以上に成長しない**</font> という様子を見ることができます（下図グラフの上に示される曲線）。これを対策するため、 **本実装ではニューラルネットワークを構成する各層の初期値を明示的に `TruncatedNormal` で指定し、学習率 (Learning Rate) も、経験的に収束しやすい値である `4e-5` を選択** しています。

![スクリーンショット 2025-01-18 18.04.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/d740721d-368a-2b5f-9ccb-dd9513d975d8.png)

#### オートエンコーダを実装する

オートエンコーダの実装を示します。エンコーダは複数の畳み込み層から構成され、デコーダは複数の逆畳み込み層から構成されます。デコーダは最後に28px x 28px x 1chのグレースケール画像を生成するために、復元した特徴量を基に畳み込みを行います。

各層の実装の際は、 **期待しない局所解になるべく陥らないように、重みの初期化方法 `kernel_initializer` に `TruncatedNormal(mean=0.0)` を指定し、バイアスの初期化方法 `bias_initializer` に `TruncatedNormal(mean=0.5)` を指定** しています。また学習に利用する **学習率(Learning Rate)は経験的に `4e-5` を指定** しています。

https://keras.io/api/layers/initializers/

https://www.tensorflow.org/api_docs/python/tf/keras/initializers

https://bibo6.hatenablog.jp/entry/2020/05/16/044302

https://qiita.com/keng000/items/c50794fb7f029062bd0d

```python
######
### オートエンコーダを定義する
######
class DenoiseEncoder(Model):
  def __init__(self, latent_dim=8):
    super(DenoiseEncoder, self).__init__()
    set_bias_initializer = tf.keras.initializers.TruncatedNormal(mean=0.5, stddev=0.05, seed=None)
    set_kernel_initializer = tf.keras.initializers.TruncatedNormal(mean=0.0, stddev=0.05, seed=None)

    self.encoder = tf.keras.Sequential([
        layers.Input(shape=(28, 28, 1)),
        layers.Conv2D(latent_dim*2, (3, 3), activation='relu', 
                      padding='same', strides=2,
                      kernel_initializer=set_kernel_initializer,
                      bias_initializer=set_bias_initializer),
        layers.Conv2D(latent_dim, (3, 3), activation='relu', 
                      padding='same', strides=2,
                      kernel_initializer=set_kernel_initializer,
                      bias_initializer=set_bias_initializer)])
    
    self.decoder = tf.keras.Sequential([
        layers.Input(shape=(7, 7, latent_dim)),
        layers.Conv2DTranspose(latent_dim, kernel_size=3, strides=2, 
                               activation='relu', padding='same',
                               kernel_initializer=set_kernel_initializer,
                               bias_initializer=set_bias_initializer),
        layers.Conv2DTranspose(latent_dim*2, kernel_size=3, strides=2, 
                               activation='relu', padding='same',
                               kernel_initializer=set_kernel_initializer,
                               bias_initializer=set_bias_initializer),
        layers.Conv2D(1, kernel_size=(3, 3), 
                      activation='sigmoid', padding='same',
                      kernel_initializer=set_kernel_initializer,
                      bias_initializer=set_bias_initializer)])

  def call(self, x):
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded

# オートエンコーダを生成する
denoise_encoder = DenoiseEncoder(latent_dim=16)
# AIモデルをコンパイルする
denoise_encoder.compile(
   optimizer=tf.keras.optimizers.Adam(learning_rate=4e-5), 
   loss=losses.MeanSquaredError())
# ネットワークを表示する
denoise_encoder.encoder.summary()
denoise_encoder.decoder.summary()
```

実装されたオートエンコーダのエンコーダとデコーダの `summary` は以下の通りです。 **非常に浅いネットワークで、パラメータ量もコンパクトです。**

![スクリーンショット 2025-01-19 1.45.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/997cb9d7-1e31-67f5-adee-4fd8e35bac5e.png)

### ノイズを含んだ画像を生成する

オートエンコーダが保持する「特徴量」にノイズが含まれないよう除外するため、オートエンコーダを学習するためのデータセットは、 **入力を「ノイズが含まれているMNIST画像」** とし、 **出力を「ノイズが含まれていないMNIST画像」** とします。これを実現するため、ノイズのないMNISTデータセットに次の処理を行い **ノイズを付与** します。

![スクリーンショット 2025-01-18 18.58.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/0a01c7c4-5580-28a8-9453-4ef534491120.png)

```python
# 入力ノイズ付き画像を出力するファイルパス
PATH_SAVE_INPUT_DATASET_TRAIN_NOISY = "input_dataset_train_noisy.png"
PATH_SAVE_INPUT_DATASET_TEST_NOISY = "input_dataset_test_noisy.png"

######
### ノイズ除去を学習させるために
### ノイズを含んだ学習用データを準備する
######
noise_factor = 0.2
imageset_train_noisy = imageset_train + noise_factor * tf.random.normal(shape=imageset_train.shape) 
imageset_test_noisy = imageset_test + noise_factor * tf.random.normal(shape=imageset_test.shape) 
imageset_train_noisy = tf.clip_by_value(imageset_train_noisy, clip_value_min=0., clip_value_max=1.)
imageset_test_noisy = tf.clip_by_value(imageset_test_noisy, clip_value_min=0., clip_value_max=1.)
# 画像の一部を出力する
image_plot(imageset_train_noisy, PATH_SAVE_INPUT_DATASET_TRAIN_NOISY)
image_plot(imageset_test_noisy, PATH_SAVE_INPUT_DATASET_TEST_NOISY)
```

### オートエンコーダの学習をする

最後に、準備したデータセットでオートエンコーダの学習を行います。

#### TensorBoardのコールバックを定義する

今回の実装では、**学習中のLoss(損失)を可視化できる「TensorBoard」を利用しました。** これにより、期待しない局所解に陥っていることを確認することができます。 **TensorBoardでは、TensorFlowの学習時 (`fit`) に、コールバックとしてTensorBoardのインスタンスを登録** することにより、可視化に必要なデータを収集することができます。

```python
# TensorBoardのログ出力用ファイルパス
LOG_FILE_DIR = "logs/fit/" 
os.makedirs(LOG_FILE_DIR, exist_ok=True)

# ...

# TensorBoardで表示するパラメータを記録するためのコールバックを指定
LOG_FILE_PATH = "{}{}".format(LOG_FILE_DIR, datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=LOG_FILE_PATH, histogram_freq=1)

# ...

# 学習が終了した後、TensorBoardを起動するコマンドを表示する
print("### Exec for TensorBoard: tensorboard --logdir={} --port 6006".format(LOG_FILE_DIR))
```

https://www.tensorflow.org/tensorboard/get_started?hl=ja

#### 学習を実行する

TensorFlow.kerasの `fit` を利用して学習を行います。**学習時の入力データセットには下図左の「ノイズを付与した画像」を指定し、学習時の出力データセットには下図右の「ノイズを付与する前の綺麗な画像」を指定** します。

![スクリーンショット 2025-01-18 18.58.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/686c113b-9da1-0ae9-5c7c-b17a4f5187ff.png)

```python
######
### 学習する
######

# ...

# 学習を実行する
denoise_encoder.fit(imageset_train_noisy, imageset_train,
                epochs=1000,
                shuffle=True,
                validation_data=(imageset_test_noisy, imageset_test),
                callbacks=[tensorboard_callback])
```

学習結果はTensorBoard上で以下のように確認することができます。 <font color="red">**Lossが早いepochsの段階で0.1付近に留まってしまっている場合は期待しない局所解に陥っている状態**</font> であり、順調に学習できている場合は **Lossが0.005以下まで改善されます。**

![スクリーンショット 2025-01-19 1.39.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/12cf89ee-6a27-c4d1-4b29-46f08e0faba0.png)

### ノイズ除去の効果を確認する

それでは、学習したモデルを利用してノイズ除去を試してみましょう。 **ノイズを付与したテスト画像を入力として、ニューラルネットワークによる推論を実行すると、結果としてノイズの無い画像を得ることができます。** ノイズを付与する前の画像と比較しても、かなり正確にノイズを除去できていることがわかります。

![スクリーンショット 2025-01-18 16.13.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/354aa667-053c-2bb2-381e-6a99ce1d9780.png)

```python
# 出力画像を出力するファイルパス
PATH_SAVE_OUTPUT_DATASET_TEST_NOISY = "output_dataset_test_noisy.png"
PATH_SAVE_OUTPUT_DATASET_TEST_CLEAR = "output_dataset_test_clear.png"

# ノイズを除去した画像を生成できるかテストする
autoencoder_inputs = imageset_test_noisy
encoded_imgs = denoise_encoder.encoder(autoencoder_inputs).numpy()
decoded_imgs = denoise_encoder.decoder(encoded_imgs).numpy()
image_plot(autoencoder_inputs, PATH_SAVE_OUTPUT_DATASET_TEST_NOISY)
image_plot(decoded_imgs, PATH_SAVE_OUTPUT_DATASET_TEST_CLEAR)
```

![スクリーンショット 2025-01-18 16.52.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/5d205660-01c9-24e2-7ba8-af544d5ae318.png)

----

以上のように、入力と同じshapeの出力を得るオートエンコーダは、一見単純なネットワークに見えますが、学習するデータに応じて様々な用途に利用することができます。

是非、みなさまの開発にお役立てください！

----

# ソースコード全文

以下にソースコード全文を掲載します。

```python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf

from tensorflow.keras import layers, losses
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Model

import os
import datetime

# AutoEncoderで扱う画像を選択する
TARGET_MNIST_LABEL = 5
# 画像サイズを指定する
IMG_W = 28
IMG_H = 28

# TensorBoardのログ出力用ファイルパス
LOG_FILE_DIR = "logs/fit/" 
os.makedirs(LOG_FILE_DIR, exist_ok=True)

# 実行中の画像を出力するディレクトリ
EXPORT_DIR_FOR_AUTOENCODER = "autoencoder_out/"
os.makedirs(os.path.dirname(EXPORT_DIR_FOR_AUTOENCODER), exist_ok=True)
# 入力画像を出力するファイルパス
PATH_SAVE_INPUT_DATASET_TRAIN = "input_dataset_train.png"
PATH_SAVE_INPUT_DATASET_TEST = "input_dataset_test.png"
# 入力画像を出力するファイルパス
PATH_SAVE_INPUT_DATASET_TRAIN_NOISY = "input_dataset_train_noisy.png"
PATH_SAVE_INPUT_DATASET_TEST_NOISY = "input_dataset_test_noisy.png"
# 出力画像を出力するファイルパス
PATH_SAVE_OUTPUT_DATASET_TEST_NOISY = "output_dataset_test_noisy.png"
PATH_SAVE_OUTPUT_DATASET_TEST_CLEAR = "output_dataset_test_clear.png"

######
### ソートされたMNISTのデータセットを生成する
######
def get_sorted_mnist(items_per_category=5000, test_select=False):
    # MNISTの学習用データセットを展開する
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    # データセットを Train のものか Test のものか選択する
    x_input = x_train
    y_input = y_train
    if test_select:
        x_input = x_test
        y_input = y_test
    # データフレームを定義する
    df = pd.DataFrame(columns=["label"])
    df["label"] = y_input.reshape([-1])
    # 0〜9までに対応するtrainデータを指定個数ずつ取り出す
    lists_images = []
    for item in range(10):
        # ラベルを指定して対応する箇所のインデックスを算出する
        list = df.loc[df.label==item].sample(n=items_per_category)
        list = list.sort_index()
        list_idx = list.index.values
        # インデックスからtrainデータの同一ラベル箇所を切り出す
        list_train = x_input[list_idx]
        list_train = list_train[np.newaxis,:]
        # リストに追加する
        lists_images.append(list_train)
    # リストを配列に変換する
    lists_images = np.vstack(lists_images)
    return lists_images

# 学習用データセットを生成する
images_train = get_sorted_mnist(items_per_category=5000, test_select=False)
images_test = get_sorted_mnist(items_per_category=100, test_select=True)
# 画像を選出しグレースケール画像のshapeを指定する
imageset_train = images_train[TARGET_MNIST_LABEL]
imageset_train = imageset_train.reshape(-1, IMG_W, IMG_H, 1)
imageset_test = images_test[TARGET_MNIST_LABEL]
imageset_test = imageset_test.reshape(-1, IMG_W, IMG_H, 1)
# 画像をfloat型に変換する
imageset_train = imageset_train.astype('float32') / 255
imageset_test = imageset_test.astype('float32') / 255

######
### Numpy配列の先頭8要素を画像へ書き出す
######
def image_plot(imgs, path_savefig):
    output_imgs = imgs[:8,:,:,:]
    # 横長の4x2で画像を保存する
    plt.figure(figsize=(5, 3))
    for k in range(output_imgs.shape[0]):
        plt.subplot(2, 4, k+1)
        plt.imshow(output_imgs[k, :, :, 0], cmap='gray')
    plt.tight_layout()
    # 画像保存先のディレクトリを作成する
    path_savefig = os.path.join(EXPORT_DIR_FOR_AUTOENCODER, path_savefig)
    os.makedirs(os.path.dirname(path_savefig), exist_ok=True)
    # 画像を保存する
    plt.savefig(path_savefig)
    print("Save images to :", path_savefig)
    plt.clf()
    plt.close()

# 画像の一部を出力する
image_plot(imageset_train, PATH_SAVE_INPUT_DATASET_TRAIN)
image_plot(imageset_test, PATH_SAVE_INPUT_DATASET_TEST)

######
### オートエンコーダを定義する
######
class DenoiseEncoder(Model):
  def __init__(self, latent_dim=8):
    super(DenoiseEncoder, self).__init__()
    set_bias_initializer = tf.keras.initializers.TruncatedNormal(mean=0.5, stddev=0.05, seed=None)
    set_kernel_initializer = tf.keras.initializers.TruncatedNormal(mean=0.0, stddev=0.05, seed=None)

    self.encoder = tf.keras.Sequential([
        layers.Input(shape=(28, 28, 1)),
        layers.Conv2D(latent_dim*2, (3, 3), activation='relu', 
                      padding='same', strides=2,
                      kernel_initializer=set_kernel_initializer,
                      bias_initializer=set_bias_initializer),
        layers.Conv2D(latent_dim, (3, 3), activation='relu', 
                      padding='same', strides=2,
                      kernel_initializer=set_kernel_initializer,
                      bias_initializer=set_bias_initializer)])
    
    self.decoder = tf.keras.Sequential([
        layers.Input(shape=(7, 7, latent_dim)),
        layers.Conv2DTranspose(latent_dim, kernel_size=3, strides=2, 
                               activation='relu', padding='same',
                               kernel_initializer=set_kernel_initializer,
                               bias_initializer=set_bias_initializer),
        layers.Conv2DTranspose(latent_dim*2, kernel_size=3, strides=2, 
                               activation='relu', padding='same',
                               kernel_initializer=set_kernel_initializer,
                               bias_initializer=set_bias_initializer),
        layers.Conv2D(1, kernel_size=(3, 3), 
                      activation='sigmoid', padding='same',
                      kernel_initializer=set_kernel_initializer,
                      bias_initializer=set_bias_initializer)])

  def call(self, x):
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded

# オートエンコーダを生成する
denoise_encoder = DenoiseEncoder(latent_dim=16)
# AIモデルをコンパイルする
denoise_encoder.compile(
   optimizer=tf.keras.optimizers.Adam(learning_rate=4e-5), 
   loss=losses.MeanSquaredError())
# ネットワークを表示する
denoise_encoder.encoder.summary()
denoise_encoder.decoder.summary()

######
### ノイズ除去を学習させるために
### ノイズを含んだ学習用データを準備する
######
noise_factor = 0.2
imageset_train_noisy = imageset_train + noise_factor * tf.random.normal(shape=imageset_train.shape) 
imageset_test_noisy = imageset_test + noise_factor * tf.random.normal(shape=imageset_test.shape) 
imageset_train_noisy = tf.clip_by_value(imageset_train_noisy, clip_value_min=0., clip_value_max=1.)
imageset_test_noisy = tf.clip_by_value(imageset_test_noisy, clip_value_min=0., clip_value_max=1.)
# 画像の一部を出力する
image_plot(imageset_train_noisy, PATH_SAVE_INPUT_DATASET_TRAIN_NOISY)
image_plot(imageset_test_noisy, PATH_SAVE_INPUT_DATASET_TEST_NOISY)

######
### 学習する
######
# TensorBoardで表示するパラメータを記録するためのコールバックを指定
LOG_FILE_PATH = "{}{}".format(LOG_FILE_DIR, datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=LOG_FILE_PATH, histogram_freq=1)
# 学習を実行する
denoise_encoder.fit(imageset_train_noisy, imageset_train,
                epochs=1000,
                shuffle=True,
                validation_data=(imageset_test_noisy, imageset_test),
                callbacks=[tensorboard_callback])

# ノイズを除去した画像を生成できるかテストする
autoencoder_inputs = imageset_test_noisy
encoded_imgs = denoise_encoder.encoder(autoencoder_inputs).numpy()
decoded_imgs = denoise_encoder.decoder(encoded_imgs).numpy()
image_plot(autoencoder_inputs, PATH_SAVE_OUTPUT_DATASET_TEST_NOISY)
image_plot(decoded_imgs, PATH_SAVE_OUTPUT_DATASET_TEST_CLEAR)

# 学習が終了した後、TensorBoardを起動するコマンドを表示する
print("### Exec for TensorBoard: tensorboard --logdir={} --port 6006".format(LOG_FILE_DIR))

# 終了
quit()
```

記事は以上です。