📑 摘要

本文全面深入地介绍了t-SNE(t-分布随机邻域嵌入)这一强大的非线性降维算法。从基础理论到实际应用,文章系统地阐述了t-SNE的核心原理、数学推导、参数调优以及在MNIST数据集上的具体实现。通过与PCA的对比分析,详细说明了t-SNE在保持数据局部结构方面的独特优势。文章还提供了完整的Python实现代码,展示了如何将高维数据可视化,并对结果进行定量分析。无论是机器学习研究者还是实践者,都能从本文获得对t-SNE的深入理解和实用指导。

关键词:t-SNE、降维算法、数据可视化、机器学习、MNIST数据集

什么是t-SNE降维?

t-SNE(t-Distributed Stochastic Neighbor Embedding,t分布邻域嵌入)是一种非线性降维方法,专注于在高维数据中保留点与点之间的局部结构。它特别适用于可视化高维复杂数据,如图像、文本或嵌入向量等。

t-SNE的核心思想

  1. 相似性建模:t-SNE通过计算概率分布来衡量高维空间中点与点之间的相似性,并尝试在低维空间中保留这些相似性。
  2. 目标函数:通过优化Kullback-Leibler散度(KL散度),使高维空间中邻近的点在低维空间中保持接近。
  3. 局部结构:强调点之间的局部关系,而不强求全局结构的保留。

t-SNE与PCA的区别

对比维度 PCA(主成分分析) t-SNE
方法类型 线性降维 非线性降维
核心目标 最大化数据在低维空间中的方差 保留高维空间中点对点的局部相似性
全局 vs 局部 适合保留全局结构 强调局部结构
速度和效率 快速,计算复杂度低 较慢,计算复杂度高
结果解释 降维后的主成分具有明确的线性意义 降维结果仅用于可视化和聚类分析
应用场景 特征提取、压缩维度 数据可视化、模式发现、聚类分析

核心区别

  1. PCA关注全局结构:通过寻找主要方向的线性变换,将高维数据投影到低维,适用于数据整体特征提取和降维。
  2. t-SNE关注局部结构:通过概率建模,让高维中“相似”的点在低维空间保持接近,更适合复杂非线性数据的可视化。

总结

  • 如果目标是快速特征提取并保持数据的全局性,选择 PCA
  • 如果需要深入分析数据的局部特性或用于数据可视化,t-SNE 是不二之选。

t-SNE的核心原理

1. 高维空间中的相似性计算

在高维空间中,相似性由条件概率表示。给定点 i 和点 j,定义点 j 相对于点 i 的相似度 p_{j|i} 为:

p j ∣ i = exp ⁡ ( − ∥ x i − x j ∥ 2 / 2 σ i 2 ) ∑ k ≠ i exp ⁡ ( − ∥ x i − x k ∥ 2 / 2 σ i 2 ) p_{j|i} = \frac{\exp\left(-\|x_i - x_j\|^2 / 2\sigma_i^2\right)}{\sum_{k \neq i} \exp\left(-\|x_i - x_k\|^2 / 2\sigma_i^2\right)} pji=k=iexp(xixk2/2σi2)exp(xixj2/2σi2)

其中:

  • x_i 和 x_j 是高维空间中的两个数据点
  • σ_i 是控制点 i 的局部范围的参数,通常通过一个特定的perplexity值(困惑度)自适应地计算

高维空间中任意两点的联合概率分布为:

p i j = p j ∣ i + p i ∣ j 2 n p_{ij} = \frac{p_{j|i} + p_{i|j}}{2n} pij=2npji+pij

其中 n 是数据点的总数。

2. 低维空间中的相似性计算

在低维空间中,相似性分布使用t分布建模,定义点 i 和点 j 的相似度 q_{ij} 为:

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

其中:

  • y_i 和 y_j 是低维空间中的数据点
  • t分布的重尾性质能够更好地处理高维空间中数据的聚类关系

3. 损失函数(KL散度)

t-SNE通过最小化高维概率分布 P = { p i j } P = \{p_{ij}\} P={pij} 和低维概率分布 Q = { q i j } Q = \{q_{ij}\} Q={qij} 之间的Kullback-Leibler散度来优化低维空间中的表示:

C = ∑ i ≠ j p i j log ⁡ p i j q i j C = \sum_{i \neq j} p_{ij} \log \frac{p_{ij}}{q_{ij}} C=i=jpijlogqijpij

这个目标函数保证了高维空间中相似的数据点在低维空间中仍然相邻。

4. 优化

t-SNE通过梯度下降法优化损失函数。梯度的计算公式为:

∂ C ∂ y i = 4 ∑ j ( p i j − q i j ) ( y i − y j ) ( 1 + ∥ y i − y j ∥ 2 ) − 1 \frac{\partial C}{\partial y_i} = 4 \sum_{j} \left(p_{ij} - q_{ij}\right) \left(y_i - y_j\right) \left(1 + \|y_i - y_j\|^2\right)^{-1} yiC=4j(pijqij)(yiyj)(1+yiyj2)1


t-SNE的核心参数

  1. Perplexity(困惑度):影响高维空间中相似性计算的范围,常取值在5到50之间
  2. 学习率:控制梯度下降步长
  3. 迭代次数:通常t-SNE需要数百次迭代以收敛

数学直观解释

  • t-SNE将高维空间中点对之间的欧几里得距离转化为条件概率 p i j p_{ij} pij,表示点对的相似性
  • 低维空间中,使用t分布代替高斯分布来避免"拥挤问题"(crowding problem),更好地保留数据间的相对距离
  • KL散度作为损失函数,最小化了高维与低维概率分布之间的差异

t-SNE的优点和局限

优点

  1. 局部结构保留:适合用于数据聚类和分类。
  2. 可视化效果优秀:在高维复杂数据上,低维投影通常展现良好的分离性。
  3. 非线性降维:能够揭示PCA无法捕获的非线性模式。

局限

  1. 计算代价高:尤其是对大规模数据集,t-SNE的计算复杂度较高。
  2. 无法保持全局结构:重点关注局部相似性,但忽略了整体数据分布。
  3. 参数敏感性:如perplexity的选择对结果影响较大。
  4. 结果不可重复:由于随机初始化,结果可能会有所不同。

t-SNE在MNIST数据集上的实战

下面我们将通过代码展示如何使用t-SNE在MNIST数据集上进行降维,并可视化结果。这一实战过程包含以下步骤:数据加载、t-SNE降维、可视化以及聚类效果分析。


1. 数据加载与预处理

MNIST数据集由手写数字图片组成,每张图片是一个28×28的灰度图像。以下是数据预处理的过程:

def load_and_preprocess_data():
    (X_train, y_train), _ = mnist.load_data()
    
    # 随机选择2000张图片用于降维分析
    n_samples = 2000
    random_idx = np.random.choice(X_train.shape[0], n_samples, replace=False)
    X = X_train[random_idx]
    y = y_train[random_idx]
    
    # 数据展平和归一化
    X = X.reshape(n_samples, -1)
    X = X / 255.0
    return X, y

参数解释:

  • 数据展平:将每张28×28的图片展平为784维向量
  • 数据归一化:将像素值缩放到[0, 1]范围

2. t-SNE降维实现

使用t-SNE将数据从784维降到2维,并保留局部结构。核心代码如下:

def perform_tsne(X, perplexity=30, n_iter=1000):
    tsne = TSNE(
        n_components=2, 
        perplexity=perplexity, 
        n_iter=n_iter, 
        random_state=42
    )
    X_tsne = tsne.fit_transform(X)
    return X_tsne

参数解释:

  • n_components=2:降维到二维,便于可视化
  • perplexity:控制高维空间的局部相似性范围,默认值为30
  • n_iter:迭代次数,默认值为1000

3. 可视化t-SNE结果

def visualize_tsne(X_tsne, y, title="t-SNE visualization of MNIST"):
    plt.figure(figsize=(10, 8))
    plt.style.use('seaborn')
    
    scatter = plt.scatter(
        X_tsne[:, 0], 
        X_tsne[:, 1], 
        c=y, 
        cmap='tab10', 
        alpha=0.6, 
        s=50
    )
    plt.colorbar(scatter)
    plt.title(title, fontsize=14)
    plt.xlabel("t-SNE feature 1", fontsize=12)
    plt.ylabel("t-SNE feature 2", fontsize=12)
    plt.show()

结果解读:

  • 每个点代表一张手写数字图片
  • 点的颜色对应其真实标签(0-9)
  • t-SNE生成的二维图中,不同类别的数字形成了明显的聚类

4. 聚类效果分析

def analyze_clusters(X_tsne, y):
    centers = {}
    for digit in range(10):
        mask = y == digit
        centers[digit] = np.mean(X_tsne[mask], axis=0)
    
    intra_distances = {}
    for digit in range(10):
        mask = y == digit
        points = X_tsne[mask]
        center = centers[digit]
        distances = np.sqrt(np.sum((points - center) ** 2, axis=1))
        intra_distances[digit] = np.mean(distances)
    
    plt.figure(figsize=(10, 5))
    plt.bar(intra_distances.keys(), intra_distances.values())
    plt.title("Average intra-cluster distances")
    plt.xlabel("Digit")
    plt.ylabel("Average distance to center")
    plt.show()
    return centers, intra_distances

分析结果:

  • 计算每类数据点到其中心的平均距离
  • 显示每个类别的类内聚集程度。距离较小的类别(如"1")通常表示聚类效果更好

5. 完整运行代码

def main():
    # 数据加载和预处理
    print("Loading and preprocessing data...")
    X, y = load_and_preprocess_data()
    
    # t-SNE降维
    print("Performing t-SNE...")
    X_tsne = perform_tsne(X)
    
    # 可视化
    print("Visualizing results...")
    visualize_tsne(X_tsne, y)
    
    # 聚类分析
    print("Analyzing clustering results...")
    centers, intra_distances = analyze_clusters(X_tsne, y)
    
    # 输出类内距离
    print("\nIntra-cluster distances:")
    for digit, distance in intra_distances.items():
        print(f"Digit {digit}: {distance:.3f}")

if __name__ == "__main__":
    main()

6. 结果展示

运行代码后,我们可以观察到:
在这里插入图片描述

  • t-SNE有效将MNIST数据的不同类别分离开来
  • 图像聚类效果清晰,各类别的聚类中心分布合理
  • 类内距离反映了各类别的紧密程度

全部代码

import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from sklearn.datasets import load_digits
from tensorflow.keras.datasets import mnist
import seaborn as sns

# 1. 数据加载和预处理
def load_and_preprocess_data():
    # 加载MNIST数据
    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    
    # 选择部分数据进行可视化(t-SNE计算量大)
    n_samples = 2000
    random_idx = np.random.choice(X_train.shape[0], n_samples, replace=False)
    X = X_train[random_idx]
    y = y_train[random_idx]
    
    # 数据预处理
    X = X.reshape(n_samples, -1)  # 展平图像
    X = X / 255.0  # 归一化
    
    return X, y

# 2. t-SNE降维
def perform_tsne(X, perplexity=30, n_iter=1000):
    tsne = TSNE(
        n_components=2,
        perplexity=perplexity,
        n_iter=n_iter,
        random_state=42
    )
    X_tsne = tsne.fit_transform(X)
    return X_tsne

# 3. 可视化函数
def visualize_tsne(X_tsne, y, title="t-SNE visualization of MNIST"):
    # 设置图形样式
    plt.figure(figsize=(10, 8))
    plt.style.use('seaborn')
    
    # 绘制散点图
    scatter = plt.scatter(
        X_tsne[:, 0], 
        X_tsne[:, 1], 
        c=y, 
        cmap='tab10',
        alpha=0.6,
        s=50
    )
    
    # 添加颜色条
    plt.colorbar(scatter)
    
    # 设置标题和标签
    plt.title(title, fontsize=14)
    plt.xlabel("t-SNE feature 1", fontsize=12)
    plt.ylabel("t-SNE feature 2", fontsize=12)
    
    # 添加图例
    legend_elements = [plt.Line2D([0], [0], marker='o', color='w', 
                                 markerfacecolor=plt.cm.tab10(i/10), 
                                 label=f'Digit {i}', markersize=10)
                      for i in range(10)]
    plt.legend(handles=legend_elements, title="Digits", 
              loc="center left", bbox_to_anchor=(1, 0.5))
    
    plt.tight_layout()
    plt.show()

# 4. 分析聚类效果
def analyze_clusters(X_tsne, y):
    # 计算每个类别的中心点
    centers = {}
    for digit in range(10):
        mask = y == digit
        centers[digit] = np.mean(X_tsne[mask], axis=0)
    
    # 计算类内距离
    intra_distances = {}
    for digit in range(10):
        mask = y == digit
        points = X_tsne[mask]
        center = centers[digit]
        distances = np.sqrt(np.sum((points - center) ** 2, axis=1))
        intra_distances[digit] = np.mean(distances)
    
    # 可视化类内距离
    plt.figure(figsize=(10, 5))
    plt.bar(intra_distances.keys(), intra_distances.values())
    plt.title("Average intra-cluster distances")
    plt.xlabel("Digit")
    plt.ylabel("Average distance to center")
    plt.show()
    
    return centers, intra_distances

# 5. 主函数
def main():
    # 加载和预处理数据
    print("Loading and preprocessing data...")
    X, y = load_and_preprocess_data()
    
    # 执行t-SNE
    print("Performing t-SNE...")
    X_tsne = perform_tsne(X)
    
    # 可视化结果
    print("Visualizing results...")
    visualize_tsne(X_tsne, y)
    
    # 分析聚类效果
    print("Analyzing clustering results...")
    centers, intra_distances = analyze_clusters(X_tsne, y)
    
    # 打印分析结果
    print("\nIntra-cluster distances:")
    for digit, distance in intra_distances.items():
        print(f"Digit {digit}: {distance:.3f}")

if __name__ == "__main__":
    main()


总结

t-SNE通过高效的非线性降维方法,为复杂高维数据的可视化提供了一种直观且实用的工具。它的核心是通过KL散度最小化高维空间和低维空间的相似性分布差异,在保持局部结构的同时进行降维。虽然计算代价较高,但在图像、文本、基因组学等领域,t-SNE已成为数据探索和模式发现的重要方法。
通过t-SNE对MNIST数据集的降维,我们直观地看到了手写数字在二维空间中的分布和聚类关系。t-SNE的强大之处在于它能够保留高维数据的局部结构,使其成为分析和可视化复杂数据的利器。

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐