Successfully reported this slideshow.                                        Upcoming SlideShare
×

# NumPy闇入門

16,276 views

Published on

NumPyのちょっと不思議な挙動などをまとめた資料です。クイズ形式で紹介＆解説をします。

PFIセミナー2016/01/28：NumPy闇入門

2016/3/20 問題の一部を修正

Published in: Engineering
• Full Name
Comment goes here.

Are you sure you want to Yes No Are you sure you want to  Yes  No

### NumPy闇入門

1. 1. NumPy闇入門 2016/1/28 PFIセミナー （株）Preferred Networks 奥田 遼介
2. 2. 自己紹介 奥田 遼介  -2014東北大学 修士  文字列処理など  2014 （株）プリファードインフラストラクチャー  2014- （株）プリファードネットワークス  映像解析系、製造業系にかかわる研究開発  ChainerやCuPyの開発  趣味  読書、高速化
3. 3. NumPyとはなにか  Python上で数値計算を効率的に行うためのライブラリ  NumPyがあるからPythonを使うくらい重要 In : import numpy as np In : np.ones((2, 1)) + np.arange(3) Out: array([[ 1., 2., 3.], [ 1., 2., 3.]])
4. 4. NumPy闇入門とは  Chainer開発勢がChainerやCuPyを作る過程で遭遇した NumPyのちょっと変な挙動（闇）を集めたもの  バグ、仕様なのか不明な場合も・・・
5. 5. NumPy闇入門とは  Chainer開発勢がChainerやCuPyを作る過程で遭遇した NumPyのちょっと変な挙動（闇）を集めたもの  バグ、仕様なのか不明な場合も・・・  普通の人々はたいてい気づかないものばかりです  ずっと気づかずにいた方が幸せです  今回はクイズ形式で１０問の闇を用意しました  クイズの作りが悪いのでだいぶ簡単です（たぶん）  何問正解できるか挑戦してみてください  断りが無い場合以下の環境を想定しています  x86_64＋Ubuntu64bit + Python 2.7 + NumPy 1.10.4
6. 6. 注意事項  変な挙動を「闇」と表現してしますが、NumPyを貶める ような意図はありません。NumPyはすばらしいです  紹介する闇に関しては解説をしていますが、NumPyの ソースコードを読まないと（読んでも）理解できない箇 所があると思います。ご了承ください
7. 7. 型の闇
8. 8. Pythonの型、NumPyの型  Pythonの数値型は少ない  bool  int, (long)  float  complex  NumPyはたくさん  NumPyはPure Cのライブラリ  演算効率のためにC互換の型が沢山  CとPythonの融合した型システム  →闇の温床
9. 9. NumPyの型の表現  2～3種類の表現がある  np.int32 # Python type  np.dtype(‘int32’) # dtype  ‘i’ # Character code  お互いに変換可能  np.dtype(np.int32) #=> dtype(‘int32’)  np.dtype(‘i’) #=> dtype(‘int32’)  np.dtype(‘int32’).type #=> np.int32  np.dtype(‘int32’).char #=> ‘i’
10. 10. 型の闇 練習問題  np.zeros(2, np.int32) + np.zeros(2, float) の演算結果のdtype は？  A. dtype(‘float32’)  B. dtype(‘float64’)  C. dtype(‘float’)  D. わからない
11. 11. 型の闇 練習問題  np.zeros(2, np.int32) + np.zeros(2, float) の演算結果のdtype は？  A. dtype(‘float32’)  B. dtype(‘float64’)  C. dtype(‘float’)  D. わからない  正解はB  int32を十分に表現可能なfloat64にキャストされて演算が行われ ます  選択肢C のようなdtypeは無いです dtype(‘float64’)になります
12. 12. 型の闇 その１  np.zeros(2, np.int64) + np.zeros(2, np.float64) の演算結果の dtypeは？  A. dtype(‘float32’)  B. dtype(‘float64’)  C. dtype(‘float128’)  D. わからない
13. 13. 型の闇 その１  np.zeros(2, np.int64) + np.zeros(2, np.float64) の演算結果の dtypeは？  A. dtype(‘float32’)  B. dtype(‘float64’)  C. dtype(‘float128’)  D. わからない  正解はB  np.float128は実在します  C言語相当なので自動でnp.float128にはならない  np.can_cast(np.zeros(2,np.int32), np.float32) # => False  np.can_cast(np.zeros(2,np.int64), np.float64) # => True
14. 14. 型の闇 その２  0 + np.float16(0) の演算結果のdtypeは？  A. dtype(‘float16’)  B. dtype(‘float32’)  C. dtype(‘float64’)  D. わからない
15. 15. 型の闇 その２  0 + np.float16(0) の演算結果のdtypeは？  A. dtype(‘float16’)  B. dtype(‘float32’)  C. dtype(‘float64’)  D. わからない  正解はC  0はint つまりこの環境ではnp.int64となる  np.int64(0) + np.float16(0)という計算に相当するため、np.float64が 正解
16. 16. 型の闇 その3  変数aの型がintの時、a + np.zeros(2, np.float32) の演算結 果のdtypeは？  A. dtype(‘float16’)  B. dtype(‘float32’)  C. dtype(‘float64’)  D. わからない
17. 17. 型の闇 その3  変数aの型がintの時、a + np.zeros(2, np.float32) の演算結 果のdtypeは？  A. dtype(‘float16’)  B. dtype(‘float32’)  C. dtype(‘float64’)  D. わからない  正解：D  変数aの値の大きさによってnp.float32か、np.float64になる  例えば -32768<= a <= 65535 であればnp.float32  変数aがスカラー相当の値の場合は同じ挙動  Chainerではまることが多いケースなので注意
18. 18. 型の闇 その４  np.array(, np.int32)+ np.zeros(2, np.float32) の演算結果 のdtypeは？  A. dtype(‘float16’)  B. dtype(‘float32’)  C. dtype(‘float64’)  D. わからない
19. 19. 型の闇 その４  np.array(, np.int32) + np.zeros(2, np.float32) の演算結果 のdtypeは？  A. dtype(‘float16’)  B. dtype(‘float32’)  C. dtype(‘float64’)  D. わからない  正解：C  np.array(, np.int32)はスカラー相当ではないので、普通のキャ ストが行われる
20. 20. 型の昇格の話  スカラー相当の値とは？  Pythonのbool, int,np.int32, np.float64・・・  0次元のndarray：shapeが長さ0であるもの  1次元以上だとスカラー相当ではないので注意  スカラー、ベクトル同士の規則は表現精度に基づき決定  スカラーとベクトルの場合は以下の通り  Kindの大きさを比較（bool -> 0, int, uint -> 1, float ->2）  スカラー側のkindが大きい→dtypeの表現精度に基づき決定  それ以外→ スカラー値を表現可能な最小の型を利用  (np.array([10,10], np.int16) + np.float16(10)).dtype # => dtype('float32')  (np.array([10,10], np.float16) + np.int16(10)).dtype # => dtype('float16')  255まではnp.float16になる
21. 21. 型の闇 その５  np.longlong, np.int_, np.intp はnp.dtype化すると全て 「dtype(‘int64’)」になる。正しいのはどれ？  A. np.longlong は np.int64 と等しくない  B. np.int_ は np.int64 と等しくない  C. np.intp は np.int64 と等しくない  D. 全部 np.int64 と等しい
22. 22. 型の闇 その５  np.longlong, np.int_, np.intp はnp.dtype化すると全て 「dtype(‘int64’)」になる。正しいのはどれ？  A. np.longlong は np.int64 と等しくない  B. np.int_ は np.int64 と等しくない  C. np.intp は np.int64 と等しくない  D. 全部 np.int64 と等しい  正解：A  numpyには、nump.int64が2つある（こともある）  Windowsだとnumpy.int32が2つある（こともある）  C言語でlong long とintと longが分かれている事に由来する
23. 23. なぜこんなに複雑になってしまったのか？  わかりません  推測：使っていて良い感じになるようにしたかった  ちなみに0次元ndarrayは気づくと単なるスカラー変数になってい たりします  type(np.array(10) + 10) #=> numpy.int64  CuPyはどこまで同じ挙動を再現しているのか？  0次元ndarrayと1次元以上のndarrayの演算の時のみ違う挙動  GPU上にある値で型が変わる挙動は性能への影響が大きいため  どうやってこの挙動を再現したのか？  本家のコードを読みました  バージョンが変わると結構変わったりします  v1.10でデフォルトのcasting ruleが変わったり
24. 24. 関数の闇
25. 25. 関数の闇 その１ expand_dimsの闇  expand_dimsは長さ1の次元を挿入する関数である  np.expand_dims(np.zeros((2,3,4)), -1).shape  #=>(2, 3, 4, 1)  -1の時は一番後ろに次元を挿入してくれる  np.expand_dims(np.zeros((2,3,4)),-5).shape は？  A. (1, 2, 3, 4)  B. (2, 3, 4, 1)  C. (2, 3, 1, 4)  D. 例外で落ちる
26. 26. 関数の闇 その１ expand_dimsの闇  expand_dimsは長さ1の次元を挿入する関数である  np.expand_dims(np.zeros((2,3,4)), -1).shape  #=>(2, 3, 4, 1)  -1の時は一番後ろに次元を挿入してくれる  np.expand_dims(np.zeros((2,3,4)),-5).shape は？  A. (1, 2, 3, 4)  B. (2, 3, 4, 1)  C. (2, 3, 1, 4)  D. 例外で落ちる  正解はC
27. 27. 関数の闇 その１ expand_dimsの闇  expand_dimsの正の方向は循環せず止まる  0 #=> (1, 2, 3, 4)  1 #=> (2, 1, 3, 4)  2 #=> (2, 3, 1, 4)  3 #=> (2, 3, 4, 1)  4 #=> (2, 3, 4, 1)  マイナスの方は不完全な2週目がある  -3 #=> (2, 1, 3, 4)  -4 #=> (1, 2, 3, 4)  -5 #=> (2, 3, 1, 4)  推測： Pythonの配列では普通に負のインデックスが使える  -1を特別扱いした→なんか変な挙動になった
28. 28. 関数の闇 その２ choose  np.chooseはnp.whereの3引数以上対応の関数です  np.choose([0, 2, 2, 1], [10, 20, 30])  # => array([10, 30, 30, 20])  この関数の第2引数にはある制約があります。それは何？  A. dtypeがnp.float系だと使えない  B. 配列の長さが1だと使えない  C. 配列の長さが50以上だと使えない  D. A,B,Cは間違い
29. 29. 関数の闇 その２ choose  np.chooseはnp.whereの3引数以上対応の関数です  np.choose([0, 2, 2, 1], [10, 20, 30])  # => array([10, 30, 30, 20])  この関数の第2引数にはある制約があります。それは何？  A. dtypeがnp.float系だと使えない  B. 配列の長さが1だと使えない  C. 配列の長さが50以上だと使えない  D. A,B,Cは間違い  正解はC  np.chooseが中で第2引数を展開して関数を呼び出している  その時の引数の数の制限（ NPY_MAXARGS）により、最大31択ま でしかできない
30. 30. 関数の闇 その３ 真偽値演算  次の真偽値演算のうち正しいのはどれ？  A  True + True #=> True  np.bool_(True) + np.bool_(True) #=> 2  B  True + True #=> 2  np.bool_(True) + np.bool_(True) #=> True  C：環境依存でAかBのどちらかになる  D：環境依存でAかBかそれ以外の何かになる
31. 31. 関数の闇 その３ 真偽値演算  次の真偽値演算のうち正しいのはどれ？  A  True + True #=> True  np.bool_(True) + np.bool_(True) #=> 2  B  True + True #=> 2  np.bool_(True) + np.bool_(True) #=> True  C：環境依存でAかBのどちらかになる  D：環境依存でAかBかそれ以外の何かになる  正解B  Pythonの真偽値はほぼ整数型です（True * True = 1）  numpy ではboolに対する加算、減算、乗算、符号反転は論理演算に なる
32. 32. 関数の闇 その３ 真偽値演算  関数のドキュメントには説明がない・・・
33. 33. 関数の闇 その４ power  次のうち正しいのはどれ？  A. np.bool_(True) ** 0 # => bool_(True)  B. np.bool_(True) ** 1 # => bool_(True)  C. np.bool_(True) ** 2 # => bool_(True)  D. ABCの全部
34. 34. 関数の闇 その４ power  次のうち正しいのはどれ？  A. np.bool_(True) ** 0 # => bool_(True)  B. np.bool_(True) ** 1 # => bool_(True)  C. np.bool_(True) ** 2 # => bool_(True)  D. ABCの全部  正解はC  他は全部1。Pythonのboolも１になる  True ** 2 # => 1  乗数が2.0 の時に分岐してかけ算を呼び出している  →つまりAND演算なのでnp.bool_(True)になる  高速化したい気持ちは分かるが、バグである
35. 35. 関数の闇 その５ sqrt  np.sqrt(np.ones(10, ???)).dtype #=> ??? が成立しないのは 次のうちどれ？  A. np.float16  B. np.float32  C. np.float64  D. A,B両方のケース
36. 36. 関数の闇 その５ sqrt  np.sqrt(np.ones(10, ???)).dtype #=> ??? が成立しないのは 次のうちどれ？  A. np.float16  B. np.float32  C. np.float64  D. A,B両方のケース  正解はA
37. 37. 関数の闇 その５ sqrt  sqrtの型判定にバグ？がある  numpy.sqrt.typesを見ると・・・  #=>['f->f', 'd->d', 'e->e', 'f->f', 'd->d', 'g->g', 'F->F', 'D->D', 'G->G', 'O->O']  ['f->f', 'd->d‘]が2回登場している  e->eのルール(np.float16)はdtypeを指定しないと使えない  Issueが2個上がっているが、スルー状態  #6134, #6255  PRじゃないと修正してもらえない？  CuPyはsqrtとsqrt_fixedの二つが用意されている
38. 38. 関数の闇 power + sqrt  ちなみにpowerは0.5乗の時にsqrtを呼び出す・・・  つまり？  np.ones(2, np.float16)**0.5  #=> array([ 1., 1.], dtype=float32)  闇の連鎖が発生
39. 39. クイズにしなかった闇  numpy.dtypeオブジェクトは常にFalse  [()] でscalarと0次元配列を行ったりきたり(v1.8)  a #=> array(0.)  a[()] #=> 0.0  a[()][()] #=> array(0.)  a[()] [()] [()] #=> 0.0  np.splitはぎりぎりで分割すると挙動が変  対称性のない挙動  np.true_divide でdtypeを指定すると計算精度が変  ufuncの仕様上しかないのかもしれないが  Macでnumpyをimportするとシグナルハンドラとシグナル マスクを勝手に変更される
40. 40. まとめ  NumPyの様々な闇を紹介しました  他にも闇？を見つけた方は是非教えて下さい  納得がいかない闇がありましたら、ぜひNumPyにIssue、 PRを送っていただければと思います  闇の無いライブラリを頑張って作っていきたいです