All Downloads are FREE. Search and download functionalities are using the official Maven repository.

smile.vq.NeuralMap Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2021 Haifeng Li. All rights reserved.
 *
 * Smile is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Smile 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Smile.  If not, see .
 */

package smile.vq;

import java.util.*;
import smile.sort.HeapSelect;
import smile.vq.hebb.Neuron;
import smile.vq.hebb.Edge;

/**
 * NeuralMap is an efficient competitive learning algorithm inspired by growing
 * neural gas and BIRCH. Like growing neural gas, NeuralMap has the ability to
 * add and delete neurons with competitive Hebbian learning. Edges exist between
 * neurons close to each other. Such edges are intended place holders for
 * localized data distribution. Such edges also help to locate distinct clusters
 * (those clusters are not connected by edges).
 *
 * @see NeuralGas
 * @see GrowingNeuralGas
 * @see BIRCH
 * 
 * @author Haifeng Li
 */
public class NeuralMap implements VectorQuantizer {
    private static final long serialVersionUID = 2L;

    /**
     * The number of signals processed so far.
     */
    private int t = 0;
    /**
     * The distance radius to activate a neuron for a given signal.
     */
    private final double r;
    /**
     * The maximum age of edges.
     */
    private final int edgeLifetime;
    /**
     * The learning rate to update nearest neuron.
     */
    private final double epsBest;
    /**
     * The learning to update neighbors of nearest neuron.
     */
    private final double epsNeighbor;
    /**
     * Decrease the freshness of all neurons by multiply them with beta.
     */
    private final double beta;
    /**
     * Neurons in the neural network.
     */
    private final ArrayList neurons = new ArrayList<>();
    /**
     * The workspace to find nearest neighbors.
     */
    private final Neuron[] top2 = new Neuron[2];

    /**
     * Constructor.
     * @param r the distance threshold to activate the nearest neuron of a signal.
     * @param epsBest the learning rate to update activated neuron.
     * @param epsNeighbor the learning rate to update neighbors of activated neuron.
     * @param edgeLifetime the maximum age of edges.
     * @param beta decrease the freshness of all neurons by multiply them with beta.
     */
    public NeuralMap(double r, double epsBest, double epsNeighbor, int edgeLifetime, double beta) {
        this.r = r;
        this.epsBest = epsBest;
        this.epsNeighbor = epsNeighbor;
        this.edgeLifetime = edgeLifetime;
        this.beta = beta;
    }

    @Override
    public void update(double[] x) {
        t++;

        if (neurons.size() < 2) {
            neurons.add(new Neuron(x.clone()));
            return;
        }

        // Find the nearest (s1) and second nearest (s2) neuron to x.
        neurons.stream().parallel().forEach(neuron -> neuron.distance(x));

        Arrays.fill(top2, null);
        HeapSelect heap = new HeapSelect<>(top2);
        for (Neuron neuron : neurons) {
            heap.add(neuron);
        }

        Neuron s1 = top2[1];
        Neuron s2 = top2[0];

        if (s1.distance > r) {
            Neuron neuron = new Neuron(x.clone());
            neurons.add(neuron);
            return;
        }

        if (s2.distance > r) {
            Neuron neuron = new Neuron(x.clone());
            neurons.add(neuron);

            s1.addEdge(neuron);
            neuron.addEdge(s1);
            return;
        }

        // update s1
        s1.update(x, epsBest);
        // Increase the freshness of neuron.
        s1.counter += 1;
        // Increase the edge of all edges emanating from s1.
        s1.age();

        boolean addEdge = true;
        for (Edge edge : s1.edges) {
            // Update s1's direct topological neighbors towards x.
            Neuron neighbor = edge.neighbor;
            neighbor.update(x, epsNeighbor);

            // Set the age to zero if s1 and s2 are already connected.
            if (neighbor == s2) {
                edge.age = 0;
                s2.setEdgeAge(s1, 0);
                addEdge = false;
            }
        }

        // Connect s1 and s2 if they are not neighbor yet.
        if (addEdge) {
            s1.addEdge(s2);
            s2.addEdge(s1);
            s2.update(x, epsNeighbor);
        }

        // Remove edges with an age larger than the threshold
        for (Iterator iter = s1.edges.iterator(); iter.hasNext();) {
            Edge edge = iter.next();
            if (edge.age > edgeLifetime) {
                iter.remove();

                Neuron neighbor = edge.neighbor;
                neighbor.removeEdge(s1);
                // Remove a neuron if it has no emanating edges
                if (neighbor.edges.isEmpty()) {
                    neurons.removeIf(neuron -> neuron == edge.neighbor);
                }
            }
        }

        // Decrease all error variables.
        for (Neuron neuron : neurons) {
            neuron.counter *= beta;
        }
    }

    /**
     * Returns the neurons.
     * @return the neurons.
     */
    public Neuron[] neurons() {
        return neurons.toArray(new Neuron[0]);
    }
    
    /**
     * Removes staled neurons and the edges beyond lifetime.
     * Neurons without emanating edges will be removed too.
     * @param eps the freshness threshold of neurons. It should
     *            be a small value (e.g. 1E-7).
     */
    public void clear(double eps) {
        ArrayList noise = new ArrayList<>();
        for (Neuron neuron : neurons) {
            if (neuron.counter < eps) {
                for (Edge edge : neuron.edges) {
                    edge.neighbor.removeEdge(neuron);
                }
                neuron.edges.clear();
            } else {
                for (Iterator iter = neuron.edges.iterator(); iter.hasNext(); ) {
                    Edge edge = iter.next();
                    if (edge.age > edgeLifetime) {
                        edge.neighbor.removeEdge(neuron);
                        iter.remove();
                    }
                }
            }

            if (neuron.edges.isEmpty()) {
                noise.add(neuron);
            }
        }

        neurons.removeAll(noise);
    }

    @Override
    public double[] quantize(double[] x) {
        neurons.stream().parallel().forEach(node -> node.distance(x));

        Neuron bmu = neurons.get(0);
        for (Neuron neuron : neurons) {
            if (neuron.distance < bmu.distance) {
                bmu = neuron;
            }
        }

        return bmu.w;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy