numpy.linalg.invとnumpy.linalg.solveを用いた逆行列計算

(Tokyo.SciPyに毎度お邪魔させて頂いているのにも関わらず今まで全くNumPyとかSciPyとか使っていなかったのだけれど) 最近ようやくNumPyやSciPyを(ほんの)少しずつ使うようになってきた.機械学習関連に限らず必ずと言っていいほどお世話になる逆行列計算.

そういえば逆行列と何かの積を取る場合,numpy.linalg.inv()じゃなくてnumpy.linalg.solve()の方が速いよ,ということをどこかで聞いていた気がするので「単位行列との積を取れば逆行列単体の計算も速くなるんじゃね?」ということをお思いついて速度差を比較してみた.

というわけで以下の4つのケースの速度を比較

  • Aの逆行列を計算 (numpy.linalg.inv)
  • Aの逆行列を計算 (numpy.linalg.solve)
  • Aの逆行列を計算 (numpy.linalg.inv) し,bの積を計算 (numpy.dot)
  • Aの逆行列とbの積を計算 (numpy.linalg.solve)

以下の適当コードで実験.

import numpy
import time

n  = 2000
A  = numpy.arange(0, n * n).reshape(n, n) + numpy.identity(n) # ランク落ちを防ぐため
b  = numpy.arange(0, n)

t1 = time.time()
numpy.linalg.inv(A)
t2 = time.time()

t3 = time.time()
numpy.linalg.solve(A, numpy.identity(n))
t4 = time.time()

print "A^(-1) (numpy.linalg.inv()): %f sec." % (t2 - t1)
print "A^(-1) (numpy.linalg.solve()): %f sec." % (t4 - t3)

t5 = time.time()
numpy.dot(numpy.linalg.inv(A), b)
t6 = time.time()

t7 = time.time()
numpy.linalg.solve(A, b)
t8 = time.time()

print "A^(-1) b (numpy.linalg.inv()): %f sec." % (t6 - t5)
print "A^(-1) b (numpy.linalg.solve()): %f sec." % (t8 - t7)


実行結果

% python numpy_prac1.py
A^(-1) (numpy.linalg.inv()): 1.482698 sec.
A^(-1) (numpy.linalg.solve()): 1.485670 sec.
A^(-1) b (numpy.linalg.inv()): 1.493846 sec.
A^(-1) b (numpy.linalg.solve()): 0.450898 sec.

n = 10000の場合,

A^(-1) (numpy.linalg.inv()): 159.744363 sec.
A^(-1) (numpy.linalg.solve()): 159.046202 sec.
A^(-1) b (numpy.linalg.inv()): 159.098885 sec.
A^(-1) b (numpy.linalg.solve()): 41.874028 sec.

なんと,A^(-1) bをsolveで計算した方がAの逆行列計算だけよりもかなり高速な計算が可能な様子 (数値誤差は未評価) 逆行列単体の計算はinv()もsolve()も変わらない様子.

行列によっても値は変わると思うのでこれだけの結果で一般化するのはいろいろ問題あるかと思うけれど,ひとまず

  • 逆行列と何かの積を取る場合にはsolve()で解く
  • 単体の場合はinv()でもsolve()でも変わらない.けれどできればA^(-1) bの形にした方がよい

という知見が得られた.

数値計算の知識があると理由がわかるのだろうけれどまずは経験的に身体に覚えさせるのが大事ということで忘れないうちにメモ.