numpy - 创建 A.T * diag(b) * A + C 形式的稀疏矩阵的最快方法?

标签 numpy scipy linear-algebra sparse-matrix hessian-matrix

我正在尝试优化一段使用内点法求解大型稀疏非线性系统的代码。在更新步骤中,涉及计算 Hessian 矩阵 H、梯度 g,然后求解 H * d = 中的 d -g 获取新的搜索方向。

Hessian 矩阵具有以下形式的对称三对角结构:

A.T * diag(b) * A + C

我已经运行了line_profiler关于相关的特定功能:

Line # Hits     Time  Per Hit % Time Line Contents
==================================================
   386                               def _direction(n, res, M, Hsig, scale_var, grad_lnprior, z, fac):
   387                               
   388                                   # gradient
   389   44  1241715  28220.8    3.7     g = 2 * scale_var * res - grad_lnprior + z * np.dot(M.T, 1. / n)
   390                               
   391                                   # hessian
   392   44  3103117  70525.4    9.3     N = sparse.diags(1. / n ** 2, 0, format=FMT, dtype=DTYPE)
   393   44 18814307 427597.9   56.2     H = - Hsig - z * np.dot(M.T, np.dot(N, M))    # slow!
   394                                   
   395                                   # update direction
   396   44 10329556 234762.6   30.8     d, fac = my_solver(H, -g, fac)
   397                                   
   398   44      111      2.5    0.0     return d, fac

从输出来看,很明显构造 H 是迄今为止成本最高的步骤 - 它比实际求解新方向所需的时间要长得多。

HsigM 都是 CSC 稀疏矩阵,n 是稠密向量,z 是标量。我使用的求解器要求 H 为 CSC 或 CSR 稀疏矩阵。

这是一个函数,它生成一些与我的真实矩阵具有相同格式、维度和稀疏性的玩具数据:

import numpy as np
from scipy import sparse

def make_toy_data(nt=200000, nc=10):

    d0 = np.random.randn(nc * (nt - 1))
    d1 = np.random.randn(nc * (nt - 1))
    M = sparse.diags((d0, d1), (0, nc), shape=(nc * (nt - 1), nc * nt),
                     format='csc', dtype=np.float64)

    d0 = np.random.randn(nc * nt)
    Hsig = sparse.diags(d0, 0, shape=(nc * nt, nc * nt), format='csc',
                        dtype=np.float64)

    n = np.random.randn(nc * (nt - 1))
    z = np.random.randn()

    return Hsig, M, n, z

这是我构建 H 的原始方法:

def original(Hsig, M, n, z):
    N = sparse.diags(1. / n ** 2, 0, format='csc')
    H = - Hsig - z * np.dot(M.T, np.dot(N, M))    # slow!
    return H

时间安排:

%timeit original(Hsig, M, n, z)
# 1 loops, best of 3: 483 ms per loop

有没有更快的方法来构造这个矩阵?

最佳答案

在计算三个对角数组的乘积 M.T * D * M 时,我的速度接近 4 倍。如果d0d1M的主对角线和上对角线,并且d的主对角线>D,那么下面的代码直接创建M.T * D * M:

def make_tridi_bis(d0, d1, d, nc=10):
    d00 = d0*d0*d
    d11 = d1*d1*d
    d01 = d0*d1*d
    len_ = d0.size
    data = np.empty((3*len_ + nc,))
    indices = np.empty((3*len_ + nc,), dtype=np.int)
    # Fill main diagonal
    data[:2*nc:2] = d00[:nc]
    indices[:2*nc:2] = np.arange(nc)
    data[2*nc+1:-2*nc:3] = d00[nc:] + d11[:-nc]
    indices[2*nc+1:-2*nc:3] = np.arange(nc, len_)
    data[-2*nc+1::2] = d11[-nc:]
    indices[-2*nc+1::2] = np.arange(len_, len_ + nc)
    # Fill top diagonal
    data[1:2*nc:2] = d01[:nc]
    indices[1:2*nc:2] = np.arange(nc, 2*nc)
    data[2*nc+2:-2*nc:3] = d01[nc:]
    indices[2*nc+2:-2*nc:3] = np.arange(2*nc, len_+nc)
    # Fill bottom diagonal
    data[2*nc:-2*nc:3] = d01[:-nc]
    indices[2*nc:-2*nc:3] = np.arange(len_ - nc)
    data[-2*nc::2] = d01[-nc:]
    indices[-2*nc::2] = np.arange(len_ - nc ,len_)

    indptr = np.empty((len_ + nc + 1,), dtype=np.int)
    indptr[0] = 0
    indptr[1:nc+1] = 2
    indptr[nc+1:len_+1] = 3
    indptr[-nc:] = 2
    np.cumsum(indptr, out=indptr)

    return sparse.csr_matrix((data, indices, indptr), shape=(len_+nc, len_+nc))

如果您的矩阵M采用CSR格式,则可以将d0d1提取为d0 = M.data[: :2]d1 = M.data[1::2],我修改了你的玩具数据制作例程以返回这些数组,这就是我得到的:

In [90]: np.allclose((M.T * sparse.diags(d, 0) * M).A, make_tridi_bis(d0, d1, d).A)
Out[90]: True

In [92]: %timeit make_tridi_bis(d0, d1, d)
10 loops, best of 3: 124 ms per loop

In [93]: %timeit M.T * sparse.diags(d, 0) * M
1 loops, best of 3: 501 ms per loop

上述代码的全部目的是利用非零条目的结构。如果您画出要相乘的矩阵的图表,则相对容易使自己相信主对角线 (d_0) 以及顶部和底部对角线 (d_1)得到的三对角矩阵很简单:

d_0 = np.zeros((len_ + nc,))
d_0[:len_] = d00
d_0[-len_:] += d11

d_1 = d01

该函数中的其余代码只是直接构建三对角矩阵,因为使用上述数据调用 sparse.diags 速度要慢几倍。

关于numpy - 创建 A.T * diag(b) * A + C 形式的稀疏矩阵的最快方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23143285/

相关文章:

python - 通过元组键列表分配矩阵元素

python - MATLAB 到 Python 代码转换(NumPy、SciPy、MatplotLib?)

python - 问题拟合曲线:

linear-algebra - 求解线性不等式系统的算法

image-processing - 仿射变换检索中的最小点数?

python - 为什么我无法在 Ubuntu 16.04 上使用 pip 安装 numpy 包?

python - 延长线与另一条线平滑连接

python - 如何选择 numpy 数组中的特定列?

python - scipy 优化 - fmin Nelder-Mead 单纯形

python - 快速加权散点矩阵计算