Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

NumPy闇入門

12,299 views

Published on

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

PFIセミナー2016/01/28:NumPy闇入門
https://www.youtube.com/watch?v=fdLKVMCrzNE

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

Published in: Engineering
  • Be the first to comment

NumPy闇入門

  1. 1. NumPy闇入門 2016/1/28 PFIセミナー (株)Preferred Networks 奥田 遼介
  2. 2. 自己紹介 奥田 遼介  -2014東北大学 修士  文字列処理など  2014 (株)プリファードインフラストラクチャー  2014- (株)プリファードネットワークス  映像解析系、製造業系にかかわる研究開発  ChainerやCuPyの開発  趣味  読書、高速化
  3. 3. NumPyとはなにか  Python上で数値計算を効率的に行うためのライブラリ  NumPyがあるからPythonを使うくらい重要 In [1]: import numpy as np In [2]: np.ones((2, 1)) + np.arange(3) Out[2]: array([[ 1., 2., 3.], [ 1., 2., 3.]])
  4. 4. NumPy闇入門とは  Chainer開発勢がChainerやCuPyを作る過程で遭遇した NumPyのちょっと変な挙動(闇)を集めたもの  バグ、仕様なのか不明な場合も・・・
  5. 5. NumPy闇入門とは  Chainer開発勢がChainerやCuPyを作る過程で遭遇した NumPyのちょっと変な挙動(闇)を集めたもの  バグ、仕様なのか不明な場合も・・・  普通の人々はたいてい気づかないものばかりです  ずっと気づかずにいた方が幸せです  今回はクイズ形式で10問の闇を用意しました  クイズの作りが悪いのでだいぶ簡単です(たぶん)  何問正解できるか挑戦してみてください  断りが無い場合以下の環境を想定しています  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. 型の闇 その1  np.zeros(2, np.int64) + np.zeros(2, np.float64) の演算結果の dtypeは?  A. dtype(‘float32’)  B. dtype(‘float64’)  C. dtype(‘float128’)  D. わからない
  13. 13. 型の闇 その1  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. 型の闇 その2  0 + np.float16(0) の演算結果のdtypeは?  A. dtype(‘float16’)  B. dtype(‘float32’)  C. dtype(‘float64’)  D. わからない
  15. 15. 型の闇 その2  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. 型の闇 その4  np.array([10], np.int32)+ np.zeros(2, np.float32) の演算結果 のdtypeは?  A. dtype(‘float16’)  B. dtype(‘float32’)  C. dtype(‘float64’)  D. わからない
  19. 19. 型の闇 その4  np.array([10], np.int32) + np.zeros(2, np.float32) の演算結果 のdtypeは?  A. dtype(‘float16’)  B. dtype(‘float32’)  C. dtype(‘float64’)  D. わからない  正解:C  np.array([10], 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. 型の闇 その5  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. 型の闇 その5  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. 関数の闇 その1 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. 関数の闇 その1 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. 関数の闇 その1 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. 関数の闇 その2 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. 関数の闇 その2 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. 関数の闇 その3 真偽値演算  次の真偽値演算のうち正しいのはどれ?  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. 関数の闇 その3 真偽値演算  次の真偽値演算のうち正しいのはどれ?  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. 関数の闇 その3 真偽値演算  関数のドキュメントには説明がない・・・
  33. 33. 関数の闇 その4 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. 関数の闇 その4 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も1になる  True ** 2 # => 1  乗数が2.0 の時に分岐してかけ算を呼び出している  →つまりAND演算なのでnp.bool_(True)になる  高速化したい気持ちは分かるが、バグである
  35. 35. 関数の闇 その5 sqrt  np.sqrt(np.ones(10, ???)).dtype #=> ??? が成立しないのは 次のうちどれ?  A. np.float16  B. np.float32  C. np.float64  D. A,B両方のケース
  36. 36. 関数の闇 その5 sqrt  np.sqrt(np.ones(10, ???)).dtype #=> ??? が成立しないのは 次のうちどれ?  A. np.float16  B. np.float32  C. np.float64  D. A,B両方のケース  正解はA
  37. 37. 関数の闇 その5 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を送っていただければと思います  闇の無いライブラリを頑張って作っていきたいです

×