web-dev-qa-db-ja.com

2Dマトリックスを3D1ホットマトリックスnumpyに変換します

Np行列があり、要素の1つのホットエンコーディングを3次元として使用して3D配列に変換したいと思います。各行をループせずに処理する方法はありますか?

a=[[1,3],
   [2,4]]

にする必要があります

b=[[1,0,0,0], [0,0,1,0],
   [0,1,0,0], [0,0,0,1]]
15
Rahul

アプローチ#1

これは悪用する生意気なワンライナーです broadcasted 比較-

_(np.arange(a.max()) == a[...,None]-1).astype(int)
_

サンプル実行-

_In [120]: a
Out[120]: 
array([[1, 7, 5, 3],
       [2, 4, 1, 4]])

In [121]: (np.arange(a.max()) == a[...,None]-1).astype(int)
Out[121]: 
array([[[1, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 1],
        [0, 0, 0, 0, 1, 0, 0],
        [0, 0, 1, 0, 0, 0, 0]],

       [[0, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 1, 0, 0, 0],
        [1, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 1, 0, 0, 0]]])
_

_0-based_インデックスの場合、次のようになります-

_In [122]: (np.arange(a.max()+1) == a[...,None]).astype(int)
Out[122]: 
array([[[0, 1, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 1],
        [0, 0, 0, 0, 0, 1, 0, 0],
        [0, 0, 0, 1, 0, 0, 0, 0]],

       [[0, 0, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 1, 0, 0, 0],
        [0, 1, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 1, 0, 0, 0]]])
_

ワンホットエンコンディングが最小値から最大値までの範囲の値をカバーする場合は、最小値でオフセットしてから、提案された_0-based_インデックス作成方法にフィードします。これは、この投稿の後半で説明する残りのアプローチにも当てはまります。

これは同じ上で実行されたサンプルです-

_In [223]: a
Out[223]: 
array([[ 6, 12, 10,  8],
       [ 7,  9,  6,  9]])

In [224]: a_off = a - a.min() # feed a_off to proposed approaches

In [225]: (np.arange(a_off.max()+1) == a_off[...,None]).astype(int)
Out[225]: 
array([[[1, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 1],
        [0, 0, 0, 0, 1, 0, 0],
        [0, 0, 1, 0, 0, 0, 0]],

       [[0, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 1, 0, 0, 0],
        [1, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 1, 0, 0, 0]]])
_

_1's_の場合はTrue、_0's_の場合はFalseのブール配列で問題がない場合は、.astype(int)変換をスキップできます。

アプローチ#2

_advanced-indexing_ を使用して、ゼロ配列を初期化し、出力にインデックスを付けることもできます。したがって、_0-based_インデックスの場合、次のようになります-

_def onehot_initialization(a):
    ncols = a.max()+1
    out = np.zeros(a.shape + (ncols,), dtype=int)
    out[all_idx(a, axis=2)] = 1
    return out
_

ヘルパー機能-

_# https://stackoverflow.com/a/46103129/ @Divakar
def all_idx(idx, axis):
    grid = np.ogrid[Tuple(map(slice, idx.shape))]
    grid.insert(axis, idx)
    return Tuple(grid)
_

これは、より広い範囲の値を処理する場合に特にパフォーマンスが向上するはずです。

_1-based_インデックスの場合は、入力として_a-1_を入力するだけです。

アプローチ#3:スパース行列ソリューション

Scipyの組み込みスパース行列は_2D_形式のみをサポートしているため、出力およびAFAIKとしてスパース配列を探している場合は、最初の2つの軸をマージして前に示した出力の再形成バージョンであるスパース出力を取得できます。 3番目の軸はそのまま保持されます。 _0-based_インデックスの実装は次のようになります-

_from scipy.sparse import coo_matrix
def onehot_sparse(a):
    N = a.size
    L = a.max()+1
    data = np.ones(N,dtype=int)
    return coo_matrix((data,(np.arange(N),a.ravel())), shape=(N,L))
_

繰り返しますが、_1-based_インデックス付けの場合は、入力として_a-1_を入力するだけです。

サンプル実行-

_In [157]: a
Out[157]: 
array([[1, 7, 5, 3],
       [2, 4, 1, 4]])

In [158]: onehot_sparse(a).toarray()
Out[158]: 
array([[0, 1, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 0, 1, 0, 0],
       [0, 0, 0, 1, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 1, 0, 0, 0],
       [0, 1, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 1, 0, 0, 0]])

In [159]: onehot_sparse(a-1).toarray()
Out[159]: 
array([[1, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0, 0, 0],
       [0, 1, 0, 0, 0, 0, 0],
       [0, 0, 0, 1, 0, 0, 0],
       [1, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 1, 0, 0, 0]])
_

これは、出力がまばらで問題がない場合は、前の2つのアプローチよりもはるかに優れています。

0ベースのインデックス作成の実行時比較

ケース#1:

_In [160]: a = np.random.randint(0,100,(100,100))

In [161]: %timeit (np.arange(a.max()+1) == a[...,None]).astype(int)
1000 loops, best of 3: 1.51 ms per loop

In [162]: %timeit onehot_initialization(a)
1000 loops, best of 3: 478 µs per loop

In [163]: %timeit onehot_sparse(a)
10000 loops, best of 3: 87.5 µs per loop

In [164]: %timeit onehot_sparse(a).toarray()
1000 loops, best of 3: 530 µs per loop
_

ケース#2:

_In [166]: a = np.random.randint(0,500,(100,100))

In [167]: %timeit (np.arange(a.max()+1) == a[...,None]).astype(int)
100 loops, best of 3: 8.51 ms per loop

In [168]: %timeit onehot_initialization(a)
100 loops, best of 3: 2.52 ms per loop

In [169]: %timeit onehot_sparse(a)
10000 loops, best of 3: 87.1 µs per loop

In [170]: %timeit onehot_sparse(a).toarray()
100 loops, best of 3: 2.67 ms per loop
_

最高のパフォーマンスを絞り出す

最高のパフォーマンスを引き出すために、アプローチ#2を変更して、_2D_形状の出力配列でインデックスを使用し、_uint8_ dtypeを使用してメモリ効率を高め、割り当てを大幅に高速化することができます。

_def onehot_initialization_v2(a):
    ncols = a.max()+1
    out = np.zeros( (a.size,ncols), dtype=np.uint8)
    out[np.arange(a.size),a.ravel()] = 1
    out.shape = a.shape + (ncols,)
    return out
_

タイミング-

_In [178]: a = np.random.randint(0,100,(100,100))

In [179]: %timeit onehot_initialization(a)
     ...: %timeit onehot_initialization_v2(a)
     ...: 
1000 loops, best of 3: 474 µs per loop
10000 loops, best of 3: 128 µs per loop

In [180]: a = np.random.randint(0,500,(100,100))

In [181]: %timeit onehot_initialization(a)
     ...: %timeit onehot_initialization_v2(a)
     ...: 
100 loops, best of 3: 2.38 ms per loop
1000 loops, best of 3: 213 µs per loop
_
32
Divakar

編集:私の答えはすでに受け入れられた答えでカバーされていることに気づきました。残念ながら、未登録のユーザーとして、これ以上削除することはできません。

受け入れられた回答の補足として:エンコードするクラスの数が非常に少なく、出力としてnp.bool配列を受け入れることができる場合、次のことがさらにわずかに高速であることがわかりました。

def onehot_initialization_v3(a):
    ncols = a.max() + 1
    labels_one_hot = (a.ravel()[np.newaxis] == np.arange(ncols)[:, np.newaxis]).T
    labels_one_hot.shape = a.shape + (ncols,)
    return labels_one_hot

タイミング(10クラスの場合):

a = np.random.randint(0,10,(100,100))
assert np.all(onehot_initialization_v2(a) == onehot_initialization_v3(a))
%timeit onehot_initialization_v2(a)
%timeit onehot_initialization_v3(a)

# 102 µs ± 1.66 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# 79.3 µs ± 815 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

ただし、クラスの数が増えると(現在は100クラス)、これは変わります。

a = np.random.randint(0,100,(100,100))
assert np.all(onehot_initialization_v2(a) == one_hot_initialization_v3(a))
%timeit onehot_initialization_v2(a)
%timeit onehot_initialization_v3(a)

# 132 µs ± 1.4 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# 639 µs ± 3.12 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

したがって、問題に応じて、どちらかがより高速なバージョンになる可能性があります。

1
simon

機械学習モデル用のワンホットテンソルを作成しようとしている場合(tensorflowまたはkerasがインストールされている場合)、 https :)からone_hot関数を使用できます。 //www.tensorflow.org/api_docs/python/tf/keras/backend/one_hot または https://www.tensorflow.org/api_docs/python/tf/one_hot

これは私が使用しているものであり、高次元データに適しています。

使用例は次のとおりです。

>>> import tensorflow as tf

>>> tf.one_hot([[0,2],[1,3]], 4).numpy()
array([[[1., 0., 0., 0.],
        [0., 0., 1., 0.]],

       [[0., 1., 0., 0.],
        [0., 0., 0., 1.]]], dtype=float32)
1
off99555