変分オートエンコーダ(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として画像を生成する。
ペコリーヌ、コッコロ、キャル
・ペコリーヌ
・コッコロ
・キャル
・学習100
・学習1500
・学習5000
ペコリーヌ(オレンジ)とコッコロ(白)のため全体的にオレンジっぽくなっている。
ただし、真ん中辺りはキャルの黒色っぽくなっている
ペコリーヌ、コッコロ、キャル、ユイ、レイ、ヒヨリ
・ユイ
・レイ
・ヒヨリ
・学習500
・学習1000
・学習5000
つづいて、
5%-95%を4等分したパーセント点関数を潜在変数のx,yとして画像を生成する。
・学習2000
真ん中が闇落ちしてる(笑)
・学習5000
個人的には左上[x,y]=[0,0]が好き(そんな変わらんけど)
疑問(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値がどれも大きい白のコッコロっぽく感じる。
・学習5000
考察
グレースケールよりはRGBを使用したほうが過学習しにくかったように感じる。
ただし、データオーギュメンテーションで画像を増やした場合との比較も行いたい。
■疑問について
学習モデルの形でreshapeした結果は変わってくると思うので、今回の疑問を解決することでよりニューラルネットワークについて理解できる気がした。
まずはnumpy,pytorchのreshapeについて理解しよう!
実装コード(GitHub)
GitHub - hiro877/VAE_Anime-Character-Image
関連記事
参考資料
・ゼロから作るDeepLearning3