学习日记_241027_高维数据可视化,t-分布邻域嵌入(t-SNE)
学习日记,高维数据可视化,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-分布邻域嵌入)是一种非线性降维技术,尤其适合用于高维数据的可视化。它通过将数据点嵌入到二维或三维空间,保留高维空间中相似点的距离关系。
数学表达
-
相似度定义
- 在高维空间中,对于两个数据点 x i x_i xi 和 x j x_j xj,定义其相似度为条件概率 p j ∣ i p_{j|i} pj∣i,表示在给定 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)} pj∣i=∑k=iexp(−∥xi−xk∥2/2σi2)exp(−∥xi−xj∥2/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=2npj∣i+pi∣j
- 在高维空间中,对于两个数据点 x i x_i xi 和 x j x_j xj,定义其相似度为条件概率 p j ∣ i p_{j|i} pj∣i,表示在给定 x i x_i xi 的情况下选择 x j x_j xj 的概率。通常使用高斯分布来计算:
-
低维空间相似度
- 在低维空间中,使用 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+∥yk−yl∥2)−1(1+∥yi−yj∥2)−1
- 在低维空间中,使用 t-分布计算相似度 q i j q_{ij} qij:
-
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
- t-SNE 通过最小化高维和低维分布之间的 Kullback-Leibler 散度来找到最优嵌入:
-
梯度下降
- 使用梯度下降法最小化 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个维度)进行求和。)
- sum_x=np.sum(np.square(x),1):
- 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
- 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×10−12
- 调整概率:
prob j = max ( prob j , 1 × 1 0 − 12 ) \text{prob}_j = \max(\text{prob}_j, 1 \times 10^{-12}) probj=max(probj,1×10−12)- 设定困惑度:
perp = − 12 \text{perp} = -12 perp=−12
情况 2: sum_prob ≥ 1 × 1 0 − 12 \text{sum\_prob} \geq 1 \times 10^{-12} sum_prob≥1×10−12
- 计算困惑度:
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,从而得到有效的概率分布。
相关拓展
- 相似性概率
相似性概率是用于度量数据点之间相互关系的概率分布。在许多机器学习和数据处理算法中,尤其是基于距离的嵌入方法中(如t-SNE),相似性概率用于将高维数据映射到低维空间。
计算过程
- 距离计算:
- 首先计算数据点之间的距离 dist ( x i , x j ) \text{dist}(x_i, x_j) dist(xi,xj)。
- 高斯核:
- 使用高斯核来将距离转换为相似性度量:
p j ∣ i = exp ( − β ⋅ dist ( x i , x j ) ) p_{j|i} = \exp(-\beta \cdot \text{dist}(x_i, x_j)) pj∣i=exp(−β⋅dist(xi,xj))
其中, β \beta β 是一个参数,控制高斯函数的宽度。它通常与数据点的局部密度有关。
- 使用高斯核来将距离转换为相似性度量:
- 排除自身的影响:
- 为避免自我相似性影响,设置 p i ∣ i = 0 p_{i|i} = 0 pi∣i=0。
- 归一化:
- 将所有相似性度量归一化为概率分布:
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}} pj∣i=∑k=ipk∣ipj∣i
这样,所有与点 i i i 相关的概率总和为1。
- 将所有相似性度量归一化为概率分布:
作用
- 局部邻域的评估: 相似性概率用于评估点在其局部邻域内的关系。较高的概率意味着两个点在高维空间中更为接近。
- 数据嵌入与降维: 在降维算法中,相似性概率帮助维持局部结构,将其从高维空间传递到低维空间。
- 聚类分析: 在聚类算法中,相似性概率用于识别和划分数据点群体。
应用
相似性概率在机器学习中的多个领域有应用,包括:
- t-SNE: 通过保持相似性概率在高低维空间的一致性,进行有效降维。
- 谱聚类: 使用相似性概率构建图的拉普拉斯矩阵,以识别数据中的聚类结构。
总之,相似性概率是一个关键概念,帮助算法在不同维度间保持数据的本质结构。
- 困惑度
困惑度(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)=−i∑pilog2pi
解释
-
直观理解: 困惑度可以被看作是对概率分布的平均不确定性的一种度量。较高的困惑度表示分布较为分散,较低的困惑度表示分布较为集中。
-
语言模型: 在自然语言处理中,困惑度用来评估语言模型的性能。模型生成文本的困惑度越低,说明模型对数据的预测越好。
应用
-
语言模型:
- 衡量模型对测试文本的预测能力。
- 通过计算生成序列的概率,来评估模型的好坏。
-
降维与聚类:
- 在t-SNE等算法中,困惑度用于平衡局部和全局数据结构,调节数据点邻域的广度。
-
信息检索与排序:
- 评估搜索算法和推荐系统的效果。
计算示例
假设有一个概率分布 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
,这可能是为了防止数值不稳定或除以零的问题。
以下是可能的原因:
- 防止除零:如果
sum_prob
过小,可能会导致后续计算中出现除以零的情况,导致数值不稳定。 - 标识异常情况:使用
-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
- dist=cal_pairwise_dist(x)
计算点对之间的距离平方的矩阵 dist- 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(j∣i)
P ( j ∣ i ) P(j|i) P(j∣i) 表示在给定数据点 i i i的情况下选择数据点 j j j的条件概率。- beta=np.ones((n,1))
初始化 β \beta β:
β = [ 1 1 ⋮ 1 ] \beta = \begin{bmatrix} 1 \\ 1 \\ \vdots \\ 1 \end{bmatrix} β= 11⋮1 - base_perp=np.log(perplexity)
确定目标困惑度- 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βimin∣Perplexity(P(i∣j;β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(j∣i)=∑k=iexp(−βi⋅dist(xi,xk))exp(−βi⋅dist(xi,xj))具体步骤为:
- 计算当前困惑度差异:
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(i∣j;βi))−Perplexity_target- 调整 β 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
- 更新上限或下限:
- 如果 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_diff∣≤tol 或达到最大尝试次数(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
- 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。
- 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:
增益的最小值,用于防止更新步长过小。增益用于调整学习率,以适应梯度变化。- 这些参数需要根据具体数据集进行调整,以获得最佳结果。
- 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: 高维空间中点对的联合概率矩阵,经过对称化和归一化处理。
- 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,1e−12):- 将 P P P中的每个元素与 1 e − 12 1e-12 1e−12进行比较,确保所有元素至少为 1 e − 12 1e-12 1e−12。这是为了避免对数计算中的零值问题(如计算 KL 散度时 log ( 0 ) \log(0) log(0)是未定义的)。这样可以提高数值稳定性。
- for iter in range(max_iter):
- 计算相似性矩阵:
num = 1 1 + ∥ y i − y j ∥ 2 \text{num} = \frac{1}{1 + \|y_i - y_j\|^2} num=1+∥yi−yj∥21
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。这样做通常是为了避免在后续的计算中出现数值上的不稳定。- 计算梯度:
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∑((Pij−Qij)⋅numij⋅(yi−yj))
关于*np.tile(PQ[:,i]num[:,i],(no_dims,1))
将数组PQ
的第i
列与数组num
的第i
列对应的元素相乘,得到一个一维数组。然后将这个一维数组在垂直方向上重复no_dims
次形成一个新的二维数组,而水平方向上保持不变。- 更新动量和增益:
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))+(gains⋅0.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=momentum⋅iy−η⋅(gains⋅dy)- 更新低维表示:
y = y + iy y = y + \text{iy} y=y+iy
并使 y y y 的均值为零。- 计算和输出误差(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,j∑Pijlog(QijPij)输出
返回:低维表示 y y y。
通过这些步骤,函数在高维数据中寻找低维表示,尽可能保留数据的局部结构。
代码运行结果
遗留问题
- 关于相似度定义,KL散度,低维相似度,高斯分布,梯度下降;这些知识需要好好学一学,,

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