ตรวจสอบ NaN ใน NumPy อย่างรวดเร็ว


120

ฉันกำลังมองหาวิธีที่เร็วที่สุดในการตรวจสอบการเกิดขึ้นของน่าน (คนnp.nan) Xในอาร์เรย์ np.isnan(X)หมดปัญหาเนื่องจากมันสร้างอาร์เรย์ของรูปร่างบูลีนX.shapeซึ่งอาจมีขนาดมหึมา

ฉันพยายามแต่ที่ดูเหมือนจะไม่ทำงานเพราะnp.nan in X np.nan != np.nanมีวิธีที่รวดเร็วและประหยัดหน่วยความจำในการดำเนินการนี้หรือไม่?

(สำหรับคนที่ถามว่า "ยักษ์ขนาดไหน": ฉันบอกไม่ได้นี่คือการตรวจสอบอินพุตสำหรับรหัสไลบรารี)


ตรวจสอบความถูกต้องของข้อมูลที่ผู้ใช้ป้อนไม่ได้ในสถานการณ์นี้หรือไม่ ในการตรวจสอบ NaN ก่อนใส่
Woot4Moo

@ Woot4Moo: ไม่ไลบรารีรับอาร์เรย์ NumPy หรือscipy.sparseเมทริกซ์เป็นอินพุต
Fred Foo

2
หากคุณทำสิ่งนี้บ่อยครั้งฉันเคยได้ยินสิ่งดีๆเกี่ยวกับคอขวด ( pypi.python.org/pypi/Bottleneck )
แมตต์

คำตอบ:


161

วิธีแก้ปัญหาของเรย์เป็นสิ่งที่ดี อย่างไรก็ตามในเครื่องของฉันใช้งานได้เร็วขึ้นประมาณ 2.5 เท่าnumpy.sumแทนnumpy.min:

In [13]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 244 us per loop

In [14]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 97.3 us per loop

ซึ่งแตกต่างmin, sumไม่จำเป็นต้องมีการแตกแขนงซึ่งบนฮาร์ดแวร์ที่ทันสมัยมีแนวโน้มที่จะราคาแพงสวย นี่คงเป็นเหตุผลว่าทำไมsumเร็วกว่า

แก้ไขการทดสอบข้างต้นดำเนินการโดยใช้ NaN เดียวตรงกลางอาร์เรย์

เป็นที่น่าสังเกตว่าminเมื่อมี NaN ช้ากว่าเมื่อไม่มี ดูเหมือนว่าจะช้าลงเมื่อ NaN เข้าใกล้จุดเริ่มต้นของอาร์เรย์มากขึ้น ในทางกลับกันทรูsumพุตดูเหมือนจะคงที่ไม่ว่าจะมี NaN หรือไม่และอยู่ที่ใด:

In [40]: x = np.random.rand(100000)

In [41]: %timeit np.isnan(np.min(x))
10000 loops, best of 3: 153 us per loop

In [42]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.9 us per loop

In [43]: x[50000] = np.nan

In [44]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 239 us per loop

In [45]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.8 us per loop

In [46]: x[0] = np.nan

In [47]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 326 us per loop

In [48]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.9 us per loop

1
np.minเร็วขึ้นเมื่ออาร์เรย์ไม่มี NaN ซึ่งเป็นอินพุตที่ฉันคาดไว้ แต่ฉันตัดสินใจที่จะยอมรับสิ่งนี้อยู่ดีเพราะมันจับได้infและneginfเช่นกัน
Fred Foo

2
สิ่งนี้จะจับได้infหรือ-infหากอินพุตมีทั้งสองอย่างและจะมีปัญหาหากอินพุตมีค่าขนาดใหญ่ แต่มีค่า จำกัด ที่มากเกินไปเมื่อรวมเข้าด้วยกัน
user2357112 รองรับ Monica

4
ขั้นต่ำและสูงสุดไม่จำเป็นต้องแตกแขนงสำหรับข้อมูลจุดลอยตัวบนชิป x86 ที่รองรับ sse ดังนั้นเมื่อ numpy 1.8 นาทีจะไม่ช้าไปกว่า sum เมื่อ amd phenom ของฉันเร็วขึ้น 20%
jtaylor

1
บนผล Intel Core i5 ฉันกับ numpy 1.9.2 ใน OSX, np.sumยังคงเป็นประมาณ 30% np.minเร็วกว่า
Matthew Brett

np.isnan(x).any(0)เร็วกว่าnp.sumและnp.minในเครื่องของฉันเล็กน้อยแม้ว่าอาจมีแคชที่ไม่ต้องการอยู่บ้าง
jsignell

28

ฉันคิดว่าnp.isnan(np.min(X))ควรทำในสิ่งที่คุณต้องการ


อืม ... นี่คือ O (n) เสมอเมื่อเป็น O (1) (สำหรับบางอาร์เรย์)
user48956

17

แม้ว่าจะมีคำตอบที่ยอมรับฉันจะสาธิตสิ่งต่อไปนี้ (ด้วย Python 2.7.2 และ Numpy 1.6.0 บน Vista):

In []: x= rand(1e5)
In []: %timeit isnan(x.min())
10000 loops, best of 3: 200 us per loop
In []: %timeit isnan(x.sum())
10000 loops, best of 3: 169 us per loop
In []: %timeit isnan(dot(x, x))
10000 loops, best of 3: 134 us per loop

In []: x[5e4]= NaN
In []: %timeit isnan(x.min())
100 loops, best of 3: 4.47 ms per loop
In []: %timeit isnan(x.sum())
100 loops, best of 3: 6.44 ms per loop
In []: %timeit isnan(dot(x, x))
10000 loops, best of 3: 138 us per loop

ดังนั้นวิธีที่มีประสิทธิภาพจริงๆอาจขึ้นอยู่กับระบบปฏิบัติการเป็นอย่างมาก อย่างไรก็ตามdot(.)ตามน่าจะเป็นมากที่สุดคนหนึ่งที่มีเสถียรภาพ


1
ฉันสงสัยว่ามันขึ้นอยู่กับระบบปฏิบัติการไม่มากนักเนื่องจากการใช้งาน BLAS และคอมไพเลอร์ C ขอบคุณ แต่ผลิตภัณฑ์ดอทมีแนวโน้มที่จะล้นเมื่อxมีค่ามากและฉันต้องการตรวจสอบ inf ด้วย
Fred Foo

1
คุณสามารถทำผลิตภัณฑ์ดอทด้วยผลิตภัณฑ์และใช้งานisfinite(.)ได้ตลอดเวลา ฉันแค่อยากจะชี้ให้เห็นถึงช่องว่างด้านประสิทธิภาพขนาดใหญ่ ขอบคุณ
กิน

เหมือนกันในเครื่องของฉัน
kawing-chiu

1
ฉลาดไม่? ตามที่Fred Fooแนะนำการเพิ่มประสิทธิภาพใด ๆ ของวิธีการตามผลิตภัณฑ์ดอทนั้นแทบจะต้องขอบคุณการติดตั้ง NumPy ในเครื่องที่เชื่อมโยงกับการใช้งาน BLAS ที่ได้รับการปรับให้เหมาะสมเช่น ATLAS, MKL หรือ OpenBLAS นี่เป็นกรณีของอนาคอนดาเช่น เนื่องจากผลิตภัณฑ์จุดนี้จะขนานกันในคอร์ทั้งหมดที่มีอยู่ ไม่สามารถพูดได้เช่นเดียวกันสำหรับmin- หรือsum- ตามแนวทางซึ่ง จำกัด อยู่ที่คอร์เดียว Ergo ช่องว่างด้านประสิทธิภาพนั้น
Cecil Curry

16

มีสองแนวทางทั่วไปที่นี่:

  • ตรวจสอบรายการอาร์เรย์แต่ละและใช้เวลาnanany
  • ใช้การดำเนินการสะสมบางอย่างที่รักษาnans (like sum) และตรวจสอบผลลัพธ์

แม้ว่าแนวทางแรกจะเป็นวิธีที่สะอาดที่สุด แต่การเพิ่มประสิทธิภาพอย่างหนักของการดำเนินการสะสมบางอย่าง (โดยเฉพาะอย่างยิ่งการดำเนินการใน BLAS เช่นdot) สามารถทำให้การดำเนินการเหล่านี้ค่อนข้างเร็ว โปรดทราบว่าdotเช่นเดียวกับการดำเนินการอื่น ๆ ของ BLAS จะมีการทำงานแบบมัลติเธรดภายใต้เงื่อนไขบางประการ สิ่งนี้อธิบายถึงความแตกต่างของความเร็วระหว่างเครื่องจักรต่างๆ

ใส่คำอธิบายภาพที่นี่

import numpy
import perfplot


def min(a):
    return numpy.isnan(numpy.min(a))


def sum(a):
    return numpy.isnan(numpy.sum(a))


def dot(a):
    return numpy.isnan(numpy.dot(a, a))


def any(a):
    return numpy.any(numpy.isnan(a))


def einsum(a):
    return numpy.isnan(numpy.einsum("i->", a))


perfplot.show(
    setup=lambda n: numpy.random.rand(n),
    kernels=[min, sum, dot, any, einsum],
    n_range=[2 ** k for k in range(20)],
    logx=True,
    logy=True,
    xlabel="len(a)",
)

4
  1. ใช้ .any ()

    if numpy.isnan(myarray).any()

  2. numpy.isfinite อาจดีกว่า isnan สำหรับการตรวจสอบ

    if not np.isfinite(prop).all()


3

หากคุณพอใจกับ ช่วยให้สร้างการลัดวงจรอย่างรวดเร็ว (หยุดทันทีที่พบ NaN):

import numba as nb
import math

@nb.njit
def anynan(array):
    array = array.ravel()
    for i in range(array.size):
        if math.isnan(array[i]):
            return True
    return False

หากไม่มีNaNฟังก์ชั่นอาจช้ากว่าจริงnp.minฉันคิดว่านั่นเป็นเพราะnp.minใช้การประมวลผลหลายขั้นตอนสำหรับอาร์เรย์ขนาดใหญ่:

import numpy as np
array = np.random.random(2000000)

%timeit anynan(array)          # 100 loops, best of 3: 2.21 ms per loop
%timeit np.isnan(array.sum())  # 100 loops, best of 3: 4.45 ms per loop
%timeit np.isnan(array.min())  # 1000 loops, best of 3: 1.64 ms per loop

แต่ในกรณีที่มี NaN ในอาร์เรย์โดยเฉพาะอย่างยิ่งถ้าตำแหน่งอยู่ที่ดัชนีต่ำจะเร็วกว่ามาก:

array = np.random.random(2000000)
array[100] = np.nan

%timeit anynan(array)          # 1000000 loops, best of 3: 1.93 µs per loop
%timeit np.isnan(array.sum())  # 100 loops, best of 3: 4.57 ms per loop
%timeit np.isnan(array.min())  # 1000 loops, best of 3: 1.65 ms per loop

ผลลัพธ์ที่คล้ายกันอาจทำได้ด้วย Cython หรือส่วนขยาย C สิ่งเหล่านี้ซับซ้อนกว่าเล็กน้อย (หรือใช้งานได้ง่ายbottleneck.anynan) แต่ในที่สุดก็ทำเช่นเดียวกับanynanฟังก์ชันของฉัน


1

ที่เกี่ยวข้องกับเรื่องนี้คือคำถามที่ว่าจะหา NaN ที่เกิดขึ้นครั้งแรกได้อย่างไร นี่เป็นวิธีที่เร็วที่สุดในการจัดการกับสิ่งที่ฉันรู้:

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.