מודלים משתנים סמוי של תהליך גאוס

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

מודלים משתנים סמויים מנסים ללכוד מבנה נסתר בנתונים בעלי ממדים גבוהים. דוגמאות כוללות ניתוח רכיבים עקרוניים (PCA) וניתוח גורמים. תהליכי גאוס הם מודלים "לא פרמטריים" שיכולים ללכוד בצורה גמישה מבנה מתאם מקומי ואי ודאות. המודל המשתנה הסמוי גאוס התהליך ( לורנס, 2004 ) משלבת מושגים אלה.

רקע: תהליכי גאוס

תהליך גאוס הוא כל אוסף של משתנים אקראיים כך שהתפלגות השולית על כל תת-קבוצה סופית היא התפלגות נורמלית רב-משתנית. לבחינה מדוקדקת על רופאים בהקשר של רגרסיה, לבדוק רגרסיה תהליך גאוס ב הסתברות TensorFlow .

אנו משתמשים במספר מדד שנקרא לתייג כול אחד מהמשתנים האקראיים באוסף כי כולל GP. במקרה של קבוצת אינדקס סופי, אנחנו פשוט מקבלים נורמה רב משתנים. של GP הם המעניינים ביותר, אם כי, כאשר אנו רואים אוספים אינסופיים. במקרה של קבוצות מדד כמו \(\mathbb{R}^D\), שבו יש לנו משתנה אקראי לכל טעם \(D\)מרחב מימדי, GP יכול להיחשב חלוקה מעל פונקציות אקראיות. תיקו בודד GP כזה, אם זה יכול להתממש, היה להקצות ערך (במשותף בדרך-מופץ) לכל נקודה \(\mathbb{R}^D\). בשנת colab זה, נתמקד GP של מעל כמה\(\mathbb{R}^D\).

התפלגויות נורמליות נקבעות לחלוטין על ידי הסטטיסטיקה של הסדר הראשון והשני שלהן -- ואכן, דרך אחת להגדיר את ההתפלגות הנורמלית היא כזו שכל המצטברים מהסדר הגבוה שלה הם אפס. זהו המקרה עבור GP של, מדי: אנחנו לגמרי לציין GP בתיאור * שונה משותף והמרושע. נזכיר שעבור נורמלי רב-משתנים סופי-ממדיים, הממוצע הוא וקטור והשונות היא מטריצה ​​מרובעת וסימטרית חיובית-מוגדרת. בשנות ה GP-ממדים האינסופיים, מבנים אלה להכליל פונקציה ממוצעת \(m : \mathbb{R}^D \to \mathbb{R}\), מוגדרת בכול נקודה של סט המדד, וכן שונות המשותפת "הקרנל" פונקציה,\(k : \mathbb{R}^D \times \mathbb{R}^D \to \mathbb{R}\). פונקציית הליבה נדרשת להיות חיובי-מובהק , אשר בעצם אומר כי, מוגבלי קבוצה סופית של נקודות, היא הניבה מטריקס postiive-המובהק.

רוב המבנה של GP נובע מפונקציית ליבת השונות שלו - פונקציה זו מתארת ​​כיצד הערכים של פונקציות שנדגמו משתנים בין נקודות קרובות (או לא כל כך קרובות). פונקציות שונות של שיתופיות מעודדות דרגות שונות של חלקות. נפוץ פונקציה הקרנל אחת מהן היא "exponentiated ריבועית" (aka, "גאוס", "בריבוע מעריכים" או "תפקוד בסיס רדיאלי"), \(k(x, x') = \sigma^2 e^{(x - x^2) / \lambda^2}\). דוגמאות נוספות מתוארים באתר של דוד Duvenaud דף ספר הבישול הקרנל , כמו גם הקנונית הטקסט תהליכים גאוס עבור מכונת למידה .

* עם סט אינדקס אינסופי, אנו דורשים גם תנאי עקביות. מכיוון שההגדרה של GP היא במונחים של שוליים סופיים, עלינו לדרוש ששוליים אלה יהיו עקביים ללא קשר לסדר שבו נלקחים השוליים. זהו נושא מתקדם במקצת בתורת התהליכים הסטוכסטיים, מחוץ לתחום של הדרכה זו; די לומר שהדברים מסתדרים בסופו של דבר!

החלת רופאי משפחה: מודלים משתנים רגרסיים וסמויים

אחת דרכים שאנחנו יכולים להשתמש ב- GPS היא לרגרסיה: נתון חבורה של נתונים נצפים בצורה של תשומות \(\{x_i\}_{i=1}^N\) (אלמנטים של סט המדד) ותצפיות\(\{y_i\}_{i=1}^N\), אנו יכולים להשתמש אלה כדי ליצור חלוק חזוי אחורית בבית חדש סט של נקודות \(\{x_j^*\}_{j=1}^M\). מאז הפצות הם כל גאוס, זה מסתכם בכמה אלגברה ליניארית פשוטה (אבל הערה: החישובים הנדרשים יש ריצה מעוקבים במספר נקודות נתונים ודורשים ריבועית החלל במספר נקודות נתונים - זהו הגורם המגביל העיקרי ב השימוש ברופאי משפחה ומחקרים עדכניים רבים מתמקדים בחלופות ברות-קיימא מבחינה חישובית להסקת מסקנות אחורית מדויקת). אנחנו מכסים רגרסיה GP ביתר פירוט את רגרסיה GP ב colab הפריון הכולל .

דרך נוספת שבה אנו יכולים להשתמש ברופאי משפחה היא כמודל משתנה סמוי: בהינתן אוסף של תצפיות בממדים גבוהים (למשל, תמונות), אנו יכולים להעמיד מבנה סמוי בממד נמוך כלשהו. אנו מניחים כי מותנה במבנה הסמוי, המספר הגדול של הפלטים (פיקסלים בתמונה) אינם תלויים זה בזה. הכשרה במודל זה מורכבת מ

  1. אופטימיזציה של פרמטרי מודל (פרמטרים של פונקציית ליבה וכן, למשל, שונות רעש תצפית), ו
  2. מציאת, עבור כל תצפית אימון (תמונה), מיקום נקודתי תואם בערכת האינדקס. כל האופטימיזציה יכולה להיעשות על ידי מיקסום הסבירות היומן השולי של הנתונים.

יבוא

import numpy as np
import tensorflow.compat.v2 as tf
tf.enable_v2_behavior()
import tensorflow_probability as tfp
tfd = tfp.distributions
tfk = tfp.math.psd_kernels
%pylab inline
Populating the interactive namespace from numpy and matplotlib

טען נתוני MNIST

# Load the MNIST data set and isolate a subset of it.
(x_train, y_train), (_, _) = tf.keras.datasets.mnist.load_data()
N = 1000
small_x_train = x_train[:N, ...].astype(np.float64) / 256.
small_y_train = y_train[:N]
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
11501568/11490434 [==============================] - 0s 0us/step

הכן משתנים הניתנים לאימון

אנו נאמן במשותף 3 פרמטרים של מודל, כמו גם את התשומות הסמויות.

# Create some trainable model parameters. We will constrain them to be strictly
# positive when constructing the kernel and the GP.
unconstrained_amplitude = tf.Variable(np.float64(1.), name='amplitude')
unconstrained_length_scale = tf.Variable(np.float64(1.), name='length_scale')
unconstrained_observation_noise = tf.Variable(np.float64(1.), name='observation_noise')
# We need to flatten the images and, somewhat unintuitively, transpose from
# shape [100, 784] to [784, 100]. This is because the 784 pixels will be
# treated as *independent* conditioned on the latent inputs, meaning we really
# have a batch of 784 GP's with 100 index_points.
observations_ = small_x_train.reshape(N, -1).transpose()

# Create a collection of N 2-dimensional index points that will represent our
# latent embeddings of the data. (Lawrence, 2004) prescribes initializing these
# with PCA, but a random initialization actually gives not-too-bad results, so
# we use this for simplicity. For a fun exercise, try doing the
# PCA-initialization yourself!
init_ = np.random.normal(size=(N, 2))
latent_index_points = tf.Variable(init_, name='latent_index_points')

בניית מודלים והדרכה

# Create our kernel and GP distribution
EPS = np.finfo(np.float64).eps

def create_kernel():
  amplitude = tf.math.softplus(EPS + unconstrained_amplitude)
  length_scale = tf.math.softplus(EPS + unconstrained_length_scale)
  kernel = tfk.ExponentiatedQuadratic(amplitude, length_scale)
  return kernel

def loss_fn():
  observation_noise_variance = tf.math.softplus(
      EPS + unconstrained_observation_noise)
  gp = tfd.GaussianProcess(
      kernel=create_kernel(),
      index_points=latent_index_points,
      observation_noise_variance=observation_noise_variance)
  log_probs = gp.log_prob(observations_, name='log_prob')
  return -tf.reduce_mean(log_probs)

trainable_variables = [unconstrained_amplitude,
                       unconstrained_length_scale,
                       unconstrained_observation_noise,
                       latent_index_points]

optimizer = tf.optimizers.Adam(learning_rate=1.0)

@tf.function(autograph=False, jit_compile=True)
def train_model():
  with tf.GradientTape() as tape:
    loss_value = loss_fn()
  grads = tape.gradient(loss_value, trainable_variables)
  optimizer.apply_gradients(zip(grads, trainable_variables))
  return loss_value

אימון ותכנן את ההטבעות הסמויות שנוצרו

# Initialize variables and train!
num_iters = 100
log_interval = 20
lips = np.zeros((num_iters, N, 2), np.float64)
for i in range(num_iters):
  loss = train_model()
  lips[i] = latent_index_points.numpy()
  if i % log_interval == 0 or i + 1 == num_iters:
    print("Loss at step %d: %f" % (i, loss))
Loss at step 0: 1108.121688
Loss at step 20: -159.633761
Loss at step 40: -263.014394
Loss at step 60: -283.713056
Loss at step 80: -288.709413
Loss at step 99: -289.662253

תוצאות עלילה

# Plot the latent locations before and after training
plt.figure(figsize=(7, 7))
plt.title("Before training")
plt.grid(False)
plt.scatter(x=init_[:, 0], y=init_[:, 1],
           c=y_train[:N], cmap=plt.get_cmap('Paired'), s=50)
plt.show()

plt.figure(figsize=(7, 7))
plt.title("After training")
plt.grid(False)
plt.scatter(x=lips[-1, :, 0], y=lips[-1, :, 1],
           c=y_train[:N], cmap=plt.get_cmap('Paired'), s=50)
plt.show()

png

png

בניית מודל חזוי ופעולות דגימה

# We'll draw samples at evenly spaced points on a 10x10 grid in the latent
# input space. 
sample_grid_points = 10
grid_ = np.linspace(-4, 4, sample_grid_points).astype(np.float64)
# Create a 10x10 grid of 2-vectors, for a total shape [10, 10, 2]
grid_ = np.stack(np.meshgrid(grid_, grid_), axis=-1)

# This part's a bit subtle! What we defined above was a batch of 784 (=28x28)
# independent GP distributions over the input space. Each one corresponds to a
# single pixel of an MNIST image. Now what we'd like to do is draw 100 (=10x10)
# *independent* samples, each one separately conditioned on all the observations
# as well as the learned latent input locations above.
#
# The GP regression model below will define a batch of 784 independent
# posteriors. We'd like to get 100 independent samples each at a different
# latent index point. We could loop over the points in the grid, but that might
# be a bit slow. Instead, we can vectorize the computation by tacking on *even
# more* batch dimensions to our GaussianProcessRegressionModel distribution.
# In the below grid_ shape, we have concatentaed
#   1. batch shape: [sample_grid_points, sample_grid_points, 1]
#   2. number of examples: [1]
#   3. number of latent input dimensions: [2]
# The `1` in the batch shape will broadcast with 784. The final result will be
# samples of shape [10, 10, 784, 1]. The `1` comes from the "number of examples"
# and we can just `np.squeeze` it off.
grid_ = grid_.reshape(sample_grid_points, sample_grid_points, 1, 1, 2)

# Create the GPRegressionModel instance which represents the posterior
# predictive at the grid of new points.
gprm = tfd.GaussianProcessRegressionModel(
    kernel=create_kernel(),
    # Shape [10, 10, 1, 1, 2]
    index_points=grid_,
    # Shape [1000, 2]. 1000 2 dimensional vectors.
    observation_index_points=latent_index_points,
    # Shape [784, 1000]. A batch of 784 1000-dimensional observations.
    observations=observations_)

צייר דוגמאות המותנות בנתונים ובהטבעות סמויות

אנו דוגמים ב-100 נקודות ברשת דו-ממדית במרחב הסמוי.

samples = gprm.sample()

# Plot the grid of samples at new points. We do a bit of tweaking of the samples
# first, squeezing off extra 1-shapes and normalizing the values.
samples_ = np.squeeze(samples.numpy())
samples_ = ((samples_ -
             samples_.min(-1, keepdims=True)) /
            (samples_.max(-1, keepdims=True) -
             samples_.min(-1, keepdims=True)))
samples_ = samples_.reshape(sample_grid_points, sample_grid_points, 28, 28)
samples_ = samples_.transpose([0, 2, 1, 3])
samples_ = samples_.reshape(28 * sample_grid_points, 28 * sample_grid_points)
plt.figure(figsize=(7, 7))
ax = plt.subplot()
ax.grid(False)
ax.imshow(-samples_, interpolation='none', cmap='Greys')
plt.show()

png

סיכום

ערכנו סיור קצר במודל המשתנה הסמוי של תהליך גאוס, והראינו כיצד אנו יכולים ליישם אותו בכמה שורות בלבד של קוד הסתברות TF ו-TF.