t-SNE

Table of Contents

1. t-SNE

1.1. SNE 与 t-SNE

t-SNE1 是一种非监督的数据降维算法, 其基本思想是, 把高维的 X 映射到低维的 Y, 期望任意两点, 在高维空间中的`距离`与低维空间中`距离`相近. 这里的`距离`并非欧氏距离, 而是基于欧氏距离的某种概率表示. t-SNE 使用用梯度下降的方法, 其损失函数为高维`距离`的分布与低维`距离`分布的 KL 散度.

t-SNE 以 SNE 为基础, SNE 算法的步骤:

  1. 假设样本数为 m, 计算一个 \(R^{m\times m}\) 的矩阵 P, 其中 \(P_{ji} = \frac{\exp(-\frac{||X_i-X_j||^2}{2\delta^2})}{\sum{\exp{-\frac{||X_i-X_k||^2}{2\delta^2}}}}\) \(P_{ji}\) 的样子与 softmax 很像, 与高斯分布的概率密度函数也有类似. 可以把 \(P\) 理解为 第 j 个点是第 i 个点的邻近点的概率, 服从高斯分布, 直观上看, \(x_i\) 与 \(x_j\) 的欧氏距离越小, 其概率越大. 所以 P 可以看作是另一种形式的`距离`
  2. 定义一个 embedding, 即一张查找表, 这个表的大小为 \(R^{m\times n}\), 每个点 \(X_i\) 被映射为一个大小为 n 的向量 \(Y_i\), 表示 X 降维到 Y
  3. 计算另一个 \(R^{m\times m}\) 的矩阵 Q, 其中 \(Q_{ji} = \frac{\exp(-\frac{||Y_i-Y_j||^2}{2\delta^2})}{\sum{\exp{-\frac{||Y_i-Y_k||^2}{2\delta^2}}}}\)
  4. 计算 KL(P,Q) 做为损失函数. KL 即 KL divergence, 对于两个概率分布 p, q, \(KL(p,q)=\sum{p_i*\big(log(p_i)-log(q_i)\big)}\), KL 与 cross entropy 类似, 可以评价两个概率分布的相似性
  5. 对 KL(P,Q) 进行梯度下降, 需要被优化的参数是 embedding, 即 Y

t-SNE 是把 SNE 中 Q 的计算变为 \(Q_{ij}=\frac{(1+||Y_i-Y_j||^2)^{-1}}{\sum{(1+||Y_k-Y_i||^2)^{-1}}}\), 即由高斯分布变为 t 分布

1.2. 实现

# https://github.com/cemoody/topicsne
# 1. 计算 p
distances = pairwise_distances(pos, metric=metric, squared=True)
pij = manifold.t_sne._joint_probabilities(distances, perplexity, False)


# 2. 计算 q 及 KL divergence
class TSNE(nn.Module):
    def __init__(self, n_points, n_topics, n_dim):
        self.n_points = n_points
        self.n_dim = n_dim
        super(TSNE, self).__init__()
        self.logits = nn.Embedding(n_points, n_topics)

    def forward(self, pij, i, j):
        x = self.logits.weight
        dkl2 = pairwise(x)
        n_diagonal = dkl2.size()[0]
        part = (1 + dkl2).pow(-1.0).sum() - n_diagonal
        xi = self.logits(i)
        xj = self.logits(j)
        num = ((1. + (xi - xj)**2.0).sum(1)).pow(-1.0).squeeze()
        qij = num / part.expand_as(num)
        # Compute KL divergence
        loss_kld = pij * (torch.log(pij) - torch.log(qij))
        return loss_kld.sum()

1.3. t-SNE 用途

t-SNE 的主要用途是高维数据的可视化, 因为它可以把高维数据映射到低维(例如 2 维), 而且能保持原来的 clustering 关系.

通常 t-SNE 也被认为是一种非线性的降维算法, 与 PCA, autoencoder 类似. 但 t-SNE 一般不能像 PCA, autoencoder 那样做为降维算法对监督学习的数据进行预处理2. 因为 t-SNE 学习到的是一个针对每个点的映射表(大小为 m*m*n), 并不知道如何把 X 的 k 的 feature 通过运算映射为 Y 的 n 的 feature (k*n). 对于没有见过的数据, t-SNE 无法处理.3

以 sklearn 的 TSNE 为例, 它只提供了 fit_transform 的 api, 并没有提供单独的 transform api, 原因同上 4

1.4. PCA, t-SNE and auto-encoder

from matplotlib import pyplot as plt
from sklearn import datasets
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE

plt.style.use("classic")

iris = datasets.load_iris()

X = iris.data
y = iris.target

target_ids = range(len(iris.target_names))

# PCA
pca = PCA(n_components=2, random_state=0)
X_2d = pca.fit_transform(X)

plt.subplot(131)
plt.title("pca")
colors = 'r', 'g', 'b', 'c', 'm', 'y', 'k', 'w', 'orange', 'purple'
for i, c, label in zip(target_ids, colors, iris.target_names):
    plt.scatter(X_2d[y == i, 0], X_2d[y == i, 1], c=c, label=label)

# TSNE
tsne = TSNE(n_components=2, random_state=0)
X_2d = tsne.fit_transform(X)
colors = 'r', 'g', 'b', 'c', 'm', 'y', 'k', 'w', 'orange', 'purple'
plt.subplot(132)
plt.title("tsne")
for i, c, label in zip(target_ids, colors, iris.target_names):
    plt.scatter(X_2d[y == i, 0], X_2d[y == i, 1], c=c, label=label)

# autoencoder
import torch
from torch import nn
from torch import optim
from torch.utils.data import DataLoader, Dataset


# ---------- data ----------
class PlainDataset(Dataset):
    def __init__(self):
        self.X = torch.from_numpy(X).float()
        self.Y = self.X

    def __getitem__(self, index):
        return self.X[index], self.Y[index]

    def __len__(self):
        return len(self.X)


training_set = PlainDataset()
training_loader = DataLoader(training_set, batch_size=30, shuffle=True)


def train():
    for i in range(5000):
        for x, y in training_loader:
            loss = criterion(model(x), y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
        # if i % 20 == 0:
        #     print("epoch #%d: loss: %f" % (i, loss.item()))


# ---------- model ----------

model = nn.Sequential(nn.Linear(4, 2), nn.ReLU(), nn.Linear(2, 4))
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=0.001)

train()
X_2d = model[0](torch.from_numpy(X).float()).detach().numpy()
colors = 'r', 'g', 'b', 'c', 'm', 'y', 'k', 'w', 'orange', 'purple'
plt.subplot(133)
plt.title("auto-encoder")
for i, c, label in zip(target_ids, colors, iris.target_names):
    plt.scatter(X_2d[y == i, 0], X_2d[y == i, 1], c=c, label=label)

plt.show()

iris.png

Footnotes:

Author: [email protected]
Date: 2018-08-30 Thu 00:00
Last updated: 2021-08-11 Wed 21:04

知识共享许可协议