从本讲开始,笔者将花一些时间和大家一起学习深度学习中的无监督模型。从现有情况来看,无监督学习很有可能是一把决定深度学习未来发展方向的钥匙,在缺乏高质量打标数据的监督机器学习时代,若是能在无监督学习方向上有所突破对于未来深度学习的发展意义重大。从自编码器到生成对抗网络,笔者将和大家一起来探索深度学习中的无监督学习。
自编码器
所谓自编码器(Autoencoder,AE),就是一种利用反向传播算法使得输出值等于输入值的神经网络,它现将输入压缩成潜在空间表征,然后将这种表征重构为输出。所以,从本质上来讲,自编码器是一种数据压缩算法,其压缩和解压缩算法都是通过神经网络来实现的。自编码器有如下三个特点:
- 数据相关性。就是指自编码器只能压缩与自己此前训练数据类似的数据,比如说我们使用mnist训练出来的自编码器用来压缩人脸图片,效果肯定会很差。
- 数据有损性。自编码器在解压时得到的输出与原始输入相比会有信息损失,所以自编码器是一种数据有损的压缩算法。
- 自动学习性。自动编码器是从数据样本中自动学习的,这意味着很容易对指定类的输入训练出一种特定的编码器,而不需要完成任何新工作。
构建一个自编码器需要两部分:编码器(Encoder)和解码器(Decoder)。编码器将输入压缩为潜在空间表征,可以用函数f(x)来表示,解码器将潜在空间表征重构为输出,可以用函数g(x)来表示,编码函数f(x)和解码函数g(x)都是神经网络模型。
所以,我们大致搞清楚了自编码器是一种让输入等于输出的算法。但仅仅如此吗?当然不是,如果一个算法只是为了让输入等于输出,那这个算法意义肯定不大,自编码器的核心价值在于经编码器压缩后的潜在空间表征。上面我们提到自编码器是一种数据有损的压缩算法,经过这种有损的数据压缩,我们可以学习到输入数据种最重要的特征。
虽然自编码器对于我们是个新概念,但是其内容本身非常简单。在后面的keras实现中大家可以看到如何用几行代码搭建和训练一个自编码器。那么重要的问题来了,自编码器这样的自我学习模型到底有什么用呢?这个问题的答案关乎无监督学习在深度学习领域的价值,所以还是非常有必要说一下的。自编码器吸引了一大批研究和关注的主要原因之一是很长时间一段以来它被认为是解决无监督学习的可能方案,即大家觉得自编码器可以在没有标签的时候学习到数据的有用表达。但就具体应用层面上而言,自编码器通常有两个方面的应用:一是数据去噪,二是为进行可视化而降维。自编码器在适当的维度和系数约束下可以学习到比PCA等技术更有意义的数据映射。
自编码器的keras实现
原始的自编码器实现起来非常容易,我们来看一下如何使用keras来实现一个简易的自编码器。使用全连接网络作为编码和解码器:
from keras.layers import Input, Dense
from keras.models import Model
import warnings warnings.filterwarnings('ignore')
# 编码潜在空间表征维度
encoding_dim = 32
# 自编码器输入
input_img = Input(shape=(784,))
# 使用一个全连接网络来搭建编码器
encoded = Dense(encoding_dim, activation='relu')(input_img)
# 使用一个全连接网络来对编码器进行解码
decoded = Dense(784, activation='sigmoid')(encoded)
# 构建keras模型
autoencoder = Model(input=input_img, output=decoded)
模型概要如下:
我们也可以把编码器和解码器当作单独的模型来使用:
# 编码器模型
encoder = Model(input=input_img, output=encoded)
# 解码器模型
encoded_input = Input(shape=(encoding_dim,)) decoder_layer = autoencoder.layers[-1] decoder = Model(input=encoded_input, output=decoder_layer(encoded_input))
对自编码器模型进行编译并使用mnist数据进行训练:
# 编译模型
autoencoder.compile(optimizer='adadelta', loss='binary_crossentropy')
# 准备mnist数据
from keras.datasets import mnist
import numpy as np (x_train, _), (x_test, _) = 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.fit(x_train, x_train, nb_epoch=50, batch_size=256, shuffle=True, validation_data=(x_test, x_test))
50轮训练之后的损失降低到0.1:
对原始输入图像和自编码器训练后的图像进行可视化的直观展示,看看自编码器学习效果如何:
import matplotlib.pyplot as plt encoded_imgs = encoder.predict(x_test) decoded_imgs = decoder.predict(encoded_imgs) n = 10
plt.figure(figsize=(20, 4))
for i in range(1, n):
# 展示原始图像 ax = plt.subplot(2, n, i) plt.imshow(x_test[i].reshape(28, 28)) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False)
# 展示自编码器重构后的图像 ax = plt.subplot(2, n, i + n) plt.imshow(decoded_imgs[i].reshape(28, 28)) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) plt.show()
效果如下,可见基于全连接网络的自编码器在mnist数据集上表现还不错。
自编码器的降噪作用
前面我们讲到自编码器的一个重要作用就是给数据进行降噪处理。同样是mnist数据集,我们给原始数据添加一些噪声看看:
from keras.datasets import mnist
import numpy as np (x_train, _), (x_test, _) = mnist.load_data() x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = np.reshape(x_train, (len(x_train), 1, 28, 28)) x_test = np.reshape(x_test, (len(x_test), 1, 28, 28))
# 给数据添加噪声
noise_factor = 0.5
x_train_noisy = x_train + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_train.shape) x_test_noisy = x_test + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_test.shape) x_train_noisy = np.clip(x_train_noisy, 0., 1.) x_test_noisy = np.clip(x_test_noisy, 0., 1.)
展示添加了噪声后的mnist数据示例:
# 噪声数据展示
n = 10
plt.figure(figsize=(20, 4))
for i in range(1, n): ax = plt.subplot(2, n, i) plt.imshow(x_train_noisy[i].reshape(28, 28)) plt.gray() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) plt.show()
下面我们想要自编码器模型对上述经过噪声处理后的数据进行降噪和还原,我们使用卷积神经网络作为编码和解码模型:
from keras.layers import Input, Dense, UpSampling2D
from keras.layers import Convolution2D, MaxPooling2D
from keras.models import Model
# 输入维度
input_img = Input(shape=(1, 28, 28))
# 基于卷积和池化的编码器
x = Convolution2D(32, 3, 3, activation='relu', border_mode='same')(input_img) x = MaxPooling2D((2, 2), border_mode='same')(x) x = Convolution2D(32, 3, 3, activation='relu', border_mode='same')(x) encoded = MaxPooling2D((2, 2), border_mode='same')(x)
# 基于卷积核上采样的解码器
x = Convolution2D(32, 3, 3, activation='relu', border_mode='same')(encoded) x = UpSampling2D((2, 2))(x) x = Convolution2D(32, 3, 3, activation='relu', border_mode='same')(x) x = UpSampling2D((2, 2))(x) decoded = Convolution2D(1, 3, 3, activation='sigmoid', border_mode='same')(x)
# 搭建模型并编译
autoencoder = Model(input_img, decoded) autoencoder.compile(optimizer='adadelta', loss='binary_crossentropy')
对噪声数据进行自编码器的训练:
# 对噪声数据进行自编码训练
autoencoder.fit(x_train_noisy, x_train, nb_epoch=100, batch_size=128, shuffle=True, validation_data=(x_test_noisy, x_test))
经过卷积自编码器训练之后的噪声图像还原效果如下:
添加的噪声基本被消除了,可见自编码器确实是一种较好的数据降噪算法。本讲的笔记就到这里,后面笔者将继续深入分享关于变分自编码器(VAE)的相关内容。