सन्निहित और गैर-सन्निहित सरणियों के बीच अंतर क्या है?


100

रेज़ैप () फ़ंक्शन के बारे में सुन्न मैनुअल में, यह कहता है

>>> a = np.zeros((10, 2))
# A transpose make the array non-contiguous
>>> b = a.T
# Taking a view makes it possible to modify the shape without modifying the
# initial object.
>>> c = b.view()
>>> c.shape = (20)
AttributeError: incompatible shape for a non-contiguous array

मेरे प्रश्न हैं:

  1. निरंतर और अनियंत्रित सरणियाँ क्या हैं? क्या यह C के सन्निहित मेमोरी ब्लॉक के समान है जैसे एक सन्निहित मेमोरी ब्लॉक क्या है?
  2. क्या इन दोनों में कोई अंतर है? हमें एक या दूसरे का उपयोग कब करना चाहिए?
  3. स्थानान्तरण गैर-सन्निहित क्यों बनाता है?
  4. क्यों c.shape = (20)एक त्रुटि फेंकता है incompatible shape for a non-contiguous array?

आपके उत्तर के लिए धन्यवाद!

जवाबों:


220

एक सन्निहित सरणी स्मृति के अटूट ब्लॉक में संग्रहीत एक सरणी है: सरणी में अगले मूल्य तक पहुंचने के लिए, हम सिर्फ अगले स्मृति पते पर जाते हैं।

2 डी सरणी पर विचार करें arr = np.arange(12).reshape(3,4)। यह इस तरह दिख रहा है:

यहां छवि विवरण दर्ज करें

कंप्यूटर की मेमोरी में, arrइस तरह के मान संग्रहीत किए जाते हैं:

यहां छवि विवरण दर्ज करें

इसका मतलब यह है arrकि सी सन्निहित सरणी है क्योंकि पंक्तियों को स्मृति के सन्निहित ब्लॉक के रूप में संग्रहीत किया जाता है। अगला मेमोरी एड्रेस उस पंक्ति पर अगली पंक्ति मान रखता है। यदि हम किसी कॉलम को नीचे ले जाना चाहते हैं, तो हमें केवल तीन ब्लॉक (जैसे 0 से 4 तक कूदने का मतलब है कि हम 1,2 और 3 से अधिक छोड़ें) पर कूदने की जरूरत है।

सरणी को arr.Tट्रांसप्लांट करने का मतलब है कि सी संलिप्तता खो गई है क्योंकि आसन्न पंक्ति प्रविष्टियाँ अब आसन्न मेमोरी पतों में नहीं हैं। हालांकि, arr.Tहै फोरट्रान सन्निहित के बाद से कॉलम स्मृति से सटे ब्लॉक में कर रहे हैं:

यहां छवि विवरण दर्ज करें


प्रदर्शन-वार, मेमोरी पतों तक पहुंचना, जो एक-दूसरे के बगल में हैं, उन अभिगम संबोधनों की तुलना में बहुत अधिक तेजी से होते हैं जो अधिक "स्प्रेड आउट" होते हैं (रैम से एक मूल्य प्राप्त करना पड़ोसी के कई पते प्राप्त कर सकता है और सीपीयू के लिए कैश किया जा सकता है।) इसका मतलब है कि सन्निहित सरणियों पर संचालन अक्सर तेज होगा।

सी सन्निहित स्मृति लेआउट के परिणामस्वरूप, पंक्ति-वार ऑपरेशन आमतौर पर कॉलम-वार ऑपरेशनों की तुलना में तेज़ होते हैं। उदाहरण के लिए, आप आमतौर पर ऐसा पाएंगे

np.sum(arr, axis=1) # sum the rows

की तुलना में थोड़ा तेज है:

np.sum(arr, axis=0) # sum the columns

इसी प्रकार, फोरट्रान सन्निहित सरणियों के लिए स्तंभों पर संचालन थोड़ा तेज़ होगा।


अंत में, हम एक नई आकृति प्रदान करके फोर्ट्रान सन्निहित सरणी को समतल क्यों नहीं कर सकते?

>>> arr2 = arr.T
>>> arr2.shape = 12
AttributeError: incompatible shape for a non-contiguous array

ऐसा संभव हो सके इसके लिए NumPy को arr.Tइस तरह से एक साथ पंक्तियों को रखना होगा :

यहां छवि विवरण दर्ज करें

( shapeविशेषता सेट करना सीधे सी ऑर्डर मानता है - यानी न्यूमपी ऑपरेशन पंक्ति-वार प्रदर्शन करने की कोशिश करता है।)

ऐसा करना असंभव है। किसी भी अक्ष के लिए, NumPy को सरणी के अगले तत्व को प्राप्त करने के लिए एक निरंतर स्ट्राइड लंबाई (स्थानांतरित करने के लिए बाइट्स की संख्या) की आवश्यकता होती है। arr.Tइस तरह से समतल करने के लिए सरणी के लगातार मूल्यों को पुनः प्राप्त करने के लिए स्मृति में आगे और पीछे लंघन की आवश्यकता होगी।

अगर हमने arr2.reshape(12)इसके बजाय लिखा , NumPy arr2 के मूल्यों को मेमोरी के एक नए ब्लॉक में कॉपी करेगा (क्योंकि यह इस आकृति के लिए मूल डेटा पर एक दृश्य वापस नहीं कर सकता है)।


मुझे यह समझने में कठिनाई हो रही है, क्या आप थोड़ा विस्तार कर सकते हैं? स्मृति में असंभव आदेश के नवीनतम चित्रमय प्रतिनिधित्व में वास्तव में प्रगति मेरे विचार में स्थिर हैं। उदाहरण के लिए 0 से 1 तक जाने के लिए स्ट्राइड 1 बाइट है (मान लीजिए कि प्रत्येक तत्व एक बाइट है) और यह प्रत्येक कॉलम के लिए समान है। इसी तरह पंक्ति में एक तत्व से अगले एक तक जाने के लिए स्ट्राइड 4 बाइट्स है और यह स्थिर भी है।
वेसनॉग

2
@Vesnog 2 डी arr2से 1 डी आकार में असफल फेरबदल (12,)सी ऑर्डर का उपयोग करता है, जिसका अर्थ है कि अक्ष 1 अक्ष 0 से पहले अनजाना है (यानी वांछित 4 डी बनाने के लिए चार पंक्तियों में से प्रत्येक को एक दूसरे के बगल में रखा जाना चाहिए)। पूर्णांक लंबाई (एक बाइट्स टू जंप) का उपयोग करके बफर से बाहर पूर्णांक (0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11) के इस क्रम को पढ़ना असंभव है। अनुक्रम में ये तत्व 4, 4, -7, 4, 4, -7, 4, 4, 7, 4, 4) होंगे। NumPy को प्रति धुरी में एक प्रतियोगी स्ट्राइड लंबाई की आवश्यकता होती है।
एलेक्स रिले

पहले धन्यवाद, मैंने सोचा कि यह एक नई सरणी बनाएगा, लेकिन यह पुराने की मेमोरी का उपयोग करता है।
वेसनॉग

@AlexRiley जब किसी व्यूह को आघातक सी या एफ का आदेश दिया जाता है तो पर्दे के पीछे क्या होता है? उदाहरण के लिए, प्रत्येक NxD सरणी को गिरफ्तार करें, और प्रिंट करें (गिरफ्तारी::, - - 1] .flags)। इस स्थिति में क्या होता है? मुझे लगता है कि सरणी वास्तव में सी या एफ का आदेश दिया गया है, लेकिन उनमें से कौन सा है? और अगर दोनों झंडे झूठे हैं, तो हम सुन्न के कौन से अनुकूलन करते हैं?
जाजंग

@Jang: क्या NumPy मानता है कि सरणी C है या F ऑर्डर पूरी तरह से आकार और स्ट्रैड पर निर्भर है (मानदंड यहां हैं )। तो जबकि arr[:, ::-1]एक ही मेमोरी बफर का एक दृश्य है arr, NumPy इसे C या F ऑर्डर नहीं मानता है क्योंकि इसने बफर में मानों को "गैर-मानक" क्रम में पीछे कर दिया है ...
एलेक्स रिले

12

शायद 12 विभिन्न सरणी मानों के साथ यह उदाहरण मदद करेगा:

In [207]: x=np.arange(12).reshape(3,4).copy()

In [208]: x.flags
Out[208]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  ...
In [209]: x.T.flags
Out[209]: 
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : False
  ...

C orderमूल्यों आदेश है कि वे में उत्पन्न किया गया में हैं। स्थानांतरित वाले नहीं हैं

In [212]: x.reshape(12,)   # same as x.ravel()
Out[212]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

In [213]: x.T.reshape(12,)
Out[213]: array([ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11])

आप दोनों के 1d विचार प्राप्त कर सकते हैं

In [214]: x1=x.T

In [217]: x.shape=(12,)

का आकार xभी बदला जा सकता है।

In [220]: x1.shape=(12,)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-220-cf2b1a308253> in <module>()
----> 1 x1.shape=(12,)

AttributeError: incompatible shape for a non-contiguous array

लेकिन परिवर्तन का आकार नहीं बदला जा सकता है। dataमें अब भी है 0,1,2,3,4...आदेश है, जो के रूप में पहुँचा नहीं जा सकता तक पहुँचा 0,4,8...एक 1d सरणी में।

लेकिन एक प्रति को x1बदला जा सकता है:

In [227]: x2=x1.copy()

In [228]: x2.flags
Out[228]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  ...
In [229]: x2.shape=(12,)

देखने से stridesभी मदद मिल सकती है। एक स्ट्राइड्स कितनी दूर (बाइट्स में) है इसे अगले मूल्य पर ले जाना है। 2d सरणी के लिए, 2 स्ट्राइड मान होंगे:

In [233]: x=np.arange(12).reshape(3,4).copy()

In [234]: x.strides
Out[234]: (16, 4)

अगली पंक्ति में जाने के लिए, चरण 16 बाइट्स, अगला स्तंभ केवल 4।

In [235]: x1.strides
Out[235]: (4, 16)

संक्रमण केवल स्ट्राइड्स के क्रम को स्विच करता है। अगली पंक्ति केवल 4 बाइट्स है- यानी अगली संख्या।

In [236]: x.shape=(12,)

In [237]: x.strides
Out[237]: (4,)

आकार बदलने से स्ट्राइड्स भी बदल जाता है - बस एक बार में बफर 4 बाइट्स के माध्यम से कदम।

In [238]: x2=x1.copy()

In [239]: x2.strides
Out[239]: (12, 4)

भले ही x2जैसा दिखता है x1, इसका एक अलग क्रम में मूल्यों के साथ, इसका अपना डेटा बफर है। अगला कॉलम अब 4 बाइट्स के ऊपर है, जबकि अगली पंक्ति 12 (3 * 4) है।

In [240]: x2.shape=(12,)

In [241]: x2.strides
Out[241]: (4,)

और जैसा है x , आकार को 1d में बदलने से स्ट्राइड्स कम हो जाता है (4,)

के लिए x1, में डेटा के साथ0,1,2,... आदेश, वहाँ एक 1 दिन छलांग कि देना होगा नहीं है 0,4,8...

__array_interface__ सरणी जानकारी प्रदर्शित करने का एक और उपयोगी तरीका है:

In [242]: x1.__array_interface__
Out[242]: 
{'strides': (4, 16),
 'typestr': '<i4',
 'shape': (4, 3),
 'version': 3,
 'data': (163336056, False),
 'descr': [('', '<i4')]}

x1डेटा बफर पते के लिए के रूप में ही किया जाएगाx , जिसके साथ यह डेटा साझा करता है। x2एक अलग बफर पता है।

तुम भी और आदेशों के order='F'लिए एक पैरामीटर जोड़ने के साथ प्रयोग कर सकते हैं ।copyreshape

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.