ほぼ個人メモです。
PytorchでVAEによる画像生成を試した。
GitHubでスターが多い以下のリポジトリを使用した。
GitHub - AntixK/PyTorch-VAE: A Collection of Variational Autoencoders (VAE) in PyTorch.
以下に、手順のメモを示す。
手順
データセット(CelebA dataset)ダウンロード
CelebAは、20万を超える有名人の画像を含む大規模な顔属性データセットである。
Googleドライブからダウンロードできる。
gdownを使用して、IDを指定してダウンロードする。
pip install gdown gdown --id 1m8-EBPgi5MRubrm6iQjafK2QMHDBMSfJ
ダウンロードしたzipを解凍
unzip celeba.zip -d Data unzip Data/celeba/img_align_celeba.zip -d Data/celeba
訓練実行
python run.py -c configs/vae.yaml
実行結果の最終行は以下のように表示される。
Epoch 99: 100%|███████| 2856/2856 [01:25<00:00, 33.33it/s, loss=0.0195, v_num=1]
結果確認
結果は、logsに出力される。
loss等のグラフは、tensorboardで確認できる。
tensorboard --logdir logs/VanillaVAE/version_0
画像生成のサンプルは、
logs/VanillaVAE/version_0/Samples
にpngでエポック毎に出力されている。
100エポック学習した時点のサンプル画像

なお、サンプル画像を見ると、数エポックで学習できており、100エポックも学習する必要はなかったようだ。
画像生成
学習済みモデルを使用して、1枚ずつ画像を生成するためのスクリプトは用意されていないため、自分でスクリプトを記述する必要があった。
以下は、Jupyter Notebookでの実行例である。
%matplotlib inline
import matplotlib.pyplot as plt
import torch
ckpt = torch.load('logs/VanillaVAE/version_0/checkpoints/last.ckpt')
import yaml
config = yaml.safe_load(open('configs/vae.yaml'))
from models import *
model = vae_models[config['model_params']['name']](**config['model_params'])
from experiment import VAEXperiment
experiment = VAEXperiment(model, config['exp_params'])
experiment.load_state_dict(ckpt['state_dict'])
device = torch.device("cpu")
img = experiment.model.sample(1, device).squeeze().permute(1,2,0).detach().numpy()
plt.imshow(img)以下のように、生成した画像が表示される。

img = experiment.model.sample(1, device).squeeze().permute(1,2,0).detach().numpy() plt.imshow(img)
をもう一度実行すると、別の画像が生成される。

モーフィング
潜在空間でサンプリングした2点を線形補完することで、モーフィングを行う。
# モーフィング
z = torch.empty(10, experiment.model.latent_dim)
src = torch.randn(2, experiment.model.latent_dim)
for i in range(10):
alpha = i / 9
z[i] = (1 - alpha) * src[0] + alpha * src[1]
samples = experiment.model.decode(z)
imgs = samples.permute(0,2,3,1).detach().numpy()
plt.figure(figsize=(12,8))
for i in range(10):
plt.subplot(1, 10, i + 1)
plt.imshow(imgs[i])
演算
データセットに付けられたラベルを利用して、ラベルに対応する潜在空間のベクトルを算出して、潜在空間でベクトルの加算を行う。
笑顔のラベル(Smiling)に対して、ベクトルの加算を行った結果は以下の通り。
from dataset import VAEDataset
data = VAEDataset(**config["data_params"], pin_memory=len(config['trainer_params']['gpus']) != 0)
data.setup()
smiling_label = data.train_dataset.attr_names.index('Smiling')
# 潜在空間でのベクトルを算出
pos_sum = torch.zeros(experiment.model.latent_dim)
neg_sum = torch.zeros(experiment.model.latent_dim)
pos_count = 0
neg_count = 0
experiment.model.eval()
for x, target in train_dataloader:
pos_sel = target[:,smiling_label] == 1
neg_sel = target[:,smiling_label] == 0
mu, log_var = experiment.model.encode(x)
z = experiment.model.reparameterize(mu, log_var).detach()
pos_sum += torch.sum(z[pos_sel], 0)
neg_sum += torch.sum(z[neg_sel], 0)
pos_count += pos_sel.sum()
neg_count += neg_sel.sum()
pos_avr = pos_sum / pos_count
neg_avr = neg_sum / neg_count
vec = pos_avr - neg_avr
# サンプリングした点に対して潜在空間でベクトルを加算
z = torch.empty(10, experiment.model.latent_dim)
src = torch.randn(experiment.model.latent_dim)
for i in range(10):
alpha = i - 5
z[i] = src + alpha * vec
samples = experiment.model.decode(z)
imgs = samples.permute(0,2,3,1).detach().numpy()
plt.figure(figsize=(12,8))
for i in range(10):
plt.subplot(1, 10, i + 1)
plt.imshow(imgs[i])
5番目がサンプリング点から生成した画像で、左に行くほど負のベクトルを大きく、右に行くほど正のベクトルを大きくしている。
右に行くほど顔の表情が笑顔になっていることが確認できる。