Performance 加权Gram-Schmidt正交化的MATLAB优化

Performance 加权Gram-Schmidt正交化的MATLAB优化,performance,matlab,optimization,linear-algebra,vectorization,Performance,Matlab,Optimization,Linear Algebra,Vectorization,我在MATLAB中有一个函数,它通过对内积应用非常重要的权重来执行计算(我认为MATLAB的内置函数不支持这一点)。 就我所知,这个函数工作得很好,但是,它在大型矩阵上太慢了。 改善这一点的最佳方法是什么 我尝试过转换成一个MEX文件,但我失去了与我正在使用的编译器的并行化,因此速度会变慢 我想在GPU上运行它,因为元素乘法是高度并行的。(但我更希望实现易于移植) 任何人都可以将此代码矢量化或使其更快吗?我不知道如何优雅地做这件事 P>我知道这里的堆栈溢出的想法令人惊叹,认为这是一个挑战: 功

我在MATLAB中有一个函数,它通过对内积应用非常重要的权重来执行计算(我认为MATLAB的内置函数不支持这一点)。 就我所知,这个函数工作得很好,但是,它在大型矩阵上太慢了。 改善这一点的最佳方法是什么

我尝试过转换成一个MEX文件,但我失去了与我正在使用的编译器的并行化,因此速度会变慢

我想在GPU上运行它,因为元素乘法是高度并行的。(但我更希望实现易于移植)

任何人都可以将此代码矢量化或使其更快吗?我不知道如何优雅地做这件事

<> P>我知道这里的堆栈溢出的想法令人惊叹,认为这是一个挑战:

功能 其中,
A
是复数的
mxn
矩阵,
w
是实数的
mx1
向量

瓶颈 这是函数最慢部分的
R(i,j)
的表达式(不100%确定符号是否正确):

其中,
w
是一个非负权重函数。 加权内积在几个维基百科页面上都有提及,并且

复制 可以使用以下脚本生成结果:

A = complex( rand(360000,100), rand(360000,100));
w = rand(360000, 1);
[Q, R] = Gram_Schmidt(A, w);
A = complex( rand( 100, 10), rand( 100, 10));
w = rand( 100, 1);
[Q , R ] = Gram_Schmidt( A, w);
[Q2, R2] = Gram_Schmidt2( A, w);
zeros1 = norm( Q - Q2 );
zeros2 = norm( R - R2 );
其中
A
w
是输入

速度和计算 如果使用上述脚本,您将获得与以下内容同义的探查器结果:

测试结果 通过使用以下脚本将函数与上述函数进行比较,可以测试结果:

A = complex( rand(360000,100), rand(360000,100));
w = rand(360000, 1);
[Q, R] = Gram_Schmidt(A, w);
A = complex( rand( 100, 10), rand( 100, 10));
w = rand( 100, 1);
[Q , R ] = Gram_Schmidt( A, w);
[Q2, R2] = Gram_Schmidt2( A, w);
zeros1 = norm( Q - Q2 );
zeros2 = norm( R - R2 );
其中,
Gram_-Schmidt
是前面描述的函数,
Gram_-Schmidt2
是替代函数。结果
zeros1
zeros2
应该非常接近于零

注意: 我试着用以下方法加速计算
R(I,j)
,但没有用

R(i,j) = ( w' * (   v    .* conj( Q(:,i) ) ) ) / ...
         ( w' * ( Q(:,i) .* conj( Q(:,i) ) ) );
1) 我第一次尝试矢量化:

function [Q, R] = Gram_Schmidt1(A, w)
    [m, n] = size(A);
    Q = complex(zeros(m, n));
    R = complex(zeros(n, n));

    for j = 1:n
        v = A(:,j);
        QQ = Q(:,1:j-1);
        QQ = bsxfun(@rdivide, bsxfun(@times, w, conj(QQ)), w.' * abs(QQ).^2);
        for i = 1:j-1
            R(i,j) = (v.' * QQ(:,i));
            v = v - R(i,j) * Q(:,i);
        end
        R(j,j) = norm(v);
        Q(:,j) = v / R(j,j);
    end
end
不幸的是,它比原来的函数慢


2) 然后我意识到这个中间矩阵
QQ
的列是增量构建的,而之前的列没有修改。这是我的第二次尝试:

function [Q, R] = Gram_Schmidt2(A, w)
    [m, n] = size(A);
    Q = complex(zeros(m, n));
    R = complex(zeros(n, n));
    QQ = complex(zeros(m, n-1));

    for j = 1:n
        if j>1
            qj = Q(:,j-1);
            QQ(:,j-1) = (conj(qj) .* w) ./ (w.' * (qj.*conj(qj)));
        end
        v = A(:,j);
        for i = 1:j-1
            R(i,j) = (v.' * QQ(:,i));
            v = v - R(i,j) * Q(:,i);
        end
        R(j,j) = norm(v);
        Q(:,j) = v / R(j,j);
    end
end
技术上没有进行重大的矢量化;我只预先计算了中间结果,并将计算移到了内部循环之外

基于快速基准测试,此新版本绝对更快:

% some random data
>> M = 10000; N = 100;
>> A = complex(rand(M,N), rand(M,N));
>> w = rand(M,1);

% time
>> timeit(@() Gram_Schmidt(A,w), 2)     % original version
ans =
    1.2444
>> timeit(@() Gram_Schmidt1(A,w), 2)    % first attempt (vectorized)
ans =
    2.0990
>> timeit(@() Gram_Schmidt2(A,w), 2)    % final version
ans =
    0.4698

% check results
>> [Q,R] = Gram_Schmidt(A,w);
>> [Q2,R2] = Gram_Schmidt2(A,w);
>> norm(Q-Q2)
ans =
   4.2796e-14
>> norm(R-R2)
ans =
   1.7782e-12

编辑: 在注释之后,我们可以重写第二个解决方案,通过将该部分移动到外循环的末尾(即,在计算新列
Q(:,j)
之后,我们计算并存储相应的
QQ(:,j)

输出功能相同,定时也没有什么不同;代码只是稍微短一点

function [Q, R] = Gram_Schmidt3(A, w)
    [m, n] = size(A);
    Q = zeros(m, n, 'like',A);
    R = zeros(n, n, 'like',A);
    QQ = zeros(m, n, 'like',A);

    for j = 1:n
        v = A(:,j);
        for i = 1:j-1
            R(i,j) = (v.' * QQ(:,i));
            v = v - R(i,j) * Q(:,i);
        end
        R(j,j) = norm(v);
        Q(:,j) = v / R(j,j);
        QQ(:,j) = (conj(Q(:,j)) .* w) ./ (w.' * (Q(:,j).*conj(Q(:,j))));
    end
end
注意,我使用了
zero(…,'like',A)
语法(在最近的MATLAB版本中是新的)。这允许我们在GPU上不经修改地运行函数(假设您拥有并行计算工具箱):

vs


不幸的是,在我的情况下,它没有任何更快。事实上,在GPU上运行要比在CPU上运行慢很多倍,但值得一试:)

这里有一个很长的讨论,但是,要跳到答案上来。你已经用向量w加权了R计算的分子和分母。加权发生在内环上,由三点积、分子中的点Q点w和分母中的点Q点w组成。如果您做一个更改,我认为代码将运行得更快。写入num=(A点sqrt(w))点(Q点sqrt(w))和写入den=(Q点sqrt(w))点(Q点sqrt(w))。这将(A点sqrt(w))和(Q点sqrt(w))乘积计算移出内部循环

我想对Gram-Schmidt正交化的公式进行描述,希望除了给出另一种计算解决方案外,还能进一步深入了解GSO的优势

GSO的“目标”有两个方面。首先,要解决Ax=y这样的方程,其中A的行远多于列。这种情况在测量数据时经常发生,因为测量的数据很容易超过状态数。实现第一个目标的方法是将A重写为QR,使得Q的列是正交的和规范化的,并且R是三角矩阵。我相信,您提供的算法实现了第一个目标。Q表示矩阵的基空间,R表示生成A的每列所需的每个基空间的振幅

GSO的第二个目标是按照重要性的顺序对基础向量进行排序。这是您尚未完成的步骤。而且,虽然包括这一步骤,可能会增加求解时间,但结果将根据测量值中包含的数据确定x的哪些元素是重要的

但是,我认为,通过这种实现,解决方案比您提出的方法更快

Aij=Qij-Rij,其中Qj是正交的,Rij是上三角形,Ri,j>i=0。Qj是A的正交基向量,Rij是每个Qj在A中创建列的参与度。因此

A_j1 = Q_j1 * R_1,1
A_j2 = Q_j1 * R_1,2 + Q_j2 * R_2,2
A_j3 = Q_j1 * R_1,3 + Q_j2 * R_2,3 + Q_j3 * R_3,3
通过检查,你可以写

A_j1 = ( A_j1 / | A_j1 | )  * | A_j1 | = Q_j1 * R_1,1
然后每隔一列A将Q_j1投影到上,得到R_1,j元素

R_1,2 = Q_j1 dot Aj2
R_1,3 = Q_j1 dot Aj3
...
R_1,j(j>1) = A_j dot Q_j1
然后从A的列中减去Q_j1的project元素(这会将第一列设置为零,因此可以忽略第一列)

for j = 2,n
  A_j = A_j - R_1,j * Q_j1
end
现在从A中删除一列,确定第一个正交基向量Q,j1,并确定第一个基向量对每列R_1,j的贡献,并从每列中减去第一个基向量的贡献。重复此步骤
for j = 2,n
  A_j = A_j - R_1,j * Q_j1
end
for i = 1,n
  R_ii = |A_i|                    A_i is the ith column of A, |A_i| is magnitude of A_i
  Q_i = A_i / R_ii                Q_i is the ith column of Q
  for j = i, n
    R_ij = | A_j dot Q_i | 
    A_j = A_j - R_ij * Q_i
  end
end
w = w / | w |
for i = 1,n
  R_ii = |A_i inner product w|        # A_i inner product w = A_i .* w                  
  Q_i = A_i / R_ii              
  for j = i, n
    R_ij = | (A_i inner product w) dot Q_i |      # A dot B = A' * B 
    A_j = A_j - R_ij * Q_i
  end
end
A inner product B = A .* B
A dot w = A' w
(A B)' = B'A'
A' conj(A) = |A|^2
function [Q, R] = Gram_Schmidt_2(A, w)

    [m, n] = size(A);
    Q = complex(zeros(m, n));
    R = complex(zeros(n, n));

    # Outer loop identifies the basis vectors    
    for j = 1:n
      aCol = A(:,j);
      # Subtract off the basis vector
      for i = 1:(j-1)
        R(i,j) = ctranspose(Q(:,j)) * aCol;
        aCol = aCol - R(i,j) * Q(:,j);
      end
      amp_A_col = norm(aCol);
      R(j,j) = amp_A_col;
      Q(:,j) = aCol / amp_A_col;
    end
end
function [Q, R] = Gram_Schmidt_2(A, w)

    [m, n] = size(A);
    Q = complex(zeros(m, n));
    R = complex(zeros(n, n));

    # Outer loop identifies the basis vectors    
    for j = 1:n
      aCol = A(:,j);
      # Subtract off the basis vector
      for i = 1:(j-1)
        # R(i,j) = ctranspose(Q(:,j)) * aCol;
        R(i,j) = sum(   aCol    .* conj( Q(:,i) ) .* w ) / ...
                 sum( Q(:,i) .* conj( Q(:,i) ) .* w );
        aCol = aCol - R(i,j) * Q(:,j);
      end
      amp_A_col = norm(aCol);
      R(j,j) = amp_A_col;
      Q(:,j) = aCol / amp_A_col;
    end
end
function [Q, R] = Gram_Schmidt_3(A, w)

    [m, n] = size(A);
    Q = complex(zeros(m, n));
    R = complex(zeros(n, n));
    Q_sw = complex(zeros(m, n));
    sw = w .^ 0.5;
    for j = 1:n
      aCol = A(:,j);
      aCol_sw = aCol .* sw;
      # Subtract off the basis vector
      for i = 1:(j-1)
        # R(i,j) = ctranspose(Q(:,i)) * aCol;
        numTerm = ctranspose( Q_sw(:,i) ) * aCol_sw;
        denTerm = ctranspose( Q_sw(:,i) ) * Q_sw(:,i);
        R(i,j) = numTerm / denTerm;
        aCol_sw = aCol_sw - R(i,j) * Q_sw(:,i);
      end
      aCol = aCol_sw ./ sw;
      amp_A_col = norm(aCol);
      R(j,j) = amp_A_col;
      Q(:,j) = aCol / amp_A_col;
      Q_sw(:,j) = Q(:,j) .* sw;
    end
end
function [Q, R] = Gram_Schmidt_4(A, w)

    [m, n] = size(A);
    Q = complex(zeros(m, n));
    R = complex(zeros(n, n));

    for j = 1:n
      aCol = A(:,j);
      for i = 1:(j-1)
        cqw = conj(Q(:,i)) .* w;
        R(i,j) = ( transpose(  aCol )  * cqw) ...
               / (transpose( Q(:,i) ) * cqw);
        aCol = aCol - R(i,j) * Q(:,i);
      end
      amp_A_col = norm(aCol);
      R(j,j) = amp_A_col;
      Q(:,j) = aCol / amp_A_col;
    end
end
function Gram_Schmidt_tester_2
      nSamples = 360000;
      nMeas = 100;
      nMeas = 15;

      A = complex( rand(nSamples,nMeas), rand(nSamples,nMeas));
      w = rand(nSamples, 1);

      profile on;
      [Q1, R1] = Gram_Schmidt_basic(A);
      profile off;
      data1 = profile ("info");
      tData1=data1.FunctionTable(1).TotalTime;
      approx_zero1 = A - Q1 * R1;
      max_value1 = max(max(abs(approx_zero1)));

      profile on;
      [Q2, R2] = Gram_Schmidt_w_Orig(A, w);
      profile off;
      data2 = profile ("info");
      tData2=data2.FunctionTable(1).TotalTime;
      approx_zero2 = A - Q2 * R2;
      max_value2 = max(max(abs(approx_zero2)));

      sw=w.^0.5;
      profile on;
      [Q3, R3] = Gram_Schmidt_sqrt_w(A, w);
      profile off;
      data3 = profile ("info");
      tData3=data3.FunctionTable(1).TotalTime;
      approx_zero3 = A - Q3 * R3;
      max_value3 = max(max(abs(approx_zero3)));

      profile on;
      [Q4, R4] = Gram_Schmidt_4(A, w);
      profile off;
      data4 = profile ("info");
      tData4=data4.FunctionTable(1).TotalTime;
      approx_zero4 = A - Q4 * R4;
      max_value4 = max(max(abs(approx_zero4)));

      profile on;
      [Q5, R5] = Gram_Schmidt_5(A, w);
      profile off;
      data5 = profile ("info");
      tData5=data5.FunctionTable(1).TotalTime;
      approx_zero5 = A - Q5 * R5;
      max_value5 = max(max(abs(approx_zero5)));


      profile on;
      [Q2a, R2a] = Gram_Schmidt2a(A, w);
      profile off;
      data2a = profile ("info");
      tData2a=data2a.FunctionTable(1).TotalTime;
      approx_zero2a = A - Q2a * R2a;
      max_value2a = max(max(abs(approx_zero2a)));


      profshow (data1, 6);
      profshow (data2, 6);
      profshow (data3, 6);
      profshow (data4, 6);
      profshow (data5, 6);
      profshow (data2a, 6);

      sprintf('Time for %s is %5.3f sec with %d samples and %d meas, max value is %g',
         data1.FunctionTable(1).FunctionName,
         data1.FunctionTable(1).TotalTime,     
         nSamples, nMeas, max_value1)
      sprintf('Time for %s is %5.3f sec with %d samples and %d meas, max value is %g',
         data2.FunctionTable(1).FunctionName,
         data2.FunctionTable(1).TotalTime,     
         nSamples, nMeas, max_value2)
      sprintf('Time for %s is %5.3f sec with %d samples and %d meas, max value is %g',
         data3.FunctionTable(1).FunctionName,
         data3.FunctionTable(1).TotalTime,     
         nSamples, nMeas, max_value3)
      sprintf('Time for %s is %5.3f sec with %d samples and %d meas, max value is %g',
         data4.FunctionTable(1).FunctionName,
         data4.FunctionTable(1).TotalTime,     
         nSamples, nMeas, max_value4)
      sprintf('Time for %s is %5.3f sec with %d samples and %d meas, max value is %g',
         data5.FunctionTable(1).FunctionName,
         data5.FunctionTable(1).TotalTime,     
         nSamples, nMeas, max_value5)
      sprintf('Time for %s is %5.3f sec with %d samples and %d meas, max value is %g',
         data2a.FunctionTable(1).FunctionName,
         data2a.FunctionTable(1).TotalTime,     
         nSamples, nMeas, max_value2a)

end
ans = Time for Gram_Schmidt_basic is 0.889 sec with 360000 samples and 15 meas, max value is 1.57009e-16
ans = Time for Gram_Schmidt_w_Orig is 0.952 sec with 360000 samples and 15 meas, max value is 6.36717e-16
ans = Time for Gram_Schmidt_sqrt_w is 0.390 sec with 360000 samples and 15 meas, max value is 6.47366e-16
ans = Time for Gram_Schmidt_4 is 0.452 sec with 360000 samples and 15 meas, max value is 6.47366e-16
ans = Time for Gram_Schmidt_5 is 2.636 sec with 360000 samples and 15 meas, max value is 6.47366e-16
ans = Time for Gram_Schmidt2a is 0.905 sec with 360000 samples and 15 meas, max value is 6.68443e-16
function [Q, R] = Gram_Schmidt5(A, w)
Q = A;
n_dimensions = size(A, 2);
R = zeros(n_dimensions);
R(1, 1) = norm(Q(:, 1));
Q(:, 1) = Q(:, 1) ./ R(1, 1);
for i = 2 : n_dimensions
    Qw = (Q(:, i - 1) .* w)' * Q(:, (i - 1) : end);
    R(i - 1, i : end) = Qw(2:end) / Qw(1);
    %% Surprisingly this loop beats the matrix multiply
    for j = i : n_dimensions
        Q(:, j) = Q(:, j) - Q(:, i - 1) * R(i - 1, j);
    end
    %% This multiply is slower than above
    %    Q(:, i : end) = ...
    %     Q(:, i : end) - ...
    %     Q(:, i - 1) * R(i - 1, i : end);
    R(i, i) = norm(Q(:,i));
    Q(:, i) = Q(:, i) ./ R(i, i);
end