声音生成项目(4)——从VariantAutoencoder(VAE)到VQ-VAE矢量量化变分编码器

news2024/11/16 3:25:55

文章目录

      • 论文介绍
      • 步骤具体讲解
        • 自定义矢量量化层
        • 获取最近距离的码字的索引计算推导
        • 损失函数
        • 相关参考
      • 矢量量化层的代码实现
      • 完整代码实现

论文介绍

  • 不同于变分编码器和自动编码器,vq-vae中的latent space是离散的,并不是连续的,这能够避免后验塌陷。除此之外,vq-vae中的先验概率分布并不是静态的,是可以通过训练进行学习的。

在这里插入图片描述

  • 编码器是使用卷积网络生成对应的特征,然后计算欧式距离,将是映射到离散的码本中
  • 解码器是根据最近的码字索引抽取对应的码本中的编码,并且使用这个码进行数据生成。这个就不用与自编码器中的latent space是一个连续的空间。
  • 前向很简单,但是反向传播就很苦难,因为在正向过程中,获取最近码字的过程,是不可导的。可以使用straight-through estimator因为编码器和解码器的大小是相同的。通过复制梯度,调整对应的编码向量的方向,实现编码器的输出不断向最近的编码靠近。

在这里插入图片描述在这里插入图片描述

  • 这里是实现将ze编码器的输出,找到最近的一个码本中的码字进行映射,然后保留对应的索引。
  • 因为对于等式2是没有真实梯度的,而且该等式是不可导的,所以我们通过straght-through estimator近似梯度,简单来说就是使用解码器的输入梯度,复制给编码器的输出梯度。
  • 因为编码器的输出和解码器的输入是具有相同的尺寸的,所以解码器的梯度信息对于编码器而言也是有用的,可以让编码器了解如何改变输出,才能降低重建损失。

在这里插入图片描述

  • 上述图片为损失函数的构成,分别是三个部分
    • 第一个部分是重建损失函数,保证重建之后和原来的之间的差异,解码器和编码器都会优化 这部分损失函数。
    • 第二部分是优化码本中的编码embedding ,是码本中码字不断向输入进行靠拢。学习embeddings.
    • 第三部分是优化编码器输出,向码本中的码进行逼近的损失函数。调整编码器的输出。

步骤具体讲解

自定义矢量量化层

在这里插入图片描述* 左边矩阵是编码器的输出,进行reshape之后的一个二维矩阵,每一行表示一个特征编码,然后在右边码本中找到欧式距离最近的一列特征进行替换映射。替换如下,每一行的f特征,换成了码本中对应E码字,这是最终结果,中间省略了很多计算过程。

在这里插入图片描述

具体计算过程如下

  • 首先计算一行特征到码字中各个码字的距离,形成一个新的向量,每一个位置,表示对应行的特征到对应列的向量之间的码字距离。

在这里插入图片描述

  • 选取每一行最小值,并记录对应的索引,生成一个新的矩阵,记录的是每一行最小值对应的索引。

在这里插入图片描述

  • 根据索引,生成对应的独热编码矩阵,根据索引形成对应的矩阵。原来获取最近的索引的时候,就是在所有的码本中找最近的码进行计算的,所以这里生成对应同宽度的独热编码矩阵。和原来的码本进行相乘,获得结果就是对应最近的码字

在这里插入图片描述

  • 直接和对应的码本进行相乘,获取最近的码字。

在这里插入图片描述

获取最近距离的码字的索引计算推导

  • 这里是距离计算的具体方式,对于两个1x3的向量,对于更加复杂维度,计算的方式也相同,这里结合具体的代码来看一下

在这里插入图片描述

  • 计算两个矩阵的相乘,就是上图中的xy
 similarity = tf.matmul(falttened_input,self.embeddings)
  • 计算两个两个矩阵的平方和,对于第一个矩阵而言,每一列是一个特征向量,对于第二个矩阵而言,每一行是个向量,是计算这两个向量的平方和。
   distances = (
            tf.reduce_sum(falttened_input**2,axis=1,keepdims=True)
            + tf.reduce_sum(self.embeddings ** 2,axis=0)
            - 2 * similarity
        )
  • 选出最近的索引
   # 获取最小距离的索引,最终返回的是每一个样本在对应行中最小值,返回的是一个(Batch_size*H*W,1)的数组,
        # 每一行的特征对应最小的码的索引
        encoding_indices = tf.argmin(distances,axis=1)
  • 具体的矩阵的形状的变化,见上一个个章节的内容。

损失函数

  • 损失函数主要由里那个部分构成,分别是量化损失(commitment loss)和码本损失(codebook loss)

  • 量化损失

    • 衡量量化之前的原始输入和量化之后的输出的差异
    • 通过计算原始输入和停止梯度的量化输出之间的均方差来衡量
    • 使模型学习将输入的数据映射到码本中最接近的向量,是控制输入x的,所以不需要对码本进行梯度调整。
  • 码本损失

    • 衡量量化输出和原始输入之间的反向重构误差
    • 通过计算量化输出和停止梯度的原始输入之间的方差衡量。
    • 使模型学习将量化输出映射回原始输入空间,是控制码本的梯度,所以不需要改变输入x的参数
  • 最终的损失函数‘

    • 两部分的损失进行加权想家,权重有 β \beta β进行控制
    • 使用stop_gradient是为了控制损失函数对于解码器输出的影响,保证解码器能够正确反量化码本向量,不受梯度的干扰。
  • 具体实现代码,说实话,这部分理解的不够透彻

# 计算矢量化的损失,并且将之加到当前层上,
    # reduce_mean:计算张量指定维度上的平均值
    # tf.stop_gradient:不计算输入变量的梯度
    commitment_loss = tf.reduce_mean((tf.stop_gradient(quantized) - x ) ** 2)
    codebook_loss = tf.reduce_mean((quantized - tf.stop_gradient(x) ) ** 2)
    self.add_loss(self.beta * commitment_loss + codebook_loss)
  • 最终的输出,总的前向传播中,码本是离散的,无法求导,需要直接将梯度传到输入续
   # straight-through estimator:直接将参数的梯度作为对应浮点型参数的梯度
        # 直接使用激活函数之后的梯度,代替之前的梯度
        quantized = x + tf.stop_gradient(quantized - x )

相关参考

Straight——through Estimator的解释
stop_gradient的解释

矢量量化层的代码实现

from tensorflow.keras import Model
# 一般进行版本更新都是改变的包的导向,或者改变包的方法名
from tensorflow.keras.layers import Input,Conv2D,ReLU,BatchNormalization,Flatten,Dense,\
    Reshape,Conv2DTranspose,Activation,Lambda
from tensorflow.keras import layers,metrics
# 引入backend,这个用来自己定义层,将一些函数定义成特定的层
from tensorflow.keras import backend as K
# 导入numpy,对三维数据进行操作
import numpy as np
# 导入对应优化器,注意,这里已经改变了包的地址
from tensorflow.keras.optimizers.legacy import Adam
# 导入损失函数
from tensorflow.keras.losses import MeanSquaredError
# 导入系统模块
import os
# 序列加载模块
import pickle



import tensorflow as tf

tf.compat.v1.disable_eager_execution()


# 自定义层,自定义矢量量化层
# 这是在编码器和解码器之间自定义的一个层。
# 输入为编码器的输出,形状是(batch_size, height, width,num_filters)
#           矢量量化器将会输入进行flatten,仅仅保证过滤核filter的尺寸不变,
#           最终的尺寸是batch_size * height * width,num_filters)
#           作用:将滤波器的总数当作是潜在embedding的大小
# 然后embedding 表格是被初始化,用来学习码本code book
# 我们我们通过计算展平之后的编码器输出和码本码字的欧式距离
# 我们选择距离最小的码字,然后应用独热编码实现量化效果,借此实现了码字和相应距离最近的编码器输出作为关联
#
# 因为矢量量化是不可导的,所以直接用解码器的梯度作为编码器的梯度
#
#

class VectorQuantizer(layers.Layer):

    def __init__(self,num_embeddings,embedding_dim,beta=0.25,**kwargs):
        '''
        确定矢量量化的码字尺寸,
        :param num_embeddings:batch_size * height * width
        :param embedding_dim:num_filters
        :param beta:beta参数设置为0.25
        :param kwargs:
        '''
        super().__init__(**kwargs)
        self.embedding_dim = embedding_dim
        self.num_embedding = num_embeddings
        self.beta = beta

        # 初始化将要量化的embedding
        # 生成具有均匀分布的张量的初始化器
        w_init = tf.random_uniform_initializer()
        # 定义tensorflow中的图片变量并且命名为embedding_vqvae
        self.embeddings = tf.Variable(
            initial_value=w_init(
                shape = (self.embedding_dim,self.num_embedding),
                dtype = "float32"
            ),
            trainable=True,
            name = "embeddings_vqvae",
        )


    def cal(self,x):
        """
        步骤:
        1、将input进行flatten,仅仅保留embedding_dim这一个维度
        2、使用独热编码对embedding进行量化
        3、将量化的值,还原回原来的输入形状
        4、计算量化层的损失函数
        5、将解码器的梯度传到编码器
        :param x: 当前层的输入
        :return: 一个展平之后的embeddings,并且保存了对应的filter_num
        """

        # 1、将input进行flatten,仅仅保留embedding_dim这一个维度,(Batch_size, H,W,filter_num)
        input_shape = tf.shape(x)
        # 将原来的处理过后的特征改变为特定的形状,最后一个维度指定,前两个维度自动调整,(Batch_size*H*W,filter_num)
        flattened = tf.reshape(x,[-1,self.embedding_dim])

        # 2、使用独热编码对embedding进行量化,获取距离最近的
        encoding_indices = self.get_code_indices(flattened)
        # 这里是生成对应的独热编码,位数是embedding的维度,索引是embedding的编号
        # 创建一个独热编码矩阵形状为(Batch_size*H*W,self.num_embeddings),根据索引将对应的列置为1
        encodings = tf.one_hot(encoding_indices,self.num_embedding)
        # 矩阵连乘,除了对应的独热编码对应的embedding可以保留,其余的都归零
        # matmul矩阵相乘,将之原来的矩阵(Batch_size*H*W,self.num_embeddings),
        # 映射为一个(Batch_size*H*W,self.embedding_dim)的矩阵
        quantized = tf.matmul(encodings,self.embeddings,transpose_b=True)

        # 将经过独热编码处理之后的embedding还原成原始大小的数据
        quantized = tf.reshape(quantized,input_shape)

        # 计算矢量化的损失,并且将之加到当前层上,
        # reduce_mean:计算张量指定维度上的平均值
        # tf.stop_gradient:不计算输入变量的梯度
        commitment_loss = tf.reduce_mean((tf.stop_gradient(quantized) - x ) ** 2)
        codebook_loss = tf.reduce_mean((quantized - tf.stop_gradient(x) ) ** 2)
        self.add_loss(self.beta * commitment_loss + codebook_loss)

        # straight-through estimator:直接将参数的梯度作为对应浮点型参数的梯度
        # 直接使用激活函数之后的梯度,代替之前的梯度
        quantized = x + tf.stop_gradient(quantized - x )

        return quantized



    def get_code_indices(self,falttened_input):
        """
        计算输入和不同码字的欧式距离,判定对应的值
        :param falttened_input:平展之后的输入
        :return:
        """
        # (Batch_size*H*W,filter_num)  * (self.embedding_dim,self.num_embedding)
        similarity = tf.matmul(falttened_input,self.embeddings)
        # 这部分就是xx+yy-2xy
        distances = (
            tf.reduce_sum(falttened_input**2,axis=1,keepdims=True)
            + tf.reduce_sum(self.embeddings ** 2,axis=0)
            - 2 * similarity
        )
        # 这里的最终结果是(Batch_size*H*W,num_embeddings)其中每一个元素为每一个输入样本和码本之间的欧式距离

        # 获取最小距离的索引,最终返回的是每一个样本在对应行中最小值,返回的是一个(Batch_size*H*W,1)的数组,
        # 每一行的特征对应最小的码的索引
        encoding_indices = tf.argmin(distances,axis=1)
        return encoding_indices


完整代码实现

from tensorflow.keras import Model
# 一般进行版本更新都是改变的包的导向,或者改变包的方法名
from tensorflow.keras.layers import Input,Conv2D,ReLU,BatchNormalization,Flatten,Dense,\
    Reshape,Conv2DTranspose,Activation,Lambda
from tensorflow.keras import layers,metrics
# 引入backend,这个用来自己定义层,将一些函数定义成特定的层
from tensorflow.keras import backend as K
# 导入numpy,对三维数据进行操作
import numpy as np
# 导入对应优化器,注意,这里已经改变了包的地址
from tensorflow.keras.optimizers.legacy import Adam
# 导入损失函数
from tensorflow.keras.losses import MeanSquaredError
# 导入系统模块
import os
# 序列加载模块
import pickle



import tensorflow as tf

tf.compat.v1.disable_eager_execution()


# 自定义层,自定义矢量量化层
# 这是在编码器和解码器之间自定义的一个层。
# 输入为编码器的输出,形状是(batch_size, height, width,num_filters)
#           矢量量化器将会输入进行flatten,仅仅保证过滤核filter的尺寸不变,
#           最终的尺寸是batch_size * height * width,num_filters)
#           作用:将滤波器的总数当作是潜在embedding的大小
# 然后embedding 表格是被初始化,用来学习码本code book
# 我们我们通过计算展平之后的编码器输出和码本码字的欧式距离
# 我们选择距离最小的码字,然后应用独热编码实现量化效果,借此实现了码字和相应距离最近的编码器输出作为关联
#
# 因为矢量量化是不可导的,所以直接用解码器的梯度作为编码器的梯度
#
#

class VectorQuantizer(layers.Layer):

    def __init__(self,num_embeddings,embedding_dim,beta=0.25,**kwargs):
        '''
        确定矢量量化的码字尺寸,
        :param num_embeddings:batch_size * height * width
        :param embedding_dim:num_filters
        :param beta:beta参数设置为0.25
        :param kwargs:
        '''
        super().__init__(**kwargs)
        self.embedding_dim = embedding_dim
        self.num_embedding = num_embeddings
        self.beta = beta

        # 初始化将要量化的embedding
        # 生成具有均匀分布的张量的初始化器
        w_init = tf.random_uniform_initializer()
        # 定义tensorflow中的图片变量并且命名为embedding_vqvae
        self.embeddings = tf.Variable(
            initial_value=w_init(
                shape = (self.embedding_dim,self.num_embedding),
                dtype = "float32"
            ),
            trainable=True,
            name = "embeddings_vqvae",
        )


    def cal(self,x):
        """
        步骤:
        1、将input进行flatten,仅仅保留embedding_dim这一个维度
        2、使用独热编码对embedding进行量化
        3、将量化的值,还原回原来的输入形状
        4、计算量化层的损失函数
        5、将解码器的梯度传到编码器
        :param x: 当前层的输入
        :return: 一个展平之后的embeddings,并且保存了对应的filter_num
        """

        # 1、将input进行flatten,仅仅保留embedding_dim这一个维度,(Batch_size, H,W,filter_num)
        input_shape = tf.shape(x)
        # 将原来的处理过后的特征改变为特定的形状,最后一个维度指定,前两个维度自动调整,(Batch_size*H*W,filter_num)
        flattened = tf.reshape(x,[-1,self.embedding_dim])

        # 2、使用独热编码对embedding进行量化,获取距离最近的
        encoding_indices = self.get_code_indices(flattened)
        # 这里是生成对应的独热编码,位数是embedding的维度,索引是embedding的编号
        # 创建一个独热编码矩阵形状为(Batch_size*H*W,self.num_embeddings),根据索引将对应的列置为1
        encodings = tf.one_hot(encoding_indices,self.num_embedding)
        # 矩阵连乘,除了对应的独热编码对应的embedding可以保留,其余的都归零
        # matmul矩阵相乘,将之原来的矩阵(Batch_size*H*W,self.num_embeddings),
        # 映射为一个(Batch_size*H*W,self.embedding_dim)的矩阵
        quantized = tf.matmul(encodings,self.embeddings,transpose_b=True)

        # 将经过独热编码处理之后的embedding还原成原始大小的数据
        quantized = tf.reshape(quantized,input_shape)

        # 计算矢量化的损失,并且将之加到当前层上,
        # reduce_mean:计算张量指定维度上的平均值
        # tf.stop_gradient:不计算输入变量的梯度
        commitment_loss = tf.reduce_mean((tf.stop_gradient(quantized) - x ) ** 2)
        codebook_loss = tf.reduce_mean((quantized - tf.stop_gradient(x) ) ** 2)
        self.add_loss(self.beta * commitment_loss + codebook_loss)

        # straight-through estimator:直接将参数的梯度作为对应浮点型参数的梯度
        # 直接使用激活函数之后的梯度,代替之前的梯度
        quantized = x + tf.stop_gradient(quantized - x )

        return quantized



    def get_code_indices(self,falttened_input):
        """
        计算输入和不同码字的欧式距离,判定对应的值
        :param falttened_input:平展之后的输入
        :return:
        """
        # (Batch_size*H*W,filter_num)  * (self.embedding_dim,self.num_embedding)
        similarity = tf.matmul(falttened_input,self.embeddings)
        # 这部分就是xx+yy-2xy
        distances = (
            tf.reduce_sum(falttened_input**2,axis=1,keepdims=True)
            + tf.reduce_sum(self.embeddings ** 2,axis=0)
            - 2 * similarity
        )
        # 这里的最终结果是(Batch_size*H*W,num_embeddings)其中每一个元素为每一个输入样本和码本之间的欧式距离

        # 获取最小距离的索引,最终返回的是每一个样本在对应行中最小值,返回的是一个(Batch_size*H*W,1)的数组,
        # 每一行的特征对应最小的码的索引
        encoding_indices = tf.argmin(distances,axis=1)
        return encoding_indices


class VQVAE:
    """
    Autoencoder: 变分自动编码器
    这个是在原来的自动编码器上做了如下的修改
    1、去除原来的bottleneck部分,换成量化层进行实现
    2、更新损失函数,加入KL散度以及修改原来的MSE
    """

    # 1、当前类别的构造函数,
    def __init__(self,
                 input_shape,
                 conv_filters,
                 conv_kernels,
                 conv_strides,
                 latent_space_dim,
                 num_embeddings
                 ):
        # 2、 将所有的属性都赋值给对应实体属性
        self.input_shape = input_shape  # [28,28,1]这里是使用minst手写数据集进行测试的
        self.conv_filters = conv_filters # [2,4,8]
        self.conv_kernels = conv_kernels # [3,5,3]
        self.conv_strides = conv_strides # [1,2,2]
        self.latent_space_dim = latent_space_dim # 码本中单个码字的维度
        self.num_embeddings = num_embeddings    # 码本中码字的个数

        # 3、这里的习惯就很好,将一个大模型拆解成两个小模型,编程的时候,只需要对应进行修改就行了
        self.encoder = None
        self.decoder = None
        self.vq_layer = None
        self.model = None


        # 4、将部分变量声明为隐私变量,前置单下划线,私有变量
        self._num_conv_layers = len(conv_filters)

        # 5、设置build函数,这里是实例化类的时候进行调用
        self._build()

        # 9、后续添加的变量
        self._shape_encoder = None

        # 3.1 添加整体模型的输入,和encoder的输入是相同的
        self._model_input = None

        # 4.2 损失函数权重
        self.reconstruction_weight = 0.5

    # 这个方法在三个模块都是需要修改的
    def summary(self):
        """ 做测试,判定模型是否成功 """
        self.encoder.summary()
        self.decoder.summary()
        self.model.summary()

    # 3.2 增加编译函数
    def compile(self,learning_rate = 0.0001):
        """ 指定损失函数和优化器,并对模型进行优化 """
        optimizer = Adam(learning_rate = learning_rate)
        self.model.compile(
            optimizer = optimizer,
            loss = self._calculate_reconstruction_loss,
        )
        self.total_loss_tracker = metrics.Mean(name="total_loss")
        self.reconstruction_loss_tracker = metrics.Mean(
            name="reconstruction_loss"
        )
        self.vq_loss_tracker = metrics.Mean(name="vq_loss")


    @property
    def metrics(self):
        return [
            self.total_loss_tracker,
            self.reconstruction_loss_tracker,
            self.vq_loss_tracker,
        ]

    # 3.3 增加训练函数
    def train(self,x_train,batch_size,num_epochs):
        self.model.fit(
            x_train,
            x_train,
            batch_size = batch_size,
            epochs = num_epochs,
            shuffle = True
        )

    # 3.4 模型保存部分
    def save(self,save_folder = "."):
        """ 保存模型,需要创建文件,分别保存参数和权重"""
        self._create_folder_if_not_exist(save_folder)
        self._save_parameters(save_folder)
        self._save_weights(save_folder)

    # 3.5 模型加载部分,这部分要注意,是声明为类方法,不用实例化,直接可以调用
    @classmethod
    def load(cls,save_folder ="."):
        """ 加载模型,包括模型的参数设置和模型的训练权重 """
        parameters_path = os.path.join(save_folder,"parameters.pkl")
        with open(parameters_path,"rb") as f:
            parameters = pickle.load(f)
        vae = VQVAE(*parameters)
        weight_path = os.path.join(save_folder,"weights.h5")
        vae.load_weights(weight_path)
        return vae

    def load_weights(self,weight_path):
        self.model.load_weights(weight_path)

    def reconstruct(self,image):
        """ 重建图片,并返回生成之后的图片以及对应的特征空间 """
        latent_space = self.encoder.predict(image)
        reconstruct_image = self.decoder.predict(latent_space)
        return reconstruct_image,latent_space

    # 4.2 将两种损失函数进行综
    # 4.2 损失函数重建
    def _calculate_reconstruction_loss(self,y_target,y_predict):
        """ 模型重建损失函数,加上了对应alpha """
        error = y_predict - y_target
        reconstruction_loss = K.mean(K.square(error),axis = [1,2,3])    # 注意,这里只需要返回除了第一个图片序号的后两个维度
        return reconstruction_loss


    # 3.4 分别实现上述方法
    def _create_folder_if_not_exist(self,save_folder):
        if not os.path.exists(save_folder):
            os.makedirs(save_folder)

    # 3.4 分别实现上述方法
    def _save_parameters(self,save_folder):
        """ 主要是保存模型对应的参数,包括每一层具体的设置 """
        parameters = [
            self.input_shape ,
            self.conv_filters,
            self.conv_kernels,
            self.conv_strides,
            self.latent_space_dim
        ]
        save_path = os.path.join(save_folder,"parameters.pkl")
        with open(save_path,"wb") as f:
            pickle.dump(parameters,f)

    # 3.4  实现save的子方法
    def _save_weights(self,save_folder):
        save_path = os.path.join(save_folder, "weights.h5")
        self.model.save_weights(save_path)


    # 6、具体实现相关的方法,这个是总的build函数,需要构建三个模块,分别是encoder、decoder和model
    def _build(self):
        self._build_encoder()
        self._build_quantizer_layer()
        self._build_decoder()
        self._build_VAE()

    def _build_quantizer_layer(self):
        # 获取编码器的输出,并生成对应的中间层
        # 感觉这里有问题,所有的层应该都是事先已经声明好的
        vq_layer = VectorQuantizer(self.num_embeddings,self.latent_space_dim,name = "vector_quantizer")
        # vq_output  =  vq_layer(quantizer_input)
        self.vq_layer = vq_layer


    # 7、从上到下,逐个子方法进行实现
    def _build_encoder(self):
        # 8、按照网络的层次,将模型串联起来,按照模块进行组装
        encoder_input =  self._add_encoder_input()
        conv_layers =  self._add_conv_layers(encoder_input)
        # 调整通道维度,使之和特征空间相适应,
        encoder_output = layers.Conv2D(self.latent_space_dim,1,padding="same")(conv_layers)
        self._shape_encoder = encoder_output.shape
        self._model_input = encoder_input
        self.encoder = Model(encoder_input,encoder_output,name="encoder")

    # 8、从上到下,按照顺序,逐个实现_build_encoder模块中所有方法
    def _add_encoder_input(self):
        return Input(shape = self.input_shape,name= "encoder_input")

    # 8、从上到下,按照顺序,逐个实现_build_encoder模块中所有方法
    def _add_conv_layers(self,encoder_input):
        """  在编码器中增加卷积模块 """
        x = encoder_input
        # 9、这部分是按照层的顺序逐渐叠加网络层
        for layer_index in range(self._num_conv_layers):
            # 尽量将自己的模块封装在别的人的模块上
            x = self._add_conv_layer(layer_index,x)
        return x

    # 8、从里到外,完成对应的卷积模块
    def _add_conv_layer(self,layer_index,x):
        """ 增加卷积模块,每一部分构成如下,conv2d + relu + batch normalization """
        layer_num = layer_index + 1
        conv_layer = Conv2D(
            filters = self.conv_filters[layer_index],
            kernel_size = self.conv_kernels[layer_index],
            strides = self.conv_strides[layer_index],
            padding = "same",
            name = f"encoder_conv_layer_{layer_num}"
        )
        x = conv_layer(x)
        x = ReLU(name = f"encoder_relu_{layer_num}")(x)
        x = BatchNormalization(name = f"encoder_bn_{layer_num}")(x)
        return x


    # 7、从上到下,逐个子方法进行实现
    # 2.1 完成解码器的大部分框架
    def _build_decoder(self):
        """ 创建解码器,输入层、全连阶层、恢复成三维、进行反卷积、输出层 """
        decoder_input = self._add_decoder_input()
        conv_transpose_layers = self._add_conv_transpose_layers(decoder_input)
        decoder_output = self._add_decoder_output(conv_transpose_layers)
        self.decoder = Model(decoder_input,decoder_output,name = "decoder")

    # 2.2 具体实现各个子函数,下述函数都是按照顺序完成并实现的
    def _add_decoder_input(self):
        """ 解码器的输入 """
        return Input(shape = self._shape_encoder[1:],name = "decoder_input")

    def _add_dense_layer(self,decoder_input):
        """ 解码器的全连阶层,输出数据是二维的,这里并不知道怎么设置??"""
        # 这部分设置神经元的数量,和输出的维度而数量相同
        num_neurons = np.prod(self._shape_before_bottleneck) # 将数据恢复原始的数据[1,2,4]=>8,现在是将8转成三维的数组
        dense_layer = Dense(num_neurons,name = "decoder_dense_layer")(decoder_input)
        return dense_layer

    def _add_reshape_layer(self,dense_layer):
        """ 增加对应的调整形状层,将全链接层的输出,恢复成三维数组 """

        # 这里并不知道调用什么层进行设计
        reshape_layer = Reshape(self._shape_before_bottleneck)(dense_layer)
        return reshape_layer

    def _add_conv_transpose_layers(self,x):
        """ 增加反卷积模块 """
        # 按照相反的顺序遍历所有的卷积层,并且在第一层停下
        for layers_index in reversed(range(1,self._num_conv_layers)):
            # 理解:原来的卷积层标记[0,1,2],翻转之后的输出为[2,1,0]
            x = self._add_conv_transpose_layer(x,layers_index)
        return x

    def _add_conv_transpose_layer(self,x,layer_index):
        # 注意,这里的层序号是按照倒序来的,需要还原成正常序号
        # 一个卷积模块:卷积层+ReLu+batchnormalization
        layer_num = self._num_conv_layers - layer_index
        conv_transpose_layer  =Conv2DTranspose(
            filters = self.conv_filters[layer_index],
            kernel_size = self.conv_kernels[layer_index],
            strides = self.conv_strides[layer_index],
            padding = "same",
            name = f"decoder_conv_transpose_layer_{layer_num}"
        )
        x = conv_transpose_layer(x)
        x  =ReLU(name=f"decoder_ReLu_{layer_num}")(x)
        x = BatchNormalization(name = f"decoder_BN_{layer_num}")(x)
        return x

    def _add_decoder_output(self,x):
        """ 增加模型的输出层 """
        # 这部分要和encoder是一个完全的逆过程,而且之前的反卷积模块是少了最后一层
        # ,所以这里需要额外设置一层
        conv_transpose_layer = Conv2DTranspose(
            filters=1, # filters 对应图片中的channel.最终生成图片是一个[28,28,1]的灰度图片
            kernel_size=self.conv_kernels[0],
            strides=self.conv_strides[0],
            padding="same",
            name=f"decoder_conv_transpose_layer_{self._num_conv_layers}"
        )
        x = conv_transpose_layer(x)
        output_layer = Activation("sigmoid",name = "sigmoid_layer")(x)
        return output_layer


    # 3.1 实现整个模型而自动编码器
    # 7、从上到下,逐个子方法进行实现
    def _build_VAE(self):
        """ 对于自动编码器的识别,链接编码器和解码器 """
        model_input = self._model_input
        encoder_output = self.encoder(model_input)
        quantizered = self.vq_layer(encoder_output)
        # model_output = self.decoder(quantized_latent)
        model_output = self.decoder(quantizered)
        self.model = Model(model_input,model_output,name = "VQVAE")


if __name__ == '__main__':
    VAE = VQVAE(
        input_shape= [28,28,1],
        conv_filters = [32,64,64,64],
        conv_kernels = [3,3,3,3],
        conv_strides= [1, 2, 2, 1],
        latent_space_dim=16,
        num_embeddings=64
    )

    VAE.summary()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/635039.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第3章“程序的机器级表示”:理解指针

指针是 C 语言的一个重要特色。它们提供一种统一方式,能够远程访问数据结构。 指针基本的概念其实非常简单,下面的代码说明了许多这样的概念: struct str { /* Example Structure */int t;char v; };union uni { /* Example Union */int t;…

大厂C++面试基础题第1辑——虚函数七题精讲之一

> “虚函数的作用” 是面向对象的C编程最基础也最核心的知识点,如果不能无法正确回答本题,则只此一题,不管大厂还是小厂,都铁定无缘了。 概述 “虚函数” 是 C面向对象三最:最基础、最重要、最关键的知识点。我们从…

什么是Vue的Vite构建工具?如何使用Vite进行项目开发

什么是Vue的Vite构建工具?如何使用Vite进行项目开发 介绍 Vite是一个由Vue.js核心团队开发的构建工具。它的目标是提供一种快速的开发体验,同时保持生产环境的稳定性和可靠性。Vite使用了ES模块作为开发环境的原生模块格式,通过在开发服务器…

C++11中的关键字constexpr

文章目录 1、constexpr修饰普通变量2、constexpr修饰函数3、constexpr修饰类的构造函数 constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C 11 标准中,constexpr 可用于修饰普通变量、函数&…

【Leetcode】DP | 序列及子数组问题

300 最长递增子序列 求数组最长严格递增子序列的长度。 D [ i ] D[i] D[i]代表以 n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列的长度。 D [ i ] max ⁡ j < i , n u m s [ i ] > n u m s [ j ] ( D [ j ] 1 ) D[i] \max_{j < i,\ nums[i]>nums[j]}(D[…

什么是Vue的UI框架?

什么是Vue的UI框架&#xff1f; Vue.js 是一款流行的 JavaScript 框架&#xff0c;用来构建现代的单页面应用程序&#xff08;SPA&#xff09;。Vue.js 提供了丰富的功能和 API&#xff0c;但是在构建应用程序时&#xff0c;我们还需要使用一些 UI 组件来实现复杂的交互和界面…

【分布式能源选址与定容】光伏、储能双层优化配置接入配电网研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

02- 输入、输出及运算符(C语言)

一 输入、输出 1.1 输出函数 printf 函数是一个可变参数函数&#xff0c;参数的个数不定&#xff1a;int printf(const char *format, ...) printf("%d\n", x); printf("%d %d\n", x, y); 1.2 输入函数 1.2.1 scanf函数&#xff1a;int scanf(const …

利用AI点亮副业变现:5个变现实操案例的启示

AI变现副业实操案例 宝宝起名服务AI科技热点号头像壁纸职业头像收徒&#xff1a;萌娃头像定制头像平台挂载 小说推广号流量营销号百家号AI共创计划公众号流量主 知识付费知识星球小报童&#xff1a; 整体思维导图&#xff1a; 在这里先分享五个实操案例: 宝宝起名服务AI科技热…

[MySQL]一文带你学明白数据库控制语言——DCL

前言 嗨咯&#xff0c;小伙伴大家好呀&#xff01;好几天没见了&#xff0c;周末过得怎么样啊&#xff01;之前学过的SQL语句不会都忘了吧。如果忘了的话大家可以看一下前几期的文章。本期要学习的是SQL语句中的数据库控制语句——DCL&#xff0c;学习完毕之后MySQL中的SQL语句…

探索ll-hls低延迟直播协议

HLS全称为HTTP Live Streaming&#xff0c;其中m3u8作为描述协议&#xff0c;指向一系列切片文件。支持多码流与自适应码率&#xff0c;支持广告无缝播放&#xff0c;支持CMAF协议的低延时直播&#xff0c;也支持CDN动态选择。 我们先看下HLS整体架构&#xff0c;由三部分构成…

存储技术3 数据保护: RAID

Why RAID 性能限制了磁盘驱动单独的驱动存在预期的使用寿命 MTBF测量若一个驱动器的MTBF是750 000小时&#xff0c; 阵列中有100个驱动&#xff0c; 阵列的MTBF会变成 750000 / 100 7500小时 RAID用于减缓这个问题RAID特点 增大容量高可用性增强的性能 RAID implementation…

仿微信我的列表功能菜单按钮 我的个人中心页面功能菜单

前端vue自定义仿微信我的列表功能菜单按钮 我的个人中心页面功能菜单, 下载完整代码请访问https://ext.dcloud.net.cn/plugin?id12990 效果图如下: #### 使用方法 使用方法 <!-- leftTitle:标题 icon&#xff1a;左边图标 click&#xff1a;点击事件 --> <ccMe…

【java】IO流

IO流 原理 分类 字节流与字符流 节点流与包装流 Java IO详解&#xff08;五)------包装流 - YSOcean - 博客园 (cnblogs.com)JAVA I/O流 字符流和字节流、节点流和处理流(包装流、过滤流)、缓冲流_过滤流和缓冲流,字节流的关系_X-Dragon烟雨任平生的博客-CSDN博客 字符流 i…

算法模板(4):动态规划(2)

8.树形DP 没有上司的舞会 树上最大独立集问题 Ural 大学有 N N N 名职员&#xff0c;编号为 1 ∼ N 1 \sim N 1∼N。他们的关系就像一棵以校长为根的树&#xff0c;父节点就是子节点的直接上司。每个职员有一个快乐指数&#xff0c;用整数 H i H_i Hi​ 给出&#xff0c;…

顺序查找和折半查找

顺序查找和折半查找 顺序查找 一、算法思想 顺序查找&#xff0c;又叫“线性查找”&#xff0c;通常用于线性表。 算法思想&#xff1a;从头到尾挨个找&#xff08;或者反过来也OK&#xff09; 二、算法实现 结构体定义 typedef struct{ElemType *elem;int TableLen; }SS…

SpringBoot + Vue 的留守儿童系统的研究与实现

文章目录 1.研究背景2. 技术栈3.系统分析4系统设计5系统的详细设计与实现5.1系统功能模块5.2管理员功能模块 1.研究背景 以往的留守儿童爱心的管理&#xff0c;一般都是纸质文件来管理留守儿童爱心信息&#xff0c;传统的管理方式已经无法满足现代人们的需求&#xff1b;使用留…

变压器差动保护的影响因素和相应的措施

由于变压器一、二次电流、电压大小不同&#xff0c;相位不同&#xff0c;电流互感器特性差异&#xff0c;电源侧有励磁电流&#xff0c;都将造成不平衡电流流过继电器&#xff0c;必须采用相应措施消除不平衡电流的影响。 主要措施 &#xff08;1&#xff09;减小稳态情况下的…

SpringCloud学习笔记(四)RabbitMQ

一、同步通讯的优缺点 时效性较强&#xff0c;可以立即得到结果但是耦合度高&#xff0c;性能和吞吐能力下降有额外的资源消耗 二、异步通讯的优缺点 耦合度低&#xff0c;吞吐量提升故障隔离&#xff0c;流量削峰依赖于Broker的可靠性、安全性和吞吐能力 三、什么是MQ MQ…

物联网Lora模块从入门到精通(七)串口通讯

一、前言 在Lora模块的程序设计中&#xff0c;串口通信一定是一个极其重要且常用的通信方式&#xff0c;借助串口通信&#xff0c;我们不但可以向外传输我们获取的数据&#xff0c;还可以根据外部指令做出相应。 同样的&#xff0c;在例程中&#xff0c;为我们提供了一个名为us…