使用加速框架进行矩阵乘法和求逆问题

7

我正在尝试在Objective-C中计算两个矩阵的乘积。 我已经在Xcode项目中导入了加速框架,编译一切都很好。 我在计算器上进行了矩阵乘法并得到了正确的值,但是运行代码时却没有得到正确结果。 这是我定义矩阵的方式:

float matrixA [3][3] = {{-.045, -.141, -.079}, {-.012, -.079, .0578}, {.112, -.011, -.0830}};

float matrixB [3][1] = {{40}, {-61}, {98}};

我随后使用了加速框架中的mmul函数。

vDSP_mmul(&matrixA[3][3], 1, &matrixB[3][1], 1, &results[3][1], 1, 3, 1, 3);

数组结果是通过以下操作创建的...
float results[3][1];

我将以下内容放在了一个空项目的viewDidLoad方法中,以便能够NSLog输出结果。当我把matrixA乘以matrixB时,应该得到如下结果:(-1, 10, -3)。然而,NSLog输出的结果是(-0.045, 0.000, 0.000),这是不正确的,我不明白为什么会这样。我的理解是,此函数会将两个矩阵相乘。但我不确定它正在做什么。我可能输入了一些不正确的内容,希望有人能帮助我。
顺便说一下,matrixA实际上是另一个矩阵的逆矩阵。然而,我找不到accelerate框架中任何可以求逆的函数。我确实找到了一个叫做的函数。
sgetrf_

我使用LAPACK但并不是很理解。如果有人能提供帮助、建议或一些教程让我跟随学习,我将不胜感激。我已经在网上查找了三天了!

2个回答

24
让我们走过三种不同的方式来计算这个问题(包括反向)在OS X或iOS上。首先,让我们做你开始时所尝试的:使用加速框架中的LAPACK和BLAS进行计算。请注意,我使用了BLAS函数 cblas_sgemv 而不是 vDSP_mmul 来执行矩阵向量乘法。我这样做有三个原因。首先,在使用LAPACK的代码中,这更符合惯例。其次,LAPACK真正希望矩阵以列主顺序存储,而BLAS支持,而vDSP则不支持。最后, vDSP_mmul 实际上只是BLAS矩阵乘法例程的包装器,因此我们可以切掉中间人。这将在OS X回到10.2和iOS回到4.0上工作-即任何您今天可以合理预期遇到的目标。
#include <Accelerate/Accelerate.h>
#include <stdio.h>
#include <string.h>

void using_blas_and_lapack(void) {
    printf("Using BLAS and LAPACK:\n");
    //  Note: you'll want to store A in *column-major* order to use it with
    //  LAPACK (even though it's not strictly necessary for this simple example,
    //  if you try to do something more complex you'll need it).
    float A[3][3] = {{-4,-3,-5}, {6,-7, 9}, {8,-2,-1}};
    float x[3] = { -1, 10, -3};
    //  Compute b = Ax using cblas_sgemv.
    float b[3];
    cblas_sgemv(CblasColMajor, CblasNoTrans, 3, 3, 1.f, &A[0][0], 3, x, 1, 0.f, b, 1);
    printf("b := A x = [ %g, %g, %g ]\n", b[0], b[1], b[2]);
    //  You probably don't actually want to compute A^-1; instead you simply
    //  want to solve the equation Ay = b for y (like y = A\b in matlab).  To
    //  do this with LAPACK, you typically use the routines sgetrf and sgetrs.
    //  sgetrf will overwrite its input matrix with the LU factorization, so
    //  we'll make a copy first in case you need to use A again.
    float factored[3][3];
    memcpy(factored, A, sizeof factored);
    //  Now that we have our copy, go ahead and factor it using sgetrf.
    __CLPK_integer n = 3;
    __CLPK_integer info = 0;
    __CLPK_integer ipiv[3];
    sgetrf_(&n, &n, &factored[0][0], &n, ipiv, &info);
    if (info != 0) { printf("Something went wrong factoring A\n"); return; }
    //  Finally, use the factored matrix to solve y = A\b via sgetrs.  Just
    //  like sgetrf overwrites the matrix with its factored form, sgetrs
    //  overwrites the input vector with the solution, so we'll make a copy
    //  before calling the function.
    float y[3];
    memcpy(y, b, sizeof y);
    __CLPK_integer nrhs = 1;
    sgetrs_("No transpose", &n, &nrhs, &factored[0][0], &n, ipiv, y, &n, &info);
    printf("y := A\\b = [ %g, %g, %g ]\n\n", y[0], y[1], y[2]);
}

接下来,让我们使用 LinearAlgebra.h 进行计算,这是 iOS 8.0 和 OS X 10.10 中加速框架的一个新功能。它在几乎所有这些计算所需的簿记方面进行了抽象处理,但它仅提供了 LAPACK 和 BLAS 中可用完整功能的一小部分。请注意,虽然需要一些样板文件将我们的原始数组转换为和从 la_objects 转换回来,但一旦拥有了它们,实际计算是非常简单的。

#include <Accelerate/Accelerate.h>
#include <stdio.h>

void using_la(void) {
    printf("Using LA:\n");
    //  LA accepts row-major as well as column-major data, but requires a short
    //  two-step dance to get our data in.
    float Adata[3][3] = {{-4, 6, 8}, {-3,-7,-2}, {-5, 9,-1}};
    float xdata[3] = { -1, 10, -3};
    la_object_t A = la_matrix_from_float_buffer(&Adata[0][0], 3, 3, 3, LA_NO_HINT, LA_DEFAULT_ATTRIBUTES);
    la_object_t x = la_vector_from_float_buffer(xdata, 3, 1, LA_DEFAULT_ATTRIBUTES);
    //  Once our data is stored as LA objects, it's easy to do the computation:
    la_object_t b = la_matrix_product(A, x);
    la_object_t y = la_solve(A, b);
    //  And finally we need to get our data back out:
    float bdata[3];
    if (la_vector_to_float_buffer(bdata, 1, b) != LA_SUCCESS) {
        printf("Something went wrong computing b.\n");
        return;
    } else printf("b := A x = [ %g, %g, %g ]\n", bdata[0], bdata[1], bdata[2]);
    float ydata[3];
    if (la_vector_to_float_buffer(ydata, 1, y) != LA_SUCCESS) {
        printf("Something went wrong computing y.\n");
        return;
    } else printf("y := A\\b = [ %g, %g, %g ]\n\n", ydata[0], ydata[1], ydata[2]);
}

最后,还有一种方法。如果你的矩阵只是3x3的话,我会使用这种方法,因为之前的两种方法所涉及到的开销将会淹没实际计算。然而,这种方法仅适用于大小为4x4或更小的矩阵。在iOS 8.0和OS X 10.10中有一个专门针对小矩阵和向量数学的新头文件,使这个过程变得非常简单和高效:

#include <simd/simd.h>
#include <stdio.h>

void using_simd(void) {
    printf("Using <simd/simd.h>:\n");
    matrix_float3x3 A = matrix_from_rows((vector_float3){-4,  6,  8},
                                         (vector_float3){-3, -7, -2},
                                         (vector_float3){-5,  9, -1});
    vector_float3 x = { -1, 10, -3 };
    vector_float3 b = matrix_multiply(A, x);
    printf("b := A x = [ %g, %g, %g ]\n", b[0], b[1], b[2]);
    vector_float3 y = matrix_multiply(matrix_invert(A),b);
    printf("y := A^-1 b = [ %g, %g, %g ]\n\n", y[0], y[1], y[2]);
}

最后,让我们仔细检查一下这些结果是否完全相同(除了小的四舍五入差异):

scanon$ xcrun -sdk macosx clang matrix.m -framework Accelerate  && ./a.out
Using BLAS and LAPACK:
b := A x = [ 40, -61, 98 ]
y := A\b = [ -0.999999, 10, -3 ]

Using LA:
b := A x = [ 40, -61, 98 ]
y := A\b = [ -1, 10, -3 ]

Using <simd/simd.h>:
b := A x = [ 40, -61, 98 ]
y := A^-1 b = [ -1, 10, -3 ]

1
哇!!非常感谢。顺便问一下,你是老师吗?这个解释是我在研究中读到的最好的解释。我希望我能接受两个答案。我给你点赞! - Douglas
我过去曾经教过,但在这种情况下,我非常非常熟悉相关的库。 - Stephen Canon

7
您正在传递指向矩阵“末尾之后”的内存地址的指针。
修复方法如下(未经测试的代码):
vDSP_mmul(&matrixA[0][0], 1, &matrixB[0][0], 1, &results[0][0], 1, 3, 1, 3);

在C语言中,将数组传递给函数实际上会传递一个指向数组第一个元素的指针,这似乎是您需要做的。您之前传递了一个指向矩阵中最后一个数组元素之后直接的内存地址的指针,这意味着您将得到无意义的结果或崩溃。


谢谢您的快速回复。然而,当我去掉&和矩阵的大小,让我的代码看起来像你的时候,我收到了一个警告:不兼容的指针类型,将float [3][3](或[3][1])传递给常量float类型的参数。这就是为什么我用那种方式写的原因,为了消除那个警告。 - Douglas
1
为了避免那个警告,你需要传递 &matrix[0][0] 而不是 &matrix[3][3] 或者 &matrix[3][1]。这会给你一个指向矩阵开头的指针。 - StilesCrisis
好的,搞定了!非常感谢您的时间。第二部分,矩阵求逆有什么想法吗?谢谢。 - Douglas
1
我绝不是 vDSP 专家,只是擅长发现指针数学错误。 - StilesCrisis
1
再次感谢,我想我在求逆方面发现了一些新的东西,我会尝试它们。 - Douglas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接