您现在的位置是:首页 >技术杂谈 >声音合成——Foley Sound——DECASE项目——多模态智能感知与应用——Autoencoder代码实现(6)网站首页技术杂谈
声音合成——Foley Sound——DECASE项目——多模态智能感知与应用——Autoencoder代码实现(6)
简介声音合成——Foley Sound——DECASE项目——多模态智能感知与应用——Autoencoder代码实现(6)
文章目录
概述
- 这部分是关于代码复现的,跟着学了一遍代码,确实学到了很多,也改变了以前一些不好的习惯。
- 下面的每一个注释记录的是作者写代码的顺序,以及这样写的作用
encoder的编写过程
代码编写
from tensorflow.keras import Model
# 一般进行版本更新都是改变的包的导向,或者改变包的方法名
from tensorflow.keras.layers import Input,Conv2D,ReLU,BatchNormalization,Flatten,Dense
# 引入backend,这个用来自己定义层,将一些函数定义成特定的层
from tensorflow.keras import backend as K
class Autoencoder:
"""
Autoencoder: 具有编码器和解码器的深度卷积自动编码器
"""
# 1、当前类别的构造函数,
def __init__(self,
input_shape,
conv_filters,
conv_kernels,
conv_strdes,
latent_space_dim
):
# 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_strdes = conv_strdes # [1,2,2]
self.latent_space_dim = latent_space_dim # 潜在映射空间的维度,这里设置为2维度空间
# 3、这里的习惯就很好,将一个大模型拆解成两个小模型,编程的时候,只需要对应进行修改就行了
self.encoder = None
self.decoder = None
self.model = None
# 4、将部分变量声明为隐私变量,前置单下划线,私有变量
self._num_conv_layers = len(conv_filters)
# 5、设置build函数,这里是实例化类的时候进行调用
self._build()
# 9、后续添加的变量
self._shape_before_bottleneck = None
def summary(self):
""" 做测试,判定模型是否成功 """
self.encoder.summary()
# 6、具体实现相关的方法,这个是总的build函数,需要构建三个模块,分别是encoder、decoder和model
def _build(self):
self._build_encoder()
self._build_decoder()
self._build_autoencoder()
# 7、从上到下,逐个子方法进行实现
def _build_encoder(self):
# 8、按照网络的层次,将模型串联起来,按照模块进行组装
encoder_input = self._add_encoder_input()
conv_layers = self._add_conv_layers(encoder_input)
bottleneck = self._add_bottleneck(conv_layers)
self.encoder = Model(encoder_input,bottleneck,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_strdes[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
# 8、从上到下,按照顺序,逐个实现_build_encoder模块中所有方法
def _add_bottleneck(self,x):
# 9、第九部分进行编写
""" 首先将数据展平,然后在传入全链接层 """
self._shape_before_bottleneck = K.int_shape(x)[1:] # [batch_size,height,weight,channel],这里只需要后面三个的大小
x = Flatten()(x)
x = Dense(self.latent_space_dim,name = "encoder_output")(x)
return x
# 7、从上到下,逐个子方法进行实现
def _build_decoder(self):
pass
# 7、从上到下,逐个子方法进行实现
def _build_autoencoder(self):
pass
if __name__ == '__main__':
autoencoder = Autoencoder(
input_shape= [28,28,1],
conv_filters = [32,64,64,64],
conv_kernels = [3,3,3,3],
conv_strdes = [1,2,2,1],
latent_space_dim=2
)
autoencoder.summary()
运行结果
问题
- 这里直接堆叠对应的卷积层,并没有计算对应的输出的张量的大小,没有根据对应的大小设置某一层的参数?
- 卷积层并不需要任何指定,这里只需要
总结
- 尽量将模型模块化设计,然后将你所调用的函数api,尽可能放在最底层。
- 在作者的代码里,将整个模型封装为encoder + decoder,然后整体的模型叫做model,然后有一个build函数分别调用
- 对于函数内部的成员,一些构造函数,要及时将之生命为私有的
- 在编写过程中,要学会模块化测试,
- 编写一部分,就测试一部分,使用summary函数
decoder的编写过程
知识补充
关于逆卷积
- 对应的链接,逆卷积和卷积的说明
代码编写
运行结果
总结
- 经过了第一部分之后,明确了一些编码的习惯和常识之后,更多的是理解如何根据输入层的翻转,构建新的输出层。
- 需要理解一下的知识点
- 反卷积层和卷积层的关系
- 最后为什么要是sigmoid
Autoencoder模型编写 + compile方法 + train方法 + 保存和加载模型模块编写
实现代码——autoencoder代码
from tensorflow.keras import Model
# 一般进行版本更新都是改变的包的导向,或者改变包的方法名
from tensorflow.keras.layers import Input,Conv2D,ReLU,BatchNormalization,Flatten,Dense,
Reshape,Conv2DTranspose,Activation
# 引入backend,这个用来自己定义层,将一些函数定义成特定的层
from tensorflow.keras import backend as K
# 导入numpy,对三维数据进行操作
import numpy as np
# 导入对应优化器,注意,这里已经改变了包的地址
from tensorflow.keras.optimizers import Adam
# 导入损失函数
from tensorflow.keras.losses import MeanSquaredError
# 导入系统模块
import os
# 序列加载模块
import pickle
class Autoencoder:
"""
Autoencoder: 具有编码器和解码器的深度卷积自动编码器
"""
# 1、当前类别的构造函数,
def __init__(self,
input_shape,
conv_filters,
conv_kernels,
conv_strides,
latent_space_dim
):
# 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 # 潜在映射空间的维度,这里设置为2维度空间
# 3、这里的习惯就很好,将一个大模型拆解成两个小模型,编程的时候,只需要对应进行修改就行了
self.encoder = None
self.decoder = None
self.model = None
# 4、将部分变量声明为隐私变量,前置单下划线,私有变量
self._num_conv_layers = len(conv_filters)
# 5、设置build函数,这里是实例化类的时候进行调用
self._build()
# 9、后续添加的变量
self._shape_before_bottleneck = None
# 3.1 添加整体模型的输入,和encoder的输入是相同的
self._model_input = None
# 这个方法在三个模块都是需要修改的
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)
mse_loss = MeanSquaredError()
self.model.compile(
optimizer = optimizer,
loss = mse_loss
)
# 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)
autoencoder = Autoencoder(*parameters)
weight_path = os.path.join(save_folder,"weights.h5")
autoencoder.load_weights(weight_path)
return autoencoder
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
# 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_decoder()
self._build_autoencoder()
# 7、从上到下,逐个子方法进行实现
def _build_encoder(self):
# 8、按照网络的层次,将模型串联起来,按照模块进行组装
encoder_input = self._add_encoder_input()
conv_layers = self._add_conv_layers(encoder_input)
bottleneck = self._add_bottleneck(conv_layers)
self._model_input = encoder_input
self.encoder = Model(encoder_input,bottleneck,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
# 8、从上到下,按照顺序,逐个实现_build_encoder模块中所有方法
def _add_bottleneck(self,x):
# 9、第九部分进行编写
""" 首先将数据展平,然后在传入全链接层 """
self._shape_before_bottleneck = K.int_shape(x)[1:] # [batch_size,height,weight,channel],这里只需要后面三个的大小
x = Flatten()(x)
x = Dense(self.latent_space_dim,name = "encoder_output")(x)
return x
# 7、从上到下,逐个子方法进行实现
# 2.1 完成解码器的大部分框架
def _build_decoder(self):
""" 创建解码器,输入层、全连阶层、恢复成三维、进行反卷积、输出层 """
decoder_input = self._add_decoder_input()
dense_layer = self._add_dense_layer(decoder_input)
reshape_layer = self._add_reshape_layer(dense_layer)
conv_transpose_layers = self._add_conv_transpose_layers(reshape_layer)
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.latent_space_dim,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_autoencoder(self):
""" 对于自动编码器的识别,链接编码器和解码器 """
model_input = self._model_input
model_output = self.decoder(self.encoder(model_input))
self.model = Model(model_input,model_output,name = "Autoencoder")
if __name__ == '__main__':
autoencoder = Autoencoder(
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=2
)
autoencoder.summary()
实现代码——train代码
from ae import Autoencoder
from tensorflow.keras.datasets import mnist
LEARNING_RATE = 0.0005
BATCH_SIZE = 32
EPOCHS = 32
def load_mnist():
(x_train,y_train),(x_test,y_test) = mnist.load_data()
# 将数据正则化,在原来的数据上在增加一个维度,形成多维度的数据
x_train = x_train.astype("float32") / 255
x_train = x_train.reshape(x_train.shape + (1,))
x_test = x_test.astype("float32") / 255
x_test = x_test.reshape(x_test.shape + (1,))
return x_train,y_train,x_test,y_test
def train(x_train,learning_rate,batch_size,epochs):
autoencoder = Autoencoder(
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 = 2,
)
autoencoder.summary()
autoencoder.compile(learning_rate)
autoencoder.train(x_train,batch_size,epochs)
return autoencoder
if __name__ == '__main__':
x_train,_,_,_ = load_mnist()
autoencoder = train(x_train[:500],LEARNING_RATE, BATCH_SIZE,EPOCHS)
autoencoder.save("model")
autoencoder2 = Autoencoder.load("model")
autoencoder2.summary()
实现代码——保存和加载模型的代码
这部分的代码并不需要掌握,也不是深度学习的重点,就是常见的画图以及scatter的绘制
""" 模型分析,这部分代码是用来分析模型的训练效果的 """
import numpy as np
import matplotlib.pyplot as plt
from ae import Autoencoder
from train_ae import load_mnist
def select_images(images, labels, num_images=10):
""" 随机选择一定数量的图片 """
sample_images_index = np.random.choice(range(len(images)), num_images)
sample_images = images[sample_images_index]
sample_labels = labels[sample_images_index]
return sample_images, sample_labels
def plot_reconstructed_images(images, reconstructed_images):
""" 用来画出重建之后的图片 """
fig = plt.figure(figsize=(15, 3))
num_images = len(images)
for i, (image, reconstructed_image) in enumerate(zip(images, reconstructed_images)):
image = image.squeeze()
ax = fig.add_subplot(2, num_images, i + 1)
ax.axis("off")
ax.imshow(image, cmap="gray_r")
reconstructed_image = reconstructed_image.squeeze()
ax = fig.add_subplot(2, num_images, i + num_images + 1)
ax.axis("off")
ax.imshow(reconstructed_image, cmap="gray_r")
plt.show()
def plot_images_encoded_in_latent_space(latent_representations, sample_labels):
""" 用来绘制特征空间的,显示模型的训练效果 """
plt.figure(figsize=(10, 10))
plt.scatter(latent_representations[:, 0],
latent_representations[:, 1],
cmap="rainbow",
c=sample_labels,
alpha=0.5,
s=2)
plt.colorbar()
plt.show()
if __name__ == "__main__":
autoencoder = Autoencoder.load("model")
x_train, y_train, x_test, y_test = load_mnist()
num_sample_images_to_show = 8
sample_images, _ = select_images(x_test, y_test, num_sample_images_to_show)
reconstructed_images, _ = autoencoder.reconstruct(sample_images)
plot_reconstructed_images(sample_images, reconstructed_images)
num_images = 6000
sample_images, sample_labels = select_images(x_test, y_test, num_images)
_, latent_representations = autoencoder.reconstruct(sample_images)
plot_images_encoded_in_latent_space(latent_representations, sample_labels)
运行结果
- 我这里训练的比较少,而且数据集也比较少,所以点比较分散,下面是模型的训练效果
总结
- 在实现代码的过程中,尽量将别人的代码进行二次封装,在自己的代码中,尽量使使用自己的代码,因为自己的代码知道有什么借口,该用什么样的参数,这点在train文件中体现的尤为明显,train以及load_mnist两个函数都是作者自己的封装的。
- 在调用的参数中,如果确实用不到的,就将之声明为_,说明不会使用到。不要像以前一样,全部都命名,整个代码看起来乱的不行
- 在准备数据集中,要注意,在原来图片的维度上,在增加一个新的维度,然后形成一个大的数据集,前三个维度是图片的温度,最后一个维度是图片的序号。
- 将列表拆成独立的参数,是通过在列表前面加上“*”实现的,不要像以前一样,一个一个拆开写。
- 对于python中的类方法需要了解一下,就是不用声明实例,可以直接调用,比如说加载模型就是这样,直接使用类名调用对应的函数,然后返回一个实例。
总结
- 仅仅使用自动编码器提取特征,是将样例映射为特征空间中的的点,具有如下的缺陷
- 特征空间分布不均衡,并不是关于原点对称
- 特征空间存在大量的空白区域,对空白区域进行采样,生成的样例特征会很差
- 特征空间分布不均衡,所以采样点也不均衡
- 这里可以进行修改的地方
- 特征提取部分可以增加一些卷积模块,使用不同的方式进行提取
- 特征空间,可以使用高维度的特征空间,二维的特征空间,包含的数据太少了,并不能很好的表现数据的特征。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。