Библиотека быстрого манипулирования матрицами для Elixir, реализованная в собственном коде C с высокооптимизированной функцией CBLAS sgemm(), используемой для умножения матриц.
Например, векторизованная линейная регрессия примерно в 13 раз быстрее, чем однопоточная реализация Octave.
Он также эффективно использует память, поэтому вы можете работать с большими матрицами, размером около миллиарда элементов.
На основе матричного кода https://gitlab.com/sdwolfz/experimental/-/tree/master/exlearn.
MacBook Pro 2015 г., Core 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)
Фактически убийство невинных.
MacBook Pro 2015 г., Core i7 2,2 ГГц, 16 ГБ ОЗУ
Скалярное произведение матриц 500×500
Библиотека | Операций/сек | По сравнению с Матрексом |
---|---|---|
Матрекс | 674,70 | |
Матрица | 0,0923 | 7 312,62× медленнее |
Нумексия | 0,0173 | 38 906,14× медленнее |
ЭксМатрикс | 0,0129 | 52 327,40× медленнее |
Скалярное произведение матриц 3×3
Библиотека | Операций/сек | По сравнению с Матрексом |
---|---|---|
Матрекс | 3624,36 К | |
ГрафМатематика | 1310,16 К | в 2,77 раза медленнее |
Матрица | 372,58 К | в 9,73 раза медленнее |
Нумексия | 89,72 К | в 40,40 раз медленнее |
ЭксМатрикс | 35,76 К | в 101,35 раза медленнее |
Транспонирование матрицы 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 он точно будет работать, но нам понадобится make-файл и инструкция по установке. Пожалуйста, внесите свой вклад.
С помощью переменной среды 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
работают в 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 )