न्यूमपी के इिनसम को समझना


190

मैं समझने के लिए संघर्ष कर रहा हूं कि कैसे einsumकाम करता है। मैंने दस्तावेज़ीकरण और कुछ उदाहरणों को देखा है, लेकिन यह छड़ी करने के लिए प्रतीत नहीं हो रहा है।

यहाँ एक उदाहरण हमने कक्षा में दिया है:

C = np.einsum("ij,jk->ki", A, B)

दो सरणियों के लिए AऔरB

मुझे लगता है कि यह लगेगा A^T * B, लेकिन मुझे यकीन नहीं है (यह उनमें से एक का संक्रमण सही ले रहा है?)। किसी ने मुझे यहाँ के माध्यम से वास्तव में क्या चल रहा है (और जब उपयोग कर सकते हैं einsum)?


7
वास्तव में यह (A * B)^Tया समकक्ष होगा B^T * A^T
तिगरान सालुव

21
मैंने einsum यहाँ की मूल बातें के बारे में एक छोटा ब्लॉग पोस्ट लिखा था । (यदि उपयोगी हो तो स्टैक ओवरफ्लो पर एक उत्तर के लिए सबसे अधिक प्रासंगिक बिट्स को ट्रांसप्लांट करने में मुझे खुशी होती है)।
एलेक्स रिले

1
@ काजल - सुंदर लिंक। धन्यवाद। numpyप्रलेखन अपर्याप्त जब विवरण समझा है।
रेयिनेंग

विश्वास मत के लिए धन्यवाद! माना, मैंने नीचे एक उत्तर दिया है
एलेक्स रिले

ध्यान दें कि पायथन में *मैट्रिक्स गुणन नहीं है, लेकिन एलिमेंटिव गुणा है। ध्यान रहे!
कंप्यूटर

जवाबों:


368

(नोट: यह उत्तर एक छोटी ब्लॉग पोस्ट पर आधारित है जिसके बारे में einsumमैंने कुछ समय पहले लिखा था।)

क्या करता einsumहै?

कल्पना करें कि हमारे पास दो बहु-आयामी सरणियाँ हैं, Aऔर B। अब मान लीजिए कि हम चाहते हैं ...

  • उत्पादों की नई सरणी बनाने के लिए एक विशेष तरीके से गुणाA करें B; और फिर शायद
  • विशेष रूप से कुल्हाड़ियों के साथ इस नए सरणी का योग ; और फिर शायद
  • एक विशेष क्रम में नए सरणी के अक्षों को स्थानांतरित करें।

एक अच्छा मौका है जो einsumहमें इस तेजी से और अधिक मेमोरी-कुशलता से करने में मदद करेगा कि NumPy फ़ंक्शन जैसे संयोजन multiply, sumऔर transposeअनुमति देगा।

कैसे einsumकाम करता है ?

यहाँ एक सरल (लेकिन पूरी तरह से तुच्छ नहीं) उदाहरण है। निम्नलिखित दो सरणियाँ लें:

A = np.array([0, 1, 2])

B = np.array([[ 0,  1,  2,  3],
              [ 4,  5,  6,  7],
              [ 8,  9, 10, 11]])

हम नए तत्व की पंक्तियों के साथ गुणा Aऔर Bतत्व-वार करेंगे और फिर योग करेंगे। "सामान्य" NumPy में हम लिखेंगे:

>>> (A[:, np.newaxis] * B).sum(axis=1)
array([ 0, 22, 76])

इसलिए यहां, Aदो सरणियों के पहले अक्षों को तर्ज पर अनुक्रमण ऑपरेशन किया जाता है ताकि गुणा को प्रसारित किया जा सके। उत्पादों की सरणी की पंक्तियों को फिर उत्तर देने के लिए अभिव्यक्त किया गया है।

अब अगर हम einsumइसके बजाय उपयोग करना चाहते हैं , तो हम लिख सकते हैं:

>>> np.einsum('i,ij->i', A, B)
array([ 0, 22, 76])

हस्ताक्षर स्ट्रिंग 'i,ij->i'यहाँ मुख्य बिंदु है और समझाने का एक छोटा सा की जरूरत है। आप इसे दो हिस्सों में सोच सकते हैं। बाईं ओर (बाईं ओर ->) हमने दो इनपुट सरणियों को लेबल किया है। के दाईं ओर ->, हमने उस सरणी को लेबल किया है जिसे हम समाप्त करना चाहते हैं।

यहाँ आगे क्या होता है:

  • Aएक अक्ष है; हमने इसे लेबल किया है i। और Bदो अक्ष हैं; हमने अक्ष 0 के रूप में iऔर अक्ष 1 को लेबल किया है j

  • द्वारा दोहरा लेबल iदोनों इनपुट सरणियों में, हम कह रहे हैं einsumकि इन दो कुल्हाड़ियों किया जाना चाहिए गुणा एक साथ। दूसरे शब्दों में, हम सरणी Aके प्रत्येक स्तंभ के साथ सरणी को गुणा कर रहे हैं B, ठीक उसी तरह जैसे A[:, np.newaxis] * B

  • नोटिस जो jहमारे वांछित आउटपुट में एक लेबल के रूप में प्रकट नहीं होता है; हमने अभी उपयोग किया है i(हम 1 डी सरणी के साथ समाप्त करना चाहते हैं)। द्वारा छोड़ते हुए लेबल, हम कह रहे हैं einsumकरने के लिए योग इस अक्ष के साथ। दूसरे शब्दों में, हम उत्पादों की पंक्तियों को जोड़ते हैं, ठीक उसी तरह जैसे .sum(axis=1)

यह मूल रूप से आप सभी का उपयोग करने के लिए पता करने की आवश्यकता है einsum। यह थोड़ा खेलने में मदद करता है; अगर हम आउटपुट में दोनों लेबल छोड़ते हैं 'i,ij->ij', तो हमें उत्पादों का एक 2D सरणी वापस मिलता है (उसी तरह A[:, np.newaxis] * B)। यदि हम कहते हैं कि कोई आउटपुट लेबल नहीं है 'i,ij->, तो हमें एक ही नंबर वापस मिलता है (जैसा कि कर रहा है (A[:, np.newaxis] * B).sum())।

einsumहालाँकि, इसके बारे में महान बात यह है कि यह पहले उत्पादों की एक अस्थायी सरणी का निर्माण नहीं करता है; यह सिर्फ उत्पादों को जाता है क्योंकि यह जाता है। इससे मेमोरी उपयोग में बड़ी बचत हो सकती है।

थोड़ा बड़ा उदाहरण

डॉट उत्पाद की व्याख्या करने के लिए, यहाँ दो नए ऐरे हैं:

A = array([[1, 1, 1],
           [2, 2, 2],
           [5, 5, 5]])

B = array([[0, 1, 0],
           [1, 1, 0],
           [1, 1, 1]])

हम का उपयोग करके डॉट उत्पाद की गणना करेंगे np.einsum('ij,jk->ik', A, B)। यहाँ की लेबलिंग दिखा एक तस्वीर है Aऔर Bऔर आउटपुट सरणी है कि हम समारोह से मिलता है:

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

आप देख सकते हैं कि लेबल jदोहराया गया है - इसका मतलब है कि हम Aस्तंभों की पंक्तियों को गुणा कर रहे हैं B। इसके अलावा, लेबल jआउटपुट में शामिल नहीं है - हम इन उत्पादों को समेट रहे हैं। लेबल iऔर kआउटपुट के लिए रखे गए हैं, इसलिए हम एक 2D सरणी प्राप्त करते हैं।

इस परिणाम की तुलना उस सरणी से करना और भी स्पष्ट हो सकता है जहाँ लेबल jको अभिव्यक्त नहीं किया गया है। नीचे, बाईं ओर आप 3D सरणी देख सकते हैं जो लिखने के परिणामस्वरूप np.einsum('ij,jk->ijk', A, B)(यानी हमने लेबल रखा है j):

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

Summing अक्ष jअपेक्षित डॉट उत्पाद देता है, जो दाईं ओर दिखाया गया है।

कुछ व्यायाम

के लिए और अधिक महसूस करने के लिए einsum, यह सबस्क्रिप्ट नोटेशन का उपयोग करके परिचित NumPy सरणी संचालन को लागू करने के लिए उपयोगी हो सकता है। कुछ भी जिसमें एक्सिस को गुणा करने और समेटने के संयोजन शामिल हैं, का उपयोग करके लिखा जा सकता है einsum

बता दें कि A और B एक ही लंबाई के साथ दो 1D सरणियाँ हैं। उदाहरण के लिए, A = np.arange(10)और B = np.arange(5, 15)

  • का योग Aलिखा जा सकता है:

    np.einsum('i->', A)
  • तत्व-वार गुणा, A * Bलिखा जा सकता है:

    np.einsum('i,i->i', A, B)
  • आंतरिक उत्पाद या डॉट उत्पाद, np.inner(A, B)या np.dot(A, B), लिखा जा सकता है:

    np.einsum('i,i->', A, B) # or just use 'i,i'
  • बाहरी उत्पाद, np.outer(A, B)लिखा जा सकता है:

    np.einsum('i,j->ij', A, B)

2 डी सरणियों के लिए, Cऔर D, बशर्ते कि कुल्हाड़ियों की संगत लंबाई हो (दोनों समान लंबाई या उनमें से एक की लंबाई 1 है), यहां कुछ उदाहरण दिए गए हैं:

  • C(मुख्य विकर्ण का योग) का चिह्न , np.trace(C)लिखा जा सकता है:

    np.einsum('ii', C)
  • तत्व-वार गुणन Cऔर के हस्तांतरण D, C * D.Tलिखा जा सकता है:

    np.einsum('ij,ji->ij', C, D)
  • Cसरणी के प्रत्येक तत्व को गुणा करना D(4D सरणी बनाने के लिए), C[:, :, None, None] * Dलिखा जा सकता है:

    np.einsum('ij,kl->ijkl', C, D)  

1
बहुत अच्छी व्याख्या, धन्यवाद। "ध्यान दें कि मैं हमारे वांछित आउटपुट में एक लेबल के रूप में प्रकट नहीं होता" - है ना?
इयान हिंक्स

धन्यवाद @ इयानहिक्स! जो टाइपो की तरह दिखता है; मैंने इसे अब ठीक कर दिया है।
एलेक्स रिले

1
बहुत अच्छा जवाब। यह भी ध्यान देने योग्य है कि ij,jkमैट्रिक्स गुणा करने के लिए स्वयं (तीरों के बिना) काम कर सकता है। लेकिन यह स्पष्टता के लिए लगता है कि तीर और फिर आउटपुट आयाम डालना सबसे अच्छा है। यह ब्लॉग पोस्ट में है।
कंप्यूटर

1
@ आकर्षक: यह उन अवसरों में से एक है जहाँ सही शब्द चुनना मुश्किल है! मुझे लगता है कि "कॉलम" यहां Aलंबाई 3 से थोड़ा बेहतर है , स्तंभों की लंबाई के समान है B(जबकि Bलंबाई 4 की पंक्तियों और तत्व-गुणा से गुणा नहीं किया जा सकता है A)।
एलेक्स रिले

1
ध्यान दें कि ->शब्दार्थ को प्रभावित करता है: "अंतर्निहित मोड में, चुने हुए सदस्यता महत्वपूर्ण हैं क्योंकि आउटपुट के अक्षों को वर्णानुक्रम में पुन: व्यवस्थित किया जाता है। इसका मतलब है कि np.einsum('ij', a)यह 2 डी सरणी को प्रभावित नहीं करता है, जबकि np.einsum('ji', a)इसका स्थानांतरण होता है।"
बॉलपॉइंटबेन

40

numpy.einsum()यदि आप इसे सहजता से समझें, तो इस विचार को समझना बहुत आसान है। एक उदाहरण के रूप में, आइए मैट्रिक्स गुणन से जुड़े एक सरल विवरण के साथ शुरू करें ।


उपयोग करने के लिए numpy.einsum(), आपको बस इतना करना होगा कि तथाकथित सब्सक्राइबर्स स्ट्रिंग को एक तर्क के रूप में पारित किया जाए , उसके बाद आपके इनपुट एरेज़ के बाद

मान लें कि आपके पास दो 2D सरणियाँ हैं, Aऔर B, और आप मैट्रिक्स गुणा करना चाहते हैं। तो तुम करते हो:

np.einsum("ij, jk -> ik", A, B)

यहाँ सबस्क्रिप्ट स्ट्रिंग ij सरणी से मेल खाती है, Aजबकि सबस्क्रिप्ट स्ट्रिंग jk सरणी से मेल खाती है B। इसके अलावा, यहां ध्यान देने वाली सबसे महत्वपूर्ण बात यह है कि प्रत्येक सबस्क्रिप्ट स्ट्रिंग में वर्णों की संख्या को सरणी के आयामों से मेल खाना चाहिए । (यानी 2 डी सरणियों के लिए दो चार्ट, 3 डी सरणियों के लिए तीन चार्ट, और इसी तरह।) और यदि आप सबस्क्रिप्ट स्ट्रिंग्स ( हमारे मामले में) के बीच के वर्णों को दोहराते हैं , तो इसका मतलब है कि आप उन आयामों के साथ होने का योग चाहते हैं । इस प्रकार, वे योग-कम हो जाएंगे। (अर्थात वह आयाम चला जाएगा jein )

इसके बाद सबस्क्रिप्ट स्ट्रिंग-> , हमारी परिणामी सरणी होगी। यदि आप इसे खाली छोड़ देते हैं, तो सब कुछ अभिव्यक्त हो जाएगा और परिणाम के रूप में एक स्केलर मूल्य वापस आ जाएगा। परिणामी सरणी में सबस्क्रिप्ट स्ट्रिंग के अनुसार आयाम होंगे । हमारे उदाहरण में, यह होगा ik। यह सहज है क्योंकि हम जानते हैं कि मैट्रिक्स गुणा के लिए सरणी में स्तंभों Aकी संख्या को सरणी में पंक्तियों की संख्या से मेल खाना है Bजो कि यहां हो रहा है (यानी हम सबस्क्रिप्ट स्ट्रिंगj में चार को दोहराकर इस ज्ञान को सांकेतिक शब्दों में बदलते हैं )


यहाँ कुछ और np.einsum()सामान्य टेनर या एनडी-सरणी संचालन को लागू करने के उपयोग / शक्ति को दर्शाते हुए कुछ और उदाहरण हैं , संक्षेप में।

इनपुट

# a vector
In [197]: vec
Out[197]: array([0, 1, 2, 3])

# an array
In [198]: A
Out[198]: 
array([[11, 12, 13, 14],
       [21, 22, 23, 24],
       [31, 32, 33, 34],
       [41, 42, 43, 44]])

# another array
In [199]: B
Out[199]: 
array([[1, 1, 1, 1],
       [2, 2, 2, 2],
       [3, 3, 3, 3],
       [4, 4, 4, 4]])

1) मैट्रिक्स गुणा (के समान np.matmul(arr1, arr2))

In [200]: np.einsum("ij, jk -> ik", A, B)
Out[200]: 
array([[130, 130, 130, 130],
       [230, 230, 230, 230],
       [330, 330, 330, 330],
       [430, 430, 430, 430]])

2) मुख्य-विकर्ण के साथ तत्व निकालें (समान np.diag(arr))

In [202]: np.einsum("ii -> i", A)
Out[202]: array([11, 22, 33, 44])

3) हेडमार्ड उत्पाद (अर्थात दो सरणियों का तत्व-वार उत्पाद) (समान arr1 * arr2)

In [203]: np.einsum("ij, ij -> ij", A, B)
Out[203]: 
array([[ 11,  12,  13,  14],
       [ 42,  44,  46,  48],
       [ 93,  96,  99, 102],
       [164, 168, 172, 176]])

4) तत्व के लिहाज से squaring (के समान np.square(arr)या arr ** 2)

In [210]: np.einsum("ij, ij -> ij", B, B)
Out[210]: 
array([[ 1,  1,  1,  1],
       [ 4,  4,  4,  4],
       [ 9,  9,  9,  9],
       [16, 16, 16, 16]])

5) ट्रेस (यानी मुख्य-विकर्ण तत्वों का योग) (समान np.trace(arr))

In [217]: np.einsum("ii -> ", A)
Out[217]: 110

6) मैट्रिक्स ट्रांज़ोज़ (समान np.transpose(arr))

In [221]: np.einsum("ij -> ji", A)
Out[221]: 
array([[11, 21, 31, 41],
       [12, 22, 32, 42],
       [13, 23, 33, 43],
       [14, 24, 34, 44]])

7) बाहरी उत्पाद (वैक्टर के) (समान np.outer(vec1, vec2))

In [255]: np.einsum("i, j -> ij", vec, vec)
Out[255]: 
array([[0, 0, 0, 0],
       [0, 1, 2, 3],
       [0, 2, 4, 6],
       [0, 3, 6, 9]])

8) इनर प्रोडक्ट (वैक्टर के) (समान np.inner(vec1, vec2))

In [256]: np.einsum("i, i -> ", vec, vec)
Out[256]: 14

9) अक्ष 0 के साथ सम (समान np.sum(arr, axis=0))

In [260]: np.einsum("ij -> j", B)
Out[260]: array([10, 10, 10, 10])

10) अक्ष 1 के साथ सम (समान np.sum(arr, axis=1))

In [261]: np.einsum("ij -> i", B)
Out[261]: array([ 4,  8, 12, 16])

11) बैच मैट्रिक्स गुणन

In [287]: BM = np.stack((A, B), axis=0)

In [288]: BM
Out[288]: 
array([[[11, 12, 13, 14],
        [21, 22, 23, 24],
        [31, 32, 33, 34],
        [41, 42, 43, 44]],

       [[ 1,  1,  1,  1],
        [ 2,  2,  2,  2],
        [ 3,  3,  3,  3],
        [ 4,  4,  4,  4]]])

In [289]: BM.shape
Out[289]: (2, 4, 4)

# batch matrix multiply using einsum
In [292]: BMM = np.einsum("bij, bjk -> bik", BM, BM)

In [293]: BMM
Out[293]: 
array([[[1350, 1400, 1450, 1500],
        [2390, 2480, 2570, 2660],
        [3430, 3560, 3690, 3820],
        [4470, 4640, 4810, 4980]],

       [[  10,   10,   10,   10],
        [  20,   20,   20,   20],
        [  30,   30,   30,   30],
        [  40,   40,   40,   40]]])

In [294]: BMM.shape
Out[294]: (2, 4, 4)

12) अक्ष 2 के साथ सम (समान np.sum(arr, axis=2))

In [330]: np.einsum("ijk -> ij", BM)
Out[330]: 
array([[ 50,  90, 130, 170],
       [  4,   8,  12,  16]])

13) सरणी में सभी तत्व (समान np.sum(arr))

In [335]: np.einsum("ijk -> ", BM)
Out[335]: 480

14) कई कुल्हाड़ियों (यानी हाशिए पर) पर सम
(समान np.sum(arr, axis=(axis0, axis1, axis2, axis3, axis4, axis6, axis7)))

# 8D array
In [354]: R = np.random.standard_normal((3,5,4,6,8,2,7,9))

# marginalize out axis 5 (i.e. "n" here)
In [363]: esum = np.einsum("ijklmnop -> n", R)

# marginalize out axis 5 (i.e. sum over rest of the axes)
In [364]: nsum = np.sum(R, axis=(0,1,2,3,4,6,7))

In [365]: np.allclose(esum, nsum)
Out[365]: True

15) डबल डॉट प्रोडक्ट्स ( np.sum (हैसमर्ड-प्रोडक्ट) cf. 3 के समान )

In [772]: A
Out[772]: 
array([[1, 2, 3],
       [4, 2, 2],
       [2, 3, 4]])

In [773]: B
Out[773]: 
array([[1, 4, 7],
       [2, 5, 8],
       [3, 6, 9]])

In [774]: np.einsum("ij, ij -> ", A, B)
Out[774]: 124

16) 2 डी और 3 डी सरणी गुणन

समीकरणों के रैखिक सिस्टम ( Ax = b ) को हल करने के दौरान ऐसा गुणन बहुत उपयोगी हो सकता है जहां आप परिणाम को सत्यापित करना चाहते हैं।

# inputs
In [115]: A = np.random.rand(3,3)
In [116]: b = np.random.rand(3, 4, 5)

# solve for x
In [117]: x = np.linalg.solve(A, b.reshape(b.shape[0], -1)).reshape(b.shape)

# 2D and 3D array multiplication :)
In [118]: Ax = np.einsum('ij, jkl', A, x)

# indeed the same!
In [119]: np.allclose(Ax, b)
Out[119]: True

इसके विपरीत, यदि किसी को np.matmul()इस सत्यापन के लिए उपयोग करना है, तो हमें reshapeउसी तरह के परिणाम प्राप्त करने के लिए कुछ संचालन करना होगा:

# reshape 3D array `x` to 2D, perform matmul
# then reshape the resultant array to 3D
In [123]: Ax_matmul = np.matmul(A, x.reshape(x.shape[0], -1)).reshape(x.shape)

# indeed correct!
In [124]: np.allclose(Ax, Ax_matmul)
Out[124]: True

बोनस : यहां अधिक गणित पढ़ें: आइंस्टीन-संक्षेप और निश्चित रूप से यहां: Tensor-Notation


7

उनके अंतर को उजागर करने के लिए विभिन्न, लेकिन संगत आयामों के साथ, 2 सरणियां बनाते हैं

In [43]: A=np.arange(6).reshape(2,3)
Out[43]: 
array([[0, 1, 2],
       [3, 4, 5]])


In [44]: B=np.arange(12).reshape(3,4)
Out[44]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

आपकी गणना, (4,2) सरणी का उत्पादन करने के लिए (3,4) के साथ (2,3) की 'डॉट' (उत्पादों की राशि) लेती है। के पिछले iमंद है A, के अंतिम C; kके अंतिम B, 1 के Cjयोग द्वारा 'भस्म' है।

In [45]: C=np.einsum('ij,jk->ki',A,B)
Out[45]: 
array([[20, 56],
       [23, 68],
       [26, 80],
       [29, 92]])

यह वही है np.dot(A,B).T- यह अंतिम आउटपुट है जिसे ट्रांसपोज़ किया गया है।

इससे अधिक क्या होता है j, यह देखने के लिए Cसदस्यताएँ बदलें ijk:

In [46]: np.einsum('ij,jk->ijk',A,B)
Out[46]: 
array([[[ 0,  0,  0,  0],
        [ 4,  5,  6,  7],
        [16, 18, 20, 22]],

       [[ 0,  3,  6,  9],
        [16, 20, 24, 28],
        [40, 45, 50, 55]]])

इसके साथ उत्पादन भी किया जा सकता है:

A[:,:,None]*B[None,:,:]

यही है, kके अंत में एक आयाम जोड़ें A, और एक iके सामने B, जिसके परिणामस्वरूप (2,3,4) सरणी है।

0 + 4 + 16 = 20, 9 + 28 + 55 = 92आदि; jपूर्व परिणाम प्राप्त करने के लिए राशि और स्थानांतरण करें:

np.sum(A[:,:,None] * B[None,:,:], axis=1).T

# C[k,i] = sum(j) A[i,j (,k) ] * B[(i,)  j,k]

6

मैंने पाया कि NumPy: व्यापार की चाल (भाग II) शिक्षाप्रद

आउटपुट सरणी के क्रम को दर्शाने के लिए हम -> का उपयोग करते हैं। इसलिए बाएं हाथ की ओर (LHS) और दाहिने हाथ की तरफ (RHS) के रूप में 'ij, i-> j' के बारे में सोचें। LHS पर किसी भी लेबल की पुनरावृत्ति उत्पाद तत्व को समझती है और फिर खत्म हो जाती है। आरएचएस (आउटपुट) पक्ष पर लेबल को बदलकर, हम उस अक्ष को परिभाषित कर सकते हैं जिसमें हम इनपुट सरणी के संबंध में आगे बढ़ना चाहते हैं, अर्थात अक्ष 0, 1 और इसी के साथ योग।

import numpy as np

>>> a
array([[1, 1, 1],
       [2, 2, 2],
       [3, 3, 3]])
>>> b
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> d = np.einsum('ij, jk->ki', a, b)

ध्यान दें कि तीन अक्ष हैं, i, j, k, और उस j को दोहराया जाता है (बाएं हाथ की तरफ)। i,jके लिए पंक्तियों और स्तंभों का प्रतिनिधित्व करते हैं aj,kके लिए b

उत्पाद की गणना करने और jधुरी को संरेखित करने के लिए हमें एक अक्ष जोड़ने की आवश्यकता है a। ( bपहली धुरी के साथ प्रसारित किया जाएगा?)

a[i, j, k]
   b[j, k]

>>> c = a[:,:,np.newaxis] * b
>>> c
array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[ 0,  2,  4],
        [ 6,  8, 10],
        [12, 14, 16]],

       [[ 0,  3,  6],
        [ 9, 12, 15],
        [18, 21, 24]]])

jदाहिने हाथ की ओर से अनुपस्थित है, इसलिए हम इस पर योग करते हैं jजो कि 3x3x3 सरणी का दूसरा अक्ष है

>>> c = c.sum(1)
>>> c
array([[ 9, 12, 15],
       [18, 24, 30],
       [27, 36, 45]])

अंत में, संकेत दाहिने हाथ की ओर उलटे होते हैं (वर्णानुक्रम में) इसलिए हम स्थानांतरित करते हैं।

>>> c.T
array([[ 9, 18, 27],
       [12, 24, 36],
       [15, 30, 45]])

>>> np.einsum('ij, jk->ki', a, b)
array([[ 9, 18, 27],
       [12, 24, 36],
       [15, 30, 45]])
>>>

NumPy: व्यापार की चाल (भाग II) के लिए साइट के मालिक के साथ-साथ एक Wordpress खाते से निमंत्रण की आवश्यकता होती है
तेजस शेट्टी

... अद्यतन लिंक, सौभाग्य से मुझे यह एक खोज के साथ मिला। - थंक्स।
wwii

@TejasShetty अब यहाँ बहुत सारे बेहतर उत्तर हैं - शायद मुझे इसे हटा देना चाहिए।
wwii

2
कृपया अपना उत्तर न हटाएँ।
तेजस शेट्टी

4

जब einsum के समीकरणों को पढ़ते हैं, तो मैंने पाया है कि यह केवल मानसिक रूप से उन्हें उनके अनिवार्य संस्करणों के लिए उबालने में सक्षम है।

आइए निम्नलिखित (थोपने वाले) कथन से शुरू करें:

C = np.einsum('bhwi,bhwj->bij', A, B)

विराम चिह्न के माध्यम से काम करते हुए पहले हम देखते हैं कि हमारे पास दो 4-अक्षर अल्पविराम-अलग-अलग बूँदें हैं - bhwiऔर bhwj, तीर से पहले, और bijउसके बाद एक एकल 3-अक्षर बूँद । इसलिए, समीकरण दो रैंक -4 टेंसर इनपुट से रैंक -3 टेनसर परिणाम उत्पन्न करता है।

अब, प्रत्येक ब्लॉब में प्रत्येक अक्षर को रेंज वेरिएबल का नाम दें। जिस स्थिति में अक्षर बूँद में दिखाई देता है, वह उस धुरी का सूचकांक होता है, जो उस टेंसर में होती है। C के प्रत्येक तत्व को उत्पन्न करने वाला अनिवार्य योग, इसलिए लूप के लिए तीन नेस्टेड के साथ शुरू करना होगा, प्रत्येक C के लिए एक।

for b in range(...):
    for i in range(...):
        for j in range(...):
            # the variables b, i and j index C in the order of their appearance in the equation
            C[b, i, j] = ...

तो, अनिवार्य रूप से, आपके पास forसी के हर आउटपुट इंडेक्स के लिए एक लूप है। हम अभी के लिए अनिर्धारित श्रेणियों को छोड़ देंगे।

अगला हम बाएं हाथ की ओर देखते हैं - क्या कोई रेंज वैरिएबल हैं जो राइट-हैंड साइड पर दिखाई नहीं देते हैं ? हमारे मामले में - हाँ, और । ऐसे प्रत्येक चर के लिए एक आंतरिक नेस्टेड लूप जोड़ें :hwfor

for b in range(...):
    for i in range(...):
        for j in range(...):
            C[b, i, j] = 0
            for h in range(...):
                for w in range(...):
                    ...

अंतरतम पाश के अंदर अब हमारे पास सभी सूचकांकों को परिभाषित किया गया है, इसलिए हम वास्तविक सारांश लिख सकते हैं और अनुवाद पूरा हो गया है:

# three nested for-loops that index the elements of C
for b in range(...):
    for i in range(...):
        for j in range(...):

            # prepare to sum
            C[b, i, j] = 0

            # two nested for-loops for the two indexes that don't appear on the right-hand side
            for h in range(...):
                for w in range(...):
                    # Sum! Compare the statement below with the original einsum formula
                    # 'bhwi,bhwj->bij'

                    C[b, i, j] += A[b, h, w, i] * B[b, h, w, j]

यदि आप इस प्रकार अब तक कोड का पालन करने में सक्षम हैं, तो बधाई! यह आप सभी को einsum समीकरण पढ़ने में सक्षम होने की जरूरत है। विशेष रूप से ध्यान दें कि ऊपर के स्निपेट में मूल यूनिन्स फॉर्मूला अंतिम समन स्टेटमेंट पर मैप कैसे करता है। फॉर-लूप्स और रेंज बाउंड्स महज फुलफिल हैं और यह अंतिम स्टेटमेंट है जो आपको वास्तव में समझने की जरूरत है कि क्या हो रहा है।

पूर्णता के लिए, आइए देखें कि प्रत्येक श्रेणी चर के लिए सीमाएं कैसे निर्धारित करें। खैर, प्रत्येक चर की सीमा आयाम (ओं) की लंबाई है जो इसे अनुक्रमित करता है। जाहिर है, अगर एक चर एक या एक से अधिक टेंसरों में एक से अधिक आयामों को अनुक्रमित करता है, तो उन आयामों में से प्रत्येक की लंबाई बराबर होनी चाहिए। यहाँ पूरी श्रेणियों के साथ ऊपर कोड है:

# C's shape is determined by the shapes of the inputs
# b indexes both A and B, so its range can come from either A.shape or B.shape
# i indexes only A, so its range can only come from A.shape, the same is true for j and B
assert A.shape[0] == B.shape[0]
assert A.shape[1] == B.shape[1]
assert A.shape[2] == B.shape[2]
C = np.zeros((A.shape[0], A.shape[3], B.shape[3]))
for b in range(A.shape[0]): # b indexes both A and B, or B.shape[0], which must be the same
    for i in range(A.shape[3]):
        for j in range(B.shape[3]):
            # h and w can come from either A or B
            for h in range(A.shape[1]):
                for w in range(A.shape[2]):
                    C[b, i, j] += A[b, h, w, i] * B[b, h, w, j]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.