Декартовий добуток масиву x і y вказує на єдиний масив 2D точок


147

У мене є два нумеровані масиви, які визначають осі x та y сітки. Наприклад:

x = numpy.array([1,2,3])
y = numpy.array([4,5])

Я б хотів генерувати декартовий продукт цих масивів для генерації:

array([[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]])

Таким чином, це не дуже ефективно, оскільки мені потрібно це робити багато разів у циклі. Я припускаю, що перетворення їх у список Python та використання itertools.productта повернення до масиву numpy не є найбільш ефективною формою.


Я помітив, що найдорожчий крок у підході до itertools - це остаточне перетворення зі списку в масив. Без цього останнього кроку це вдвічі швидше, ніж приклад Кена.
Олексій Лебедєв

Відповіді:


88
>>> numpy.transpose([numpy.tile(x, len(y)), numpy.repeat(y, len(x))])
array([[1, 4],
       [2, 4],
       [3, 4],
       [1, 5],
       [2, 5],
       [3, 5]])

Див. Розділ Використання numpy для побудови масиву всіх комбінацій двох масивів для загального рішення для обчислення декартового добутку N масивів.


1
Перевагою такого підходу є те, що він дає послідовний вихід для масивів однакового розміру. Підхід meshgrid+ dstack, хоча в деяких випадках швидше, може призвести до помилок, якщо ви очікуєте, що декартовий продукт буде побудований в одному порядку для масивів однакового розміру.
tlnagy

3
@tlnagy, я не помітив жодного випадку, коли такий підхід дасть інші результати від результатів meshgrid+ dstack. Чи можете ви розмістити приклад?
senderle

148

Канонічний cartesian_product(майже)

Існує багато підходів до цієї проблеми з різними властивостями. Деякі швидші за інших, а деякі - більш загального призначення. Після багатьох тестувань та налаштувань я виявив, що наступна функція, яка обчислює n-мірність cartesian_product, швидше, ніж більшість інших, для багатьох входів. Про пару підходів, які є трохи складнішими, але навіть трохи швидшими у багатьох випадках, дивіться відповідь Пола Панзера .

З огляду на цю відповідь, це вже не найшвидше реалізація декартового продукту в numpyтому, що мені відомо. Однак я думаю, що його простота продовжить робити корисним орієнтиром для подальшого вдосконалення:

def cartesian_product(*arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[...,i] = a
    return arr.reshape(-1, la)

Варто згадати, що цю функцію використовують ix_незвично; тоді як задокументоване використання - ix_це генерування індексів у масиві, просто так трапляється, що масиви з однаковою формою можуть використовуватися для широкомовного призначення. Величезне спасибі mgilson , який надихнув мене спробувати використовувати ix_цей спосіб, та unutbu , який надав надзвичайно корисні відгуки щодо цієї відповіді, включаючи пропозицію використовувати numpy.result_type.

Помітні альтернативи

Іноді швидше писати суміжні блоки пам'яті у порядку Fortran. Це основа цієї альтернативи, cartesian_product_transposeяка виявилася швидшою для деяких апаратних засобів, ніж cartesian_product(див. Нижче). Однак відповідь Пола Пензера, який використовує той самий принцип, ще швидша. Все-таки я включаю це тут для зацікавлених читачів:

def cartesian_product_transpose(*arrays):
    broadcastable = numpy.ix_(*arrays)
    broadcasted = numpy.broadcast_arrays(*broadcastable)
    rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
    dtype = numpy.result_type(*arrays)

    out = numpy.empty(rows * cols, dtype=dtype)
    start, end = 0, rows
    for a in broadcasted:
        out[start:end] = a.reshape(-1)
        start, end = end, end + rows
    return out.reshape(cols, rows).T

Після того, як зрозуміти підхід Panzer, я написав нову версію, яка майже така ж швидка, як і його, і майже така ж проста cartesian_product:

def cartesian_product_simple_transpose(arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty([la] + [len(a) for a in arrays], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[i, ...] = a
    return arr.reshape(la, -1).T

Це, мабуть, має деякий накладний накладний час, що змушує його працювати повільніше, ніж Panzer для невеликих входів. Але що стосується більших показників, то в усіх тестах, які я проводив, він виконує так само добре, як і його найшвидше виконання ( cartesian_product_transpose_pp).

У наступних розділах я включаю деякі тести інших альтернатив. Зараз вони дещо застаріли, але, замість того, щоб повторювати зусилля, я вирішив залишити їх тут, не маючи історичних інтересів. Щоб дізнатися про сучасні тести, див відповідь Panzer, а також Ніко Шльомер .

Тести на альтернативи

Ось безліч тестів, які показують підвищення продуктивності, що деякі з цих функцій надають відносно ряду альтернативних варіантів. Усі показані тут тести проводилися на чотирьохядерній машині, що працює під управлінням Mac OS 10.12.5, Python 3.6.1 та numpy1.12.1. Відомо, що варіанти апаратного та програмного забезпечення дають різні результати, тому YMMV. Виконайте ці тести для себе, щоб бути впевненим!

Визначення:

import numpy
import itertools
from functools import reduce

### Two-dimensional products ###

def repeat_product(x, y):
    return numpy.transpose([numpy.tile(x, len(y)), 
                            numpy.repeat(y, len(x))])

def dstack_product(x, y):
    return numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)

### Generalized N-dimensional products ###

def cartesian_product(*arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[...,i] = a
    return arr.reshape(-1, la)

def cartesian_product_transpose(*arrays):
    broadcastable = numpy.ix_(*arrays)
    broadcasted = numpy.broadcast_arrays(*broadcastable)
    rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
    dtype = numpy.result_type(*arrays)

    out = numpy.empty(rows * cols, dtype=dtype)
    start, end = 0, rows
    for a in broadcasted:
        out[start:end] = a.reshape(-1)
        start, end = end, end + rows
    return out.reshape(cols, rows).T

# from https://stackoverflow.com/a/1235363/577088

def cartesian_product_recursive(*arrays, out=None):
    arrays = [numpy.asarray(x) for x in arrays]
    dtype = arrays[0].dtype

    n = numpy.prod([x.size for x in arrays])
    if out is None:
        out = numpy.zeros([n, len(arrays)], dtype=dtype)

    m = n // arrays[0].size
    out[:,0] = numpy.repeat(arrays[0], m)
    if arrays[1:]:
        cartesian_product_recursive(arrays[1:], out=out[0:m,1:])
        for j in range(1, arrays[0].size):
            out[j*m:(j+1)*m,1:] = out[0:m,1:]
    return out

def cartesian_product_itertools(*arrays):
    return numpy.array(list(itertools.product(*arrays)))

### Test code ###

name_func = [('repeat_product',                                                 
              repeat_product),                                                  
             ('dstack_product',                                                 
              dstack_product),                                                  
             ('cartesian_product',                                              
              cartesian_product),                                               
             ('cartesian_product_transpose',                                    
              cartesian_product_transpose),                                     
             ('cartesian_product_recursive',                           
              cartesian_product_recursive),                            
             ('cartesian_product_itertools',                                    
              cartesian_product_itertools)]

def test(in_arrays, test_funcs):
    global func
    global arrays
    arrays = in_arrays
    for name, func in test_funcs:
        print('{}:'.format(name))
        %timeit func(*arrays)

def test_all(*in_arrays):
    test(in_arrays, name_func)

# `cartesian_product_recursive` throws an 
# unexpected error when used on more than
# two input arrays, so for now I've removed
# it from these tests.

def test_cartesian(*in_arrays):
    test(in_arrays, name_func[2:4] + name_func[-1:])

x10 = [numpy.arange(10)]
x50 = [numpy.arange(50)]
x100 = [numpy.arange(100)]
x500 = [numpy.arange(500)]
x1000 = [numpy.arange(1000)]

Результати тесту:

In [2]: test_all(*(x100 * 2))
repeat_product:
67.5 µs ± 633 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
dstack_product:
67.7 µs ± 1.09 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product:
33.4 µs ± 558 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product_transpose:
67.7 µs ± 932 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product_recursive:
215 µs ± 6.01 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_itertools:
3.65 ms ± 38.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [3]: test_all(*(x500 * 2))
repeat_product:
1.31 ms ± 9.28 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
dstack_product:
1.27 ms ± 7.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product:
375 µs ± 4.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_transpose:
488 µs ± 8.88 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_recursive:
2.21 ms ± 38.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
105 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [4]: test_all(*(x1000 * 2))
repeat_product:
10.2 ms ± 132 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
dstack_product:
12 ms ± 120 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product:
4.75 ms ± 57.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_transpose:
7.76 ms ± 52.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_recursive:
13 ms ± 209 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
422 ms ± 7.77 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

У всіх випадках, cartesian_productяк визначено на початку, ця відповідь є найшвидшою.

Для тих функцій, які приймають довільну кількість вхідних масивів, варто також перевірити продуктивність len(arrays) > 2. (Поки я не можу визначити, чому cartesian_product_recursiveв цьому випадку видається помилка, я її видалив із цих тестів.)

In [5]: test_cartesian(*(x100 * 3))
cartesian_product:
8.8 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_transpose:
7.87 ms ± 91.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
518 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [6]: test_cartesian(*(x50 * 4))
cartesian_product:
169 ms ± 5.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_transpose:
184 ms ± 4.32 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_itertools:
3.69 s ± 73.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [7]: test_cartesian(*(x10 * 6))
cartesian_product:
26.5 ms ± 449 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_transpose:
16 ms ± 133 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
728 ms ± 16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [8]: test_cartesian(*(x10 * 7))
cartesian_product:
650 ms ± 8.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cartesian_product_transpose:
518 ms ± 7.09 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cartesian_product_itertools:
8.13 s ± 122 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

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

Варто ще раз зазначити, що користувачі з іншими апаратними та операційними системами можуть бачити різні результати. Наприклад, звіти unutbu, які бачать такі результати для цих тестів, використовуючи Ubuntu 14.04, Python 3.4.3 та numpy1.14.0.dev0 + b7050a9:

>>> %timeit cartesian_product_transpose(x500, y500) 
1000 loops, best of 3: 682 µs per loop
>>> %timeit cartesian_product(x500, y500)
1000 loops, best of 3: 1.55 ms per loop

Нижче я розглядаю декілька деталей про попередні тести, які я проходив у цих напрямках. Відносна ефективність цих підходів з часом змінювалася для різних апаратних засобів та різних версій Python та numpy. Хоча це не одразу корисно людям, які використовують сучасні версії numpy, воно ілюструє, як змінилися речі з моменту першої версії цієї відповіді.

Проста альтернатива: meshgrid+dstack

В даний час прийнята відповідь використовує tileта repeatтранслює два масиви разом. Але meshgridфункція робить практично те саме. Ось результат tileта repeatперед тим, як перейти до транспозиції:

In [1]: import numpy
In [2]: x = numpy.array([1,2,3])
   ...: y = numpy.array([4,5])
   ...: 

In [3]: [numpy.tile(x, len(y)), numpy.repeat(y, len(x))]
Out[3]: [array([1, 2, 3, 1, 2, 3]), array([4, 4, 4, 5, 5, 5])]

І ось результат meshgrid:

In [4]: numpy.meshgrid(x, y)
Out[4]: 
[array([[1, 2, 3],
        [1, 2, 3]]), array([[4, 4, 4],
        [5, 5, 5]])]

Як бачите, він майже однаковий. Нам потрібно лише переробити результат, щоб отримати точно такий же результат.

In [5]: xt, xr = numpy.meshgrid(x, y)
   ...: [xt.ravel(), xr.ravel()]
Out[5]: [array([1, 2, 3, 1, 2, 3]), array([4, 4, 4, 5, 5, 5])]

Замість того, щоб переробляти на цьому етапі, однак ми можемо передати висновок meshgridдо dstackта змінити його згодом, що економить певну роботу:

In [6]: numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
Out[6]: 
array([[1, 4],
       [2, 4],
       [3, 4],
       [1, 5],
       [2, 5],
       [3, 5]])

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

Тестування meshgrid+ dstackпроти repeat+transpose

Відносна ефективність цих двох підходів з часом змінювалася. У більш ранній версії Python (2.7) результат з використанням meshgrid+ dstackбув помітно швидшим для невеликих входів. (Зауважте, що ці тести виходять із старої версії цієї відповіді.) Визначення:

>>> def repeat_product(x, y):
...     return numpy.transpose([numpy.tile(x, len(y)), 
                                numpy.repeat(y, len(x))])
...
>>> def dstack_product(x, y):
...     return numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
...     

Для введення середнього розміру я побачив значну швидкість. Але я повторно повторив ці тести з більш новими версіями Python (3.6.1) та numpy(1.12.1) на більш новій машині. Два підходи зараз майже однакові.

Старий тест

>>> x, y = numpy.arange(500), numpy.arange(500)
>>> %timeit repeat_product(x, y)
10 loops, best of 3: 62 ms per loop
>>> %timeit dstack_product(x, y)
100 loops, best of 3: 12.2 ms per loop

Новий тест

In [7]: x, y = numpy.arange(500), numpy.arange(500)
In [8]: %timeit repeat_product(x, y)
1.32 ms ± 24.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [9]: %timeit dstack_product(x, y)
1.26 ms ± 8.47 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Як завжди, YMMV, але це говорить про те, що в останніх версіях Python і numpy вони взаємозамінні.

Узагальнені функції продукту

Загалом, ми можемо очікувати, що використання вбудованих функцій буде швидше для малих входів, тоді як для великих входів функція, побудована за призначенням, може бути швидшою. Крім того , для узагальненого п-мірного продукту, tileа repeatне допоможе, тому що вони не мають чітких Багатовимірні аналоги. Тож варто вивчити і поведінку цілеспрямованих функцій.

Більшість відповідних тестів з'являються на початку цієї відповіді, але ось кілька тестів, виконаних на більш ранніх версіях Python та numpyдля порівняння.

cartesianФункція , певна в іншому відповіді використовується для виконання досить добре для великих входів. (Це те ж саме, що і функція називається cartesian_product_recursiveвище.) Для того , щоб порівняти cartesianз dstack_prodct, ми використовуємо тільки два виміри.

Тут знову старий тест показав істотну різницю, тоді як новий тест не показує майже жодного.

Старий тест

>>> x, y = numpy.arange(1000), numpy.arange(1000)
>>> %timeit cartesian([x, y])
10 loops, best of 3: 25.4 ms per loop
>>> %timeit dstack_product(x, y)
10 loops, best of 3: 66.6 ms per loop

Новий тест

In [10]: x, y = numpy.arange(1000), numpy.arange(1000)
In [11]: %timeit cartesian([x, y])
12.1 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [12]: %timeit dstack_product(x, y)
12.7 ms ± 334 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Як і раніше, dstack_productвсе ще б'ється cartesianпри менших масштабах.

Новий тест ( надлишковий старий тест не показаний )

In [13]: x, y = numpy.arange(100), numpy.arange(100)
In [14]: %timeit cartesian([x, y])
215 µs ± 4.75 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [15]: %timeit dstack_product(x, y)
65.7 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

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


1
і додавання dtype=objectв arr = np.empty( )це дозволить використовувати різні типи у продукті, наприклад arrays = [np.array([1,2,3]), ['str1', 'str2']].
користувач3820991

Дуже дякую за ваші інноваційні рішення. Подумав, що ви хочете знати, що деякі користувачі можуть знайти cartesian_product_tranposeшвидше, ніж це cartesian_productзалежить від їх ОС, версії python або numpy. Наприклад, на Ubuntu 14.04, python3.4.3, Numpy 1.14.0.dev0 + b7050a9, %timeit cartesian_product_transpose(x500,y500)врожайність в 1000 loops, best of 3: 682 µs per loopтой час як %timeit cartesian_product(x500,y500)врожайність 1000 loops, best of 3: 1.55 ms per loop. Я також знаходжу, cartesian_product_transposeможливо, коли швидше len(arrays) > 2.
unutbu

Крім того, cartesian_productповертає масив dtype з плаваючою комою, в той час як cartesian_product_transposeповертає масив того самого dtype, що і перший (широкомовний) масив. Можливість збереження dtype під час роботи з цілими масивами може стати причиною переваги користувачів cartesian_product_transpose.
unutbu

@unutbu ще раз дякую - як я повинен був знати, клонування dtype не просто додає зручності; це прискорює код ще на 20-30% в деяких випадках.
senderle

1
@senderle: Нічого, це приємно! Крім того, мені просто спало на думку, що щось подібне dtype = np.find_common_type([arr.dtype for arr in arrays], [])можна було б використовувати для пошуку загального типу всіх масивів, а не змушувати користувача розміщувати масив, який керує dtype спочатку.
unutbu

44

Ви можете просто зробити звичайне розуміння списку в python

x = numpy.array([1,2,3])
y = numpy.array([4,5])
[[x0, y0] for x0 in x for y0 in y]

який повинен вам дати

[[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]

28

Я також зацікавився цим і трохи порівняв ефективність, можливо, дещо чіткіше, ніж у відповіді @ senderle.

Для двох масивів (класичний випадок):

введіть тут опис зображення

Для чотирьох масивів:

введіть тут опис зображення

(Зверніть увагу, що довжина масивів становить лише кілька десятків записів.)


Код для відтворення сюжетів:

from functools import reduce
import itertools
import numpy
import perfplot


def dstack_product(arrays):
    return numpy.dstack(numpy.meshgrid(*arrays, indexing="ij")).reshape(-1, len(arrays))


# Generalized N-dimensional products
def cartesian_product(arrays):
    la = len(arrays)
    dtype = numpy.find_common_type([a.dtype for a in arrays], [])
    arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[..., i] = a
    return arr.reshape(-1, la)


def cartesian_product_transpose(arrays):
    broadcastable = numpy.ix_(*arrays)
    broadcasted = numpy.broadcast_arrays(*broadcastable)
    rows, cols = reduce(numpy.multiply, broadcasted[0].shape), len(broadcasted)
    dtype = numpy.find_common_type([a.dtype for a in arrays], [])

    out = numpy.empty(rows * cols, dtype=dtype)
    start, end = 0, rows
    for a in broadcasted:
        out[start:end] = a.reshape(-1)
        start, end = end, end + rows
    return out.reshape(cols, rows).T


# from https://stackoverflow.com/a/1235363/577088
def cartesian_product_recursive(arrays, out=None):
    arrays = [numpy.asarray(x) for x in arrays]
    dtype = arrays[0].dtype

    n = numpy.prod([x.size for x in arrays])
    if out is None:
        out = numpy.zeros([n, len(arrays)], dtype=dtype)

    m = n // arrays[0].size
    out[:, 0] = numpy.repeat(arrays[0], m)
    if arrays[1:]:
        cartesian_product_recursive(arrays[1:], out=out[0:m, 1:])
        for j in range(1, arrays[0].size):
            out[j * m : (j + 1) * m, 1:] = out[0:m, 1:]
    return out


def cartesian_product_itertools(arrays):
    return numpy.array(list(itertools.product(*arrays)))


perfplot.show(
    setup=lambda n: 2 * (numpy.arange(n, dtype=float),),
    n_range=[2 ** k for k in range(13)],
    # setup=lambda n: 4 * (numpy.arange(n, dtype=float),),
    # n_range=[2 ** k for k in range(6)],
    kernels=[
        dstack_product,
        cartesian_product,
        cartesian_product_transpose,
        cartesian_product_recursive,
        cartesian_product_itertools,
    ],
    logx=True,
    logy=True,
    xlabel="len(a), len(b)",
    equality_check=None,
)

17

Спираючись на зразкові наземні роботи @ senderle, я придумав дві версії - одну для C і одну для макетів Fortran - які часто трохи швидші.

  • cartesian_product_transpose_ppє - на відміну від @ senderle's, cartesian_product_transposeяка взагалі використовує іншу стратегію - версія, cartesion_productяка використовує більш сприятливий компонування пам'яті транспонування + деякі дуже незначні оптимізації.
  • cartesian_product_ppпалички з оригінальним макетом пам'яті. Завдяки цьому відбувається швидке копіювання. Суміжні копії виявляються настільки швидшими, що копіювання повного блоку пам'яті, навіть якщо лише частина його містить дійсні дані, бажано лише копіювати дійсні біти.

Деякі парфлоти. Я зробив окремі для макетів C і Fortran, оскільки це різні завдання ІМО.

Імена, що закінчуються на "pp" - це мої підходи.

1) безліч крихітних факторів (по 2 елемента кожен)

введіть тут опис зображеннявведіть тут опис зображення

2) безліч малих факторів (по 4 елемента кожен)

введіть тут опис зображеннявведіть тут опис зображення

3) три фактори однакової довжини

введіть тут опис зображеннявведіть тут опис зображення

4) два коефіцієнти однакової довжини

введіть тут опис зображеннявведіть тут опис зображення

Код (потрібно робити окремі прогони для кожного сюжету b / c. Я не міг розібратися, як скинути; також потрібно правильно відредагувати / прокоментувати вхід / вихід):

import numpy
import numpy as np
from functools import reduce
import itertools
import timeit
import perfplot

def dstack_product(arrays):
    return numpy.dstack(
        numpy.meshgrid(*arrays, indexing='ij')
        ).reshape(-1, len(arrays))

def cartesian_product_transpose_pp(arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty((la, *map(len, arrays)), dtype=dtype)
    idx = slice(None), *itertools.repeat(None, la)
    for i, a in enumerate(arrays):
        arr[i, ...] = a[idx[:la-i]]
    return arr.reshape(la, -1).T

def cartesian_product(arrays):
    la = len(arrays)
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
    for i, a in enumerate(numpy.ix_(*arrays)):
        arr[...,i] = a
    return arr.reshape(-1, la)

def cartesian_product_transpose(arrays):
    broadcastable = numpy.ix_(*arrays)
    broadcasted = numpy.broadcast_arrays(*broadcastable)
    rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
    dtype = numpy.result_type(*arrays)

    out = numpy.empty(rows * cols, dtype=dtype)
    start, end = 0, rows
    for a in broadcasted:
        out[start:end] = a.reshape(-1)
        start, end = end, end + rows
    return out.reshape(cols, rows).T

from itertools import accumulate, repeat, chain

def cartesian_product_pp(arrays, out=None):
    la = len(arrays)
    L = *map(len, arrays), la
    dtype = numpy.result_type(*arrays)
    arr = numpy.empty(L, dtype=dtype)
    arrs = *accumulate(chain((arr,), repeat(0, la-1)), np.ndarray.__getitem__),
    idx = slice(None), *itertools.repeat(None, la-1)
    for i in range(la-1, 0, -1):
        arrs[i][..., i] = arrays[i][idx[:la-i]]
        arrs[i-1][1:] = arrs[i]
    arr[..., 0] = arrays[0][idx]
    return arr.reshape(-1, la)

def cartesian_product_itertools(arrays):
    return numpy.array(list(itertools.product(*arrays)))


# from https://stackoverflow.com/a/1235363/577088
def cartesian_product_recursive(arrays, out=None):
    arrays = [numpy.asarray(x) for x in arrays]
    dtype = arrays[0].dtype

    n = numpy.prod([x.size for x in arrays])
    if out is None:
        out = numpy.zeros([n, len(arrays)], dtype=dtype)

    m = n // arrays[0].size
    out[:, 0] = numpy.repeat(arrays[0], m)
    if arrays[1:]:
        cartesian_product_recursive(arrays[1:], out=out[0:m, 1:])
        for j in range(1, arrays[0].size):
            out[j*m:(j+1)*m, 1:] = out[0:m, 1:]
    return out

### Test code ###
if False:
  perfplot.save('cp_4el_high.png',
    setup=lambda n: n*(numpy.arange(4, dtype=float),),
                n_range=list(range(6, 11)),
    kernels=[
        dstack_product,
        cartesian_product_recursive,
        cartesian_product,
#        cartesian_product_transpose,
        cartesian_product_pp,
#        cartesian_product_transpose_pp,
        ],
    logx=False,
    logy=True,
    xlabel='#factors',
    equality_check=None
    )
else:
  perfplot.save('cp_2f_T.png',
    setup=lambda n: 2*(numpy.arange(n, dtype=float),),
    n_range=[2**k for k in range(5, 11)],
    kernels=[
#        dstack_product,
#        cartesian_product_recursive,
#        cartesian_product,
        cartesian_product_transpose,
#        cartesian_product_pp,
        cartesian_product_transpose_pp,
        ],
    logx=True,
    logy=True,
    xlabel='length of each factor',
    equality_check=None
    )

Дякуємо, що поділилися цією чудовою відповіддю. Коли розмір arraysв cartesian_product_transpose_pp (масиви) перевищить певний розмір, MemoryErrorвідбудеться. У цій ситуації я хотів би, щоб ця функція давала менші шматки результатів. Я опублікував питання з цього приводу. Чи можете ви вирішити моє запитання? Дякую.
Ведмідь Сонця,

13

Станом на жовтень 2017 року numpy тепер має загальну np.stackфункцію, яка приймає параметр осі. Використовуючи його, ми можемо мати "узагальнений декартовий продукт", використовуючи техніку "dstack and meshgrid":

import numpy as np
def cartesian_product(*arrays):
    ndim = len(arrays)
    return np.stack(np.meshgrid(*arrays), axis=-1).reshape(-1, ndim)

Примітка до axis=-1параметра. Це остання (найбільш внутрішня) вісь у результаті. Це еквівалентно використанню axis=ndim.

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


8

Я деякий час використовував відповідь @kennytm , але, намагаючись зробити те ж саме в TensorFlow, але виявив, що TensorFlow не має еквівалента numpy.repeat(). Після невеликого експерименту, я думаю, що я знайшов більш загальне рішення для довільних векторів точок.

Для нуме:

import numpy as np

def cartesian_product(*args: np.ndarray) -> np.ndarray:
    """
    Produce the cartesian product of arbitrary length vectors.

    Parameters
    ----------
    np.ndarray args
        vector of points of interest in each dimension

    Returns
    -------
    np.ndarray
        the cartesian product of size [m x n] wherein:
            m = prod([len(a) for a in args])
            n = len(args)
    """
    for i, a in enumerate(args):
        assert a.ndim == 1, "arg {:d} is not rank 1".format(i)
    return np.concatenate([np.reshape(xi, [-1, 1]) for xi in np.meshgrid(*args)], axis=1)

і для TensorFlow:

import tensorflow as tf

def cartesian_product(*args: tf.Tensor) -> tf.Tensor:
    """
    Produce the cartesian product of arbitrary length vectors.

    Parameters
    ----------
    tf.Tensor args
        vector of points of interest in each dimension

    Returns
    -------
    tf.Tensor
        the cartesian product of size [m x n] wherein:
            m = prod([len(a) for a in args])
            n = len(args)
    """
    for i, a in enumerate(args):
        tf.assert_rank(a, 1, message="arg {:d} is not rank 1".format(i))
    return tf.concat([tf.reshape(xi, [-1, 1]) for xi in tf.meshgrid(*args)], axis=1)

6

Пакет Scikit-learn має швидке реалізацію саме цього:

from sklearn.utils.extmath import cartesian
product = cartesian((x,y))

Зауважте, що умова цієї реалізації відрізняється від бажаного, якщо ви дбаєте про порядок виводу. Для точного замовлення ви можете зробити

product = cartesian((y,x))[:, ::-1]

Це швидше, ніж функція @ senderle?
cs95

@ cᴏʟᴅsᴘᴇᴇᴅ Я не перевірений. Я сподівався, що це буде реалізовано, наприклад, на C або Fortran, і, таким чином, є значно неперевершеним, але, здається, це написано за допомогою NumPy. Як така, ця функція зручна, але не повинна бути значно швидшою, ніж те, що можна побудувати за допомогою конструкцій NumPy.
jmd_dk

4

Більш загально, якщо у вас є два двомірні масиви a і b, і ви хочете об'єднати кожен рядок a в кожен рядок b (декартовий добуток рядків, подібний до об'єднання в базу даних), ви можете використовувати цей метод :

import numpy
def join_2d(a, b):
    assert a.dtype == b.dtype
    a_part = numpy.tile(a, (len(b), 1))
    b_part = numpy.repeat(b, len(a), axis=0)
    return numpy.hstack((a_part, b_part))

3

Найшвидший ви можете отримати, поєднавши вираз генератора з функцією map:

import numpy
import datetime
a = np.arange(1000)
b = np.arange(200)

start = datetime.datetime.now()

foo = (item for sublist in [list(map(lambda x: (x,i),a)) for i in b] for item in sublist)

print (list(foo))

print ('execution time: {} s'.format((datetime.datetime.now() - start).total_seconds()))

Вихідні дані (фактично друкується весь отриманий список):

[(0, 0), (1, 0), ...,(998, 199), (999, 199)]
execution time: 1.253567 s

або за допомогою подвійного генераторного виразу:

a = np.arange(1000)
b = np.arange(200)

start = datetime.datetime.now()

foo = ((x,y) for x in a for y in b)

print (list(foo))

print ('execution time: {} s'.format((datetime.datetime.now() - start).total_seconds()))

Виходи (весь список надрукований):

[(0, 0), (1, 0), ...,(998, 199), (999, 199)]
execution time: 1.187415 s

Враховуйте, що більша частина часу на обчислення йде в команду друку. Інакше розрахунки генератора є пристойно ефективними. Без друку часові розрахунки є:

execution time: 0.079208 s

для генератора вираз + функція карти та:

execution time: 0.007093 s

для подвійного генераторного вираження.

Якщо ви насправді хочете - це обчислити фактичний добуток кожної з координатних пар, найшвидше це вирішити як матричний продукт матриці:

a = np.arange(1000)
b = np.arange(200)

start = datetime.datetime.now()

foo = np.dot(np.asmatrix([[i,0] for i in a]), np.asmatrix([[i,0] for i in b]).T)

print (foo)

print ('execution time: {} s'.format((datetime.datetime.now() - start).total_seconds()))

Виходи:

 [[     0      0      0 ...,      0      0      0]
 [     0      1      2 ...,    197    198    199]
 [     0      2      4 ...,    394    396    398]
 ..., 
 [     0    997   1994 ..., 196409 197406 198403]
 [     0    998   1996 ..., 196606 197604 198602]
 [     0    999   1998 ..., 196803 197802 198801]]
execution time: 0.003869 s

і без друку (у цьому випадку це не економить багато, оскільки насправді роздруковується лише крихітний фрагмент матриці):

execution time: 0.003083 s

Для розрахунку продукту зовнішнє мовлення продукту foo = a[:,None]*bвідбувається швидше. Використовуючи свій метод хронометражу print(foo), це 0,001103 с проти 0,002225 с. Використовуючи timeit, це 304 мкс проти 1,6 мс. Як відомо, матриця повільніше, ніж ndarray, тому я спробував ваш код з np.array, але він все ще повільніше (1,57 мс), ніж мовлення.
syockit

2

Це також легко зробити за допомогою методу itertools.product

from itertools import product
import numpy as np

x = np.array([1, 2, 3])
y = np.array([4, 5])
cart_prod = np.array(list(product(*[x, y])),dtype='int32')

Результат: масив ([
[1, 4],
[1, 5],
[2, 4],
[2, 5],
[3, 4],
[3, 5]], dtype = int32)

Час виконання: 0.000155 с


1
вам не потрібно дзвонити нудно. звичайний старий масив пітонів також працює з цим.
Кодді

0

У конкретному випадку, коли вам потрібно виконати прості операції, такі як додавання для кожної пари, ви можете ввести додатковий вимір і дозволити трансляції виконувати роботу:

>>> a, b = np.array([1,2,3]), np.array([10,20,30])
>>> a[None,:] + b[:,None]
array([[11, 12, 13],
       [21, 22, 23],
       [31, 32, 33]])

Я не впевнений, чи є якийсь подібний спосіб насправді отримати пари.


Якщо dtypeце floatви можете зробити , (a[:, None, None] + 1j * b[None, :, None]).view(float)що на диво швидко.
Пол Панцер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.