GAN入門(2)〜簡単なGANの実装〜

# 前置き
GANについての知識をきちんと整理しようと思い、最終的にはNLPや音声認識に応用することを目標にGANについて学習・実装していこうという試み。
[前回](https://qiita.com/dekamisako/items/f6df866b5d4218c22920)は知識の整理をしたので今回は簡単に実装してみる。(活性化層と全結合層のみを採用)

# 目次

- 実装するもの
- 実装
  - データの読み込み
  - 生成器・識別器
  - 訓練部分
- 最後に

# 実装するもの
MNISTのデータセットを用いた簡単なGANによる手書き文字の生成。
ここに[実装](https://github.com/shelley-chama/GANs/tree/master/systems/simple_GAN)をあげる。
指摘等あればお願いします。

# 実装
全体構成は大きく３部分に分かれている。

- データの読み込み
- ネットワーク(生成器・識別器)
- 訓練部分(主に損失関数の計算)

## データの読み込み
```python:data_load.py
def data_loader(batch_size, train=True, data_dir='./../../data'):
	"""
	params)
		train: True->train mode, False->test mode
	"""
	# ここで前処理を定義
	transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, ), (0.5, ))])
	# データセットと、それに施す前処理を定義
	dataset = torchvision.datasets.MNIST(root=data_dir, train=True, download=True, transform=transform)
	# データセットに対するデータローダーを定義
	data_loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=2)
	return data_loader

```

引数についてはこんな感じ

- batch_size: バッチサイズを指定
- train: 学習かテストどちらで使うか指定
- data_dir: MNISTのデータの保存先のディレクトリを指定

pytorchでは学習データのサンプリングにDataLoaderクラスを使うのが便利。
流れはコメントに記述。



## 生成器・識別器
今回は活性化層と全結合層のみを採用したかなりシンプルなGANを実装。

```python:generator.py
class Generator(nn.Module):
    def __init__(self, img_size, z_dim):
        """
        img_size: channels * rows * cols 
        """
        super(Generator, self).__init__()
        self.img_size = img_size

        self.affine1 = nn.Linear(z_dim, 128)
        self.activation_layer1 = nn.LeakyReLU(0.01)
        self.affine2 = nn.Linear(128, 28 * 28 * 1)
        self.activation_layer2 = nn.Tanh()
        
        initialize_weights(self)

    def forward(self, input):
        x = self.affine1(input)
        x = self.activation_layer1(x)
        x = self.affine2(x)
        x = self.activation_layer2(x)
        
        channels, rows, cols = self.img_size
        # MNIST画像の型にはめる
        x = torch.reshape(x, (-1, channels, rows, cols))
        return x
```
生成器はz_dimで指定した次元の乱数を画像のshapeに変換する。

```python:discriminator.py
class Flatten(nn.Module):
    def forward(self, input):
        return input.view(input.size(0), -1)

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.flatten = Flatten()
        self.affine1 = nn.Linear(28 * 28 * 1, 128)
        self.activation_layer1 = nn.LeakyReLU(0.01)
        self.affine2 = nn.Linear(128, 1)
        self.activation_layer2 = nn.Sigmoid()
        
        initialize_weights(self)
    
    def forward(self, input):
        x = self.flatten(input)
        x = self.affine1(x)
        x = self.activation_layer1(x)
        x = self.affine2(x)
        x = self.activation_layer2(x)
        return x
```

識別器側では受け取った画像が本物であるか偽物であるかの確率を出力する。
特徴としては

- Flattenクラスで画像データを一次元配列に変換して入力
- シグモイド関数で0~1の間の数に変換して出力結果を確率として扱えるようにする

## 訓練部分
識別器の損失計算(G:生成器, D:識別器)

```python
#二値交差エントロピー損失
criterion = nn.BCELoss()

# y_real: 1, y_fake: 0
D_real = D(real_images)
D_real_loss = criterion(D_real, y_real)

# detachによってGとDを分離
fake_images = G(z)
D_fake = D(fake_images.detach())
D_fake_loss = criterion(D_fake, y_fake)

D_loss = D_real_loss + D_fake_loss
```

- 識別器の重みだけを更新するため、生成された画像をから生成器の重みを更新しないようにdetach()を使う
- 「偽の画像の識別」と「本物の画像の識別」、両方の損失の和を識別器の損失とする


生成器の損失計算

```python

# Generatorの訓練
G_optimizer.zero_grad()

fake_images = G(z)
D_fake = D(fake_images)
# y_real: 1, y_fake: 0
G_loss = criterion(D_fake, y_real)
```

- 生成された画像と本物との識別誤差を計算する(本物っぽいものを生成するように学習)

# 最後に
GANの最もシンプルなのではないかというモデルを実装してみた。
生成器と識別器の損失計算の仕方が他のニューラルネットと比べて特殊ではあるが、それ以外はいわゆる深層学習の実装と同じなので、なんとかなりそう。
次回は応用的なGANの実装や側面に触れていきたい。