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

时间:2014-04-17 20:55:52

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

我试图优化使用内点法解决大型稀疏非线性系统的一段代码。在更新步骤中,这涉及计算Hessian矩阵H,渐变g,然后求解d中的H * 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

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

2 个答案:

答案 0 :(得分:3)

在计算三个对角阵列中的产品M.T * D * M时,我接近4倍的加速。如果d0d1M的主对角线和上对角线,而dD的主对角线,则以下代码会创建{{1}直接:

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)) 采用CSR格式,则可以将Md0提取为d1d0 = 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 要慢几倍。

答案 1 :(得分:0)

我尝试运行您的测试用例并遇到np.dot(N, M)问题。我没有深入研究它,但我认为我的numpy /稀疏组合(两者都很新)在稀疏数组上使用np.dot时遇到了问题。

但是H = -Hsig - z*M.T.dot(N.dot(M))运行得很好。这使用sparse dot

我没有运行个人资料,但这里有几个部分的Ipython计时。生成数据所需的时间比完成双点的时间要长。

In [37]: timeit Hsig,M,n,z=make_toy_data()
1 loops, best of 3: 2 s per loop

In [38]: timeit N = sparse.diags(1. / n ** 2, 0, format='csc')
1 loops, best of 3: 377 ms per loop

In [39]: timeit H = -Hsig - z*M.T.dot(N.dot(M))
1 loops, best of 3: 1.55 s per loop

H

<2000000x2000000 sparse matrix of type '<type 'numpy.float64'>'
    with 5999980 stored elements in Compressed Sparse Column format>