/* * Copyright 1999-2002 Carnegie Mellon University. * Portions Copyright 2002 Sun Microsystems, Inc. * Portions Copyright 2002 Mitsubishi Electric Research Laboratories. * All Rights Reserved. Use is subject to license terms. * * See the file "license.terms" for information on usage and * redistribution of this file, and for a DISCLAIMER OF ALL * WARRANTIES. */ package edu.cmu.sphinx.util; /** * Provides a set of methods for performing simple math in the log domain. * * The logarithmic base can be set by the * property: <code>edu.cmu.sphinx.util.LogMath.logBase</code> */ public final class LogMath { public static final float LOG_ZERO = -Float.MAX_VALUE; public static final float LOG_ONE = 0.f; // Singleton instance. private static LogMath instance; private static float logBase = 1.0001f; private static boolean useTable = true; private float naturalLogBase; private float inverseNaturalLogBase; private float theAddTable[]; private LogMath() { naturalLogBase = (float) Math.log(logBase); inverseNaturalLogBase = 1.0f / naturalLogBase; if (useTable) { // Now create the addTable table. // summation needed in the loop float innerSummation; // First decide number of elements. int entriesInTheAddTable; final int veryLargeNumberOfEntries = 150000; final int verySmallNumberOfEntries = 0; // To decide size of table, take into account that a base // of 1.0001 or 1.0003 converts probabilities, which are // numbers less than 1, into integers. Therefore, a good // approximation for the smallest number in the table, // therefore the value with the highest index, is an // index that maps into 0.5: indices higher than that, if // they were present, would map to less values less than // 0.5, therefore they would be mapped to 0 as // integers. Since the table implements the expression: // // log(1.0 + base^(-index))) // // then the highest index would be: // // topIndex = - log(logBase^(0.5) - 1) // // where log is the log in the appropriate base. // // Added -Math.rint(...) to round to nearest // integer. Added the negation to match the preceding // documentation entriesInTheAddTable = (int) -Math .rint(linearToLog(logToLinear(0.5f) - 1)); // We reach this max if the log base is 1.00007. The // closer you get to 1, the higher the number of entries // in the table. if (entriesInTheAddTable > veryLargeNumberOfEntries) { entriesInTheAddTable = veryLargeNumberOfEntries; } if (entriesInTheAddTable <= verySmallNumberOfEntries) { throw new IllegalArgumentException("The log base " + logBase + " yields a very small addTable. " + "Either choose not to use the addTable, " + "or choose a logBase closer to 1.0"); } // PBL added this just to see how many entries really are // in the table theAddTable = new float[entriesInTheAddTable]; for (int index = 0; index < entriesInTheAddTable; ++index) { // This loop implements the expression: // // log( 1.0 + power(base, index)) // // needed to add two numbers in the log domain. innerSummation = (float) logToLinear(-index); innerSummation += 1.0f; theAddTable[index] = linearToLog(innerSummation); } } } public static LogMath getLogMath() { if (null == instance) { synchronized(LogMath.class) { if (null == instance) instance = new LogMath(); } } return instance; } /** * Sets log base. * <p> * According to forum discussions a value between 1.00001 and 1.0004 should * be used for speech recognition. Going above 1.0005 will probably hurt. * * @param logBase Log base */ public static void setLogBase(float logBase) { synchronized(LogMath.class) { assert instance == null; LogMath.logBase = logBase; } } /** * The property that controls whether we use the old, slow (but correct) * method of performing the LogMath.add by doing the actual computation. * @param useTable to configure table lookups */ public static void setUseTable(boolean useTable) { synchronized(LogMath.class) { assert instance == null; LogMath.useTable = useTable; } } /** * Returns the summation of two numbers when the arguments and the result are in log. <p> That is, it returns * log(a + b) given log(a) and log(b) </p> <p> This method makes use of the equality: </p> <p> <b>log(a * + b) = log(a) + log (1 + exp(log(b) - log(a))) </b> </p> <p> which is derived from: </p> <p> <b>a + b * = a * (1 + (b / a)) </b> </p> <p> which in turns makes use of: </p> <p> <b>b / a = exp (log(b) - * log(a)) </b> </p> <p> Important to notice that <code>subtractAsLinear(a, b)</code> is *not* the same as * <code>addAsLinear(a, -b)</code>, since we're in the log domain, and -b is in fact the inverse. </p> <p> No * underflow/overflow check is performed. </p> * * @param logVal1 value in log domain (i.e. log(val1)) to add * @param logVal2 value in log domain (i.e. log(val2)) to add * @return sum of val1 and val2 in the log domain */ public final float addAsLinear(float logVal1, float logVal2) { float logHighestValue = logVal1; float logDifference = logVal1 - logVal2; /* * [ EBG: maybe we should also have a function to add many numbers, * * say, return the summation of all terms in a given vector, if * * efficiency becomes an issue. */ // difference is always a positive number if (logDifference < 0) { logHighestValue = logVal2; logDifference = -logDifference; } return logHighestValue + addTable(logDifference); } /** * Method used by add() internally. It returns the difference between the highest number and the total summation of * two numbers. <p> Considering the expression (in which we assume natural log) <p> <b>log(a + b) = log(a) + * log(1 + exp(log(b) - log(a))) </b> </p> * <p> * the current function returns the second term of the right hand side of the equality above, generalized for the * case of any log base. This function can be constructed as a table, if table lookup is faster than actual * computation. * * @param index the index into the addTable * @return the value pointed to by index */ @SuppressWarnings("unused") private float addTableActualComputation(float index) { double logInnerSummation; // Negate index, since the derivation of this formula implies // the smallest number as a numerator, therefore the log of the // ratio is negative logInnerSummation = logToLinear(-index); logInnerSummation += 1.0; return linearToLog(logInnerSummation); } /** * Method used by add() internally. It returns the difference between the highest number and the total summation of * two numbers. <p> Considering the expression (in which we assume natural log) <p> <b>log(a + b) = log(a) + * log(1 + exp(log(b) - log(a))) </b> </p> * <p> * the current function returns the second term of the right hand side of the equality above, generalized for the * case of any log base. This function is constructed as a table lookup. * * @param index the index into the addTable * @return the value pointed to by index * @throws IllegalArgumentException */ private float addTable(float index) throws IllegalArgumentException { // int intIndex = (int) Math.rint(index); int intIndex = (int) (index + 0.5); // When adding two numbers, the highest one should be // preserved, and therefore the difference should always // be positive. if (intIndex < theAddTable.length) { return theAddTable[intIndex]; } else { return 0.0f; } } /** * Returns the difference between two numbers when the arguments and the result are in log. <p> That is, it * returns log(a - b) given log(a) and log(b) </p> <p> Implementation is less efficient than add(), since * we're less likely to use this function, provided for completeness. Notice however that the result only makes * sense if the minuend is higher than the subtrahend. Otherwise, we should return the log of a negative number. * </p> <p> It implements the subtraction as: </p> <p> <b>log(a - b) = log(a) + log(1 - exp(log(b) - * log(a))) </b> </p> <p> No need to check for underflow/overflow. </p> * * @param logMinuend value in log domain (i.e. log(minuend)) to be subtracted from * @param logSubtrahend value in log domain (i.e. log(subtrahend)) that is being subtracted * @return difference between minuend and the subtrahend in the log domain * @throws IllegalArgumentException <p> This is a very slow way to do this, but this method should rarely be used. * </p> */ public final float subtractAsLinear(float logMinuend, float logSubtrahend) throws IllegalArgumentException { double logInnerSummation; if (logMinuend < logSubtrahend) { throw new IllegalArgumentException("Subtraction results in log " + "of a negative number: " + logMinuend + " - " + logSubtrahend); } logInnerSummation = 1.0; logInnerSummation -= logToLinear(logSubtrahend - logMinuend); return logMinuend + linearToLog(logInnerSummation); } /** * Converts the source, which is assumed to be a log value whose base is sourceBase, to a log value whose base is * resultBase. Possible values for both the source and result bases include Math.E, 10.0, LogMath.getLogBase(). If a * source or result base is not supported, an IllegalArgumentException will be thrown. <p> It takes advantage * of the relation: </p> <p> <b>log_a(b) = log_c(b) / lob_c(a) </b> </p> <p> or: </p> <p> * <b>log_a(b) = log_c(b) * lob_a(c) </b> </p> <p> where <b>log_a(b) </b> is logarithm of <b>b </b> base <b>a * </b> etc. </p> * * @param logSource log value whose base is sourceBase * @param sourceBase the base of the log the source * @param resultBase the base to convert the source log to * @return converted value * @throws IllegalArgumentException if arguments out of bounds */ public static float logToLog(float logSource, float sourceBase, float resultBase) throws IllegalArgumentException { // TODO: This is slow, but it probably doesn't need // to be too fast. // It can be made more efficient if one of the bases is // Math.E. So maybe we should consider two functions logToLn and // lnToLog instead of a generic function like this?? float lnSourceBase = (float) Math.log(sourceBase); float lnResultBase = (float) Math.log(resultBase); return (logSource * lnSourceBase / lnResultBase); } /** * Converts the source, which is a number in base Math.E, to a log value which base is the LogBase of this LogMath. * * @return converted value * @param logSource the number in base Math.E to convert */ public final float lnToLog(float logSource) { return (logSource * inverseNaturalLogBase); } /** * Converts the source, which is a number in base 10, to a log value which base is the LogBase of this LogMath. * * @return converted value * @param logSource the number in base Math.E to convert */ public final float log10ToLog(float logSource) { return logToLog(logSource, 10.0f, logBase); } /** * Converts the source, whose base is the LogBase of this LogMath, to a log value which is a number in base Math.E. * * @param logSource the number to convert to base Math.E * @return converted value */ public final float logToLn(float logSource) { return logSource * naturalLogBase; } /** * Converts the value from linear scale to log scale. The log scale numbers are limited by the range of the type * float. The linear scale numbers can be any double value. * * @param linearValue the value to be converted to log scale * @return the value in log scale * @throws IllegalArgumentException if value out of range */ public final float linearToLog(double linearValue) throws IllegalArgumentException { return (float)Math.log(linearValue) * inverseNaturalLogBase; } /** * Converts the value from log scale to linear scale. * * @param logValue the value to be converted to the linear scale * @return the value in the linear scale */ public final double logToLinear(float logValue) { return Math.exp(logToLn(logValue)); } /** @return the actual log base. */ public final float getLogBase() { return logBase; } public boolean isUseTable() { return useTable; } /** * Returns the log (base 10) of value * * @param value the value to take the log of * @return the log (base 10) of value */ // [ EBG: Shouldn't we be using something like logToLog(value, base, 10) // for this? ] public static float log10(float value) { return (float) (0.4342944819 * java.lang.Math.log(value)); // If you want to get rid of the constant: // return ((1.0f / Math.log(10.0f)) * Math.log(value)); } /** Converts a vector from linear domain to log domain using a given <code>LogMath</code>-instance for conversion. * @param vector to convert in-place */ public void linearToLog(float[] vector) { int nbGaussians = vector.length; for (int i = 0; i < nbGaussians; i++) { vector[i] = linearToLog(vector[i]); } } /** Converts a vector from log to linear domain using a given <code>LogMath</code>-instance for conversion. * @param vector to convert * @param out result */ public void logToLinear(float[] vector, float[] out) { for (int i = 0; i < vector.length; i++) { out[i] = (float)logToLinear(vector[i]); } } }