2025, Oct 15 16:31

CuPy arrays से hashable tuples: NumPy से पोर्ट का भरोसेमंद तरीका

NumPy से CuPy में पोर्ट करते समय unhashable ndarray के कारण आने वाली TypeError को कैसे सुलझाएँ। tolist() या float cast से hashable nested tuples बनाकर sets में रखें.

NumPy के कोड को CuPy में बदलना आम तौर पर सीधा होता है, लेकिन कई बार छोटे‑मोटे टाइप के फर्क अप्रत्याशित जगहों पर सामने आ जाते हैं। एक आम मुश्किल तब आती है जब आप CuPy arrays से निकले डेटा को hash करने की कोशिश करते हैं। अगर आप CuPy ndarrays से बने nested tuples को किसी set में डाल रहे हैं, तो आपको unhashable type की शिकायत वाला TypeError मिल सकता है।

समस्या का सार

उद्देश्य है CuPy मैट्रिक्स के कॉलम्स को पुनःक्रमित करना और नतीजा एक Python set के भीतर nested tuple के रूप में सहेजना। जो तरीका NumPy के साथ सहजता से चलता है, वही CuPy में त्रुटि दे देता है:

import cupy as cp
def reorder_cols(arr: cp.ndarray):
    col_idx = cp.lexsort(arr[:, 1:])
    arr[:, 1:] = arr[:, 1:][:, col_idx]
    return arr
gpu_mat = cp.array([
    [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.],
    [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0., -1.]
])
seen_patterns = set()
seen_patterns.add(tuple(tuple(row) for row in reorder_cols(gpu_mat)))

यह त्रुटि आती है:

TypeError: unhashable type: 'ndarray'

असल में हो क्या रहा है

Nested tuple तभी hashable होता है जब उसके सभी तत्व hashable हों। NumPy में, किसी पंक्ति पर iterate करने पर अक्सर Python floats मिलते हैं, जो hashable होते हैं। CuPy में, tuple comprehension के अंदर वही तत्व Python floats के बजाय CuPy ऑब्जेक्ट्स के रूप में आते हैं, इसलिए आपके tuple के भीतर unhashable entries रह जाती हैं। यही असंगति set में जोड़ने की प्रक्रिया को विफल कर देती है।

समाधान और काम करने वाला उदाहरण

सबसे छोटा समाधान है tuples बनाते समय CuPy scalars को पहले Python floats में बदलना, या हर पंक्ति को पहले Python list के रूप में materialize करना। दोनों ही तरीके hashable सामग्री देते हैं। यहां एक संक्षिप्त रूप है जो tolist() का उपयोग करके inner generator से बचता है, यह थोड़ा तेज और सीधे‑सीधे काम करता है:

import cupy as cp
def reorder_cols(arr: cp.ndarray):
    col_idx = cp.lexsort(arr[:, 1:])
    arr[:, 1:] = arr[:, 1:][:, col_idx]
    return arr
gpu_mat = cp.array([
    [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.],
    [ 0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0., -1.]
])
seen_patterns = set()
seen_patterns.add(tuple(tuple(r.tolist()) for r in reorder_cols(gpu_mat)))

इसी का एक बराबर विकल्प है कि nested tuple बनाते समय हर तत्व पर float(x) के साथ स्पष्ट रूप से cast किया जाए।

यह क्यों मायने रखता है

NumPy से CuPy में पोर्ट करना हमेशा केवल खोज‑और‑बदल जैसा काम नहीं होता। वह कोड जो Python scalars में निहित रूपांतरण पर निर्भर करता है, अलग तरह से व्यवहार कर सकता है जब आधारभूत लाइब्रेरी ऐसे ऑब्जेक्ट्स लौटाती है जो hashable नहीं हैं। जैसे ही आप व्युत्पन्न मानों को sets में रखते हैं या उन्हें dictionary keys के रूप में इस्तेमाल करते हैं, यह निर्णायक हो जाता है। Scalars का रूपांतरण स्पष्ट रखने से आपका hashing लॉजिक भरोसेमंद रहता है और runtime पर अप्रिय आश्चर्य नहीं होते।

मुख्य बातें

अगर आप CuPy arrays से hashable संरचनाएँ बना रहे हैं, तो उन्हें sets में डालने से पहले सुनिश्चित करें कि तत्व Python scalars या अन्य hashable प्रकार हों। पंक्तियों को tolist() के जरिये बदलना या तत्वों को float में cast करना, आपके एल्गोरिद्म को बिना बदले TypeError को दूर कर देता है। साथ ही, def reorder_cols(arr: cp.ndarray) की तरह type hints को CuPy के अनुरूप रखना इरादा स्पष्ट करता है और जब आप array operations को GPU पर ले जाते हैं तो भ्रम से बचाता है।

यह लेख StackOverflow पर प्रश्न (लेखक: EzTheBoss 2) और EzTheBoss 2 के उत्तर पर आधारित है।