def deconv2d(self, filter_size, output_channels, stride=1, padding='SAME', stoch=False, ladder=None,
                 activation_fn=tf.nn.relu, b_value=0.0, s_value=1.0, bn=True):
        """
        2D Deconvolutional Layer
        :param filter_size: int. assumes square filter
        :param output_channels: int
        :param stride: int
        :param padding: 'VALID' or 'SAME'
        :param activation_fn: tf.nn function
        :param b_value: float
        :param s_value: float
        """
        self.count['deconv'] += 1
        scope = 'deconv_' + str(self.count['deconv'])
        with tf.variable_scope(scope):

            # Calculate the dimensions for deconv function
            batch_size = tf.shape(self.input)[0]
            input_height = tf.shape(self.input)[1]
            input_width = tf.shape(self.input)[2]

            if padding == "VALID":
                out_rows = (input_height - 1) * stride + filter_size
                out_cols = (input_width - 1) * stride + filter_size
            else:  # padding == "SAME":
                out_rows = input_height * stride
                out_cols = input_width * stride

            # Deconv function
            input_channels = self.input.get_shape()[3]
            output_shape = [filter_size, filter_size, output_channels, input_channels]
            w = self.weight_variable(name='weights', shape=output_shape)
            deconv_out_shape = tf.pack([batch_size, out_rows, out_cols, output_channels])
            self.input = tf.nn.conv2d_transpose(self.input, w, deconv_out_shape, [1, stride, stride, 1], padding)

            # Additional functions
            if ladder is not None:
                stoch = False
                enc_mean = self.dec_ladder[self.stoch_count_dec][0]
                enc_std = self.dec_ladder[self.stoch_count_dec][1]
                self.stoch_count_dec += 1
                with tf.variable_scope("ladder"):
                    input_shape = [enc_mean.get_shape()[1], enc_mean.get_shape()[2], enc_mean.get_shape()[3]]
                    w_std = self.weight_variable(name='weights_mean', shape=input_shape)
                    w_mean = self.weight_variable(name='weights_std', shape=input_shape)
                    mean = self.input * w_mean
                    std = tf.nn.softplus(self.input * w_std)
                    if ladder == 1:  # LVAE Implementation
                        eps = 1e-10
                        new_std = 1 / ((enc_std + eps) ** 2 + (std + eps) ** 2)
                        new_mean = new_std * (enc_mean * (1 / (enc_std + eps) ** 2) + mean * (1 / (std + eps) ** 2))
                        self.input = new_mean + tf.random_normal(tf.shape(self.input)) * new_std
                    elif ladder == 2:  # BLN Implementation
                        raise NotImplementedError
                    else:
                        self.input = mean + tf.random_normal(tf.shape(self.input)) * std
            if stoch is True:  # Draw sample from Normal Layer
                mean, std = tf.split(3, 2, self.input)
                self.input = mean + tf.random_normal(tf.shape(mean)) * std
                output_channels = int(output_channels/2)
            if bn is True:  # batch normalization
                self.input = self.batch_norm(self.input)
            if b_value is not None:  # bias value
                b = self.const_variable(name='bias', shape=[output_channels], value=b_value)
                self.input = tf.add(self.input, b)
            if s_value is not None:  # scale value
                s = self.const_variable(name='scale', shape=[output_channels], value=s_value)
                self.input = tf.multiply(self.input, s)
            if activation_fn is not None:  # non-linear activation function
                self.input = activation_fn(self.input)
        self.print_log(scope + ' output: ' + str(self.input.get_shape()))