原文链接
现代的神经网络中,我们的任务常常都是通过隐藏层学习出输入或是数据的表达(representation),进而完成诸如数据压缩,降维,特征提炼等诸多任务。自编码器就是帮助我们学习数据表达的一种方式,实际上现在的深度网络可以看成是自编码器的堆砌来完成feature extract的工作,再利用学习到的feature完成分类或是回归任务。那么如何理解自编码器呢?我们已经知道,现在的大多神经网络都是通过各种方式在输入与输出
之间建立映射函数
。而自编码器要学习的是一种特殊的映射,即恒等映射
,换句话说,它做的工作是重建(reconstruct)输入。
自编码器最简单的形式,这里我们以单隐藏层的自编码器为例,输入与输出相同,使用l2 loss
from tensorflow.examples.tutorials.mnist import input_data
from keras.layers import Input, Dense, Conv2D, MaxPooling2D, UpSampling2D, Flatten, Reshape
from keras.models import Model
from keras.optimizers import Adam
from keras.regularizers import activity_l1
import numpy as np
import matplotlib.pyplot as plt
import keras.backend as K
import tensorflow as tf
mnist = input_data.read_data_sets('../data/MNIST_data', one_hot=True)
X, _ = mnist.train.images, mnist.train.labels
inputs = Input(shape=(784,))
h = Dense(64, activation='sigmoid')(inputs)
outputs = Dense(784)(h)
model = Model(input=inputs, output=outputs)
model.compile(optimizer='adam', loss='mse')
model.fit(X, X, batch_size=64, nb_epoch=5)
到此我们不禁要问,费了这么大功夫我们就折腾出来一个恒等映射?这有什么价值?
是的,单纯的恒等映射没有什么用处,但的当我们加上约束之后就能挖掘出它的价值所在。比如在上面的例子中,我们用将784维的特征向量输入,而隐藏层只有64个单元。即我们强迫网络用64维来表达原本784维的信息,网络需要进行压缩并提炼出高维表达以完成encode过程。这种思想与PCA主成分分析或是CNN中1*1卷积核dimension reduction 都有相通之处。
另一种添加约束(constraint)的方法就是对loss损失函数添加约束,如正则化。下面以l1正则化为例给出了实现代码
inputs = Input(shape=(784,))
h = Dense(64, activation='sigmoid', activity_regularizer=activity_l1(1e-5))(inputs)
outputs = Dense(784)(h)
model = Model(input=inputs, output=outputs)
model.compile(optimizer='adam', loss='mse')
model.fit(X, X, batch_size=64, nb_epoch=5)
计算隐藏层的mean value 我们得到的结果是0.148664,而上面的朴素自编码器得到的是0.512477,由此可以发现隐藏层的激活程度被抑制了,即神经元之间的连接更稀疏了,因此我们称这种编码器为稀疏自编码器
很自然地,我们会想到增加自编码器中的网络层数,这里我们增加到3个隐藏层
inputs = Input(shape=(784,))
h = Dense(128, activation='relu')(inputs)
encoded = Dense(64, activation='relu', activity_regularizer=activity_l1(1e-5))(h)
h = Dense(128, activation='relu')(encoded)
outputs = Dense(784)(h)
model = Model(input=inputs, output=outputs)
model.compile(optimizer='adam', loss='mse')
model.fit(X, X, batch_size=64, nb_epoch=5)
在这些中间层中,我们可以选取任意层来作为我们的数据表达。简单与对称起见,我们常常选择正中间的一层
把自编码器中的全连接层用卷积层替代
inputs = Input(shape=(28, 28, 1))
h = Conv2D(4, 3, 3, activation='relu', border_mode='same')(inputs)
encoded = MaxPooling2D((2, 2))(h)
h = Conv2D(4, 3, 3, activation='relu', border_mode='same')(encoded)
h = UpSampling2D((2, 2))(h)
outputs = Conv2D(1, 3, 3, activation='relu', border_mode='same')(h)
model = Model(input=inputs, output=outputs)
model.compile(optimizer='adam', loss='mse')
model.fit(X, X, batch_size=64, nb_epoch=5)
从上面的例子中我们可以看到,自编码器实际上就是通过若干个隐藏层(hidden layer)实现的,我们通过施加不同的约束来学习我们想要的表达。下一篇博客会讲述收缩自编码器(CAE),去噪自编码器(DAE),变分自编码器(VAE)以及条件变分自编码器(CVAE)