見出し画像

GANの学習-2-【オートエンコーダ(モデル構築)】

前回の続きのモデル。前回は全然自分では理解できていなかったので理解を深めつつアウトプットしていきます

PyTorchよりオートエンコーダのモデルを構築します。
Encoder、Decoderの順に層を重ねます。
入力の値は0から1の範囲なのですが、出力の範囲をこれに合わせる必要があります。
従って、出力層の活性化関数には出力範囲が0から1に収まるシグモイド関数を使います。

import torch.nn as nn
import torch.nn.functional as F
#__init__()で使用するモジュール(レイヤー)のインスタンスを生成し、forward()で所望の順番で適用していく。super().__init__()を忘れないように注意。
class Autoencoder(nn.Module):
    def __init__(self):
        super().__init__()
#nn.Linear = IN OUT 構造 下記説明
        self.encoder = nn.Linear(n_in_out, n_mid)  # Encoder
        self.decoder = nn.Linear(n_mid, n_in_out)  # Decoder

    def forward(self, x):
#第一引数が-1なら(元の配列の行数)×(元の配列の列数)=(第二引数に合わせた値)×(第二引数)となり自動的に合わせる。
#第一引数しかなく第一引数が-1だった場合は、行数1の配列となる。
        x = x.view(-1, n_in_out)  # バッチサイズ×入力の数
        x = F.relu(self.encoder(x))
        x = F.sigmoid(self.decoder(x))
        return x

    def encode(self, x):
#第一引数が-1なら(元の配列の行数)×(元の配列の列数)=(第二引数に合わせた値)×(第二引数)となり自動的に合わせる。
#第一引数しかなく第一引数が-1だった場合は、行数1の配列となる。
        x = x.view(-1, n_in_out)  # バッチサイズ×入力の数
        x = F.relu(self.encoder(x))
        return x
        #↑途中経過の表示

autoencoder = Autoencoder()
autoencoder.cuda()  # GPU対応
print(autoencoder)
from torch import optim

# 二乗和誤差
loss_fnc = nn.MSELoss()

# Adam
optimizer = optim.Adam(autoencoder.parameters())

# 損失のログ
record_loss_train = []

# 学習
#設定で epochs = 100 となっている。
for i in range(epochs):
    #上記の図 autoencoder インスタンス。
    #train()↓学習では訓練データを入力して出力を求める。その後、出力と正解との誤差を誤差関数に従って計算し、誤差をバックプロパゲーションして、最後に結合パラメータを更新学習。
    #eval()推論ではテストデータを入力して出力を求め、実際の正解と一致した割合を求める。
    autoencoder.train()  # 訓練モード
    loss_train = 0
    for j, (x,) in enumerate(train_loader):  # ミニバッチ(x,)を取り出す
        x = x.cuda()  # GPU対応
        y = autoencoder(x)
        loss = loss_fnc(y, x)# 二乗和誤差
        loss_train += loss.item()#loss_trainに加算される。
        optimizer.zero_grad()
        #↑Backpropするときにgradientがたまる。
        #たまったgradientを消さないとそのまま残るから普段は学習loopの始まりで消す。
        #消さないと、前のgradient情報も残ってgradientの方向が最小値に向かない。
        loss.backward()#誤差逆伝搬
        optimizer.step()#今回はAdam
    loss_train /= j+1#バッチで割る。平均。
    record_loss_train.append(loss_train)#誤差記録

    if i%interval == 0:
        print("Epoch:", i, "Loss_Train:", loss_train)

誤差の推移

記録された誤差の、推移を確認します。

import matplotlib.pyplot as plt

plt.plot(range(len(record_loss_train)), record_loss_train, label="Train")
plt.legend()

plt.xlabel("Epochs")
plt.ylabel("Error")
plt.show()

誤差が滑らかに減少している様子が確認できます。

生成された画像の表示

画像が適切に再構築されているかどうか、中間層がどのような状態にあるのかを確認します。
入力画像と、再構築された画像を並べて表示します。
また、エンコーダーの出力も5☓5の画像として表示します。

n_img = batch_size  # 表示する画像の数
#loaderいろいろな情報が入っている様子。ただリストが入っているわけではない。
#つまり、ただスライスではデータを取り出すことができない。
#for 以外で取り出す方法は
#iter([バッチ化されたデータ]).next()[0]
#.next()[0]でリストで取り出すことができる。
x = iter(train_loader).next()[0]
x = x.cuda()#GPU使う
#train()↓学習では訓練データを入力して出力を求める。その後、出力と正解との誤差を誤差関数に従って計算し、誤差をバックプロパゲーションして、最後に結合パラメータを更新学習。
#eval()推論ではテストデータを入力して出力を求め、実際の正解と一致した割合を求める。
autoencoder.eval()  # 評価モード
m = autoencoder.encode(x)  # 中間層の状態
y = autoencoder(x)

# NumPyの配列に変換
x_sample = x.cpu().detach().numpy()
m_sample = m.cpu().detach().numpy()
y_sample = y.cpu().detach().numpy()

plt.figure(figsize=(n_img, 3))
for i in range(n_img):
    # 入力画像
    ax = plt.subplot(3, n_img, i+1)
    plt.imshow(x_sample[i].reshape(img_size, -1).tolist(), cmap="Greys_r")
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)

    # 中間層の出力
    ax = plt.subplot(3, n_img, i+1+n_img)
    #5☓5の中間ニューロン数25で作成したのでreshapeで5☓5の表示にする。
    plt.imshow(m_sample[i].reshape(5, -1).tolist(), cmap="Greys_r")
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    
    # 出力画像
    ax = plt.subplot(3, n_img, i+1+2*n_img)
    plt.imshow(y_sample[i].reshape(img_size, -1).tolist(), cmap="Greys_r")
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)

plt.show()

出力は入力をある程度再現した画像になっており、中間層は数字画像ごとに異なる状態となっています。
64ピクセルの画像を特徴付ける情報を、16の状態に圧縮できたことになります。
しかしながら、中間層の状態と出力画像の対応関係を直感的に把握したり、中間層の状態を調整して出力画像を変化させることは難しそうです。

この記事が気に入ったらサポートをしてみませんか?