Как можно быстрее вычислить пустое пространство матрицы - PullRequest
18 голосов
/ 02 февраля 2010

Мне нужно вычислить нулевое пространство нескольких тысяч маленьких матриц (8x9, а не 4x3, как я писал ранее) параллельно (CUDA). Все ссылки указывают на SVD, но алгоритм в числовых рецептах кажется очень дорогим и дает мне много вещей, кроме пустого пространства, в котором я действительно не нуждаюсь. Гауссовское исключение действительно не вариант? Существуют ли другие распространенные методы?

Ответы [ 7 ]

11 голосов
/ 08 февраля 2010

Чтобы ответить на ваш вопрос напрямую ... да! QR-разложение!

Пусть A - матрица размером m на n с рангом n. QR-разложение находит ортонормированную матрицу m-by-m Q и верхнюю треугольную матрицу M-by-n R, такую ​​что A = QR. Если мы определим Q = [Q1 Q2], где Q1 - m-by-n, а Q2 - m-by- (m-n), то столбцы Q2 образуют нулевое пространство A ^ T.

QR-разложение вычисляется с помощью поворотов Грамма-Шмидта, Гивенса или отражений Домохозяина. Они имеют разные свойства стабильности и количество операций.

Вы правы: СВД дорог! Я не могу говорить о том, что использует современный материал, но когда я слышу «вычислить нулевое пространство» (РЕДАКТИРОВАТЬ: в простой для меня форме), я думаю, что QR.

3 голосов
/ 03 января 2012

Я не думаю, что предложенный выше метод всегда дает все пустое пространство. Напомним: «A = QR, где Q = [Q1 Q2], а Q1 m-by-n и Q2 m-by- (mn). Тогда столбцы Q2 образуют нулевое пространство A ^ T»

Действительно, это может дать только подпространство нулевого пространства. Простой контрпример - это когда A = 0, и в этом случае нулевое пространство A ^ T является целым R ^ m.

Следовательно, необходимо проверить и R. Исходя из моего опыта работы с Matlab, если строка R прямая 0, то соответствующий столбец в Q также должен быть базисом нулевого пространства A ^ T. Очевидно, что это наблюдение является эвристическим и зависит от конкретного алгоритма, используемого для разложения QR.

3 голосов
/ 02 февраля 2010

Устранение Гаусса достаточно быстро для матриц 4x3. IIRC Я сделал около 5 миллионов в секунду с Java без параллелизма. С такой маленькой проблемой, вам лучше всего написать код программы (сокращение строк и т. Д.) Самостоятельно; в противном случае вы будете тратить большую часть времени, переводя данные в правильный формат для внешней процедуры.

1 голос
/ 06 февраля 2015

В приведенных выше ответах уже было указано, как можно вычислить нулевое пространство матрицы с использованием подхода QR или SVD. SVD следует отдавать предпочтение, когда требуется точность, см. Также Пустое пространство прямоугольной плотной матрицы .

По состоянию на февраль 2015 года, CUDA 7 (сейчас в версии-кандидате) делает SVD доступным через свою новую библиотеку cuSOLVER. Ниже я приведу пример того, как с помощью SVD cuSOLVER вычислить нулевое пространство матрицы.

Помните, что проблема, на которой вы концентрируетесь, касается вычисления нескольких маленьких матриц, поэтому вам следует адаптировать приведенный ниже пример, используя потоки, чтобы иметь смысл для вашего случая. Чтобы связать поток с каждой задачей, вы можете использовать

cudaStreamCreate()

и

cusolverDnSetStream()

kernel.cu

#include "cuda_runtime.h"
#include "device_launch_paraMeters.h"

#include<iostream>
#include<iomanip>
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
#include<math.h>

#include <cusolverDn.h>
#include <cuda_runtime_api.h>

#include "Utilities.cuh"

/********/
/* MAIN */
/********/
int main(){

    // --- gesvd only supports Nrows >= Ncols
    // --- column major memory ordering

    const int Nrows = 7;
    const int Ncols = 5;

    // --- cuSOLVE input/output parameters/arrays
    int work_size = 0;
    int *devInfo;           gpuErrchk(cudaMalloc(&devInfo,          sizeof(int)));

    // --- CUDA solver initialization
    cusolverDnHandle_t solver_handle;
    cusolverDnCreate(&solver_handle);

    // --- Singular values threshold
    double threshold = 1e-12;

    // --- Setting the host, Nrows x Ncols matrix
    double *h_A = (double *)malloc(Nrows * Ncols * sizeof(double));
    for(int j = 0; j < Nrows; j++)
        for(int i = 0; i < Ncols; i++)
            h_A[j + i*Nrows] = (i + j*j) * sqrt((double)(i + j));

    // --- Setting the device matrix and moving the host matrix to the device
    double *d_A;            gpuErrchk(cudaMalloc(&d_A,      Nrows * Ncols * sizeof(double)));
    gpuErrchk(cudaMemcpy(d_A, h_A, Nrows * Ncols * sizeof(double), cudaMemcpyHostToDevice));

    // --- host side SVD results space
    double *h_U = (double *)malloc(Nrows * Nrows     * sizeof(double));
    double *h_V = (double *)malloc(Ncols * Ncols     * sizeof(double));
    double *h_S = (double *)malloc(min(Nrows, Ncols) * sizeof(double));

    // --- device side SVD workspace and matrices
    double *d_U;            gpuErrchk(cudaMalloc(&d_U,  Nrows * Nrows     * sizeof(double)));
    double *d_V;            gpuErrchk(cudaMalloc(&d_V,  Ncols * Ncols     * sizeof(double)));
    double *d_S;            gpuErrchk(cudaMalloc(&d_S,  min(Nrows, Ncols) * sizeof(double)));

    // --- CUDA SVD initialization
    cusolveSafeCall(cusolverDnDgesvd_bufferSize(solver_handle, Nrows, Ncols, &work_size));
    double *work;   gpuErrchk(cudaMalloc(&work, work_size * sizeof(double)));

    // --- CUDA SVD execution
    cusolveSafeCall(cusolverDnDgesvd(solver_handle, 'A', 'A', Nrows, Ncols, d_A, Nrows, d_S, d_U, Nrows, d_V, Ncols, work, work_size, NULL, devInfo));
    int devInfo_h = 0;  gpuErrchk(cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost));
    if (devInfo_h != 0) std::cout   << "Unsuccessful SVD execution\n\n";

    // --- Moving the results from device to host
    gpuErrchk(cudaMemcpy(h_S, d_S, min(Nrows, Ncols) * sizeof(double), cudaMemcpyDeviceToHost));
    gpuErrchk(cudaMemcpy(h_U, d_U, Nrows * Nrows     * sizeof(double), cudaMemcpyDeviceToHost));
    gpuErrchk(cudaMemcpy(h_V, d_V, Ncols * Ncols     * sizeof(double), cudaMemcpyDeviceToHost));

    for(int i = 0; i < min(Nrows, Ncols); i++) 
        std::cout << "d_S["<<i<<"] = " << std::setprecision(15) << h_S[i] << std::endl;

    printf("\n\n");

    int count = 0;
    bool flag = 0;
    while (!flag) {
        if (h_S[count] < threshold) flag = 1;
        if (count == min(Nrows, Ncols)) flag = 1;
        count++;
    }
    count--;
    printf("The null space of A has dimension %i\n\n", min(Ncols, Nrows) - count);

    for(int j = count; j < Ncols; j++) {
        printf("Basis vector nr. %i\n", j - count);
        for(int i = 0; i < Ncols; i++)
                std::cout << "d_V["<<i<<"] = " << std::setprecision(15) << h_U[j*Ncols + i] << std::endl;
        printf("\n");
    }

    cusolverDnDestroy(solver_handle);

    return 0;

}

Utilities.cuh

#ifndef UTILITIES_CUH
#define UTILITIES_CUH

extern "C" int iDivUp(int, int);
extern "C" void gpuErrchk(cudaError_t);
extern "C" void cusolveSafeCall(cusolverStatus_t);

#endif

Utilities.cu

#include <stdio.h>
#include <assert.h>

#include "cuda_runtime.h"
#include <cuda.h>

#include <cusolverDn.h>

/*******************/
/* iDivUp FUNCTION */
/*******************/
extern "C" int iDivUp(int a, int b){ return ((a % b) != 0) ? (a / b + 1) : (a / b); }

/********************/
/* CUDA ERROR CHECK */
/********************/
// --- Credit to http://stackoverflow.com/questions/14038589/what-is-the-canonical-way-to-check-for-errors-using-the-cuda-runtime-api
void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
   if (code != cudaSuccess)
   {
      fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
      if (abort) { exit(code); }
   }
}

extern "C" void gpuErrchk(cudaError_t ans) { gpuAssert((ans), __FILE__, __LINE__); }

/**************************/
/* CUSOLVE ERROR CHECKING */
/**************************/
static const char *_cudaGetErrorEnum(cusolverStatus_t error)
{
    switch (error)
    {
        case CUSOLVER_STATUS_SUCCESS:
            return "CUSOLVER_SUCCESS";

        case CUSOLVER_STATUS_NOT_INITIALIZED:
            return "CUSOLVER_STATUS_NOT_INITIALIZED";

        case CUSOLVER_STATUS_ALLOC_FAILED:
            return "CUSOLVER_STATUS_ALLOC_FAILED";

        case CUSOLVER_STATUS_INVALID_VALUE:
            return "CUSOLVER_STATUS_INVALID_VALUE";

        case CUSOLVER_STATUS_ARCH_MISMATCH:
            return "CUSOLVER_STATUS_ARCH_MISMATCH";

        case CUSOLVER_STATUS_EXECUTION_FAILED:
            return "CUSOLVER_STATUS_EXECUTION_FAILED";

        case CUSOLVER_STATUS_INTERNAL_ERROR:
            return "CUSOLVER_STATUS_INTERNAL_ERROR";

        case CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED:
            return "CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED";

    }

    return "<unknown>";
}

inline void __cusolveSafeCall(cusolverStatus_t err, const char *file, const int line)
{
    if(CUSOLVER_STATUS_SUCCESS != err) {
        fprintf(stderr, "CUSOLVE error in file '%s', line %d\n %s\nerror %d: %s\nterminating!\n",__FILE__, __LINE__,err, \
                                _cudaGetErrorEnum(err)); \
        cudaDeviceReset(); assert(0); \
    }
}

extern "C" void cusolveSafeCall(cusolverStatus_t err) { __cusolveSafeCall(err, __FILE__, __LINE__); }
1 голос
/ 06 февраля 2010

Мне было интересно, связаны ли матрицы, а не случайные, так что искомые пустые пространства можно рассматривать как одномерные касательные к кривой в N-пространстве (N = 9). Если это так, вы можете ускорить процесс, используя метод Ньютона для решения последовательных экземпляров системы квадратных уравнений Ax = 0, | x | ^ 2 = 1, начиная с предыдущего нулевого пространственного вектора. Метод Ньютона использует первые производные, чтобы сходиться к решению, и поэтому использовал бы устранение Гаусса для решения систем 9x9. Использование этой техники потребовало бы, чтобы вы могли делать небольшие шаги от матрицы к матрице, скажем, путем изменения параметра.

Таким образом, идея заключается в том, что вы инициализируете, используя SVD для первой матрицы, но после этого переходите от матрицы к матрице, используя нулевой пространственный вектор, равный единице, в качестве начальной точки для итерации для следующей. Вам нужна одна или две итерации, чтобы получить сходимость. Если вы не получаете выпуклости, вы используете SVD для перезапуска. Если это то, что у вас есть, это намного быстрее, чем начать заново на каждой матрице.

Я использовал это давным-давно, чтобы отобразить контуры в решениях наборов квадратичных уравнений 50 x 50, связанных с поведением электроэнергетических систем.

1 голос
/ 02 февраля 2010

Я думаю, что наиболее важным для CUDA является поиск алгоритма, который не зависит от условного ветвления (который довольно медленный на графическом оборудовании). Просто, если операторы, которые можно оптимизировать в условное присваивание, намного лучше (или вы можете использовать оператор?:).

При необходимости вы должны быть в состоянии выполнить какую-либо форму поворота с помощью условного присваивания. На самом деле может быть сложнее определить, как хранить ваш результат: если ваша матрица имеет недостаток ранга, что вы хотите, чтобы ваша программа CUDA с этим сделала?

Если вы предполагаете, что ваша матрица 4x3 на самом деле не ранг-дефицитна, вы можете найти свой (одиночный) нулевой вектор вообще без каких-либо условий: матрица достаточно мала, чтобы вы могли эффективно использовать правило Крамера.

На самом деле, поскольку на самом деле вас не волнует масштаб вашего нулевого вектора, вам не нужно делить на определитель - вы можете просто взять определители младших:

    x1 x2 x3
M = y1 y2 y3
    z1 z2 z3
    w1 w2 w3

         |y1 y2 y3|        |x1 x2 x3|       |x1 x2 x3|        |x1 x2 x3|
->  x0 = |z1 z2 z3|  y0 = -|z1 z2 z3|  z0 = |y1 y2 y3|  w0 = -|y1 y2 y3|
         |w1 w2 w3|        |w1 w2 w3|       |w1 w2 w3|        |z1 z2 z3|

Обратите внимание, что эти детерминанты 3х3 являются просто тройными произведениями; Вы можете сохранить вычисления, повторно используя перекрестные произведения.

1 голос
/ 02 февраля 2010

"кажется очень дорогим" - какие данные у вас есть, которые поддерживают это?

Может быть Block Lanczos - это ответ, который вы ищете.

А может быть это .

И JAMA, и Apache Commons Math имеют реализации SVD на Java. Почему бы не взять их и не попробовать их? Получите реальные данные для вашего дела вместо впечатлений. Это не будет стоить вам дорого, поскольку код уже написан и протестирован.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...