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.