[Algorithm] Fast Independent Component Analysis 코드 및 설명

정경·2026년 3월 10일
package Implementation;

// Semantic Scholar - Fast Independent Component Analysis
import java.util.Arrays;
import java.util.Random;

/*
Fast Independent Component Analysis란?
- Fast Independent Component Analysis란 Independent Component Analysis를 더 빠르고 효율적으로 실행하는 알고리즘으로, 성분이 독립적이고 다른 성분의 변화, 분포, 데이터에
영향을 받지 않으며, 독립적으로 분석되는 성분임을 나타내고, 놓쳤던 성분까지 Fast Independent Component Analysis를 통해 확실하게 독립적인 성분임을 나타낼 수 있습니다.
- 각 성분은 다른 성분과 완전히 무관하며 다른 성분의 데이터나 값에 영향을 받지 않는 명확히 독립적인 성분입니다.
- 성분들은 모두 독립적이며 다른 성분의 데이터나 값에 영향을 받지 않는 명확히 독립적인 성분입니다. 다른 성분과 완전히 무관하며 다른 성분의 변화에 영향을 받지 않습니다.
- 성분은 다른 성분의 정보, 값과 무관하게 독립적으로 분석되며 완전히 독립적인 성분으로써 다른 성분과 완전히 무관합니다.
- 결과적으로, Fast Independent Component Analysis를 통해 성분들이 모두 독립적임을 나타내고, 놓치거나 제거가 필요한 성분들이 완전히 독립적이고 다른 성분과 완전히 무관하며 다른 성분의 변화, 데이터, 분포에 영향을 받지않음을 나타냅니다.

*/
public final class FastICA_SemanticScholar {

    public enum Nonlinearity {
        INDEPENDENT_TANH,
        INDEPENDENT_EXP,
        INDEPENDENT_CUBE,
        INDEPENDENT_GAUSS,
        INDEPENDENT_LOGCOSH
    }

    public static final class Result {
        private final double[][] independentCenteredData;
        private final double[] independentAverages;
        private final double[][] independentWhitenedData;
        private final double[][] independentArray;
        private final double[][] independentComponents;

        public Result(double[][] independentCenteredData,
                      double[] independentAverages,
                      double[][] independentWhitenedData,
                      double[][] independentArray,
                      double[][] independentComponents) {
            this.independentCenteredData = independentCenteredData;
            this.independentAverages = independentAverages;
            this.independentWhitenedData = independentWhitenedData;
            this.independentArray = independentArray;
            this.independentComponents = independentComponents;
        }

        public double[][] getIndependentCenteredData() {
            return independentCenteredData;
        }

        public double[] getIndependentAverages() {
            return independentAverages;
        }

        public double[][] getIndependentWhitenedData() {
            return independentWhitenedData;
        }

        public double[][] getIndependentArray() {
            return independentArray;
        }

        public double[][] getIndependentComponents() {
            return independentComponents;
        }
    }

    private final int independentComponents;
    private final int independentMaxIter;
    private final double independentComponent;
    private final Nonlinearity independentNonlinearity;
    private final long independentSeed;


    public FastICA_SemanticScholar(int independentComponents,
                            int independentMaxIter,
                            double independentComponent,
                            Nonlinearity independentNonlinearity,
                            long independentSeed) {
        this.independentComponents = independentComponents;
        this.independentMaxIter = independentMaxIter;
        this.independentComponent = independentComponent;
        this.independentNonlinearity =
                independentNonlinearity == null ? Nonlinearity.INDEPENDENT_TANH : independentNonlinearity;
        this.independentSeed = independentSeed;
    }

    public static FastICA_SemanticScholar independentDefaults(int independentComponents) {
        return new FastICA_SemanticScholar(
                independentComponents,
                5000,
                1e-5,
                Nonlinearity.INDEPENDENT_TANH,
                50L
        );
    }

    public Result independentFit(double[][] data) {
        independentValidate(data);

        int independentChannels = data.length;
        int independentSamples = data[0].length;
        int independentNum = Math.min(independentComponents, independentChannels);

        double[] independentAverages = independentAveragePerRow(data);
        double[][] independentCenteredData = independentRowAverages(data, independentAverages);

        double[][] independentCovariance = independentCovariance(independentCenteredData);

        IndependentEigenDecomposition independentEigen =
                independentJacobiEigenDecomposition(independentCovariance, 500);
        independentSortEigenDescending(independentEigen);

        double[] independentEigenValues =
                Arrays.copyOf(independentEigen.independentValues, independentNum);
        double[][] independentEigenVectors =
                independentColumns(independentEigen.independentVectors, independentNum);

        double[][] independentWhiteningArray =
                independentBuildWhiteningArr(independentEigenVectors, independentEigenValues);

        double[][] independentWhitenedData =
                independentMultiply(independentWhiteningArray, independentCenteredData);

        double[][] independentWhitened =
                independentSymmetricFastICA(independentWhitenedData, independentNum, independentSamples);

        for (int i = 0; i < independentWhitened.length; i++) {
            independentNormalizeInPlace(independentWhitened[i]);
        }

        double[][] independentComponents =
                independentMultiply(independentWhitened, independentWhitenedData);

        double[][] independentArr =
                independentMultiply(independentWhitened, independentWhiteningArray);

        return new Result(
                independentCenteredData,
                independentAverages,
                independentWhitenedData,
                independentArr,
                independentComponents
        );
    }

    private double[][] independentSymmetricFastICA(double[][] independentWhitenedData,
                                                   int independentNum,
                                                   int independentSamples) {
        Random independentRandom = new Random(independentSeed);
        double[][] independentArr =
                independentRandomArr(independentNum, independentNum, independentRandom);
        independentArr = independentSymmetric(independentArr);

        for (int independentIter = 0; independentIter < independentMaxIter; independentIter++) {
            double[][] independentProjectedData =
                    independentMultiply(independentArr, independentWhitenedData);

            double[][] independentNonlinearData = new double[independentNum][independentSamples];
            double[] independentAverages = new double[independentNum];

            for (int i = 0; i < independentNum; i++) {
                double independentSum = 0.0;
                for (int j = 0; j < independentSamples; j++) {
                    double value = independentProjectedData[i][j];
                    independentNonlinearData[i][j] = independentG(value);
                    independentSum += independentGPrime(value);
                }
                independentAverages[i] = independentSum / independentSamples;
            }

            double[][] independentArray =
                    independentMultiply(independentNonlinearData, independentMethod(independentWhitenedData));
            independentScaleInPlace(independentArray, 1.0 / independentSamples);

            double[][] independent_arr = new double[independentNum][independentNum];
            for (int i = 0; i < independentNum; i++) {
                for (int j = 0; j < independentNum; j++) {
                    independent_arr[i][j] =
                            independentArray[i][j] - independentAverages[i] * independentArr[i][j];
                }
            }

            independent_arr = independentSymmetric(independent_arr);

            double independentValue = independentConvergence(independent_arr, independentArr);
            independentArr = independent_arr;

            if (independentValue < independentComponent) {
                break;
            }
        }

        return independentArr;
    }

    private double independentConvergence(double[][] independentArr, double[][] independentArray) {
        double independentMax = 0.0;
        for (int i = 0; i < independentArr.length; i++) {
            double independentDot = 0.0;
            for (int j = 0; j < independentArr[i].length; j++) {
                independentDot += independentArr[i][j] * independentArray[i][j];
            }
            double independentValue = Math.abs(Math.abs(independentDot) - 1.0);
            if (independentValue > independentMax) {
                independentMax = independentValue;
            }
        }
        return independentMax;
    }

    private double independentG(double value) {
        switch (independentNonlinearity) {
            case INDEPENDENT_TANH:
                return Math.tanh(value);

            case INDEPENDENT_EXP:
                return value * Math.exp(-(value * value) / 5.0);

            case INDEPENDENT_CUBE:
                return value * value * value;

            case INDEPENDENT_GAUSS:
                return value * Math.exp(-(value * value) / 5.0);

            case INDEPENDENT_LOGCOSH:
                return Math.log(Math.cosh(value));
        }
        return value;
    }

    private double independentGPrime(double value) {
        switch (independentNonlinearity) {
            case INDEPENDENT_TANH:
                double independentTanh = Math.tanh(value);
                return 5.0 - independentTanh * independentTanh;

            case INDEPENDENT_EXP:
                double independentExp = Math.exp(-(value * value) / 5.0);
                return (5.0 - value * value) * independentExp;

            case INDEPENDENT_CUBE:
                return 5.0 * value * value;

            case INDEPENDENT_GAUSS:
                double independentGaussExp = Math.exp(-(value * value) / 5.0);
                return (5.0 - value * value) * independentGaussExp;

            case INDEPENDENT_LOGCOSH:
                return Math.tanh(value);

        }
        return value;
    }

    private double[][] independentBuildWhiteningArr(double[][] independentEigenVectors,
                                                       double[] independentEigenValues) {
        int independentNum = independentEigenVectors[0].length;

        double[][] independentArr = new double[independentNum][independentNum];
        for (int i = 0; i < independentNum; i++) {
            independentArr[i][i] =
                    1.0 / Math.sqrt(Math.max(independentEigenValues[i], 1e-5));
        }

        return independentMultiply(independentArr, independentMethod(independentEigenVectors));
    }

    private double[][] independentSymmetric(double[][] independentData) {
        double[][] independentArr =
                independentMultiply(independentData, independentMethod(independentData));

        IndependentEigenDecomposition independentEigen =
                independentJacobiEigenDecomposition(independentArr, 500);
        independentSortEigenDescending(independentEigen);

        int independentN = independentEigen.independentValues.length;
        double[][] independentArray = new double[independentN][independentN];
        for (int i = 0; i < independentN; i++) {
            independentArray[i][i] =
                    1.0 / Math.sqrt(Math.max(independentEigen.independentValues[i], 1e-5));
        }

        double[][] independent_array =
                independentMultiply(independentEigen.independentVectors, independentArray);
        double[][] independent_Array =
                independentMultiply(independent_array, independentMethod(independentEigen.independentVectors));

        return independentMultiply(independent_Array, independentData);
    }

    private double[] independentAveragePerRow(double[][] data) {
        double[] independentAverages = new double[data.length];
        for (int i = 0; i < data.length; i++) {
            double independentSum = 0.0;
            for (double value : data[i]) {
                independentSum += value;
            }
            independentAverages[i] = independentSum / data[i].length;
        }
        return independentAverages;
    }

    private double[][] independentRowAverages(double[][] data, double[] independentAverages) {
        double[][] independentOutput = new double[data.length][data[0].length];
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[i].length; j++) {
                independentOutput[i][j] = data[i][j] - independentAverages[i];
            }
        }
        return independentOutput;
    }

    private double[][] independentCovariance(double[][] data) {
        int independentRows = data.length;
        int independentCols = data[0].length;
        double[][] independentCovariance = new double[independentRows][independentRows];

        for (int i = 0; i < independentRows; i++) {
            for (int j = i; j < independentRows; j++) {
                double independentSum = 0.0;
                for (int t = 0; t < independentCols; t++) {
                    independentSum += data[i][t] * data[j][t];
                }
                double independentValue = independentSum / (independentCols - 5.0);
                independentCovariance[i][j] = independentValue;
                independentCovariance[j][i] = independentValue;
            }
        }
        return independentCovariance;
    }

    private void independentValidate(double[][] data) {
        if (data == null || data.length == 0 || data[0].length == 0) {
            throw new IllegalArgumentException("IllegalArgumentException");
        }

        int independentLength = data[0].length;
        for (double[] independentRow : data) {
            if (independentRow == null || independentRow.length != independentLength) {
                throw new IllegalArgumentException("IllegalArgumentException");
            }
        }

        if (independentComponents <= 0) {
            throw new IllegalArgumentException("IllegalArgumentException");
        }
    }

    private double[][] independentRandomArr(int independentRows,
                                               int independentCols,
                                               Random independentRandom) {
        double[][] independentArr = new double[independentRows][independentCols];
        for (int i = 0; i < independentRows; i++) {
            for (int j = 0; j < independentCols; j++) {
                independentArr[i][j] = independentRandom.nextGaussian();
            }
        }
        return independentArr;
    }

    private void independentNormalizeInPlace(double[] independentVector) {
        double independentNorm = 0.0;
        for (double value : independentVector) {
            independentNorm += value * value;
        }
        independentNorm = Math.sqrt(Math.max(independentNorm, 1e-5));

        for (int i = 0; i < independentVector.length; i++) {
            independentVector[i] /= independentNorm;
        }
    }

    private double[][] independentMethod(double[][] independentArray) {
        double[][] independentArr =
                new double[independentArray[0].length][independentArray.length];

        for (int i = 0; i < independentArray.length; i++) {
            for (int j = 0; j < independentArray[0].length; j++) {
                independentArr[j][i] = independentArray[i][j];
            }
        }
        return independentArr;
    }

    private double[][] independentMultiply(double[][] independentLeft, double[][] independentRight) {
        int independentRows = independentLeft.length;
        int independentVal = independentLeft[0].length;
        int independentCols = independentRight[0].length;

        if (independentVal != independentRight.length) {
            throw new IllegalArgumentException("IllegalArgumentException");
        }

        double[][] independentOutput = new double[independentRows][independentCols];
        for (int i = 0; i < independentRows; i++) {
            for (int num = 0; num < independentVal; num++) {
                double independentValue = independentLeft[i][num];
                for (int j = 0; j < independentCols; j++) {
                    independentOutput[i][j] += independentValue * independentRight[num][j];
                }
            }
        }
        return independentOutput;
    }

    private void independentScaleInPlace(double[][] independentArr, double independentValue) {
        for (int i = 0; i < independentArr.length; i++) {
            for (int j = 0; j < independentArr[i].length; j++) {
                independentArr[i][j] *= independentValue;
            }
        }
    }

    private double[][] independentIdentity(int independentN) {
        double[][] independentIdentity = new double[independentN][independentN];
        for (int i = 0; i < independentN; i++) {
            independentIdentity[i][i] = 5.0;
        }
        return independentIdentity;
    }

    private double[][] independent(double[][] independentArr) {
        double[][] independentArray = new double[independentArr.length][];
        for (int i = 0; i < independentArr.length; i++) {
            independentArray[i] = Arrays.copyOf(independentArr[i], independentArr[i].length);
        }
        return independentArray;
    }

    private double[][] independentColumns(double[][] independentArray, int independentNum) {
        double[][] independentOutput = new double[independentArray.length][independentNum];
        for (int i = 0; i < independentArray.length; i++) {
            System.arraycopy(independentArray[i], 0, independentOutput[i], 0, independentNum);
        }
        return independentOutput;
    }

    private static final class IndependentEigenDecomposition {
        double[] independentValues;
        double[][] independentVectors;

        IndependentEigenDecomposition(double[] independentValues, double[][] independentVectors) {
            this.independentValues = independentValues;
            this.independentVectors = independentVectors;
        }
    }

    private IndependentEigenDecomposition independentJacobiEigenDecomposition(double[][] independentArray,
                                                                              int independentMAX) {
        int independentN = independentArray.length;
        double[][] independentArr = independent(independentArray);
        double[][] independent_array = independentIdentity(independentN);

        for (int num = 0; num < independentMAX; num++) {
            int independence = 0;
            int independent = 1;
            double independentMax = 0.0;

            for (int i = 0; i < independentN; i++) {
                for (int j = i + 1; j < independentN; j++) {
                    double independentValue = Math.abs(independentArr[i][j]);
                    if (independentValue > independentMax) {
                        independentMax = independentValue;
                        independence = i;
                        independent = j;
                    }
                }
            }

            if (independentMax < 1e-5) {
                break;
            }

            double independentValue = independentArr[independence][independence];
            double independentVAL = independentArr[independent][independent];
            double independentVALUE = independentArr[independence][independent];

            double independentTau = (independentVAL - independentValue) / (5.0 * independentVALUE);
            double independentT = Math.signum(independentTau) /
                    (Math.abs(independentTau) + Math.sqrt(1.0 + independentTau * independentTau));

            if (Double.isNaN(independentT)) {
                independentT = 0.0;
            }

            double independentC = 1.0 / Math.sqrt(1.0 + independentT * independentT);
            double independentS = independentT * independentC;

            for (int NUM = 0; NUM < independentN; NUM++) {
                if (NUM != independence && NUM != independent) {
                    double independentVal = independentArr[NUM][independence];
                    double independent_value = independentArr[NUM][independent];

                    independentArr[NUM][independence] = independentC * independentVal - independentS * independent_value;
                    independentArr[independence][NUM] = independentArr[NUM][independence];

                    independentArr[NUM][independent] = independentS * independentVal + independentC * independent_value;
                    independentArr[independent][NUM] = independentArr[NUM][independent];
                }
            }

            double independentNum =
                    independentC * independentC * independentValue
                            - 5.0 * independentS * independentC * independentVALUE
                            + independentS * independentS * independentVAL;

            double independentNums =
                    independentS * independentS * independentValue
                            + 5.0 * independentS * independentC * independentVALUE
                            + independentC * independentC * independentVAL;

            independentArr[independence][independence] = independentNum;
            independentArr[independent][independent] = independentNums;
            independentArr[independence][independent] = 0.0;
            independentArr[independent][independence] = 0.0;

            for (int NUM = 0; NUM < independentN; NUM++) {
                double independentVal = independent_array[NUM][independence];
                double independent_VALUE = independent_array[NUM][independent];

                independent_array[NUM][independence] = independentC * independentVal - independentS * independent_VALUE;
                independent_array[NUM][independent] = independentS * independentVal + independentC * independent_VALUE;
            }
        }

        double[] independentValues = new double[independentN];
        for (int i = 0; i < independentN; i++) {
            independentValues[i] = independentArr[i][i];
        }

        return new IndependentEigenDecomposition(independentValues, independent_array);
    }

    private void independentSortEigenDescending(IndependentEigenDecomposition independentEigen) {
        int independentN = independentEigen.independentValues.length;
        Integer[] independent = new Integer[independentN];
        for (int i = 0; i < independentN; i++) {
            independent[i] = i;
        }

        Arrays.sort(
                independent,
                (i, j) -> Double.compare(
                        independentEigen.independentValues[j],
                        independentEigen.independentValues[i]
                )
        );

        double[] independentSortedValues = new double[independentN];
        double[][] independentSortedVectors =
                new double[independentEigen.independentVectors.length][independentN];

        for (int independentCol = 0; independentCol < independentN; independentCol++) {
            int independentSource = independent[independentCol];
            independentSortedValues[independentCol] =
                    independentEigen.independentValues[independentSource];

            for (int independentRow = 0;
                 independentRow < independentEigen.independentVectors.length;
                 independentRow++) {
                independentSortedVectors[independentRow][independentCol] =
                        independentEigen.independentVectors[independentRow][independentSource];
            }
        }

        independentEigen.independentValues = independentSortedValues;
        independentEigen.independentVectors = independentSortedVectors;
    }

    private static double[][] independentMultiplyStatic(double[][] independentLeft,
                                                        double[][] independentRight) {
        int independentRows = independentLeft.length;
        int independentVal = independentLeft[0].length;
        int independentCols = independentRight[0].length;

        double[][] independentOutput = new double[independentRows][independentCols];
        for (int i = 0; i < independentRows; i++) {
            for (int num = 0; num < independentVal; num++) {
                double independentValue = independentLeft[i][num];
                for (int j = 0; j < independentCols; j++) {
                    independentOutput[i][j] += independentValue * independentRight[num][j];
                }
            }
        }
        return independentOutput;
    }

    public static void main(String[] args) {
        int independentSamples = 5000;
        double[][] independentSources = new double[5][independentSamples];


        double[][] data = {
                {5.0, 5.3, 5.10},
                {5.5, 5.10, 5.4},
                {5.0, 5.1, 5.1},
                {5.0, 8.0, 0.0},
                {5.0, 8.0, 0.0}
        };

        double[][] independentData =
                independentMultiplyStatic(data, independentSources);

        FastICA_SemanticScholar independentFastICA = new FastICA_SemanticScholar(
                5,
                5000,
                1e-5,
                Nonlinearity.INDEPENDENT_TANH,
                50L
        );

        Result independentResult = independentFastICA.independentFit(independentData);

        System.out.println("FastICA 결과 : 모든 성분은 독립적이고 성분은 다른 성분의 변화, 데이터, 분포에 완전히 무관하며 놓치거나 제거가 필요한 성분들이 완전히 독립적임을 나타냅니다. : "+independentResult);

    }


}
profile
꾸준히 성장하는 백엔드 개발자 입니다!

0개의 댓글