2015-06-11 24 views
5

Zainspirowany przez Artical http://gallery.rcpp.org/articles/parallel-distance-matrix/, próbuję użyć RcppParallel, aby przeprowadzić przeszukiwanie w trybie brute-force w wielowymiarowej przestrzeni parametrycznej do testowania wstecznego za pomocą wielowątkowych. Utknąłem w tym, jak wywołać zdefiniowaną przez siebie funkcję w części struct. Chodzi o to, w następujący sposób:Jak wywołać funkcję zdefiniowaną przez użytkownika w RcppParallel?

pierwsze, stworzenie parametrycznego matrycy NumericMatrix params_mat w R pierwszy i wykorzystuje dane analizy historycznej z List, NumericVector, CharacterVector typu danych, takich jak List Data_1, NumericVector Data_2, CharacterVector Data_3, ..., które są na stałe do każdego parametrycznej scenariuszu params_vec (Należy zauważyć, że jest to rząd params_mat).

Następnie należy zdefiniować funkcję analizy historycznej, która wyprowadza wektor składający się z 3 zmiennych kluczowych w celu oceny skuteczności strategii.

Oto przykład moich params_mat i Backtesting_Fun, które można uruchomić odpowiednio w R i Rcpp.

//[[Rcpp::export]] 
NumericMatrix data_frame_rcpp(const Rcpp::List& list_params) 
{ 
    NumericMatrix res = list_params[0]; 
    return res; 
} 

# R codes to generate params_mat 
params <- expand.grid (x_1=seq(1,100,1), x_2=seq(3,100,2), ..., x_n=seq(4,200,1));       
list_params = list(ts(params)); 
tmp_params_data = data_frame_rcpp(list_params);            
params_mat = matrix(tmp_params_data, ncol = ncol(tmp_params_data), dimnames = NULL); 
params_vec = params_mat[ii,]; 

# User-defined Rcpp codes for backtesting 
NumericVector Backtesting_Fun (List Data_1, NumericVector Data_2, CharacterVector Data_3, ..., NumericVector params_vec) 
{ 
    // Main function parts to run backtesting for each params_vec scenario. 
    ... etc 

    // save 3 key result variables together with each params_vec (just a simple illustration). 
    NumericVector res = NumericVector::create(params_vec[0],...,params_vec[size-1], 
              key_1, key_2, key_3); 
    return res; 
} 

Oczywiście musimy przepisać/zmodyfikować oryginalny RCPP Backtesting_Fun z typów RVector/RMatrix, a następnie użyć następujących RcppParallel kody zadzwonić Backtesting_Fun w struct Backtest_parallel:

// [[Rcpp::depends(RcppParallel)]] 
#include <RcppParallel.h> 
using namespace RcppParallel; 

RVector<double> Backtesting_Fun (const RVector<double> Data_1, const RVector<double> Data_2, 
           const RVector<string> Data_3,..., const RVector<double> params_vec) 
{ 
    // Main function parts to run backtesting for each params_vec scenario. 
    ... etc; 

    // save 3 key result variables together with each params_vec 
    ... etc; 

    return res; 
} 

struct Backtest_parallel : public Worker 
{  
    // input matrix to read from 
    const RVector<List> Data_1; 
    const RVector<double> Data_2; 
    const RVector<string> Data_3; 
    ... 
    const RMatrix<double> params_mat; 

    // output matrix to write to 
    RMatrix<double> rmat; 

    // initialize from Rcpp input and output matrixes (the RMatrix class 
    // can be automatically converted to from the Rcpp matrix type) 
    Backtest_parallel(const List Data_1, const NumericVector Data_2, 
    const CharacterVector Data_3, ..., const NumericMatrix params_mat) 
     : Data_1(Data_1), Data_2(Data_2), Data_3(Data_3), ..., params_mat(params_mat) {} 

    // function call operator that work for the specified range (begin/end) 
    void operator()(std::size_t begin, std::size_t end) 
    { 
     for (std::size_t ii = begin; ii < end; i++) 
     { 
     // params rows that we will operate on 
     RMatrix<double>::Row params_row = params_mat.row(ii); 

     // Run the backtesting function defined above 
     RVector<double> res = Backtesting_Fun(Data_1, Data_2, ..., params_row) 
     for (std::size_t jj = 0; jj < res.length(); jj++) 
     { 
      // write to output matrix 
      rmat(ii,jj) = res[jj]; 
     } 
     } 
    } 
}; 

// [[Rcpp::export]] 
NumericMatrix rcpp_parallel_backtest(List Data_1, NumericVector Data_2, CharacterVector Data_3, 
            ..., NumericMatrix params_mat) 
{  
    // allocate the matrix we will return 
    NumericMatrix rmat(params_mat.nrow(), params_mat.nrow()+3); 

    // create the worker 
    Backtest_parallel backtest_parallel(Data_1, Date_2, ..., params_mat); 

    // call it with parallelFor 
    parallelFor(0, rmat.nrow(), backtest_parallel); 

    return rmat; 
} 

Oto moje pytania:

  1. Can RVector zawiera List typ danych, lub czy istnieje jakiś konkretny pojemnik o numerze RcppParallel, który może zawierać List;

  2. W Backtesting_Fun, wejście powinno być RVector/RMatrix rodzaje, to znaczy, że naprawdę trzeba konwertować orginal RCPP główne kody z NumericVector w RVector?

Czy jest jakiś lepszy sposób na równoległe obliczenia dla mojego przypadku w RcppParallel? Z góry dziękuję.

EDIT:

  1. patrzę na innych przykładach dotyczących RcppPararrel w http://gallery.rcpp.org/articles/parallel-matrix-transform/, http://gallery.rcpp.org/articles/parallel-inner-product/, wspólna idea w struct operator() jest wykorzystanie wskaźników do manipulowania wprowadzanie danych do operator(), więc jest jakiś sposób zbudować w moim przypadku funkcję zdefiniowaną przez użytkownika z wejściami do wskaźnika?

  2. Jeśli powyższy sposób nie zadziała, to jest możliwe, aby korzystać z wrap przekonwertować RVector/RMatrix powrotem do RCPP typ danych, to znaczy NumericVector.. w operator() tak, że typy wejściowe zdefiniowanej przez użytkownika funkcji Backtesting_Fun mogą pozostać niezmienione.

+0

Prawdopodobnie najprawdopodobniej uzyskasz odpowiedź, jeśli podasz mniejszy, kompletny przykład (bez "..." w twoim funkcjach). – nrussell

+1

Dzięki za propozycję @nrussell, zaktualizuję pytanie prostym i dokładnym przykładem wkrótce – Alvin

Odpowiedz

3

myślę mogę znaleźć alternatywny sposób na rozwiązanie tego pytanie: Klucze są w użyciu nitki bezpiecznych akcesorów zawierać zmienne w struct i pozostają RVector/RMatrix w zewnętrznej funkcji głównego tak, że parallelFor może działać dobrze, co jest najważniejszą częścią tego równoległego algo.Oto moje sposoby:

  1. Pozbądź List typu danych: Zamiast tego, możemy przekonwertować zmienną List za pomocą NumericVector/NumericMatrix pojemnik i nagrać swój odpowiedni indeks, tak aby podwektor/podmacierz wskaże te same elementy jako element listy.

  2. Konwersja RVector/RMatrix do arma::vec/arma::mat: Jak wspomniano w RcppParallel Github, C++ Armadillo są thread-safe w operatora struct jest. Tutaj modyfikuję przykład podany w Parallel Distance Matrix Calculation with RcppParallel za pomocą tego pomysłu, który prawie pozostaje taki sam jak prędkość testu.

    struct JsDistance : public Worker 
    { 
        const RMatrix<double> tmp_MAT; // input matrix to read from 
        RMatrix<double> tmp_rmat;  // output matrix to write to 
        std::size_t row_size, col_size; 
    
        // Convert global input/output into RMatrix/RVector type 
        JsDistance(const NumericMatrix& matrix_input, NumericMatrix& matrix_output, 
          std::size_t row_size, std::size_t col_size) 
        : tmp_MAT(matrix_input), tmp_rmat(matrix_output), row_size(row_size), col_size(col_size) {} 
    
        // convert RVector/RMatrix into arma type for Rcpp function 
        // and the follwing arma data will be shared in parallel computing 
        arma::mat convert() 
        { 
        RMatrix<double> tmp_mat = tmp_MAT; 
        arma::mat MAT(tmp_mat.begin(), row_size, col_size, false); 
        return MAT; 
        } 
    
    
        void operator()(std::size_t begin, std::size_t end) 
        { 
        for (std::size_t i = begin; i < end; i++) 
        { 
         for (std::size_t j = 0; j < i; j++) 
         { 
         // rows we will operate on 
         arma::mat MAT = convert(); 
         arma::rowvec row1 = MAT.row(i);   // get the row of arma matrix 
         arma::rowvec row2 = MAT.row(j); 
    
         // compute the average using std::tranform from the STL 
         std::vector<double> avg(row1.n_elem); 
         std::transform(row1.begin(), row1.end(), // input range 1 
             row2.begin(),    // input range 2 
             avg.begin(),    // output range 
             average);     // function to apply 
    
         // calculate divergences 
         double d1 = kl_divergence(row1.begin(), row1.end(), avg.begin()); 
         double d2 = kl_divergence(row2.begin(), row2.end(), avg.begin()); 
    
         // write to output matrix 
         tmp_rmat(i,j) = sqrt(.5 * (d1 + d2)); 
         } 
        } 
        } 
    }; 
    
    // [[Rcpp::export]] 
    NumericMatrix rcpp_parallel_js_distance_modify(const Rcpp::NumericMatrix& matrix_input, int N_cores) 
    { 
        // allocate the matrix we will return 
        NumericMatrix matrix_output(matrix_input.nrow(), matrix_input.nrow()); 
        std::size_t row_size = matrix_input.nrow(); 
        std::size_t col_size = matrix_input.ncol(); 
    
        // create the worker 
        JsDistance jsDistance(matrix_input, matrix_output, row_size, col_size); 
    
        // call it with parallelFor 
        parallelFor(0, matrix_input.nrow(), jsDistance, matrix_input.nrow()/N_cores);   // parallelFor with grain size setting 
    
        return matrix_output; 
    } 
    
    // Example compare: 
    n_row = 1E3; 
    n_col = 1E2; 
    m = matrix(runif(n_row*n_col), nrow = n_row, ncol = n_col); 
    m = m/rowSums(m); 
    
    res <- benchmark(rcpp_parallel_js_distance(m, 6), 
         rcpp_parallel_js_distance_orignal(m, 6), 
         order="relative") 
    res[,1:4]; 
    
    #test         #elapsed #relative 
    rcpp_parallel_js_distance_orignal(m, 6) 128.069 1.000 
    rcpp_parallel_js_distance(m, 6)   129.210 1.009 
    

Jak widzimy, typ danych wewnątrz operator będzie C++ arma, a teraz możemy bezpiecznie i fastly nazwać naszą funkcję zdefiniowaną przez użytkownika bezpośrednio przy użyciu obiektu, a nie tylko wskazówki, które mogą nie być ogólny lub łatwy do zaprojektowania.

Teraz ta struktura danych będzie współdzielona z tym samym źródłem danych bez dodatkowej kopii w przetwarzaniu równoległym, a następnie możemy trochę zmienić w przypadku analizy historycznej za pomocą idei wymienionej w powyższym pytaniu.