/* * File: ContinuousDensityHiddenMarkovModelTest.java * Authors: Kevin R. Dixon * Company: Sandia National Laboratories * Project: Cognitive Foundry * * Copyright Jan 19, 2010, Sandia Corporation. * Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive * license for use of this work by or on behalf of the U.S. Government. * Export of this program may require a license from the United States * Government. See CopyrightHistory.txt for complete details. * */ package gov.sandia.cognition.learning.algorithm.hmm; import gov.sandia.cognition.math.matrix.Matrix; import gov.sandia.cognition.math.matrix.MatrixFactory; import gov.sandia.cognition.math.matrix.Vector; import gov.sandia.cognition.math.matrix.VectorFactory; import gov.sandia.cognition.math.matrix.mtj.DenseMatrix; import gov.sandia.cognition.math.matrix.mtj.decomposition.EigenDecompositionRightMTJ; import gov.sandia.cognition.statistics.ComputableDistribution; import gov.sandia.cognition.statistics.MultivariateDistributionTestHarness; import gov.sandia.cognition.statistics.distribution.BinomialDistribution; import gov.sandia.cognition.statistics.distribution.DefaultDataDistribution; import gov.sandia.cognition.statistics.distribution.MultivariateGaussian; import gov.sandia.cognition.util.WeightedValue; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; /** * Unit tests for ContinuousDensityHiddenMarkovModelTest. * * @author krdixon */ public class HiddenMarkovModelTest extends MultivariateDistributionTestHarness<Vector> { /** * Num states */ public static int DEFAULT_NUM_STATES = 3; /** * observation dim */ public static int DEFAULT_OBSERVATION_DIM = 1; /** * Tests for class ContinuousDensityHiddenMarkovModelTest. * @param testName Name of the test. */ public HiddenMarkovModelTest( String testName) { super(testName); } /** * Creates static CDHMM * @return * CDHMM */ public static HiddenMarkovModel<Vector> staticCreateInstance() { int k = DEFAULT_NUM_STATES; int dim = DEFAULT_OBSERVATION_DIM; ArrayList<MultivariateGaussian.PDF> pdfs = new ArrayList<MultivariateGaussian.PDF>( k ); Matrix C = MatrixFactory.getDefault().createIdentity(dim, dim).scale( 0.1); for( int i = 0; i < k; i++ ) { pdfs.add( new MultivariateGaussian.PDF( VectorFactory.getDefault().createVector( dim, i ), C.clone() ) ); } Matrix A = MatrixFactory.getDefault().createMatrix(k, k); A.setElement(0, 0, 0.5); A.setElement(1, 0, 0.2 ); A.setElement(2, 0, 0.3 ); A.setElement(0, 1, 0.3 ); A.setElement(1, 1, 0.5 ); A.setElement(2, 1, 0.2 ); A.setElement(0, 2, 0.3 ); A.setElement(1, 2, 0.2 ); A.setElement(2, 2, 0.5 ); Vector pi = VectorFactory.getDefault().copyValues( 0.5, 0.25, 0.25 ); return new HiddenMarkovModel<Vector>( pi, A, pdfs ); } /** * Creates an instance. * @return * CDHMM */ public HiddenMarkovModel<Vector> createInstance() { return staticCreateInstance(); } /** * Tests the constructors of class ContinuousDensityHiddenMarkovModelTest. */ @SuppressWarnings("unchecked") public void testConstructors() { System.out.println( "Constructors" ); HiddenMarkovModel<Vector> hmm = new HiddenMarkovModel<Vector>(); assertEquals( HiddenMarkovModel.DEFAULT_NUM_STATES, hmm.getNumStates() ); assertNull( hmm.getEmissionFunctions() ); int k = RANDOM.nextInt(10) + 10; hmm = new HiddenMarkovModel<Vector>( k ); assertEquals( k, hmm.getNumStates() ); assertNull( hmm.getEmissionFunctions() ); HiddenMarkovModel<Vector> hmm2 = this.createInstance(); hmm = new HiddenMarkovModel<Vector>( hmm2.getInitialProbability(), hmm2.getTransitionProbability(), hmm2.getEmissionFunctions() ); assertSame( hmm2.getInitialProbability(), hmm.getInitialProbability() ); assertSame( hmm2.getTransitionProbability(), hmm.getTransitionProbability() ); assertSame( hmm2.getEmissionFunctions(), hmm.getEmissionFunctions() ); } /** * Test of computeObservationLikelihoods method, of class ContinuousDensityHiddenMarkovModel. */ public void testComputeObservationLikelihoods() { System.out.println("computeObservationLikelihoods"); HiddenMarkovModel<Vector> instance = this.createInstance(); Vector observation = VectorFactory.getDefault().copyValues(1.0); Vector result = instance.computeObservationLikelihoods(observation); Vector expected = VectorFactory.getDefault().copyValues( 0.008500366602520341, 1.26156626101008, 0.008500366602520341 ); System.out.println( "Observation: " + observation ); System.out.println( "Result: " + result ); System.out.println( "Expected: " + expected ); assertTrue( expected.equals( result, TOLERANCE ) ); } /** * Test of computeForwardProbabilities method, of class ContinuousDensityHiddenMarkovModel. */ public void testKnownValues() { System.out.println("computeForwardProbabilities"); HiddenMarkovModel<Vector> instance = this.createInstance(); ArrayList<Vector> observations = instance.sample(RANDOM, 10); ArrayList<Vector> b = instance.computeObservationLikelihoods(observations); boolean normalize = true; ArrayList<WeightedValue<Vector>> normalizedAlphas = instance.computeForwardProbabilities(b,normalize); double logLikelihood1 = 0.0; for( WeightedValue<Vector> a : normalizedAlphas ) { logLikelihood1 -= Math.log(a.getWeight()); System.out.println( a.getWeight() + ": " + a.getValue() ); assertEquals( 1.0, a.getValue().norm1(), TOLERANCE ); } normalize = false; ArrayList<WeightedValue<Vector>> unnormalizedAlphas = instance.computeForwardProbabilities(b,normalize); double logLikelihood2 = Math.log( unnormalizedAlphas.get(observations.size()-1).getValue().norm1() ); assertEquals( logLikelihood1, logLikelihood2, TOLERANCE ); } /** * Test of computeBackwardProbabilities method, of class ContinuousDensityHiddenMarkovModel. */ public void testComputeBackwardProbabilities_ArrayList() { System.out.println("computeBackwardProbabilities"); HiddenMarkovModel<Vector> instance = this.createInstance(); ArrayList<Vector> observations = instance.sample(RANDOM, 10); ArrayList<Vector> bs = instance.computeObservationLikelihoods(observations); boolean normalize = true; ArrayList<WeightedValue<Vector>> alphas = instance.computeForwardProbabilities(bs, normalize); ArrayList<WeightedValue<Vector>> normalizedBetas = instance.computeBackwardProbabilities(bs, alphas); double logLikelihood1 = 0.0; for( WeightedValue<Vector> b : normalizedBetas ) { logLikelihood1 -= Math.log(b.getWeight()); System.out.println( b.getWeight() + ": " + b.getValue() ); // assertEquals( 1.0, b.getValue().norm1(), TOLERANCE ); } normalize = false; alphas = instance.computeForwardProbabilities(bs, normalize); ArrayList<WeightedValue<Vector>> unnormalizedBetas = instance.computeBackwardProbabilities(bs, alphas); Vector beta0 = unnormalizedBetas.get(0).getValue(); double logLikelihood2 = Math.log(beta0.norm1()); // assertEquals( logLikelihood1, logLikelihood2, TOLERANCE ); } /** * Test of createUniformInitialProbability method, of class ContinuousDensityHiddenMarkovModel. */ public void testCreateUniformInitialProbability() { System.out.println("createUniformInitialProbability"); int numStates = RANDOM.nextInt(10) + 1; Vector result = HiddenMarkovModel.createUniformInitialProbability(numStates); double p = 1.0/numStates; assertEquals( numStates, result.getDimensionality() ); for( int i = 0; i < numStates; i++ ) { assertEquals( p, result.getElement(i) ); } } /** * Test of createUniformTransitionProbability method, of class ContinuousDensityHiddenMarkovModel. */ public void testCreateUniformTransitionProbability() { System.out.println("createUniformTransitionProbability"); int numStates = RANDOM.nextInt(10) + 1; double p = 1.0/numStates; Matrix result = HiddenMarkovModel.createUniformTransitionProbability(numStates); assertEquals( numStates, result.getNumRows() ); assertEquals( numStates, result.getNumColumns() ); for( int j = 0; j < numStates; j++ ) { for( int i = 0; i < numStates; i++ ) { assertEquals( p, result.getElement(i, j) ); } } } /** * Test of getNumStates method, of class ContinuousDensityHiddenMarkovModel. */ public void testGetNumStates() { System.out.println("getNumStates"); HiddenMarkovModel<Vector> instance = this.createInstance(); assertEquals( DEFAULT_NUM_STATES, instance.getNumStates() ); } /** * Test of toString method, of class ContinuousDensityHiddenMarkovModel. */ @Override public void testToString() { System.out.println("toString"); HiddenMarkovModel<Vector> instance = this.createInstance(); String s = instance.toString(); System.out.println( "toSring: " + s ); assertNotNull( s ); } /** * Test of getInitialProbability method, of class ContinuousDensityHiddenMarkovModel. */ public void testGetInitialProbability() { System.out.println("getInitialProbability"); HiddenMarkovModel<Vector> instance = this.createInstance(); assertEquals( 1.0, instance.getInitialProbability().norm1(), TOLERANCE ); } /** * Test of setInitialProbability method, of class ContinuousDensityHiddenMarkovModel. */ public void testSetInitialProbability() { System.out.println("setInitialProbability"); HiddenMarkovModel<Vector> instance = this.createInstance(); Vector p = instance.getInitialProbability().clone(); instance.setInitialProbability(p); assertSame( p, instance.getInitialProbability() ); Vector p2 = p.scale(2.0); Vector p2clone = p2.clone(); instance.setInitialProbability(p2); assertEquals( 1.0, instance.getInitialProbability().norm1() ); assertFalse( p2.equals( p2clone ) ); assertEquals( p2, p2clone.scale(1.0/p2clone.norm1() ) ); p.setElement(0, -1.0); try { instance.setInitialProbability(p); fail( "initial probability must be >= 0.0" ); } catch (Exception e) { System.out.println( "Good: " + e ); } try { instance.setInitialProbability(null); fail( "Cannot be null" ); } catch (Exception e) { System.out.println( "Good: " + e ); } } /** * Test of getTransitionProbability method, of class ContinuousDensityHiddenMarkovModel. */ public void testGetTransitionProbability() { System.out.println("getTransitionProbability"); HiddenMarkovModel<Vector> instance = this.createInstance(); for( int j = 0; j < instance.getNumStates(); j++ ) { assertEquals( 1.0, instance.getTransitionProbability().getColumn(j).norm1(), TOLERANCE ); } } /** * Test of setTransitionProbability method, of class ContinuousDensityHiddenMarkovModel. */ public void testSetTransitionProbability() { System.out.println("setTransitionProbability"); HiddenMarkovModel<Vector> instance = this.createInstance(); Matrix A = instance.getTransitionProbability(); Matrix A2 = A.scale(2.0); Matrix A2clone = A2.clone(); instance.setTransitionProbability(A2); for( int j = 0; j < instance.getNumStates(); j++ ) { Vector Aj = instance.getTransitionProbability().getColumn(j); Vector A2clonej = A2clone.getColumn(j); assertFalse( Aj.equals(A2clonej) ); assertEquals( Aj, A2clonej.scale(1.0/A2clonej.norm1() ) ); } A2.setElement(0, 0, -1.0); try { instance.setTransitionProbability(A2); fail( "Transition Probabilities must be >= 0.0" ); } catch (Exception e) { System.out.println( "Good: " + e ); } try { instance.setTransitionProbability(null); fail( "Cannot be null" ); } catch (Exception e) { System.out.println( "Good: " + e ); } } /** * Test of getEmissionFunctions method, of class ContinuousDensityHiddenMarkovModel. */ public void testGetEmissionFunctions() { System.out.println("getEmissionFunctions"); HiddenMarkovModel<Vector> instance = this.createInstance(); assertEquals( instance.getNumStates(), instance.getEmissionFunctions().size() ); } /** * Test of setEmissionFunctions method, of class ContinuousDensityHiddenMarkovModel. */ public void testSetEmissionFunctions() { System.out.println("setEmissionFunctions"); HiddenMarkovModel<Vector> instance = this.createInstance(); Collection<? extends ComputableDistribution<Vector>> emissionFunctions = instance.getEmissionFunctions(); assertNotNull( emissionFunctions ); instance.setEmissionFunctions(null); assertNull( instance.getEmissionFunctions() ); instance.setEmissionFunctions(emissionFunctions); assertSame( emissionFunctions, instance.getEmissionFunctions() ); } /** * Tests getSteadyStateDistribution */ public void testGetSteadyStateDistribution() { System.out.println( "getSteadyStateDistribution" ); HiddenMarkovModel<Vector> instance = this.createInstance(); Vector phat = instance.getSteadyStateDistribution(); EigenDecompositionRightMTJ evd = EigenDecompositionRightMTJ.create( (DenseMatrix) instance.getTransitionProbability() ); Vector p = evd.getEigenVectorsRealPart().getColumn(0); // We do the manual sum (instead of norm1) in case the EVD found // the negative of the eigenvector. double sum = 0.0; for( int i = 0; i < p.getDimensionality(); i++ ) { sum += p.getElement(i); } p.scaleEquals( 1.0/sum ); System.out.println( "P: " + p ); System.out.println( "Phat: " + phat ); assertTrue( p.equals( phat, TOLERANCE ) ); } /* @Override public void testGetMean() { System.out.println( "getMean" ); int temp = NUM_SAMPLES; NUM_SAMPLES = 10000; double tt = TOLERANCE; TOLERANCE = 1e-2; HiddenMarkovModel<Vector> instance = this.createInstance(); Collection<Vector> samples = instance.sample(RANDOM, NUM_SAMPLES); Vector sampleMean = MultivariateStatisticsUtil.computeMean(samples); Vector result = instance.getMean(); if( !result.equals( sampleMean, TOLERANCE ) ) { assertEquals( result, sampleMean ); } HiddenMarkovModel<Double> hmm2 = new HiddenMarkovModel<Double>(); int k = hmm2.getNumStates(); ArrayList<UnivariateGaussian.PDF> pdfs = new ArrayList<UnivariateGaussian.PDF>( k ); for( int i = 0; i < k; i++ ) { UnivariateGaussian.PDF f = new UnivariateGaussian.PDF( RANDOM.nextGaussian(), RANDOM.nextDouble() ); pdfs.add( f ); } hmm2.setEmissionFunctions(pdfs); Collection<Double> samples2 = hmm2.sample(RANDOM, NUM_SAMPLES ); Double sampleMean2 = UnivariateStatisticsUtil.computeMean(samples2); Double result2 = hmm2.getMean(); assertEquals( result2, sampleMean2, TOLERANCE ); TOLERANCE = tt; NUM_SAMPLES = temp; HiddenMarkovModel<String> hmm3 = new HiddenMarkovModel<String>(); k = hmm3.getNumStates(); ArrayList<DefaultDataDistribution.PMF<String>> sf = new ArrayList<DefaultDataDistribution.PMF<String>>( k ); for( int i = 0; i < k; i++ ) { DefaultDataDistribution.PMF<String> f = new DefaultDataDistribution.PMF<String>(); f.add( "a" ); sf.add( f ); } hmm3.setEmissionFunctions(sf); try { String sm = hmm3.getMean(); fail( "Mean not implemented for Strings" ); } catch (Exception e) { System.out.println( "Good: " + e ); } } */ /** * viterbi */ public void testViterbi() { System.out.println( "viterbi" ); HiddenMarkovModel<Vector> instance = this.createInstance(); Collection<Vector> samples = instance.sample(RANDOM, 10); ArrayList<Integer> states = instance.viterbi(samples); assertEquals( samples.size(), states.size() ); for( Integer state : states ) { System.out.print( state + "->" ); assertTrue( 0 <= state ); assertTrue( state < instance.getNumStates() ); } System.out.println( " Done" ); double all = instance.computeObservationLogLikelihood(samples); double ml = instance.computeObservationLogLikelihood(samples, states); System.out.println( "All sequences: " + all ); System.out.println( "ML sequence: " + ml ); assertTrue( ml <= all ); assertEquals( -13.051414871301235, ml, TOLERANCE ); } /** * createRandom */ public void testCreateRandom() { System.out.println( "createRandom" ); Collection<String> data = Arrays.asList( "a", "b", "c", "d", "a" ); DefaultDataDistribution.WeightedEstimator<String> learner = new DefaultDataDistribution.WeightedEstimator<String>(); HiddenMarkovModel<String> hmm = HiddenMarkovModel.createRandom( 5, learner, data, RANDOM ); System.out.println( "HMM:\n" + hmm ); BinomialDistribution distribution = new BinomialDistribution( 10, RANDOM.nextDouble() ); HiddenMarkovModel<Number> hmm2 = HiddenMarkovModel.createRandom( 4, distribution, RANDOM ); System.out.println( "HMM2:\n" + hmm2 ); } }