【转】使用R的统计学习:算法与实践(一):MDS

标签:
r语言 |
我打算以这本书的框架写一些读书笔记。一方面用来串一下自己的统计和机器学习的知识,
另一方面可以由此系统一下R在多元统计和统计学习的应用。
——————————————— 书评的分割线——————————————————
1.MDS简介
多维标度法(Multidimensional
Scaling),是一种维数缩减方法,把高维的数据点映射到一个低维的流形上;同时也是一种可视化方法,实践中通常利用2D或3D的MDS
结果观察(投影后)点的分布和聚集来研究数据的性质。
以及地理学,生态学,天文学,分子生物学,计算化学,图形学甚至流行音乐研究等等。
MDS的思想方法在随机森林中也起着重要的作用,同时启发了非线性流形学习。
一个简单的例子和一些术语
一个经典的例子。关于MDS的易于理解的经典案例往往来自地理方面(本例最早来自文献[4]):
关于美国十个城市间飞行里程的数据。这十个城市是:亚特兰大(Atlanta)、芝加哥(Chicago)、丹佛(Denver)、休斯顿(Houston)、洛杉矶(Los
Angeles)、迈阿密(Miami)、纽约(New
York)、旧金山(Sanfran)、西雅图(Seattle)和华盛顿(Wash•DC)。用MDS分析这个数据集,绘出一张“真实”的地图。数据取自http://lem.gdcc.edu.cn/jiaox/epd/contents/softstudy/sas/jlfx01-left1.HTM。
把数据按照表格中的形式整理为csv文件。使用R函数cmdscale(stats包,使用经典标度)
city<-read.csv('airline.csv',header=TRUE)
city1<-city[,-1]#这个数据集的第一列是名字,先把它去掉
for (i in 1:9)
for(j in(i+1):10)
city1[i,j]=city1[j,i] #把上三角部分补足
rownames(city1)<-colnames(city1) #再把行名加回来
city2<-as.dist(city1, diag = TRUE, upper = TRUE)#转换为dist类型
city3<-as.matrix(city2) #转化为矩阵
citys<-cmdscale(city3,k=2)
#计算MDS,为可视化,取前两个主坐标plot(citys[,1],citys[,2],type='n') #绘图
text(citys[,1],citys[,2],labels(city2),cex=.7)#标上城市名字
看看图和真实的地图方向(东西海岸)是反的阿,没关系,修改一下:
plot(-citys[,1],citys[,2],type='n')
text(-citys[,1],citys[,2],labels(city2),cex=.7)
现在就是真实地图的结构了。
下面用ggmap包画张地图,对比一下:
MDS建立于实体(entity)的成对比较之上。实体的接近程度称为“邻近”(Proximity),
邻近可以是定量的,如真实实体的物理距离,对顺序尺度(ordinal
scale)的主观判断,也可以是定性的,如在心理测量中关于相似或者相异程度的主观评分。
设δ[i,j]表示i,j两个实体的相异程度,对全部的n个实体X[1]…X[n],我们可以得到矩阵
Δ =(δ[i,j]),这称为距离矩阵(Proximity
Matrices)。这个矩阵是通常写为一个下三角的非负的矩阵(主对角线一般为0)。在可度量(metric)距离之下,满足三角不等式。(注:在某些心理测量实验中,距离矩阵可能是非对称的,一种调整的方法是取对角线两侧的对应元素均值,使之对称)。关于距离矩阵的构建在某些问题里可能相当复杂。可参考[2]。
MDS的目的是寻找一个低维( t维)空间,其上对应n个实体的点为Y[1],…,Y[n],
他们的距离d[i,j]与δ[i,j]尽量接近。Y
称为MDS的一个解,在进行可视化时也可以称为拟合构图。所以,MDS是在寻找关于解的优化算法。
2.经典标度法(Classical Scaling)
经典标度法是MDS系列算法中最早出现的一种。尽管经典标度法可视为最小二乘标度法在某种情形下的一个特例,探讨经典标度法的算法仍有助对MDS的理解。
关于经典标度法的算法如下:(读书笔记嘛,直接截取书里边的内容)
关于算法的细节可以参考[1],[3]。
说明:B =
HAH,来源于对观察距离δ[i,j]的平方和展开。这个算法就是特征值分解,其思想和主成分分析(PCA)非常相似。在欧氏距离之下,这种算法求得的主坐标(principal
coordinates)相当于PCA中前t个主成分的得分。
关于维度的选取
一种常用方法是按照矩阵B的特征值的结构来选择维数。(类似于PCA对主成分的选择)把正特征值按从大到小排列,变得稳定即可。当然处于可视化的考虑,一般都是选择维数为2或3。
下面继续美国城市航线的例子,看看经典标度的效果:
a<-cmdscale(city3, k = 2, eig = TRUE)#结果是一个列表
#观察其特征值(矩阵B的特征值):
a$eig
#绘原始距离和估计距离的散点图
city2d<-data.frame(a[1])#把前两个主坐标取出
city2d11<-dist(city2d, method =
"euclidean",diag=FALSE)#生成距离矩阵
city2dv1<-as.vector(city2d11)
city2<-as.dist(city1, diag = FALSE)
city2v<-as.vector(city2)
c<-data.frame(city2v,city2dv1)
library(ggplot2)
p<-ggplot(data=c,aes(x=city2v,y=city2dv1))+
geom_point(pch=19,colour='red',size=2,alpha=.6)+
stat_smooth(method=lm)
update_labels( p,list(x="Observed Distance",y="Estimated
Distance"))
#绘残差图
res<-c[,1]-c[,2]
d=data.frame(res,x=c(1:45))
ggplot(data=d,aes(y=res,x=x))+
geom_point(pch=19,colour='red',size=2,alpha=.6)+
geom_text(data=d,colour="black",aes(x=x,y=res+10,label=x,fontface=1),size=3)
得到原始距离和由前2个主坐标得到的拟合距离的散点图和残差图,拟合的效果还可以。明显的过拟合和拟合不足的点基本都和丹佛有关,哪位熟悉美国航线的给解释一下?
3.距离标度(Distance Scaling)
距离标度中,降维之后的距离d[i,j]≈f(δ[i,j]),其中f是一个单调增函数。
距离标度可分为度量距离标度(metric distance
scaling):相异度是定量的(比例或区间)和非度量距离标度(nonmetric distance
scaling):相异度是定性的(比如顺序)。
(1)度量距离标度
度量距离标度
在度量距离标度中,单调函数f是一个参数化的函数,比如有这样的形式:
f(δ[i,j])=a+bδ[i,j],(a,b是参数)。对这样的形式,很自然的会采用最小二乘法使用{d[i,j]}来对f(δ[i,j])进行拟合。这就是度量最小二乘标度(Metric
Least-Squares Scaling)。通常使用的是加权的最小二乘的形式,寻找t(
,
其平方根
称为压力函数(stress function)。其中W=(w(i,j))为权重矩阵。
极小化压力函数,可得到MDS的优化解。这样就可以保证尽可能的保持每对观测之间的距离。求解一般采用梯度下降算法。
经典标度和最小二乘标度的关系
当采用欧氏距离且f为恒等函数时,经典标度可视为最小二乘标度的一个特殊情况。
从最小二乘的观点,经典标度事实上采用了中心化的内积(参见上一小节经典标度算法的第一步),从而存在一个可以用特征向量表示的显示解。
Sammon映射
最小二乘标度的一个变种是Sammon映射(Sammon, 1969)(文献在这里可以找到http://en.wikipedia.org/wiki/Sammon_mapping),其中权重取为
且单调函数f为恒等函数。Sammon映射可以保持较小的距离δ[i,j],在拟合中给以较大的权重。这样在识别数据的聚类时有更好的效果。求解Sammon
mapping采用迭代数值方法(拟牛顿迭代,pseudo-Newton iterative
procedure),通常把经典标度解作为初始迭代值。[6]
度量标度的例:劳埃德银行的雇员(Lloyds Bank Employees)数据
这个例子最早来自[5],该文的作者研究的是类似劳埃德银行这样的英格兰银行是如何从1900年代早期的稳定的员工安排变化成如今高度动态的职业类型。
本书提供的这个数据集可由http://astro.temple.edu/~alan/MMST/datasets.html下载。因为原数据集的记录庞大结构复杂,本书选取的是1905-1909以及1925-1929两个时间段入职的劳埃德员工的职业记录。关于数据结构,在下载页有文件说明,把这个数据集转化为距离矩阵采用的是文献[5]中的匹配方法。关于这个方法,在本章最后我专文讨论。在这里先用本书作者给出的距离矩阵samp05d(1905-1909时间段)看一下度量标度的效果。
s05d<-read.csv('samp05d.csv',head=TRUE)
s05d1<-s05d[,-1]
colnames(s05d1)<-c(1:80)
rownames(s05d1)<-colnames(s05d1)
s05dmds<-cmdscale(s05d1,k=2)#求经典标度
下面用MASS包的sammon函数求sammon映射
s05d1<-as.matrix(s05d1, diag = TRUE, upper =
TRUE)#sammon要求对称阵或dist class
for(i in(1:80))
{for(j in(1:80))
{if((i!=j)&(s05d1[i,j]==0))
s05d1[i,j]=0.00000001}}#sammon要求输入矩阵非对角线不能为0
library(MASS)
s05dsa<-sammon(s05d1,y=s05dmds,k = 2, niter = 100, trace =
TRUE,magic = 0.2, tol = 1e-4) #求sammon映射,使用默认参数
画图比较结果:
s05dsad<-s05dsa[1]#双层中括号
par(mfrow=c(1,2))
plot(s05dmds[,1],s05dmds[,2],type='p',col='blue',pch=19)
#绘图text(s05dmds[,1],s05dmds[,2]+.1,labels(colnames(s05d1)),cex=.7)
plot(s05dsad[,1],s05dsad[,2],type='p',col='blue',pch=19)
text(s05dsad[,1],s05dsad[,2]+.1,labels(colnames(s05d1)),cex=.7)
从结果看,比对雇员数据,这些银行雇员可分为两个集群右图(sammon映射的结果)数据的结构情况看得更明显,其中29,30,57这三个点是比较明显的离群点,对比数据,他们分别为1587,
1590,
3240号观测,其中1587号(29)在银行工作了59年(最长),而1590(30),3240(57)号只在银行工作了2年(最短)。
图有点小,各自来一张。
http://img3.douban.com/view/note/large/public/p7960247.jpg |
经典标度,明显分为两个集群。
|
http://img5.douban.com/view/note/large/public/p7960248.jpg |
sammon映射。保持了集群,最左边是29右上角是30和57
|
(2)非度量距离标度
Shepard diagram
当相异度是定性的次序量,采用非度量标度。此时,就是寻找一个t维空间上的结构
Y={Y[1],…,Y[n]},用它们的内点距离的大小次序来拟合原始相异度的大小次序。
设对称的相异度矩阵为(δ[i,j]),(其主对角线为0),把其中的相异度按从小到大的顺序排列:
δ[i1,j1]<…<δ[im,jm], (m=n(n-1)/2)
对给定的t,希望t维空间上点的结构Y的内点距离(不一定是欧氏的)能保持原r维空间的相异次序。即d[i1,j1]<…
但事实上,这一点不是总可以做到的。因此,需要对距离d进行修正,变成距离d^,使d[i,j]≈d^[i,j],使d^[ik,jk],k=1,…,m单调不减。这一种近似的替代,称为“不一致”(disparities)。
因此,事实上,我们需要作的是找到一个保持单调性的任意函数f,f满足:
当δ[i,j]< δ[k,l] 时, f(δ[i,j])小于等于f(δ[k,l])
使得d^[i,j]=f(δ[i,j])。
这些disparities叠加在针对次序的结构距离图上是,连起来形成一条曲线,称为Shepard diagram[7]。
进行计算的方法常见的是保序回归(isotonic regression,Kruskal,1964[8,9])。
关于保序回归,在此不赘述,请参考http://en.wikipedia.org/wiki/Isotonic_regression。
在R的stats包有函数isoreg,可以计算保序回归,看一个简单的例子:
a<-c(2.3,2.7,8.1,5.7,6.2,8.1,8.6,7.7,6.8,9.3,10.5,9.8,10,12.6,12.8)
a.ir<-isoreg(a)
plot(a.ir,col='blue')
图上蓝色的点为初始的向量a,连线即为保序回归拟合的结果。经过保序回归之后,原来不单调的序列a,变成了单调不减的序列:
> a.ir$yf
[15] 12.800000
非度量标度的求解
Shepard diagram的残差平方和:
因残差平方和不能对结构{Y}的尺度伸缩保持不变,因此需要对其进行修正,采用加权形式:
权重常取为:
即有
这称为Kruskal压力公式。也即非度量标度的拟合优度的损失函数。
压力函数还有其他的形式。
利用这个损失函数,一般采用基于梯度的优化方法求非度量标度的解。
算法如下:
但需要注意的是,基于梯度的方法并不一定得到全局的最优解。因此,实践上应尝试不同的初始迭代结构,以检验算法的收敛性质。
关于MDS解的评价
Kruskal给出了关于评价MDS解的一个经验方法:按照stress的值来进行评价:
但是,这个方法并非适用所有的问题。
关于维数的选择
MDS本质上是个维数缩减方法,对于变化的t,t越大,stress越小。
选择维数的一个方法是碎石图(scree
Plot),类似于PCA或者CA选择主成分(因子)的方法,把对应每个t值的最小stress画出来,当stress值变化变平缓的时候,可视为合适的t值。
当然,一般出于可视化的考虑,t常取为2或3。
R的MASS包有函数isoMDS,采用上面所述的Kruskal方法求解非度量标度。
例:先看一个简单的例子,前面我们用过的美国城市航线的例子。
非度量标度当然也可用于度量标度的情形,只要把实体的连续的距离看作表示差异的次序值。
整理数据的过程略,得到dist类city2
library(MASS)
city.nmds<-isoMDS(city2, y = cmdscale(city2, 2), k = 2, maxit =
50, trace = TRUE,
和isoMDS相伴随的还有一个表示保序回归拟合的函数Shepard
Shepard(city2, x=city.nmds$points, p = 2)#需要输入最后的迭代解
(1)基于梯度算法的R函数
作为一种应用领域相当广泛的方法,MDS在不同的R包中,以不同的函数来解决。这些函数大多采用基于梯度的优化算法。有些在前面的例子中已经尝试过,不再一一举例。
经典标度:
cmdscale( ) stats包
wcmdscale( ) vegan包:加权的经典标度
pco( ),ecodist包
pco( ),labdsv包
pcoa( ),ape包
dudi.pco( ),ade4包:后四种方法采用主坐标分析(经典标度的另一种表述),这几个包和生态,进化,遗传有关。
度量标度:
sammon映射:sammon(),MASS包
非度量标度:
isoMDS(),MASS包
metaMDS(),vegan包
monoMDS(),vegan包(全局和局部的非度量标度,局部的多维标度在非线性维数缩减部分介绍)
保序回归:
isoreg(),stats包
除了基于梯度的优化方法,近来在MDS领域,也在发展基于其它优化算法的MDS解法。
SMACOF(Scaling by MAjorizing a COmplicated Function)方法就是其一。SMACOF方法使用优化控制majorixation algorithm来最小化压力函数(http://en.wikipedia.org/wiki/Stress_majorization。 或参考[10])。
smacof包是基于SMACOF方法,专门用来求解MDS的R包。同时按照数据结构,给出几种不同的MDS问题的解法。
几种MDS的表现形式:
1-way vs. multi-way MDS: 在K-way MDS中,每个目标对有来自不同“回答”的K个相异度的测量(如重复测量或多重评价人)。
.1-mode vs. multi-mode MDS: 和上边的区别相似,但K个相异度是定性的区别
每种MDS版本都有对应的度量或非度量标度的变体。关于此包的详细算法可参考[10]
smacof包的基本函数包括:
smacofSym( ):基本(对称)SMACOF
smacofRect:rectangular SMACOF。
smacofIndDiff( ):3向数据,
smacofConstraint ( ) :带外部约束的SMACOF
smacofSphere.primal( ) ,smacofSphere.dual( ) : 球面投影。
相关的图形输出包括:con_guration plot, Shepard diagram, residual plot, and stress decomposition diagram等。
几个例子
(a)SMACOF的基本解:Ekman's color data
这是MDS的一个经典案例,Ekman(1954)针对14种颜色(波长从434到674),由31位受试者按照两两配对的相似程度进行测量,相似程度分为5个等级(0=完全不相似,4=一致)。31位受试者的数据平均后,相似程度被除以四。
smacof包带有这个数据集的相似矩阵,下面的代码取自此包的自带案例:
library(smacof)
data("ekman")
ekman.d <- sim2diss(ekman, method = 1)#把相似阵转化为相异阵
res.basic <- smacofSym(ekman.d, metric = FALSE)
plot(res.sphere, main = "Configurations Sphere SMACOF")
smacofSym( )是2维SMACOF的基本解,采用经典标度作为默认的初始结构(也可以自定义)。smacofSphere.primal 是二次曲面SMACOF,投影到球面结构上。计算都采用非度量标度。
(b) rectangular SMACOF:早餐评级数据 (Breakfast rating)
矩形SMACOF解决的是metric unfolding model。这种模型针对偏好选择问题(preferential choice)。它假设不同的个人可以按相同的方式感受多样的对象区别在于他们所考虑的对象属性的理想组合。这些数据可以看作是个体和选择对象之间的邻近矩阵,即部分邻近矩阵有缺失数据的MDS的特殊形式。这种形式如下图所示:
早餐评分数据,来自[4],samcof包自带数据集。
42个人根据他们的偏好对15种早餐进行评分。
library(smacof)
data("breakfast")
res.rect <- smacofRect(breakfast, itmax = 1000)
plot(res.rect, joint = TRUE)
图形同时表现了早餐类型和评分者的结构。
(c)3D spherical SMACOF:世界城市航线
世界18个城市的航线距离,在samcof包中采用dual算法生成球面投影,并用3D图表示出来。考虑到航线其实是在地球表面的曲线,使用欧氏距离的MDS结构必然会导致拟合的较大偏差。数据来自[1]
数据整理的部分略,得到dist类矩阵globe.dist
#经典标度
globe.cmds2d<-cmdscale(globe.dist,k=2)
globe.cmds3d<-cmdscale(globe.dist,k=3)
##画2D,3D图
color<-c('purple','brown','purple','purple','blue','black','red','red','blue',
'purple','red','blue','orange','blue','red','purple','blue','purple')
plot(globe.cmds2d[,c(1:2)],type='n')
text(globe.cmds2d,labels(globe.dist),cex=.7,col =color)
plot3d(globe.cmds3d,type='p',col =color,size=10,pch=16)
#Asia (purple), North America (red), South
##America (yellow), Europe (blue), Africa (brown), and Australasia (orange).
#球面投影
globe<-globe/15349 #(用最大值)归一化,否则球面画不出
globe.dist<-as.dist(globe)
res.sphere <- smacofSphere.dual(globe.dist, ndim = 3)
3维图都是可旋转的。
例1:Morse code data
摩尔斯电码的基本成分是36个有点和划构成(依次对应26个字母和0-9这10个数字)。
为了检验这些电码的易混淆程度,Rothkopf在1957年做了一个实验,对598名应试者采用成对的方式播放这36个基本电码,让受试者判断代码相同或者不同,按照判断相同的比例,得到了一个邻近矩阵。
这个矩阵可以在这里下载http://astro.temple.edu/~alan/MMST/datasets.html.
这个问题的复杂性在于,这个矩阵不是对称的,主对角线元素也不为1(对同一组里两个相同代码也会有错误的判断)。需要按照某种方法把它转化为对称阵。一个可以考虑的方法是把新邻近矩阵取为原矩阵和它的转置矩阵和的一半。
morse<-read.csv('morse.csv',head=TRUE)
rownames(morse)<-colnames(morse)
morse.a<-(1/2)*(morse+t(morse))
for(i in(1:length(morse.a[,1])))
morse.a[i,i]=1
morse.m<-as.matrix(morse.a)
##使用smacof包,这是非度量标度的情况。
library(smacof)
morse.d <- sim2diss(morse.m, method = 1)#相似阵转化为距离阵
res.basic <- smacofSym(morse.d, metric = FALSE)
plot(-res.basic$conf[,1],-res.basic$conf[,2],type='n',col='blue',xlim=c(-1.2,1.2),ylim=c(-1.2,1.2)) #绘图text(-res.basic$conf[,1],-res.basic$conf[,2]+.1,labels=colnames(morse),cex=.7)
#画图对主坐标取负值的目的是保持和本书的图形一致
结论是带有很多划的长代码比带有点的短代码更容易混淆。分析的细节参考[2]
例2
美国国会投票数据
本例来自Machine Learning for Hackers,第九章。作者分析这个案例的意图是希望通过美国国会参议院的议员投票的结果使用MDS寻找是否美国国会中是否存在共和党和民主党的分界。数据简化方式按此书中的方法,此处略,主要看MDS的结果,只把图形的代码放在这里(比原文有点修改)。
library(ggplot2)
head(rollcall.mds[11])
cong.110 <- rollcall.mds[10]#原书这里有错,10对应110届国会
base.110 <- ggplot(cong.110, aes(x=x, y=-y))#为了把奥巴马参议员放到上方:)
base.110+geom_text(aes(color=party, label=as.factor(cong.110$name)) ,alpha=0.75, size=2)+
theme_bw()+
scale_color_manual(name="Party", values=c("100"="red","200"="blue",
"328"="grey"),labels=c("Dem.", "Rep.", "Ind."))+
ggtitle('Roll Call Vote MDS Clustering for 110th U.S.Senate')
http://img3.douban.com/view/note/large/public/p7988784.jpg |
左边是民主党右边是共和党(貌似颜色搞反了:()
|
http://img3.douban.com/view/note/large/public/p7988821.jpg |
局部,左一民主党的奥巴马,右一共和党的麦凯恩
|
把101届到111届国会的情况都看一下
all.mds <- do.call(rbind, rollcall.mds)
all.plot <- ggplot(all.mds, aes(x=x, y=y))+
geom_point(aes(shape=party, alpha=0.75), size=2)+
scale_alpha(legend=FALSE)+theme_bw()+
scale_shape(name="Party", breaks=c("100","200","328"),
labels=c("Dem.", "Rep.", "Ind."),
solid=FALSE)+facet_wrap(~ congress)
all.plot
因为采用了同样的坐标系来画这11个图,这里反应的并非真实的结构,而是相对的结构,不能由此得到演化的结论。
结论是两党在参议院确实泾渭分明,而且为时已久。
同样方法,可以看看地域的影响(参议院所在的州)