1.用MDS的场景
从降维的层面来说,由于MDS是一种降维方法,那么它和PCA等其他降维方式有什么不同呢,什么样的场景适用于使用MDS呢?与PCA不同的是,MDS保证了原始数据点之间的距离与降维后数据点的距离一致。
另一方面,假如我们只知道一组点之间的距离,我们如何反演出它的相对坐标?这也是MDS可以做到的。
MDS的这种降维是继承了原始数据空间中的欧式距离度量。使得数据点能在低维空间重构其相对位置。
举个例子,比如如果用欧式距离度量两种商品的相似度,那么,商品特征繁多,我们就需要在不破坏商品之间的这种相似度的情况下对数据进行降维。
再比如我们手头只有一个邻接矩阵,代表飞机坠机后的残骸之间的距离,现在新来了一个残骸,多个声呐探测后能够计算出这个残骸与其他所有残骸的距离,但是我们无法得知其具体位置,那么我们可以通过MDS反演出其相对距离,那么我们只要知道其中一个残骸的位置,这个残骸的位置也就很明朗了。根据这个相对距离缩放到真实距离就行。
2.MDS算法的数学推导
定理:在维欧式空间中有一组点集,这组点集之间的欧式距离由矩阵给出,即令定义一个
在如上定义之下,当且仅当是半正定矩阵,是欧式距离矩阵。
我们现在给出构造性证明:
首先是必要性:
即:
如果是点集的欧氏距离矩阵;那么:
证明:
矩阵A的列平均:
矩阵A的行平均:
矩阵A的整体平均:
那么:
所以:
由此可得,B一定是一个对称半正定矩阵,必要性证毕。
接着证明充分性:
即:
如果是秩为的对称半正定矩阵,那么可以将按照如下方式构造:
令表示矩阵的所有大于0的特征值,且对应的特征向量为:;
那么在维欧式空间中,点集之间的距离可由距离矩阵给出。
证明:
假设特征值对应的矩阵为:
令因为
那么;
所以可以将特征分解为:
即:;
我们算一下第个特征向量和第个特征向量的欧式距离,以确定其确实可以由距离矩阵给出。
因为
那么
那么
且:
所以:
这恰好证明了那么在维欧式空间中,点集之间的距离可由距离矩阵给出。充分性证毕。
那么也就是说只要是半正定的矩阵,那么我们总能找到它的特征向量,将对应的坐标求出。
我们还能发现一些好玩的性质,比如因为所以:
由于不同特征值对应的特征向量是正交的,那么这个式子就告诉我们是其中一个特征向量。且对应特征值为0。
也就是说:
这表明实际上,我们在保持其欧式距离结构的前提下,将坐标原点定在了所有点的均值处。
回顾一下我们做了什么,我们有一个距离矩阵,这个距离矩阵的来源可以是我们手上有一堆已有数据产生的,或者是直接给出的;我们试图在低维或者同维度下重构它的数据点,我们首先通过矩阵找到矩阵,矩阵构成矩阵,由于距离矩阵一定是对称半正定的,矩阵一定也是对称半正定的。由于是对称的,所以我们一定可以对它进行特征分解:;如果此时需要在低维下重构,那么对特征值排序后剔除小的,如果在同维度下重构,那么保持原样即可。我们最后就直接还原出其相对坐标:。
3.MDS中加入新数据点的问题
我们要是新来一个数据点,或者说新来一点,我只是知道它与其他所有点的距离,这样的情况怎么办。
我们可能会不假思索的回答说,这个很简单啊,只要把点加入矩阵D中,由变成就好了,其他重头再来一遍得到一个新的坐标点集就好了。
在数据量不是很大的情况下,这确实不失为一种好方法,但是万一数据量很大,重头再来的代价就让人有点头疼了,这就是为什么我们要看看有什么办法可以绕过这样重头再来的过程。
假设我们新的数据点是
我们得计算一下它和已有的第个点之间的距离:
这里面我们什么是已知的,什么是未知的呢?
是我们未知的,其他都是已知的。
我们对求和,看看是否能将一个未知量给表示成另一个未知量。
由于上面我们有说过:
所以:
因此:
将这个结果代入原式:
我们令
写成矩阵形式:
因为
所以新加入的数据点可以由下面公式导出:
4.代码
import numpy as np
class MDS:
def __init__(self,Distance_Matrix,q):
self.D=Distance_Matrix
self.q=q
self.n=np.shape(self.D)[0]
self.A=(-1/2)*self.D
self.H=np.identity(self.n)-(1/self.n)*np.ones([self.n,self.n])
self.B=self.H.dot(self.A).dot(self.H)
def rebuild_coordinates(self):
self.lambda_,self.U=np.linalg.eig(self.B)
Largest_q_EigenValues=self.lambda_[np.where(self.lambda_>0)]
Largest_q_EigenVector=self.U[np.where(self.lambda_>0)]
Largest_q_EigenValues=Largest_q_EigenValues[np.where(np.argsort(Largest_q_EigenValues)<self.q)]
Largest_q_EigenVector=Largest_q_EigenVector[np.where(np.argsort(Largest_q_EigenValues)<self.q)]
self.Lambda=np.diag(Largest_q_EigenValues.real)
self.U=Largest_q_EigenVector.T
self.Z=self.U.dot(self.Lambda**(1/2)).real
return self.Z
def add_new(self,distance_to_everypoint):
Z=self.rebuild_coordinates()
d_i_square=np.array([np.sum(Z**2,axis=1)]).T
alpha=d_i_square-distance_to_everypoint.T
new_z=(1/2)*np.linalg.inv(self.Lambda).dot(Z.T).dot(alpha)
return new_z
if __name__ == '__main__':
from sklearn.datasets import load_iris
import seaborn as sns
import matplotlib.pyplot as plt
def calculate_distance_matrix(X):
D = []
for every_point in X:
dis = np.sum((X - np.array([every_point])) ** 2, axis=1)
D.append(dis)
D = np.array(D)
return D
def plot(dfData):
plt.subplots(figsize=(9, 9))
sns.heatmap(dfData, annot=True, vmax=1, square=True, cmap="Blues")
plt.show()
data=load_iris()
X=np.array([[1,2],[3,4],[2,2],[0,1],[1,3],[5,4]])
new_point=np.array([[1,4]])
new_distance=np.array([np.sum((X - new_point) ** 2, axis=1)])
D=calculate_distance_matrix(X)
mds=MDS(D,2)
Z=mds.rebuild_coordinates()
new_z=mds.add_new(new_distance)
print(Z)