Как я мог ускорить свою функцию, используя библиотеку Eigen в C ++?

0

Я пытаюсь получить серию Остаточной суммы Squarts (RSS) из программы C++, используя для цикла. И я использовал RcppEigen.package.skeleton() для бесшовного объединения C++ и R. Хотя, когда я запускаю данные X с 788rows * 857cols и Y с 788rows * 1cols, время выполнения программы C++ является пользователем (4.62 s) (3,87 с), прошедшее (8.51 с), а время выполнения программы R - это пользовательская (8,68 с) система (1,78 с), прошедшая (10,53 с). Программа C++ работает быстрее, чем R. Используемая платформа - win7 (X64) с 8G RAM. Как я могу ускорить свою программу? Любая помощь будет оценена.

Вот программа C++:

#include <RcppEigen.h>

//*---get Residual Sum of Squarts via Matrix Operation
//fastLm()
double getRSS(const Eigen::MatrixXd& X, const Eigen::MatrixXd& Y){
   Eigen::MatrixXd RSS=((Y-X*((X.transpose()*X).inverse()*X.transpose()*Y)).transpose())*(Y-X*((X.transpose()*X).inverse()*X.transpose()*Y));
   double RSSd = RSS.determinant();   
   return RSSd;             
}

//*---get F value from RSS and df
double getFval(double RSS1,double RSS2, int n1,int n2,int nObs){
  return (RSS1-RSS2)/(n1-n2)/(RSS2/(nObs-n2-1));      
}

//*---remove p columns from  i-th collumn of matrix
Eigen::MatrixXd removeColumn(const Eigen::MatrixXd& matrix, unsigned int i,int p){
    unsigned int numRows = matrix.rows();
    unsigned int numCols = matrix.cols()-p;

    Eigen::MatrixXd X;
    X=matrix;
    if( i < numCols )
        X.block(0,i,numRows,numCols-i) = matrix.block(0,i+p,numRows,numCols-i);

    X.conservativeResize(numRows,numCols);
    return X;
}

// [[Rcpp::export]]
Rcpp::List getPIcvalue(bool findIn,int p,int n, const Eigen::VectorXd& varIn, const Eigen::MatrixXd& Y,const Eigen::MatrixXd& Xf,const Eigen::MatrixXd& X0){
          //  varIn=(0,1,0,1...,0); p=1 :addition or elimination column; findIn=false,add 1 column of Xf to X0, findIn=false,eliminate 1 column to X0. n=X0.rows();
    bool valid;     
    valid=true;  
    double FitStat1;
    FitStat1 = 1e+10;              

    int pointer;
    pointer=-2;
    double FitStat;
    int nR = n-X0.cols();   // n is the X0.rows()
    int nF;     //nF=nR-1  //findIn=false
    double RSSr;
    double RSSf;
    double F_value;
    RSSr = getRSS(X0,Y);
    int k;
    if(false==findIn){
        k = p;                  
    }else{
        k = -p;      
    }
    Eigen::MatrixXd X(n,X0.cols()+k); 

    if(false==findIn){
        for(int i=0;i<Xf.cols();i++){
            if(0==varIn[i]){
                X<<X0,Xf.col(i);   // X: combine X0 and ith column of Xf                  
                nF = n-X.cols();     
                RSSf = getRSS(X,Y);
                FitStat = getFval(RSSr,RSSf,X.cols(),X0.cols(),n);
                //FitStat = getPvalue(F_value,nF,nR); 
                if(FitStat<FitStat1){
                    FitStat1=FitStat;
                    pointer=i;                    
                }                 
            }//varIn     
        }//for i                 
    }else{
        for(int i=1;i<X0.cols();i++){
            X =  removeColumn(X0,i,p);       
            RSSf = getRSS(X,Y);
            FitStat = getFval(RSSf,RSSr,X0.cols(),X.cols(),n);
            //FitStat = getPvalue(F_value,nR,nF); 
            if(FitStat<FitStat1){
                FitStat1=FitStat;
                pointer=i;                    
            }                 
        }//for i    
    }//findIn 
    return Rcpp::List::create(Rcpp::Named("keyV")=FitStat1,
                              Rcpp::Named("keyP")=pointer+1,
                              Rcpp::Named("keyR")=valid);
}
  • 0
    Вы упоминаете все, кроме того, что может потребоваться, чтобы даже начать отвечать на ваш вопрос. А именно 1) Compiler used and version of the compiler и 2) Compilation settings to confirm whether you are compiling with optimizations turned on.
  • 0
    Я использовал DEV-C++ для компиляции программы на C ++. Я скачал R-3.1.1 , Rtools и библиотеку RcppEigen в режиме онлайн, я использовал RcppEigen.package.skeleton() для установки локальной библиотеки. Я запускаю эту программу в R.
Показать ещё 3 комментария
Теги:
eigen
rcpp

1 ответ

10
Лучший ответ

Ваше выражение для формулы матрицы RSS крайне неэффективно. Ты делаешь это:

Eigen::MatrixXd RSS = (
  (Y - X * 
    ( ( X.transpose() * X ).inverse() * X.transpose() * Y ) 
  ).transpose() ) * 
  ( Y - X * 
    ( ( X.transpose() * X ).inverse() * X.transpose() * Y ) 
  );

который явно очень повторяется и многократно перерабатывает одни и те же дорогостоящие операции. Транспонирование матрицы должно быть очень дешевым, если только оно не требует копии. Но инвертирование матрицы (даже симметричная положительно определенная матрица, как это имеет место здесь, которую Эйген не знает, если вы не говорите об этом) очень дорога. Хе.. даже матричные умножения стоят дорого.

Возможно, вы думаете, что Eigen делает магию под капотом, чтобы преодолеть избыточные операции и находит наиболее эффективную последовательность операций, чтобы сделать результаты. Но Eigen остается довольно консервативным на этом фронте (полагаясь на шаблоны консервативного выражения, разрешенные во время компиляции, когда на самом деле нужно использовать оптимизацию экспрессии во время выполнения). Таким образом, это действительно не так много. Вам нужно помочь ему удалить избыточные операции, выполнив эту работу самостоятельно.

И, наконец, вы можете комбинировать инверсию и умножение, делая вместо этого линейное системное решение (вместо A = inv(X) * B, вы solve(X * A = B)), что также позволяет вам указывать наиболее подходящее разложение (здесь оно либо llt, либо ldlt, в зависимости от того, насколько хорошо вы ожидаете, что ваша матрица (Xt*X) будет).

Вы получите следующее:

auto Xt = X.transpose(); //<- deduce the type with 'auto' to avoid copy-evaluation of the transpose.
const Eigen::MatrixXd A = X * ( Xt * X ).ldlt().solve(Xt);
const Eigen::MatrixXd Y_AY = Y - A * Y;
Eigen::MatrixXd RSS = Y_AY.transpose() * Y_AY;

Но на самом деле вы можете еще больше оптимизировать это, осознав, что X * (Xt * X)^-1 * Xt * Y фактически эквивалентно X * B где B - наименьшее квадратное решение для X*B = Y Если вы используете QR-метод (не используйте SVD здесь, это полный избыток и очень медленный, я не понимаю, почему он даже упоминается в документах Eigen как жизнеспособный метод для линейного наименьшего квадрата (вероятно, потому, что Eigen people любители!)), вы можете сделать это:

const Eigen::MatrixXd B = X.colPivHouseholderQr().solve( Y );
const Eigen::MatrixXd Y_XB = Y - X * B;
Eigen::MatrixXd RSS = Y_XB.transpose() * Y_XB;

который должен быть быстрее, чем у вас раньше (по крайней мере, с точки зрения сложности времени, это должно быть на порядок быстрее).

Кроме того, если Y оказывается квадратной матрицей, то вы должны вычислить детерминант Y_XB и Y_XB его квадрат, вместо вычисления определителя его произведения с его собственным транспозисом. Это позволит удалить одно матричное умножение (и скопировать в RSS).

Наконец, я не слишком много смотрю на ваши другие функции (которые вызывают getRSS), но вы должны делать все возможное, чтобы избежать перекомпоновки (на каждой итерации) вещей, которые не меняются или не меняются слишком сильно, например, QR-разложение X. Существуют способы, которыми вы можете поддерживать QR-декомпозицию во всех изменениях X, но это больше, чем я могу здесь остановиться, и, вероятно, вы не можете сделать что-то с Eigen.

  • 1
    Спасибо, это просто нужно 0.03s запустить for цикла , чтобы получить RSS .
  • 0
    По моим оценкам, разложение LDLT и разложение QR имеют вполне сопоставимую скорость. Иногда разложение ЛПНП происходит немного быстрее.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню