Schnelle Matrixmanipulationsbibliothek für Elixir, implementiert in nativem C-Code mit hochoptimiertem CBLAS sgemm() für die Matrixmultiplikation.
Beispielsweise ist die vektorisierte lineare Regression etwa 13-mal schneller als die Single-Threaded-Implementierung von Octave.
Es ist außerdem speichereffizient, sodass Sie mit großen Matrizen arbeiten können, die etwa eine Milliarde Elemente groß sind.
Basierend auf Matrixcode von https://gitlab.com/sdwolfz/experimental/-/tree/master/exlearn
2015 MacBook Pro, 2,2 GHz Core i7, 16 GB RAM
Operationen werden an 3000×3000-Matrizen durchgeführt, die mit Zufallszahlen gefüllt sind.
Sie können Benchmarks aus dem Ordner /bench mit python numpy_bench.py und MIX_ENV=bench mix bench ausführen.
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)
Eigentlich ein Abschlachten der Unschuldigen.
2015 MacBook Pro, 2,2 GHz Core i7, 16 GB RAM
Skalarprodukt von 500×500-Matrizen
| Bibliothek | Operationen/Sek | Im Vergleich zu Matrex |
|---|---|---|
| Matrex | 674,70 | |
| Matrix | 0,0923 | 7 312,62× langsamer |
| Numexy | 0,0173 | 38 906,14× langsamer |
| ExMatrix | 0,0129 | 52 327,40× langsamer |
Skalarprodukt von 3×3-Matrizen
| Bibliothek | Operationen/Sek | Im Vergleich zu Matrex |
|---|---|---|
| Matrex | 3624,36 K | |
| GraphMath | 1310,16 K | 2,77x langsamer |
| Matrix | 372,58 K | 9,73x langsamer |
| Numexy | 89,72 K | 40,40x langsamer |
| ExMatrix | 35,76 K | 101,35x langsamer |
Transponierende 1000x1000-Matrix
| Bibliothek | Operationen/Sek | Im Vergleich zu Matrex |
|---|---|---|
| Matrex | 428,69 | |
| ExMatrix | 9.39 | 45,64× langsamer |
| Matrix | 8.54 | 50,17× langsamer |
| Numexy | 6,83 | 62,80× langsamer |
Vollständiges Beispiel der Matrex-Bibliothek bei der Arbeit: Lineare Regression auf MNIST-Ziffern (Jupyter-Notizbuch)
Matrex implementiert Inspect -Protokoll und sieht in Ihrer Konsole gut aus:
Es kann sogar eine Heatmap Ihrer Matrix in der Konsole zeichnen! Hier ist eine Animation des logistischen Regressionstrainings mit der Matrex-Bibliothek und einigen Matrix-Heatmaps:
Das Paket kann installiert werden, indem Sie matrex zu Ihrer Abhängigkeitsliste in mix.exs hinzufügen:
def deps do
[
{ :matrex , "~> 0.6" }
]
endDank des Accelerate-Frameworks funktioniert alles sofort. Wenn ein Kompilierungsfehler auftritt
native/src/matrix_dot.c:5:10: fatal error: 'cblas.h' file not found
Stellen Sie dann sicher, dass die XCode-Befehlszeilentools installiert sind ( xcode-select --install ). Wenn der Fehler immer noch nicht behoben ist, führen Sie für MacOS Mojave open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg aus, um /usr/include und /usr/lib wiederherzustellen.
Unter MacOS 10.15 kann dieser Fehler mit behoben werden
export CPATH=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/
oder mit
C_INCLUDE_PATH=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Accelerate.framework/Frameworks/vecLib.framework/Headers mix compile
Sie müssen wissenschaftliche Bibliotheken installieren, damit dieses Paket kompiliert werden kann:
> sudo apt-get install build-essential erlang-dev libatlas-base-devEs wird auf jeden Fall unter Windows funktionieren, wir benötigen jedoch ein Makefile und eine Installationsanleitung. Bitte tragen Sie bei.
Mithilfe der Umgebungsvariablen MATREX_BLAS können Sie auswählen, mit welcher BLAS-Bibliothek eine Verknüpfung hergestellt werden soll. Es kann die Werte blas (Standard), atlas , openblas oder noblas annehmen.
Die letzte Option bedeutet, dass Sie C-Code ohne externe Abhängigkeiten kompilieren, sodass er überall mit einem C-Compiler-Ort funktionieren sollte:
$ mix clean
$ MATREX_BLAS=noblas mix compileDas Zugriffsverhalten ist teilweise für Matrex implementiert, sodass Sie Folgendes tun können:
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.0Oder auch:
iex > m [ 1 .. 2 ]
#Matrex[2×3]
┌ ┐
│ 8.0 1.0 6.0 │
│ 3.0 5.0 7.0 │
└ ┘Es gibt auch mehrere Abkürzungen zum Abrufen der Matrixdimensionen:
iex > m [ :rows ]
3
iex > m [ :size ]
{ 3 , 3 }Berechnung des Maximalwerts der gesamten Matrix:
iex > m [ :max ]
9.0oder nur eine seiner Zeilen:
iex > m [ 2 ] [ :max ]
7.0Berechnen des einsbasierten Index des maximalen Elements für die gesamte Matrix:
iex > m [ :argmax ]
8und eine Zeile:
iex > m [ 2 ] [ :argmax ]
3 Matrex.Operators -Modul definiert die mathematischen Kernel -Operatoren (+, -, *, / <|>) neu und definiert einige praktische Funktionen, sodass Sie Berechnungscode auf natürlichere Weise schreiben können.
Es sollte mit großer Vorsicht verwendet werden. Wir empfehlen, es nur innerhalb bestimmter Funktionen und nur zur besseren Lesbarkeit zu verwenden, da die Verwendung von Matrex -Modulfunktionen, insbesondere solchen, die zwei oder mehr Vorgänge bei einem Aufruf ausführen, zwei bis drei Mal schneller ist.
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 }
endDieselbe Funktion, codiert mit Modulmethodenaufrufen (2,5-mal schneller):
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 implementiert Enumerable , sodass alle Arten von Enum -Funktionen anwendbar sind:
iex > Enum . member? ( m , 2.0 )
true
iex > Enum . count ( m )
9
iex > Enum . sum ( m )
45 Für Funktionen, die sowohl in Enum als auch in Matrex vorhanden sind, wird die Verwendung der Matrex-Version bevorzugt, da diese normalerweise viel, viel schneller ist. Das heißt, für eine 1.000 x 1.000-Matrix sind Matrex.sum/1 und Matrex.to_list/1 438 bzw. 41 Mal schneller als ihre Enum Gegenstücke.
Sie können die Matrix im nativen Binärdateiformat (besonders schnell) und im CSV-Format (langsam, insbesondere bei großen Matrizen) speichern/laden.
Das Matrex CSV-Format ist mit der GNU Octave CSV-Ausgabe kompatibel, sodass Sie es zum Datenaustausch zwischen zwei Systemen verwenden können.
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 │
└ ┘ Float-Sonderwerte wie :nan und :inf leben gut in Matrizen und können aus Dateien geladen und in Dateien gespeichert werden. Aber wenn sie in Elixir übernommen werden, werden sie in :nan , :inf und :neg_inf -Atome übertragen, da BEAM keine Sonderwerte als gültige Floats akzeptiert.
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 )