ट्रैक
/
Julia
Julia
/
सिलेबस
/
रैखिक बीजगणित की मूल बातें
रै

रैखिक बीजगणित की मूल बातें में Julia

1 अभ्यास

रैखिक बीजगणित की मूल बातें के बारे में

"रैखिक बीजगणित" क्या है?

तकनीकी परिभाषाएँ बहुत हैं: विकिपीडिया पर, पाठ्यपुस्तकों में, और 3Blue1Brown जैसी बेहतरीन वेबसाइटों पर। लेकिन हमारे उद्देश्य के लिए हम थोड़े अनौपचारिक रह सकते हैं।

Note

रैखिक बीजगणित उन बहुत सी दिलचस्प (और बहुत उपयोगी) चीज़ों के बारे में है जो आप वेक्टर और मैट्रिक्स के साथ कर सकते हैं।

Julia (और Python) के मेंटेनर को ऐसी चीज़ें बहुत पसंद हैं। Exercism के प्रबंधन को, उतनी नहीं।

यह कॉन्सेप्ट एक बहुत बड़े विषय के सिर्फ आसान पहलुओं तक सीमित रहेगा, लेकिन चेतावनी है: यह अनिवार्य रूप से काफी गणितीय कॉन्सेप्ट है।

वेक्टर क्या है?

यह इस पर निर्भर करता है कि आप किससे पूछते हैं, और किसी काफी अमूर्त चीज़ की कल्पना आप कैसे करना चाहते हैं।

  • Julia में, Vector{T} बस Array{T, 1} का एक उपनाम है, और eltype T कुछ भी हो सकता है।
  • भौतिकी में (काफी हद तक), वेक्टर एक तीर है जिसकी N-आयामी अंतरिक्ष में length और direction होती है, लेकिन कोई निश्चित स्थिति नहीं होती।
  • रैखिक बीजगणित में, भौतिकविदों का तीर अंतरिक्ष में स्थिर होता है, जिसकी पूँछ मूल बिंदु पर होती है। इससे तीर का डंडा अनावश्यक हो जाता है, इसलिए हम वेक्टर को N-आयामी अंतरिक्ष में एक point के रूप में दर्शा सकते हैं, जिसमें N एलिमेंट मूल बिंदु से हर एक N अक्ष के साथ की दूरी दर्शाते हैं।

अभी के लिए, स्ट्रिंग या अक्षर वाले वेक्टर को छोड़ दीजिए। इस कॉन्सेप्ट में हम संख्यात्मक टाइप के साथ काम करेंगे: Int, Float, या Complex.

मैट्रिक्स क्या है?

फिर से, आपको कई तरह के जवाब मिलेंगे।

  • संख्याओं का एक आयताकार 2-D ऐरे (जिसके किनारे बराबर हों). वर्ग मैट्रिक्स एक आम विशेष स्थिति है।
  • स्तंभ वेक्टरों का एक linear combination, जो साथ-साथ रखे गए हों।
  • एक transformation जिसे किसी वेक्टर पर लागू किया जा सकता है, ठीक वैसे ही जैसे किसी function को दूसरे इनपुट टाइप पर लागू किया जाता है।

कुछ तरह के वर्ग मैट्रिक्स इतने आम हैं कि उनके विशेष नाम हैं।

विकर्ण मैट्रिक्स: सारी गैर-शून्य प्रविष्टियाँ main diagonal पर होती हैं (ऊपर-बाएँ से नीचे-दाएँ)।

julia> [1 0 0; 0 2 0; 0 0 3]
3×3 Matrix{Int64}:
 1  0  0
 0  2  0
 0  0  3

# a convenient shortcut
julia> diagm(1:3)
3×3 Matrix{Int64}:
 1  0  0
 0  2  0
 0  0  3

तत्समक मैट्रिक्स: एक विकर्ण मैट्रिक्स जिसके विकर्ण पर सिर्फ एक (1) होते हैं (ऐसे कारणों से जो आगे और साफ हो जाएँगे)। इसे अक्सर I से संक्षिप्त किया जाता है।

julia> Matrix{Float64}(I, 3, 3)
3×3 Matrix{Float64}:
 1.0  0.0  0.0
 0.0  1.0  0.0
 0.0  0.0  1.0

ऊपरी त्रिभुजीय मैट्रिक्स: विकर्ण पर और उसके ऊपर गैर-शून्य मान, नीचे शून्य। निचला त्रिभुजीय मैट्रिक्स का अंदाज़ा आप लगा सकते हैं।

मैट्रिक्स का ट्रांसपोज़ करना

अगर आप पंक्तियों को सचमुच स्तंभों से बदलना चाहते हैं, तो permutedims() फंक्शन यही करता है और आपको एक नया मैट्रिक्स देता है।

इसमें कॉपी करना शामिल है, जो बड़े मैट्रिक्स के साथ काम करते समय धीमा होता है और अधिक मेमोरी लेता है।

रैखिक बीजगणित के उद्देश्य से, transpose() फंक्शन अधिक उपयोगी है, क्योंकि यह मूल मैट्रिक्स के चारों ओर जल्दी से एक लेज़ी रैपर बनाता है।

उससे भी अधिक उपयोगी adjoint() फंक्शन है, जो किसी भी कॉम्प्लेक्स संख्या में काल्पनिक भाग का चिह्न भी बदल देता है (अगर आप सोचते हैं कि यह इतना महत्वपूर्ण क्यों है, तो एक तेज़ जवाब है क्वांटम यांत्रिकी)। यह ऑपरेशन इतना आम है कि एडजॉइंट बनाने के लिए हम वेरिएबल के नाम के आगे सिर्फ एक अपॉस्ट्रॉफ़ी ' जोड़ सकते हैं।

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

# new, full copy
julia> permutedims(m)
3×2 Matrix{Int64}:
 1  4
 2  5
 3  6

# lazy version
julia> transpose(m)
3×2 transpose(::Matrix{Int64}) with eltype Int64:
 1  4
 2  5
 3  6

julia> mc = [1+2im 2+3im; 3+2im 1+2im]
2×2 Matrix{Complex{Int64}}:
 1+2im  2+3im
 3+2im  1+2im

# lazy conjugate transpose
julia> adjoint(mc)
2×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}:
 1-2im  3-2im
 2-3im  1-2im

# syntactic sugar for adjoint
julia> mc'
2×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}:
 1-2im  3-2im
 2-3im  1-2im

गुणा

बाकी दस्तावेज़ में LinearAlgebra मॉड्यूल की कार्यक्षमता शामिल है। एक using LinearAlgebra लाइन इसे नेमस्पेस में ले आएगी, लेकिन हम इसे उदाहरणों में बार-बार नहीं दोहराएँगे (देखने में बहुत ज़्यादा भीड़)।

वेक्टर, एलिमेंट-वाइज़ गुणनफल

इस पर Vector Operations कॉन्सेप्ट में चर्चा की गई थी। ऑपरेटर .* है, जो इनपुट वेक्टरों पर जोड़ी-जोड़ी करके काम करता है और इनपुट के ही आकार और टाइप का आउटपुट देता है।

julia> [1, 2] .* [3, 4]
2-element Vector{Int64}:
 3
 8

वेक्टर, डॉट गुणनफल

यह बेहद आम ऑपरेशन पाठ्यपुस्तकों में u ⋅ v लिखा जाता है और इसे "डॉट" गुणनफल कहते हैं।

डॉट गुणनफल एलिमेंट-वाइज़ गुणनफल के योग के बराबर होता है।

दो वेक्टरों को सामान्य * ऑपरेटर से गुणा किया जा सकता है, पर तभी जब बायाँ वेक्टर एक adjoint हो: जिसे सुविधाजनक रूप से u' * v लिखा जाता है। यह बात मैट्रिक्स गुणा वाले आगे के हिस्से में और साफ हो जानी चाहिए।

ऊपर उठा हुआ (बीच का) डॉट Julia में उपलब्ध है (\cdot और फिर tab से डाला जाता है), जो dot() फंक्शन के लिए सिंटैक्टिक शुगर है। एडजॉइंट निर्दिष्ट करने की ज़रूरत नहीं है, क्योंकि यह ब्यौरा अपने आप संभाल लिया जाता है।

julia> using LinearAlgebra

# with element-wise syntax
julia> sum([1, 2] .* [3, 4])
11

# with u' * v syntax
julia> [1, 2]' * [3, 4]
11

# with dot()
julia> dot([1, 2], [3, 4])
11

# with \cdot syntax
julia> [1, 2] ⋅ [3, 4]
11

कॉम्प्लेक्स-वैल्यू वाले वेक्टरों के लिए, बाएँ वेक्टर का संयुग्मी होना ज़रूरी है (काल्पनिक भाग का चिह्न बदला हुआ)। dot फंक्शन यह अपने आप करता है, u' सिंटैक्स इसे स्पष्ट रूप से करता है, लेकिन अगर u और v कॉम्प्लेक्स हों तो sum(u .* v) विफल हो जाएगा।

वेक्टर, क्रॉस गुणनफल

उन सबके लिए थोड़ी पुरानी यादें जिन्होंने पहले कभी विद्युत और चुंबकत्व का कोर्स किया है! साथ ही, उन इंजीनियरों के लिए भी जो टॉर्क या कोणीय संवेग वेक्टर निकालने से परिचित हैं।

जहाँ डॉट गुणनफल दो वेक्टरों को एक scalar में बदल देता है, वहीं क्रॉस गुणनफल दो 3-वेक्टरों को एक तीसरा 3-वेक्टर बनाता है।

वेक्टर अंतरिक्ष के ज्यामितीय निरूपण में, नया वेक्टर उस तल के लंबवत होता है जिसमें दोनों इनपुट वेक्टर हैं। अगर दोनों इनपुट समानांतर हैं (चिह्न तक), तो वे कोई तल नहीं बनाते, इसलिए आउटपुट [0, 0, 0] होगा।

क्रम मायने रखता है: u × v == -(v × u)। यह वही प्रसिद्ध दाहिने हाथ का नियम है, जिसने हममें से बहुतों को अंतरिक्ष में अपने अंगूठे और दो उँगलियों को घुमाते हुए उन्हें हैरानी से घूरते छोड़ दिया है (अक्सर उलझे भाव के साथ)।

# with \times syntax
julia> [1, 2, 3] × [3, 4, 5]
3-element Vector{Int64}:
 -2
  4
 -2
# with cross()
julia> cross([1, 2, 3], [3, 4, 5])
3-element Vector{Int64}:
 -2
  4
 -2

ध्यान दीजिए कि यह ऑपरेशन लंबाई-3 वेक्टरों तक ही सीमित है (जो लंबकोणीय x, y, z अक्षों वाले यूक्लिडीय अंतरिक्ष के बराबर है)। गणितज्ञ इसे दूसरे आयामों के लिए भी परिभाषित करने में खुशी महसूस करते हैं, जिसका नतीजा समझ से परे शब्दों का ढेर होता है (उच्च-कोटि टेंसर, वेज गुणनफल, बाहरी गुणनफल, मल्टीवेक्टर...)। उस समय हममें से ज़्यादातर भाग जाते हैं या फौरन विषय बदल देते हैं!

नॉर्म

एक वेक्टर कितना "बड़ा" होता है?

norm इसी को पकड़ने की कोशिश है, जो वेक्टर को एक उपयुक्त स्केलर में बदल देता है।

नॉर्म का एक पूरा परिवार परिभाषित है, लेकिन अब तक सबसे आम है 2-नॉर्म, जो √(v ⋅ v) है।

यह वर्ग-माध्य-मूल ऑपरेशन मूल बिंदु से पाइथागोरस दूरी है (N-आयामी अंतरिक्ष में)। अगर हम वेक्टर की कल्पना एक तीर के रूप में करें, जिसकी पूँछ मूल बिंदु पर हो, तो 2-नॉर्म उस तीर की लंबाई है।

किसी भी p-नॉर्म की गणना p को दूसरे आर्गुमेंट के रूप में देकर की जा सकती है। 1-नॉर्म कभी-कभी उपयोगी होता है: यह बस प्रविष्टियों के निरपेक्ष मानों का योग है (इसलिए निकालना बहुत तेज़ और आसान है)।

# defaults to the 2-norm
julia> norm([1, 2, 3])
3.7416573867739413
# the 1-norm
julia> norm([1, -2, 3], 1)
6.0

मैट्रिक्स गुणा

कभी-कभी लगता है कि दुनिया के हर अनुप्रयुक्त गणितज्ञ ने पिछले 80 सालों का ज़्यादातर हिस्सा हर गणना को मैट्रिक्स गुणा की एक श्रृंखला में बदलने में लगाया है।

यह एक ऐसा ऑपरेशन है जिसमें कंप्यूटर बहुत अच्छे हैं:

  • यह बहुत दोहरावदार होता है।
  • इसे कुशलता से समानांतरित किया जा सकता है।
  • इसे तेज़ बनाने के लिए बहुत सारा विशेष हार्डवेयर विकसित किया गया है: 1970 के दशक के $80 मिलियन वाले Cray-1 से लेकर उस GPU तक जो शायद आपके लैपटॉप में पहले से जुड़ा है।

ब्यौरे काफी आसान हैं, हालाँकि पहली नज़र में बहुत सहज नहीं लगते।

मान लीजिए एक मैट्रिक्स A किसी वेक्टर v को गुणा करके एक आउटपुट w देता है (रैखिक बीजगणित की परंपरा से, हम मैट्रिक्स के लिए बड़े अक्षर और वेक्टरों के लिए छोटे अक्षर इस्तेमाल करेंगे)।

w का पहला एलिमेंट पाने के लिए A की ऊपरी पंक्ति का v के साथ डॉट गुणन किया जाता है, दूसरी पंक्ति से दूसरा एलिमेंट मिलता है, और इसी तरह नीचे तक।

julia> A = [1 2; 3 4]
2×2 Matrix{Int64}:
 1  2
 3  4
julia> v = [5, 6]
2-element Vector{Int64}:
 5
 6
julia> A * v
2-element Vector{Int64}:
 17  # equals [1, 2] ⋅ [5, 6]
 39  # equals [3, 4] ⋅ [5, 6]

मैट्रिक्स * मैट्रिक्स गुणा इसे दाईं ओर वाले मैट्रिक्स के सारे स्तंभों पर लागू करता है।

C = A * B के लिए, हम मान सकते हैं कि A B के हर स्तंभ को गुणा करके C का उससे मेल खाता स्तंभ देता है: यानी मैट्रिक्स-वेक्टर गुणा की एक श्रृंखला।

इसी तरह, हम कह सकते हैं कि A की पहली पंक्ति का B के हर स्तंभ के साथ डॉट गुणन करके C की ऊपरी पंक्ति मिलती है, दूसरी पंक्ति से दूसरी पंक्ति मिलती है, और इसी तरह नीचे तक।

ऐसे कई सोचने के तरीके हैं, और इच्छुक विद्यार्थी इन पर चर्चा करता एक पूरा MIT व्याख्यान देख सकते हैं।

julia> A
2×2 Matrix{Int64}:
 1  2
 3  4

julia> B = [5 7; 6 8]
2×2 Matrix{Int64}:
 5  7
 6  8

# left column is the same as A*v previously
julia> A * B
2×2 Matrix{Int64}:
 17  23
 39  53

julia> B * A
2×2 Matrix{Int64}:
 26  38
 30  44

जैसा ऊपर के उदाहरण में दिखाया गया है, मैट्रिक्स गुणा क्रम-विनिमेय नहीं है: A*B और B*A के बीच कोई सीधा संबंध नहीं होता।

जो इसके लिए नए हैं, उनके लिए सिर्फ शब्द पढ़कर मैट्रिक्स गुणा की कल्पना करना शायद मुश्किल है। YouTube पर इसे ग्राफ़िक रूप से दिखाने वाले बहुत सारे वीडियो हैं, तो "matrix multiplication" खोजिए और अपनी पसंद की शैली, ब्यौरे के स्तर और भाषा वाला कोई एक चुन लीजिए।

आयाम

दो वेक्टरों का डॉट गुणनफल इस पर निर्भर करता है कि उनकी लंबाई बराबर हो।

इसी तरह, मैट्रिक्स गुणा के लिए बाएँ मैट्रिक्स के स्तंभों की संख्या दाएँ मैट्रिक्स की पंक्तियों की संख्या से मेल खानी चाहिए।

आकारों को (nrows, ncols) ट्यूपल के रूप में लिखें, जैसा size(A) देता है, तो हमें (a, b) * (b, c) -> (a, c) मिलता है। "अंदरूनी" आयाम, यहाँ b और b, डॉट गुणनफल लेने के लिए संगत हैं। "बाहरी" आयाम, यहाँ a और c, आउटपुट के आयाम तय करते हैं।

आयताकार मैट्रिक्स के साथ एक उदाहरण:

julia> D = reshape(1:6, 2, 3)
2×3 reshape(::UnitRange{Int64}, 2, 3) with eltype Int64:
 1  3  5
 2  4  6

julia> E = reshape(1:12, 3, 4)
3×4 reshape(::UnitRange{Int64}, 3, 4) with eltype Int64:
 1  4  7  10
 2  5  8  11
 3  6  9  12

julia> D * E
2×4 Matrix{Int64}:
 22  49   76  103
 28  64  100  136

# (3, 4) * (2, 3) not possible
julia> E * D
ERROR: DimensionMismatch: matrix A has axes (Base.OneTo(3),Base.OneTo(4)), matrix B has axes (Base.OneTo(2),Base.OneTo(3))

वेक्टरों के जोड़ों को मैट्रिक्स की तरह गुणा करने के दो तरीके हैं।

परंपरा से, हम डॉट गुणनफल के बराबर u' * v इस्तेमाल करेंगे। आयाम (1, 3) * (3, 1) हैं, और Julia (1, 1) आउटपुट को स्केलर में सरल कर देती है (मिसाल के तौर पर R के विपरीत)।

इसके बजाय, हम u * v' इस्तेमाल कर सकते हैं, जिसके आयाम (3, 1) * (1, 3) -> (3, 3) हैं, जो एलिमेंटों के सभी संभव जोड़ों को गुणा करके वेक्टरों को एक मैट्रिक्स तक फैला देता है।

julia> u = [1, 2]
2-element Vector{Int64}:
 1
 2
julia> v = [3, 4]
2-element Vector{Int64}:
 3
 4
julia> u' * v
11
julia> u * v'
2×2 Matrix{Int64}:
 3  4
 6  8

u' * v को कभी-कभी आंतरिक गुणनफल कहा जाता है।

(कम आम) u * v' उसी तरह बाहरी गुणनफल है। इसका संबंध टेंसर गुणनफल से है, हालाँकि वह हमारे दायरे से बहुत बाहर है।

घूर्णन

एक खास तौर पर आम मैट्रिक्स गुणा में घूर्णन मैट्रिक्स शामिल होते हैं।

2D में, किसी वेक्टर को θ रेडियन वामावर्त घुमाने के लिए एक अपेक्षाकृत आसान मैट्रिक्स होता है।

julia> rot2d(θ, vec) = [cos(θ) -sin(θ); sin(θ) cos(θ)] * vec
rot2d (generic function with 1 method)

# unit vector in the x direction
julia> i_hat = [1, 0]
2-element Vector{Int64}:
 1
 0

# rotate 45 degrees
julia> rot2d(π/4, i_hat)
2-element Vector{Float64}:
 0.7071067811865476
 0.7071067811865475

# rotate 90 degrees -> unit vector in the y direction, j_hat
julia> rot2d(π/2, i_hat)
2-element Vector{Float64}:
 6.123233995736766e-17  # zero, within numerical error
 1.0

3D में घूर्णन के लिए दो कोणों वाला एक अधिक जटिल मैट्रिक्स चाहिए। बहुत सारा LaTeX डाले बिना इसे Markdown दस्तावेज़ में दिखाना मुश्किल है, तो फ़ॉर्मूला के लिए विकिपीडिया देखिए।

3-D ग्राफ़िक्स के लिए, जैसे OpenGL और उसके बाद आने वाले टूल, परंपरा समरूप निर्देशांक के साथ काम करने की है।

हर शीर्ष को एक 4-वेक्टर (या उसी तरह एक मैट्रिक्स स्तंभ) से दर्शाया जाता है: (x, y, z) पर स्थित बिंदु के लिए [x, y, z, 1.0]।

इससे अधिक विस्तृत रूपांतरण मैट्रिक्स संभव होते हैं। घूर्णन फिर भी A[1:3, 1:3] पर होता है, स्थानांतरण A[1:3, 4] पर [Δx, Δy, Δz] होते हैं, स्केलिंग विकर्ण पर होती है, और तिरछापन, परिप्रेक्ष्य आदि के लिए और भी संभावनाएँ हैं।

प्रदर्शन

दुनिया भर के कंप्यूटर विज्ञान विभागों में ऐसे बहुत से PhD शोध-प्रबंध हैं जिनमें मैट्रिक्स गुणा के एल्गोरिदम में छोटे-छोटे सुधारों पर एक या ज़्यादा अध्याय हैं। यह सचमुच महत्वपूर्ण है, और कई संगठन अपने फ़ायदे के लिए इस शोध पर पैसा लगाने को तैयार हैं।

प्रदर्शन एक बड़ा विषय है जिसमें हम गहराई तक नहीं जा सकते, लेकिन मिसाल के लिए हम अलग-अलग आकार के रैंडम मैट्रिक्स गुणा करके देख सकते हैं।

नीचे दिया गया कोड एक जल्दी से किया गया मोटा अनुमान है (बेहतर तरीके के लिए BenchmarkTools.jl इस्तेमाल कीजिए)।

इस्तेमाल किया गया सिस्टम एक छोटा $430 (US) का PC था: Ryzen 9 प्रोसेसर, 32GB RAM, Linux Mint 22.1, Julia 1.11.6.

julia> mmul(A) = A * A
mmul (generic function with 1 method)
julia> A = rand(Float64, 1_000, 1_000);
julia> @time mmul(A);
  0.011055 seconds (3 allocations: 7.629 MiB)
julia> A = rand(Float64, 10_000, 10_000);
julia> @time mmul(A);
  7.236284 seconds (1.61 k allocations: 763.027 MiB, 17.92% gc time, 0.15% compilation time)

मोटे तौर पर, दस लाख एलिमेंट वाले मैट्रिक्स के एक जोड़े में कुछ मिलीसेकंड लगे, और 10 करोड़ एलिमेंट वाले मैट्रिक्स में कुछ सेकंड। कई और कोटि बढ़ाने के लिए बेहतर हार्डवेयर चाहिए होगा...

GitHub के ज़रिए संपादित करें यह लिंक नई विंडो या टैब में खुलता है।

रैखिक बीजगणित की मूल बातें सीखिए