paint-brush
Varyasyonel Otomatik Kodlayıcıyla Gizli Uzaydan Örnekleme Nasıl Yapılır?ile@owlgrey
7,772 okumalar
7,772 okumalar

Varyasyonel Otomatik Kodlayıcıyla Gizli Uzaydan Örnekleme Nasıl Yapılır?

ile Dmitrii Matveichev 17m2024/02/29
Read on Terminal Reader

Çok uzun; Okumak

Geleneksel AE modellerinden farklı olarak, Değişken Otomatik Kodlayıcılar (VAE'ler) girişleri çok değişkenli bir normal dağılıma eşleyerek çeşitli örnekleme yöntemleri aracılığıyla yeni veri üretimine olanak tanır. Bu makalede ele alınan örnekleme yöntemleri, arka örnekleme, ön örnekleme, iki vektör arasında enterpolasyon ve gizli boyut geçişidir.
featured image - Varyasyonel Otomatik Kodlayıcıyla Gizli Uzaydan Örnekleme Nasıl Yapılır?
Dmitrii Matveichev  HackerNoon profile picture

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ı Google Colab'da mevcuttur.

1 VAE Modelinin Uygulanması


AE modeli yeniden yapılanma kaybını en aza indirerek eğitilir (örneğin BCE veya MSE)


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 modeli, yeniden yapılanma kaybını ve KL-diverjans kaybını en aza indirerek eğitilir


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:


  • yeniden yapılanma kaybı - giriş görüntüleri ile kod çözücünün çıkışı arasındaki fark.


  • Kullback-Leibler sapma 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.

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)


  • p(z) öncesi gizli uzayının, ortalaması sıfır ve her bir gizli uzay boyutunda N(0, I ) standart sapması bir olan normal bir dağılım olduğu varsayılır.


    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 Normal dağılım oluşturabilir ve yeniden parametrelendirme hilesini uygulayan rsample() yöntemiyle bundan örnek alabilirsiniz: torch.randn(z_dim) * stddev + mean) ile aynıdır

 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) 

VAE eğitim adımları

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) 

UMAP ile görselleştirilen VAE modeli gizli alanı

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:

  • Arka örnekleme: Sağlanan bir girdi verildiğinde son dağılımdan örnekleme.


  • ön ö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.


  • enterpolasyon : 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.


  • gizli boyutların çaprazlanması : 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).


    Ö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)

Kodlayıcı, gizli uzayda bir dağılım (normal dağılımın μ_x ve 𝝈_x) çıktısını verir. Normal dağılım N(μ_x, 𝝈_x)'ten örnekleme ve örneklenen vektörün kod çözücüye geçirilmesi, verilen giriş görüntüsüne benzer bir görüntünün üretilmesiyle sonuçlanır.

 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)

Dağıtımdan örnekleme ve örneklenen vektörün kod çözücüye iletilmesi, yeni verilerin üretilmesine olanak tanır

 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, I ) ile ön örnekleme her zaman makul veriler üretmez ancak yüksek değişkenliğe sahiptir.

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.

3. sınıf merkezden oluşturulan görüntü

4. sınıf merkezinden oluşturulan görüntü

ICDF'de kullanılan düşük ve yüksek yüzdelik değerler, yüksek veri varyansına neden olur

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 p olasılığı için ICDF, rastgele bir değişkenin <= p_icdf olma olasılığı verilen p olasılığına eşit olacak şekilde bir p_icdf değeri çıkarır?


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.

0,025, 0,5, 0,975 olasılıkları verildiğinde CDF'nin ve ICDF tarafından döndürülen değerlerin görselleştirilmesi

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 z_dim =2 ile eğitilmişse, gizli uzayından 2 boyutlu bir rakam manifoldu görüntülemek mümkündür. Bunu yapmak için traverse_two_latent_dimensions fonksiyonunu dim_1 =0 ve dim_2 =2 ile kullanacağım.

 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') 

2 boyutlu gizli alan