تم تنفيذ مكتبة معالجة المصفوفات السريعة لـ Elixir في التعليمات البرمجية الأصلية لـ C باستخدام CBLAS sgemm() المُحسن للغاية المستخدم لضرب المصفوفات.
على سبيل المثال، يكون الانحدار الخطي المتجه أسرع بحوالي 13 مرة من تنفيذ Octave ذو الخيوط الفردية.
كما أنها فعالة من حيث الذاكرة، لذا يمكنك العمل مع مصفوفات كبيرة، يبلغ حجمها حوالي مليار عنصر.
استنادًا إلى رمز المصفوفة من https://gitlab.com/sdwolfz/experimental/-/tree/master/exlearn
ماك بوك برو 2015، معالج كور i7 بتردد 2.2 جيجاهرتز، وذاكرة وصول عشوائي سعة 16 جيجابايت
يتم تنفيذ العمليات على مصفوفات 3000 × 3000 مملوءة بأرقام عشوائية.
يمكنك تشغيل الاختبارات المعيارية من المجلد /bench
باستخدام أوامر python numpy_bench.py
و MIX_ENV=bench mix bench
.
benchmark iterations average time
logistic_cost() 1000 1.23 ms/op
np.divide(A, B) 100 15.43 ms/op
np.add(A, B) 100 14.62 ms/op
sigmoid(A) 50 93.28 ms/op
np.dot(A, B) 10 196.57 ms/op
benchmark iterations average time
logistic_cost() 1000 1.23 ms/op (on par)
divide(A, B) 200 7.32 ms/op (~ 2× faster)
add(A, B) 200 7.71 ms/op (~ 2× faster)
sigmoid(A) 50 71.47 ms/op (23% faster)
dot(A, B) 10 213.31 ms/op (8% slower)
ذبح الأبرياء، في الواقع.
ماك بوك برو 2015، معالج كور i7 بتردد 2.2 جيجاهرتز، وذاكرة وصول عشوائي سعة 16 جيجابايت
المنتج النقطي للمصفوفات 500 × 500
مكتبة | العمليات/ثانية | بالمقارنة مع ماتريكس |
---|---|---|
ماتريكس | 674.70 | |
مصفوفة | 0.0923 | 7312.62× أبطأ |
نومكسي | 0.0173 | 38906.14× أبطأ |
اكس ماتريكس | 0.0129 | 52327.40× أبطأ |
المنتج النقطي للمصفوفات 3 × 3
مكتبة | العمليات/ثانية | بالمقارنة مع ماتريكس |
---|---|---|
ماتريكس | 3624.36 ك | |
غرافماث | 1310.16 ك | أبطأ بمقدار 2.77 مرة |
مصفوفة | 372.58 ك | 9.73x أبطأ |
نومكسي | 89.72 ك | 40.40x أبطأ |
اكس ماتريكس | 35.76 ك | 101.35x أبطأ |
نقل مصفوفة 1000x1000
مكتبة | العمليات/ثانية | بالمقارنة مع ماتريكس |
---|---|---|
ماتريكس | 428.69 | |
اكس ماتريكس | 9.39 | 45.64× أبطأ |
مصفوفة | 8.54 | 50.17× أبطأ |
نومكسي | 6.83 | 62.80× أبطأ |
مثال كامل لمكتبة Matrex في العمل: الانحدار الخطي على أرقام MNIST (دفتر Jupyter)
تطبق Matrex بروتوكول Inspect
وتبدو جميلة في وحدة التحكم الخاصة بك:
يمكنه أيضًا رسم خريطة حرارية للمصفوفة الخاصة بك في وحدة التحكم! فيما يلي رسم متحرك للتدريب على الانحدار اللوجستي باستخدام مكتبة Matrex وبعض الخرائط الحرارية للمصفوفة:
يمكن تثبيت الحزمة عن طريق إضافة matrex
إلى قائمة التبعيات الخاصة بك في mix.exs
:
def deps do
[
{ :matrex , "~> 0.6" }
]
end
كل شيء يعمل خارج الصندوق، وذلك بفضل إطار العمل Accelerate. إذا واجهت خطأ في التجميع
native/src/matrix_dot.c:5:10: fatal error: 'cblas.h' file not found
ثم تأكد من تثبيت أدوات سطر أوامر XCode ( xcode-select --install
). إذا لم يتم حل الخطأ بعد، بالنسبة لنظام MacOS Mojave، قم بتشغيل open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg
لاستعادة /usr/include و/usr/lib.
في نظام التشغيل MacOS 10.15، يمكن حل هذا الخطأ باستخدام
export CPATH=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/
أو مع
C_INCLUDE_PATH=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Accelerate.framework/Frameworks/vecLib.framework/Headers mix compile
تحتاج إلى تثبيت مكتبات علمية لهذه الحزمة لتجميع:
> sudo apt-get install build-essential erlang-dev libatlas-base-dev
سيعمل بالتأكيد على نظام التشغيل Windows، لكننا بحاجة إلى ملف تعريف وتعليمات التثبيت. من فضلك ساهم.
بمساعدة متغير البيئة MATREX_BLAS
يمكنك اختيار مكتبة BLAS التي تريد الارتباط بها. يمكن أن تأخذ القيم blas
(الافتراضية) أو atlas
أو openblas
أو noblas
.
الخيار الأخير يعني أنك تقوم بترجمة كود C دون أي تبعيات خارجية، لذلك، يجب أن تعمل في أي مكان مع مكان مترجم C:
$ mix clean
$ MATREX_BLAS=noblas mix compile
يتم تنفيذ سلوك الوصول جزئيًا لـ Matrex، لذا يمكنك القيام بما يلي:
iex > m = Matrex . magic ( 3 )
#Matrex[3×3]
┌ ┐
│ 8.0 1.0 6.0 │
│ 3.0 5.0 7.0 │
│ 4.0 9.0 2.0 │
└ ┘
iex > m [ 2 ] [ 3 ]
7.0
أو حتى:
iex > m [ 1 .. 2 ]
#Matrex[2×3]
┌ ┐
│ 8.0 1.0 6.0 │
│ 3.0 5.0 7.0 │
└ ┘
هناك أيضًا العديد من الاختصارات للحصول على أبعاد المصفوفة:
iex > m [ :rows ]
3
iex > m [ :size ]
{ 3 , 3 }
حساب القيمة القصوى للمصفوفة بأكملها:
iex > m [ :max ]
9.0
أو أحد صفوفه فقط:
iex > m [ 2 ] [ :max ]
7.0
حساب الفهرس الأحادي للعنصر الأقصى للمصفوفة بأكملها:
iex > m [ :argmax ]
8
و صف :
iex > m [ 2 ] [ :argmax ]
3
تعيد وحدة Matrex.Operators
تعريف عوامل تشغيل العمليات الحسابية Kernel
(+، -، *، / <|>) وتحدد بعض وظائف الراحة، حتى تتمكن من كتابة كود العمليات الحسابية بطريقة أكثر طبيعية.
يجب استخدامه بحذر شديد. نقترح استخدامه فقط داخل وظائف محددة وفقط لزيادة سهولة القراءة، لأن استخدام وظائف وحدة Matrex
، خاصة تلك التي تقوم بعمليتين أو أكثر في مكالمة واحدة، تكون أسرع 2-3 مرات.
def lr_cost_fun_ops ( % Matrex { } = theta , { % Matrex { } = x , % Matrex { } = y , lambda } = _params )
when is_number ( lambda ) do
# Turn off original operators
import Kernel , except: [ -: 1 , +: 2 , -: 2 , *: 2 , /: 2 , <|>: 2 ]
import Matrex.Operators
import Matrex
m = y [ :rows ]
h = sigmoid ( x * theta )
l = ones ( size ( theta ) ) |> set ( 1 , 1 , 0.0 )
j = ( - t ( y ) * log ( h ) - t ( 1 - y ) * log ( 1 - h ) + lambda / 2 * t ( l ) * pow2 ( theta ) ) / m
grad = ( t ( x ) * ( h - y ) + ( theta <|> l ) * lambda ) / m
{ scalar ( j ) , grad }
end
نفس الوظيفة، مشفرة باستدعاءات أساليب الوحدة النمطية (أسرع 2.5 مرة):
def lr_cost_fun ( % Matrex { } = theta , { % Matrex { } = x , % Matrex { } = y , lambda } = _params )
when is_number ( lambda ) do
m = y [ :rows ]
h = Matrex . dot_and_apply ( x , theta , :sigmoid )
l = Matrex . ones ( theta [ :rows ] , theta [ :cols ] ) |> Matrex . set ( 1 , 1 , 0 )
regularization =
Matrex . dot_tn ( l , Matrex . square ( theta ) )
|> Matrex . scalar ( )
|> Kernel . * ( lambda / ( 2 * m ) )
j =
y
|> Matrex . dot_tn ( Matrex . apply ( h , :log ) , - 1 )
|> Matrex . subtract (
Matrex . dot_tn (
Matrex . subtract ( 1 , y ) ,
Matrex . apply ( Matrex . subtract ( 1 , h ) , :log )
)
)
|> Matrex . scalar ( )
|> ( fn
:nan -> :nan
x -> x / m + regularization
end ) . ( )
grad =
x
|> Matrex . dot_tn ( Matrex . subtract ( h , y ) )
|> Matrex . add ( Matrex . multiply ( theta , l ) , 1.0 , lambda )
|> Matrex . divide ( m )
{ j , grad }
end
تقوم Matrex بتنفيذ Enumerable
، لذا فإن جميع أنواع وظائف Enum
قابلة للتطبيق:
iex > Enum . member? ( m , 2.0 )
true
iex > Enum . count ( m )
9
iex > Enum . sum ( m )
45
بالنسبة للوظائف الموجودة في Enum
و Matrex
، يفضل استخدام إصدار Matrex، لأنه عادة ما يكون أسرع بكثير. أي أنه بالنسبة للمصفوفة 1000 × 1000 فإن Matrex.sum/1
و Matrex.to_list/1
أسرع بـ 438 و41 مرة، على التوالي، من نظيرتيهما Enum
.
يمكنك حفظ/تحميل المصفوفة بتنسيق ملف ثنائي أصلي (سريع للغاية) وCSV (بطيء، خاصة في المصفوفات الكبيرة).
تنسيق Matrex CSV متوافق مع مخرجات GNU Octave CSV، لذا يمكنك استخدامه لتبادل البيانات بين نظامين.
iex > Matrex . random ( 5 ) |> Matrex . save ( "rand.mtx" )
:ok
iex > Matrex . load ( "rand.mtx" )
#Matrex[5×5]
┌ ┐
│ 0.05624 0.78819 0.29995 0.25654 0.94082 │
│ 0.50225 0.22923 0.31941 0.3329 0.78058 │
│ 0.81769 0.66448 0.97414 0.08146 0.21654 │
│ 0.33411 0.59648 0.24786 0.27596 0.09082 │
│ 0.18673 0.18699 0.79753 0.08101 0.47516 │
└ ┘
iex > Matrex . magic ( 5 ) |> Matrex . divide ( Matrex . eye ( 5 ) ) |> Matrex . save ( "nan.csv" )
:ok
iex > Matrex . load ( "nan.csv" )
#Matrex[5×5]
┌ ┐
│ 16.0 ∞ ∞ ∞ ∞ │
│ ∞ 4.0 ∞ ∞ ∞ │
│ ∞ ∞ 12.0 ∞ ∞ │
│ ∞ ∞ ∞ 25.0 ∞ │
│ ∞ ∞ ∞ ∞ 8.0 │
└ ┘
يمكن تحميل القيم الخاصة العائمة، مثل :nan
و :inf
داخل المصفوفات، وحفظها في الملفات. ولكن عند إدخالها في Elixir، يتم نقلها إلى ذرات :nan
و :inf
و :neg_inf
، لأن BEAM لا يقبل القيم الخاصة كعوامات صالحة.
iex > m = Matrex . eye ( 3 )
#Matrex[3×3]
┌ ┐
│ 1.0 0.0 0.0 │
│ 0.0 1.0 0.0 │
│ 0.0 0.0 1.0 │
└ ┘
iex > n = Matrex . divide ( m , Matrex . zeros ( 3 ) )
#Matrex[3×3]
┌ ┐
│ ∞ NaN NaN │
│ NaN ∞ NaN │
│ NaN NaN ∞ │
└ ┘
iex > n [ 1 ] [ 1 ]
:inf
iex > n [ 1 ] [ 2 ]
:nan
iex ( 166 ) > matrex_logo =
... ( 166 ) > "../emnist/emnist-letters-test-images-idx3-ubyte"
.. . ( 166 ) > |> Matrex . load ( :idx )
.. . ( 166 ) > |> Access . get ( 9601 .. 10200 )
.. . ( 166 ) > |> Matrex . list_of_rows ( )
.. . ( 166 ) > |> Enum . reduce ( fn x , sum -> add ( x , sum ) end )
.. . ( 166 ) > |> Matrex . reshape ( 28 , 28 )
.. . ( 166 ) > |> Matrex . transpose ( )
.. . ( 166 ) > |> Matrex . resize ( 2 )
.. . ( 166 ) > |> Matrex . heatmap ( :color24bit )