Gonum - это набор числовых библиотек для Go, поддерживающих линейную алгебру, статистику и оптимизацию. Его можно считать в целом аналогом Numpy / Scipy в Python.

Хотя существует подробная документация по пакетам gonum, доступная через GoDoc, существует мало дополнительных материалов, которые помогут новым пользователям начать работу с Gonum. Это особенно сложно, поскольку Gonum представляет собой свободную конфедерацию связанных библиотек, и не всегда ясно, какую из них использовать. Например, матрицы и векторы реализованы в трех разных библиотеках: blas, lapack и mat (который содержит интерфейсы более высокого уровня, построенные поверх blas и lapack), а также в устаревшем пакете mat64, который остается самым популярным в Google для «Gonum». линейная алгебра ».

В этом руководстве основное внимание уделяется базовым операциям линейной алгебры с использованием пакета Gonum mat. Документы для мата доступны здесь, и их стоит прочитать.

Я предполагаю, что у вас уже установлен Go и настроена ваша среда. Чтобы установить gonum, просто откройте свой терминал и введите следующее

go get -u gonum.org/v1/gonum/…

После установки вы можете импортировать гонум следующим образом:

import “gonum.org/v1/gonum/mat”

Типы данных и Гонум

От любого, кто привык работать с Numpy, Pandas или R, вы можете рассчитывать на поддержку различных типов данных в своих матрицах. Однако Gonum / mat - это пакет чистой линейной алгебры, который поддерживает элементы любого типа данных, если они являются float64.

Инициализация вектора

Первое правило инициализации вектора - «вы не можете инициализировать вектор».

Поскольку mat.Vector - это не структура данных, это интерфейс. Как и мат.Матрица. Вы также не можете инициализировать матрицу. Базовым ванильным представлением вектора в GoNum является mat.VecDense, которое удовлетворяет интерфейсу Vector.

На момент публикации этого поста gonum / mat не поддерживает разреженные матрицы, хотя реализация в настоящее время находится в стадии реализации. Вы можете следить за развитием на Github здесь.

// Initialize with the length of the vector, followed by a slice of floats containing the data.
u := mat.NewVecDense(3, []float64{1, 2, 3})
v := mat.NewVecDense(3, []float64{4, 5, 6})

Установка и получение значений

В отличие от массивов Numpy, векторы и матрицы GoNum не поддерживают индексацию с использованием квадратных скобок. Вместо этого значения должны быть получены и назначены с помощью методов установки и получения.

// This doesn’t work
a := u[0]
// Find 1st element of u
a := u.AtVec(1)
// Equivalent getter method for vectors
// The At method may be used on anything that satisfies the Matrix // interface.
a := u.At(1, 0)
// Overwrite 1st element of u with 33.2
u.SetVec(1, 33.2)

Векторная арифметика

Самая нелогичная арифметика в Gonum заключается в том, что все выполняется на месте, без возврата каких-либо значений. Это удобный способ выполнять сложные операции с массивными матрицами с минимальными накладными расходами в памяти, но к нему нужно немного привыкнуть, и вам нужно быть осторожным, чтобы не изменить какую-либо структуру, которая может понадобиться снова как есть.

Так что не делай этого:

// This doesn’t work.
// Arithmetic functions do not return values.
w := AddVec(u, v)

Вместо этого вы можете добавить два вектора следующим образом:

// Leave u and v unaltered and create a new vector, w, to receive result
// Be sure to use the right length or operation will panic
w := mat.NewVecDense(3, nil)
w.AddVec(u, v)
// Or, you can overwrite u with the result of your operation to save space.
u.AddVec(u, v)

Вот компилируемая программа, демонстрирующая некоторые другие общие арифметические операции:

package main
import (
 "fmt"
 "gonum.org/v1/gonum/mat"
)
func main() {
 u := mat.NewVecDense(3, []float64{1, 2, 3})
 println("u: ")
 matPrint(u)
 v := mat.NewVecDense(3, []float64{4, 5, 6})
 println("v: ")
 matPrint(v)
 w := mat.NewVecDense(3, nil)
 w.AddVec(u, v)
 println("u + v: ")
 matPrint(w)
 // Or, you can overwrite u with the result of your operation to
 //save space.
 u.AddVec(u, v)
 println("u (overwritten):")
 matPrint(u)
 // Add u + alpha * v for some scalar alpha
 w.AddScaledVec(u, 2, v)
 println("u + 2 * v: ")
 matPrint(w)
 // Subtract v from u
 w.SubVec(u, v)
 println("v - u: ")
 matPrint(w)
 // Scale u by alpha
 w.ScaleVec(23, u)
 println("u * 23: ")
 matPrint(w)
 // Compute the dot product of u and v
 // Since float64’s don’t have a dot method, this is not done
 //inplace
 d := mat.Dot(u, v)
 println("u dot v: ", d)
 // Find length of v
 l := v.Len()
 println("Length of v: ", l)
// We can also find the length of v in Euclidean space
// The 2 parameter specifices that this is the Frobenius norm
// Rather than the maximum absolute column sum
// This distinction is more important when Norm is applied to
// Matrices since vectors only have one column and the the
// maximum absolute column sum is the Frobenius norm squared.
 println(mat.Norm(v, 2))
}
func matPrint(X mat.Matrix) {
 fa := mat.Formatted(X, mat.Prefix(""), mat.Squeeze())
 fmt.Printf("%v\n", fa)
}

Последнее замечание по работе с VecDense

VecDense удовлетворяет как матричный, так и векторный интерфейс. Если вы решите использовать VecDense в качестве аргумента для функций, которые действуют на матрицу, имейте в виду, что GoNum обрабатывает каждый вектор как столбец, независимо от контекста. Чтобы использовать вектор как строку, вы можете использовать метод T ().

Инициализация матрицы

Самая базовая реализация плотной матрицы, * мат. Dense. Если данные, с которыми вы работаете, принадлежат к симметричной, треугольной или полосовой матрице, вы получите лучшую производительность с * мат. BandDense или * мат. SymDense или * мат. SymBandDense или * мат. TriDense ». Все они соответствуют интерфейсу Matrix и имеют много общих методов.

В этом руководстве будут рассмотрены только основы плотных матриц.

В настоящее время Gonum не поддерживает загрузку данных непосредственно из файлов .csv. Все данные должны быть загружены в память, а затем переданы в матрицу в виде фрагмента с плавающей запятой. Первые два аргумента NewDense определяют количество строк и столбцов соответственно.

v := []float64{1,2,3,4,5,6,7,8,9,10,11,12}
A := mat.NewDense(3, 4, v)

Демонстрация матричной арифметики

package main
import (
 "fmt"
 "gonum.org/v1/gonum/mat"
)
func matPrint(X mat.Matrix) {
 fa := mat.Formatted(X, mat.Prefix(""), mat.Squeeze())
 fmt.Printf("%v\n", fa)
}
func main() {
 v := make([]float64, 12)
 for i := 0; i < 12; i++ {
  v[i] = float64(i)
 }
 // Create a new matrix
 A := mat.NewDense(3, 4, v)
 println("A:")
 matPrint(A)
 // Setting and getting are pretty simple
 a := A.At(0, 2)
 println("A[0, 2]: ", a)
 A.Set(0, 2, -1.5)
 matPrint(A)
 // However, we can also set and get rows and columns
 // Rows and columns are returned as vectors, which can be used for
 // other computations
 println("Row 1 of A:")
 matPrint(A.RowView(1))
 println("Column 0 of A:")
 matPrint(A.ColView(0))
 // Rows and columns may be set,
 // But this is done from slices of floats
 row := []float64{10, 9, 8, 7}
 A.SetRow(0, row)
 matPrint(A)
 col := []float64{3, 2, 1}
 A.SetCol(0, col)
 matPrint(A)
 // Addition and subtraction are identical for Matrices and vectors
 // However, if the dimensions don't match,
 // the function will throw a panic.
 B := mat.NewDense(3, 4, nil)
 B.Add(A, A)
 println("B:")
 matPrint(B)
 C := mat.NewDense(3, 4, nil)
 C.Sub(A, B)
 println("A - B:")
 matPrint(C)
 // We can scale all elements of the matrix by a constant
 C.Scale(3.5, B)
 println("3.5 * B:")
 matPrint(C)
 // Transposing a matrix is a little funky.
 // A.T() is no longer *Dense, but is now a Matrix
 // We can still do most of the same operations with it,
 // but we cannot set any of its values.
 println("A'")
 matPrint(A.T())
 // Multiplication is pretty straightforward
 D := mat.NewDense(3, 3, nil)
 D.Product(A, B.T())
 println("A * B'")
 matPrint(D)
 // We can use Product to multiply as many matrices as we want,
 // provided the receiver has the appropriate dimensions
 // The order of operations is optimized to reduce operations.
 D.Product(D, A, B.T(), D)
 println("D * A * B' * D")
 matPrint(D)
 // We can also apply a function to elements of the matrix.
 // This function must take two integers and a float64,
 // representing the row and column indices and the value in the
 // input matrix.  It must return a float.  See sumOfIndices below.
 C.Apply(sumOfIndices, A)
 println("C:")
 matPrint(C)
 // Once again, we have some functions that return scalar values
 // For example, we can compute the determinant
 E := A.Slice(0, 3, 0, 3)
 d := mat.Det(E)
 println("det(E): ", d)
 // And the trace
 t := mat.Trace(E)
 println("tr(E)", t)
}
func sumOfIndices(i, j int, v float64) float64 {
 return float64(i + j)
}

С пакетом gonum mat можно сделать гораздо больше, но этот пост в блоге становится довольно длинным, и я надеюсь, что это будет подходящее введение в базовую механику этих матриц.

Если есть интерес, я могу написать еще один учебник по собственному материалу и SVD.