R
Keras
Author

Rui

Published

June 10, 2023

Autoencoder

最简单的自编码器(AE)形式是一个前馈的、非循环的神经网络,类似于多层感知器(MLP)中的单层感知器,用一层或多层隐藏层链接输入和输出。输出层具有与输入层相同数量的节点(神经元)。输出层节点数和输入层一致。其目的是重构输入(最小化输入和输出之间的差异),而不是在给定输入 \(X\) 的情况下预测目标值 \(Y\), 所以自编码器属于无监督学习。

\[ \phi :{\mathcal {X}}\rightarrow {\mathcal {F}} \\ \]

\[ \psi :{\mathcal {F}}\rightarrow {\mathcal {X}} \\ \]

\[ \phi, \psi ={\underset {\phi, \psi }{\operatorname {argmin} }}\|{\mathcal {X}}-(\psi \circ \phi ){\mathcal {X}}\|^{2} \]

在最简单的情况下,给定一个隐藏层,自编码器的编码阶段接受输入 \({\displaystyle \mathbf {x} \in \mathbb {R} ^{d}={\mathcal {X}}}\) 并将其映射到 \({\displaystyle \mathbf {h} \in \mathbb {R} ^{p}={\mathcal {F}}}\)\({\displaystyle \mathbf {h} =\sigma (\mathbf {Wx} +\mathbf {b})}\)\({\displaystyle \mathbf {h}}\) 通常表示编码、潜变量或潜在表示。 \(\sigma\) 是一个逐元素的激活函数(例如 sigmoid 函数或线性整流函数)。 \({\mathbf {W}}\)是权重矩阵,\(\mathbf {b}\) 是偏置向量。 权重和偏置通常随机初始化,并在训练期间通过反向传播迭代更新。 自编码器的解码阶段映射 \({\displaystyle \mathbf {h}}\) 到重构 \({\displaystyle \mathbf {x'}}\)(与 \(\mathbf {x}\) 形状一致):\({\displaystyle \mathbf {x'} =\sigma '(\mathbf {W'h} +\mathbf {b'} )}\) 其中解码器部分的 \({\displaystyle \mathbf {\sigma '} ,\mathbf {W'} ,\mathbf {b'}}\) 可能与编码器部分的 \({\displaystyle \mathbf {\sigma } ,\mathbf {W} ,\mathbf {b}}\) 无关。

自编码器被训练来最小化重建误差(如平方误差),通常被称为 “损失”:

\[ {\displaystyle {\mathcal {L}}(\mathbf {x} ,\mathbf {x'})=\|\mathbf {x} -\mathbf {x'} \|^{2}=\|\mathbf {x} -\sigma '(\mathbf {W'} (\sigma (\mathbf {Wx} +\mathbf {b} ))+\mathbf {b'} )\|^{2}} \]

当特征空间 \(\mathcal {F}\) 的维度比输入空间 \(\mathcal {X}\) 低时,特征向量 \(\phi (x)\) 可以看作时输入 \(\mathbf {x}\) 的压缩表示,这就是不完备自动编码(undercomplete autoencoders)的情况。 如果隐藏层大于(过完备)或等于输入层的数量,或者隐藏单元的容量足够大,自编码器就可能学会恒等函数而变得无用。

本例中自编码器将使用 keras 包构建。与任何神经网络一样,自编码器的构建具有很大的灵活性,例如隐藏层的数量和每个隐藏层中节点的数量。每个隐藏层都会尝试在数据中找到新的结构。大部分情况下,自编码器是对称的,中间层是瓶颈层(bottleneck)。自编码器的前半部分被称为编码器(encoder),后半部分被称为解码器(decoder)。

数据预处理

建立神经网络模型之前对数据进行标准化往往可以加速模型收敛。

Code
library(tidyverse)
x_train <- iris[ , -5] %>% 
  scale(center = TRUE, scale = TRUE) %>% # 标准化
  as.data.frame()
Code
x_train %>% head() %>% knitr::kable()
Sepal.Length Sepal.Width Petal.Length Petal.Width
-0.8976739 1.0156020 -1.335752 -1.311052
-1.1392005 -0.1315388 -1.335752 -1.311052
-1.3807271 0.3273175 -1.392399 -1.311052
-1.5014904 0.0978893 -1.279104 -1.311052
-1.0184372 1.2450302 -1.335752 -1.311052
-0.5353840 1.9333146 -1.165809 -1.048667

建立自动编码器

建立模型

Code
# autoencoder in keras
library(keras)
# set training data
x_train <- as.matrix(x_train)
# set model
model <- keras_model_sequential()
model %>%
  layer_dense(units = 4, activation = "tanh", input_shape = ncol(x_train)) %>%
  layer_dense(units = 2, activation = "tanh", name = "bottleneck") %>%
  layer_dense(units = 4, activation = "tanh") %>%
  layer_dense(units = ncol(x_train))
# view model layers
summary(model)
## Model: "sequential"
## ________________________________________________________________________________
##  Layer (type)                       Output Shape                    Param #     
## ================================================================================
##  dense_2 (Dense)                    (None, 4)                       20          
##  bottleneck (Dense)                 (None, 2)                       10          
##  dense_1 (Dense)                    (None, 4)                       12          
##  dense (Dense)                      (None, 4)                       20          
## ================================================================================
## Total params: 62
## Trainable params: 62
## Non-trainable params: 0
## ________________________________________________________________________________

编译模型

Code
# compile model
model %>% compile(
  loss = "mean_squared_error", 
  optimizer = "adam"
)
# fit model
model %>% fit(
  x = x_train, 
  y = x_train, 
  epochs = 2000,
  verbose = 0
)
# evaluate the model
evaluate(model, x_train, x_train)
##       loss 
## 0.04230129

提取出中间层

Code
intermediate_layer_model <- keras_model(inputs = model$input, outputs = get_layer(model, "bottleneck")$output)
intermediate_output <- predict(intermediate_layer_model, x_train)

可视化

Code
ggplot(data.frame(node1 = intermediate_output[, 1], node2 = intermediate_output[, 2]), aes(x = node1, y = node2, col = iris$Species)) + 
  geom_point() +
  guides(color=guide_legend(title = "Species")) +
  theme_bw()

可以看出,在降维的同时,AE 并没有丢失 iris 数据集中的类别特征。