Numpy/Scipyを使った効率の良い
数値計算
加藤公一
2014/7/9 Python札幌・ミニ勉強会
1 / 32
自己紹介
加藤公一(きみかず)
Twitter : @hamukazu
シルバーエッグ・テクノロジー(株)チーフサイエンティスト
博士(情報理工学)
レコメンドのアルゴリズムを考える仕事をしてます。
岩見沢市出身です。
Pythonを本格的に使い出したのは1年半前くらいです。(ビルドツールのwafは前か
ら使ってた)
数学科出身、今まで数値解析、数理最適化などに関わる仕事をしてきました。
C, C++, Haskell, Scalaなどの経験あり。
2 / 32
所属会社
シルバーエッグ・テクノロジー株式会社
大手ショッピングサイトにレコメンドシステムを提供している会社です。また、レ
コメンドの技術を利用した広告サービスもやっています。
3 / 32
告知
PyCon JP 2014 (9/12-15) のトークセッションで講演します。
(英語セッション)
今日の話はその縮小版です。
(9月に話す資料が今できているはずがない)
まだわからないことも多いので、ご指摘お待ちしております。
(「もっといいやりかたあるよ」とか)
4 / 32
目次
イントロダクション(数値計算について)
Numpy/Scipyについて
疎行列について
ケーススタディ(機械学習でよく出てくるやつ)
お詫び:資料作る時間があまりなかったので、ほとんど絵がないです。必要に応じて
ホワイトボード等で解説します。
(SlideShareで見てる人ごめんなさい)
5 / 32
数値計算(数値解析)
常微分方程式、偏微分方程式、各種シミュレーション、機械学習、etc.
今日は主に行列計算の話
6 / 32
個人的なPython体験
最初:遅い!
使ってるうちに:遅すぎる!
慣れてくると:自分のコードが悪いんじゃね?
さらに慣れると:そんなにおそくないじゃん(←いまここ)
7 / 32
数値計算のための
プログラミング言語
FORTRAN, C
速い
最適化が効く
コード書くのが大変
デバッグも大変
Python
生産性高い
しかし遅い
でもそんなに遅くない
8 / 32
Pythonで数値計算をする
メリット
生産性が高い
デバッグのしやすさ
便利なライブラリ群
可視化が(もし必要ならば)楽
各種ウェブフレームワーク
グラフの作成(matplotlib)
9 / 32
リスト vs 配列(Numpy)
0から999999の和を計算してみる
a=range(1000000)
printsum(a)
importnumpyasnp
a=np.arange(1000000)
printa.sum()
ベンチマーク:
>>>fromtimeitimporttimeit
>>>timeit('sum(range(1000000))','fromnumpyimportarange',number=100)
1.927393913269043
>>>timeit('arange(1000000).sum()','fromnumpyimportarange',number=100)
0.10005307197570801
10 / 32
明示的なループは
避けるべき
s=0
foriinrange(1000000):
s+=i
prints
これは論外!(常識?)
とてつもなく遅い。
11 / 32
ブロードキャスティング
importnumpyasnp
a=np.array([1,2,3])
printa*2#=>[2,4,6]
printnp.sin(a*np.pi/2)#=>[1,0,-1](だだし多少の誤差あり)
printa>=2#=>[False,True,True]
このsinのように、配列に適用すると各要素に作用させることができる関数はユニバ
ーサル関数と呼ばれる。
12 / 32
要素ごとの積と行列積
>>>a=np.array([[1,2,3],[4,5,6],[7,8,9]])
>>>b=np.array([[1,0,0],[0,1,0],[0,0,1]])
>>>a*b
array([[1,0,0],
[0,5,0],
[0,0,9]])
>>>np.dot(a,b)
array([[1,2,3],
[4,5,6],
[7,8,9]])
13 / 32
ブロードキャスティング
(2次元)
>>>a=np.arange(12).reshape(3,4)
>>>b=np.array([1,2,3,4])
>>>a
array([[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9,10,11]])
>>>a*b
array([[0, 2, 6,12],
[4,10,18,28],
[8,18,30,44]])
14 / 32
インデキシング
配列の[]の中身が配列等(シーケンス)だと、複数の要素を同時に取り出す。
例:
>>>a=array([2,3,5,7])
>>>i=array([1,3])
>>>a[i]
array([3,7])
2次元の場合:
>>>a=arange(9).reshape(3,3)
>>>a
array([[0,1,2],
[3,4,5],
[6,7,8]])
>>>i=array([0,1,1])
>>>j=array([0,1,2])
>>>a[i,j]
array([0,4,5])
15 / 32
ブロードキャスティング、
インデキシングについて...
もっとキモい複雑な使い方はNumpy Medkit参照
16 / 32
疎行列とは
ほとんどの値がゼロである行列。
非ゼロ要素の値とインデックスを保持することで、メモリ消費、計算量ともに
少なくすることができる。
要素とインデックスの持ち方で、様々なデータ形式がある。
17 / 32
numpy.sparse
主に3つの疎行列型 :lil_matrix, csr_matrx, csc_matrix(他にもあるが今日は忘れよ
う)
lil_matrix: 値を詰めるのに便利、実際の計算はcsr_matrixやcsc_matrixに変換し
てから行う
importscipy.sparseassparse
a=sparse.lil_matrix((100,100))
a[0,0]=1.0
a[2,0]=2.0
a[0,5]=5.0
x=a.tocsr()
#xに関する計算
csr_matrix: 行を取り出すのは高速。csr_matrix同士の和や積は高速。
csc_matrix: 列を取り出すのは高速。csc_matrix同士の和や積は高速。
18 / 32
以下応用編
19 / 32
ケース1
ノルムの計算
密な場合
v=np.array([1,2,3,4])
print(v**2).sum()#こうするより...
printnp.dot(v,v)#こっちのほうが速い
疎な場合
a=sp.lil_matrix((100,100))
a[0,0]=1
a[0,10]=2
a[10,5]=3
a[50,50]=4
a=sp.csr_matrix(a)
printa.multiply(a).sum()#フロベニウスノルム
r=a.getrow(0)#0行目の疎ベクトル
printr.multiply(r).sum()#疎ベクトルのノルム
multiplyメソッド便利!
疎行列でdotメソッドは遅い。(転置行列をとるとCSR→CSC, CSC→CSRに変わってし
まう) 20 / 32
ケース2
シグモイド関数の作用
importnumpyasnp
defsig_warn(a):
return1/(1+np.exp(-a))
defsig(a):
return1/(1+np.exp(np.where(a<-5e2,5e2,-a)))
実行結果:
>>>x=np.array([-1e100,1,-10])
>>>sig_warn(x)
sig.py:4:RuntimeWarning:overflowencounteredinexp
return1/(1+np.exp(-a))
array([ 0.00000000e+00, 7.31058579e-01, 4.53978687e-05])
>>>sig(x)
array([ 7.12457641e-218, 7.31058579e-001, 4.53978687e-005])
σ(x) =
1
1 + e
−x
21 / 32
同値な計算:
defsig(a):
return[1/(1+np.exp(5e2ifx<-5e2else-x))forxina]
(Warningを無視していいのなら問題はない?)
条件分岐させたいときにwhereは便利。
22 / 32
ケース3
ユニバーサル関数の作用
配列の各要素に作用できるユニバーサル関数は便利。
でも、これは疎行列にはそのまま使えない。$x=0$のときに$f(x)=0$となる関数な
ら、関数を作用させたあとも疎行列のはずである。
csr_matrixに作用させたいものとして話を進める。(csc_matrixの場合も同様)
importnumpyasnp
importscipy.sparseassp
a=np.array([1,2,3])
b=sp.lil_matrix((100,100))
b[0,0]=1.0
b[1,1]=2.0
b[2,2]=3.0
b=b.toscr()
printnp.tanh(a)#計算できる
printnp.tanh(b)#エラーになる
23 / 32
ではどうするか?
scipy.sparseの内部型を直接いじる
ここでもブロードキャスティングを利用
24 / 32
csr_matrixの内部構造
内部構造:data, indices, indptrによって表現
i行目について、行列の要素の値はdata[indptr[i]]~data[indptr[i+1]-1]に格納さ
れていて、非ゼロ要素のインデックス
はindices[indptr[i]]~indices[indptr[i+1]-1]に格納されている。
25 / 32
例:
>>>b=np.array([[1,0,2],[0,0,3],[4,5,6]])
>>>b
array([[1,0,2],
[0,0,3],
[4,5,6]])
>>>a=sp.csr_matrix(b)
>>>a.data
array([1,2,3,4,5,6])
>>>a.indices
array([0,2,2,0,1,2],dtype=int32)
>>>a.indptr
array([0,2,3,6],dtype=int32)
1行目の情報:data[0]~data[1], indices[0]~indices[1]
2行目の情報: data[2], indices[2]
3行目の情報:data[3]~data[5], indices[3]~indices[5]
26 / 32
csc_matrixの内部構造
csr_matrixの行と列を逆にしただけ(詳細略)
>>>b=np.array([[1,0,2],[0,0,3],[4,5,6]])
>>>b
array([[1,0,2],
[0,0,3],
[4,5,6]])
>>>a=sp.csc_matrix(b)
>>>a.data
array([1,4,5,2,3,6])
>>>a.indices
array([0,2,2,0,1,2],dtype=int32)
>>>a.indptr
array([0,2,3,6],dtype=int32)
27 / 32
コンストラクタ
内部データ構造を元に疎行列を作れる
csr_matrix((data,indices,indptr),[shape=(M,N)])
28 / 32
csr_matrixへの
ユニバーサル関数の作用
indicesとindptrはそのままにしておいて、dataだけ変換すればいい。
先ほどのコンストラクタを利用し、ここで配列へのユニバーサル関数の作用を使
う。
29 / 32
importnumpyasnp
importscipy.sparseassp
a=sp.lil_matrix((5,5))
a[0,0]=1.0
a[1,1]=2.0
a[2,2]=3.0
printa
print"------"
a=a.tocsr()
b=sp.csr_matrix((np.tanh(a.data),a.indices,a.indptr),
shape=a.shape)
printb
実行結果:
(0,0) 1.0
(1,1) 2.0
(2,2) 3.0
-----
(0,0) 0.761594155956
(1,1) 0.964027580076
(2,2) 0.995054753687
30 / 32
まとめ
for文は極力使わない(リスト内包表記も)
scipy.sparseは積極的に使おう
疎行列型は、必要であれば内部表現をグリグリいじる
高速化の知識とあわせて、そこに持っていく数学的同値変形重要
ちょっと中途半端な気もしますが、詳細はPyCon JPで話します。
31 / 32
参考文献
Gabriele Lanaro, "Python High Performance Programming," Packt
Publishing, 2013.
Stéfan van der Walt, Numpy Medkit
神嶌敏弘「機械学習の Python との出会い」
32 / 32

Sapporo20140709