Реализация Метода Главного Компонента В C#

Всем привет. Эта неделя в курсе машинное обучение Профессор Эндрю Нг рассказал слушателям о метод главных компонент , который можно использовать для уменьшения размерности пространства признаков ваших данных.

Но к сожалению, он не рассказал о методе вычисления собственных векторов и собственных значений матрицы, просто сказал, что это сложно и посоветовал использовать функцию Matlab/octave [U S V] = svd(a).

Для моего проекта мне нужна была реализация этого метода на C#, чем я и занимался сегодня.

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

Проблема с вычислением собственных векторов матрицы состоит в том, что нет быстрый способ вычислить их точные значения, так что вам придется уйти.

Я хочу рассказать об одном из таких способов выхода, а также приведу C#-код, выполняющий эту процедуру.

Пожалуйста под кат. Метод главных компонентов (кода пока нет) Часть первая: инициализация алгоритма.

Входными данными алгоритма является массив данных, а также размерность пространства, до которого необходимо свести данные.

  1. Рассчитано ковариационная матрица (у этой матрицы есть замечательное свойство — она симметрична, это нам очень пригодится)
  2. Собственные векторы матрицы вычисляются
  3. Выбираются первые собственные векторы en, где en — та самая размерность, до которой необходимо привести размерность пространства признаков; выбранные векторы можно записать вертикально и собрать в матрицу

    Реализация метода главного компонента в C#

Часть вторая: уменьшение размерности входного вектора.

Входные данные — вектор тех же размеров, что и массив данных, используемый на этапе инициализации; выходной сигнал представляет собой вектор меньшей размерности (или проекцию входного вектора на ортонормированный базис, образованный выбором собственных векторов).

  • Умножив входной вектор скалярно на все векторы из выборки собственных векторов, получим уменьшился вектор:

    Реализация метода главного компонента в C#

Часть третья: восстановление размерности вектора (разумеется, с потерей информации).

Входными данными является вектор размерности, равный тому, которому мы уменьшенный векторы; Выходные данные представляют собой вектор исходного измерения.

  • Если мы транспонируем матрицу

    Реализация метода главного компонента в C#

    тогда он будет содержать то же количество строк, что и размерность входного вектора на предыдущем шаге, а искомый вектор восстанавливается по формуле:

    Реализация метода главного компонента в C#

Как видите, вся вычислительная сложность заключается в этапе инициализации при вычислении ковариационной матрицы и собственных векторов.

Я не буду подробно останавливаться на расчете ковариационной матрицы, поскольку она по определению рассчитывается наивно.

Изучив несколько алгоритмов вычисления собственных векторов матрицы, я остановился на итерационном методе QR-декомпозиции.

Имеет существенное ограничение, более-менее точный результат выдает только для симметричных матриц, но нам повезло :), ковариационная матрица именно такая.

Второе ограничение этого алгоритма состоит в том, что матрица должна быть полноранговый .

QR-разложение Итерационный QR-метод для поиска собственных векторов, очевидно, использует QR-разложение , поэтому сначала вам придется реализовать этот процесс.

Для реализации этого процесса был выбран алгоритм Грэм-Шмидт .

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

Реализация метода главного компонента в C#

, где <> - обозначаем скалярное произведение векторов.

  
  
  
  
  
  
  
  
  
  
  
  
  
   

public static double[] VectorProjection(double[] a, double[] b) { double k = ScalarVectorProduct(a, b)/ScalarVectorProduct(b, b); return ScalarToVectorProduct(k, b); }

Я не буду останавливаться на еще более простых функциях, таких как ScalarVectorProduct, чтобы размер кода в тексте не превышал критическую норму.

Итак, сама процедура разложения QR IList РазложениеGS(double[][] a) получает на вход матрицу и выводит в ответ две матрицы:

  1. первый содержит в своих столбцах ортонормированный базис такой, что

    Реализация метода главного компонента в C#

    ;
  2. вторая матрица будет верхний треугольный .

Первым делом разбиваем матрицу на столбцы и записываем их в список средний :

List<double[]> av = new List<double[]>(); foreach (double[] vector in DecomposeMatrixToColumnVectors(a)) { av.Add(vector); }

Инициализируем два вспомогательных списка:

List<double[]> u = new List<double[]>(); u.Add(av[0]); List<double[]> e = new List<double[]>(); e.Add(ScalarToVectorProduct(1 / NormOfVector(u[0]), u[0]));

Это те же одноименные списки, которые используются в схеме алгоритма:

Реализация метода главного компонента в C#

После инициализации первый ты И е мы рассчитали, мы продолжаем цикл для расчета следующих значений:

for (int i = 1; i < a.Length; i++) { double[] projAcc = new double[a.Length]; for (int j = 0; j < projAcc.Length; j++) { projAcc[j] = 0; } for (int j = 0; j < i; j++) { double[] proj = VectorProjection(av[i], e[j]); for (int k = 0; k < projAcc.Length; k++) { projAcc[k] += proj[k]; } } double[] ui = new double[a.Length]; for (int j = 0; j < ui.Length; j++) { ui[j] = a[j][i] - projAcc[j]; } u.Add(ui); e.Add(ScalarToVectorProduct(1/NormOfVector(u[i]), u[i])); }

И, наконец, мы генерируем данные в выходном формате:

double[][] q = new double[a.Length][]; for (int i = 0; i < q.Length; i++) { q[i] = new double[a.Length]; for (int j = 0; j < q[i].

Length; j++) { q[i][j] = e[j][i]; } } double[][] r = new double[a.Length][]; for (int i = 0; i < r.Length; i++) { r[i] = new double[a.Length]; for (int j = 0; j < r[i].

Length; j++) { if (i >= j) { r[i][j] = ScalarVectorProduct(e[j], av[i]); } else { r[i][j] = 0; } } } r = Transpose(r); List<double[][]> res = new List<double[][]>(); res.Add(q); res.Add(r); return res;

Ура! Теперь нам нужен тест для этого:

[Test(Description = "Test of QRDecompositionGS")] public void QRDecompositionGSTest() { double[][] a = new double[][] { new double[] {12, -51, 4}, new double[] {6, 167, -68}, new double[] {-4, 24, -41} }; IList<double[][]> res = LinearAlgebra.QRDecompositionGS(a); double[][] expQ = new double[][] { new double[] {6.0/7, -69.0/175, -58.0/175}, new double[] {3.0/7, 158.0/175, 6.0/175}, new double[] {-2.0/7, 6.0/35, -33.0/35} }; double[][] expR = new double[][] { new double[] {14, 21, -14}, new double[] {0, 175, -70}, new double[] {0, 0, 35} }; Assert.True(Helper.AreEqualMatrices(expQ, res[0], 0.0001), "expQ != Q"); Assert.True(Helper.AreEqualMatrices(expR, res[1], 0.0001), "expR != R"); }

Функция Helper.AreEqualMatrices сравнивает матрицы поэлементно до третьего параметра.

Итерационный метод QR Итерационный метод QR основан на замечательной теореме, суть которой состоит в том, что если мы инициализируем матрицу A нулем как

Реализация метода главного компонента в C#

и повторяйте следующий процесс бесконечно:



  1. Реализация метода главного компонента в C#



  2. Реализация метода главного компонента в C#

а затем умножить все полученные вопрос , то результатом будет матрица, столбцы которой будут содержать собственные векторы исходной матрицы, значения которой будут тем точнее, чем дольше длится процесс.

Другими словами, поскольку число итераций стремится к бесконечности, произведение

Реализация метода главного компонента в C#

будет стремиться к точным значениям собственных векторов.

В то же время последний

Реализация метода главного компонента в C#

будет содержать на главной диагонали собственные значения матрицы, приближенные конечно.

Напомню, что этот алгоритм более-менее точно работает только для симметричных матриц.

Итак, метод QR-итерации IList EigenVectorValuesExtractionQRIterative(double[][] a, двойная точность, int maxIterations) получает входные данные от самой матрицы, а также еще несколько параметров:

  • двойная точность — точность, которую мы хотим достичь, алгоритм остановится, если изменения значений собственных векторов будут не больше этой величины;
  • int maxIterations — максимальное количество итераций.

На выходе получаем две матрицы:
  1. первый содержит в своих столбцах собственные векторы матрицы а ;
  2. вторая матрица на своей главной диагонали содержит собственные значения матрицы а .

Итак приступим к написанию алгоритма, сначала создадим матрицы, которые будут содержать собственные векторы и собственные значения исходной матрицы:

double[][] aItr = a; double[][] q = null;

И давайте запускать цикл до тех пор, пока алгоритм не остановится:

for (int i = 0; i < maxIterations; i++) { IList<double[][]> qr = QRDecompositionGS(aItr); aItr = MatricesProduct(qr[1], qr[0]); if (q == null) { q = qr[0]; } else { double[][] qNew = MatricesProduct(q, qr[0]); bool accuracyAcheived = true; for (int n = 0; n < q.Length; n++) { for (int m = 0; m < q[n].

Length; m++) { if (Math.Abs(Math.Abs(qNew[n][m]) - Math.Abs(q[n][m])) > accuracy) { accuracyAcheived = false; break; } } if (!accuracyAcheived) { break; } } q = qNew; if (accuracyAcheived) { break; } } }

Сгенерируем выходные данные:

List<double[][]> res = new List<double[][]>(); res.Add(q); res.Add(aItr); return res;

И конечно тест:

[Test(Description = "Test of Eigen vectors extraction")] public void EigenVectorExtraction() { double[][] a = new double[][] { new double[] {1, 2, 4}, new double[] {2, 9, 8}, new double[] {4, 8, 2} }; IList<double[][]> ev = LinearAlgebra.EigenVectorValuesExtractionQRIterative(a, 0.001, 1000); double expEV00 = 15.2964; double expEV11 = 4.3487; double expEV22 = 1.0523; Assert.AreEqual(expEV00, Math.Round(Math.Abs(ev[1][0][0]), 4)); Assert.AreEqual(expEV11, Math.Round(Math.Abs(ev[1][1][1]), 4)); Assert.AreEqual(expEV22, Math.Round(Math.Abs(ev[1][2][2]), 4)); }

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

Реализация метода главных компонент Теперь все готово для реализации темы статьи.

Скрытым параметром модели (класса) будет определенное подмножество собственных векторов ковариационной матрицы из исходных данных:

private IList<double[]> _eigenVectors = null;

Конструктор принимает на вход массив данных и размерность, до которой должно быть уменьшено пространство признаков, а также параметры для QR-декомпозиции.

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

В общем, есть способ выбрать оптимальное количество собственных векторов для желаемого параметра потери информации, т. е.

до какого измерения можно уменьшить пространство, не потеряв более фиксированного процента информации, но я опускаю этот шаг; послушать его можно в лекции Эндрю Нга на сайте курса.



internal DimensionalityReductionPCA(IList<double[]> dataSet, double accuracyQR, int maxIterationQR, int componentsNumber) { double[][] cov = BasicStatFunctions.CovarianceMatrixOfData(dataSet); IList<double[][]> eigen = LinearAlgebra.EigenVectorValuesExtractionQRIterative(cov, accuracyQR, maxIterationQR); IList<double[]> eigenVectors = LinearAlgebra.DecomposeMatrixToColumnVectors(eigen[0]); if (componentsNumber > eigenVectors.Count) { throw new ArgumentException("componentsNumber > eigenVectors.Count"); } _eigenVectors = new List<double[]>(); for (int i = 0; i < componentsNumber; i++) { _eigenVectors.Add(eigenVectors[i]); } }

Затем реализуем прямое и обратное преобразование по формулам из начала статьи.



public double[] Transform(double[] dataItem) { if (_eigenVectors[0].

Length != dataItem.Length) { throw new ArgumentException("_eigenVectors[0].

Length != dataItem.Length"); } double[] res = new double[_eigenVectors.Count]; for (int i = 0; i < _eigenVectors.Count; i++) { res[i] = 0; for (int j = 0; j < dataItem.Length; j++) { res[i] += _eigenVectors[i][j]*dataItem[j]; } } return res; } public double[] Reconstruct(double[] transformedDataItem) { if (_eigenVectors.Count != transformedDataItem.Length) { throw new ArgumentException("_eigenVectors.Count != transformedDataItem.Length"); } double[] res = new double[_eigenVectors[0].

Length]; for (int i = 0; i < res.Length; i++) { res[i] = 0; for (int j = 0; j < _eigenVectors.Count; j++) { res[i] += _eigenVectors[j][i]*transformedDataItem[j]; } } return res; }

Тестирование Для тестирования придумаем небольшой массив данных и, проверив значения на Matlab (используя код из нашего домашнего задания PCA =), напишем класс для тестирования:

[TestFixture(Description = "Test of DimensionalityReductionPCA")] public class DimensionalityReductionPCATest { private IList<double[]> _data = null; private IDataTransformation<double[]> _transformation = null; private double[] _v = null; [SetUp] public void SetUp() { _v = new double[] { 1, 0, 3 }; _data = new List<double[]>() { new double[] {1, 2, 23}, new double[] {-3, 17, 5}, new double[] {13, -6, 7}, new double[] {7, 8, -9} }; _transformation = new DimensionalityReductionPCA(_data, 0.0001, 1000, 2); } [Test(Description = "Test of DimensionalityReductionPCA transform")] public void DimensionalityReductionPCATransformTest() { double[] reduced = _transformation.Transform(_v); double[] expReduced = new double[] {-2.75008, 0.19959}; Assert.IsTrue(Helper.AreEqualVectors(expReduced, reduced, 0.001)); double[] reconstructed = _transformation.Reconstruct(reduced); double[] expReconstructed = new double[] {-0.21218, -0.87852, 2.60499}; Assert.IsTrue(Helper.AreEqualVectors(expReconstructed, reconstructed, 0.001)); }

Ссылки

Теги: #анализ главных компонентов #pca #C++ #Интеллектуальный анализ данных #.

NET #машинное обучение #Машинное обучение #.

NET #Интеллектуальный анализ данных #Алгоритмы

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.