Geleneksel otomatik kodlayıcılarla aynı şekilde VAE mimarisinin de iki bölümü vardır: kodlayıcı ve kod çözücü. Geleneksel AE modelleri, girdileri gizli uzay vektörüne eşler ve bu vektörden çıkan çıktıyı yeniden oluşturur. VAE, girdileri çok değişkenli bir normal dağılıma eşler (kodlayıcı, her bir gizli boyutun ortalamasını ve varyansını çıkarır). VAE kodlayıcı bir dağılım ürettiğinden, bu dağılımdan örnekleme yapılarak ve örneklenen gizli vektörün kod çözücüye geçirilmesiyle yeni veriler üretilebilir. Çıkış görüntüleri oluşturmak için üretilen dağıtımdan örnekleme yapmak, VAE'nin giriş verilerine benzer ancak aynı olan yeni verilerin oluşturulmasına olanak sağladığı anlamına gelir. Bu makale VAE mimarisinin bileşenlerini araştırıyor ve VAE modelleriyle yeni görüntüler oluşturmanın (örnekleme) çeşitli yollarını sunuyor. Kodun tamamı mevcuttur. Google Colab'da 1 VAE Modelinin Uygulanması Otomatik Kodlayıcılar ve Değişken Otomatik Kodlayıcıların her ikisinin de iki bölümü vardır: kodlayıcı ve kod çözücü. AE'nin kodlayıcı sinir ağı, her görüntüyü gizli uzaydaki tek bir vektöre eşlemeyi öğrenir ve kod çözücü, orijinal görüntüyü kodlanmış gizli vektörden yeniden oluşturmayı öğrenir. VAE'nin kodlayıcı sinir ağı, gizli alanın her boyutu için bir olasılık dağılımını (çok değişkenli dağılım) tanımlayan parametrelerin çıktısını verir. Her girdi için kodlayıcı, gizli uzayın her boyutu için bir ortalama ve bir varyans üretir. Çıkış ortalaması ve varyansı, çok değişkenli bir Gauss dağılımını tanımlamak için kullanılır. Kod çözücü sinir ağı AE modellerindekiyle aynıdır. 1.1 VAE Kayıpları Bir VAE modelinin eğitiminin amacı, sağlanan gizli vektörlerden gerçek görüntüler üretme olasılığını en üst düzeye çıkarmaktır. Eğitim sırasında VAE modeli iki kaybı en aza indirir: - giriş görüntüleri ile kod çözücünün çıkışı arasındaki fark. yeniden yapılanma kaybı (KL Diverjans, iki olasılık dağılımı arasındaki istatistiksel mesafe) - kodlayıcı çıktısının olasılık dağılımı ile bir önceki dağılım (standart normal dağılım) arasındaki mesafe, gizli alanın düzenlenmesine yardımcı olur. Kullback-Leibler sapma kaybı 1.2 Yeniden Yapılanma Kaybı Yaygın yeniden yapılandırma kayıpları ikili çapraz entropi (BCE) ve ortalama kare hatasıdır (MSE). Bu yazımda demo için MNIST veri setini kullanacağım. MNIST görüntülerinin tek kanalı vardır ve pikseller 0 ile 1 arasında değerler alır. Bu durumda BCE kaybı, MNIST görüntülerinin piksellerini Bernoulli dağılımını takip eden ikili rastgele değişken olarak ele almak için yeniden yapılandırma kaybı olarak kullanılabilir. reconstruction_loss = nn.BCELoss(reduction='sum') 1.3 Kullback-Leibler Ayrımı Yukarıda belirtildiği gibi - KL diverjansı iki dağılım arasındaki farkı değerlendirir. Uzaklığın simetrik bir özelliğine sahip olmadığına dikkat edin: KL(P‖Q)!=KL(Q‖P). Karşılaştırılması gereken iki dağılım şunlardır: giriş görüntüleri verilen kodlayıcı çıkışının gizli alanı x: q(z|x) öncesi gizli uzayının, ortalaması sıfır ve her bir gizli uzay boyutunda standart sapması bir olan normal bir dağılım olduğu varsayılır. p(z) N(0, ) I Böyle bir varsayım, KL diverjans hesaplamasını basitleştirir ve gizli alanın bilinen, yönetilebilir bir dağılımı takip etmesini teşvik eder. from torch.distributions.kl import kl_divergence def kl_divergence_loss(z_dist): return kl_divergence(z_dist, Normal(torch.zeros_like(z_dist.mean), torch.ones_like(z_dist.stddev)) ).sum(-1).sum() 1.4 Kodlayıcı class Encoder(nn.Module): def __init__(self, im_chan=1, output_chan=32, hidden_dim=16): super(Encoder, self).__init__() self.z_dim = output_chan self.encoder = nn.Sequential( self.init_conv_block(im_chan, hidden_dim), self.init_conv_block(hidden_dim, hidden_dim * 2), # double output_chan for mean and std with [output_chan] size self.init_conv_block(hidden_dim * 2, output_chan * 2, final_layer=True), ) def init_conv_block(self, input_channels, output_channels, kernel_size=4, stride=2, padding=0, final_layer=False): layers = [ nn.Conv2d(input_channels, output_channels, kernel_size=kernel_size, padding=padding, stride=stride) ] if not final_layer: layers += [ nn.BatchNorm2d(output_channels), nn.ReLU(inplace=True) ] return nn.Sequential(*layers) def forward(self, image): encoder_pred = self.encoder(image) encoding = encoder_pred.view(len(encoder_pred), -1) mean = encoding[:, :self.z_dim] logvar = encoding[:, self.z_dim:] # encoding output representing standard deviation is interpreted as # the logarithm of the variance associated with the normal distribution # take the exponent to convert it to standard deviation return mean, torch.exp(logvar*0.5) 1.5 Kod Çözücü class Decoder(nn.Module): def __init__(self, z_dim=32, im_chan=1, hidden_dim=64): super(Decoder, self).__init__() self.z_dim = z_dim self.decoder = nn.Sequential( self.init_conv_block(z_dim, hidden_dim * 4), self.init_conv_block(hidden_dim * 4, hidden_dim * 2, kernel_size=4, stride=1), self.init_conv_block(hidden_dim * 2, hidden_dim), self.init_conv_block(hidden_dim, im_chan, kernel_size=4, final_layer=True), ) def init_conv_block(self, input_channels, output_channels, kernel_size=3, stride=2, padding=0, final_layer=False): layers = [ nn.ConvTranspose2d(input_channels, output_channels, kernel_size=kernel_size, stride=stride, padding=padding) ] if not final_layer: layers += [ nn.BatchNorm2d(output_channels), nn.ReLU(inplace=True) ] else: layers += [nn.Sigmoid()] return nn.Sequential(*layers) def forward(self, z): # Ensure the input latent vector z is correctly reshaped for the decoder x = z.view(-1, self.z_dim, 1, 1) # Pass the reshaped input through the decoder network return self.decoder(x) 1.6 VAE Modeli Rastgele bir örnek üzerinden geriye yayılım yapmak için, parametreler aracılığıyla gradyan hesaplamasına izin vermek üzere rastgele örneğin parametrelerini ( ve 𝝈) fonksiyonun dışına taşımanız gerekir. Bu adıma aynı zamanda "yeniden parametrelendirme numarası" da denir. μ PyTorch'ta, kodlayıcının ve 𝝈 çıktılarıyla bir dağılım oluşturabilir ve yeniden parametrelendirme hilesini uygulayan yöntemiyle bundan örnek alabilirsiniz: ile aynıdır μ Normal rsample() torch.randn(z_dim) * stddev + mean) class VAE(nn.Module): def __init__(self, z_dim=32, im_chan=1): super(VAE, self).__init__() self.z_dim = z_dim self.encoder = Encoder(im_chan, z_dim) self.decoder = Decoder(z_dim, im_chan) def forward(self, images): z_dist = Normal(self.encoder(images)) # sample from distribution with reparametarazation trick z = z_dist.rsample() decoding = self.decoder(z) return decoding, z_dist 1.7 VAE Eğitimi MNIST eğitim ve test verilerini yükleyin. transform = transforms.Compose([transforms.ToTensor()]) # Download and load the MNIST training data trainset = datasets.MNIST('.', download=True, train=True, transform=transform) train_loader = DataLoader(trainset, batch_size=64, shuffle=True) # Download and load the MNIST test data testset = datasets.MNIST('.', download=True, train=False, transform=transform) test_loader = DataLoader(testset, batch_size=64, shuffle=True) Yukarıdaki şekilde görselleştirilen VAE eğitim adımlarını takip eden bir eğitim döngüsü oluşturun. def train_model(epochs=10, z_dim = 16): model = VAE(z_dim=z_dim).to(device) model_opt = torch.optim.Adam(model.parameters()) for epoch in range(epochs): print(f"Epoch {epoch}") for images, step in tqdm(train_loader): images = images.to(device) model_opt.zero_grad() recon_images, encoding = model(images) loss = reconstruction_loss(recon_images, images)+ kl_divergence_loss(encoding) loss.backward() model_opt.step() show_images_grid(images.cpu(), title=f'Input images') show_images_grid(recon_images.cpu(), title=f'Reconstructed images') return model z_dim = 8 vae = train_model(epochs=20, z_dim=z_dim) 1.8 Gizli Alanı Görselleştirin def visualize_latent_space(model, data_loader, device, method='TSNE', num_samples=10000): model.eval() latents = [] labels = [] with torch.no_grad(): for i, (data, label) in enumerate(data_loader): if len(latents) > num_samples: break mu, _ = model.encoder(data.to(device)) latents.append(mu.cpu()) labels.append(label.cpu()) latents = torch.cat(latents, dim=0).numpy() labels = torch.cat(labels, dim=0).numpy() assert method in ['TSNE', 'UMAP'], 'method should be TSNE or UMAP' if method == 'TSNE': tsne = TSNE(n_components=2, verbose=1) tsne_results = tsne.fit_transform(latents) fig = px.scatter(tsne_results, x=0, y=1, color=labels, labels={'color': 'label'}) fig.update_layout(title='VAE Latent Space with TSNE', width=600, height=600) elif method == 'UMAP': reducer = umap.UMAP() embedding = reducer.fit_transform(latents) fig = px.scatter(embedding, x=0, y=1, color=labels, labels={'color': 'label'}) fig.update_layout(title='VAE Latent Space with UMAP', width=600, height=600 ) fig.show() visualize_latent_space(vae, train_loader, device='cuda' if torch.cuda.is_available() else 'cpu', method='UMAP', num_samples=10000) 2 VAE ile Örnekleme Değişken Otomatik Kodlayıcıdan (VAE) örnekleme, eğitim sırasında görülene benzer yeni verilerin oluşturulmasını sağlar ve VAE'yi geleneksel AE mimarisinden ayıran benzersiz bir özelliktir. Bir VAE'den numune almanın birkaç yolu vardır: Sağlanan bir girdi verildiğinde son dağılımdan örnekleme. Arka örnekleme: standart normal çok değişkenli bir dağılım varsayarak gizli alandan örnekleme. Bu, gizli değişkenlerin normal şekilde dağıldığı varsayımı (VAE eğitimi sırasında kullanılan) nedeniyle mümkündür. Bu yöntem, belirli özelliklere sahip verilerin üretilmesine (örneğin, belirli bir sınıftan veri üretilmesine) izin vermez. ön örnekleme: : Gizli uzaydaki iki nokta arasındaki enterpolasyon, gizli uzay değişkenindeki değişikliklerin, oluşturulan verilerdeki değişikliklere nasıl karşılık geldiğini ortaya çıkarabilir. enterpolasyon : VAE'nin gizli boyutlarının çaprazlanması verilerin gizli uzay varyansı her boyuta bağlıdır. Geçiş, gizli vektörün seçilen bir boyut ve kendi aralığında seçilen boyutun değişen değerleri dışında tüm boyutlarının sabitlenmesiyle yapılır. Gizli alanın bazı boyutları, verinin belirli niteliklerine karşılık gelebilir (VAE'nin bu davranışı zorlayacak özel mekanizmaları yoktur, ancak bu gerçekleşebilir). gizli boyutların çaprazlanması Örneğin gizli uzaydaki bir boyut, bir yüzün duygusal ifadesini veya bir nesnenin yönelimini kontrol edebilir. Her örnekleme yöntemi, VAE'nin gizli alanı tarafından yakalanan veri özelliklerini keşfetmenin ve anlamanın farklı bir yolunu sağlar. 2.1 Arka Örnekleme (Verilen Bir Giriş Görüntüsünden) def posterior_sampling(model, data_loader, n_samples=25): model.eval() images, _ = next(iter(data_loader)) images = images[:n_samples] with torch.no_grad(): _, encoding_dist = model(images.to(device)) input_sample=encoding_dist.sample() recon_images = model.decoder(input_sample) show_images_grid(images, title=f'input samples') show_images_grid(recon_images, title=f'generated posterior samples') posterior_sampling(vae, train_loader, n_samples=30) Arka örnekleme, düşük değişkenliğe sahip gerçekçi veri örneklerinin oluşturulmasına olanak tanır: çıktı verileri, girdi verilerine benzer. 2.2 Ön Örnekleme (Rastgele Gizli Uzay Vektöründen) def prior_sampling(model, z_dim=32, n_samples = 25): model.eval() input_sample=torch.randn(n_samples, z_dim).to(device) with torch.no_grad(): sampled_images = model.decoder(input_sample) show_images_grid(sampled_images, title=f'generated prior samples') prior_sampling(vae, z_dim, n_samples=40) N(0, ) ile ön örnekleme her zaman makul veriler üretmez ancak yüksek değişkenliğe sahiptir. I 2.3 Sınıf Merkezlerinden Örnekleme Her sınıfın ortalama kodlamaları tüm veri kümesinden toplanabilir ve daha sonra kontrollü (koşullu üretim) için kullanılabilir. def get_data_predictions(model, data_loader): model.eval() latents_mean = [] latents_std = [] labels = [] with torch.no_grad(): for i, (data, label) in enumerate(data_loader): mu, std = model.encoder(data.to(device)) latents_mean.append(mu.cpu()) latents_std.append(std.cpu()) labels.append(label.cpu()) latents_mean = torch.cat(latents_mean, dim=0) latents_std = torch.cat(latents_std, dim=0) labels = torch.cat(labels, dim=0) return latents_mean, latents_std, labels def get_classes_mean(class_to_idx, labels, latents_mean, latents_std): classes_mean = {} for class_name in train_loader.dataset.class_to_idx: class_id = train_loader.dataset.class_to_idx[class_name] labels_class = labels[labels==class_id] latents_mean_class = latents_mean[labels==class_id] latents_mean_class = latents_mean_class.mean(dim=0, keepdims=True) latents_std_class = latents_std[labels==class_id] latents_std_class = latents_std_class.mean(dim=0, keepdims=True) classes_mean[class_id] = [latents_mean_class, latents_std_class] return classes_mean latents_mean, latents_stdvar, labels = get_data_predictions(vae, train_loader) classes_mean = get_classes_mean(train_loader.dataset.class_to_idx, labels, latents_mean, latents_stdvar) n_samples = 20 for class_id in classes_mean.keys(): latents_mean_class, latents_stddev_class = classes_mean[class_id] # create normal distribution of the current class class_dist = Normal(latents_mean_class, latents_stddev_class) percentiles = torch.linspace(0.05, 0.95, n_samples) # get samples from different parts of the distribution using icdf # https://pytorch.org/docs/stable/distributions.html#torch.distributions.distribution.Distribution.icdf class_z_sample = class_dist.icdf(percentiles[:, None].repeat(1, z_dim)) with torch.no_grad(): # generate image directly from mean class_image_prototype = vae.decoder(latents_mean_class.to(device)) # generate images sampled from Normal(class mean, class std) class_images = vae.decoder(class_z_sample.to(device)) show_image(class_image_prototype[0].cpu(), title=f'Class {class_id} prototype image') show_images_grid(class_images.cpu(), title=f'Class {class_id} images') Ortalaması μ sınıfı olan normal bir dağılımdan örnekleme, aynı sınıftan yeni verilerin üretilmesini garanti eder. 2.4 Enterpolasyon def linear_interpolation(start, end, steps): # Create a linear path from start to end z = torch.linspace(0, 1, steps)[:, None].to(device) * (end - start) + start # Decode the samples along the path vae.eval() with torch.no_grad(): samples = vae.decoder(z) return samples 2.4.1 İki Rastgele Gizli Vektör Arasında İnterpolasyon start = torch.randn(1, z_dim).to(device) end = torch.randn(1, z_dim).to(device) interpolated_samples = linear_interpolation(start, end, steps = 24) show_images_grid(interpolated_samples, title=f'Linear interpolation between two random latent vectors') 2.4.2 İki Sınıf Merkezi Arasında İnterpolasyon for start_class_id in range(1,10): start = classes_mean[start_class_id][0].to(device) for end_class_id in range(1, 10): if end_class_id == start_class_id: continue end = classes_mean[end_class_id][0].to(device) interpolated_samples = linear_interpolation(start, end, steps = 20) show_images_grid(interpolated_samples, title=f'Linear interpolation between classes {start_class_id} and {end_class_id}') 2.5 Gizli Uzay Geçişi Gizli vektörün her boyutu normal bir dağılımı temsil eder; Boyutun değer aralığı, boyutun ortalaması ve varyansı tarafından kontrol edilir. Değer aralığını geçmenin basit bir yolu, normal dağılımın ters CDF'sini (kümülatif dağılım fonksiyonları) kullanmak olacaktır. ICDF, 0 ile 1 arasında (olasılığı temsil eden) bir değer alır ve dağılımdan bir değer döndürür. Belirli bir olasılığı için ICDF, rastgele bir değişkenin <= olma olasılığı verilen olasılığına eşit olacak şekilde bir değeri çıkarır? p p_icdf p p_icdf Normal bir dağılımınız varsa, icdf(0,5) dağılımın ortalamasını döndürmelidir. icdf(0,95), dağılımdaki verilerin %95'inden daha büyük bir değer döndürmelidir. 2.5.1 Tek Boyutlu Gizli Uzay Geçişi def latent_space_traversal(model, input_sample, norm_dist, dim_to_traverse, n_samples, latent_dim, device): # Create a range of values to traverse assert input_sample.shape[0] == 1, 'input sample shape should be [1, latent_dim]' # Generate linearly spaced percentiles between 0.05 and 0.95 percentiles = torch.linspace(0.1, 0.9, n_samples) # Get the quantile values corresponding to the percentiles traversed_values = norm_dist.icdf(percentiles[:, None].repeat(1, z_dim)) # Initialize a latent space vector with zeros z = input_sample.repeat(n_samples, 1) # Assign the traversed values to the specified dimension z[:, dim_to_traverse] = traversed_values[:, dim_to_traverse] # Decode the latent vectors with torch.no_grad(): samples = model.decoder(z.to(device)) return samples for class_id in range(0,10): mu, std = classes_mean[class_id] with torch.no_grad(): recon_images = vae.decoder(mu.to(device)) show_image(recon_images[0], title=f'class {class_id} mean sample') for i in range(z_dim): interpolated_samples = latent_space_traversal(vae, mu, norm_dist=Normal(mu, torch.ones_like(mu)), dim_to_traverse=i, n_samples=20, latent_dim=z_dim, device=device) show_images_grid(interpolated_samples, title=f'Class {class_id} dim={i} traversal') Tek bir boyutun çaprazlanması, rakam stilinde veya kontrol rakamı yönünde bir değişikliğe neden olabilir. 2.5.3 İki Boyutlu Gizli Uzay Geçişi def traverse_two_latent_dimensions(model, input_sample, z_dist, n_samples=25, z_dim=16, dim_1=0, dim_2=1, title='plot'): digit_size=28 percentiles = torch.linspace(0.10, 0.9, n_samples) grid_x = z_dist.icdf(percentiles[:, None].repeat(1, z_dim)) grid_y = z_dist.icdf(percentiles[:, None].repeat(1, z_dim)) figure = np.zeros((digit_size * n_samples, digit_size * n_samples)) z_sample_def = input_sample.clone().detach() # select two dimensions to vary (dim_1 and dim_2) and keep the rest fixed for yi in range(n_samples): for xi in range(n_samples): with torch.no_grad(): z_sample = z_sample_def.clone().detach() z_sample[:, dim_1] = grid_x[xi, dim_1] z_sample[:, dim_2] = grid_y[yi, dim_2] x_decoded = model.decoder(z_sample.to(device)).cpu() digit = x_decoded[0].reshape(digit_size, digit_size) figure[yi * digit_size: (yi + 1) * digit_size, xi * digit_size: (xi + 1) * digit_size] = digit.numpy() plt.figure(figsize=(6, 6)) plt.imshow(figure, cmap='Greys_r') plt.title(title) plt.show() for class_id in range(10): mu, std = classes_mean[class_id] with torch.no_grad(): recon_images = vae.decoder(mu.to(device)) show_image(recon_images[0], title=f'class {class_id} mean sample') traverse_two_latent_dimensions(vae, mu, z_dist=Normal(mu, torch.ones_like(mu)), n_samples=8, z_dim=z_dim, dim_1=3, dim_2=6, title=f'Class {class_id} traversing dimensions {(3, 6)}') Aynı anda birden çok boyutun geçilmesi, yüksek değişkenliğe sahip veriler üretmenin kontrol edilebilir bir yolunu sağlar. 2.6 Bonus - Gizli Uzaydan Gelen Rakamların 2B Manifoldu Bir VAE modeli gizli uzayından 2 boyutlu bir rakam manifoldu görüntülemek mümkündür. Bunu yapmak için fonksiyonunu ve ile kullanacağım. =2 ile eğitilmişse, z_dim traverse_two_latent_dimensions =0 dim_1 =2 dim_2 vae_2d = train_model(epochs=10, z_dim=2) z_dist = Normal(torch.zeros(1, 2), torch.ones(1, 2)) input_sample = torch.zeros(1, 2) with torch.no_grad(): decoding = vae_2d.decoder(input_sample.to(device)) traverse_two_latent_dimensions(vae_2d, input_sample, z_dist, n_samples=20, dim_1=0, dim_2=1, z_dim=2, title=f'traversing 2D latent space')