numpy 숫자 형 배열을 성장시키는 가장 빠른 방법


80

요구 사항 :

  • 데이터에서 임의로 큰 배열을 확장해야합니다.
  • 배열이 매번 맞을 것이라는 보장없이 크기 (대략 100-200)를 추측 할 수 있습니다.
  • 최종 크기로 커지면 숫자 계산을 수행해야하므로 결국에는 2 차원 numpy 배열을 선호합니다.
  • 속도가 중요합니다. 예를 들어, 300 개 파일 중 하나에 대해 update () 메서드는 4,500 만 번 호출되고 (150 초 정도 소요) finalize () 메서드는 50 만 번 호출됩니다 (총 106 초 소요) ... 총 250 초 소요 정도.

내 코드는 다음과 같습니다.

def __init__(self):
    self.data = []

def update(self, row):
    self.data.append(row)

def finalize(self):
    dx = np.array(self.data)

내가 시도한 다른 것들은 다음 코드를 포함하지만 ... 이것은 속도가 느립니다.

def class A:
    def __init__(self):
        self.data = np.array([])

    def update(self, row):
        np.append(self.data, row)

    def finalize(self):
        dx = np.reshape(self.data, size=(self.data.shape[0]/5, 5))

이것이 어떻게 호출되는지에 대한 회로도는 다음과 같습니다.

for i in range(500000):
    ax = A()
    for j in range(200):
         ax.update([1,2,3,4,5])
    ax.finalize()
    # some processing on ax

2
완료되기 전에 numpy 배열이어야합니까? 그렇지 않은 경우 목록 목록을 사용한 다음 완료되면 변환하십시오.
Andrew Jaffe

1
@AndrewJaffe 목록 목록이 numpy의 메모리 효율성과 일치합니까?
AturSams

답변:


96

타이밍과 함께 몇 가지 다른 것을 시도했습니다.

import numpy as np
  1. 느리다고 언급 한 방법 : (32.094 초)

    class A:
    
        def __init__(self):
            self.data = np.array([])
    
        def update(self, row):
            self.data = np.append(self.data, row)
    
        def finalize(self):
            return np.reshape(self.data, newshape=(self.data.shape[0]/5, 5))
    
  2. 일반 ol Python 목록 : (0.308 초)

    class B:
    
        def __init__(self):
            self.data = []
    
        def update(self, row):
            for r in row:
                self.data.append(r)
    
        def finalize(self):
            return np.reshape(self.data, newshape=(len(self.data)/5, 5))
    
  3. numpy에서 arraylist 구현 시도 : (0.362 초)

    class C:
    
        def __init__(self):
            self.data = np.zeros((100,))
            self.capacity = 100
            self.size = 0
    
        def update(self, row):
            for r in row:
                self.add(r)
    
        def add(self, x):
            if self.size == self.capacity:
                self.capacity *= 4
                newdata = np.zeros((self.capacity,))
                newdata[:self.size] = self.data
                self.data = newdata
    
            self.data[self.size] = x
            self.size += 1
    
        def finalize(self):
            data = self.data[:self.size]
            return np.reshape(data, newshape=(len(data)/5, 5))
    

그리고 이것이 내가 시간을 정한 방법입니다.

x = C()
for i in xrange(100000):
    x.update([i])

따라서 일반적인 오래된 Python 목록이 꽤 좋은 것 같습니다.)


1
60M 업데이트와 500K가 통화를 마무리하면 비교가 더 명확하다고 생각합니다. 이 예에서는 finalize를 호출하지 않은 것 같습니다.
fodon

1
@fodon 나는 실제로 finalize를 호출했습니다. 실행 당 한 번 (그래서 실제로 큰 영향을 미치지는 않습니다). 하지만 이로 인해 데이터가 어떻게 증가하고 있는지 오해 할 수 있다고 생각합니다. 업데이트에서 6 천만 건을 받으면 다음 마무리를 위해 최소 6 천만 데이터를 제공 할 것이라고 생각 했나요?
Owen

@Owen 60M 및 500K 평균 6000 만 및 50 만 호출에 updatefinalize각각. 1 비율 : 100 테스트 내 개정 타이밍 참조 update에를finalize
Prashant 쿠마

나는 이것이 어떻게 작동하는지에 대한 아이디어를 제공하기 위해 짧은 스크립트 (구문 상 정확하지 않을 수 있음)로 질문을 업데이트했습니다.
fodon

3
세 번째 옵션은 메모리가 부족할 때 더 우수합니다. 두 번째 옵션에는 많은 메모리가 필요합니다. 그 이유는 Python의 목록은 값에 대한 참조 배열 인 반면 NumPy의 배열은 실제 값 배열이기 때문입니다.
Fabianius

20

np.append ()는 매번 배열의 모든 데이터를 복사하지만 목록은 용량 (1.125)을 늘립니다. 목록은 빠르지 만 메모리 사용량은 배열보다 큽니다. 메모리에 관심이 있다면 파이썬 표준 라이브러리의 배열 모듈을 사용할 수 있습니다.

이 주제에 대한 토론은 다음과 같습니다.

동적 배열을 만드는 방법


2
목록이 증가하는 요인을 변경하는 방법이 있습니까?
fodon

1
np.append () 소요 시간은 요소의 수에 따라 기하 급수적으로 증가합니다.
시계 ZHONG

1
^ 선형 (즉, 총 누적 시간은 2 차), 지수가 아닙니다.
user1111929

15

Owen의 게시물에있는 클래스 선언을 사용하여 마무리 효과가있는 수정 된 타이밍이 있습니다.

요컨대, 나는 원래 포스트의 방법보다 60 배 이상 빠른 구현을 제공하는 클래스 C를 발견했습니다. (텍스트 벽에 대한 사과)

내가 사용한 파일 :

#!/usr/bin/python
import cProfile
import numpy as np

# ... class declarations here ...

def test_class(f):
    x = f()
    for i in xrange(100000):
        x.update([i])
    for i in xrange(1000):
        x.finalize()

for x in 'ABC':
    cProfile.run('test_class(%s)' % x)

이제 결과 타이밍 :

ㅏ:

     903005 function calls in 16.049 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000   16.049   16.049 <string>:1(<module>)
100000    0.139    0.000    1.888    0.000 fromnumeric.py:1043(ravel)
  1000    0.001    0.000    0.003    0.000 fromnumeric.py:107(reshape)
100000    0.322    0.000   14.424    0.000 function_base.py:3466(append)
100000    0.102    0.000    1.623    0.000 numeric.py:216(asarray)
100000    0.121    0.000    0.298    0.000 numeric.py:286(asanyarray)
  1000    0.002    0.000    0.004    0.000 test.py:12(finalize)
     1    0.146    0.146   16.049   16.049 test.py:50(test_class)
     1    0.000    0.000    0.000    0.000 test.py:6(__init__)
100000    1.475    0.000   15.899    0.000 test.py:9(update)
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
100000    0.126    0.000    0.126    0.000 {method 'ravel' of 'numpy.ndarray' objects}
  1000    0.002    0.000    0.002    0.000 {method 'reshape' of 'numpy.ndarray' objects}
200001    1.698    0.000    1.698    0.000 {numpy.core.multiarray.array}
100000   11.915    0.000   11.915    0.000 {numpy.core.multiarray.concatenate}

비:

     208004 function calls in 16.885 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.001    0.001   16.885   16.885 <string>:1(<module>)
  1000    0.025    0.000   16.508    0.017 fromnumeric.py:107(reshape)
  1000    0.013    0.000   16.483    0.016 fromnumeric.py:32(_wrapit)
  1000    0.007    0.000   16.445    0.016 numeric.py:216(asarray)
     1    0.000    0.000    0.000    0.000 test.py:16(__init__)
100000    0.068    0.000    0.080    0.000 test.py:19(update)
  1000    0.012    0.000   16.520    0.017 test.py:23(finalize)
     1    0.284    0.284   16.883   16.883 test.py:50(test_class)
  1000    0.005    0.000    0.005    0.000 {getattr}
  1000    0.001    0.000    0.001    0.000 {len}
100000    0.012    0.000    0.012    0.000 {method 'append' of 'list' objects}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  1000    0.020    0.000    0.020    0.000 {method 'reshape' of 'numpy.ndarray' objects}
  1000   16.438    0.016   16.438    0.016 {numpy.core.multiarray.array}

씨:

     204010 function calls in 0.244 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.244    0.244 <string>:1(<module>)
  1000    0.001    0.000    0.003    0.000 fromnumeric.py:107(reshape)
     1    0.000    0.000    0.000    0.000 test.py:27(__init__)
100000    0.082    0.000    0.170    0.000 test.py:32(update)
100000    0.087    0.000    0.088    0.000 test.py:36(add)
  1000    0.002    0.000    0.005    0.000 test.py:46(finalize)
     1    0.068    0.068    0.243    0.243 test.py:50(test_class)
  1000    0.000    0.000    0.000    0.000 {len}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  1000    0.002    0.000    0.002    0.000 {method 'reshape' of 'numpy.ndarray' objects}
     6    0.001    0.000    0.001    0.000 {numpy.core.multiarray.zeros}

클래스 A는 업데이트에 의해 파괴되고 클래스 B는 파이널 라이즈에 의해 파괴됩니다. 클래스 C는 두 가지면에서 견고합니다.


업데이트가 한 번 수행 된 다음 finalize가 한 번 호출됩니다. 이 전체 프로세스는 m 번 수행됩니다 (그렇지 않으면 완료 할 데이터가 없음). 또한 원본 게시물과 비교할 때 ... 첫 번째 (array.append + numpy 변환) 또는 (numpy.append + reshape)를 의미합니까?
fodon

1
cProfile. 내 코드 스 니펫에서 호출 된 첫 번째 가져 오기와 마지막 줄입니다.
Prashant 쿠마

5

마무리에 사용하는 기능에 큰 성능 차이가 있습니다. 다음 코드를 고려하십시오.

N=100000
nruns=5

a=[]
for i in range(N):
    a.append(np.zeros(1000))

print "start"

b=[]
for i in range(nruns):
    s=time()
    c=np.vstack(a)
    b.append((time()-s))
print "Timing version vstack ",np.mean(b)

b=[]
for i in range(nruns):
    s=time()
    c1=np.reshape(a,(N,1000))
    b.append((time()-s))

print "Timing version reshape ",np.mean(b)

b=[]
for i in range(nruns):
    s=time()
    c2=np.concatenate(a,axis=0).reshape(-1,1000)
    b.append((time()-s))

print "Timing version concatenate ",np.mean(b)

print c.shape,c2.shape
assert (c==c2).all()
assert (c==c1).all()

concatenate를 사용하는 것은 첫 번째 버전보다 두 배 빠르며 두 번째 버전보다 10 배 이상 빠릅니다.

Timing version vstack  1.5774928093
Timing version reshape  9.67419199944
Timing version concatenate  0.669512557983

1

목록 작업으로 성능을 향상 시키려면 blist 라이브러리를 살펴보십시오. 파이썬 목록 및 기타 구조의 최적화 된 구현입니다.

나는 그것을 아직 벤치마킹하지 않았지만 그들의 페이지의 결과는 유망 해 보입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.