自编码器是一种神经网络,可以执行可视化降维。例如,假设您有代表男女年龄和身高的数据。如果你想绘制你的数据,你可以通过在 x 轴上绘制年龄,在 y 轴上绘制身高,男性用蓝点,女性用粉色点来轻松完成。但是如果你的数据有五个维度,比如(年龄、身高、体重、收入、受教育年限),那么就没有简单的方法来绘制数据。
图 7-1:使用 Keras 进行可视化的自编码器降维
图 7-1 中的截图展示了可视化自编码器降维的演示。演示程序首先将 1797 个数据项加载到内存中。每个数据项有 64 个维度。演示程序创建了一个自编码器,将每个 64 维数据项编码/压缩到二维,然后将结果绘制成图形。每个数据项属于十个类中的一个,这个信息被用来给图上的每个点着色。
1,797 个数据项代表从 0 到 9 的手写数字的原始 8×8 位图。换句话说,自编码器可视化应用于本身就是可视化表示的数据。然而,用于可视化的自编码器降维可以应用于任何类型的数据。例如,费希尔的虹膜数据集有四个维度(萼片长度、萼片宽度、花瓣长度和花瓣宽度),数据可以简化为两个维度,以便使用自编码器进行可视化。
演示数据如下所示:
0,0,0,1,11,0,0,0,0,0,0,7,8, . . . 16,4,0,0,4 0,0,9,14,8,1,0,0,0,0,12,14, . . . 5,11,1,0,8 . . .
每行数据代表一个手写数字。一行中的前 64 个值是介于 0 和 16 之间的灰度像素值。一行的最后一个值是数字值。
图 7-2 中的截图显示了其中一个数据项。数据项首先显示在外壳中,使用以十六进制表示的原始像素值。然后项目以图形方式显示。
图 7-2:UCI 数字之一
自编码器的目标是将一个项目的 64 个维度减少到只有两个值,以便可以将该项目绘制为 x-y 图上的一个点。
代码清单 7-1 显示了生成图 7-1 所示输出的完整程序。该程序首先对程序文件名和使用的 Python、TensorFlow 和 Keras 版本进行注释,然后导入 NumPy、Keras、TensorFlow、PyPlot 和 OS 包:
`# digits_autoenc.py
import numpy as np import keras as K`
import tensorflow as tf import matplotlib.pyplot as plt import os os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
在非演示场景中,您可能希望在注释中包含更多细节。因为 Keras 和 TensorFlow 正在快速发展,所以您应该始终记录正在使用的版本。在使用 Keras 和其他开源软件时,版本不兼容可能是一个严重的问题。
代码清单 7-1:自编码器程序
#
digits_autoenc.py
#
Python 3.5.2, TensorFlow 2.1.5, Keras 1.7.0
#
==================================================================================
import numpy as np
import keras as K
import tensorflow as tf
import matplotlib.pyplot as plt
import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2' #
suppress CPU msg
class MyLogger(K.callbacks.Callback):
def __init__(self, n):
self.n = n # print
loss every n epochs
def on_epoch_end(self, epoch, logs={}):
if epoch % self.n == 0:
curr_loss =logs.get('loss')
print("epoch =
%4d loss = %0.6f"
% (epoch, curr_loss))
def main():
# 0\. get started
print("\nBegin
UCI digits dim reduction using an autoencoder")
np.random.seed(1)
tf.set_random_seed(1)
# 1\. load data into memory
print("Loading
8x8 digits data into memory \n")
data_file = ".\\Data\\digits_uci_test_1797.txt"
data_x = np.loadtxt(data_file, delimiter=",", usecols=range(0,64),
dtype=np.float32)
labels = np.loadtxt(data_file, delimiter=",", usecols=[64],
dtype=np.float32)
data_x = data_x / 16
# 2\. define autoencoder
my_init = K.initializers.glorot_uniform(seed=1)
X = K.layers.Input(shape=[64])
layer1 = K.layers.Dense(units=32, activation='sigmoid',
kernel_initializer=my_init)(X)
layer2 = K.layers.Dense(units=2, activation='sigmoid',
kernel_initializer=my_init)(layer1)
layer3 = K.layers.Dense(units=32, activation='sigmoid',
kernel_initializer=my_init)(layer2)
layer4 = K.layers.Dense(units=64, activation='sigmoid',
kernel_initializer=my_init)(layer3)
enc_dec = K.models.Model(X, layer4)
encoder = K.models.Model(X, layer2)
# 3\. compile model
simple_adam = K.optimizers.Adam()
enc_dec.compile(loss='mean_squared_error',
optimizer=simple_adam)
# 4\. train model
print("Starting
training")
max_epochs = 500
my_logger = MyLogger(n=100)
h = enc_dec.fit(x=data_x, y=data_x, batch_size=8, epochs=max_epochs,
verbose=0, callbacks=[my_logger])
print("Training
complete \n")
# 5\. generate (x,y) pairs for each
digit
reduced = encoder.predict(data_x)
# 6\. graph the digits in 2D
print("Displaying
64-dim data in 2D: \n")
plt.scatter(x=reduced[:, 0], y=reduced[:, 1],
c=labels, edgecolors='none', alpha=0.9,
cmap=plt.cm.get_cmap('nipy_spectral', 10), s=20)
plt.xlabel('component
1')
plt.ylabel('component
2')
plt.colorbar()
plt.show()
#
==================================================================================
if __name__ == "__main__":
main()
程序导入整个 Keras 包并分配一个别名K
。另一种方法是只导入您需要的模块,例如:
from keras.models import Sequential from keras.layers import Dense, Activation
即使 Keras 使用 TensorFlow 作为其后端引擎,您也不需要显式导入 TensorFlow,除非是为了设置其随机种子。导入操作系统包只是为了抑制恼人的 TensorFlow 启动警告消息。
程序结构由一个单独的main
函数组成,外加一个用于在训练期间显示消息的助手类。助手类的定义是:
class MyLogger(K.callbacks.Callback): def __init__(self, n): self.n = n # print loss every n epochs
def on_epoch_end(self, epoch, logs={}): if epoch % self.n == 0: curr_loss =logs.get('loss') print("epoch = %4d loss = %0.6f" % (epoch, curr_loss))
MyLogger
类用于每 100 个纪元打印一次内置损耗函数的值。其思想是fit()
方法可以显示每个时期的进度消息,或者根本不显示,但是如果您想要每隔几个时期显示消息,您必须定义一个自定义回调类。
main()
代码以下列内容开头:
`def main():
print("\nBegin UCI digits dim reduction using an autoencoder") np.random.seed(1) tf.set_random_seed(1)
print("Loading 8x8 digits data into memory \n") data_file = ".\Data\digits_uci_test_1797.txt" data_x = np.loadtxt(data_file, delimiter=",", usecols=range(0,64), dtype=np.float32) labels = np.loadtxt(data_file, delimiter=",", usecols=[64], dtype=np.float32) data_x = data_x / 16 . . .`
在大多数情况下,你希望你的结果是可复制的。Keras 库广泛使用了 NumPy 全局随机数生成器,因此设置种子值是一个很好的做法。程序中使用的种子值1
是任意的。同样,因为 Keras 使用 TensorFlow,所以您也会经常想要设置它的种子。不幸的是,由于并行任务的数值舍入顺序,程序结果通常不完全可再现。
我更喜欢用两个空格缩进,而不是通常的四个空格。所有正常的错误检查都被删除了,以保持主要思想尽可能清晰。
程序假设训练和测试数据文件位于名为Data
的子目录中。程序本身没有任何关于数据文件结构的信息。我强烈建议您在计划中加入以下评论:
`# data has 1797 items, is comma-delimited and looks like:
当你写程序的时候,这种信息很容易记住,但是几个星期后就很难记住了。
使用np.loadtxt()
功能将单个数据文件读入存储器。将数据读入内存的方法有很多,但loadtxt()
功能的通用性足以满足大多数问题场景。NumPy genfromtxt()
函数非常相似,但是它给了你一些额外的选项,比如处理丢失的数据。loadtxt()
功能参数很多,但大多数情况下,只需要usecols
、delimiter
、dtype
即可。
注意usecols
可以接受[64]
这样的列表或者range(0,64)
这样的 Python 范围。如果使用range()
功能,注意记住第一个参数是包含的,第二个参数是排他的。
默认dtype
参数值为numpy.float
,是 Python float
的别名,与numpy.float64
完全相同。但是几乎所有 Keras 函数的默认数据类型都是numpy.float32
,所以程序指定了这种类型。其思想是,对于大多数机器学习问题,使用 64 位值获得的精度优势不值得内存和性能损失。
不同于使用诸如loadtxt()
这样的 NumPy 函数将数据读入内存,一种不同的方法是使用 Pandas(最初是“面板数据”,现在是“Python 数据分析”)库,该库具有许多高级数据操作功能。然而,熊猫有一条不平凡的学习曲线,需要你投入大量的时间。
数字像素 x 数据加载到内存后,通过将所有值除以 16 对数据进行归一化。这使得所有像素值都在 0.0 和 1.0 之间,这使得自编码器更容易训练。
该程序使用以下代码定义 64-32-2-32-64 自编码器:
`# 2. define autoencoder my_init = K.initializers.glorot_uniform(seed=1) X = K.layers.Input(shape=[64]) layer1 = K.layers.Dense(units=32, activation='sigmoid', kernel_initializer=my_init)(X) layer2 = K.layers.Dense(units=2, activation='sigmoid', kernel_initializer=my_init)(layer1) layer3 = K.layers.Dense(units=32, activation='sigmoid', kernel_initializer=my_init)(layer2) layer4 = K.layers.Dense(units=64, activation='sigmoid', kernel_initializer=my_init)(layer3)
enc_dec = K.models.Model(X, layer4) encoder = K.models.Model(X, layer2)`
用于降维的自编码器的架构最好用图表来解释——见图 7-3。该图显示了一个小型 6-3-2-3-6 自编码器,而不是演示程序的大型 64-32-2-32-64 架构。
有两个关键的想法。首先,自编码器的输入和输出是相同的。第二,最内层有两个节点,对应于绘图的 x 轴和 y 轴值。
图 7-3: A 6-3-2-3-6 自动转换器
自编码器是一种特殊的神经网络,它学习预测自己的输入。训练后,最内部的两个节点是输入的降维表示。自编码器是一种特殊类型的神经网络,称为编码器-解码器。网络的编码器部分提取网络输入的压缩表示。
自编码器输入节点和输出节点的数量由数据的维度决定。如果目标是可视化的降维,最内层通常有两三个节点。其他隐藏层的数量和每个层中的节点数量是自由参数。
将训练数据读入存储器并定义自编码器后,对模型进行编译和训练:
`# 3. compile model simple_adam = K.optimizers.Adam() enc_dec.compile(loss='mean_squared_error', optimizer=simple_adam)
print("Starting training") max_epochs = 500 my_logger = MyLogger(n=100) h = enc_dec.fit(x=data_x, y=data_x, batch_size=8, epochs=max_epochs, verbose=0, callbacks=[my_logger]) print("Training complete \n")`
Adam
(自适应矩估计)优化器对于深度神经网络来说是一个很好的通用学习器,但是Adagrad
、Adadelta
和RMSprop
是合理的选择。因为目标值是类型np.float32
,自编码器是使用均方误差而不是交叉熵误差编译的。
训练时期的数量和批量大小是自由参数。请注意,x
和y
参数都通过了fit()
方法data_x
。演示程序从fit()
方法中捕获保存训练历史的返回对象,但不使用它。如果您想查看损失值,可以这样做:
print(h.history['loss'])
verbose=0
参数抑制所有内置的日志消息,以便只显示由my_logger
回调对象生成的消息。
在大多数情况下,你会想要保存一个训练好的模型,尤其是如果训练花费了几个小时甚至更长的时间。演示程序不会保存经过训练的自编码器,但您可以这样做:
# save autoencoder model print("Saving model to disk \n") mp = ".\\Models\\autoenc_model.h5" encoder.save(mp)
Keras 使用分层数据格式(HDF)版本 5 保存训练好的模型。它是二进制格式,所以保存的模型不能用文本编辑器检查。除了保存整个模型,您还可以只保存模型权重和偏差,这有时很有用。您也可以保存没有权重的模型架构。
保存的 Keras 自编码器可以从不同的程序加载,如下所示:
print("Loading a saved model") mp = ".\\Models\\autoenc_model.h5" encoder = K.models.load_model(mp)
演示程序使用以下语句生成 1797 个 64 维数据项的二维可视化:
`# 5. generate (x,y) pairs for each digit reduced = encoder.predict(data_x)
print("Displaying 64-dim data in 2D: \n") plt.scatter(x=reduced[:, 0], y=reduced[:, 1], c=labels, edgecolors='none', alpha=0.9, cmap=plt.cm.get_cmap('nipy_spectral', 10), s=20)
plt.xlabel('component 1') plt.ylabel('component 2') plt.colorbar() plt.show()`
对predict()
方法的调用返回一个名为reduced
的 NumPy 数组样式矩阵,有 1797 行和两列,其中列[0]是 x 轴值,列[1]是 y 轴值。PyPlot scatter()
功能用于生成散点图。
scatter()
参数名称有点神秘。参数c
是使用cmap
参数映射到颜色的 n 个数字的序列。回想一下,数组label
保存每个数据项的类标签 0 到 9(类型为np.float32
)。
cmap
参数(“颜色图”)有值'nipy_spectral'
,它使用一组连续的颜色,从深紫色到绿色,再到深红色。还有很多其他 PyPlot 彩色地图,包括'rainbow'
、'jet'
、'Dark1'
和'cool'
。
s
参数控制散点图上标记点的大小(以点为单位)。alpha
参数控制标记点的透明度。
自编码器是一种特殊类型的神经网络,它学习预测自己的输入值。因为自编码器在训练过程中不使用标记数据,所以自编码器是无监督技术的一个例子。
自编码器的一个常见用途是降维,这样高维数据可以在二维或三维图形上可视化。
你可以在这里找到演示程序使用的 1797 项数据文件。
其他资源: