Для реализации множественной линейной регрессии потребовалось поработать с матрицами — а именно, научиться их перемножать, транспонировать и инвертировать. Поскольку ничего такого раньше делать не умел, решил самостоятельно реализовать это в библиотеке машинного обучения, которую сейчас пишу (такой вот способ изучения машинного обучения с нуля придумал для себя).
Библиотека находится на github ВОТ ТУТ. Пока что всё в ней написано на c#. Может быть, еще добавится что-то функциональное — но никак не могу выбрать между F# и всем остальным… По уму-то — нужен F#, так как всё же это у меня .NET. Но вот F#, кажется, сейчас уже никому не нужен, поэтому не знаю, какой язык осваивать.
Итак, создаем матрицу. Конструктор принимает двухмерный массив double — собственно, саму матрицу:
double[,] a = new double[3, 3]; sqvArr[0, 0] = -1; sqvArr[0, 1] = -2; sqvArr[0, 2] = 2; sqvArr[1, 0] = 2; sqvArr[1, 1] = 1; sqvArr[1, 2] = 1; sqvArr[2, 0] = 3; sqvArr[2, 1] = 4; sqvArr[2, 2] = 5; Matrix matrixA = new Matrix(a);
Также матрицу можно загрузить из файла, указав в методе разделить для столбцов в файле и путь к файлу:
Matrix mFromFile = Matrix.GetMatrixFromTXT("data\\regress_data.txt", '\t');
Если нужно взять часть матрицы (допустим, взять только независимые переменные, или только зависимую (ые), или часть строк), то используется метод, который принимает 2 массива целочисленных значений. 1-ый аргумент — индексы строк, второй — индексы столбцов. В примере ниже берем все строки:
int[] rows = Enumerable.Range(0, mFromFile.matrixBase.GetLength(0))
.Select(i => i)
.ToArray();
Matrix bVector = mlr.GetBCoefficientsForMatrix(mFromFile.GetMatrixPart(rows, new int[] { 1, 2, 3 }),
mFromFile.GetMatrixPart(rows, new int[] { 0 }));
Для сложения матриц используется статистический метод SumMatrices, принимающий 2 аргумента — суммируемые матрицы и возвращающий их сумму (матрицу, само собой):
Matrix c = Matrix.SumMatrices(matrixA, matrixB);
Вычитаются аналогично:
Matrix c = Matrix.SubstractMatrices(matrixA, matrixB);
Умножение матриц:
Matrix c = Matrix.MultiplyMatrices(matrixA, matrixB);
Умножение матрицы на скаляр уже сделано не статичным:
Matrix c = matrixA.MultiplyToScalar(5);
Транспонирование матрицы:
matrixA = matrixA.Transpose();
Детерминант матрицы:
double determinant = matrixA.GetDeterminant();
Инверсия матрицы:
Matrix invertedSkv = matrixSkv.Invert();
Еще есть переопределенный ToString(). Собственно, если столбцов немного — смотрится нормально.
Самое сложное было реализовать детерминант и инверсию. Вот реализация GetDeterminant():
public double GetDeterminant()
{
if (this.matrixBase.GetLength(0) != this.matrixBase.GetLength(1))
throw new InvalidOperationException("Matrices should be squared");
//if it is 2x2 matrix
if(this.matrixBase.GetLength(0) == 2)
{
return this.matrixBase[0, 0] * this.matrixBase[1, 1]
- this.matrixBase[0, 1] * this.matrixBase[1, 0];
}
//new matrix with new columns (we need the same number of columns - 1) for the same values
//from beginning of matrix
double[,] extendedMatrixBase = new double[this.matrixBase.GetLength(0),
this.matrixBase.GetLength(1) + this.matrixBase.GetLength(1) - 1];
//filling part of new matrix with the same values as in this object matrix
for(int i = 0; i < this.matrixBase.GetLength(0); i++)
{
for(int j = 0; j < this.matrixBase.GetLength(1); j++)
{
extendedMatrixBase[i, j] = this.matrixBase[i, j];
}
}
//filling new columns with values the same as at beginning of matrix
for(int i = 0; i < this.matrixBase.GetLength(0); i++)
{
for(int j = this.matrixBase.GetLength(1); j < extendedMatrixBase.GetLength(1); j++)
{
extendedMatrixBase[i, j] = this.matrixBase[i, j - this.matrixBase.GetLength(0)];
}
}
Matrix extendedMatrix = new Matrix(extendedMatrixBase);
//calculating determinant
double determinant = 0.0;
//summing
double sumsResult = 0.0;
for(int i = 0; i < this.matrixBase.GetLength(0); i++)
{
int row = 0;
int column = i;
double diagonalResult = 1.0;
for (int j = 0; j < this.matrixBase.GetLength(0); j++, row++, column++)
{
diagonalResult *= extendedMatrix.matrixBase[row,column];
}
sumsResult += diagonalResult;
}
//substracting
double substractionsResult = 0.0;
for (int i = 0; i < this.matrixBase.GetLength(0); i++)
{
int row = 0;
int column = extendedMatrix.matrixBase.GetLength(1) - 1 - i;
double diagonalResult = 1.0;
for(int j = 0; j < this.matrixBase.GetLength(0); j++, row++, column--)
{
diagonalResult *= extendedMatrix.matrixBase[row, column];
}
substractionsResult += diagonalResult;
}
determinant = sumsResult - substractionsResult;
return determinant;
}
И реализация инверсии:
public Matrix Invert()
{
Matrix invertedMatrix;
if (this.isInvertable())
{
//Getting matrix of minors
Matrix matrixOfMinors = new Matrix(new double[matrixBase.GetLength(0), matrixBase.GetLength(1)]);
for (int row = 0; row < matrixBase.GetLength(0); row++)
{
for(int column= 0; column < matrixBase.GetLength(1); column++)
{
double[,] subMArrBase = new double[matrixBase.GetLength(0)-1,matrixBase.GetLength(1)-1];
Matrix subMatrix = new Matrix(subMArrBase);
int subMRow = 0;
for(int r = 0; r < matrixBase.GetLength(0); r++)
{
if (row == r)
continue;
int subMColumn = 0;
for (int c = 0; c < matrixBase.GetLength(1); c++)
{
if (column == c)
continue;
subMArrBase[subMRow, subMColumn] = this.matrixBase[r, c];
subMColumn++;
}
subMRow++;
}
matrixOfMinors.matrixBase[row, column] = subMatrix.GetDeterminant();
}
}
//creating cofactor matrix
int rowStartMultiplier;
for(int i = 0; i < matrixOfMinors.matrixBase.GetLength(0); i++)
{
if (i % 2 == 0)
rowStartMultiplier = 1;
else
rowStartMultiplier = -1;
for(int j = 0; j < matrixOfMinors.matrixBase.GetLength(1); j++)
{
matrixOfMinors.matrixBase[i, j] *= rowStartMultiplier;
//swap
rowStartMultiplier *= -1;
}
}
//determinant of THIS matrix
double determinant = this.GetDeterminant();
Matrix transposed = matrixOfMinors.Transpose();
invertedMatrix = transposed.MultiplyToScalar(1 / determinant);
}
else
throw new InvalidOperationException("Matrix is not invertable");
return invertedMatrix;
}
В следующем посте покажу, как написал множественную линейную регрессию.