क्यों np.dot imprecise है? (एन-मंद सरणियाँ)


15

मान लीजिए कि हम np.dotदो 'float32'2D सरणियाँ लेते हैं:

res = np.dot(a, b)   # see CASE 1
print(list(res[0]))  # list shows more digits
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]

नंबर। सिवाय, वे बदल सकते हैं:


मामला 1 : टुकड़ाa

np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')

for i in range(1, len(a)):
    print(list(np.dot(a[:i], b)[0])) # full shape: (i, 6)
[-0.9044868,  -1.1708502, 0.90713596, 3.5594249, 1.1374012, -1.3826287]
[-0.90448684, -1.1708503, 0.9071359,  3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.9071359,  3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]

परिणाम भिन्न होते हैं, भले ही मुद्रित स्लाइस ठीक उसी संख्या से गुणा किया जाता है।


मामला 2 : समतल करें a, 1D संस्करण लें b, फिर स्लाइस करें a:

np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(1, 6).astype('float32')

for i in range(1, len(a)):
    a_flat = np.expand_dims(a[:i].flatten(), -1) # keep 2D
    print(list(np.dot(a_flat, b)[0])) # full shape: (i*6, 6)
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]

मामले 3 : मजबूत नियंत्रण; सभी गैर-शामिल एंट्री को शून्य पर सेट करें : a[1:] = 0CAS 1 कोड में जोड़ें । परिणाम: विसंगतियां बनी रहती हैं।


मामले 4 : के अलावा अन्य सूचकांकों की जाँच करें [0]; जैसे [0], परिणाम सृजन के बिंदु से एक निश्चित # सरणी वृद्धि को स्थिर करना शुरू करते हैं। उत्पादन

np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')

for j in range(len(a) - 2):
    for i in range(1, len(a)):
        res = np.dot(a[:i], b)
        try:    print(list(res[j]))
        except: pass
    print()

इसलिए, 2D * 2D मामले के लिए, परिणाम भिन्न होते हैं - लेकिन 1D * 1D के लिए संगत होते हैं। मेरे कुछ रीडिंग से, यह 1D-1D से साधारण जोड़ का उपयोग करके स्टेम करने के लिए प्रकट होता है, जबकि 2D-2D में 'फैनसीयर' का उपयोग किया जाता है, प्रदर्शन-बढ़ाने वाला जोड़ कम सटीक हो सकता है (जैसे कि जोड़-तोड़ इसके विपरीत होता है)। फिर भी, मैं यह समझने में असमर्थ हूं कि एक बार a'थ्रेशोल्ड' के पिछले टुकड़े किए जाने के मामले में विसंगतियां 1 में गायब क्यों हो जाती हैं ; बड़ा aऔर b, बाद में यह सीमा झूठ लगती है, लेकिन यह हमेशा मौजूद रहती है।

सभी ने कहा: np.dotएनडी-एनडी सरणियों के लिए इंप्रूव (और असंगत) क्यों है ? प्रासंगिक गिट


अतिरिक्त जानकारी :

  • पर्यावरण : विन -10 ओएस, पायथन 3.7.4, स्पाइडर 3.3.6 आईडीई, एनाकोंडा 3.0 2019/10
  • CPU : i7-7700HQ 2.8 GHz
  • Numpy v1.16.5

संभावित अपराधी पुस्तकालय : नॉम्पी एमकेएल - ब्लास लाइब्रेरी भी; ध्यान देने के लिए बी रीको का धन्यवाद


तनाव-परीक्षण कोड : जैसा कि कहा गया है, विसंगतियां आवृत्ति w / बड़े सरणियों में बढ़ जाती हैं; यदि ऊपर प्रजनन योग्य नहीं है, तो नीचे होना चाहिए (यदि नहीं, तो बड़े आकार का प्रयास करें)। मेरा आउटपुट

np.random.seed(1)
a = (0.01*np.random.randn(9, 9999)).astype('float32') # first multiply then type-cast
b = (0.01*np.random.randn(9999, 6)).astype('float32') # *0.01 to bound mults to < 1

for i in range(1, len(a)):
    print(list(np.dot(a[:i], b)[0]))

समस्या की गंभीरता : दिखाई गई विसंगतियां 'छोटी' हैं, लेकिन अब ऐसा नहीं है जब तंत्रिका नेटवर्क पर अरबों की संख्या के साथ कुछ सेकंड में गुणा किया जाता है, और पूरे रनटाइम पर खरबों; रिपोर्ट किए गए मॉडल की सटीकता प्रति धागे के अनुसार 10 प्रतिशत है ।

नीचे एक मॉडल को खिलाने से उत्पन्न सरणियों का एक gif है जो मूल रूप से a[0], w / len(a)==1बनाम है len(a)==32:


पॉल के परीक्षण के अनुसार और उसके साथ अन्य PLATFORMS परिणाम :

केस 1 पुनरुत्पादित (आंशिक रूप से) :

  • Google Colab VM - Intel Xeon 2.3 G-Hz - जुपिटर - पायथन 3.6.8
  • विन -10 प्रो डॉकर डेस्कटॉप - इंटेल i7-8700K - ज्यूपिटर / स्किपी-नोटबुक - पायोनॉन 3.3
  • उबंटू 18.04.2 एलटीएस + डोकर - एएमडी एफएक्स -8150 - ज्यूपिटर / स्केपी-नोटबुक - पायनियर 3.3

नोट : इन उपज बहुत कम त्रुटि से ऊपर दिखाया गया है; पहली पंक्तियों पर दो प्रविष्टियाँ अन्य पंक्तियों में संबंधित प्रविष्टियों से कम से कम महत्वपूर्ण अंक में 1 से बंद हैं।

केस 1 पुन : प्रस्तुत नहीं :

  • Ubuntu 18.04.3 LTS - Intel i7-8700K - IPython 5.5.0 - पायथन 2.7.15+ और 3.6.8 (2 परीक्षण)
  • Ubuntu 18.04.3 LTS - Intel i5-3320M - IPython 5.5.0 - पायथन 2.7.15+
  • Ubuntu 18.04.2 LTS - AMD FX-8150 - IPython 5.5.0 - पायथन 2.7.15rc1

नोट :

  • जुड़ा हुआ Colab नोटबुक और jupyter वातावरण में कहीं कम भिन्नता नहीं है (और केवल पहले दो पंक्तियों के लिए) की तुलना में अपने सिस्टम पर मनाया जाता है। इसके अलावा, केस 2 कभी नहीं (अभी तक) ने अपव्यय दिखाया।
  • इस बहुत ही सीमित नमूने के भीतर, वर्तमान (डॉकरीकृत) ज्यूपिटर पर्यावरण आईपीथॉन पर्यावरण की तुलना में अधिक अतिसंवेदनशील है।
  • np.show_config()पोस्ट करने के लिए बहुत लंबा है, लेकिन सारांश में: IPython envs BLAS / LAPACK- आधारित हैं; कोलाब ओपनबीएलएएस-आधारित है। IPython Linux envs में, BLAS लाइब्रेरी सिस्टम-स्थापित हैं - जुपिटर और कोलाब में, वे / ऑप्ट / कोंडा / लिब से आते हैं

अद्यतन : स्वीकृत उत्तर सटीक, लेकिन व्यापक और अपूर्ण है। यह सवाल किसी के लिए भी खुला रहता है जो कोड स्तर पर व्यवहार की व्याख्या कर सकता है - अर्थात्, एक सटीक एल्गोरिथम जिसका उपयोग किया जाता है np.dot, और यह कैसे उपरोक्त परिणामों में मनाया गया 'सुसंगत असंगतता' (यह भी टिप्पणियां देखें) बताता है। यहाँ मेरे डिक्रिपरिंग से परे कुछ प्रत्यक्ष कार्यान्वयन हैं: sdot.c - arraytypes.c.src


टिप्पणियाँ विस्तारित चर्चा के लिए नहीं हैं; इस वार्तालाप को बातचीत में स्थानांतरित कर दिया गया है ।
शमूएल ल्यू

आम एल्गोरिदम ndarraysआमतौर पर संख्यात्मक सटीक नुकसान की उपेक्षा करते हैं। क्योंकि वे reduce-sumप्रत्येक अक्ष के साथ सादगी के लिए , संचालन का क्रम इष्टतम नहीं हो सकता है ... ध्यान दें कि यदि आप सटीक त्रुटि का मन बनाते हैं तो आप उपयोग कर सकते हैंfloat64
Vitor SRG

मेरे पास कल की समीक्षा करने का समय नहीं हो सकता है, इसलिए अब इनाम देना है।
पॉल

@Paul इसे वैसे भी उच्चतम मत वाले उत्तर के लिए स्वचालित रूप से प्रदान किया जाएगा - लेकिन ठीक है, सूचित करने के लिए धन्यवाद
OverLordGoldDragon

जवाबों:


7

यह अपरिहार्य संख्यात्मक संसेचन जैसा दिखता है। जैसा कि यहां बताया गया है , NumPy मैट्रिक्स गुणन के लिए अत्यधिक अनुकूलित, सावधानीपूर्वक ट्यून किए गए BLAS विधि का उपयोग करता है । इसका मतलब है कि शायद मैट्रिक्स के आकार में परिवर्तन होने पर 2 मैट्रिसेस को बदलने के लिए ऑपरेशन (योग और उत्पादों) का क्रम बदल जाता है।

स्पष्ट होने की कोशिश करते हुए, हम जानते हैं कि, गणितीय रूप से , परिणामस्वरूप मैट्रिक्स के प्रत्येक तत्व की गणना दो वैक्टर के डॉट उत्पाद (संख्याओं के बराबर-लंबाई अनुक्रम) के रूप में की जा सकती है। लेकिन यह नहीं है कि NumPy परिणामी मैट्रिक्स के एक तत्व की गणना कैसे करता है। वास्तव में स्ट्रैसेन एल्गोरिथ्म की तरह अधिक कुशल लेकिन जटिल एल्गोरिदम हैं, जो सीधे पंक्ति-स्तंभ डॉट उत्पाद की गणना किए बिना एक ही परिणाम प्राप्त करते हैं।

ऐसे एल्गोरिदम का उपयोग करते समय, भले ही परिणामी मैट्रिक्स C = AB के तत्व C ij को गणितीय रूप से B के j-th कॉलम के साथ A की i-th पंक्ति के डॉट उत्पाद के रूप में परिभाषित किया गया हो , यदि आप एक मैट्रिक्स A2 को गुणा करते हैं समान i-th पंक्ति, A के साथ एक मैट्रिक्स B2 में B के समान J-th कॉलम है , तत्व C2 ij को वास्तव में संचालन के एक अलग अनुक्रम के बाद गणना की जाएगी (जो पूरे A2 और B2 पर निर्भर करता है। matrices), संभवतः विभिन्न संख्यात्मक त्रुटियों के लिए अग्रणी।

इसीलिए, भले ही गणितीय रूप से C ij = C2 ij (जैसे आपकी CASE 1), गणना में एल्गोरिथ्म द्वारा अनुसरण किए जाने वाले संचालन के विभिन्न अनुक्रम (मैट्रिक्स आकार में परिवर्तन के कारण) अलग-अलग संख्यात्मक त्रुटियों की ओर जाता है। संख्यात्मक त्रुटि यह भी बताती है कि पर्यावरण और तथ्य के आधार पर कुछ अलग परिणाम हैं, कुछ मामलों में, कुछ वातावरणों के लिए, संख्यात्मक त्रुटि अनुपस्थित हो सकती है।


2
लिंक के लिए धन्यवाद, इसमें प्रासंगिक जानकारी शामिल है - आपका जवाब, हालांकि, अधिक विस्तृत हो सकता है, क्योंकि अभी तक यह सवाल के नीचे टिप्पणियों का एक पैराफ्रासिंग है। उदाहरण के लिए, लिंक किया गया SO प्रत्यक्ष Cकोड दिखाता है , और एल्गोरिथम-स्तरीय स्पष्टीकरण प्रदान करता है, इसलिए यह सही दिशा में बढ़ रहा है।
OverLordGoldDragon

यह "अपरिहार्य" भी नहीं है, जैसा कि सवाल के तल पर दिखाया गया है - और
आवेग की

1
@OverLordGoldDragon: (1) जोड़ के साथ एक तुच्छ उदाहरण: संख्या लें n, संख्या kऐसी लें कि यह kअंतिम मंटिसा अंक की शुद्धता से कम हो । पायथन के मूल निवास के लिए, n = 1.0और k = 1e-16काम करता है। अब चलो ks = [k] * 100। देखें कि sum([n] + ks) == n, जबकि sum(ks + [n]) > n, वह है, जो समन क्रम मायने रखता है। (2) आधुनिक सीपीयू में समानांतर में फ्लोटिंग-पॉइंट (एफपी) संचालन को निष्पादित करने के लिए कई इकाइयां हैं, और जिस क्रम में a + b + c + dसीपीयू पर गणना की जाती है वह परिभाषित नहीं है, भले ही कमांड मशीन कोड में a + bपहले आता हो c + d
9000

1
@OverLordGoldDragon आपको इस बात से अवगत होना चाहिए कि आप जिस नंबर से निपटने के लिए अपने प्रोग्राम से पूछते हैं, उनमें से अधिकांश को एक फ्लोटिंग पॉइंट द्वारा बिल्कुल प्रतिनिधित्व नहीं किया जा सकता है। कोशिश करो format(0.01, '.30f')। यदि कोई साधारण संख्या जैसे 0.01कि एक NumPy फ़्लोटिंग पॉइंट द्वारा बिल्कुल प्रतिनिधित्व नहीं किया जा सकता है, तो मेरे उत्तर की बात को समझने के लिए NumPy मैट्रिक्स गुणन एल्गोरिथम के गहरे विवरण को जानने की कोई आवश्यकता नहीं है; वह यह है कि विभिन्न प्रारंभिक मैट्रिक्स आपरेशन के विभिन्न दृश्यों के लिए नेतृत्व ताकि गणितीय बराबर परिणाम संख्यात्मक त्रुटियों के कारण एक छोटी राशि से भिन्न हो सकती है।
एमएमज

2
@OverLordGoldDragon re: काला जादू। CS MOOCs के एक जोड़े को पढ़ने के लिए आवश्यक पेपर है। मेरा स्मरण इतना महान नहीं है, लेकिन मुझे लगता है कि यह है: itu.dk/~sestoft/bacademy/IEEE754_article.pdf
पॉल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.