爱气象,爱气象家园! 

气象家园

 找回密码
 立即注册

QQ登录

只需一步,快速开始

新浪微博登陆

只需一步, 快速开始

搜索
查看: 13911|回复: 5

[源代码] python实现reof

[复制链接]

新浪微博达人勋

发表于 2020-8-30 16:11:51 | 显示全部楼层 |阅读模式

登录后查看更多精彩内容~

您需要 登录 才可以下载或查看,没有帐号?立即注册 新浪微博登陆

x
花了几天时间研究了下python版本的reof,抛砖引玉,时间仓促还没用真实气象数据验证,鉴于本人水平有限可能许多地方理解有出入,重在理解。
  1. import numpy as np
  2. from tqdm import tqdm

  3. #生成一个玩具数据集,840个站点1200个时次的数据
  4. x  = np.random.randint(1,20,size=(840,1200))
  5. def standardization(data):
  6.     """
  7.     标准化资料阵
  8.     """
  9.     mu = np.mean(data, axis=1,keepdims = True)
  10.     #print(mu.shape)
  11.     m = np.tile(mu,(1,data.shape[1]))
  12.     #print(m.shape)
  13.     sigma = np.std(data, axis=1,keepdims = True)
  14.     s = np.tile(sigma,(1,data.shape[1]))
  15.     fengzi = (data - m)
  16.     fengmu = s
  17.     return fengzi / fengmu

  18. #对资料阵标准化
  19. data = standardization(x)
  20. #计算协方差阵,也时相关阵
  21. covx = 1.0/x.shape[1]*np.matmul(x,x.T)
  22. #covx = np.cov(x,rowvar=1)
  23. print(covx.shape)
  24. #计算特征值和特征向量
  25. eigenvalues,eigenvecter =  np.linalg.eig(covx)
  26. #print(np.matmul(eigenvecter.T,eigenvecter))
  27. print(eigenvecter[:,0].var())
  28. #根据特征值(方差贡献排序)
  29. idx = np.argsort(eigenvalues)[::-1]
  30. eigenvecter = eigenvecter [:,idx]
  31. -----------------------------------------------------------------
  32. #根据累计方差贡献大于85%来确定待旋转的特征向量
  33. totalvar = np.sum(eigenvalues)
  34. cumsum = 0
  35. #eigvalue_sort  = sorted(eigenvalues, reverse=True)
  36. eigvalue_sort = eigenvalues[idx]
  37. #print(eigvalue_sort)
  38. for number,i in enumerate(eigvalue_sort):
  39.     cumsum += i/totalvar
  40.     if cumsum > 0.85:
  41.         break
  42. #print(number)
  43. -------------------------------------------------
  44. rotate_eigvector = eigenvecter[:,:number+1]
  45. eigvalue_sort = eigvalue_sort[:number+1]

  46. #极大方差旋转,刚性旋转,轴是正交的
  47. #1.使用wiki里的迭代解法,但无论用哪个版本都需要将特征向量转变成载荷矩阵
  48. loadings = np.dot(rotate_eigvector, np.diag(np.sqrt(eigvalue_sort)))
  49. #loadings = eVec_corr3*np.sqrt(eVal_corr3)
  50. n_rows, n_cols = loadings.shape
  51. #对loadings标准化,这里是横向给每个元素除共同度的开根也就是h
  52. normalized_mtx = np.apply_along_axis(lambda x: np.sqrt(np.sum(x**2)), 1, loadings.copy())
  53. normlize_loadings = (loadings.T / normalized_mtx).T
  54. #print(normlize_loadings[:,0].mean())
  55. #print((loadings[:,0]*loadings[:,1]).mean())
  56. #assert loadings == n]ormlize_loadings
  57. #初始化旋转矩阵,为1的对角矩阵,意味着第一次旋转相当于原地不动
  58. rotation_mtx = np.eye(n_cols)
  59. d = 0
  60. for i in range(1000):
  61.   print('process-->',i)
  62.   old_d = d
  63.   #basis应该是旋转后的载荷矩阵
  64.   basis = np.dot(loadings, rotation_mtx)
  65.   """
  66.   下面这个地方最难理解并且是最核心的地方,因为这个公式实在太抽象了我试着猜一下大概是怎么回事,这里参考我下面贴的svd旋转矩阵的链接
  67.   首先套公式argmax(x.T*y),如果载荷矩阵的转置是x的话,那么很容易猜到这个后面一长串的公式就是这个y,根据帖子中x,y都是多维空间中的点
  68.   群,x是原来的载荷矩阵,那么y就应该是更具最大方差原则处理后的载荷矩阵,并且我猜这里没有正交的约束,因为svd分解在后面,它的跳出循环的
  69.   条件应该就是不满足正交矩阵了。这个basis3次方困惑了我很久,根据
  70.   黄嘉佑老师书中的公式,可以确定的最大化方差的计算中每一个因子(特征向量)(列)中每个值都是平方的,在求方差应该
  71.   是有四次方项的,但这个y肯定包含了方差最大化的条件,所以我从公式的右半侧开始看np.diag(np.diag(np.dot(basis.T, basis)这个
  72.   最好理解它的作用是先求每个因子的方差然后对角化在除于因子(特征向量)的长度,相当于因子方差平均到每个feature(站点)上的大小
  73.   接下来就纯属于我个人理解(猜测),因子旋转的目的是让因子中的某几个feature(站点或格点)具有高载荷,它不同于pca(或eof)只需要让
  74.   方差集中在前几个因子上就行了,那么旋转的目的就是让大的更大小的更小,所以根据这个思路这个basis**3应该拆开来看,这里的三次方是逐元素
  75.   的幂,将其拆成basis*(basis**2)这里的*是哈达玛逐元素乘积,那么这个公式应该可以合并成basis*(basis**2 - 每一个因子元素的平均方差)
  76.   那么根据这个思路(如果正确的话)就比较好理解了,大于平均方差的项朝着更大的方向转,小于的则更小这也符合旋转的原则,最后在这个方向上根
  77.   据svd求出正交的部分(旋转矩阵)直到按照这个方向旋转求不出正交部分为止,这根家园fortran版本里的解法应该是相反的(我只是简单的看了
  78.   下),fortran的版本是先固定正交的条件,然后两两排列组合旋转,所以也非常好理解。
  79.   
  80.   """
  81.   transformed = np.dot(loadings.T, basis**3 - (1.0 / n_rows) * np.dot(basis, np.diag(np.diag(np.dot(basis.T, basis)))))
  82.   U, S, V = np.linalg.svd(transformed)
  83.   #下面参考svd求旋转矩阵的连接wiki里也有简要的推导
  84.   rotation_mtx = np.dot(U, V)
  85.   d = np.sum(S)
  86.   #这里的意思应该是保证对角线上奇异值的和保持不变从而保证旋转的矩阵是正交的
  87.   if old_d != 0 and d / old_d < 1 + 1e-05 :
  88.       break
  89. new_loadings = np.dot(loadings, rotation_mtx)
  90. print(new_loadings)
  91. #通过旋转后的载荷矩阵计算新的方差贡献,载荷矩阵对应的因子(模态)的总方差跟未旋转的总方差相同
  92. new_variance = np.apply_along_axis(lambda x: np.sum(x**2), 0, new_loadings)
  93. old_variance = np.apply_along_axis(lambda x: np.sum(x**2), 0, loadings)
  94. new_variance = new_variance/new_variance.sum()
  95. old_variance = old_variance/old_variance.sum()



  96. #参考链接
  97. #wiki源码链接:https://en.wikipedia.org/wiki/Talk%3aVarimax_rotation
  98. #因子分析:https://blog.csdn.net/mengjizhiyou/article/details/83241469
  99. #varimax :https://github.com/odeviron/varimax/blob/master/varimax_lib.py
  100. #https://github.com/trhgu/Varimax_Rotation_and_Thereafter_module/blob/master/Varimax_Rotation_and_Thereafter.ipynb
  101. #https://github.com/EducationalTestingService/factor_analyzer/blob/master/factor_analyzer/factor_analyzer.py
  102. #https://github.com/EducationalTestingService/factor_analyzer/blob/master/factor_analyzer/rotator.py
  103. #svd求解旋转矩阵
  104. #https://blog.csdn.net/dfdfdsfdfdfdf/article/details/53213240
复制代码
密码修改失败请联系微信:mofangbao

新浪微博达人勋

 楼主| 发表于 2020-8-30 16:17:06 | 显示全部楼层
希望能跟大家交流一下,我给程序里写了详细的注释,希望有高手指点一下我对transformed计算的那一大段的理解是否正确{:eb302:}{:eb302:}{:eb302:}{:eb302:}
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-8-30 18:05:25 | 显示全部楼层
感谢分享,可以尝试用python进行一次reof分析了。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-8-31 09:52:51 | 显示全部楼层
多谢楼主分享呀
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

发表于 2020-8-31 10:11:05 | 显示全部楼层
之前好像在关于因子分析的心理学的论文里看到过推导,似乎是直接当最优化问题来求,实在是看不懂,只能调包来用了。
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

新浪微博达人勋

 楼主| 发表于 2020-8-31 12:30:40 | 显示全部楼层
灭火器 发表于 2020-8-31 10:11
之前好像在关于因子分析的心理学的论文里看到过推导,似乎是直接当最优化问题来求,实在是看不懂,只能调包 ...

fortran版本的reof也是解最优化,包括各种气象统计书上的算法都是每次只旋转两个,但维基上的算法是直接对载荷矩阵操作,就很迷
密码修改失败请联系微信:mofangbao
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 新浪微博登陆

本版积分规则

Copyright ©2011-2014 bbs.06climate.com All Rights Reserved.  Powered by Discuz! (京ICP-10201084)

本站信息均由会员发表,不代表气象家园立场,禁止在本站发表与国家法律相抵触言论

快速回复 返回顶部 返回列表