Найшвидший спосіб виробити числовий масив numpy


80

Вимоги:

  • Мені потрібно виростити масив довільно великого з даних.
  • Я можу вгадати розмір (приблизно 100-200) без жодних гарантій, що масив буде підходити кожного разу
  • Як тільки він виросте до остаточного розміру, мені потрібно виконати на ньому числові обчислення, тому я волів би врешті дістатись до 2-D масиву numpy.
  • Швидкість є критичною. Наприклад, для одного з 300 файлів метод update () викликається 45 мільйонів разів (займає близько 150 секунд), а метод finalize () викликається 500 тисяч разів (займає 106 секунд) ... загалом бере 250 або так.

Ось мій код:

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

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

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

Інші речі, які я спробував, включають наступний код ... але це waaaaay повільніше.

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. Звичайний список 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: (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
Думаю, порівняння стає чіткішим із оновленнями 60 млн., А 500 тис. Завершує дзвінки. Схоже, у цьому прикладі ви не називали завершення.
fodon

1
@fodon Я насправді зателефонував завершити - один раз за пробіг (тому, мабуть, це не дуже сильно впливає). Але це змушує мене думати, можливо, я неправильно зрозумів, як зростають ваші дані: якщо ви оновите 60 мільйонів, я думав, що це забезпечить принаймні 60 мільйонів даних для наступного завершення?
Оуен,

@Owen 60M і 500K означає 60 мільйонів і 500 тисяч дзвінків на updateта finalizeвідповідно. Дивіться мій переглянутий час, який перевіряє співвідношення 100: 1 updateдоfinalize
Прашант Кумар

Я оновив запитання коротким сценарієм (який може бути не синтаксично правильним), щоб дати уявлення про те, як це працює.
fodon

3
Зверніть увагу, що третій варіант є кращим, коли у вас закінчується пам’ять. Другий варіант вимагає багато пам'яті. Причина в тому, що списки Python - це масиви посилань на значення, тоді як масиви NumPy - це фактичні масиви значень.
Фабіаній

20

np.append () кожен раз копіює всі дані в масиві, але список збільшує ємність у коефіцієнт (1,125). список швидкий, але використання пам'яті більше, ніж масив. Ви можете використовувати модуль масиву стандартної бібліотеки python, якщо вам потрібна пам’ять.

Ось дискусія на цю тему:

Як створити динамічний масив


2
чи є спосіб змінити фактор, за допомогою якого зростає список?
fodon

1
np.append (), витрачаючи час, експоненціально збільшуючись із збільшенням кількості елементів.
Годинник ЧЖОНГ

1
^ лінійна (тобто загальний накопичений час квадрична), а не експоненціальна.
user1111929

15

Використовуючи декларації класів у дописі Оуена, ось переглянутий час із певним ефектом завершення.

Коротше кажучи, я вважаю, що клас C забезпечує реалізацію, яка в 60 разів швидша, ніж метод у вихідному дописі. (вибачення за стіну тексту)

Файл, який я використав:

#!/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)

Тепер отримані терміни:

A:

     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}

B:

     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}

C:

     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 знищується доопрацюванням. Клас С надійний на очах обох.


Оновлення виконується раз, тоді завершення викликається один раз. Весь цей процес виконується m разів (інакше даних для завершення немає). Крім того, під час порівняння з оригінальним повідомленням ... ви маєте на увазі перший (array.append + перетворення numpy) або (numpy.append + reshape)?
fodon

1
cProfile. Це перший імпорт і останній рядок, викликаний у моєму фрагменті коду.
Прашант Кумар,

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()

Здається, використання конкатенату вдвічі швидше, ніж перша версія, і більш ніж у 10 разів швидше, ніж друга версія.

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

1

Якщо ви хочете покращити продуктивність за допомогою операцій зі списком, перегляньте бібліотеку для розмивання. Це оптимізована реалізація списку пітонів та інших структур.

Я ще не тестував його, але результати на їхній сторінці здаються багатообіцяючими.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.