在 numpy 中使用单精度浮点数的不便
Inconvenience to use single precision floats in numpy
在numpy中使用单精度(float32
)编写代码时,太难写了。
首先,单精度浮标标量过长。我们必须按如下方式键入所有变量。
a = np.float32(5)
但是其他一些语言使用更简单的表示。
a = 5.f
其次,算术运算也不方便。
b = np.int32(5)+np.float32(5)
我预计 b
的类型是 numpy.float32
但它是 numpy.float64
.
当然,
b = np.add(np.int32(5), np.float32(5), dtype=np.float32)
returns我想要的。但是替换所有操作太长了
有没有更简单的方法在 numpy 中使用单精度?
问题是当您在操作中使用不同类型时,NumPy 会提升类型。如果另一个数字操作数的数据类型为:
,则 float32
仅保留 float32
float32
或更少
int16
或更少
uint16
或更少
如果另一个操作数具有另一个 dtype,则结果将为 float64
(如果另一个操作数是复数,则结果为 complex
)。上面列出的数据类型不是最常见的,所以几乎所有操作(尤其是当另一个是 Python integer/float 时)都使用标准运算符 +
、-
、/
、*
、...会将您的 float32
提升为 float64
。
不幸的是,您无法避免这种情况。在很多情况下,NumPy 这样做是可以的,因为:
- 大多数架构处理双精度的速度与处理单精度浮点数的速度一样快。 Python 中的算术运算在 Python 类型上运行速度很快,但在其他类型上运行速度较慢。
import numpy as np
a32 = np.float32(1)
a64 = np.float64(1)
a = 1.
%timeit [a32 + a32 for _ in range(20000)] # 100 loops, best of 3: 4.58 ms per loop
%timeit [a64 + a64 for _ in range(20000)] # 100 loops, best of 3: 4.83 ms per loop
%timeit [a + a for _ in range(20000)] # 100 loops, best of 3: 2.72 ms per loop
- Python 类型的开销如此之大,以至于 标量 双精度浮点数的内存开销几乎可以忽略不计。
import sys
import numpy as np
sys.getsizeof(np.float32(1)) # 28
sys.getsizeof(np.float64(1)) # 32
sys.getsizeof(1.) # 24 # that's also a double on my computer!
但是,如果您有大量数组并且 运行 遇到内存问题,或者如果您与其他需要单精度浮点数的库(机器学习、GPU、 ...)。
但如上所述,您几乎总是会与强制规则作斗争,这会防止您 运行 陷入意想不到的问题。
带int32 + float32
的例子实际上是一个很好的例子!您希望结果为 float32
- 但存在一个问题:您不能将每个 int32
表示为 float32
:
np.iinfo(np.int32(1)) # iinfo(min=-2147483648, max=2147483647, dtype=int32)
int(np.float32(2147483647)) # 2147483648
np.int32(np.float32(2147483647)) # -2147483648
是的,只需将值转换为单精度浮点数并将其转换回整数即可更改其值。这就是 NumPy 使用双精度的原因,这样您就不会得到 意外 结果!这就是为什么你需要 强制 NumPy 做一些可能出错的事情(从一般用户的角度来看)。
因为没有(据我所知)限制 Numpy 类型提升的方法,你必须自己发明。
例如,您可以创建一个 class 来包装 NumPy 数组并使用特殊方法为运算符实现 dtype-d 函数:
import numpy as np
class Arr32:
def __init__(self, arr):
self.arr = arr
def __add__(self, other):
other_arr = other
if isinstance(other, Arr32):
other_arr = other.arr
return self.__class__(np.add(self.arr, other_arr, dtype=np.float32))
def __sub__(self, other):
other_arr = other
if isinstance(other, Arr32):
other_arr = other.arr
return self.__class__(np.subtract(self.arr, other_arr, dtype=np.float32))
def __mul__(self, other):
other_arr = other
if isinstance(other, Arr32):
other_arr = other.arr
return self.__class__(np.multiply(self.arr, other_arr, dtype=np.float32))
def __truediv__(self, other):
other_arr = other
if isinstance(other, Arr32):
other_arr = other.arr
return self.__class__(np.divide(self.arr, other_arr, dtype=np.float32))
但这只实现了 NumPy 功能的一小部分,并且会很快导致大量代码和边缘情况可能被遗忘。现在使用 __array_ufunc__
or __array_function__
可能有更聪明的方法,但我自己没有使用过这些方法,所以我无法评论工作量或适用性。
所以我的首选解决方案是为所需的函数创建辅助函数:
import numpy as np
def arr32(a):
return np.float32(a)
def add32(a1, a2):
return np.add(a1, a2, dtype=np.float32)
def sub32(a1, a2):
return np.subtract(a1, a2, dtype=np.float32)
def mul32(a1, a2):
return np.multiply(a1, a2, dtype=np.float32)
def div32(a1, a2):
return np.divide(a1, a2, dtype=np.float32)
或者只使用就地操作,因为这些操作不会提升类型:
>>> import numpy as np
>>> arr = np.float32([1,2,3])
>>> arr += 2
>>> arr *= 3
>>> arr
array([ 9., 12., 15.], dtype=float32)
在numpy中使用单精度(float32
)编写代码时,太难写了。
首先,单精度浮标标量过长。我们必须按如下方式键入所有变量。
a = np.float32(5)
但是其他一些语言使用更简单的表示。
a = 5.f
其次,算术运算也不方便。
b = np.int32(5)+np.float32(5)
我预计 b
的类型是 numpy.float32
但它是 numpy.float64
.
当然,
b = np.add(np.int32(5), np.float32(5), dtype=np.float32)
returns我想要的。但是替换所有操作太长了
有没有更简单的方法在 numpy 中使用单精度?
问题是当您在操作中使用不同类型时,NumPy 会提升类型。如果另一个数字操作数的数据类型为:
,则float32
仅保留 float32
float32
或更少int16
或更少uint16
或更少
如果另一个操作数具有另一个 dtype,则结果将为 float64
(如果另一个操作数是复数,则结果为 complex
)。上面列出的数据类型不是最常见的,所以几乎所有操作(尤其是当另一个是 Python integer/float 时)都使用标准运算符 +
、-
、/
、*
、...会将您的 float32
提升为 float64
。
不幸的是,您无法避免这种情况。在很多情况下,NumPy 这样做是可以的,因为:
- 大多数架构处理双精度的速度与处理单精度浮点数的速度一样快。 Python 中的算术运算在 Python 类型上运行速度很快,但在其他类型上运行速度较慢。
import numpy as np
a32 = np.float32(1)
a64 = np.float64(1)
a = 1.
%timeit [a32 + a32 for _ in range(20000)] # 100 loops, best of 3: 4.58 ms per loop
%timeit [a64 + a64 for _ in range(20000)] # 100 loops, best of 3: 4.83 ms per loop
%timeit [a + a for _ in range(20000)] # 100 loops, best of 3: 2.72 ms per loop
- Python 类型的开销如此之大,以至于 标量 双精度浮点数的内存开销几乎可以忽略不计。
import sys
import numpy as np
sys.getsizeof(np.float32(1)) # 28
sys.getsizeof(np.float64(1)) # 32
sys.getsizeof(1.) # 24 # that's also a double on my computer!
但是,如果您有大量数组并且 运行 遇到内存问题,或者如果您与其他需要单精度浮点数的库(机器学习、GPU、 ...)。
但如上所述,您几乎总是会与强制规则作斗争,这会防止您 运行 陷入意想不到的问题。
带int32 + float32
的例子实际上是一个很好的例子!您希望结果为 float32
- 但存在一个问题:您不能将每个 int32
表示为 float32
:
np.iinfo(np.int32(1)) # iinfo(min=-2147483648, max=2147483647, dtype=int32)
int(np.float32(2147483647)) # 2147483648
np.int32(np.float32(2147483647)) # -2147483648
是的,只需将值转换为单精度浮点数并将其转换回整数即可更改其值。这就是 NumPy 使用双精度的原因,这样您就不会得到 意外 结果!这就是为什么你需要 强制 NumPy 做一些可能出错的事情(从一般用户的角度来看)。
因为没有(据我所知)限制 Numpy 类型提升的方法,你必须自己发明。
例如,您可以创建一个 class 来包装 NumPy 数组并使用特殊方法为运算符实现 dtype-d 函数:
import numpy as np
class Arr32:
def __init__(self, arr):
self.arr = arr
def __add__(self, other):
other_arr = other
if isinstance(other, Arr32):
other_arr = other.arr
return self.__class__(np.add(self.arr, other_arr, dtype=np.float32))
def __sub__(self, other):
other_arr = other
if isinstance(other, Arr32):
other_arr = other.arr
return self.__class__(np.subtract(self.arr, other_arr, dtype=np.float32))
def __mul__(self, other):
other_arr = other
if isinstance(other, Arr32):
other_arr = other.arr
return self.__class__(np.multiply(self.arr, other_arr, dtype=np.float32))
def __truediv__(self, other):
other_arr = other
if isinstance(other, Arr32):
other_arr = other.arr
return self.__class__(np.divide(self.arr, other_arr, dtype=np.float32))
但这只实现了 NumPy 功能的一小部分,并且会很快导致大量代码和边缘情况可能被遗忘。现在使用 __array_ufunc__
or __array_function__
可能有更聪明的方法,但我自己没有使用过这些方法,所以我无法评论工作量或适用性。
所以我的首选解决方案是为所需的函数创建辅助函数:
import numpy as np
def arr32(a):
return np.float32(a)
def add32(a1, a2):
return np.add(a1, a2, dtype=np.float32)
def sub32(a1, a2):
return np.subtract(a1, a2, dtype=np.float32)
def mul32(a1, a2):
return np.multiply(a1, a2, dtype=np.float32)
def div32(a1, a2):
return np.divide(a1, a2, dtype=np.float32)
或者只使用就地操作,因为这些操作不会提升类型:
>>> import numpy as np
>>> arr = np.float32([1,2,3])
>>> arr += 2
>>> arr *= 3
>>> arr
array([ 9., 12., 15.], dtype=float32)