Elixir 用の高速行列操作ライブラリは、行列の乗算に使用される高度に最適化された CBLAS sgemm() を備えた C ネイティブ コードで実装されています。
たとえば、ベクトル化された線形回帰は、Octave シングル スレッド実装よりも約 13 倍高速です。
また、メモリ効率も高いため、サイズが約 10 億要素の大きな行列を扱うことができます。
https://gitlab.com/sdwolfz/experimental/-/tree/master/exlearn のマトリックス コードに基づく
2015 MacBook Pro、2.2 GHz Core i7、16 GB RAM
演算は、乱数が詰め込まれた 3000×3000 の行列に対して実行されます。
python numpy_bench.py
およびMIX_ENV=bench mix bench
コマンドを使用して、 /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 MacBook Pro、2.2 GHz Core i7、16 GB RAM
500×500行列の内積
図書館 | オペレーション/秒 | マトレックスとの比較 |
---|---|---|
マトレックス | 674.70 | |
マトリックス | 0.0923 | 7 312.62倍遅い |
ヌメクシー | 0.0173 | 38 906.14× 遅い |
エクスマトリックス | 0.0129 | 52 327.40× 遅い |
3×3行列の内積
図書館 | オペレーション/秒 | マトレックスとの比較 |
---|---|---|
マトレックス | 3624.36K | |
グラフ数学 | 1310.16K | 2.77 倍遅い |
マトリックス | 372.58K | 9.73 倍遅い |
ヌメクシー | 89.72K | 40.40倍遅い |
エクスマトリックス | 35.76K | 101.35倍遅い |
1000x1000 マトリックスの転置
図書館 | オペレーション/秒 | マトレックスとの比較 |
---|---|---|
マトレックス | 428.69 | |
エクスマトリックス | 9.39 | 45.64倍遅い |
マトリックス | 8.54 | 50.17倍遅い |
ヌメクシー | 6.83 | 62.80倍遅い |
動作する Matrex ライブラリの完全な例: MNIST 桁の線形回帰 (Jupyter ノートブック)
Matrex はInspect
プロトコルを実装しており、コンソールで見栄えがよくなります。
コンソールにマトリックスのヒートマップを描画することもできます。以下は、Matrex ライブラリといくつかの行列ヒートマップを使用したロジスティック回帰トレーニングのアニメーションです。
このパッケージは、 mix.exs
の依存関係のリストにmatrex
追加することでインストールできます。
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
またはその行の 1 つだけ:
iex > m [ 2 ] [ :max ]
7.0
行列全体の最大要素の 1 から始まるインデックスを計算します。
iex > m [ :argmax ]
8
そして行:
iex > m [ 2 ] [ :argmax ]
3
Matrex.Operators
モジュールは、 Kernel
数学演算子 (+、-、、/ <|>) を再定義し、いくつかの便利な関数を定義するため、より自然な方法で計算コードを作成できます。
使用には十分な注意が必要です。 Matrex
モジュール関数、特に 1 回の呼び出しで 2 つ以上の操作を実行する関数を使用すると 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 バージョンを使用することをお勧めします。これは、通常、Matrex バージョンの方がはるかに高速であるためです。つまり、1 000 x 1 000 行列の場合、 Matrex.sum/1
とMatrex.to_list/1
は、対応するEnum
よりもそれぞれ 438 倍と 41 倍高速です。
ネイティブ バイナリ ファイル形式 (非常に高速) および CSV (特に大きな行列では低速) を使用して行列を保存/ロードできます。
Matrex CSV 形式は GNU Octave CSV 出力と互換性があるため、2 つのシステム間でデータを交換するために使用できます。
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
などの浮動小数点特殊値は行列内でうまく動作し、ファイルからロードしたり、ファイルに保存したりできます。ただし、BEAM は特別な値を有効な浮動小数点として受け入れないため、それらを Elixir に取り込む場合:nan
、 :inf
、および:neg_inf
アトムに転送されます。
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 )