Skip to main content

t-SNE implementation in TensorFlow

t-Distributed Stochastic Neighbor Embedding (t-SNE) is widely used for visualizing high-dimensional data by reducing it to 2 or 3 dimensions. In this article, we will implement t-SNE using TensorFlow, a popular deep learning library.

1. Introduction

TensorFlow provides powerful tools for building and training machine learning models, but it doesn't include a direct implementation of t-SNE. However, with TensorFlow, you can manually compute the necessary steps for t-SNE, giving you more control and flexibility over the process.

In this example, we will apply t-SNE to the MNIST dataset of handwritten digits to visualize the data in 2D space.

2. Importing necessary libraries

We start by importing TensorFlow and other necessary libraries.

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.preprocessing import StandardScaler

Explanation:

  • tensorflow: TensorFlow is used for numerical computations and creating the t-SNE implementation.
  • sklearn.datasets: Provides the MNIST dataset.
  • matplotlib.pyplot: Used for plotting the results.

3. Loading and preprocessing the dataset

We'll use the MNIST dataset, which contains 8x8 pixel images of handwritten digits.

# Load the digits dataset
digits = load_digits()
X = digits.data
y = digits.target

# Standardize the data
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

Explanation:

  • load_digits: Loads the MNIST dataset.
  • StandardScaler: Standardizes the data, which is important for the performance of t-SNE.

4. Implementing t-SNE in TensorFlow

Since TensorFlow doesn’t have a built-in t-SNE function, we will manually implement the key steps.

Step 1: Compute pairwise distances

First, we compute the pairwise distances between all points in the dataset.

# Compute pairwise squared Euclidean distances
def pairwise_distances(X):
sum_X = tf.reduce_sum(tf.square(X), 1)
distances = tf.add(tf.transpose(sum_X), sum_X) - 2 * tf.matmul(X, tf.transpose(X))
return distances

pairwise_dist = pairwise_distances(X_scaled)

Explanation:

  • pairwise_distances: This function computes the squared Euclidean distances between all points in the dataset.

Step 2: Compute joint probabilities

Next, we compute the joint probabilities of the high-dimensional data points.

def joint_probabilities(distances, sigma=1.0):
P = tf.exp(-distances / (2 * tf.square(sigma)))
sum_P = tf.reduce_sum(P)
return P / sum_P

P = joint_probabilities(pairwise_dist)

Explanation:

  • joint_probabilities: Converts distances into probabilities using a Gaussian kernel.

Step 3: Initialize the low-dimensional map

We initialize a random 2D map for our low-dimensional representation.

# Initialize low-dimensional map
Y = tf.Variable(tf.random.normal([X_scaled.shape[0], 2]))

Explanation:

  • tf.random.normal: Initializes the low-dimensional representation randomly.

Step 4: Optimize the t-SNE objective function

Finally, we optimize the t-SNE objective function using gradient descent.

optimizer = tf.keras.optimizers.Adam(learning_rate=200)

def tsne_step():
with tf.GradientTape() as tape:
low_dim_distances = pairwise_distances(Y)
Q = joint_probabilities(low_dim_distances, sigma=1.0)
kl_divergence = tf.reduce_sum(P * tf.math.log(P / (Q + 1e-10)))

gradients = tape.gradient(kl_divergence, [Y])
optimizer.apply_gradients(zip(gradients, [Y]))
return kl_divergence

# Perform the optimization for a set number of iterations
for i in range(1000):
loss = tsne_step()
if i % 100 == 0:
print(f"Iteration {i}, Loss: {loss.numpy()}")

Explanation:

  • tsne_step: Implements one step of the t-SNE optimization, minimizing the Kullback-Leibler divergence between the high-dimensional and low-dimensional joint probabilities.
  • optimizer.apply_gradients: Applies the computed gradients to update the low-dimensional representation.

5. Visualizing the results

After the optimization, we can visualize the resulting 2D map.

# Plot the t-SNE result
Y_np = Y.numpy()

plt.figure(figsize=(10, 7))
scatter = plt.scatter(Y_np[:, 0], Y_np[:, 1], c=y, cmap='viridis', s=50, alpha=0.7)
plt.colorbar(scatter, label='Digit Label')
plt.title('t-SNE Visualization of MNIST Digits (TensorFlow)')
plt.xlabel('t-SNE Dimension 1')
plt.ylabel('t-SNE Dimension 2')
plt.grid(True)
plt.show()

Explanation:

  • Y_np: Converts the TensorFlow variable to a NumPy array for plotting.
  • plt.scatter: Plots the 2D t-SNE result.

6. Conclusion

In this article, we manually implemented t-SNE using TensorFlow and applied it to the MNIST dataset. While TensorFlow doesn’t provide a built-in t-SNE function, our implementation demonstrates how you can leverage TensorFlow's flexibility to build custom machine learning models. By understanding the t-SNE algorithm in depth, you can apply it to a wide range of high-dimensional datasets.