人工知性を作りたい

私が日々、挑戦したことや学んだことなどを紹介していく雑記ブログです。 (新しいAI技術HTM, 専門の音声信号処理, 趣味のアニメ等も書いてます。)

【実装 変分オートエンコーダ(VAE)】プリコネキャラ画像を生成! Reshapeの形で異なる画像が出現する?!#実験結果 #RGB画像 #color #生成モデル

f:id:hiro-htm877:20200712105825p:plain

f:id:hiro-htm877:20200712105859p:plain



 

変分オートエンコーダ(VAE:Variational Auto Encoder)という生成モデルを用いて画像を生成します。

今回は学習データにアニメキャラ(カラー画像、RGB)を使用しました。

 

本記事のテーマ

【自作変分オートエンコーダを使った画像生成】

・3枚のキャラの画像を学習させて画像を生成する。使用する画像:

アニメ「プリンセスコネクト!Re:Dive」から

ペコリーヌ、コッコロ、キャル、ユイ、レイ、ヒヨリ

幅:96

縦:96

VAE Model (変分オートエンコーダのモデル)

潜在変数 :latent_size=2

    class Encoder(nn.Module):
    def __init__(self, latent_size, image_size):
        super(Encoder, self).__init__()
        self.H = int(image_size/2)
        self.W = int(image_size/2)
        self.latent_size = latent_size
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)
        self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1)
        self.conv3 = nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1)
        self.conv4 = nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1)
        self.linear1 = nn.Linear(128*self.H*self.W, 64)
        self.linear2 = nn.Linear(64, latent_size)
        self.linear3 = nn.Linear(64, latent_size)
        self.dropout1 = torch.nn.Dropout2d(p=0.2) 

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = F.relu(self.conv4(x))
        x = x.view(x.shape[0],-1)
        x = F.relu(self.linear1(x))
        z_mean = self.linear2(x)
        z_log_var = self.linear3(x)
        return z_mean, z_log_var

    def sampling(self, z_mean, z_log_var):
      epsilon = torch.randn(z_mean.shape, device="cuda")
      return z_mean + epsilon * torch.exp(0.5*z_log_var)

class Decoder(nn.Module):
    def __init__(self, image_size):
        super().__init__()
        self.H = int(image_size/2)
        self.W = int(image_size/2)
        self.to_shape = (128, self.H, self.W)  # (C, H, W)
        self.linear = nn.Linear(2, 2*np.prod(self.to_shape))
        self.deconv1 = nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1)
        self.deconv2 = nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1)
        self.conv = nn.Conv2d(64, 3, kernel_size=3, stride=1, padding=1)
        self.conv2 = nn.Conv2d(128, 64, kernel_size=3, stride=1, padding=1)
        # self.dropout1 = torch.nn.Dropout2d(p=0.2) 
        self.leakyrelu = nn.LeakyReLU(0.2, inplace=True)

    def forward(self, x):
        x = self.leakyrelu(self.linear(x))
        x = x.view(-1, 256, self.H, self.W)  # reshape to (-1, C, H, W)
        x = self.leakyrelu(self.deconv1(x))
        x = self.leakyrelu(self.conv2(x))
        x = self.conv(x)
        x = torch.sigmoid(x)
        return x


class VAE(nn.Module):
    def __init__(self, latent_size, image_size):
        super(VAE, self).__init__()
        self.encoder = Encoder(latent_size, image_size)
        self.decoder = Decoder(image_size)

    def forward(self, x, C=1.0, k=1):
        """Call loss function of VAE.
        The loss value is equal to ELBO (Evidence Lower Bound)
        multiplied by -1.

        Args:
            x (Variable or ndarray): Input variable.
            C (int): Usually this is 1.0. Can be changed to control the
                second term of ELBO bound, which works as regularization.
            k (int): Number of Monte Carlo samples used in encoded vector.
        """
        z_mean, z_log_var = self.encoder(x)

        rec_loss = 0
        for l in range(k):
            z = self.encoder.sampling(z_mean, z_log_var)
            if(DEBUG):print("latent: ", z.shape)
            if(DEBUG):print("latent data: ", z)
            y = self.decoder(z)
            rec_loss += F.binary_cross_entropy(torch.flatten(y, start_dim=1), torch.flatten(x, start_dim=1)) / k

        kl_loss = C * (z_mean ** 2 + torch.exp(z_log_var) - z_log_var - 1) * 0.5
        kl_loss = torch.sum(kl_loss) / len(x)
        return rec_loss + kl_loss

 

実験結果

5%-95%を15等分したパーセント点関数を潜在変数のx,yとして画像を生成する。

ペコリーヌ、コッコロ、キャル

・ペコリーヌ

f:id:hiro-htm877:20200712110209p:plain

・コッコロ

f:id:hiro-htm877:20200712110225p:plain

・キャル

f:id:hiro-htm877:20200712110236p:plain


 

・学習100

f:id:hiro-htm877:20200712110409p:plain



・学習1500

f:id:hiro-htm877:20200712110842p:plain



・学習5000

ペコリーヌ(オレンジ)とコッコロ(白)のため全体的にオレンジっぽくなっている。

ただし、真ん中辺りはキャルの黒色っぽくなっている

f:id:hiro-htm877:20200712110909p:plain

ペコリーヌ、コッコロ、キャル、ユイ、レイ、ヒヨリ

・ユイ

f:id:hiro-htm877:20200712110250p:plain

・レイ

f:id:hiro-htm877:20200712110302p:plain

・ヒヨリ

f:id:hiro-htm877:20200712110313p:plain

・学習500

f:id:hiro-htm877:20200712111403p:plain



・学習1000

f:id:hiro-htm877:20200712111417p:plain



・学習5000

f:id:hiro-htm877:20200712111432p:plain

つづいて、

5%-95%を4等分したパーセント点関数を潜在変数のx,yとして画像を生成する。

・学習2000

真ん中が闇落ちしてる(笑)

f:id:hiro-htm877:20200712111823p:plain

・学習5000

 個人的には左上[x,y]=[0,0]が好き(そんな変わらんけど)

f:id:hiro-htm877:20200712111843p:plain

疑問(reshapeしても画像として認識できる)

Decoderの出力は(3,96,96)としている。

そのため、上記の画像は(1,96,96)を一つずつ3つ取り出して、(96,96,3)のRGBの画像データに変換して表示している。

もう一つの方法で、単純にDecoderの出力(3,96,96)をreshape(96,96,3)とした場合も画像が生成されていることに気づいた。

つまり、一つのデータで2つの意味=画像を表現できていることに驚いた。

機械学習について学びたてのため良く分かっていないが、一つのデータを色々な側面から見ることで異なる意味のあるものになるのであれば、暗号化、圧縮などに用いれるのではないのかと考えた。

 

■(3,96,96).reshape(96,96,3)

5%-95%を4等分したパーセント点関数を潜在変数のx,yとして画像を生成する。

 ですので、小さいキャラ3x3が一枚(96,96,3)の出力

・学習2000

左上を[x,y]=[0,0]とする。

おそらく、y=1,4,7,10がRed、y=2,5,8,11がGreen、y=3,6,9,12がBlueを学習している。

[12,3]辺りはBleuの強いレイっぽく感じるし、一番下[:, 12]はRGB値がどれも大きい白のコッコロっぽく感じる。

f:id:hiro-htm877:20200712120228p:plain

・学習5000

f:id:hiro-htm877:20200712120348p:plain

 

考察

グレースケールよりはRGBを使用したほうが過学習しにくかったように感じる。

ただし、データオーギュメンテーションで画像を増やした場合との比較も行いたい。

■疑問について

学習モデルの形でreshapeした結果は変わってくると思うので、今回の疑問を解決することでよりニューラルネットワークについて理解できる気がした。

 まずはnumpy,pytorchのreshapeについて理解しよう!

実装コード(GitHub)

GitHub - hiro877/VAE_Anime-Character-Image

 

関連記事

 

www.hiro877.com

 

 

www.hiro877.com

 

参考資料

・ゼロから作るDeepLearning3

github.com