t-SNE
Table of Contents
1. t-SNE
1.1. SNE 与 t-SNE
t-SNE1 是一种非监督的数据降维算法, 其基本思想是, 把高维的 X 映射到低维的 Y, 期望任意两点, 在高维空间中的`距离`与低维空间中`距离`相近. 这里的`距离`并非欧氏距离, 而是基于欧氏距离的某种概率表示. t-SNE 使用用梯度下降的方法, 其损失函数为高维`距离`的分布与低维`距离`分布的 KL 散度.
t-SNE 以 SNE 为基础, SNE 算法的步骤:
- 假设样本数为 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 可以看作是另一种形式的`距离` - 定义一个 embedding, 即一张查找表, 这个表的大小为 \(R^{m\times n}\), 每个点 \(X_i\) 被映射为一个大小为 n 的向量 \(Y_i\), 表示 X 降维到 Y
- 计算另一个 \(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}}}}\)
- 计算 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 类似, 可以评价两个概率分布的相似性
- 对 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()