在numpy中使用单精度浮点数不方便

acruukt9  于 2023-03-12  发布在  其他
关注(0)|答案(1)|浏览(187)

当在numpy中使用单精度(float32)编写代码时,编写起来太难了。
首先,单精度浮点数的decalartion太长了。我们必须按如下方式输入所有变量。

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)

返回我想要的。但是它太长了,无法替换所有操作。
有没有更简单的方法在numpy中使用单精度?

xqkwcwgp

xqkwcwgp1#

问题在于,当您在运算中使用不同类型时,NumPy会提升类型。只有当另一个数值操作数的dtype为:

  • float32或更少
  • int16或更少
  • uint16或更少

如果另一个操作数具有另一个dtype,则结果为float64(如果另一个操作数是复数,则为complex)。上面列出的数据类型不是最常见的,因此几乎所有操作都是(特别是当另一个是Python整型/浮点型时),使用标准运算符+-/*,...将您的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做一些可能出错的事情(从一般用户的Angular 来看)。
因为没有(据我所知)限制Numpy类型提升的方法,所以你必须自己发明。
例如,您可以创建一个类来 Package 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____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)

相关问题