変分オートエンコーダ(VAE:Variational Auto Encoder)という生成モデルを用いて画像を生成します。
今回は学習データにアニメキャラ(グレースケール)を使用しました。
本記事のテーマ
【自作変分オートエンコーダを使った画像生成】
・3枚のキャラの画像を学習させて画像を生成する。使用する画像:
アニメ「プリンセスコネクト!Re:Dive」から
ペコリーヌ、コッコロ、キャル、ユイ、レイ、ヒヨリ
幅:90
縦:90
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(1, 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, 1, kernel_size=3, stride=1, padding=1) self.conv2 = nn.Conv2d(128, 64, kernel_size=3, stride=1, padding=1) 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) 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として画像を生成する。
ペコリーヌ、コッコロ、キャル
・ペコリーヌ
・コッコロ
・キャル
・学習0
・学習100
・学習200
・学習1000
上段、右端辺りはコッコロっぽい。
殆どがキャルっぽい。
ペコリーヌはどこかへ行ってしまったのか?
・学習5000
平均的な画像がキャルっぽくなった
ただ、目などはキャルではなく、平均っぽい
ペコリーヌ、コッコロ、キャル、ユイ、レイ、ヒヨリ
・ユイ
・レイ
・ヒヨリ
・学習500
・学習1000
・学習5000
考察
今回は学習画像を3枚、6枚としたため、過学習となり、潜在変数の値を変えても同じような画像しか生成されていなかった。
データオーギュメンテーションで画像を増やした場合との比較も行いたい。
実装コード(GitHub)
GitHub - hiro877/VAE_Anime-Character-Image
関連記事
参考資料
・ゼロから作るDeepLearning3