sapporo20140709

32
Numpy/Scipyを使った効率の良い 数値計算 加藤公一 2014/7/9 Python札幌・ミニ勉強会 1 / 32

Upload: kimikazu-kato

Post on 27-Dec-2014

4.769 views

Category:

Technology


0 download

DESCRIPTION

2014/7/9 Python札幌・ミニ勉強会 の資料です。 Remark.jsを使ってます。ソースコードはこちら: https://github.com/hamukazu/sapporo20140709

TRANSCRIPT

Page 1: Sapporo20140709

Numpy/Scipyを使った効率の良い数値計算

加藤公一

2014/7/9 Python札幌・ミニ勉強会

1 / 32

Page 2: Sapporo20140709

自己紹介加藤公一(きみかず)Twitter : @hamukazu

シルバーエッグ・テクノロジー(株)チーフサイエンティスト博士(情報理工学)

レコメンドのアルゴリズムを考える仕事をしてます。

岩見沢市出身です。

Pythonを本格的に使い出したのは1年半前くらいです。(ビルドツールのwafは前から使ってた)

数学科出身、今まで数値解析、数理最適化などに関わる仕事をしてきました。

C, C++, Haskell, Scalaなどの経験あり。

2 / 32

Page 3: Sapporo20140709

所属会社シルバーエッグ・テクノロジー株式会社

大手ショッピングサイトにレコメンドシステムを提供している会社です。また、レコメンドの技術を利用した広告サービスもやっています。

3 / 32

Page 4: Sapporo20140709

告知PyCon JP 2014 (9/12-15) のトークセッションで講演します。(英語セッション)

今日の話はその縮小版です。(9月に話す資料が今できているはずがない)

まだわからないことも多いので、ご指摘お待ちしております。(「もっといいやりかたあるよ」とか)

4 / 32

Page 5: Sapporo20140709

目次イントロダクション(数値計算について)Numpy/Scipyについて疎行列についてケーススタディ(機械学習でよく出てくるやつ)

お詫び:資料作る時間があまりなかったので、ほとんど絵がないです。必要に応じてホワイトボード等で解説します。(SlideShareで見てる人ごめんなさい)

5 / 32

Page 6: Sapporo20140709

数値計算(数値解析)

常微分方程式、偏微分方程式、各種シミュレーション、機械学習、etc.今日は主に行列計算の話

6 / 32

Page 7: Sapporo20140709

個人的なPython体験最初:遅い!使ってるうちに:遅すぎる!慣れてくると:自分のコードが悪いんじゃね?さらに慣れると:そんなにおそくないじゃん(←いまここ)

7 / 32

Page 8: Sapporo20140709

数値計算のためのプログラミング言語

FORTRAN, C速い最適化が効くコード書くのが大変デバッグも大変

Python生産性高いしかし遅いでもそんなに遅くない

8 / 32

Page 9: Sapporo20140709

Pythonで数値計算をするメリット

生産性が高いデバッグのしやすさ便利なライブラリ群

可視化が(もし必要ならば)楽各種ウェブフレームワークグラフの作成(matplotlib)

9 / 32

Page 10: Sapporo20140709

リスト vs 配列(Numpy)

0から999999の和を計算してみる

a=range(1000000)print sum(a)

import numpy as npa=np.arange(1000000)print a.sum()

ベンチマーク:

>>> from timeit import timeit>>> timeit('sum(range(1000000))','from numpy import arange',number=100)1.927393913269043>>> timeit('arange(1000000).sum()','from numpy import arange',number=100)0.10005307197570801

10 / 32

Page 11: Sapporo20140709

明示的なループは避けるべき

s=0for i in range(1000000): s+=iprint s

これは論外!(常識?)

とてつもなく遅い。

11 / 32

Page 12: Sapporo20140709

ブロードキャスティング

import numpy as npa=np.array([1,2,3])print a*2 # => [2,4,6]print np.sin(a*np.pi/2) # => [1,0,-1] (だだし多少の誤差あり)print a>=2 # => [False, True, True]

このsinのように、配列に適用すると各要素に作用させることができる関数はユニバーサル関数と呼ばれる。

12 / 32

Page 13: Sapporo20140709

要素ごとの積と行列積

>>> 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*barray([[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

Page 14: Sapporo20140709

ブロードキャスティング(2次元)

>>> a=np.arange(12).reshape(3,4)>>> b=np.array([1,2,3,4])>>> aarray([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])>>> a*barray([[ 0, 2, 6, 12], [ 4, 10, 18, 28], [ 8, 18, 30, 44]])

14 / 32

Page 15: Sapporo20140709

インデキシング配列の[]の中身が配列等(シーケンス)だと、複数の要素を同時に取り出す。

例:

>>> a=array([2,3,5,7])>>> i=array([1,3])>>> a[i]array([3, 7])

2次元の場合:

>>> a=arange(9).reshape(3,3)>>> aarray([[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

Page 16: Sapporo20140709

ブロードキャスティング、インデキシングについて...

もっとキモい複雑な使い方はNumpy Medkit参照

16 / 32

Page 17: Sapporo20140709

疎行列とはほとんどの値がゼロである行列。非ゼロ要素の値とインデックスを保持することで、メモリ消費、計算量ともに少なくすることができる。要素とインデックスの持ち方で、様々なデータ形式がある。

17 / 32

Page 18: Sapporo20140709

numpy.sparse

主に3つの疎行列型 :lil_matrix, csr_matrx, csc_matrix (他にもあるが今日は忘れよう)

lil_matrix: 値を詰めるのに便利、実際の計算はcsr_matrixやcsc_matrixに変換してから行う

import scipy.sparse as sparsea=sparse.lil_matrix((100,100))a[0,0]=1.0a[2,0]=2.0a[0,5]=5.0x=a.tocsr()# xに関する計算

csr_matrix: 行を取り出すのは高速。csr_matrix同士の和や積は高速。csc_matrix: 列を取り出すのは高速。csc_matrix同士の和や積は高速。

18 / 32

Page 19: Sapporo20140709

以下応用編

19 / 32

Page 20: Sapporo20140709

ケース1ノルムの計算密な場合

v=np.array([1,2,3,4])print (v**2).sum() # こうするより...print np.dot(v,v) # こっちのほうが速い

疎な場合

a=sp.lil_matrix((100,100))a[0,0]=1a[0,10]=2a[10,5]=3a[50,50]=4a=sp.csr_matrix(a)

print a.multiply(a).sum() # フロベニウスノルムr=a.getrow(0) # 0行目の疎ベクトルprint r.multiply(r).sum() # 疎ベクトルのノルム

multiplyメソッド便利!疎行列でdotメソッドは遅い。(転置行列をとるとCSR→CSC, CSC→CSRに変わってしまう) 20 / 32

Page 21: Sapporo20140709

ケース2シグモイド関数の作用

import numpy as np

def sig_warn(a): return 1/(1+np.exp(-a))

def sig(a): return 1/(1+np.exp(np.where(a<-5e2,5e2,-a)))

実行結果:

>>> x=np.array([-1e100,1,-10])>>> sig_warn(x)sig.py:4: RuntimeWarning: overflow encountered in exp return 1/(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

Page 22: Sapporo20140709

同値な計算:

def sig(a): return [1/(1+np.exp(5e2 if x<-5e2 else -x)) for x in a]

(Warningを無視していいのなら問題はない?)条件分岐させたいときにwhereは便利。

22 / 32

Page 23: Sapporo20140709

ケース3ユニバーサル関数の作用配列の各要素に作用できるユニバーサル関数は便利。

でも、これは疎行列にはそのまま使えない。$x=0$のときに$f(x)=0$となる関数なら、関数を作用させたあとも疎行列のはずである。

csr_matrixに作用させたいものとして話を進める。(csc_matrixの場合も同様)

import numpy as npimport scipy.sparse as spa=np.array([1,2,3])b=sp.lil_matrix((100,100))b[0,0]=1.0b[1,1]=2.0b[2,2]=3.0b=b.toscr()print np.tanh(a) # 計算できるprint np.tanh(b) # エラーになる

23 / 32

Page 24: Sapporo20140709

ではどうするか?

scipy.sparseの内部型を直接いじるここでもブロードキャスティングを利用

24 / 32

Page 25: Sapporo20140709

csr_matrixの内部構造内部構造:data, indices, indptrによって表現

i行目について、行列の要素の値はdata[indptr[i]]~data[indptr[i+1]-1]に格納されていて、非ゼロ要素のインデックスはindices[indptr[i]]~indices[indptr[i+1]-1]に格納されている。

25 / 32

Page 26: Sapporo20140709

例:

>>> b=np.array([[1,0,2],[0,0,3],[4,5,6]])>>> barray([[1, 0, 2], [0, 0, 3], [4, 5, 6]])>>> a=sp.csr_matrix(b)>>> a.dataarray([1, 2, 3, 4, 5, 6])>>> a.indicesarray([0, 2, 2, 0, 1, 2], dtype=int32)>>> a.indptrarray([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

Page 27: Sapporo20140709

csc_matrixの内部構造csr_matrixの行と列を逆にしただけ(詳細略)

>>> b=np.array([[1,0,2],[0,0,3],[4,5,6]])>>> barray([[1, 0, 2], [0, 0, 3], [4, 5, 6]])>>> a=sp.csc_matrix(b)>>> a.dataarray([1, 4, 5, 2, 3, 6])>>> a.indicesarray([0, 2, 2, 0, 1, 2], dtype=int32)>>> a.indptrarray([0, 2, 3, 6], dtype=int32)

27 / 32

Page 28: Sapporo20140709

コンストラクタ内部データ構造を元に疎行列を作れる

csr_matrix((data, indices, indptr), [shape=(M, N)])

28 / 32

Page 29: Sapporo20140709

csr_matrixへのユニバーサル関数の作用indicesとindptrはそのままにしておいて、dataだけ変換すればいい。

先ほどのコンストラクタを利用し、ここで配列へのユニバーサル関数の作用を使う。

29 / 32

Page 30: Sapporo20140709

import numpy as npimport scipy.sparse as sp

a=sp.lil_matrix((5,5))a[0,0]=1.0a[1,1]=2.0a[2,2]=3.0print aprint "------"a=a.tocsr()

b=sp.csr_matrix((np.tanh(a.data),a.indices,a.indptr), shape=a.shape)print b

実行結果:

(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

Page 31: Sapporo20140709

まとめfor文は極力使わない(リスト内包表記も)scipy.sparseは積極的に使おう疎行列型は、必要であれば内部表現をグリグリいじる高速化の知識とあわせて、そこに持っていく数学的同値変形重要

ちょっと中途半端な気もしますが、詳細はPyCon JPで話します。

31 / 32

Page 32: Sapporo20140709

参考文献Gabriele Lanaro, "Python High Performance Programming," PacktPublishing, 2013.Stéfan van der Walt, Numpy Medkit神嶌敏弘「機械学習の Python との出会い」

32 / 32