前言

提醒:
文章内容为方便作者自己后日复习与查阅而进行的书写与发布,其中引用内容都会使用链接表明出处(如有侵权问题,请及时联系)。
其中内容多为一次书写,缺少检查与订正,如有问题或其他拓展及意见建议,欢迎评论区讨论交流。

相关链接:
t-SNE高维数据可视化(python)
t-SNE使用过程中的一些坑
代码抄录源


代码抄录

import numpy as np 
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
import time

def cal_pairwise_dist(x):
    sum_x=np.sum(np.square(x),1)
    dist=np.add(np.add(-2*np.dot(x,x.T),sum_x).T,sum_x)
    return dist
def cal_perplexity(dist,idx=0,beta=1.0):
    prob=np.exp(-dist*beta)
    prob[idx]=0
    sum_prob=np.sum(prob)
    if sum_prob<1e-12:
        prob=np.maximum(prob,1e-12)
        perp=-12
    else:
        perp=np.log(sum_prob) + beta*np.sum(dist*prob) / sum_prob
        prob=prob/sum_prob
    return perp,prob
def search_prob(x,tol=1e-5,perplexity=30.0):
    print("Computing pairwise distances...")
    (n,d)=x.shape
    dist=cal_pairwise_dist(x)
    dist[dist<0]=0
    pair_prob=np.zeros((n,n))
    beta=np.ones((n,1))
    base_perp=np.log(perplexity)
    for i in range(n):
        if i%500==0:
            print("Computing pair_prob for point %s of %s..." % (i,n))
        betamin=-np.inf
        betamax=np.inf
        perp,this_prob=cal_perplexity(dist[i],i,beta[i])
        perp_diff=perp-base_perp
        tries=0
        while np.abs(perp_diff)>tol and tries<50:
            if perp_diff>0:
                betamin=beta[i].copy()
                if betamax==np.inf or betamax==-np.inf:
                    beta[i]=beta[i]*2
                else:
                    beta[i]=(beta[i]+betamax)/2
            else:
                betamax=beta[i].copy()
                if betamin==np.inf or betamin==-np.inf:
                    beta[i]=beta[i]/2
                else:
                    beta[i]=(beta[i]+betamin)/2
            perp,this_prob=cal_perplexity(dist[i],i,beta[i])
            perp_diff=perp-base_perp
            tries=tries+1
        pair_prob[i,]=this_prob
    print("Mean value of sigma: %f" % np.mean(np.sqrt(1/(beta))))
    return pair_prob
def tsne(x,no_dims=2,perplexity=30.0,max_iter=1000):
    if isinstance(no_dims,float):
        print("Error: array x should have type float")
        return -1
    (n,d)=x.shape
    initial_momentum=0.5
    final_momentum=0.8
    eta=500
    min_gain=0.01
    y=np.random.randn(n,no_dims)
    dy=np.zeros((n,no_dims))
    iy=np.zeros((n,no_dims))
    gains=np.ones((n,no_dims))
    P=search_prob(x,1e-5,perplexity)
    P=P+np.transpose(P)
    P=P/np.sum(P)
    print("T_SNE DURING:%s" % time.process_time())
    P=P*4
    P=np.maximum(P,1e-12)
    for iter in range(max_iter):
        sum_y=np.sum(np.square(y),1)
        num=1/(1+np.add(np.add(-2*np.dot(y,y.T),sum_y).T,sum_y))
        num[range(n),range(n)]=0
        Q=num/np.sum(num)
        Q=np.maximum(Q,1e-12)
        PQ=P-Q
        for i in range(n):
            dy[i,:]=np.sum(np.tile(PQ[:,i]*num[:,i],(no_dims,1)).T*(y[i,:]-y),0)
        if iter<20:
            momentum=initial_momentum
        else:
            momentum=final_momentum
        gains=(gains+0.2)*((dy>0)!=(iy>0))+(gains*0.8)*((dy>0)==(iy>0))
        gains[gains<min_gain]=min_gain
        iy=momentum*iy-eta*(gains*dy)
        y=y+iy
        y=y-np.tile(np.mean(y,0),(n,1))
        if (iter+1)%100==0:
            C=np.sum(P*np.log(P/Q))
            print("Iteration",(iter+1),": error is",C)
            if (iter+1)!=100:
                ratio=C/oldC   
                print("ratio ",ratio)
                if ratio>=0.95:
                    break
            oldC=C
        if iter==100:
            P=P/4
    print("finished training ")
    return y

if __name__=="__main__":
    digits=load_digits()
    X=digits.data
    Y=digits.target
    data_2d=tsne(X,2)
    plt.scatter(data_2d[:,0],data_2d[:,1],c=Y)
    plt.show()

代码分析

数据集介绍

load_digits()scikit-learn 库中的一个函数,用于加载手写数字数据集。这个数据集常用于图像分类和机器学习算法的测试和演示。

详细介绍

  • load_digits(): 加载手写数字数据集。
  • digits.data: 一个二维数组,形状为 (1797, 64),每一行表示一个 8x8 的图像展开成的 64 维特征向量。
  • digits.images: 一个三维数组,形状为 (1797, 8, 8),表示原始的 8x8 图像。
  • digits.target: 一个一维数组,长度为 1797,表示图像对应的数字标签(0 到 9)。
  • digits.target_names: 数组,表示可能的目标名称(数字 0 到 9)。

代码展示

from sklearn.datasets import load_digits
import matplotlib.pyplot as plt

# 加载数据集
digits = load_digits()

# 数据集的基本信息
print(f"数据集形状: {digits.data.shape}")
print(f"目标类别数量: {len(digits.target_names)}")

# 显示前5个数据点的图像和标签
fig, axes = plt.subplots(1, 5, figsize=(10, 3))
for ax, image, label in zip(axes, digits.images, digits.target):
    ax.set_axis_off()
    ax.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    ax.set_title(f'Target: {label}')
plt.show()

# 打印第一张图片的矩阵
print(digits.images[0])

运行结果为:
在这里插入图片描述

注意 d i g i t s . d a t a . s h a p e digits.data.shape digits.data.shaped的形状为(1797,64),即数据集有1797条数据,每条数据为64个数(8*8矩阵摊平)的行向量

t-SNE算法简介

t-SNE(t-分布邻域嵌入)是一种非线性降维技术,尤其适合用于高维数据的可视化。它通过将数据点嵌入到二维或三维空间,保留高维空间中相似点的距离关系。

数学表达

  1. 相似度定义

    • 在高维空间中,对于两个数据点 x i x_i xi x j x_j xj,定义其相似度为条件概率 p j ∣ i p_{j|i} pji,表示在给定 x i x_i xi 的情况下选择 x j x_j xj 的概率。通常使用高斯分布来计算:
      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(-\|x_i - x_j\|^2 / 2\sigma_i^2)}{\sum_{k \neq i} \exp(-\|x_i - x_k\|^2 / 2\sigma_i^2)} pji=k=iexp(xixk2/2σi2)exp(xixj2/2σi2)
    • 最终相似度 p i j p_{ij} pij 是对称的,定义为:
      p i j = p j ∣ i + p i ∣ j 2 n p_{ij} = \frac{p_{j|i} + p_{i|j}}{2n} pij=2npji+pij
  2. 低维空间相似度

    • 在低维空间中,使用 t-分布计算相似度 q i j q_{ij} qij
      q i j = ( 1 + ∥ y i − y j ∥ 2 ) − 1 ∑ k ≠ l ( 1 + ∥ y k − y l ∥ 2 ) − 1 q_{ij} = \frac{(1 + \|y_i - y_j\|^2)^{-1}}{\sum_{k \neq l} (1 + \|y_k - y_l\|^2)^{-1}} qij=k=l(1+ykyl2)1(1+yiyj2)1
  3. Kullback-Leibler 散度

    • t-SNE 通过最小化高维和低维分布之间的 Kullback-Leibler 散度来找到最优嵌入:
      KL ( P ∣ ∣ Q ) = ∑ i ≠ j p i j log ⁡ p i j q i j \text{KL}(P || Q) = \sum_{i \neq j} p_{ij} \log \frac{p_{ij}}{q_{ij}} KL(P∣∣Q)=i=jpijlogqijpij
  4. 梯度下降

    • 使用梯度下降法最小化 KL 散度,更新低维空间坐标 y i y_i yi

优势和应用

  • 保留局部结构: t-SNE 能够很好地保留高维空间中局部结构的关系。
  • 数据可视化: 被广泛用于图像、文本和基因数据的降维与可视化。

注意事项

  • 计算复杂度高: t-SNE 对于大数据集可能较慢。
  • 参数选择: 需要调节参数如perplexity来获得最佳结果。

cal_pairwise_dist(x)

def cal_pairwise_dist(x):
    sum_x=np.sum(np.square(x),1)
    dist=np.add(np.add(-2*np.dot(x,x.T),sum_x).T,sum_x)
    return  dist

代码展示的结果为:
(在 np.sum(np.square(x), 1) 中,1 表示沿着矩阵的行方向(即第2个维度)进行求和。)

  1. sum_x=np.sum(np.square(x),1):在这里插入图片描述
  2. dist=np.add(np.add(-2*np.dot(x,x.T),sum_x).T,sum_x):
    在这里插入图片描述
    最终,得到的矩阵 D \mathbf{D} D的每个元素 D i j \mathbf{D}_{ij} Dij代表数据点 x i x_i xi x j x_j xj之间的欧几里得距离的平方。

cal_perplexity(dist,idx=0,beta=1.0)

def cal_perplexity(dist,idx=0,beta=1.0):
    prob=np.exp(-dist*beta)
    prob[idx]=0
    sum_prob=np.sum(prob)
    if sum_prob<1e-12:
        prob=np.maximum(prob,1e-12)
        perp=-12
    else:
        perp=np.log(sum_prob) + beta*np.sum(dist*prob) / sum_prob
        prob=prob/sum_prob
    return perp,prob
  1. prob=np.exp(-dist*beta)
    prob[idx]=0
    sum_prob=np.sum(prob)

    概率(probability)困惑度(perplexity)
    计算未归一化的相似性概率:
    对于给定的点 i i i,计算与其他点 j j j 的未归一化相似性概率:
    prob j = exp ⁡ ( − β ⋅ dist ( x i , x j ) ) \text{prob}_j = \exp(-\beta \cdot \text{dist}(x_i, x_j)) probj=exp(βdist(xi,xj))
    这里, dist ( x i , x j ) \text{dist}(x_i, x_j) dist(xi,xj) 是点 i i i 和点 j j j 之间的距离, β \beta β 是控制高斯宽度的参数。
    排除自身概率:
    设置 prob i = 0 \text{prob}_i = 0 probi=0 以避免自我相似性计算:
    prob i = 0 \text{prob}_i = 0 probi=0
    计算概率和:
    计算所有未归一化概率的和:
    sum_prob = ∑ j prob j \text{sum\_prob} = \sum_{j} \text{prob}_j sum_prob=jprobj
    这个过程为每个点计算与其他点的相对相似性,除了自身。
    if sum_prob<1e-12:
        prob=np.maximum(prob,1e-12)
        perp=-12
    else:
        perp=np.log(sum_prob) + beta*np.sum(dist*prob) / sum_prob
        prob=prob/sum_prob

这段代码处理相似性概率的数学表达如下:
情况 1: sum_prob < 1 × 1 0 − 12 \text{sum\_prob} < 1 \times 10^{-12} sum_prob<1×1012

  • 调整概率:
    prob j = max ⁡ ( prob j , 1 × 1 0 − 12 ) \text{prob}_j = \max(\text{prob}_j, 1 \times 10^{-12}) probj=max(probj,1×1012)
  • 设定困惑度:
    perp = − 12 \text{perp} = -12 perp=12

情况 2: sum_prob ≥ 1 × 1 0 − 12 \text{sum\_prob} \geq 1 \times 10^{-12} sum_prob1×1012

  • 计算困惑度:
    perp = log ⁡ ( sum_prob ) + β ∑ j dist ( x i , x j ) ⋅ prob j sum_prob \text{perp} = \log(\text{sum\_prob}) + \frac{\beta \sum_{j} \text{dist}(x_i, x_j) \cdot \text{prob}_j}{\text{sum\_prob}} perp=log(sum_prob)+sum_probβjdist(xi,xj)probj
  • 归一化概率:
    prob j = prob j sum_prob \text{prob}_j = \frac{\text{prob}_j}{\text{sum\_prob}} probj=sum_probprobj

解释

  • 调整概率: 当 sum_prob \text{sum\_prob} sum_prob 非常小时,概率调整到一个最小值以避免数值不稳定性。
  • 困惑度定义: 困惑度(perplexity)提供了一种衡量信息熵的方式。它反映了概率分布的复杂性。
  • 归一化: 确保所有概率之和为1,从而得到有效的概率分布。

相关拓展

  1. 相似性概率
    相似性概率是用于度量数据点之间相互关系的概率分布。在许多机器学习和数据处理算法中,尤其是基于距离的嵌入方法中(如t-SNE),相似性概率用于将高维数据映射到低维空间。

计算过程

  1. 距离计算:
    • 首先计算数据点之间的距离 dist ( x i , x j ) \text{dist}(x_i, x_j) dist(xi,xj)
  2. 高斯核:
    • 使用高斯核来将距离转换为相似性度量:
      p j ∣ i = exp ⁡ ( − β ⋅ dist ( x i , x j ) ) p_{j|i} = \exp(-\beta \cdot \text{dist}(x_i, x_j)) pji=exp(βdist(xi,xj))
      其中, β \beta β 是一个参数,控制高斯函数的宽度。它通常与数据点的局部密度有关。
  3. 排除自身的影响:
    • 为避免自我相似性影响,设置 p i ∣ i = 0 p_{i|i} = 0 pii=0
  4. 归一化:
    • 将所有相似性度量归一化为概率分布:
      p j ∣ i = p j ∣ i ∑ k ≠ i p k ∣ i p_{j|i} = \frac{p_{j|i}}{\sum_{k \neq i} p_{k|i}} pji=k=ipkipji
      这样,所有与点 i i i 相关的概率总和为1。

作用

  • 局部邻域的评估: 相似性概率用于评估点在其局部邻域内的关系。较高的概率意味着两个点在高维空间中更为接近。
  • 数据嵌入与降维: 在降维算法中,相似性概率帮助维持局部结构,将其从高维空间传递到低维空间。
  • 聚类分析: 在聚类算法中,相似性概率用于识别和划分数据点群体。

应用

相似性概率在机器学习中的多个领域有应用,包括:

  • t-SNE: 通过保持相似性概率在高低维空间的一致性,进行有效降维。
  • 谱聚类: 使用相似性概率构建图的拉普拉斯矩阵,以识别数据中的聚类结构。

总之,相似性概率是一个关键概念,帮助算法在不同维度间保持数据的本质结构。

  1. 困惑度
    困惑度(Perplexity)是信息理论中的一个概念,用于量化概率分布的不确定性或复杂性。它常用于自然语言处理和机器学习领域。

定义

困惑度是熵的指数形式,用来表示概率分布的有效状态数。对于一个概率分布 P P P,困惑度定义为:

Perplexity ( P ) = 2 H ( P ) \text{Perplexity}(P) = 2^{H(P)} Perplexity(P)=2H(P)

其中 H ( P ) H(P) H(P) 是熵,定义为:

H ( P ) = − ∑ i p i log ⁡ 2 p i H(P) = -\sum_{i} p_i \log_2 p_i H(P)=ipilog2pi

解释

  • 直观理解: 困惑度可以被看作是对概率分布的平均不确定性的一种度量。较高的困惑度表示分布较为分散,较低的困惑度表示分布较为集中。

  • 语言模型: 在自然语言处理中,困惑度用来评估语言模型的性能。模型生成文本的困惑度越低,说明模型对数据的预测越好。

应用

  1. 语言模型:

    • 衡量模型对测试文本的预测能力。
    • 通过计算生成序列的概率,来评估模型的好坏。
  2. 降维与聚类:

    • 在t-SNE等算法中,困惑度用于平衡局部和全局数据结构,调节数据点邻域的广度。
  3. 信息检索与排序:

    • 评估搜索算法和推荐系统的效果。

计算示例

假设有一个概率分布 P = [ 0.2 , 0.5 , 0.3 ] P = [0.2, 0.5, 0.3] P=[0.2,0.5,0.3],其熵为:

H ( P ) = − ( 0.2 log ⁡ 2 0.2 + 0.5 log ⁡ 2 0.5 + 0.3 log ⁡ 2 0.3 ) H(P) = -(0.2 \log_2 0.2 + 0.5 \log_2 0.5 + 0.3 \log_2 0.3) H(P)=(0.2log20.2+0.5log20.5+0.3log20.3)

困惑度则为:

Perplexity ( P ) = 2 H ( P ) \text{Perplexity}(P) = 2^{H(P)} Perplexity(P)=2H(P)

关于源代码perp=-12的可能原因

困惑度(perplexity)是概率分布的不确定性度量,其取值范围一般为 ([1, +\infty))。具体来说:

  • 下限:困惑度的最小值是 1。当所有概率质量集中在一个事件上时(即确定性分布),困惑度为 1。

  • 上限:困惑度没有严格的上限,理论上可以趋向于无穷大。当概率分布非常均匀时(如在一个高维空间中),困惑度会变得很大。
    在实际应用中,选择的困惑度值通常反映了我们对数据局部相似性的期望。对于t-SNE等算法,常用的困惑度值在 5 到 50 之间,这取决于数据的具体性质和结构。

在代码中,perp 被设为 -12 的情况是在 sum_prob 非常小的情况下发生的。当 sum_prob 小于 1e-12 时,代码将 perp 设置为 -12,这可能是为了防止数值不稳定或除以零的问题。

以下是可能的原因:

  1. 防止除零:如果 sum_prob 过小,可能会导致后续计算中出现除以零的情况,导致数值不稳定。
  2. 标识异常情况:使用 -12 作为一个标志值,表示在计算中出现了异常或意外情况,从而可以在后续处理中识别和处理。

这个值的选择是人为的,并不代表任何特定的数学意义。开发人员可能选择 -12 只是为了在特定应用中充当一个明显的标志。

总结

困惑度是衡量概率分布复杂性的重要工具,广泛用于各种机器学习和数据处理任务中。它帮助我们理解模型在处理未知数据时的准确性和可靠性。

search_prob(x,tol=1e-5,perplexity=30.0)

def search_prob(x,tol=1e-5,perplexity=30.0):
    print("Computing pairwise distances...")
    (n,d)=x.shape
    dist=cal_pairwise_dist(x)
    dist[dist<0]=0
    pair_prob=np.zeros((n,n))
    beta=np.ones((n,1))
    base_perp=np.log(perplexity)
    for i in range(n):
        if i%500==0:
            print("Computing pair_prob for point %s of %s..." % (i,n))
        betamin=-np.inf
        betamax=np.inf
        perp,this_prob=cal_perplexity(dist[i],i,beta[i])
        perp_diff=perp-base_perp
        tries=0
        while np.abs(perp_diff)>tol and tries<50:
            if perp_diff>0:
                betamin=beta[i].copy()
                if betamax==np.inf or betamax==-np.inf:
                    beta[i]=beta[i]*2
                else:
                    beta[i]=(beta[i]+betamax)/2
            else:
                betamax=beta[i].copy()
                if betamin==np.inf or betamin==-np.inf:
                    beta[i]=beta[i]/2
                else:
                    beta[i]=(beta[i]+betamin)/2
            perp,this_prob=cal_perplexity(dist[i],i,beta[i])
            perp_diff=perp-base_perp
            tries=tries+1
        pair_prob[i,]=this_prob
    print("Mean value of sigma: %f" % np.mean(np.sqrt(1/(beta))))
    return pair_prob
  1. dist=cal_pairwise_dist(x)
    计算点对之间的距离平方的矩阵 dist
  2. pair_prob=np.zeros((n,n))
    概率矩阵pair_prob为(n,n)的全零矩阵,最终得到 pair_prob ( i , j ) = P ( j ∣ i ) \text{pair\_prob}(i, j) = P(j|i) pair_prob(i,j)=P(ji)
    P ( j ∣ i ) P(j|i) P(ji) 表示在给定数据点 i i i的情况下选择数据点 j j j的条件概率。
  3. beta=np.ones((n,1))
    初始化 β \beta β
    β = [ 1 1 ⋮ 1 ] \beta = \begin{bmatrix} 1 \\ 1 \\ \vdots \\ 1 \end{bmatrix} β= 111
  4. base_perp=np.log(perplexity)
    确定目标困惑度
  5. for i in range(n):
    ……

    其余代码含义:使用二分法进行困惑度计算以确定pair_prob
    对于每个数据点 i i i,调整参数 β i \beta_i βi使得该点的条件概率分布的困惑度接近目标困惑度 perplexity。数学上,目标是找到:
    β i = arg ⁡ min ⁡ β i ∣ Perplexity ( P ( i ∣ j ; β i ) ) − Perplexity_target ∣ \beta_i = \arg\min_{\beta_i} | \text{Perplexity}(P(i|j; \beta_i)) - \text{Perplexity\_target} | βi=argβiminPerplexity(P(ij;βi))Perplexity_target
    其中,条件概率计算为
    P ( j ∣ i ) = exp ⁡ ( − β i ⋅ dist ( x i , x j ) ) ∑ k ≠ i exp ⁡ ( − β i ⋅ dist ( x i , x k ) ) P(j|i) = \frac{\exp(-\beta_i \cdot \text{dist}(x_i, x_j))}{\sum_{k \neq i} \exp(-\beta_i \cdot \text{dist}(x_i, x_k))} P(ji)=k=iexp(βidist(xi,xk))exp(βidist(xi,xj))

具体步骤为:

  1. 计算当前困惑度差异
    perp_diff = Perplexity ( P ( i ∣ j ; β i ) ) − Perplexity_target \text{perp\_diff} = \text{Perplexity}(P(i|j; \beta_i)) - \text{Perplexity\_target} perp_diff=Perplexity(P(ij;βi))Perplexity_target
  2. 调整 β i \beta_i βi 使用二分法
  • 如果 perp_diff > 0 \text{perp\_diff} > 0 perp_diff>0
  • 则增加 β i \beta_i βi
  • 如果 β max ⁡ \beta_{\max} βmax 未定义,则 β i = 2 β i \beta_i = 2 \beta_i βi=2βi
  • 否则, β i = β i + β max ⁡ 2 \beta_i = \frac{\beta_i + \beta_{\max}}{2} βi=2βi+βmax
  • 如果 perp_diff < 0 \text{perp\_diff} < 0 perp_diff<0
  • 则减少 β i \beta_i βi
    • 如果 β min ⁡ \beta_{\min} βmin 未定义,则 β i = β i 2 \beta_i = \frac{\beta_i}{2} βi=2βi
    • 否则, β i = β i + β min ⁡ 2 \beta_i = \frac{\beta_i + \beta_{\min}}{2} βi=2βi+βmin
  1. 更新上限或下限
  • 如果 perp_diff > 0 \text{perp\_diff} > 0 perp_diff>0,更新 β min ⁡ = β i \beta_{\min} = \beta_i βmin=βi
  • 如果 perp_diff < 0 \text{perp\_diff} < 0 perp_diff<0,更新 β max ⁡ = β i \beta_{\max} = \beta_i βmax=βi
    这段过程重复,直到 ∣ perp_diff ∣ ≤ tol |\text{perp\_diff}| \leq \text{tol} perp_difftol 或达到最大尝试次数(50次)。
    最终更新 pair_prob [ i , : ] = this_prob \text{pair\_prob}[i,:] = \text{this\_prob} pair_prob[i,:]=this_prob,作为点 i i i 的条件概率分布。

tsne(x,no_dims=2,perplexity=30.0,max_iter=1000)

def tsne(x,no_dims=2,perplexity=30.0,max_iter=1000):
    if isinstance(no_dims,float):
        print("Error: array x should have type float")
        return -1
    (n,d)=x.shape
    initial_momentum=0.5
    final_momentum=0.8
    eta=500
    min_gain=0.01
    y=np.random.randn(n,no_dims)
    dy=np.zeros((n,no_dims))
    iy=np.zeros((n,no_dims))
    gains=np.ones((n,no_dims))
    P=search_prob(x,1e-5,perplexity)
    P=P+np.transpose(P)
    P=P/np.sum(P)
    print("T_SNE DURING:%s" % time.process_time())
    P=P*4
    P=np.maximum(P,1e-12)
    for iter in range(max_iter):
        sum_y=np.sum(np.square(y),1)
        num=1/(1+np.add(np.add(-2*np.dot(y,y.T),sum_y).T,sum_y))
        num[range(n),range(n)]=0
        Q=num/np.sum(num)
        Q=np.maximum(Q,1e-12)
        PQ=P-Q
        for i in range(n):
            dy[i,:]=np.sum(np.tile(PQ[:,i]*num[:,i],(no_dims,1)).T*(y[i,:]-y),0)
        if iter<20:
            momentum=initial_momentum
        else:
            momentum=final_momentum
        gains=(gains+0.2)*((dy>0)!=(iy>0))+(gains*0.8)*((dy>0)==(iy>0))
        gains[gains<min_gain]=min_gain
        iy=momentum*iy-eta*(gains*dy)
        y=y+iy
        y=y-np.tile(np.mean(y,0),(n,1))
        if (iter+1)%100==0:
            C=np.sum(P*np.log(P/Q))
            print("Iteration",(iter+1),": error is",C)
            if (iter+1)!=100:
                ratio=C/oldC   
                print("ratio ",ratio)
                if ratio>=0.95:
                    break
            oldC=C
        if iter==100:
            P=P/4
    print("finished training ")
    return y
  1. tsne(x,no_dims=2,perplexity=30.0,max_iter=1000)
  • x x x: 原始数据矩阵,形状为 $ n \times d ( ( n $ 个样本,每个样本有 $ d $ 个特征)。
  • no_dims \text{no\_dims} no_dims: 目标低维空间的维度,默认为 2。
  • perplexity \text{perplexity} perplexity: 用于计算条件概率的困惑度。
  • max_iter \text{max\_iter} max_iter: 最大迭代次数,默认为 1000。
  1. initial_momentum=0.5
    final_momentum=0.8
    eta=500
    min_gain=0.01

    这些是 t-SNE 算法中的一些超参数,控制着梯度下降的行为:
  • initial_momentum = 0.5:
    初始动量,用于在前几次迭代中加速收敛。动量是梯度下降中的一项技术,帮助跳出局部最小值。
  • final_momentum = 0.8:
    最终动量。在算法运行一段时间后,增加动量的值以帮助更稳定地收敛。
  • eta = 500:
    学习率,控制每次迭代时更新的步长大小。较大的学习率可能导致不稳定,较小的学习率可能导致收敛缓慢。
  • min_gain = 0.01:
    增益的最小值,用于防止更新步长过小。增益用于调整学习率,以适应梯度变化。
  • 这些参数需要根据具体数据集进行调整,以获得最佳结果。
  1. y=np.random.randn(n,no_dims)
    dy=np.zeros((n,no_dims))
    iy=np.zeros((n,no_dims))
    gains=np.ones((n,no_dims))
    P=search_prob(x,1e-5,perplexity)
    P=P+np.transpose(P)
    P=P/np.sum(P)
  • y y y: 随机初始化的低维表示,形状为 n × no_dims n \times \text{no\_dims} n×no_dims
  • dy \text{dy} dy, iy \text{iy} iy: 梯度和动量,初始化为零矩阵。
  • gains \text{gains} gains: 增益矩阵,初始化为全 1。
  • P P P: 高维空间中点对的联合概率矩阵,经过对称化和归一化处理。
  1. P=P*4;P=np.maximum(P,1e-12)
    这段代码的作用是对概率矩阵 ( P ) 进行调整:
    P = P × 4 P = P \times 4 P=P×4:
  • 将矩阵 P P P中的每个元素乘以 4。这个步骤在 t-SNE 中是为了在前几次迭代中提高 P P P 的权重,使低维表示更快地形成。
    P = np.maximum ( P , 1 e − 12 ) P = \text{np.maximum}(P, 1e-12) P=np.maximum(P,1e12):
  • P P P中的每个元素与 1 e − 12 1e-12 1e12进行比较,确保所有元素至少为 1 e − 12 1e-12 1e12。这是为了避免对数计算中的零值问题(如计算 KL 散度时 log ⁡ ( 0 ) \log(0) log(0)是未定义的)。这样可以提高数值稳定性。
  1. for iter in range(max_iter):
  1. 计算相似性矩阵
    num = 1 1 + ∥ y i − y j ∥ 2 \text{num} = \frac{1}{1 + \|y_i - y_j\|^2} num=1+yiyj21
    Q = num ∑ num Q = \frac{\text{num}}{\sum \text{num}} Q=numnum
    Q Q Q 是低维空间中的相似性矩阵。
    关于P=np.maximum(P,1e-12)
    np.maximum()函数的作用是逐元素比较两个数组,并返回两者中较大的值。表达式Q=np.maximum(Q,1e-12)的意思是将数组Q中的每个元素与1e-12进行比较,并将Q中每个小于1e-12的元素替换为1e-12。这样做通常是为了避免在后续的计算中出现数值上的不稳定。
  2. 计算梯度
    dy [ i , : ] = ∑ j ( ( P i j − Q i j ) ⋅ num i j ⋅ ( y i − y j ) ) \text{dy}[i,:] = \sum_j \left((P_{ij} - Q_{ij}) \cdot \text{num}_{ij} \cdot (y_i - y_j)\right) dy[i,:]=j((PijQij)numij(yiyj))
    关于*np.tile(PQ[:,i]num[:,i],(no_dims,1))
    将数组 PQ 的第 i 列与数组 num 的第 i 列对应的元素相乘,得到一个一维数组。然后将这个一维数组在垂直方向上重复 no_dims 次形成一个新的二维数组,而水平方向上保持不变。
  3. 更新动量和增益
    gains = ( gains + 0.2 ) ⋅ ( ( dy > 0 ) ≠ ( iy > 0 ) ) + ( gains ⋅ 0.8 ) ⋅ ( ( dy > 0 ) = = ( iy > 0 ) ) \text{gains} = (\text{gains} + 0.2) \cdot ((\text{dy} > 0) \neq (\text{iy} > 0)) + (\text{gains} \cdot 0.8) \cdot ((\text{dy} > 0) == (\text{iy} > 0)) gains=(gains+0.2)((dy>0)=(iy>0))+(gains0.8)((dy>0)==(iy>0))
    gains [ gains < min_gain ] = min_gain \text{gains}[\text{gains} < \text{min\_gain}] = \text{min\_gain} gains[gains<min_gain]=min_gain
    iy = momentum ⋅ iy − η ⋅ ( gains ⋅ dy ) \text{iy} = \text{momentum} \cdot \text{iy} - \eta \cdot (\text{gains} \cdot \text{dy}) iy=momentumiyη(gainsdy)
  4. 更新低维表示
    y = y + iy y = y + \text{iy} y=y+iy
    并使 y y y 的均值为零。
  5. 计算和输出误差(KL散度)
    C = ∑ i , j P i j log ⁡ ( P i j Q i j ) C = \sum_{i,j} P_{ij} \log\left(\frac{P_{ij}}{Q_{ij}}\right) C=i,jPijlog(QijPij)

输出

返回:低维表示 y y y
通过这些步骤,函数在高维数据中寻找低维表示,尽可能保留数据的局部结构。

代码运行结果

在这里插入图片描述

遗留问题

  1. 关于相似度定义,KL散度,低维相似度,高斯分布,梯度下降;这些知识需要好好学一学,,
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐