Ho una matrice NxM (di solito 10k X 10k elementi) che descrive un set di terra. Ogni linea rappresenta un oggetto e ogni colonna è una caratteristica specifica. Ad esempio, nella matriceAccelerazione della distanza L1 tra tutte le coppie in un set di terra
f1 f2 f3
x1 0 4 -1
x2 1 0 5
x3 4 0 0
x4 0 1 0
dell'oggetto x1 ha valore 0 in funzione 1, valore 4 nella caratteristica 1, e il valore 0 in funzione -1. I valori di questo sono numeri reali generali (double).
Devo calcolare diverse distanze/dissomiglianze personalizzate tra tutte le coppie di oggetti (tutte le coppie di linee). Per confrontare, voglio calcolare le distanze L1 (manhattan) e L2 (euclidee).
Ho utilizzato la libreria Eigen per eseguire la maggior parte dei miei calcoli. Per calcolare la L2 (euclideo), utilizzo la seguente osservazione: per due vettori un e b di dimensioni n, abbiamo:
||a - b||^2 = (a_1 - b_1)^2 + (a_2 - b_2)^2 + ... +(a_n - b_n)^2 = a_1^2 + b_1^2 - 2 a_1 b_1 + a_2^2 + b_2^2 - 2 a_2 b_2 + ... + a_n^2 + b_n^2 - 2 a_n b_n = a . a + b . b - 2ab
In altre parole, si riscrive la norma quadrata usando il prodotto punto del vettore da solo e sottrarre il doppio del prodotto punto tra di loro. Da quello, prendiamo il quadrato e abbiamo finito. Col tempo, ho trovato questo trucco molto tempo fa e sfortunatamente ho perso il riferimento dell'autore.
Comunque, questo permette di scrivere un codice di fantasia utilizzando Eigen (in C++):
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> matrix, XX, D;
// Load matrix here, for example
// matrix << 0, 4, -1,
// 1, 0, 5,
// 4, 0, 0,
// 0, 1, 0;
const auto N = matrix.rows();
XX.resize(N, 1);
D.resize(N, N);
XX = matrix.array().square().rowwise().sum();
D.noalias() = XX * Eigen::MatrixXd::Ones(1, N) +
Eigen::MatrixXd::Ones(N, 1) * XX.transpose();
D -= 2 * matrix * matrix.transpose();
D = D.cwiseSqrt();
Per matrici 10k X 10k, siamo in grado di calcolare la distanza L2 per tutte le coppie di oggetti/linee in meno di 1 min (2 core/4 thread), che personalmente considero una buona prestazione per i miei scopi. Eigen è in grado di combinare le operazioni e utilizzare diverse ottimizzazioni di livello basso/alto per eseguire questi calcoli. In questo caso, Eigen utilizza tutti i core disponibili (e, ovviamente, possiamo regolarli).
Tuttavia, ho ancora bisogno di calcolare la distanza L1, ma non sono riuscito a capire una buona forma algebrica da utilizzare con Eigen e ottenere prestazioni soddisfacenti. Fino ad ora ho il seguente:
const auto N = matrix.rows();
for(long i = 0; i < N - 1; ++i) {
const auto &row = matrix.row(i);
#ifdef _OPENMP
#pragma omp parallel for shared(row)
#endif
for(long j = i + 1; j < N; ++j) {
distance(i, j) = (row - matrix.row(j)).lpNorm<1>();
}
}
Ma questo prendere tempo molto più lungo: per la stessa matrice 10k X 10k, questo codice utilizza 3,5 min, che è molto peggio se si considera che L1 e L2 sono molto vicini nel loro originale forma:
L1(a, b) = sum_i |a_i - b_i|
L2(a, b) = sqrt(sum_i |a_i - b_i|^2)
Qualsiasi idea di come il cambiamento L1 utilizzare calcoli veloci con Eigen? O una forma migliore per farlo e io non l'ho capito.
Grazie mille per il vostro aiuto!
Carlos
Questo non risponde alla tua domanda, ma nota che se hai solo 2 core fisici, allora dovresti abilitare solo 2 thread perché l'hyperthreading rallenta le operazioni intensive della CPU. Puoi anche inizializzare D usando replicate: 'D = XX.replicate (1, n) + XX.transpose(). Replicate (n, 1);' – ggael
@ggael In effetti, uso sempre solo il numero di core fisici e, quando possibile, spengo l'hyperthreading nelle macchine. A proposito, grazie per il suggerimento. –
L2 può essere eseguito in O (N^2.81) utilizzando l'algoritmo Strassen per la moltiplicazione rapida della matrice che la libreria potrebbe già utilizzare. ma L1 è così semplice da portare O (N^3) per completare. quella potrebbe essere la ragione per cui L1 è più lento. –