org.eclipse.recommenders.jayes.transformation.LatentDeterministicDecomposition.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.recommenders.jayes.transformation.LatentDeterministicDecomposition.java

Source

/*******************************************************************************
 * Copyright (c) 2012 Michael Kutschke. All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: Michael Kutschke - initial API and implementation
 ******************************************************************************/
package org.eclipse.recommenders.jayes.transformation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;

import org.apache.commons.math.optimization.GoalType;
import org.apache.commons.math.optimization.OptimizationException;
import org.apache.commons.math.optimization.RealPointValuePair;
import org.apache.commons.math.optimization.linear.LinearConstraint;
import org.apache.commons.math.optimization.linear.LinearObjectiveFunction;
import org.apache.commons.math.optimization.linear.Relationship;
import org.apache.commons.math.optimization.linear.SimplexSolver;
import org.eclipse.recommenders.jayes.factor.AbstractFactor;
import org.eclipse.recommenders.jayes.transformation.util.CanonicalDoubleArrayManager;
import org.eclipse.recommenders.jayes.transformation.util.DecompositionFailedException;

import com.google.common.base.Functions;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;

/**
 * To be used when a conditional probability density consists of mostly the same few distributions, which is often the
 * case when a deterministic distribution is smoothed. <br/>
 * 
 * <pre>
 * Example:
 *  {
 *    { 0.99 0.01 }
 *    { 0.01 0.99 }
 *    { 0.01 0.99 }
 *  }
 * 
 *  would decompose to the new basis
 *  
 *  {
 *    { 0.01 0.99 }
 *    { 0.99 0.01 }
 *  }
 *  
 *  and 
 *  {
 *    { 0 1 }
 *    { 1 0 }
 *    { 1 0 }
 *  }
 * </pre>
 * 
 * <br/>
 * The decomposition is still applicable when there are some parts of the distribution that can not be directly
 * expressed by one of the basis vectors. In that case, that particular distribution will be represented as non-negative
 * linear combination of the basis vectors. Note that because of that, it is possible that the decomposition fails.
 * 
 */
public class LatentDeterministicDecomposition extends AbstractDecomposition {

    @Override
    protected List<double[]> getBasis(AbstractFactor f, List<double[]> vectors)
            throws DecompositionFailedException {
        Map<double[], Integer> counts = count(vectors);
        List<double[]> basis = getBest(counts, f.getValues().length() / vectors.size(), vectors.size() / 2);
        if (basis == null)
            throw new DecompositionFailedException("Could not find a good enough basis");
        return basis;
    }

    private Map<double[], Integer> count(List<double[]> vectors) {
        Map<double[], Integer> counts = new HashMap<double[], Integer>();
        for (double[] vector : Lists.transform(vectors, new CanonicalDoubleArrayManager())) {
            if (!counts.containsKey(vector)) {
                counts.put(vector, 0);
            }
            counts.put(vector, counts.get(vector) + 1);
        }

        return counts;
    }

    private List<double[]> getBest(final Map<double[], Integer> counts, int basisSize, int minTotalCounts) {
        PriorityQueue<double[]> q = new PriorityQueue<double[]>(basisSize,
                Ordering.natural().onResultOf(Functions.forMap(counts)));

        for (Entry<double[], Integer> e : counts.entrySet()) {
            if (q.isEmpty() || q.size() < basisSize) {
                q.add(e.getKey());
            } else {
                double[] head = q.peek();
                if (counts.get(head) < counts.get(e.getKey())) {
                    q.remove();
                    q.add(e.getKey());
                }
            }
        }

        int totalcounts = 0;
        for (double[] v : q) {
            totalcounts += counts.get(v);
        }
        if (totalcounts < minTotalCounts)
            return null;

        return new ArrayList<double[]>(q);
    }

    @Override
    protected double[] toLatentSpace(double[] v, List<double[]> basis) throws DecompositionFailedException {
        // we can assume here that equals works, we canonized everything before!
        int ind = basis.indexOf(v);
        if (ind != -1) {
            double[] l = new double[v.length];
            l[ind] = 1;
            return l;
        }
        // have to figure out a suitable non-negative linear combination of the base vectors
        // -> use simplex
        List<double[]> transposedBasis = transpose(basis);

        List<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
        for (int i = 0; i < v.length; i++) {
            LinearConstraint c = new LinearConstraint(transposedBasis.get(i), Relationship.EQ, v[i]);
            constraints.add(c);
        }

        LinearObjectiveFunction obj = new LinearObjectiveFunction(new double[v.length], 0);

        RealPointValuePair result;
        try {
            result = new SimplexSolver().optimize(obj, constraints, GoalType.MINIMIZE, true);
        } catch (OptimizationException e) {
            throw new DecompositionFailedException(e);
        }

        return result.getPoint();
    }

}