mirror of
https://github.com/hanzoai/jube-fork.git
synced 2026-10-30 18:33:33 +00:00
Included open-source Accord.net library code natively to the solution given the deprecated state of that project. Accord.net will be supported inside Jube for own needs (such as upgrading to .Net 7+). Accord.net libraries modified to compile to .Net 6. Modified Accord.net libraries to better support Newtonsoft Json.net serialisation. Included annotations in the Accord.net libraries to house important dependency statistics. Serialised json can be used independently of Jube to recall a Neural Network. Removal of Binary Serializer totally as a precursor to the projects support of .Net 7+. Extensive bug fixing in crucial functions of Neural Network training and recall. While this part of the system remains in Alpha, it is substantially more fit for purpose.
1085 lines
39 KiB
C#
1085 lines
39 KiB
C#
// Accord Statistics Library
|
|
// The Accord.NET Framework
|
|
// http://accord-framework.net
|
|
//
|
|
// Copyright © César Souza, 2009-2017
|
|
// cesarsouza at gmail.com
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
//
|
|
|
|
namespace Accord.Statistics.Analysis
|
|
{
|
|
using System;
|
|
using System.Collections.ObjectModel;
|
|
using Accord.Math;
|
|
using Accord.Math.Decompositions;
|
|
using Accord.Statistics.Analysis.ContrastFunctions;
|
|
using Accord.MachineLearning;
|
|
using Accord.Statistics.Models.Regression.Linear;
|
|
using Accord.Compat;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
/// <summary>
|
|
/// FastICA's algorithms to be used in Independent Component Analysis.
|
|
/// </summary>
|
|
///
|
|
public enum IndependentComponentAlgorithm
|
|
{
|
|
/// <summary>
|
|
/// Deflation algorithm.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// In the deflation algorithm, components are found one
|
|
/// at a time through a series of sequential operations.
|
|
/// It is particularly useful when only a small number of
|
|
/// components should be computed from the input data set.
|
|
/// </remarks>
|
|
///
|
|
Deflation,
|
|
|
|
/// <summary>
|
|
/// Symmetric parallel algorithm (default).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// In the parallel (symmetric) algorithm, all components
|
|
/// are computed at once. This is the default algorithm for
|
|
/// <seealso cref="IndependentComponentAnalysis">Independent
|
|
/// Component Analysis</seealso>.
|
|
/// </remarks>
|
|
///
|
|
Parallel,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Independent Component Analysis (ICA).
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Independent Component Analysis is a computational method for separating
|
|
/// a multivariate signal (or mixture) into its additive subcomponents, supposing
|
|
/// the mutual statistical independence of the non-Gaussian source signals.</para>
|
|
/// <para>
|
|
/// When the independence assumption is correct, blind ICA separation of a mixed
|
|
/// signal gives very good results. It is also used for signals that are not supposed
|
|
/// to be generated by a mixing for analysis purposes.</para>
|
|
/// <para>
|
|
/// A simple application of ICA is the "cocktail party problem", where the underlying
|
|
/// speech signals are separated from a sample data consisting of people talking
|
|
/// simultaneously in a room. Usually the problem is simplified by assuming no time
|
|
/// delays or echoes.</para>
|
|
/// <para>
|
|
/// An important note to consider is that if N sources are present, at least N
|
|
/// observations (e.g. microphones) are needed to get the original signals.</para>
|
|
///
|
|
/// <para>
|
|
/// References:
|
|
/// <list type="bullet">
|
|
/// <item><description>
|
|
/// Hyvärinen, A (1999). Fast and Robust Fixed-Point Algorithms for Independent Component
|
|
/// Analysis. IEEE Transactions on Neural Networks, 10(3),626-634. Available on:
|
|
/// <a href="http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.50.4731">
|
|
/// http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.50.4731 </a></description></item>
|
|
/// <item><description>
|
|
/// E. Bingham and A. Hyvärinen A fast fixed-point algorithm for independent component
|
|
/// analysis of complex-valued signals. Int. J. of Neural Systems, 10(1):1-8, 2000. </description></item>
|
|
/// <item><description>
|
|
/// FastICA: FastICA Algorithms to perform ICA and Projection Pursuit. Available on:
|
|
/// <a href="http://cran.r-project.org/web/packages/fastICA/index.html">
|
|
/// http://cran.r-project.org/web/packages/fastICA/index.html </a></description></item>
|
|
/// <item><description>
|
|
/// Wikipedia, The Free Encyclopedia. Independent component analysis. Available on:
|
|
/// http://en.wikipedia.org/wiki/Independent_component_analysis </description></item>
|
|
/// </list></para>
|
|
/// </remarks>
|
|
///
|
|
/// <example>
|
|
/// <code source="Unit Tests\Accord.Tests.Statistics\Analysis\IndependentComponentAnalysisTest.cs" region="doc_learn" />
|
|
/// </example>
|
|
///
|
|
[Serializable]
|
|
#pragma warning disable 612, 618
|
|
public class IndependentComponentAnalysis : MultipleTransformBase<double[], double>,
|
|
IMultivariateAnalysis, IParallel,
|
|
IUnsupervisedLearning<MultivariateLinearRegression, double[], double[]>
|
|
#pragma warning restore 612, 618
|
|
{
|
|
private double[,] sourceMatrix;
|
|
|
|
|
|
private double[][] whiteningMatrix; // pre-whitening matrix
|
|
private double[][] mixingMatrix; // estimated mixing matrix
|
|
private double[][] revertMatrix; // estimated de-mixing matrix
|
|
private double[,] resultMatrix;
|
|
|
|
private AnalysisMethod analysisMethod = AnalysisMethod.Center;
|
|
private bool overwriteSourceMatrix;
|
|
|
|
private double[] columnMeans;
|
|
private double[] columnStdDev;
|
|
|
|
private int maxIterations = 100;
|
|
private double tolerance = 1e-3;
|
|
|
|
private IndependentComponentAlgorithm algorithm;
|
|
private IContrastFunction contrast = new Logcosh();
|
|
|
|
private IndependentComponentCollection componentCollection;
|
|
|
|
[NonSerialized]
|
|
private ParallelOptions parallelOptions = new ParallelOptions();
|
|
|
|
private MultivariateLinearRegression mix;
|
|
private MultivariateLinearRegression demix;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Constructs a new Independent Component Analysis.
|
|
/// </summary>
|
|
///
|
|
/// <param name="data">The source data to perform analysis. The matrix should contain
|
|
/// variables as columns and observations of each variable as rows.</param>
|
|
///
|
|
[Obsolete("Please do not pass data to this constructor, and use the Learn method instead.")]
|
|
public IndependentComponentAnalysis(double[,] data)
|
|
: this(data, AnalysisMethod.Center, IndependentComponentAlgorithm.Parallel)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new Independent Component Analysis.
|
|
/// </summary>
|
|
///
|
|
/// <param name="data">The source data to perform analysis. The matrix should contain
|
|
/// variables as columns and observations of each variable as rows.</param>
|
|
/// <param name="algorithm">The FastICA algorithm to be used in the analysis. Default
|
|
/// is <see cref="IndependentComponentAlgorithm.Parallel"/>.</param>
|
|
///
|
|
[Obsolete("Please do not pass data to this constructor, and use the Learn method instead.")]
|
|
public IndependentComponentAnalysis(double[,] data, IndependentComponentAlgorithm algorithm)
|
|
: this(data, AnalysisMethod.Center, algorithm)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new Independent Component Analysis.
|
|
/// </summary>
|
|
///
|
|
/// <param name="data">The source data to perform analysis. The matrix should contain
|
|
/// variables as columns and observations of each variable as rows.</param>
|
|
/// <param name="method">The analysis method to perform. Default is
|
|
/// <see cref="AnalysisMethod.Center"/>.</param>
|
|
///
|
|
[Obsolete("Please do not pass data to this constructor, and use the Learn method instead.")]
|
|
public IndependentComponentAnalysis(double[,] data, AnalysisMethod method)
|
|
: this(data, method, IndependentComponentAlgorithm.Parallel)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new Independent Component Analysis.
|
|
/// </summary>
|
|
///
|
|
/// <param name="data">The source data to perform analysis. The matrix should contain
|
|
/// variables as columns and observations of each variable as rows.</param>
|
|
/// <param name="method">The analysis method to perform. Default is
|
|
/// <see cref="AnalysisMethod.Center"/>.</param>
|
|
/// <param name="algorithm">The FastICA algorithm to be used in the analysis. Default
|
|
/// is <see cref="IndependentComponentAlgorithm.Parallel"/>.</param>
|
|
///
|
|
[Obsolete("Please do not pass data to this constructor, and use the Learn method instead.")]
|
|
public IndependentComponentAnalysis(double[,] data, AnalysisMethod method,
|
|
IndependentComponentAlgorithm algorithm)
|
|
{
|
|
if (data == null)
|
|
throw new ArgumentNullException("data");
|
|
|
|
this.sourceMatrix = data;
|
|
this.algorithm = algorithm;
|
|
this.analysisMethod = method;
|
|
|
|
// Calculate common measures to speedup other calculations
|
|
this.columnMeans = Measures.Mean(sourceMatrix, dimension: 0);
|
|
this.columnStdDev = Measures.StandardDeviation(sourceMatrix, columnMeans);
|
|
|
|
this.NumberOfInputs = data.Columns();
|
|
this.NumberOfOutputs = data.Columns();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new Independent Component Analysis.
|
|
/// </summary>
|
|
///
|
|
/// <param name="data">The source data to perform analysis. The matrix should contain
|
|
/// variables as columns and observations of each variable as rows.</param>
|
|
/// <param name="method">The analysis method to perform. Default is
|
|
/// <see cref="AnalysisMethod.Center"/>.</param>
|
|
/// <param name="algorithm">The FastICA algorithm to be used in the analysis. Default
|
|
/// is <see cref="IndependentComponentAlgorithm.Parallel"/>.</param>
|
|
///
|
|
[Obsolete("Please do not pass data to this constructor, and use the Learn method instead.")]
|
|
public IndependentComponentAnalysis(double[][] data, AnalysisMethod method = AnalysisMethod.Center,
|
|
IndependentComponentAlgorithm algorithm = IndependentComponentAlgorithm.Parallel)
|
|
{
|
|
if (data == null)
|
|
throw new ArgumentNullException("data");
|
|
|
|
this.sourceMatrix = data.ToMatrix();
|
|
this.algorithm = algorithm;
|
|
this.analysisMethod = method;
|
|
|
|
// Calculate common measures to speedup other calculations
|
|
this.columnMeans = Measures.Mean(sourceMatrix, dimension: 0);
|
|
this.columnStdDev = Measures.StandardDeviation(sourceMatrix, columnMeans);
|
|
|
|
this.NumberOfInputs = data.Columns();
|
|
this.NumberOfOutputs = data.Columns();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new Independent Component Analysis.
|
|
/// </summary>
|
|
///
|
|
public IndependentComponentAnalysis(AnalysisMethod method = AnalysisMethod.Center,
|
|
IndependentComponentAlgorithm algorithm = IndependentComponentAlgorithm.Parallel)
|
|
{
|
|
this.algorithm = algorithm;
|
|
this.analysisMethod = method;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets or sets the parallelization options for this algorithm.
|
|
/// </summary>
|
|
///
|
|
public ParallelOptions ParallelOptions
|
|
{
|
|
get { return parallelOptions; }
|
|
set { ParallelOptions = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a cancellation token that can be used
|
|
/// to cancel the algorithm while it is running.
|
|
/// </summary>
|
|
///
|
|
public CancellationToken Token
|
|
{
|
|
get { return parallelOptions.CancellationToken; }
|
|
set { parallelOptions.CancellationToken = value; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Source data used in the analysis.
|
|
/// </summary>
|
|
///
|
|
[Obsolete("This property will be removed.")]
|
|
public double[,] Source
|
|
{
|
|
get { return sourceMatrix; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the maximum number of iterations
|
|
/// to perform. If zero, the method will run until
|
|
/// convergence.
|
|
/// </summary>
|
|
///
|
|
/// <value>The iterations.</value>
|
|
///
|
|
public int Iterations
|
|
{
|
|
get { return maxIterations; }
|
|
set { maxIterations = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the maximum absolute change in
|
|
/// parameters between iterations that determine
|
|
/// convergence.
|
|
/// </summary>
|
|
///
|
|
public double Tolerance
|
|
{
|
|
get { return tolerance; }
|
|
set { tolerance = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the resulting projection of the source
|
|
/// data given on the creation of the analysis
|
|
/// into the space spawned by independent components.
|
|
/// </summary>
|
|
///
|
|
/// <value>The resulting projection in independent component space.</value>
|
|
///
|
|
[Obsolete("This property will be removed.")]
|
|
public double[,] Result
|
|
{
|
|
get { return resultMatrix; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a matrix containing the mixing coefficients for
|
|
/// the original source data being analyzed. Each column
|
|
/// corresponds to an independent component.
|
|
/// </summary>
|
|
///
|
|
public double[][] MixingMatrix
|
|
{
|
|
get { return mixingMatrix; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a matrix containing the demixing coefficients for
|
|
/// the original source data being analyzed. Each column
|
|
/// corresponds to an independent component.
|
|
/// </summary>
|
|
///
|
|
public double[][] DemixingMatrix
|
|
{
|
|
get { return revertMatrix; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the whitening matrix used to transform
|
|
/// the original data to have unit variance.
|
|
/// </summary>
|
|
///
|
|
public double[][] WhiteningMatrix
|
|
{
|
|
get { return whiteningMatrix; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Independent Components in a object-oriented structure.
|
|
/// </summary>
|
|
///
|
|
/// <value>The collection of independent components.</value>
|
|
///
|
|
public IndependentComponentCollection Components
|
|
{
|
|
get { return componentCollection; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether calculations will be performed overwriting
|
|
/// data in the original source matrix, using less memory.
|
|
/// </summary>
|
|
///
|
|
public bool Overwrite
|
|
{
|
|
get { return overwriteSourceMatrix; }
|
|
set { overwriteSourceMatrix = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="IndependentComponentAlgorithm">
|
|
/// FastICA</see> algorithm used by the analysis.
|
|
/// </summary>
|
|
///
|
|
public IndependentComponentAlgorithm Algorithm
|
|
{
|
|
get { return algorithm; }
|
|
set { algorithm = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the normalization method used for this analysis.
|
|
/// </summary>
|
|
///
|
|
public AnalysisMethod Method
|
|
{
|
|
get { return analysisMethod; }
|
|
set { analysisMethod = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="IContrastFunction">
|
|
/// Contrast function</see> to be used by the analysis.
|
|
/// </summary>
|
|
///
|
|
public IContrastFunction Contrast
|
|
{
|
|
get { return contrast; }
|
|
set { contrast = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the column means of the original data.
|
|
/// </summary>
|
|
///
|
|
public double[] Means
|
|
{
|
|
get { return columnMeans; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the column standard deviations of the original data.
|
|
/// </summary>
|
|
///
|
|
public double[] StandardDeviation
|
|
{
|
|
get { return columnStdDev; }
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Computes the Independent Component Analysis algorithm.
|
|
/// </summary>
|
|
///
|
|
[Obsolete("Please use Learn instead.")]
|
|
public void Compute()
|
|
{
|
|
Compute(NumberOfOutputs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Computes the Independent Component Analysis algorithm.
|
|
/// </summary>
|
|
///
|
|
[Obsolete("Please set NumberOfOutputs to the desired number of components and use Learn instead.")]
|
|
public void Compute(int components)
|
|
{
|
|
NumberOfOutputs = components;
|
|
|
|
// First, the data should be centered by subtracting
|
|
// the mean of each column in the source data matrix.
|
|
double[][] matrix = Adjust(sourceMatrix.ToJagged(), overwriteSourceMatrix);
|
|
|
|
// Pre-process the centered data matrix to have unit variance
|
|
double[][] whiten = Statistics.Tools.Whitening(matrix, out whiteningMatrix);
|
|
|
|
// Generate a new unitary initial guess for the de-mixing matrix
|
|
double[][] initial = Jagged.Random(components, matrix.Columns());
|
|
|
|
|
|
// Compute the demixing matrix using the selected algorithm
|
|
if (algorithm == IndependentComponentAlgorithm.Deflation)
|
|
{
|
|
revertMatrix = deflation(whiten, components, initial);
|
|
}
|
|
else // if (algorithm == IndependentComponentAlgorithm.Parallel)
|
|
{
|
|
revertMatrix = parallel(whiten, components, initial);
|
|
}
|
|
|
|
// Combine the rotation and demixing matrices
|
|
revertMatrix = whiteningMatrix.DotWithTransposed(revertMatrix);
|
|
revertMatrix.Divide(revertMatrix.Sum(), result: revertMatrix);
|
|
|
|
// Compute the original source mixing matrix
|
|
mixingMatrix = Matrix.PseudoInverse(revertMatrix);
|
|
mixingMatrix.Divide(mixingMatrix.Sum(), result: mixingMatrix);
|
|
|
|
// Demix the data into independent components
|
|
resultMatrix = Matrix.Dot(matrix, revertMatrix).ToMatrix();
|
|
|
|
this.demix = createRegression(revertMatrix, columnMeans, columnStdDev, analysisMethod);
|
|
this.mix = demix.Inverse();
|
|
|
|
// Creates the object-oriented structure to hold the principal components
|
|
var array = new IndependentComponent[components];
|
|
for (int i = 0; i < array.Length; i++)
|
|
array[i] = new IndependentComponent(this, i);
|
|
this.componentCollection = new IndependentComponentCollection(array);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Separates a mixture into its components (demixing).
|
|
/// </summary>
|
|
///
|
|
[Obsolete("Please use jagged matrices instead.")]
|
|
public double[,] Separate(double[,] data)
|
|
{
|
|
return demix.Transform(data.ToJagged()).ToMatrix();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Separates a mixture into its components (demixing).
|
|
/// </summary>
|
|
///
|
|
[Obsolete("Please use Demix.Transform() instead.")]
|
|
public float[][] Separate(float[][] data)
|
|
{
|
|
return demix.Transform(data.ToDouble().Transpose()).ToSingle().Transpose(); // TODO: Implement float support in linear regression
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Combines components into a single mixture (mixing).
|
|
/// </summary>
|
|
///
|
|
[Obsolete("Please use Mix.Transform instead.")]
|
|
public double[,] Combine(double[,] data)
|
|
{
|
|
return mix.Transform(data.ToJagged()).ToMatrix();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Combines components into a single mixture (mixing).
|
|
/// </summary>
|
|
///
|
|
[Obsolete("Please use Mix.Transform instead.")]
|
|
public float[][] Combine(float[][] data)
|
|
{
|
|
return mix.Transform(data.ToDouble().Transpose()).ToSingle().Transpose(); // TODO: Implement float support in linear regression;
|
|
}
|
|
|
|
|
|
|
|
#region FastICA Algorithms
|
|
|
|
/// <summary>
|
|
/// Deflation iterative algorithm.
|
|
/// </summary>
|
|
///
|
|
/// <returns>
|
|
/// Returns a matrix in which each row contains
|
|
/// the mixing coefficients for each component.
|
|
/// </returns>
|
|
///
|
|
private double[][] deflation(double[][] X, int components, double[][] init)
|
|
{
|
|
// References:
|
|
// - Hyvärinen, A (1999). Fast and Robust Fixed-Point
|
|
// Algorithms for Independent Component Analysis.
|
|
|
|
// There are two ways to apply the fixed-unit algorithm to compute the whole
|
|
// ICA iteration. The more simpler is to perform a deflation as in the Gram-
|
|
// Schmidt orthogonalization process [Hyvärinen]. In this scheme, independent
|
|
// components are estimated one-by-one. See referenced paper for details.
|
|
|
|
int n = X.Rows();
|
|
int m = X.Columns();
|
|
|
|
// Algorithm initialization
|
|
var W = Jagged.Zeros(components, m);
|
|
var wx = new double[n];
|
|
var gwx = new double[n];
|
|
var dgwx = new double[n];
|
|
|
|
|
|
// For each component to be computed,
|
|
for (int i = 0; i < components; i++)
|
|
{
|
|
// Will compute each of the basis vectors
|
|
// individually and sequentially, re-using
|
|
// previous computations to form basis W.
|
|
//
|
|
|
|
// Initialization
|
|
int iterations = 0;
|
|
bool stop = false;
|
|
//double lastChange = 1;
|
|
|
|
double[] w = init.GetRow(i);
|
|
double[] w0 = init.GetRow(i);
|
|
|
|
|
|
do // until convergence
|
|
{
|
|
// Start with deflation
|
|
for (int u = 0; u < i; u++)
|
|
{
|
|
double proj = 0;
|
|
for (int j = 0; j < w0.Length; j++)
|
|
proj += w0[j] * W[u][j];
|
|
|
|
for (int j = 0; j < w0.Length; j++)
|
|
w[j] = w0[j] - proj * W[u][j];
|
|
}
|
|
|
|
// Normalize
|
|
w = w.Divide(Norm.Euclidean(w));
|
|
|
|
|
|
// Gets the maximum parameter absolute change
|
|
double delta = getMaximumAbsoluteChange(w, w0);
|
|
|
|
// Check for convergence
|
|
if (!(delta > tolerance && iterations < maxIterations) || Token.IsCancellationRequested)
|
|
{
|
|
stop = true;
|
|
}
|
|
else
|
|
{
|
|
// Advance to the next iteration
|
|
w0 = w; w = new double[m];
|
|
//lastChange = delta;
|
|
iterations++;
|
|
|
|
// Compute wx = w*x
|
|
for (int j = 0; j < n; j++)
|
|
{
|
|
double s = 0;
|
|
for (int k = 0; k < w0.Length; k++)
|
|
s += w0[k] * X[j][k];
|
|
wx[j] = s;
|
|
}
|
|
|
|
// Compute g(w*x) and g'(w*x)
|
|
contrast.Evaluate(wx, gwx, dgwx);
|
|
|
|
// Compute E{ x*g(w*x) }
|
|
double[] means = new double[m];
|
|
for (int j = 0; j < means.Length; j++)
|
|
{
|
|
for (int k = 0; k < gwx.Length; k++)
|
|
means[j] += X[k][j] * gwx[k];
|
|
means[j] /= n;
|
|
}
|
|
|
|
// Compute E{ g'(w*x) }
|
|
double mean = Measures.Mean(dgwx);
|
|
|
|
|
|
// Compute next update for w according
|
|
// to Hyvärinen paper's equation (20).
|
|
|
|
// w+ = E{ xg(w*x)} - E{ g'(w*x)}*w
|
|
for (int j = 0; j < means.Length; j++)
|
|
w[j] = means[j] - mean * w0[j];
|
|
|
|
// The normalization to w* will be performed
|
|
// in the beginning of the next iteration.
|
|
}
|
|
|
|
} while (!stop);
|
|
|
|
// Store the just computed component
|
|
// in the resulting component matrix.
|
|
W.SetRow(i, w);
|
|
}
|
|
|
|
// Return the component basis matrix
|
|
return W; // vectors stored as rows.
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Parallel (symmetric) iterative algorithm.
|
|
/// </summary>
|
|
///
|
|
/// <returns>
|
|
/// Returns a matrix in which each row contains
|
|
/// the mixing coefficients for each component.
|
|
/// </returns>
|
|
///
|
|
private double[][] parallel(double[][] X, int components, double[][] winit)
|
|
{
|
|
// References:
|
|
// - Hyvärinen, A (1999). Fast and Robust Fixed-Point
|
|
// Algorithms for Independent Component Analysis.
|
|
|
|
// There are two ways to apply the fixed-unit algorithm to compute the whole
|
|
// ICA iteration. The second approach is to perform orthogonalization at once
|
|
// using an Eigendecomposition [Hyvärinen]. The Eigendecomposition can in turn
|
|
// be converted to a more stable singular value decomposition and be used to
|
|
// create a projection basis in the same way as in Principal Component Analysis.
|
|
|
|
int n = X.Rows();
|
|
int m = X.Columns();
|
|
|
|
// Algorithm initialization
|
|
double[][] W0 = winit;
|
|
double[][] W = winit;
|
|
double[][] K = Jagged.Zeros(components, components);
|
|
|
|
bool stop = false;
|
|
int iterations = 0;
|
|
//double lastChange = 1;
|
|
|
|
|
|
do // until convergence
|
|
{
|
|
|
|
// [Hyvärinen, 1997]'s paper suggests the use of the Eigendecomposition
|
|
// to orthogonalize W (after equation 10). However, [E, D] = eig(W'W)
|
|
// can be replaced by [U, S] = svd(W), which is more stable and avoids
|
|
// computing W'W. Since the singular values are already the square roots
|
|
// of the eigenvalues of W'W, the computation of E'*D^(-1/2)*E' reduces
|
|
// to U*S^(-1)*U'.
|
|
|
|
// Perform simultaneous decorrelation of all components at once
|
|
var svd = new JaggedSingularValueDecomposition(W,
|
|
computeLeftSingularVectors: true,
|
|
computeRightSingularVectors: false,
|
|
autoTranspose: true);
|
|
|
|
double[] S = svd.Diagonal;
|
|
double[][] U = svd.LeftSingularVectors;
|
|
|
|
// Form orthogonal projection basis K
|
|
for (int i = 0; i < components; i++)
|
|
{
|
|
for (int j = 0; j < components; j++)
|
|
{
|
|
double s = 0;
|
|
for (int k = 0; k < S.Length; k++)
|
|
if (S[k] != 0.0)
|
|
s += U[i][k] * U[j][k] / S[k];
|
|
K[i][j] = s;
|
|
}
|
|
}
|
|
|
|
// Orthogonalize
|
|
W = Matrix.Dot(K, W);
|
|
|
|
// Gets the maximum parameter absolute change
|
|
double delta = getMaximumAbsoluteChange(W0, W);
|
|
|
|
// Check for convergence
|
|
if (delta < tolerance || iterations >= maxIterations || Token.IsCancellationRequested)
|
|
{
|
|
stop = true;
|
|
}
|
|
else
|
|
{
|
|
// Advance to the next iteration
|
|
W0 = W;
|
|
W = Jagged.Zeros(components, m);
|
|
//lastChange = delta;
|
|
iterations++;
|
|
|
|
|
|
// For each component (in parallel)
|
|
Parallel.For(0, components, parallelOptions, i =>
|
|
{
|
|
double[] wx = new double[n];
|
|
double[] dgwx = new double[n];
|
|
double[] gwx = new double[n];
|
|
double[] means = new double[m];
|
|
|
|
// Compute wx = w*x
|
|
for (int j = 0; j < wx.Length; j++)
|
|
{
|
|
double s = 0;
|
|
for (int k = 0; k < m; k++)
|
|
s += W0[i][k] * X[j][k];
|
|
wx[j] = s;
|
|
}
|
|
|
|
// Compute g(wx) and g'(wx)
|
|
contrast.Evaluate(wx, gwx, dgwx);
|
|
|
|
// Compute E{ x*g(w*x) }
|
|
for (int j = 0; j < means.Length; j++)
|
|
{
|
|
for (int k = 0; k < gwx.Length; k++)
|
|
means[j] += X[k][j] * gwx[k];
|
|
means[j] /= n;
|
|
}
|
|
|
|
// Compute E{ g'(w*x) }
|
|
double mean = Measures.Mean(dgwx);
|
|
|
|
|
|
// Compute next update for w according
|
|
// to Hyvärinen paper's equation (20).
|
|
|
|
// w+ = E{ xg(w*x)} - E{ g'(w*x)}*w
|
|
for (int j = 0; j < means.Length; j++)
|
|
W[i][j] = means[j] - mean * W0[i][j];
|
|
|
|
// The normalization to w* will be performed
|
|
// in the beginning of the next iteration.
|
|
});
|
|
}
|
|
|
|
} while (!stop);
|
|
|
|
// Return the component basis matrix
|
|
return W; // vectors stored as rows.
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Computes the maximum absolute change between two members of a matrix.
|
|
/// </summary>
|
|
///
|
|
private static double getMaximumAbsoluteChange(double[][] W, double[][] W0)
|
|
{
|
|
// Used in the parallel method
|
|
double[] diag = W.DotWithTransposed(W0).Diagonal();
|
|
|
|
return diag.Abs(result: diag)
|
|
.Subtract(1, result: diag)
|
|
.Abs(result: diag)
|
|
.Max();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Computes the maximum absolute change between two members of a vector.
|
|
/// </summary>
|
|
///
|
|
private static double getMaximumAbsoluteChange(double[] w, double[] w0)
|
|
{
|
|
// Used in deflation method
|
|
double[] diag = Elementwise.Multiply(w, w0);
|
|
|
|
return Math.Abs(Math.Abs(diag.Sum()) - 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Learns a model that can map the given inputs to the desired outputs.
|
|
/// </summary>
|
|
/// <param name="x">The model inputs.</param>
|
|
/// <param name="weights">The weight of importance for each input sample.</param>
|
|
/// <returns>
|
|
/// A model that has learned how to produce suitable outputs
|
|
/// given the input data <paramref name="x" />.
|
|
/// </returns>
|
|
public MultivariateLinearRegression Learn(double[][] x, double[] weights = null)
|
|
{
|
|
if (weights != null)
|
|
throw new ArgumentException(Accord.Properties.Resources.NotSupportedWeights, "weights");
|
|
|
|
// Calculate common measures to speedup other calculations
|
|
this.columnMeans = Measures.Mean(x, dimension: 0);
|
|
this.columnStdDev = Measures.StandardDeviation(x, columnMeans);
|
|
|
|
NumberOfInputs = x.Columns();
|
|
if (NumberOfOutputs == 0)
|
|
NumberOfOutputs = NumberOfInputs;
|
|
|
|
// First, the data should be centered by subtracting
|
|
// the mean of each column in the source data matrix.
|
|
var matrix = Adjust(x, overwriteSourceMatrix);
|
|
|
|
// Pre-process the centered data matrix to have unit variance
|
|
var whiten = Statistics.Tools.Whitening(matrix, out whiteningMatrix);
|
|
|
|
// Generate a new unitary initial guess for the de-mixing matrix
|
|
var initial = Jagged.Random(NumberOfOutputs, matrix.Columns());
|
|
|
|
|
|
// Compute the demixing matrix using the selected algorithm
|
|
if (algorithm == IndependentComponentAlgorithm.Deflation)
|
|
{
|
|
revertMatrix = deflation(whiten, NumberOfOutputs, initial);
|
|
}
|
|
else // if (algorithm == IndependentComponentAlgorithm.Parallel)
|
|
{
|
|
revertMatrix = parallel(whiten, NumberOfOutputs, initial);
|
|
}
|
|
|
|
// Combine the rotation and demixing matrices
|
|
revertMatrix = whiteningMatrix.DotWithTransposed(revertMatrix);
|
|
revertMatrix.Divide(revertMatrix.Sum(), result: revertMatrix);
|
|
|
|
// Compute the original source mixing matrix
|
|
mixingMatrix = Matrix.PseudoInverse(revertMatrix);
|
|
mixingMatrix.Divide(mixingMatrix.Sum(), result: mixingMatrix);
|
|
|
|
this.demix = createRegression(revertMatrix, columnMeans, columnStdDev, analysisMethod);
|
|
this.mix = demix.Inverse();
|
|
|
|
// Creates the object-oriented structure to hold the principal components
|
|
var array = new IndependentComponent[NumberOfOutputs];
|
|
for (int i = 0; i < array.Length; i++)
|
|
array[i] = new IndependentComponent(this, i);
|
|
this.componentCollection = new IndependentComponentCollection(array);
|
|
|
|
return demix;
|
|
}
|
|
|
|
private static MultivariateLinearRegression createRegression(double[][] coef, double[] means, double[] stdDev, AnalysisMethod method)
|
|
{
|
|
double[][] B = coef.Copy();
|
|
|
|
if (method == AnalysisMethod.Standardize)
|
|
B.Divide(stdDev, dimension: (VectorType)0, result: B);
|
|
|
|
double[] a = means.Dot(B);
|
|
a.Multiply(-1.0, result: a);
|
|
|
|
return new MultivariateLinearRegression()
|
|
{
|
|
Weights = B,
|
|
Intercepts = a
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obsolete
|
|
/// </summary>
|
|
///
|
|
protected float[][] Adjust(float[][] matrix, bool inPlace)
|
|
{
|
|
int rows = matrix.Length;
|
|
int cols = matrix[0].Length;
|
|
|
|
// Prepare the data, storing it in a new matrix if needed.
|
|
float[][] result = matrix;
|
|
|
|
if (!inPlace)
|
|
result = Jagged.CreateAs(matrix);
|
|
|
|
matrix.Subtract(columnMeans, dimension: (VectorType)0, result: result);
|
|
if (this.analysisMethod == AnalysisMethod.Standardize)
|
|
result.Divide(columnStdDev, dimension: (VectorType)0, result: result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obsolete
|
|
/// </summary>
|
|
///
|
|
protected double[][] Adjust(double[][] matrix, bool inPlace)
|
|
{
|
|
int rows = matrix.Length;
|
|
int cols = matrix[0].Length;
|
|
|
|
// Prepare the data, storing it in a new matrix if needed.
|
|
double[][] result = matrix;
|
|
|
|
if (!inPlace)
|
|
result = Jagged.CreateAs(matrix);
|
|
|
|
matrix.Subtract(columnMeans, dimension: (VectorType)0, result: result);
|
|
if (this.analysisMethod == AnalysisMethod.Standardize)
|
|
result.Divide(columnStdDev, dimension: (VectorType)0, result: result);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Applies the transformation to an input, producing an associated output.
|
|
/// </summary>
|
|
/// <param name="input">The input data to which the transformation should be applied.</param>
|
|
/// <param name="result"></param>
|
|
/// <returns>
|
|
/// The output generated by applying this transformation to the given input.
|
|
/// </returns>
|
|
public override double[] Transform(double[] input, double[] result)
|
|
{
|
|
// Data-adjust and separate the samples
|
|
double[] matrix = Adjust(new[] { input }, false)[0];
|
|
return Matrix.Dot(matrix, revertMatrix, result: result);
|
|
}
|
|
}
|
|
|
|
|
|
#region Support Classes
|
|
|
|
/// <summary>
|
|
/// Represents an Independent Component found in the Independent Component
|
|
/// Analysis, allowing it to be directly bound to controls like the DataGridView.
|
|
/// </summary>
|
|
///
|
|
[Serializable]
|
|
public class IndependentComponent
|
|
{
|
|
|
|
private int index;
|
|
private IndependentComponentAnalysis analysis;
|
|
|
|
|
|
/// <summary>
|
|
/// Creates an independent component representation.
|
|
/// </summary>
|
|
///
|
|
/// <param name="analysis">The analysis to which this component belongs.</param>
|
|
/// <param name="index">The component index.</param>
|
|
///
|
|
internal IndependentComponent(IndependentComponentAnalysis analysis, int index)
|
|
{
|
|
this.index = index;
|
|
this.analysis = analysis;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the Index of this component on the original component collection.
|
|
/// </summary>
|
|
///
|
|
public int Index
|
|
{
|
|
get { return this.index; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a reference to the parent analysis object.
|
|
/// </summary>
|
|
///
|
|
public IndependentComponentAnalysis Analysis
|
|
{
|
|
get { return this.analysis; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the mixing vector for the current independent component.
|
|
/// </summary>
|
|
///
|
|
public double[] MixingVector
|
|
{
|
|
get { return this.analysis.MixingMatrix.GetColumn(index); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the demixing vector for the current independent component.
|
|
/// </summary>
|
|
///
|
|
public double[] DemixingVector
|
|
{
|
|
get { return this.analysis.DemixingMatrix.GetColumn(index); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the whitening factor for the current independent component.
|
|
/// </summary>
|
|
///
|
|
public double[] WhiteningVector
|
|
{
|
|
get { return this.analysis.WhiteningMatrix.GetColumn(index); }
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a Collection of Independent Components found in the
|
|
/// Independent Component Analysis. This class cannot be instantiated.
|
|
/// </summary>
|
|
///
|
|
[Serializable]
|
|
public class IndependentComponentCollection : ReadOnlyCollection<IndependentComponent>
|
|
{
|
|
internal IndependentComponentCollection(IndependentComponent[] components)
|
|
: base(components)
|
|
{
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
|
|
}
|