Java中互信息的高效实现

我想使用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中提供的方法进行了比较。

它的工作原理是重新排列互信息方程,使得可以尽量减少浮点运算的数量:

互信息方程

我们首先定义pcdot为样本/交易数量上的计数/频率。因此,我们定义项目数量为n,x出现的次数为|x|,y出现的次数为|y|,它们共同出现的次数为|x,y|。然后我们得到,

mi1.

现在,我们可以通过翻转内部除法的底部来重新排列它,这给我们提供了(n|x,y|)/(|x||y|)。此外,计算使用N = 1/n,这样我们就少了一次除法操作。这给我们带来:

mi2

这给我们带来了以下代码:

/*** * 计算变量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运算会更耗时。这是通过以下步骤完成的:

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

这给出:

mi2

Related Posts

L1-L2正则化的不同系数

我想对网络的权重同时应用L1和L2正则化。然而,我找不…

使用scikit-learn的无监督方法将列表分类成不同组别,有没有办法?

我有一系列实例,每个实例都有一份列表,代表它所遵循的不…

f1_score metric in lightgbm

我想使用自定义指标f1_score来训练一个lgb模型…

通过相关系数矩阵进行特征选择

我在测试不同的算法时,如逻辑回归、高斯朴素贝叶斯、随机…

可以将机器学习库用于流式输入和输出吗?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

在TensorFlow中,queue.dequeue_up_to()方法的用途是什么?

我对这个方法感到非常困惑,特别是当我发现这个令人费解的…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注