我想使用Java计算两个特征之间的互信息。
我已经阅读了在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);}
回答:
我发现以下方法运行速度很快,但我没有将其与你的方法进行比较——只与weka中提供的方法进行了比较。
它的工作原理是重新排列互信息方程,使得可以尽量减少浮点运算的数量:
我们首先定义为样本/交易数量上的计数/频率。因此,我们定义项目数量为n,x出现的次数为|x|,y出现的次数为|y|,它们共同出现的次数为|x,y|。然后我们得到,
.
现在,我们可以通过翻转内部除法的底部来重新排列它,这给我们提供了(n|x,y|)/(|x||y|)。此外,计算使用N = 1/n,这样我们就少了一次除法操作。这给我们带来:
这给我们带来了以下代码:
/*** * 计算变量t和a之间的互信息。假设a.length == t.length。 * @param a 候选变量a * @param avals a可以取的值的数量(max(a) == avals) * @param t 目标变量 * @param tvals t可以取的值的数量(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; // 这里需要使用long类型,因为计算中涉及到大数乘法 long[][] crosscounts = new long[avals][tvals]; long[] tcounts = new long[tvals]; long[] acounts = new long[avals]; // 计算两个变量的计数 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) { // 主分数:(n|x,y|)/(|x||y|) double sumtmp = (numinst*crosscounts[av][tv])/(acounts[av]*tcounts[tv]); // 对数部分 (|x,y|/n) 并更新乘积 sum += oneovernuminst*crosscounts[av][tv]*Math.log(sumtmp)*log2; } } } return sum;}
这段代码假设a和t的值不是稀疏的(即min(t)=0且tvals=max(t)),这样才能高效运行。否则(如注释所述),会创建大型且不必要的数组。
我认为这种方法在同时计算多个变量之间的互信息时可以进一步改进(计数操作可以被压缩——特别是目标变量的计数)。我使用的实现是与WEKA接口的实现。
最后,将对数移出求和运算可能更有效。但我不确定在循环中是log还是power运算会更耗时。这是通过以下步骤完成的:
- 应用a*log(b) = log(a^b)
- 将对数移到求和运算之外,使用log(a)+log(b) = log(ab)
这给出: