Bootstrap

【自学笔记】t-分布邻域嵌入(t-SNE)

介绍

  t-分布邻域嵌入(t-SNE, t-Distributed Stochastic Neighbor Embedding)是一种常用于降维和可视化高维数据的非线性算法。它尤其擅长将高维数据映射到2D或3D空间,以便进行可视化,同时保持数据点之间的相对结构。

t-SNE 是一种基于概率的降维方法,它的目标是保持高维空间中相似数据点的相对距离关系,同时尽可能减少低维空间中相似点的距离差异。

相似度的数学表示

高维空间相似度

  t-SNE 通过将每对数据点的相似度表示为条件概率来计算相似度。在高维空间中,对于数据点 x i x_{i} xi x j x_{j} xj,我们计算条件概率 p i j p_{ij} pij,表示在给定点 x i x_{i} xi的情况下,点 x j x_{j} xj为其邻居的概率。

 这个概率的计算基于高斯分布:

p i j = exp ⁡ ( − ∥ x i − x j ∥ 2 2 σ i 2 ) ∑ k ≠ i exp ⁡ ( − ∥ x i − x k ∥ 2 2 σ i 2 ) p_{ij} = \frac{\exp\left(-\frac{\|x_i - x_j\|^2}{2\sigma_i^2}\right)}{\sum_{k \neq i} \exp\left(-\frac{\|x_i - x_k\|^2}{2\sigma_i^2}\right)} pij=k=iexp(2σi2xixk2)exp(2σi2xixj2)
  其中, ∣ ∣ x i − x j ∣ ∣ ||x_{i}-x_{j}|| ∣∣xixj∣∣为点 x i x_{i} xi与点 x j x_{j} xj间的距离(默认为欧几里得距离),而 σ \sigma σ是为每个点定义的尺度参数,用来控制高斯分布的宽度。

低维空间相似度

  在低维空间中,t-SNE 使用 t-分布(通常是自由度为1的学生t分布,也称为 Cauchy 分布)来度量数据点之间的相似性。t-分布的优势在于,它比高斯分布更有助于将远离的点拉开,使得低维空间中的点更容易分离。
  对于低维空间点 y i y_{i} yi y j y_{j} yj,其相似度计算为:

q i j = ( 1 + ∥ y i − y j ∥ 2 ) − 1 ∑ k ≠ l ( 1 + ∥ y k − y l ∥ 2 ) − 1 q_{ij} = \frac{\left(1 + \|y_i - y_j\|^2\right)^{-1}}{\sum_{k \neq l} \left(1 + \|y_k - y_l\|^2\right)^{-1}} qij=k=l(1+ykyl2)1(1+yiyj2)1

损失函数

  t-SNE 的目标是使低维空间中的相似度分布 q i j q_{ij} qij尽可能地接近高维空间中的相似度分布 p i j p_{ij} pij。因此,t-SNE 使用 Kullback-Leibler 散度(KL散度)作为目标函数,来度量这两者之间的差异:

C ( Y ) = ∑ i < j p i j l o g p i j q i j C(Y)=\sum_{i<j}p_{ij}log\frac{p_{ij}}{q_{ij}} C(Y)=i<jpijlogqijpij

Barnes-Hut t-SNE优化

在这里插入图片描述
  Barnes-Hut t-SNE 通过引入 Barnes-Hut 近似 方法来降低计算复杂度,从 O ( N 2 ) O(N^2) O(N2)降低到 O ( N l o g N ) O(NlogN) O(NlogN)

四叉树(Quadtree)

  四叉树是一种递归的空间分区数据结构,主要用于 二维空间(2D)。
  基本思想(分治):将一个平面区域反复划分为四个子区域(象限),直到满足某种条件为止(如每个区域内的点数少于某个阈值)。
  节点分割:每个节点对应一个矩形区域,子节点表示将该区域划分为的四个子矩形。
  应用在 t-SNE 中,低维空间的点对相似度 q i j q_{ij} qij的计算复杂度为 O ( N 2 ) O(N^2) O(N2)。通过四叉树,将远离某点的点簇合并为一个伪点进行计算,从而降低复杂度。

八叉树(Octree)

  八叉树是四叉树在 三维空间(3D) 的扩展。过程与四叉树基本一致。
  选择 4 和 8 作为分区数量,主要是基于四叉树和八叉树的核心思想:均匀分割空间

FIt-SNE

  FIt-SNE 使用 FFT 计算高维数据点之间的引力和斥力,从而替代了 Barnes-Hut t-SNE 的树结构,更加优化了时间复杂度。虽然复杂度仍是 O ( N l o g N ) O(NlogN) O(NlogN),但在极大的数据集上的效果优于Barnes-Hut t-SNE。
  此外,FLt-SNE支持GPU加速。

代码实现

  普通的t-SNE:

from sklearn.manifold import TSNE
from sklearn.datasets import make_swiss_roll
import matplotlib.pyplot as plt

# 生成三维的瑞士卷数据集
X, color = make_swiss_roll(n_samples=1000, noise=0.05)

# 可视化原始高维数据
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=color, cmap=plt.cm.Spectral)
plt.title("Original 3D Swiss Roll")
plt.show()



# 创建 t-SNE 对象,设置目标维度为2
tsne = TSNE(n_components=2)

# 降维
X_tsne = tsne.fit_transform(X)

# 可视化降维后的结果
plt.figure(figsize=(8, 6))
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=color, cmap=plt.cm.Spectral)
plt.title("2D Embedding using tsne")
plt.xlabel("Component 1")
plt.ylabel("Component 2")
plt.show()

在这里插入图片描述
在这里插入图片描述
  以及使用Barnes-Hut t-SNE需要修改的地方

tsne = TSNE(
    n_components=2,         # 降维到 2 维
    perplexity=30,          # 困惑度,控制局部结构
    learning_rate=200,      # 学习率
    n_iter=1000,            # 最大迭代次数
    method='barnes_hut',    # 使用 Barnes-Hut 近似算法
    n_jobs=-1,              # 并行计算,加速处理
    random_state=42         # 固定随机种子,确保结果可复现
)

  而Flt-SNE则要用库openTSNE:

from openTSNE import TSNE
from sklearn.datasets import make_swiss_roll
import matplotlib.pyplot as plt

# 生成三维的瑞士卷数据集
X, color = make_swiss_roll(n_samples=1000, noise=0.05)

# 可视化原始高维数据
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=color, cmap=plt.cm.Spectral)
plt.title("Original 3D Swiss Roll")
plt.show()

# 使用 FIt-SNE 进行降维
fit_tsne = TSNE(
    n_components=2,         # 降维到二维
    perplexity=20,          # 调整困惑度
    learning_rate=200,      # 学习率
    n_iter=500,             # 最大迭代次数
    initialization="pca",   # 使用 PCA 初始化
    random_state=42,        # 固定随机种子
    # n_jobs=1                # 强制单线程
    n_jobs=-1               # 多线程,不知道为什么我多线程跑不出结果,单线程也很慢很慢
)

# 执行降维
X_fit_tsne = fit_tsne.fit(X)

# 可视化降维后的二维结果
plt.figure(figsize=(8, 6))
plt.scatter(X_fit_tsne[:, 0], X_fit_tsne[:, 1], c=color, cmap=plt.cm.Spectral, s=15, edgecolor='k')
plt.title("2D Embedding using FIt-SNE")
plt.xlabel("Component 1")
plt.ylabel("Component 2")
plt.show()

;