NumPy闇入門

486
-1

Published on

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

Published in: Engineering
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
486
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
1
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

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とも1行目が間違っている(Trueでも2でもない)  D:A, Bとも2行目が間違っている(Trueでも2でもない)
  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とも1行目が間違っている(Trueでも2でもない)  D:A, Bとも2行目が間違っている(Trueでも2でもない)  正解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を送っていただければと思います  闇の無いライブラリを頑張って作っていきたいです
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×