在 TensorFlow.org 上查看 | 在 Google Colab 中运行 | 在 GitHub 上查看源代码 | 下载笔记本 |
概率主成分分析 (PCA) 是一种降维技术,它通过低维潜在空间分析数据 (Tipping 和 Bishop 1999)。当数据中存在缺失值或用于多维尺度分析时,它通常被使用。
导入
import functools
import warnings
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import tensorflow as tf
import tf_keras
import tensorflow_probability as tfp
from tensorflow_probability import bijectors as tfb
from tensorflow_probability import distributions as tfd
plt.style.use("ggplot")
warnings.filterwarnings('ignore')
模型
考虑一个包含 \(N\) 个数据点的 \(D\) 维数据集 \(\mathbf{X} = \{\mathbf{x}_n\}\),其中每个数据点都是 \(D\) 维的,$\mathbf{x}_n \in \mathbb{R}^D\(. 我们的目标是使用低维潜在变量 \(\mathbf{z}_n \in \mathbb{R}^K\) 表示每个 \)\mathbf{x}_n\),其中 $K < D\(. 主轴集 \)\mathbf{W}$ 将潜在变量与数据相关联。
具体来说,我们假设每个潜在变量都服从正态分布,
\[ \begin{equation*} \mathbf{z}_n \sim N(\mathbf{0}, \mathbf{I}). \end{equation*} \]
相应的数据点通过投影生成,
\[ \begin{equation*} \mathbf{x}_n \mid \mathbf{z}_n \sim N(\mathbf{W}\mathbf{z}_n, \sigma^2\mathbf{I}), \end{equation*} \]
其中矩阵 \(\mathbf{W}\in\mathbb{R}^{D\times K}\) 被称为主轴。在概率 PCA 中,我们通常对估计主轴 \(\mathbf{W}\) 和噪声项 \(\sigma^2\) 感兴趣。
概率 PCA 概括了经典 PCA。将潜在变量边缘化后,每个数据点的分布为
\[ \begin{equation*} \mathbf{x}_n \sim N(\mathbf{0}, \mathbf{W}\mathbf{W}^\top + \sigma^2\mathbf{I}). \end{equation*} \]
当噪声的协方差变得无限小,\(\sigma^2 \to 0\) 时,经典 PCA 是概率 PCA 的特例。
我们在下面设置我们的模型。在我们的分析中,我们假设 \(\sigma\) 是已知的,并且我们不是将 \(\mathbf{W}\) 作为模型参数进行点估计,而是对其进行先验分布,以便推断主轴的分布。我们将模型表示为 TFP JointDistribution,具体来说,我们将使用 JointDistributionCoroutineAutoBatched.
def probabilistic_pca(data_dim, latent_dim, num_datapoints, stddv_datapoints):
w = yield tfd.Normal(loc=tf.zeros([data_dim, latent_dim]),
scale=2.0 * tf.ones([data_dim, latent_dim]),
name="w")
z = yield tfd.Normal(loc=tf.zeros([latent_dim, num_datapoints]),
scale=tf.ones([latent_dim, num_datapoints]),
name="z")
x = yield tfd.Normal(loc=tf.matmul(w, z),
scale=stddv_datapoints,
name="x")
num_datapoints = 5000
data_dim = 2
latent_dim = 1
stddv_datapoints = 0.5
concrete_ppca_model = functools.partial(probabilistic_pca,
data_dim=data_dim,
latent_dim=latent_dim,
num_datapoints=num_datapoints,
stddv_datapoints=stddv_datapoints)
model = tfd.JointDistributionCoroutineAutoBatched(concrete_ppca_model)
数据
我们可以通过从联合先验分布中采样来使用模型生成数据。
actual_w, actual_z, x_train = model.sample()
print("Principal axes:")
print(actual_w)
Principal axes: tf.Tensor( [[ 2.2801023] [-1.1619819]], shape=(2, 1), dtype=float32)
我们可视化数据集。
plt.scatter(x_train[0, :], x_train[1, :], color='blue', alpha=0.1)
plt.axis([-20, 20, -20, 20])
plt.title("Data set")
plt.show()
最大后验概率推断
我们首先搜索使后验概率密度最大化的潜在变量的点估计。这被称为最大后验概率 (MAP) 推断,它是通过计算使后验密度 \(p(\mathbf{W}, \mathbf{Z} \mid \mathbf{X}) \propto p(\mathbf{W}, \mathbf{Z}, \mathbf{X})\) 最大化的 \(\mathbf{W}\) 和 \(\mathbf{Z}\) 的值来完成的。
w = tf.Variable(tf.random.normal([data_dim, latent_dim]))
z = tf.Variable(tf.random.normal([latent_dim, num_datapoints]))
target_log_prob_fn = lambda w, z: model.log_prob((w, z, x_train))
losses = tfp.math.minimize(
lambda: -target_log_prob_fn(w, z),
optimizer=tf_keras.optimizers.Adam(learning_rate=0.05),
num_steps=200)
plt.plot(losses)
[<matplotlib.lines.Line2D at 0x7f19897a42e8>]
我们可以使用模型为 \(\mathbf{W}\) 和 \(\mathbf{Z}\) 的推断值采样数据,并将其与我们所依赖的实际数据集进行比较。
print("MAP-estimated axes:")
print(w)
_, _, x_generated = model.sample(value=(w, z, None))
plt.scatter(x_train[0, :], x_train[1, :], color='blue', alpha=0.1, label='Actual data')
plt.scatter(x_generated[0, :], x_generated[1, :], color='red', alpha=0.1, label='Simulated data (MAP)')
plt.legend()
plt.axis([-20, 20, -20, 20])
plt.show()
MAP-estimated axes: <tf.Variable 'Variable:0' shape=(2, 1) dtype=float32, numpy= array([[ 2.9135954], [-1.4826864]], dtype=float32)>
变分推断
MAP 可用于找到后验分布的众数(或其中一个众数),但不会提供有关后验分布的其他见解。接下来,我们使用变分推断,其中后验分布 \(p(\mathbf{W}, \mathbf{Z} \mid \mathbf{X})\) 使用由 \(\boldsymbol{\lambda}\) 参数化的变分分布 \(q(\mathbf{W}, \mathbf{Z})\) 进行近似。目标是找到使 q 与后验之间的 KL 散度最小化的变分参数 \(\boldsymbol{\lambda}\),\(\mathrm{KL}(q(\mathbf{W}, \mathbf{Z}) \mid\mid p(\mathbf{W}, \mathbf{Z} \mid \mathbf{X}))\),或者等效地,使证据下界最大化,\(\mathbb{E}_{q(\mathbf{W},\mathbf{Z};\boldsymbol{\lambda})}\left[ \log p(\mathbf{W},\mathbf{Z},\mathbf{X}) - \log q(\mathbf{W},\mathbf{Z}; \boldsymbol{\lambda}) \right]\).
qw_mean = tf.Variable(tf.random.normal([data_dim, latent_dim]))
qz_mean = tf.Variable(tf.random.normal([latent_dim, num_datapoints]))
qw_stddv = tfp.util.TransformedVariable(1e-4 * tf.ones([data_dim, latent_dim]),
bijector=tfb.Softplus())
qz_stddv = tfp.util.TransformedVariable(
1e-4 * tf.ones([latent_dim, num_datapoints]),
bijector=tfb.Softplus())
def factored_normal_variational_model():
qw = yield tfd.Normal(loc=qw_mean, scale=qw_stddv, name="qw")
qz = yield tfd.Normal(loc=qz_mean, scale=qz_stddv, name="qz")
surrogate_posterior = tfd.JointDistributionCoroutineAutoBatched(
factored_normal_variational_model)
losses = tfp.vi.fit_surrogate_posterior(
target_log_prob_fn,
surrogate_posterior=surrogate_posterior,
optimizer=tf_keras.optimizers.Adam(learning_rate=0.05),
num_steps=200)
print("Inferred axes:")
print(qw_mean)
print("Standard Deviation:")
print(qw_stddv)
plt.plot(losses)
plt.show()
Inferred axes: <tf.Variable 'Variable:0' shape=(2, 1) dtype=float32, numpy= array([[ 2.4168603], [-1.2236133]], dtype=float32)> Standard Deviation: <TransformedVariable: dtype=float32, shape=[2, 1], fn="softplus", numpy= array([[0.0042499 ], [0.00598824]], dtype=float32)>
posterior_samples = surrogate_posterior.sample(50)
_, _, x_generated = model.sample(value=(posterior_samples))
# It's a pain to plot all 5000 points for each of our 50 posterior samples, so
# let's subsample to get the gist of the distribution.
x_generated = tf.reshape(tf.transpose(x_generated, [1, 0, 2]), (2, -1))[:, ::47]
plt.scatter(x_train[0, :], x_train[1, :], color='blue', alpha=0.1, label='Actual data')
plt.scatter(x_generated[0, :], x_generated[1, :], color='red', alpha=0.1, label='Simulated data (VI)')
plt.legend()
plt.axis([-20, 20, -20, 20])
plt.show()
致谢
本教程最初是在 Edward 1.0 中编写的 (源代码)。我们感谢所有为编写和修订该版本做出贡献的人。
参考文献
[1]: Michael E. Tipping 和 Christopher M. Bishop。概率主成分分析。皇家统计学会杂志:B 系列(统计方法),61(3): 611-622, 1999。