Elixir 的快速矩阵操作库以 C 本机代码实现,具有用于矩阵乘法的高度优化的 CBLAS sgemm()。
例如,矢量化线性回归比 Octave 单线程实现快约 13 倍。
它的内存效率也很高,因此您可以处理大型矩阵,大约有十亿个元素。
基于 https://gitlab.com/sdwolfz/experimental/-/tree/master/exlearn 的矩阵代码
2015 款 MacBook Pro,2.2 GHz 酷睿 i7,16 GB 内存
对填充随机数的 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 酷睿 i7,16 GB 内存
500×500 矩阵的点积
图书馆 | 操作数/秒 | 与Matrex相比 |
---|---|---|
矩阵 | 674.70 | |
矩阵 | 0.0923 | 7 312.62× 较慢 |
努梅克西 | 0.0173 | 38 906.14× 较慢 |
矩阵 | 0.0129 | 52 327.40× 较慢 |
3×3 矩阵的点积
图书馆 | 操作数/秒 | 与Matrex相比 |
---|---|---|
矩阵 | 3624.36K | |
图数学 | 1310.16K | 慢 2.77 倍 |
矩阵 | 372.58K | 慢 9.73 倍 |
努梅克西 | 89.72K | 慢 40.40 倍 |
矩阵 | 35.76K | 慢 101.35 倍 |
转置 1000x1000 矩阵
图书馆 | 操作数/秒 | 与Matrex相比 |
---|---|---|
矩阵 | 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 上运行,但我们需要 makefile 和安装说明。请贡献一下。
在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 版本,因为它通常要快得多。即,对于 1 000 x 1 000 矩阵, Matrex.sum/1
和Matrex.to_list/1
分别比其Enum
对应项快 438 倍和 41 倍。
您可以使用本机二进制文件格式(超快)和 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 )