6.13. الجبر الخطي¶
الجبر الخطي على الكاميرا هو عمل بمصفوفات صغيرة: دوران 3x3 يدمج عينة IMU في إطار العالم، ومصفوفة معايرة تصحح عدسة، وتحديث حالة-تباين لمرشح كالمان، وملاءمة كثيرة حدود تخرج معادلاتها الطبيعية كحلّ خطي صغير. وتغطي الوحدتان الفرعيتان numpy.linalg و scipy.linalg هذا النطاق تمامًا.
توجد الدوال في وحدتين. فعمليات حاصل ضرب المصفوفات في المستوى الأعلى من numpy؛ والتفكيكات ومعكوس المصفوفة تحت numpy.linalg؛ وحلّالات الأنظمة الخطية المخصصة تحت scipy.linalg. فضرب مصفوفتين 2x2، على سبيل المثال:
from ulab import numpy as np
A = np.array([[1, 2], [3, 4]], dtype=np.float)
B = np.array([[5, 6], [7, 8]], dtype=np.float)
np.dot(A, B)
# array([[19.0, 22.0],
# [43.0, 50.0]])
6.13.1. ما المتاح¶
dot()-- حاصل ضرب مصفوفة أو متجه.cross()-- حاصل الضرب الاتجاهي لمتجهين ثلاثيي الأبعاد.trace()-- مجموع القطر.inv()-- معكوس المصفوفة.det()-- المحدِّد (determinant).cholesky()-- تفكيك تشوليسكي (مدخل متماثل موجب التحديد).eig()-- القيم الذاتية والمتجهات الذاتية لمصفوفة حقيقية متماثلة.norm()-- المعيار 2 (2-norm) لمتجه أو مصفوفة.qr()-- تفكيك QR معmode='reduced'(الافتراضي) أوmode='complete'.solve_triangular()-- حلّA @ x = bعندما تكونAمثلثية.cho_solve()-- حلّA @ x = bبمعلومية عامل تشوليسكي لـA.
6.13.2. dot وcross وtrace¶
الدالة dot() هي الطريقة التي يُكتب بها ضرب المصفوفات على الكاميرا. أما العامل @ الذي يوفره numpy على سطح المكتب للمهمة نفسها فهو غير منفَّذ على ndarray، لذا فإن كل حاصل ضرب مصفوفة-متجه ومصفوفة-مصفوفة وجداء قياسي لمتجهين يمر عبر استدعاء dot()
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
np.dot(a, b) # 32.0 (scalar product)
np.cross(a, b) # array([-3.0, 6.0, -3.0])
m = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
np.dot(m, a) # matrix-vector product
np.dot(m, m) # matrix-matrix product
np.trace(m) # 1 + 5 + 9 = 15.0
نتيجة dot() تكون دائمًا من النوع float. والدالة cross() هي الحاصل الاتجاهي لمتجهين ثلاثيين، و trace() هي مجموع القطر الرئيسي لمصفوفة مربعة.
6.13.3. inv وdet¶
m = np.array([[1, 2, 3, 4],
[4, 5, 6, 4],
[7, 9, 9, 4],
[3, 4, 5, 6]])
print(np.linalg.inv(m))
print(np.linalg.det(m))
يُحسب المعكوس بطريقة حذف غاوس-جوردان، لذا تطلق inv() الاستثناء ValueError عندما تكون المصفوفة شاذة (أي عندما يصبح أحد عناصر القطر صفرًا أثناء الحذف). وكلفة الذاكرة RAM تقارب ضعف حجم المدخل.
يعيد المحدِّد استخدام الحذف نفسه -- فزمن التشغيل مطابق جوهريًا للمعكوس.
عندما يكون هدف التطبيق هو حلّ نظام خطي، فلا تعكس وتضرب -- بل فضّل الحلّالات المخصصة أدناه. فكلاهما أسرع وأفضل سلوكًا عدديًا.
6.13.4. cholesky¶
بالنسبة لمصفوفة A متماثلة وموجبة التحديد، تعيد cholesky() مصفوفة مثلثية سفلية L بحيث A = L @ L.T
a = np.array([[25, 15, -5],
[15, 18, 0],
[-5, 0, 11]])
L = np.linalg.cholesky(a)
إذا لم يكن المدخل موجب التحديد أو لم يكن متماثلًا، يُطلق الاستثناء ValueError.
عامل تشوليسكي يكلّف نصف عمل تفكيك LU، وهو نقطة الانطلاق الصحيحة لأي مسألة تُعرف مصفوفتها بأنها متماثلة وموجبة التحديد (تحديثات التباين، والمعادلات الطبيعية من ملاءمة المربعات الصغرى).
6.13.5. eig¶
تعمل eig() على المصفوفات الحقيقية المتماثلة فقط. أما المصفوفات غير المتماثلة فتطلق ValueError. وتعيد ثنائية (eigenvalues, eigenvectors)
a = np.array([[1, 2, 1, 4],
[2, 5, 3, 5],
[1, 3, 6, 1],
[4, 5, 1, 7]], dtype=np.uint8)
x, y = np.linalg.eig(a)
ملاحظات:
تعود القيم الذاتية دون ترتيب معيّن. طبّق
sort()(والتبديلة نفسها على المتجهات الذاتية عبرargsort()) عندما يكون الترتيب مهمًا.المتجه الذاتي وحيد فقط حتى عامل قياسي غير صفري، لذا فإن إشارة المتجهات الذاتية الفردية ليست محددة على نحو وحيد. وقد ينتج تشغيلان صحيحان متجهات بإشارات متعاكسة؛ وهذا غير ضار.
6.13.6. norm¶
المعيار الإقليدي (فروبينيوس) لمتجه أو مصفوفة:
v = np.array([1, 2, 3, 4, 5])
m = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
np.linalg.norm(v) # 7.416...
np.linalg.norm(m) # 16.881...
الكلمة المفتاحية الاختيارية axis= تأخذ المعيار على امتداد محور واحد بدلًا من المصفوفة بأكملها.
6.13.7. qr¶
تُفكّك qr() مصفوفة مستطيلة A (شكلها (M, N)) إلى مصفوفة متعامدة المعايير Q ومصفوفة مثلثية علوية R بحيث A == Q @ R
A = np.arange(6).reshape((3, 2))
q, r = np.linalg.qr(A)
# mode='reduced' (default): q is (3, 2), r is (2, 2)
q, r = np.linalg.qr(A, mode='complete')
# q is (3, 3), r is (3, 2)
يُنفَّذ التفكيك عبر دورانات غيفنز المتعاقبة. وهو الخيار الصحيح لمسائل المربعات الصغرى حيث تكون المصفوفة غير متماثلة.
6.13.8. حلّ الأنظمة¶
الحلّالان المخصصان تحت ulab.scipy.linalg أسرع وأدق كلاهما من np.dot(np.linalg.inv(A), b):
solve_triangular(a, b, lower=False)()-- حلّa @ x = bبافتراض أنaمثلثية:A = np.array([[3, 0, 0, 0], [2, 1, 0, 0], [1, 0, 1, 0], [1, 2, 1, 8]]) b = np.array([4, 2, 4, 2]) x = sp.linalg.solve_triangular(A, b, lower=True)
cho_solve(L, b)()-- بمعلومية عامل تشوليسكيL، حلّA @ x = bحيثA = L @ L.TL = np.linalg.cholesky(A) x = sp.linalg.cho_solve(L, b)
استعن بهذه بدلًا من العكس كلما سمح هيكل A بذلك -- فهي توفر عمل الحذف و المعكوس الصريح.
6.13.9. حلّ نظام خطي صغير¶
A = np.array([[3, 0, 1, 1],
[0, 1, 0, 2],
[1, 0, 1, 1],
[1, 2, 1, 8]])
b = np.array([4, 2, 4, 2])
x = np.dot(np.linalg.inv(A), b)
print(x)
print(np.dot(A, x)) # should equal b
يمكن التعبير عن المسألة نفسها بتفكيك A واستدعاء الحلّال المناسب -- وهو أسرع وأدق عندما يكون لـ A الهيكل المناسب.
للاطلاع على المرجع الكامل على مستوى الوسائط، راجع numpy.linalg --- إجراءات الجبر الخطي و scipy.linalg --- إجراءات الجبر الخطي.