행렬 곱셈에 사용되는 고도로 최적화된 CBLAS sgemm()을 사용하여 C 네이티브 코드로 구현된 Elixir용 빠른 행렬 조작 라이브러리입니다.
예를 들어, 벡터화된 선형 회귀는 Octave 단일 스레드 구현보다 약 13배 빠릅니다.
또한 메모리 효율성도 뛰어나므로 크기가 약 10억 개의 요소인 대규모 행렬로 작업할 수 있습니다.
https://gitlab.com/sdwolfz/experimental/-/tree/master/exlearn의 매트릭스 코드를 기반으로 합니다.
2015 맥북 프로, 2.2GHz 코어 i7, 16GB 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 맥북 프로, 2.2GHz 코어 i7, 16GB 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에서는 확실히 작동하지만 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
전체 행렬에 대한 최대 요소의 1 기반 인덱스를 계산합니다.
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 )