放养的深度学习-浅谈自编码器
说起非监督学习,总逃不过Yann LeCnn的这张图,他将强化学习比做蛋糕上的樱桃,将监督学习比做蛋糕的奶油,而将非监督学习比做蛋糕底座。这个比喻不止说明了这三种学习范式的江湖地位。更形象的指出了各自的特点,强化学习得到最多的关注,监督学习好吃但是相对价格更贵(需要人工进行数据标记),而非监督学习虽然潜力巨大,但却相对来说最默默无闻。
除了不需要人工数据标记这个优点,非监督学习的另一个优点是不会引入人的偏见。举一个具体的例子,如果要开发一个能够自动对手机里照片按照内容分类的工具,那么监督学习的做法就是将图像进行分类,看看图片中的主体是人物,动植物,建筑,自然风景或是文字,然后按照每张照片的分类将照片放到不同的相册中。而非监督的方式要做的是将每一张图片放到一个二维空间中,不是随便的放,而是要让内容相近的图片都凑到一起,最终出现几个图片簇,而在每个簇之间的距离要尽可能的大。
如何判断图片的语义是否相近了,可以假设同一个时间段拍摄的,在同一个地点拍摄的照片具有相似的内容。一旦训练好了这样一个将图片自动聚类(降维)的模型,那么你朋友第二天发给你的昨天聚会照片,即使系统显示的时间不在同一天,也会被归档在正确的相册中。
在深度学习出现之前,聚类已经有了很多种成熟的方法了,从最简单的K means到不需要设定聚类数的affinitypropagation,再到层次化的聚类。然而,这些聚类方法对于非结构化的数据,例如图片,声音处理的不好,如果能够将非结构化数据的维度降低,那就可以使用传统的聚类方法了。然而,线性的聚类方法,例如PCA或MDS,在图像上的表现不佳。这里展示的是使用PCA对MINST数据进行降维的结果。不同的数字并没有被明显的区分开。
而使用非监督学习在深度学习中最典型的架构-自编码器,就可以做到对图像等非结构数据进行降维。下面展示的是用深度学习对手写数字进行的降维,不同的数字区分要比上一副图好的多。
任何深度学习的架构的构建,都是将想要达到的目标翻译成损失函数的过程,数据降维的目标是让降维后的数据能够更好的保持原有数据的区分度,让原来能分开的数据现在也能分开。但原始的没有标签的数据点之间距离都没有明确的定义,又如何用一个公式来量化降维后数据点之间的区分度。
如果在横向上无法解决问题,那可以试试在纵向上进行探索。先假设问题已经解决了,我们找到了一种完美的将MINST图像进行降维的方案,那么我们能拿这个方案做什么?假设每个MINST数据集中的图像有一个唯一的6位ID,越靠前的位置表示对该图片的越大类的分类。那么我们完美的降维方案给出的结果将可以100%的预测出图片的ID。接着假设我们还没有找到这个完美的降维方案,但已经差的不多了,那么我们根据降维后预测出的图像ID应该只在最后几位有差距。到了这一步,待优化的损失函数已经呼之欲出了,即降维后的特征生成的图像和原始的图像差距有多大。接下来要做的就是先训练一个神经网络来降维,再训练一个网络进行升维。
从最基本的自编码器出发,将损失函数进行改变,让输入加入误差,而待重构的是不带误差的原始数据,就可以得到变分自编码器(VAE)。而在网络结构上,也可以使用卷积网络或循环神经网络。然而不同于深度网络,自编码器的结构使得网络可以逐层训练。例如一个堆叠的自编码器,先将原始的100维数据降低为50维,再降低为10维,最后降低为3维。那么就可以先训练一个浅层的100->50->100的自编码器,训练好后将网络固定,再使用上次训练得出的降维数据训练50->10>50的自编码器,依次类推,最后将训练好的编码器和解码器按顺序堆叠起来。这样的训练方式可以避免梯度消失的问题。
不同深度的网络学习率随着深度增加显著降低
自编码器进行数据降维的目地,是为了进行方便聚类。使用Kmeans等算法,可以对原来的Minst数据进行聚类。在有数据标签的时候,使用Normalized Mutual Information (NMI),可以来评价聚类的效果,这个数字越大,表示被正确的聚在一起的数字越多。如此,就可以评价使用原始的数据聚类和使用自编码器降维后的数据聚类的效果。
以下是Keras相关代码
# this is our input placeholder
input_img = Input(shape=(784,))
# "encoded" is the encoded representation of the input
encoded = Dense(500, activation='relu')(input_img)
encoded = Dense(500, activation='relu')(encoded)
encoded = Dense(2000, activation='relu')(encoded)
encoded = Dense(10, activation='sigmoid')(encoded)
# "decoded" is the lossy reconstruction of the input
decoded = Dense(2000, activation='relu')(encoded)
decoded = Dense(500, activation='relu')(decoded)
decoded = Dense(500, activation='relu')(decoded)
decoded = Dense(784)(decoded)
# this model maps an input to its reconstruction
autoencoder = Model(input_img, decoded)
# this model maps an input to its encoded representation
encoder = Model(input_img, encoded)
autoencoder.compile(optimizer='adam', loss='mse')
pred_auto_train = encoder.predict(train_x)
pred_auto = encoder.predict(val_x)
km.fit(pred_auto_train)
pred = km.predict(pred_auto)
normalized_mutual_info_score(val_y, pred)