【问题标题】:Fast Method for computing 3x3 symmetric matrix spectral decomposition计算 3x3 对称矩阵谱分解的快速方法
【发布时间】:2011-05-21 07:25:50
【问题描述】:

我正在开展一个项目,我基本上在 20-100 点的集合上执行数百万次 PCA。目前,我们正在使用一些遗留代码,这些代码使用 GNU 的 GSL 线性代数包对协方差矩阵进行 SVD。这可行,但速度很慢。

我想知道是否有任何简单的方法可以在 3x3 对称矩阵上进行特征分解,这样我就可以将它放在 GPU 上并让它并行运行。

由于矩阵本身很小,我不确定要使用哪种算法,因为它们似乎是为大型矩阵或数据集设计的。也可以选择对数据集进行直接 SVD,但我不确定什么是最佳选择。

我必须承认,我在线性代数方面并不出色,尤其是在考虑算法优势时。任何帮助将不胜感激。

(我现在正在使用 C++)

【问题讨论】:

  • 您需要哪些具体值?你需要特征值本身吗?因式分解?求解线性系统?更多细节可能会有所帮助。
  • 我需要 3 个特征值本身,以及最后一个特征向量。谢谢
  • 您可以使用解析方法,再加上多精度算术。它应该比基于 QR 的迭代方法更快,并且应该只包含几个分支。

标签: c++ algorithm optimization cuda linear-algebra


【解决方案1】:

使用特征多项式有效,但它往往在数值上有些不稳定(或至少不准确)。

计算对称矩阵特征系统的标准算法是 QR 方法。对于 3x3 矩阵,通过从旋转构建正交变换并将它们表示为四元数,可以实现非常巧妙的实现。假设你有一个 3x3 矩阵和一个四元数类,这个想法在 C++ 中的一个(很短!)实现可以在here 找到。该算法应该非常适合 GPU 实现,因为它是迭代的(因此是自校正的),可以在可用时合理地很好地利用快速的低维向量数学原语,并且使用相当少量的向量寄存器(所以它允许更多活动线程)。

【讨论】:

    【解决方案2】:

    大多数方法对于更大的矩阵都很有效。对于小型的,分析方法是最快和最简单的,但在某些情况下是不准确的。

    Joachim Kopp 为 3x3 对称矩阵开发了一个优化的“混合”method,该矩阵依赖于解析方法,但回退到 QL 算法。

    可以在here(对称三对角 QL 算法)中找到 3x3 对称矩阵的另一种解决方案。

    【讨论】:

      【解决方案3】:
      // Slightly modified version of  Stan Melax's code for 3x3 matrix diagonalization (Thanks Stan!)
      // source: http://www.melax.com/diag.html?attredirects=0
      void Diagonalize(const Real (&A)[3][3], Real (&Q)[3][3], Real (&D)[3][3])
      {
          // A must be a symmetric matrix.
          // returns Q and D such that 
          // Diagonal matrix D = QT * A * Q;  and  A = Q*D*QT
          const int maxsteps=24;  // certainly wont need that many.
          int k0, k1, k2;
          Real o[3], m[3];
          Real q [4] = {0.0,0.0,0.0,1.0};
          Real jr[4];
          Real sqw, sqx, sqy, sqz;
          Real tmp1, tmp2, mq;
          Real AQ[3][3];
          Real thet, sgn, t, c;
          for(int i=0;i < maxsteps;++i)
          {
              // quat to matrix
              sqx      = q[0]*q[0];
              sqy      = q[1]*q[1];
              sqz      = q[2]*q[2];
              sqw      = q[3]*q[3];
              Q[0][0]  = ( sqx - sqy - sqz + sqw);
              Q[1][1]  = (-sqx + sqy - sqz + sqw);
              Q[2][2]  = (-sqx - sqy + sqz + sqw);
              tmp1     = q[0]*q[1];
              tmp2     = q[2]*q[3];
              Q[1][0]  = 2.0 * (tmp1 + tmp2);
              Q[0][1]  = 2.0 * (tmp1 - tmp2);
              tmp1     = q[0]*q[2];
              tmp2     = q[1]*q[3];
              Q[2][0]  = 2.0 * (tmp1 - tmp2);
              Q[0][2]  = 2.0 * (tmp1 + tmp2);
              tmp1     = q[1]*q[2];
              tmp2     = q[0]*q[3];
              Q[2][1]  = 2.0 * (tmp1 + tmp2);
              Q[1][2]  = 2.0 * (tmp1 - tmp2);
      
              // AQ = A * Q
              AQ[0][0] = Q[0][0]*A[0][0]+Q[1][0]*A[0][1]+Q[2][0]*A[0][2];
              AQ[0][1] = Q[0][1]*A[0][0]+Q[1][1]*A[0][1]+Q[2][1]*A[0][2];
              AQ[0][2] = Q[0][2]*A[0][0]+Q[1][2]*A[0][1]+Q[2][2]*A[0][2];
              AQ[1][0] = Q[0][0]*A[0][1]+Q[1][0]*A[1][1]+Q[2][0]*A[1][2];
              AQ[1][1] = Q[0][1]*A[0][1]+Q[1][1]*A[1][1]+Q[2][1]*A[1][2];
              AQ[1][2] = Q[0][2]*A[0][1]+Q[1][2]*A[1][1]+Q[2][2]*A[1][2];
              AQ[2][0] = Q[0][0]*A[0][2]+Q[1][0]*A[1][2]+Q[2][0]*A[2][2];
              AQ[2][1] = Q[0][1]*A[0][2]+Q[1][1]*A[1][2]+Q[2][1]*A[2][2];
              AQ[2][2] = Q[0][2]*A[0][2]+Q[1][2]*A[1][2]+Q[2][2]*A[2][2];
              // D = Qt * AQ
              D[0][0] = AQ[0][0]*Q[0][0]+AQ[1][0]*Q[1][0]+AQ[2][0]*Q[2][0]; 
              D[0][1] = AQ[0][0]*Q[0][1]+AQ[1][0]*Q[1][1]+AQ[2][0]*Q[2][1]; 
              D[0][2] = AQ[0][0]*Q[0][2]+AQ[1][0]*Q[1][2]+AQ[2][0]*Q[2][2]; 
              D[1][0] = AQ[0][1]*Q[0][0]+AQ[1][1]*Q[1][0]+AQ[2][1]*Q[2][0]; 
              D[1][1] = AQ[0][1]*Q[0][1]+AQ[1][1]*Q[1][1]+AQ[2][1]*Q[2][1]; 
              D[1][2] = AQ[0][1]*Q[0][2]+AQ[1][1]*Q[1][2]+AQ[2][1]*Q[2][2]; 
              D[2][0] = AQ[0][2]*Q[0][0]+AQ[1][2]*Q[1][0]+AQ[2][2]*Q[2][0]; 
              D[2][1] = AQ[0][2]*Q[0][1]+AQ[1][2]*Q[1][1]+AQ[2][2]*Q[2][1]; 
              D[2][2] = AQ[0][2]*Q[0][2]+AQ[1][2]*Q[1][2]+AQ[2][2]*Q[2][2];
              o[0]    = D[1][2];
              o[1]    = D[0][2];
              o[2]    = D[0][1];
              m[0]    = fabs(o[0]);
              m[1]    = fabs(o[1]);
              m[2]    = fabs(o[2]);
      
              k0      = (m[0] > m[1] && m[0] > m[2])?0: (m[1] > m[2])? 1 : 2; // index of largest element of offdiag
              k1      = (k0+1)%3;
              k2      = (k0+2)%3;
              if (o[k0]==0.0)
              {
                  break;  // diagonal already
              }
              thet    = (D[k2][k2]-D[k1][k1])/(2.0*o[k0]);
              sgn     = (thet > 0.0)?1.0:-1.0;
              thet   *= sgn; // make it positive
              t       = sgn /(thet +((thet < 1.E6)?sqrt(thet*thet+1.0):thet)) ; // sign(T)/(|T|+sqrt(T^2+1))
              c       = 1.0/sqrt(t*t+1.0); //  c= 1/(t^2+1) , t=s/c 
              if(c==1.0)
              {
                  break;  // no room for improvement - reached machine precision.
              }
              jr[0 ]  = jr[1] = jr[2] = jr[3] = 0.0;
              jr[k0]  = sgn*sqrt((1.0-c)/2.0);  // using 1/2 angle identity sin(a/2) = sqrt((1-cos(a))/2)  
              jr[k0] *= -1.0; // since our quat-to-matrix convention was for v*M instead of M*v
              jr[3 ]  = sqrt(1.0f - jr[k0] * jr[k0]);
              if(jr[3]==1.0)
              {
                  break; // reached limits of floating point precision
              }
              q[0]    = (q[3]*jr[0] + q[0]*jr[3] + q[1]*jr[2] - q[2]*jr[1]);
              q[1]    = (q[3]*jr[1] - q[0]*jr[2] + q[1]*jr[3] + q[2]*jr[0]);
              q[2]    = (q[3]*jr[2] + q[0]*jr[1] - q[1]*jr[0] + q[2]*jr[3]);
              q[3]    = (q[3]*jr[3] - q[0]*jr[0] - q[1]*jr[1] - q[2]*jr[2]);
              mq      = sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
              q[0]   /= mq;
              q[1]   /= mq;
              q[2]   /= mq;
              q[3]   /= mq;
          }
      }
      

      【讨论】:

      • 不适用于 {1, 0.0001, 0}, {0.0001, 1, 0}, {0, 0, 1}(返回 45 度)
      【解决方案4】:

      我也不擅长线性代数,但自从墨菲说“当你不知道自己在说什么时,一切皆有可能”,CULA pack might be relevant to your needs.他们做 SVD 和特征值分解

      【讨论】:

      • 是的,我看过 CULA 包,但它是为通过并行计算解决大型矩阵而设计的。但是,我想做很多小计算,这不是我想要的:\谢谢
      猜你喜欢
      • 2021-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多