সূচকগুলিতে নামী অ্যারের গোষ্ঠীর নাম ম্যাপ করার দ্রুততম উপায় কী?


9

আমি লিডারের থ্রিডি পয়েন্টক্লাউড নিয়ে কাজ করছি। পয়েন্টগুলি ন্যাপি অ্যারে দিয়ে দেওয়া হয়েছে যা দেখতে এরকম দেখাচ্ছে:

points = np.array([[61651921, 416326074, 39805], [61605255, 416360555, 41124], [61664810, 416313743, 39900], [61664837, 416313749, 39910], [61674456, 416316663, 39503], [61651933, 416326074, 39802], [61679969, 416318049, 39500], [61674494, 416316677, 39508], [61651908, 416326079, 39800], [61651908, 416326087, 39802], [61664845, 416313738, 39913], [61674480, 416316668, 39503], [61679996, 416318047, 39510], [61605290, 416360572, 41118], [61605270, 416360565, 41122], [61683939, 416313004, 41052], [61683936, 416313033, 41060], [61679976, 416318044, 39509], [61605279, 416360555, 41109], [61664837, 416313739, 39915], [61674487, 416316666, 39505], [61679961, 416318035, 39503], [61683943, 416313004, 41054], [61683930, 416313042, 41059]])

আমি আমার ডেটাগুলি আকারের কিউবগুলিতে গোষ্ঠীযুক্ত রাখতে চাই 50*50*50যাতে প্রতিটি ঘনক্ষেত্রটি আমার pointsমধ্যে থাকা কিছু হ্যাশযোগ্য সূচক এবং অদৃশ্য সূচকগুলি সংরক্ষণ করে । বিভাজন পেতে, আমি cubes = points \\ 50কোন ফলাফল আউটপুট নিযুক্ত:

cubes = np.array([[1233038, 8326521, 796], [1232105, 8327211, 822], [1233296, 8326274, 798], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233038, 8326521, 796], [1233599, 8326360, 790], [1233489, 8326333, 790], [1233038, 8326521, 796], [1233038, 8326521, 796], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233599, 8326360, 790], [1232105, 8327211, 822], [1232105, 8327211, 822], [1233678, 8326260, 821], [1233678, 8326260, 821], [1233599, 8326360, 790], [1232105, 8327211, 822], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233599, 8326360, 790], [1233678, 8326260, 821], [1233678, 8326260, 821]])

আমার পছন্দসই আউটপুটটি দেখতে এমন দেখাচ্ছে:

{(1232105, 8327211, 822): [1, 13, 14, 18]), 
(1233038, 8326521, 796): [0, 5, 8, 9], 
(1233296, 8326274, 798): [2, 3, 10, 19], 
(1233489, 8326333, 790): [4, 7, 11, 20], 
(1233599, 8326360, 790): [6, 12, 17, 21], 
(1233678, 8326260, 821): [15, 16, 22, 23]}

আমার আসল পয়েন্টক্লাউডে কয়েক শ মিলিয়ন মিলিয়ন 3 ডি পয়েন্ট রয়েছে। এই জাতীয় গ্রুপিংয়ের দ্রুততম উপায় কী?

আমি বিভিন্ন সমাধানের সিংহভাগ চেষ্টা করেছি। এখানে পয়েন্টগুলির আকার 20 মিলিয়ন এবং পৃথক কিউবের আকার 1 মিলিয়ন এর কাছাকাছি ধরে ধরে সময় সংযোজনের তুলনা করা হচ্ছে:

পান্ডাস [টিপল (এলেম) -> এনপি.আররে (ডাইটিপ = ইন 64৪)]

import pandas as pd
print(pd.DataFrame(cubes).groupby([0,1,2]).indices)
#takes 9sec

Defauldict [elem.tobytes () বা tuple -> list]

#thanks @abc:
result = defaultdict(list)
for idx, elem in enumerate(cubes):
    result[elem.tobytes()].append(idx) # takes 20.5sec
    # result[elem[0], elem[1], elem[2]].append(idx) #takes 27sec
    # result[tuple(elem)].append(idx) # takes 50sec

numpy_indexed [int -> np.array]

# thanks @Eelco Hoogendoorn for his library
values = npi.group_by(cubes).split(np.arange(len(cubes)))
result = dict(enumerate(values))
# takes 9.8sec

পান্ডা + মাত্রিকতা হ্রাস [ইন -> এনপি.আররে (dtype = int64)]

# thanks @Divakar for showing numexpr library:
import numexpr as ne
def dimensionality_reduction(cubes):
    #cubes = cubes - np.min(cubes, axis=0) #in case some coords are negative 
    cubes = cubes.astype(np.int64)
    s0, s1 = cubes[:,0].max()+1, cubes[:,1].max()+1
    d = {'s0':s0,'s1':s1,'c0':cubes[:,0],'c1':cubes[:,1],'c2':cubes[:,2]}
    c1D = ne.evaluate('c0+c1*s0+c2*s0*s1',d)
    return c1D
cubes = dimensionality_reduction(cubes)
result = pd.DataFrame(cubes).groupby([0]).indices
# takes 2.5 seconds

এখানেcubes.npz ফাইল ডাউনলোড করা এবং একটি কমান্ড ব্যবহার করা সম্ভব

cubes = np.load('cubes.npz')['array']

কর্মক্ষমতা সময় চেক।


আপনার ফলাফলের প্রতিটি তালিকায় সর্বদা একই সূচক রয়েছে?
মাইকোলা জোটকো

হ্যাঁ, এটি সর্বদা একই: উল্লিখিত সমস্ত সমাধানের জন্য 983234 স্বতন্ত্র কিউব।
ম্যাথফাক্স

1
এটির সম্ভাবনা খুব কম নয় যে এ জাতীয় সরল পান্ডাস দ্রবণটি একটি সহজ পদ্ধতির দ্বারা পরাজিত হবে, কারণ এটির অনুকূলকরণের জন্য প্রচুর প্রচেষ্টা ব্যয় করা হয়েছে। সাইথন-ভিত্তিক একটি পদ্ধতি সম্ভবত এটির কাছে আসতে পারে তবে আমি সন্দেহ করি যে এটি এর চেয়ে বেশি কার্যকর হবে।
norok2

1
@ ম্যাথফাক্স আপনার কি অভিধান হিসাবে চূড়ান্ত আউটপুট আছে বা গ্রুপ এবং তাদের সূচকগুলি দুটি আউটপুট হিসাবে রাখা ভাল হবে?
দিবাকর

@ নরোক 2 numpy_indexedকেবল এটির কাছেও আসে। আমার ধারণা ঠিক আছে। আমি pandasবর্তমানে আমার শ্রেণিবিন্যাস প্রক্রিয়া ব্যবহার করি।
ম্যাথফক্স

উত্তর:


6

প্রতি গ্রুপে নিয়মিত সংখ্যার সূচক

পন্থা # 1

আমরা 1D অ্যারে dimensionality-reductionহ্রাস করতে পারফর্ম করতে পারি cubes। এটি রৈখিক-সূচক সমতুল্য গণনা করার জন্য প্রদত্ত কিউবস ডেটার ম্যাপিংয়ের ভিত্তিতে একটি এন-ডিম্প গ্রিডের উপর ভিত্তি করে আলোচনা করা হয়েছে here। তারপরে, এই লিনিয়ার সূচকগুলির স্বতন্ত্রতার ভিত্তিতে আমরা অনন্য গ্রুপ এবং তাদের সম্পর্কিত সূচকগুলি পৃথক করতে পারি। সুতরাং, এই কৌশলগুলি অনুসরণ করে, আমাদের মতো একটি সমাধান হবে -

N = 4 # number of indices per group
c1D = np.ravel_multi_index(cubes.T, cubes.max(0)+1)
sidx = c1D.argsort()
indices = sidx.reshape(-1,N)
unq_groups = cubes[indices[:,0]]

# If you need in a zipped dictionary format
out = dict(zip(map(tuple,unq_groups), indices))

বিকল্প # 1: যদি পূর্ণসংখ্যার মানগুলি cubesখুব বড় হয় তবে আমরা এটি করতে চাই dimensionality-reductionযে সংক্ষিপ্ত পরিমাণের মাত্রাগুলি প্রাথমিক অক্ষ হিসাবে বেছে নেওয়া হয়। অতএব, এই ক্ষেত্রে, আমরা হ্রাস পদক্ষেপটি c1Dযেমন পরিবর্তন করতে পারি তেমন পরিবর্তন করতে পারি -

s1,s2 = cubes[:,:2].max(0)+1
s = np.r_[s2,1,s1*s2]
c1D = cubes.dot(s)

পদ্ধতির # 2

পরবর্তী, আমরা নিকটতম প্রতিবেশী সূচকগুলি পেতে নিকটতম Cython-powered kd-tree-প্রতিবেশী অনুসন্ধানের জন্য ব্যবহার করতে পারি এবং আমাদের কেসটি এর মতো সমাধান করতে পারে -

from scipy.spatial import cKDTree

idx = cKDTree(cubes).query(cubes, k=N)[1] # N = 4 as discussed earlier
I = idx[:,0].argsort().reshape(-1,N)[:,0]
unq_groups,indices = cubes[I],idx[I]

জেনেরিক কেস: প্রতি গ্রুপে সূচকের পরিবর্তনীয় সংখ্যা

আমরা আমাদের কাঙ্ক্ষিত আউটপুট পেতে কিছু বিভক্তির সাথে আর্গোর্ট ভিত্তিক পদ্ধতিটি প্রসারিত করব -

c1D = np.ravel_multi_index(cubes.T, cubes.max(0)+1)

sidx = c1D.argsort()
c1Ds = c1D[sidx]
split_idx = np.flatnonzero(np.r_[True,c1Ds[:-1]!=c1Ds[1:],True])
grps = cubes[sidx[split_idx[:-1]]]

indices = [sidx[i:j] for (i,j) in zip(split_idx[:-1],split_idx[1:])]
# If needed as dict o/p
out = dict(zip(map(tuple,grps), indices))

cubesকী হিসাবে গোষ্ঠীগুলির 1D সংস্করণ ব্যবহার করা

আমরা cubesঅভিধান তৈরির প্রক্রিয়াটিকে সহজ করার জন্য এবং কীগুলি এর সাথে দক্ষ করে তুলতে কীগুলির গ্রুপগুলির সাথে পূর্ববর্তী তালিকাভুক্ত পদ্ধতিটি প্রসারিত করব -

def numpy1(cubes):
    c1D = np.ravel_multi_index(cubes.T, cubes.max(0)+1)        
    sidx = c1D.argsort()
    c1Ds = c1D[sidx]
    mask = np.r_[True,c1Ds[:-1]!=c1Ds[1:],True]
    split_idx = np.flatnonzero(mask)
    indices = [sidx[i:j] for (i,j) in zip(split_idx[:-1],split_idx[1:])]
    out = dict(zip(c1Ds[mask[:-1]],indices))
    return out

পরবর্তী, আমরা numbaপুনরাবৃত্তি করতে এবং চূড়ান্ত hashable অভিধান আউটপুট পেতে প্যাকেজ ব্যবহার করব । এটির সাথে যেতে হলে দুটি সমাধান হতে পারে - একটি যা কীগুলি এবং মানগুলি পৃথকভাবে ব্যবহার করে পায় numbaএবং মূল কলিংটি জিপ করে ডিককে রূপান্তরিত করবে, অন্য অপরটি numba-supportedডিকের ধরণ তৈরি করবে এবং সুতরাং মূল কলিং ফাংশনের জন্য অতিরিক্ত কোনও কাজের প্রয়োজন নেই One ।

সুতরাং, আমাদের প্রথম numbaসমাধান হবে:

from numba import  njit

@njit
def _numba1(sidx, c1D):
    out = []
    n = len(sidx)
    start = 0
    grpID = []
    for i in range(1,n):
        if c1D[sidx[i]]!=c1D[sidx[i-1]]:
            out.append(sidx[start:i])
            grpID.append(c1D[sidx[start]])
            start = i
    out.append(sidx[start:])
    grpID.append(c1D[sidx[start]])
    return grpID,out

def numba1(cubes):
    c1D = np.ravel_multi_index(cubes.T, cubes.max(0)+1)
    sidx = c1D.argsort()
    out = dict(zip(*_numba1(sidx, c1D)))
    return out

এবং দ্বিতীয় numbaসমাধান হিসাবে:

from numba import types
from numba.typed import Dict

int_array = types.int64[:]

@njit
def _numba2(sidx, c1D):
    n = len(sidx)
    start = 0
    outt = Dict.empty(
        key_type=types.int64,
        value_type=int_array,
    )
    for i in range(1,n):
        if c1D[sidx[i]]!=c1D[sidx[i-1]]:
            outt[c1D[sidx[start]]] = sidx[start:i]
            start = i
    outt[c1D[sidx[start]]] = sidx[start:]
    return outt

def numba2(cubes):
    c1D = np.ravel_multi_index(cubes.T, cubes.max(0)+1)    
    sidx = c1D.argsort()
    out = _numba2(sidx, c1D)
    return out

সঙ্গে সময় cubes.npzডেটা -

In [4]: cubes = np.load('cubes.npz')['array']

In [5]: %timeit numpy1(cubes)
   ...: %timeit numba1(cubes)
   ...: %timeit numba2(cubes)
2.38 s ± 14.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
2.13 s ± 25.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.8 s ± 5.95 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

বিকল্প # 1:numexpr বৃহত অ্যারে গণনা করার জন্য আমরা আরও গতি অর্জন করতে পারি c1D, এর মতো -

import numexpr as ne

s0,s1 = cubes[:,0].max()+1,cubes[:,1].max()+1
d = {'s0':s0,'s1':s1,'c0':cubes[:,0],'c1':cubes[:,1],'c2':cubes[:,2]}
c1D = ne.evaluate('c0+c1*s0+c2*s0*s1',d)

এটি প্রয়োজনীয় সমস্ত স্থানে প্রযোজ্য হবে c1D


প্রতিক্রিয়া জন্য অনেক ধন্যবাদ! আমি আশা করিনি যে এখানে সিকেডিট্রি ব্যবহার সম্ভব হবে। তবে আপনার # অ্যাপ্রোচ 1 নিয়ে এখনও কিছু সমস্যা রয়েছে। আউটপুট দৈর্ঘ্য 915791 শুধুমাত্র। আমি এই দ্বন্দ্বের কিছু ধরনের dtypes int32এবংint64
mathfux

@ মাথফাক্স আমি ধরে নিচ্ছি number of indices per group would be a constant numberযে আমি মন্তব্যগুলি সংগ্রহ করেছি। এটি কি নিরাপদ অনুমান হবে? এছাড়াও, আপনি কি cubes.npzআউটপুট জন্য পরীক্ষা করছেন 915791?
দিবাকর

হ্যাঁ আমি করেছি. আমি গোষ্ঠী অনুসারে সূচকগুলির সংখ্যা পরীক্ষা করিনি কারণ গোষ্ঠীর নামের ক্রম ভিন্ন হতে পারে। আমি cubes.npzকেবল আউটপুট অভিধানের দৈর্ঘ্য পরীক্ষা করি এবং এটি 983234আমার প্রস্তাবিত অন্যান্য পদ্ধতির জন্য ছিল ।
ম্যাথফক্স

1
@ ম্যাথফাক্স Approach #3 সূচকগুলির পরিবর্তনশীল সংখ্যার সেই জেনেরিক ক্ষেত্রে দেখুন।
দিবাকর

1
@ ম্যাথফাক্স ইয়ুপ যে অফসেটিংটি সাধারণত ন্যূনতম 0 এর চেয়ে কম হলে প্রয়োজন the
দিবাকর

5

আপনি কেবল পুনরাবৃত্তি করতে পারেন এবং প্রতিটি তালিকার সূচকটি সংশ্লিষ্ট তালিকায় যুক্ত করতে পারেন।

from collections import defaultdict

res = defaultdict(list)

for idx, elem in enumerate(cubes):
    #res[tuple(elem)].append(idx)
    res[elem.tobytes()].append(idx)

রানটাইম কীটিকে টুপলে রূপান্তর না করে টোবাইট () ব্যবহার করে আরও উন্নত করা যেতে পারে ।


আমি এই মুহুর্তে পারফরম্যান্সের সময় পর্যালোচনা করার চেষ্টা করছি (20 এম পয়েন্টের জন্য)। মনে হচ্ছে সময়ের সমাধানে আমার সমাধান আরও দক্ষ কারণ কারণ পুনরাবৃত্তি এড়ানো যায়। আমি একমত, স্মৃতিশক্তি ব্যয় প্রচুর is
ম্যাথফাক্স

অন্য প্রস্তাবটি res[tuple(elem)].append(idx)এর সংস্করণ বনাম 50 সেকেন্ড res[elem[0], elem[1], elem[2]].append(idx)নিয়েছিল যা 30 সেকেন্ড সময় নিয়েছিল।
ম্যাথফাক্স

3

আপনি সিথন ব্যবহার করতে পারেন:

%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True

import math
import cython as cy

cimport numpy as cnp


cpdef groupby_index_dict_cy(cnp.int32_t[:, :] arr):
    cdef cy.size_t size = len(arr)
    result = {}
    for i in range(size):
        key = arr[i, 0], arr[i, 1], arr[i, 2]
        if key in result:
            result[key].append(i)
        else:
            result[key] = [i]
    return result

তবে এটি আপনাকে পান্ডাসের তুলনায় দ্রুততর করে তুলবে না, যদিও এটি তার পরে (এবং সম্ভবত numpy_indexভিত্তিক সমাধান) সবচেয়ে দ্রুত এবং এটির স্মৃতি শাস্তির সাথে আসে না। এখন পর্যন্ত যা প্রস্তাব করা হয়েছে তার একটি সংগ্রহ এখানে রয়েছে

ওপি'র মেশিনে যা প্রায় 12 ডলার সেকেন্ডের সময় কার্যকর হওয়া উচিত।


1
অনেক ধন্যবাদ, আমি পরে এটি পরীক্ষা করব।
mathfux
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.