Skip to content

Files

Latest commit

8e4ed31 · Jan 10, 2022

History

History
365 lines (228 loc) · 14.7 KB

7.md

File metadata and controls

365 lines (228 loc) · 14.7 KB

七、自编码器

自编码器是一种神经网络,可以执行可视化降维。例如,假设您有代表男女年龄和身高的数据。如果你想绘制你的数据,你可以通过在 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

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'

在非演示场景中,您可能希望在注释中包含更多细节。因为 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():

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 . . .`

在大多数情况下,你希望你的结果是可复制的。Keras 库广泛使用了 NumPy 全局随机数生成器,因此设置种子值是一个很好的做法。程序中使用的种子值1是任意的。同样,因为 Keras 使用 TensorFlow,所以您也会经常想要设置它的种子。不幸的是,由于并行任务的数值舍入顺序,程序结果通常不完全可再现。

我更喜欢用两个空格缩进,而不是通常的四个空格。所有正常的错误检查都被删除了,以保持主要思想尽可能清晰。

程序假设训练和测试数据文件位于名为Data的子目录中。程序本身没有任何关于数据文件结构的信息。我强烈建议您在计划中加入以下评论:

`# data has 1797 items, is comma-delimited and looks like:

0,0,12,10, . . 13,11,5

0,0,0,2,8, . . 12,10,8

first 64 values are grayscale pixel values between 0 and 16

last value is the class label, '0' through '9'`

当你写程序的时候,这种信息很容易记住,但是几个星期后就很难记住了。

使用np.loadtxt()功能将单个数据文件读入存储器。将数据读入内存的方法有很多,但loadtxt()功能的通用性足以满足大多数问题场景。NumPy genfromtxt()函数非常相似,但是它给了你一些额外的选项,比如处理丢失的数据。loadtxt()功能参数很多,但大多数情况下,只需要usecolsdelimiterdtype即可。

注意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)

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")`

Adam(自适应矩估计)优化器对于深度神经网络来说是一个很好的通用学习器,但是AdagradAdadeltaRMSprop是合理的选择。因为目标值是类型np.float32,自编码器是使用均方误差而不是交叉熵误差编译的。

训练时期的数量和批量大小是自由参数。请注意,xy参数都通过了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)

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()`

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 项数据文件。

其他资源: