spiderChow
11/26/2017 - 2:30 AM

VAE structure

class Encoder(torch.nn.Module):
    def __init__(self, in_dim, h_dim, z_dim):
        super(Encoder, self).__init__()
        self.h_net = nn.Sequential(
            nn.Linear(in_dim, h_dim),
            nn.Tanh()
        )
        self.mu_linear = nn.Linear(h_dim, z_dim)
        self.log_sigma_square_linear = nn.Linear(h_dim, z_dim)

    def forward(self, x):
        # x = batch x in_dim
        h = self.h_net(x)  # h = batch x h_dim
        mu = self.mu_linear(h)  # mu = batch x z_dim
        log_sigma_square = self.log_sigma_square_linear(h)  # log(sigma^2) = batch x z_dim

        return mu, log_sigma_square
        
        
class Decoder(torch.nn.Module):
    def __init__(self, z_dim, h_dim, out_dim):
        super(Decoder, self).__init__()
        self.net = self.sequential = nn.Sequential(
            nn.Linear(z_dim, h_dim),
            nn.Tanh(),
            nn.Linear(h_dim, out_dim),
            nn.Sigmoid() # so the input need to within (0-1)?
        )

    def forward(self, z):
        # z = batch x z_dim
        return self.net(z)  # batch x out_dim
        
class VAE(torch.nn.Module):
    latent_dim = 8

    def __init__(self, input_dim, enc_h_dim, z_dim, dec_h_dim, out_dim):
        super(VAE, self).__init__()
        assert input_dim == out_dim
        self.encoder = Encoder(input_dim, enc_h_dim, z_dim)
        self.decoder = Decoder(z_dim, dec_h_dim, out_dim)

    def _sample_latent(self, z_mu, log_sigma_square):
        """
        Return the latent normal sample z ~ N(mu, sigma^2)
        """
        z_sigma = torch.exp(0.5 * log_sigma_square)
        gaussian = torch.randn(z_sigma.size())
        return z_mu + z_sigma * Variable(gaussian, requires_grad=False)  # Reparameterization trick

    def forward(self, input):
        # input = batch x in_dim
        z_mu, log_sigma_square = self.encoder(input)  # batch x z_dim
        z = self._sample_latent(z_mu, log_sigma_square)  # batch x z_dim
        return self.decoder(z), z_mu, log_sigma_square  # batch x out_dim; batch x z_dim

    def decode(self, z):

        return self.decoder(z)
        
def vae_loss(out_x, x, z_mu, log_sigma_square):
    # batch x dim
    # loss = kl + cross_entropy
    ## batch
    batch = x.size(0)

    #BCE = F.binary_cross_entropy(out_x, x.view(-1, 784))  # False: sum of all the element in batch x dim
    BCE = F.mse_loss(out_x, x.view(-1, 784))
    # BCE = BCE / batch
    ''' 
    When the prior p(z)=N(0,1) and posterior approximation q(z|x) is also Gaussian,
    the KLD(q|p) is as below.
    '''
    KLD = -0.5 * torch.sum(1 + log_sigma_square - torch.mul(z_mu, z_mu) - torch.exp(log_sigma_square))
    KLD = KLD / (batch * 28 * 28)
    # KLD per picture

    return KLD + BCE