【问题标题】:Efficient implementation of mutual information in JavaJava中互信息的高效实现
【发布时间】:2011-09-29 18:07:15
【问题描述】:

我希望使用 Java 计算两个特征之间的互信息。

我已经阅读了Calculating Mutual Information For Selecting a Training Set in Java,但那是关于互信息是否适合发帖者的讨论,只有一些关于实现的简单伪代码。

我当前的代码如下,但我希望有办法对其进行优化,因为我有大量信息需要处理。我知道调用另一种语言/框架可能会提高速度,但现在想专注于用 Java 解决这个问题。

非常感谢任何帮助。

public static double calculateNewMutualInformation(double frequencyOfBoth, double frequencyOfLeft,
                                                   double frequencyOfRight, int noOfTransactions) {
    if (frequencyOfBoth == 0 || frequencyOfLeft == 0 || frequencyOfRight == 0)
        return 0;
    // supp = f11
    double supp = frequencyOfBoth / noOfTransactions; // P(x,y)
    double suppLeft = frequencyOfLeft / noOfTransactions; // P(x)
    double suppRight = frequencyOfRight / noOfTransactions; // P(y)
    double f10 = (suppLeft - supp); // P(x) - P(x,y)
    double f00 = (1 - suppRight) - f10; // (1-P(y)) - P(x,y)
    double f01 = (suppRight - supp); // P(y) - P(x,y)

    // -1 * ((P(x) * log(Px)) + ((1 - P(x)) * log(1-p(x)))
    double HX = -1 * ((suppLeft * MathUtils.logWithoutNaN(suppLeft)) + ((1 - suppLeft) * MathUtils.logWithoutNaN(1 - suppLeft)));
    // -1 * ((P(y) * log(Py)) + ((1 - P(y)) * log(1-p(y)))
    double HY = -1 * ((suppRight * MathUtils.logWithoutNaN(suppRight)) + ((1 - suppRight) * MathUtils.logWithoutNaN(1 - suppRight)));

    double one = (supp * MathUtils.logWithoutNaN(supp)); // P(x,y) * log(P(x,y))
    double two = (f10 * MathUtils.logWithoutNaN(f10)); 
    double three = (f01 * MathUtils.logWithoutNaN(f01));
    double four = (f00 * MathUtils.logWithoutNaN(f00));
    double HXY = -1 * (one + two + three + four);
    return (HX + HY - HXY) / (HX == 0 ? MathUtils.EPSILON : HX);
}        

public class MathUtils {
public static final double EPSILON = 0.000001;

public static double logWithoutNaN(double value) {
    if (value == 0) {
        return Math.log(EPSILON);
    } else if (value < 0) {
        return 0;
    }
    return Math.log(value);
}  

【问题讨论】:

  • 您是否测量了性能并认为它很慢?
  • 很好的问题,但是你能在互信息的上下文中将每个符号映射到它的变量中吗?因为我有点困惑。

标签: java optimization machine-learning


【解决方案1】:

我发现以下方法很快,但我没有将它与您的方法进行比较 - 仅在 weka 中提供。

它的工作前提是重新排列 MI 方程,从而可以最大限度地减少浮点运算的次数:

我们首先将 定义为样本/事务数的计数/频率。因此,我们将项目数定义为 n,x 出现的次数为 |x|,y 出现的次数为 |y|以及它们作为 |x,y| 共同出现的次数。然后我们得到,

.

现在,我们可以通过翻转内部分隔的底部来重新排列它,得到 (n|x,y|)/(|x||y|)。此外,计算使用 N = 1/n,因此我们少了一个除法运算。这给了我们:

这给了我们以下代码:

/***
 * Computes MI between variables t and a. Assumes that a.length == t.length.
 * @param a candidate variable a
 * @param avals number of values a can take (max(a) == avals)
 * @param t target variable
 * @param tvals number of values a can take (max(t) == tvals)
 * @return 
 */
static double computeMI(int[] a, int avals, int[] t, int tvals) {
    double numinst = a.length;
    double oneovernuminst = 1/numinst;
    double sum = 0;

    // longs are required here because of big multiples in calculation
    long[][] crosscounts = new long[avals][tvals];
    long[] tcounts = new long[tvals];
    long[] acounts = new long[avals];
    // Compute counts for the two variables
    for (int i=0;i<a.length;i++) {
        int av = a[i];
        int tv = t[i];
        acounts[av]++;
        tcounts[tv]++;
        crosscounts[av][tv]++;
    }

    for (int tv=0;tv<tvals;tv++) {
        for (int av=0;av<avals;av++) {
            if (crosscounts[av][tv] != 0) {
                // Main fraction: (n|x,y|)/(|x||y|)
                double sumtmp = (numinst*crosscounts[av][tv])/(acounts[av]*tcounts[tv]);
                // Log bit (|x,y|/n) and update product
                sum += oneovernuminst*crosscounts[av][tv]*Math.log(sumtmp)*log2;
            }
        }

    }

    return sum;
}

此代码假定 a 和 t 的值不是稀疏的(即 min(t)=0 和 tvals=max(t))以使其高效。否则(如评论)会创建大型且不必要的数组。

我相信这种方法在一次计算多个变量之间的 MI 时会进一步改进(计数操作可以被压缩——尤其是目标的操作)。我使用的实现是与 WEKA 接口的实现。

最后,将日志从求和中取出可能更有效。但我不确定日志或功率是否会在循环中进行更多计算。这是由以下人员完成的:

  1. 应用 a*log(b) = log(a^b)
  2. 将日志移到求和之外,使用 log(a)+log(b) = log(ab)

并给出:

【讨论】:

  • 考虑到 OP 要求一种有效的实现,您提供的代码有一个小的效率低下和一个潜在的巨大效率低下。
  • ...正如我所说...首先您可以将常量“log2”移出循环。其次,交叉计数矩阵可能非常稀疏,因此您执行大量无意义的迭代,交叉计数只需 a.length * t.length,例如如果 a 和 t 都是 {10, 100, 1000} 那么你有一百万个单元矩阵,只有 9 个非零值。
  • 我认为答案提到将日志移出循环(尽管不在代码段中)。我同意关于稀疏性的问题,但我会假设价值观没有差距。如果不是,或者有其他原因导致数据稀疏,稀疏矩阵库会有所帮助。
  • 我认为计算这两个变量的部分不正确!它如何计算每个变量的发生次数??
  • @MaryamFekri 怎么样?它在计数数组中创建一个“直方图”(以值作为索引)。它假设值是从零开始的连续值。无论如何,这给出了与 WEKA 的 InfoGainAttEval 相同的结果。
【解决方案2】:

我不是数学家,但是..

这里只有一堆浮点计算。一些数学家可能会减少计算量,试试Math SE

同时,您应该可以将static final double 用于Math.log(EPSILON)

您的问题可能不是单个调用,而是必须进行此计算的数据量。这个问题最好通过投入更多硬件来解决。

【讨论】:

  • 看起来没有灵丹妙药,但这是一个好的开始,谢谢
  • @Ina 在接受我的回答两年半后,你回来把它拿走 :( 很高兴你找到了正确的解决方案。
猜你喜欢
  • 2014-09-01
  • 2021-10-08
  • 1970-01-01
  • 1970-01-01
  • 2011-06-24
  • 2012-10-15
  • 1970-01-01
  • 2012-07-23
  • 1970-01-01
相关资源
最近更新 更多