x配列とy配列の点のデカルト積を2D点の単一配列に


147

グリッドのx軸とy軸を定義する2つの派手な配列があります。例えば:

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アプローチで最もコストのかかるステップは、リストから配列への最終的な変換であることに気付きました。この最後のステップがないと、ケンの例の2倍の速さになります。
Alexey Lebedev

回答:


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

N配列のデカルト積を計算する一般的なソリューションについては、numpyを使用して2つの配列のすべての組み合わせの配列を作成するを参照してください。


1
このアプローチの利点は、同じサイズの配列に対して一貫した出力を生成することです。meshgrid+のdstackあなたはデカルト積は、同じサイズの配列のために同じ順序で構築することが予想される場合のアプローチは、いくつかのケースで速くながら、バグにつながることができます。
tlnagy

3
@tlnagy、私はこのアプローチがmeshgrid+ によって生成されたものとは異なる結果を生成するケースに気づきませんでしたdstack。例を投稿していただけませんか?
センダーレ2017

148

正規cartesian_product(ほぼ)

さまざまなプロパティを使用して、この問題に対する多くのアプローチがあります。他のものより高速なものもあれば、より汎用的なものもあります。多くのテストと調整を行った結果、n次元を計算する次の関数は、cartesian_product多くの入力に対して他のほとんどの関数よりも高速であることがわかりました。少し複雑ですが、多くの場合はさらに高速な2つのアプローチについては、Paul Panzerの回答を参照してください。

その答えを考えると、これは、私の知る限りでは、最速のデカルト積の実装ではなくなってい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と、使用の提案を含め、この回答について非常に役立つフィードバックを提供してくれたunutbuに感謝します。ix_numpy.result_type

注目すべき選択肢

連続したメモリブロックをFortran順に書き込む方が高速な場合があります。cartesian_product_transposeこれは、一部のハードウェアでより高速であることが証明されているcartesian_product(以下を参照)この代替案の基礎です。ただし、同じ原理を使用するPaul Panzerの答えはさらに高速です。それでも、興味のある読者のためにこれをここに含めます:

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の回答とNicoSchlömerの回答を参照してください。

代替案に対するテスト

これらの機能のいくつかがいくつかの選択肢と比較して提供するパフォーマンスの向上を示す一連のテストがあります。ここに示すすべてのテストは、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入力配列の数が(ほぼ)4つを超えるまで、競争力を維持します。その後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を使用して2つのアレイを一緒にブロードキャストします。しかし、meshgrid関数は実質的に同じことを行います。ここでの出力だtilerepeat転置に渡される前には:

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

ただし、この時点で形状を変更するのではなく、meshgridto の出力を渡して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+ dstackrepeat+transpose

これら2つのアプローチの相対的なパフォーマンスは、時間とともに変化しました。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)で新しいマシンで再試行しました。2つのアプローチは現在ほとんど同じです。

古いテスト

>>> 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の最近のバージョンでは、これらは交換可能であることを示唆しています。

一般化された製品機能

一般的に、小さな入力の場合は組み込み関数を使用した方が速く、大きな入力の場合は専用の関数を使用した方が高速であると予想できます。さらに、一般化されたn次元の製品の場合tilerepeat明確な高次元の類似体がないため、効果がありません。したがって、専用の関数の動作も調査する価値があります。

関連するテストのほとんどはこの回答の冒頭にありますが、ここでは、以前のバージョンのPythonで実行されたテストとnumpy比較のためのテストのいくつかを示します。

cartesianで定義された機能別の答えは、大きな入力に対してかなりよく実行するために使用されます。(cartesian_product_recursive上記の関数と同じです。)と比較するcartesianためにdstack_prodct、2つの次元のみを使用します。

ここでも、古いテストでは大きな違いが見られましたが、新しいテストではほとんど違いがありませんでした。

古いテスト

>>> 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']]ます。
user3820991

革新的なソリューションをありがとうございます。一部のユーザーがマシンのOS、python、またはnumpyのバージョンに依存するcartesian_product_tranposeよりも早く見つけることができることを知りたいと思っただけですcartesian_product。たとえば、Ubuntu 14.04では、python3.4.3、numpy 1.14.0.dev0 + b7050a9、%timeit cartesian_product_transpose(x500,y500)yields 1000 loops, best of 3: 682 µs per loopwhileが%timeit cartesian_product(x500,y500)生成され1000 loops, best of 3: 1.55 ms per loopます。私はまた、cartesian_product_transposeより速くなる可能性があることを発見していlen(arrays) > 2ます。
unutbu 2017

さらに、cartesian_product浮動小数点のdtype cartesian_product_transposeの配列を返しますが、最初の(ブロードキャストされた)配列と同じdtypeの配列を返します。整数配列を操作するときにdtypeを保持する機能は、ユーザーが好む理由の1つですcartesian_product_transpose
unutbu 2017

@unutbu感謝します-私が知っているべきであるように、dtypeを複製することは単に便宜を追加するだけではありません。場合によっては、コードがさらに20〜30%高速化されます。
センダーレ2017

1
@senderle:わあ、それはいいですね!また、dtype = np.find_common_type([arr.dtype for arr in arrays], [])ユーザーに最初にdtypeを制御する配列を配置させるのではなく、すべての配列の共通のdtypeを見つけるために、次のようなものが使用できることに気づきました。
unutbu 2017

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の回答よりも多少明確な、わずかなパフォーマンス比較を行いました。

2つの配列の場合(古典的な場合):

ここに画像の説明を入力してください

4つのアレイの場合:

ここに画像の説明を入力してください

(配列の長さはここでは数十のエントリにすぎないことに注意してください。)


プロットを再現するコード:

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レイアウト版の2つのバージョンを思いつきました。

  • cartesian_product_transpose_ppcartesian_product_transpose異なる戦略を使用する@senderleとは異なりcartesion_product、より好ましい転置メモリレイアウトといくつかの非常に小さな最適化を使用するバージョンです。
  • cartesian_product_pp元のメモリレイアウトに固執します。高速になるのは、連続コピーを使用することです。連続したコピーは非常に高速であることが判明し、メモリの一部のみが有効なデータを含む場合でも、有効なビットのみをコピーするよりもメモリのブロック全体をコピーする方が望ましいです。

一部のperfplots。CとFortranのレイアウト用に別々のものを作成しました。これらはIMOの異なるタスクだからです。

「pp」で終わる名前は私のアプローチです。

1)多くの小さな要因(それぞれ2つの要素)

ここに画像の説明を入力してくださいここに画像の説明を入力してください

2)多くの小さな要因(それぞれ4つの要素)

ここに画像の説明を入力してくださいここに画像の説明を入力してください

3)同じ長さの3つの要素

ここに画像の説明を入力してくださいここに画像の説明を入力してください

4)長さが等しい2つの要素

ここに画像の説明を入力してくださいここに画像の説明を入力してください

コード(各プロットに対して個別に実行する必要がある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
    )

このすばらしい答えを共有してくれてありがとう。サイズ場合arrayscartesian_product_transpose_ppに(アレイ)が一定サイズを超え、MemoryError発生します。この状況では、この関数で結果のチャンクを小さくしたいと思います。この件について質問を投稿しました。私の質問に対応できますか?ありがとう。
マレーグマ

13

2017年10月の時点で、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です。

もう1つのコメントは、デカルト積は非常に急速に爆発するため、何らかの理由でメモリ内の配列を実現する必要がない限り、積が非常に大きい場合はitertools、その場で値を利用したい場合があります。


8

しばらく@kennytmの回答を使用しましたが、TensorFlowで同じことを行おうとすると、TensorFlowにはに相当するものがないことがわかりましたnumpy.repeat()。少し実験した結果、任意の点のベクトルのより一般的な解決策を見つけたと思います。

numpyの場合:

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学習パッケージは、まさにこのの高速な実装があります。

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

出力の順序を気にする場合、この実装の規則は希望のものとは異なることに注意してください。あなたの正確な注文のために、あなたはすることができます

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

これは@senderleの関数よりも高速ですか?
cs95 2018年

@cᴏʟᴅsᴘᴇᴇᴅ私はテストしていません。これがCやFortranなどで実装されてい、たいてい無敵ですが、NumPyを使用して書かれているようです。そのため、この関数は便利ですが、NumPyを使用して自分で構築できるものよりも大幅に速くなることはありません。
jmd_dk 2018年

4

より一般的には、2つの2 numpy配列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

あなたが得ることができる最も速いのは、ジェネレータ式をマップ関数と組み合わせることです:

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

doubleジェネレータ式の場合。

実際に必要なのは、各座標ペアの実際の積を計算することである場合、最も速いのはそれをナンピー行列積として解決することです。

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秒vs 0.002225秒になります。timeitを使用すると、1.6 msに対して304μsになります。Matrixはndarrayよりも遅いことがわかっているため、np.arrayを使用してコードを試しましたが、ブロードキャストよりも遅い(1.57 ms)です。
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 s


1
numpyを呼び出す必要はありません。プレーンな古いpython配列もこれで動作します。
Coddy

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)驚くほど速くなります。
Paul Panzer、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.