LoginSignup
31
29

More than 3 years have passed since last update.

[Python3] numpyの float16 / float32 / float64の精度・速度比較

Last updated at Posted at 2019-10-13

概要

  • 「float64よりfloat32を使った方が高速らしいよ」
    ( ・ω・) (^ワ^ )
  • 「でも、大事なデータが破損してしまうのでは....?」
    ( ´・ω・`) (^ワ^;) (え?)
  • 「よし、データサイズを小さくしたらどのくらい精度が落ちるのか、確かめることにしよう!」
    (`・ω・´)

精度測定実験

1. 大きな整数

jupyter-notebook
bigint = np.array([123456789012345678901234567])
bigint
-> array([123456789012345678901234567], dtype=object)

# このままだとちょっとしか表示されないが...
bigint.astype('float64')
-> array([1.23456789e+26])

# ndarray内の要素を一つ指定すると、保持されている桁が最後まで見える
# (konandoiruasaさんの指摘により)
bigint.astype('float64')[0]
-> 1.2345678901234568e+26 # 16桁目までは元の値を保持

bigint.astype('float32')[0]
-> 1.2345679e+26 # 7桁目までは元の値を保持

bigint.astype('float16')[0]
-> inf # 草w

2. 小さな小数

jupyter-notebook
small_float = np.array([-0.00001234567890123456789])
small_float[0]
-> -1.2345678901234568e-05 # 生成してすぐに17桁目以降が破損...

small_float.astype('float64')[0]
-> -1.2345678901234568e-05 # 変化なし

small_float.astype('float32')[0]
-> -1.2345679e-05 # 7桁目までは元の値を保持

small_float.astype('float16')[0]
-> -1.234e-05 # 4桁目までは元の値を保持

3. 間を取ってみる

jupyter-notebook
halfway_number = np.array([1234.567890123456789012345])
halfway_number[0]
-> 1234.567890123457 # 生成してすぐに16桁目以降が破損...

halfway_number.astype('float64')[0]
-> 1234.567890123457 # 変化なし

halfway_number.astype('float32')[0]
-> 1234.5679 # 7桁目までは元の値を保持

halfway_number.astype('float16')[0]
-> 1235.0 # 3桁目までしか元の値が保持されない...

わかったこと(精度)(^ワ^*)

  • float16
    数値が4桁3桁に収まるならOK
  • float32
    数値が8桁7桁に収まるならOK?
    ただし、konandoiruasaさんの指摘にあるように、7桁であっても8388608までしか保持されないと考えると、8388608以上の数値は壊れてしまう可能性がある
    というわけで、6桁で済む情報を格納する場合に限定した方がよさそう
    ※ただし、float32に関する 不具合事例(20191022追記)も参照
  • float64
    扱える数値は12桁16桁までっぽい?
    ただし、同じ理由で4503599627370496以上の値は壊れてしまう可能性がある
    そうすると、15桁までしか保持されないと考えるのがよさそう

こんな感じか

numpyの型 桁数
float16 3
float32 6
float64 15

不具合事例(20191022追記)

float32を使うと、

  • たとえ6桁に収まる小数であっても最下位の桁が想定外の値になる
  • roundメソッドによる丸め処理の結果がおかしい

事例がありました。

sample

df = pd.DataFrame({'i': [132.2, 332.11, 5.555555555]})
df.astype('float32')
    i
0   132.199997  <= 元々4桁にもかかわらず最下位がずれる
1   332.109985  <= 元々5桁にもかかわらず最下位がずれる
2   5.555555    <= まあ予想通り

更に、float32だとroundメソッドによる丸めすら行えない?!

# float32 の dataframe を round(3) しても何も起こらない...
df.astype('float32').round(3)
    i
0   132.199997 <= 3桁目以下も残っている....
1   332.109985
2   5.556000

# float64 なら round で下位の桁を排除できる
df.astype('float64').round(3)
    i
0   132.200
1   332.110
2   5.556

ただし、生の値を実際に取り出せば、たとえfloat32であっても期待した値を取得できる

df.astype('float32').round(3).values

array([[132.2  ],
       [332.11 ],
       [  5.556]], dtype=float32)

いずれも3桁目以下が丸められて消えており期待した値になっている

ここからわかること

  • dtype=float32 の dataframe は信用できない
    (小数点がない場合は調べてないので不明)
    float32だと、dataframeから値を取り出してみるまで、実際にどんな値が入っているのかわからないことがある.....(-□-;)

こうなると、float16やfloat32を使うべきタイミングは、相当限られてきそう

float32でも精度が十分足りる場合

# この程度の桁数なら、十分正しく動作する
df = pd.DataFrame({'消費税': [0.100000, 0.080000, 0.050000]})
df.astype('float32').round(3)

    消費税
0   0.10
1   0.08
2   0.05

速度測定実験

1. ひたすらndarrayを連結し続ける

測定対象のメソッドはこちら

jupyter-notebook
def generate_ndarray(num, np_type):
    number = np.array([1234.56789123456789]).astype(np_type)
    np_ar = np.array([]).astype(np_type)
    count_ar = np.arange(num)

    for i in count_ar:
        np_ar = np.concatenate([np_ar, number])
    return np_ar

結果はこちら

jupyter-notebook
%timeit generate_ndarray(num=1000, np_type=np.float64)
-> 5.88 ms ± 1.12 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit generate_ndarray(num=1000, np_type=np.float32)
-> 5.96 ms ± 328 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit generate_ndarray(num=1000, np_type=np.float16)
-> 5.77 ms ± 512 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

何も変わらないじゃないか!!!ヾ(`д´)ノ
気を取り直して....

2. 巨大行列同士の掛け算

jupyter-notebook
a = np.zeros((1000, 1000), dtype=np.float64)
b = np.zeros((1000, 1000), dtype=np.float32)
c = np.zeros((1000, 1000), dtype=np.float16)
jupyter-notebook
%timeit a * a
-> 12.2 ms ± 1.13 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit b * b
-> 6.44 ms ± 461 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit c * c
-> 31.5 ms ± 910 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
# 遅くなった!!!!

おや....これはどういうことだ.....( ´・ω・`) (・ω・`;)

わかったこと?(速度)(。ŏ﹏ŏ)

  • float64 -> float32
    処理によっては時間が半分で済むが、あまり時間が変わらない場合もある
    値を計算に使用したとき、初めて差が出てくるのかも?
    もう少し調べてみたい
  • float64 -> float16
    むしろ遅くなる!!! float16についてはもうちょっと継続調査しないとだめかも...?

参考サイト

より詳細が知りたい方はこちらへ....

31
29
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
29