使用TensorFlow和Keras的自动编码器指南

235 阅读15分钟

所有的神经网络架构都是基于 相同的原则。有偏向的神经元和 激活函数 ,用加权连接连接。然而,不同的问题需要这些神经元和连接的不同组合。这就是为什么我们最终会有一个大的 "神经网络动物园",有不同的架构和学习过程。针对不同的问题有很多不同的架构,比如 卷积神经网络, 长短期神经网络, 自组织图, 变形金刚,等等。

这一次,我们将深入研究自动编码器的世界。自动编码器的概念可以追溯到1987年,是LeCun的工作。起初,这些网络被用于降维,但最近它们也被用于生成式建模。例如,DALL-E(由OpenAI发布)是一个基于变形金刚和自动编码器架构的神经网络。

自动编码器的结构与标准前馈神经网络的结构非常相似,但其主要目标与它们不同。由于这种相似性,同样的学习技术也可以用在这种类型的网络上。自动编码器利用无监督学习,或者更具体地说,自我监督学习。

使用这种类型的学习的神经网络只获得输入数据,并在此基础上产生某种形式的输出。这意味着,它们在训练过程中不使用预期的输出值,就像监督学习架构一样。在自动编码器的情况下,它们在训练过程中试图将输入信息复制到输出中。

在这篇文章中,我们将探讨以下主题:

  1. 自动编码器结构
  2. 它们是如何学习的?
  3. 稀疏性和去噪
  4. 数据集
  5. 用低级别的TF API实现
  6. 用Keras实现
  7. 卷积自动编码器 实施

1.自动编码器结构

最后一句话可能有点令人困惑,让我们观察一下下面的图片,把事情弄清楚。自动编码器之所以被称为自动编码器,是因为从本质上讲,它们自动对信息进行编码。我这样说并不是指某种形式的加密,而更像是一种压缩。

正如你所看到的,中间的隐藏层比输入和输出层的神经元少。在实践中,我们可以有更多的隐藏层。越靠近网络中间的层,它们的神经元就越少。这些对称的、类似沙漏的自动编码器通常被称为不完全自动编码器。

Autoencoder Architecture

我们也可以从数学上观察到这一点。第一部分,直到架构的中间,被称为编码-- f(x)。中间的隐藏层被称为代码,它是编码的结果-- h = f(x)。最后一个部分被称为解密(令人震惊!),它产生了数据的重建-- y = g(h) = g(f(x))。

因此,自动编码器在输入层获得信息,将其传播到中间层,然后在输出端返回相同的信息。这就是为什么中间层的神经元上的信息实际上是最有趣的信息,因为它代表了编码的信息。

1.1 过完整自动编码器

然而,有时中间层并不是这样使用的。意思是说,有时中间层的神经元比输入层和输出层多。在这种情况下,我们试图从输入层获得比输入本身所呈现的更多信息。这类自动编码器如下图所示,它们被称为超完整自动编码器。

Autoencoder Architecture - Overcomplete

如前所述,这种自动编码器的目标是从输入信息中提取比输入中给出的更多信息。然而,在这种情况下,没有什么能阻止自动编码器将信息从输入端移到输出端。

我们没有限制中间层的大小,而是使用了不同的正则化技术,鼓励自动编码器具有其他属性。这样一来,自动编码器可以是超完整的和非线性的。一些用于此的技术是稀疏和去噪,这将在后面进行研究。首先,我们需要介绍一下学习过程本身。

2.它们是如何学习的?

我们看到,自动编码器的结构让我们想起了前馈神经网络的结构。从本质上讲,这意味着我们可以采用之前使用过的相同技术。然而,我们也说过,自动编码器使用无监督学习。

诀窍在于,我们使用反向传播和其他来自监督学习模型的学习技术,但只使用输入数据,因为我们的输入数据和输出数据是一样的。有了这个简单的技巧,自动编码器把所有这些概念都放在一个伞下。

Programming Visual

2.1 处理数据

让我们来观察下图中的自动编码器。它是一个相当简单的结构:输入和输出层有五个神经元,中间层有两个。这个想法是要用两个值来呈现五个输入的信息。

值得注意的是,对于自动编码器来说,中间层的神经元通常使用 塔恩 激活函数,而输出层的神经元使用softmax激活函数。

Autoencoder Architecture - Learning

这种配置给出了最好的结果,所以我们在我们的例子中也使用了它。请注意,标有绿色的连接的权重为1,其余的连接的权重值为1。

在我们继续之前,我需要提到的另一件事是,我在例子中使用了二进制的输入值,这并不是强制性的。 现在让我们把一些输入数据放到我们的输入层中。在这个例子中,我们将放入一个数值为 [1, 0, 0, 0, 0] 的数组 ,最后会出现这种情况。

Autoencoder Architecture - Learning

当然,因为我们在输出层的神经元上使用了softmax激活函数,所以我们最终会得到与输入[1, 0, 0, 0, 0]相同的值。这正是我们想要的结果。

2.2 训练自动编码器

尽管如此,为了得到正确的权重值,也就是前面例子中给出的值,我们需要训练自动编码器。要做到这一点,我们需要遵循以下步骤:

  1. 在输入层上设置输入向量
  2. 将输入向量编码为低维度的向量--代码
  3. 通过解码编码向量来解构输入向量
  4. 计算重建误差 d(x, y) = ||x - y|||
  5. 误差反向传播
  6. 在多个历时中使重建误差最小化

总而言之,我们可以用以下函数来表达学习过程。

Autoencoder Formula

其中 L 是损失函数,使重建误差最小。对我来说,自动编码器最酷的部分是它们结合了监督学习的技术来进行无监督学习。

3.稀疏性和去噪

如前所述,超完整自动编码器使用不同的调节技术来限制数据的直接传播。这包括在重建误差中加入一定的偏差,称为稀疏性驱动- Ω(h)。然后,学习过程由函数表示。

Formula

一般来说,我们的想法是把这个额外的值作为一个阈值。这意味着只有某些误差被用于反向传播,而其他的误差则变得无关紧要。这种自动编码器通常在其他监督学习模型之前使用,以从输入数据中提取额外信息。通常他们都是以这种形象出现的。

Autocoder Sparsity

另一种调节过完整自动编码器的方法是所谓的去噪技术。这种技术在用于计算重建误差的输入值中加入随机噪声。这意味着,我们不是像稀疏自动编码器那样给成本函数的结果增加某个值,而是给该成本函数的条款增加一些值。在数学上,这可以用以下方式表示。

Autoencoder Density Formula

其中 x' 是 有一些噪声的输入 x的副本 。这种自动编码器提供了一个很好的例子,说明只要我们注意到它们不学习身份函数,就可以使用超完整自动编码器。

4.数据集

我们现在不再使用标准的MNIST数据集,而是使用Fashion-MNIST数据集。这个数据集的结构与MNIST数据集相同,即它有一个由60,000个样本组成的训练集和一个由10,000张衣服图像组成的测试集。所有的图像都经过了尺寸标准化和居中处理。图像的大小也被固定为28×28,所以预处理的图像数据被最小化。下面是这个样子的。

FMINST

在这个数据集中有10类衣服,但这并不是我们在这里的兴趣。本章的目的是让我们对自动编码器的结构更加熟悉,看看它们在图像压缩方面是否有什么优势。

5.自动编码器的实现--低级别的TensorFlow API

在这些例子中,我们实现了自动编码器,它有三个层:输入层、输出层和一个中间层。这个自动编码器功能的实现位于Autoencoder 类中。这里是使用低级别的TensorFlow API实现的。

import tensorflow as tf

class Autoencoder(object):
    def __init__(self, inout_dim, encoded_dim):
        learning_rate = 0.1 
        
        # Weights and biases
        hiddel_layer_weights = tf.Variable(tf.random_normal([inout_dim, encoded_dim]))
        hiddel_layer_biases = tf.Variable(tf.random_normal([encoded_dim]))
        output_layer_weights = tf.Variable(tf.random_normal([encoded_dim, inout_dim]))
        output_layer_biases = tf.Variable(tf.random_normal([inout_dim]))
        
        # Neural network
        self._input_layer = tf.placeholder('float', [None, inout_dim])
        self._hidden_layer = tf.nn.sigmoid(tf.add(tf.matmul(self._input_layer, hiddel_layer_weights), hiddel_layer_biases))
        self._output_layer = tf.matmul(self._hidden_layer, output_layer_weights) + output_layer_biases
        self._real_output = tf.placeholder('float', [None, inout_dim])
        
        self._meansq = tf.reduce_mean(tf.square(self._output_layer - self._real_output))
        self._optimizer = tf.train.AdagradOptimizer(learning_rate).minimize(self._meansq)
        self._training = tf.global_variables_initializer()
        self._session = tf.Session()
        
    def train(self, input_train, input_test, batch_size, epochs):
        self._session.run(self._training)
        
        for epoch in range(epochs):
            epoch_loss = 0
            for i in range(int(input_train.shape[0]/batch_size)):
                epoch_input = input_train[ i * batch_size : (i + 1) * batch_size ]
                _, c = self._session.run([self._optimizer, self._meansq], feed_dict={self._input_layer: epoch_input, self._real_output: epoch_input})
                epoch_loss += c
                print('Epoch', epoch, '/', epochs, 'loss:',epoch_loss)
        
    def getEncodedImage(self, image):
        encoded_image = self._session.run(self._hidden_layer, feed_dict={self._input_layer:[image]})
        return encoded_image
    
    def getDecodedImage(self, image):
        decoded_image = self._session.run(self._output_layer, feed_dict={self._input_layer:[image]})
        return decoded_image

让我们来研究一下这个类。正如你所看到的,这里有四个主要部分我们需要讨论。它们是构造函数、训练方法getEncodedImage 方法和getDecodedImage 方法。

5.1 构造函数

通过构造函数,我们得到输入层和输出层的尺寸,以及中间层的尺寸。我们也在构造函数中定义了所有对我们的TensorFlow图来说是必要的变量。

learning_rate = 0.1 
        
# Weights and biases
hiddel_layer_weights = tf.Variable(tf.random_normal([inout_dim, encoded_dim]))
hiddel_layer_biases = tf.Variable(tf.random_normal([encoded_dim]))
output_layer_weights = tf.Variable(tf.random_normal([encoded_dim, inout_dim]))
output_layer_biases = tf.Variable(tf.random_normal([inout_dim]))

学习率被硬编码为0.1,但如果你想的话,你也可以把这个值作为构造函数的参数来传递。在这里,我们定义了所有连接的权重值以及中间层和输出层的神经元的偏置。之后,神经网络就被定义了。

 # Neural network
self._input_layer = tf.placeholder('float', [None, inout_dim])
self._hidden_layer = tf.nn.sigmoid(tf.add(tf.matmul(self._input_layer, hiddel_layer_weights), hiddel_layer_biases))
self._output_layer = tf.matmul(self._hidden_layer, output_layer_weights) + output_layer_biases
self._real_output = tf.placeholder('float', [None, inout_dim])

self._meansq = tf.reduce_mean(tf.square(self._output_layer - self._real_output))
self._optimizer = tf.train.AdagradOptimizer(learning_rate).minimize(self._meansq)
self._training = tf.global_variables_initializer()
self._session = tf.Session()

对于中间层,我们使用一个简单的sigmoid函数。此外,我们还定义了*_real_output*占位符,用来保存预期的输出值(正如你所知道的,在自动编码器中,输出值与输入值相同)。对于误差函数,平均平方函数与Adagrad优化器结合使用。最后,TensorFlow会话被创建。

Coding Visual

5.2 培训方法

训练方法中,这个自动编码器被训练。首先,通过在定义的会话中运行 _training 操作来初始化所有全局变量。然后使用数据集中的数据来最小化误差。

def train(self, input_train, input_test, batch_size, epochs):
        self._session.run(self._training)
        
        for epoch in range(epochs):
            epoch_loss = 0
            for i in range(int(input_train.shape[0]/batch_size)):
                epoch_input = input_train[ i * batch_size : (i + 1) * batch_size ]
                _, c = self._session.run([self._optimizer, self._meansq], feed_dict={self._input_layer: epoch_input, self._real_output: epoch_input})
                epoch_loss += c
                print('Epoch', epoch, '/', epochs, 'loss:',epoch_loss)

最后,让我们看一下getEncodedImagegetDecodedImage方法。它们非常简单明了。在第一个中,我们得到编码过程的结果,在第二个函数中,我们得到解码过程的结果。注意,getDecodedImage 方法使用原始图像作为输入。下面是它们的样子。

def getEncodedImage(self, image):
    encoded_image = self._session.run(self._hidden_layer, feed_dict={self._input_layer:[image]})
    return encoded_image

def getDecodedImage(self, image):
    decoded_image = self._session.run(self._output_layer, feed_dict={self._input_layer:[image]})
    return decoded_image

5.3 使用自动编码器

使用这个类是非常简单的,我们只需要调用构造函数,然后是训练方法。然而,为了在Fashion-MNIST数据集上使用它,我们需要修改一下数据,因为你可以看到自动编码器的输入被定义为一个数据数组,而在数据集中我们有28×28的图像。下面是使用实例。

import numpy as np
from tensorflow.keras.datasets import fashion_mnist
from autoencoder_tensorflow import Autoencoder
import matplotlib.pyplot as plt

# Import data
(x_train, _), (x_test, _) = fashion_mnist.load_data()

# Prepare input
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))

# Tensorflow implementation
autoencodertf = Autoencoder(x_train.shape[1], 32)
autoencodertf.train(x_train, x_test, 100, 100)
encoded_img = autoencodertf.getEncodedImage(x_test[1])
decoded_img = autoencodertf.getDecodedImage(x_test[1])

# Tensorflow implementation results
plt.figure(figsize=(20, 4))
subplot = plt.subplot(2, 10, 1)
plt.imshow(x_test[1].reshape(28, 28))
plt.gray()
subplot.get_xaxis().set_visible(False)
subplot.get_yaxis().set_visible(False)

subplot = plt.subplot(2, 10, 2)
plt.imshow(decoded_img.reshape(28, 28))
plt.gray()
subplot.get_xaxis().set_visible(False)
subplot.get_yaxis().set_visible(False)

这个数据集最酷的地方在于它是Keras的一部分,它是TensorFlow 1.10.0的一部分。Keras是一个高级的API,它不再是一个单独的库,这使我们的生活变得更加容易。因此,我们从这个数据集中导入数据,然后将每张图片重塑为一个数组。

之后,我们创建一个Autoencoder的实例。对于中间层,我们使用32个神经元,这意味着我们要把一张图片从784(28×28)位压缩到32位。最后,我们训练自动编码器,得到解码后的图像并绘制结果。

6.自动编码器的实现 - Keras

这应该不是什么大问题,因为 Keras对 用户非常友好,易于使用。让我们 使用这个库 重新实现 Autoencoder 类。

import numpy as np

from tensorflow.keras.datasets import fashion_mnist
from tensorflow.keras.layers import Dense, Input
from tensorflow.keras.models import Model

import matplotlib.pyplot as plt

class Autoencoder(object):
    
    def __init__(self, inout_dim, encoded_dim):    
        input_layer = Input(shape=(inout_dim,))
        hidden_input = Input(shape=(encoded_dim,))
        hidden_layer = Dense(encoded_dim, activation='relu')(input_layer)
        output_layer = Dense(784, activation='sigmoid')(hidden_layer)
        
        self._autoencoder_model = Model(input_layer, output_layer)
        self._encoder_model = Model(input_layer, hidden_layer)
        tmp_decoder_layer = self._autoencoder_model.layers[-1]
        self._decoder_model = Model(hidden_input, tmp_decoder_layer(hidden_input))
        
        self._autoencoder_model.compile(optimizer='adadelta', loss='binary_crossentropy')
        
    def train(self, input_train, input_test, batch_size, epochs):    
        self._autoencoder_model.fit(input_train, 
                                    input_train,
                                    epochs = epochs,
                                    batch_size=batch_size,
                                    shuffle=True,
                                    validation_data=(
                                            input_test, 
                                            input_test))
        
    def getEncodedImage(self, image):
        encoded_image = self._encoder_model.predict(image)
        return encoded_image
    
    def getDecodedImage(self, encoded_imgs):
        decoded_image = self._decoder_model.predict(encoded_imgs)
        return decoded_image

Autoencoder 类 的API 很简单。 getDecodedImage 方法接收编码后的图像作为输入。从 Keras API的 模块 中 ,使用了Dense和Input类,从 模型 模块中, 导入了 Model 类。模型类用于表示神经网络。我们用它来创建三个模型。 _autoencoder_model, _encoder_model_decoder_model

Programming Visual

第一个是最重要的一个,它本质上是我们的自动编码器。其他两个只是辅助工具,用于在各自的函数中获得编码和解码的图像。 密集 类和 输入 类代表神经网络的层。

Input 类用于输入层, Dense 用于所有其他层。创建的模型最好的一点是,我们可以非常容易地训练它,只需调用fit函数并将输入数据传递给它。这正是我们在 训练方法 中所做的 。

实现的自动编码器类是这样使用的。

(x_train, _), (x_test, _) = fashion_mnist.load_data()

x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))

autoencoder = Autoencoder(x_train.shape[1], 32)
autoencoder.train(x_train, x_test, 256, 50)
encoded_imgs = autoencoder.getEncodedImage(x_test)
decoded_imgs = autoencoder.getDecodedImage(encoded_imgs)

然而,使用这段代码的结果与我们在之前的实现中使用的差不多,也就是说,不是很好。

正如你所看到的,结果是好了一点,但还是远远不够好。事实证明,自动编码器在图像压缩方面不是那么好。在下一个也是最后一个例子中,我们将尝试使用卷积层来达到更好的效果。

7.卷积自动编码器的实现

让我们把我们的自动编码器变得更复杂一些。由于我们处理的是图像,我们可以尝试使用卷积神经网络通常利用的层。毕竟,我们知道,当涉及到图片时,卷积神经网络是我们的首选武器。自动编码器类的第二个化身看起来像这样:

import numpy as np

from tensorflow.keras.datasets import fashion_mnist
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D
from tensorflow.keras.models import Model

import matplotlib.pyplot as plt

class Autoencoder(object):
    
    def __init__(self):    
        
        # Encoding
        input_layer = Input(shape=(28, 28, 1)) 
        encoding_conv_layer_1 = Conv2D(16, (3, 3), activation='relu', 
                                       padding='same')(input_layer)
        encoding_pooling_layer_1 = MaxPooling2D((2, 2), 
                  
                                     padding='same')(encoding_conv_layer_1)
        encoding_conv_layer_2 = Conv2D(8, (3, 3), activation='relu', 
                                     padding='same')(encoding_pooling_layer_1)
        encoding_pooling_layer_2 = MaxPooling2D((2, 2), 
                                     padding='same')(encoding_conv_layer_2)
        encoding_conv_layer_3 = Conv2D(8, (3, 3), activation='relu', 
                                     padding='same')(encoding_pooling_layer_2)
        code_layer = MaxPooling2D((2, 2), padding='same')(encoding_conv_layer_3)
        
        # Decoding
        decodging_conv_layer_1 = Conv2D(8, (3, 3), activation='relu', 
                                      padding='same')(code_layer)
        decodging_upsampling_layer_1 = UpSampling2D((2, 2))(decodging_conv_layer_1)
        decodging_conv_layer_2 = Conv2D(8, (3, 3), activation='relu', 
                                      padding='same')(decodging_upsampling_layer_1)
        decodging_upsampling_layer_2 = UpSampling2D((2, 2))(decodging_conv_layer_2)
        decodging_conv_layer_3 = Conv2D(16, (3, 3),                      
                                   activation='relu')(decodging_upsampling_layer_2)
        decodging_upsampling_layer_3 = UpSampling2D((2, 2))(decodging_conv_layer_3)
        output_layer = Conv2D(1, (3, 3), activation='sigmoid', 
                                      padding='same')(decodging_upsampling_layer_3)
        
        self._model = Model(input_layer, output_layer)
        self._model.compile(optimizer='adadelta', loss='binary_crossentropy')
        
    def train(self, input_train, input_test, batch_size, epochs):    
        self._model.fit(input_train, 
                        input_train,
                        epochs = epochs,
                        batch_size=batch_size,
                        shuffle=True,
                        validation_data=(
                                input_test, 
                                input_test))
        
    
    def getDecodedImage(self, encoded_imgs):
        decoded_image = self._model.predict(encoded_imgs)
        return decoded_image

正如你所看到的,API已经改变了。构造函数不再获得输入和中间层的尺寸。这是隐含的,因为我们知道我们得到的图像是28×28的。即使输入层现在被塑造成一个图像,它也不再只是一个数据数组。

这意味着我们不需要再从数据集中重塑图像,至少不需要像以前的实现方式那样。然后使用三个卷积层和最大集合层。这个过程会对图像进行压缩,因为我们在每个卷积层中使用的特征检测器越来越少。图像的_代码_层大小将是(4,4,8),即128维。

AI Visual

之后,自动编码器的解码部分使用一系列的卷积层和上采样层。这样,图像就被重建了。关于自动编码器的训练,我们使用同样的方法,也就是说,我们将必要的信息传递给拟合方法。

这里需要注意的另一件事是,我们不再有getEncodedImage函数。这意味着这个类可以像这样使用。

# Prepare input
X_train = X_train.astype('float32') / 255.
X_test = X_test.astype('float32') / 255.
X_train = np.reshape(X_train, (len(X_train), 28, 28, 1))
X_test = np.reshape(X_test, (len(X_test), 28, 28, 1))

autoencoder = Autoencoder()
autoencoder.train(X_train, X_test, 256, 50)
decoded_imgs = autoencoder.getDecodedImage(X_test)

plt.figure(figsize=(20, 4))
for i in range(10):
    # Original
    subplot = plt.subplot(2, 10, i + 1)
    plt.imshow(X_test[i].reshape(28, 28))
    plt.gray()
    subplot.get_xaxis().set_visible(False)
    subplot.get_yaxis().set_visible(False)

    # Reconstruction
    subplot = plt.subplot(2, 10, i + 11)
    plt.imshow(decoded_imgs[i].reshape(28, 28))
    plt.gray()
    subplot.get_xaxis().set_visible(False)
    subplot.get_yaxis().set_visible(False)
plt.show()

显而易见,流程非常相似。现在的数据是与卷积使用相一致的,它仍然被重塑,但方式不同。基本上,只有一个通道被定义为图像,因为它们只有黑色和白色。之后,自动编码器被训练,结果被绘制出来。

结论

我们在这篇文章中玩了很多不同的概念。其目的是让我们对自动编码器的结构更加熟悉,并使用不同的方法。我们可以从这个实验中得到的是,自动编码器在图像压缩方面不是很好,在这种情况下,使用jpeg压缩可能是一个更好的主意。然而,我们看到Autoencoders可以用不同的方式构建。

谢谢您的阅读!