在Java中使用隐马尔可夫模型获得正确的数学

时间:2017-07-09 15:37:23

标签: java statistics

为了学习和使用隐藏的马尔可夫模型,我正在编写自己的代码来实现它们。我正在使用此wiki article来帮助我的工作。我不想求助于预先编写的图书馆,因为我发现如果我自己编写图书馆,我可以更好地理解它。不,这不是学校的任务! :)

不幸的是,我的最高教育水平包括高中计算机科学和统计学。我没有机器学习的背景,除了偶尔探索ANN库和TensorFlow。因此,在将数学方程式转换为代码时遇到了一些麻烦。具体来说,我担心我的alpha和beta函数的实现在功能上是不正确的。如果有人可以协助描述我搞砸的地方以及如何纠正我的错误以便有一个正常运行的HMM实现,那么我们将不胜感激。

以下是我的班级全局变量:

public int n; //number of states
public int t; //number of observations
public int time; //iteration holder
public double[][] emitprob; //Emission parameter
public double[][] stprob; //State transition parameter
public ArrayList<String> states, observations, x, y;

我的构造函数:

public Model(ArrayList<String> sts, ArrayList<String> obs)
{

    //the most important algorithm we need right now is 
    //unsupervised learning through BM. Supervised is 
    //pretty easy.

    //need hashtable of count objects... Aya... 

    //perhaps a learner...?
    states = sts;
    observations = obs;
    n = states.size();
    t = observations.size();

    x = new ArrayList();
    y = new ArrayList();

    time = 0;

    stprob = new double[n][n];

    emitprob = new double[n][t];

    stprob = newDistro(n,n);
    emitprob = newDistro(n,t);

}

newDistro方法用于创建新的,统一的,正态分布:

public double[][] newDistro(int x, int y)
{
    Random r = new Random(System.currentTimeMillis());
    double[][] returnme = new double[x][y];
    double sum = 0;
    for(int i = 0; i < x; i++)
    {
        for(int j = 0; j < y; j++)
        {
            returnme[i][j] = Math.abs(r.nextInt());
            sum += returnme[i][j];
        }
    }

    for(int i = 0; i < x; i++)
    {
        for(int j = 0; j < y; j++)
        {
            returnme[i][j] /= sum;
        }
    }

    return returnme;
}

我的维特比算法实现:

public ArrayList<String> viterbi(ArrayList<String> obs)
{
    //K means states
    //T means observations
    //T arrays should be constructed as K * T (N * T)
    ArrayList<String> path = new ArrayList();
    String firstObservation = obs.get(0);
    int firstObsIndex = observations.indexOf(firstObservation);
    double[] pi = new double[n]; //initial probs of first obs for each st
    int ts = obs.size();
    double[][] t1 = new double[n][ts];
    double[][] t2 = new double[n][ts];
    int[] y = new int[obs.size()];
    for(int i = 0; i < obs.size(); i++)
    {
        y[i] = observations.indexOf(obs.get(i));
    }

    for(int i = 0; i < n; i++)
    {
        pi[i] = emitprob[i][firstObsIndex];
    }

    for(int i = 0; i < n; i++)
    {
        t1[i][0] = pi[i] * emitprob[i][y[0]];
        t2[i][0] = 0;
    }

    for(int i = 1; i < ts; i++)
    {
        for(int j = 0; j < n; j++)
        {
            double maxValue = 0;
            int maxIndex = 0;
            //first we compute the max value
            for(int q = 0; q < n; q++)
            {
                double value = t1[q][i-1] * stprob[q][j];
                if(value > maxValue)
                {
                    maxValue = value; //the max
                    maxIndex = q; //the argmax
                }
            }
            t1[j][i] = emitprob[j][y[i]] * maxValue;
            t2[j][i] = maxIndex;
        }
    }
    int[] z = new int[ts];

    int maxIndex = 0;
    double maxValue = 0.0d;
    for(int k = 0; k < n; k++)
    {
        double myValue =  t1[k][ts-1];
        if(myValue > maxValue)
        {
            myValue = maxValue;
            maxIndex = k;
        }
    }
    path.add(states.get(maxIndex));

    for(int i = ts-1; i >= 2; i--)
    {
        z[i-1] = (int)t2[z[i]][i];
        path.add(states.get(z[i-1]));
    }
    System.out.println(path.size());
    for(String s: path)
    {
        System.out.println(s);
    }
    return path;
}

我的前向算法,它取代了后面描述的alpha函数:

public double forward(ArrayList<String> obs)
{
    double result = 0;
    int length = obs.size()-1;
    for(int i = 0; i < n; i++)
    {
        result += alpha(i, length, obs);
    }

    return result;
}

其余功能用于实现Baum-Welch算法。

阿尔法功能是我恐怕我在这里做错了。我无法理解它需要迭代序列的“方向” - 我是从最后一个元素(size-1)还是从第一个元素(在零指数处)开始的?

public double alpha(int j, int t, ArrayList<String> obs)
{

    double sum = 0;

    if(t == 0)
    {
        return stprob[0][j];
    }

    else
    {
        String lastObs = obs.get(t);
        int obsIndex = observations.indexOf(lastObs);
        for(int i = 0; i < n; i++)
        {
            sum += alpha(i, t-1, obs) * stprob[i][j] * emitprob[j][obsIndex];
        }
    }

    return sum;
}

我的beta功能存在类似的“正确性”问题:

public double beta(int i, int t, ArrayList<String> obs)
{
    double result = 0;

    int obsSize = obs.size()-1;

    if(t == obsSize)
    {
        return 1;
    }

    else
    {
        String lastObs = obs.get(t+1);
        int obsIndex = observations.indexOf(lastObs);
        for(int j = 0; j < n; j++)
        {
            result += beta(j, t+1, obs) * stprob[i][j] * emitprob[j][obsIndex];
        }
    }

    return result;
}

我对伽玛功能更有信心;但是,由于它明确要求使用alpha和beta,显然我担心它会以某种方式“关闭”。

public double gamma(int i, int t, ArrayList<String> obs)
{
    double top = alpha(i, t, obs) * beta(i, t, obs);
    double bottom = 0;
    for(int j = 0; j < n; j++)
    {
        bottom += alpha(j, t, obs) * beta(j, t, obs);
    }
    return top / bottom;
}

我的“波浪”功能相同 - 我为命名道歉;不确定符号的实际名称。

public double squiggle(int i, int j, int t, ArrayList<String> obs)
{
    String lastObs = obs.get(t+1);
    int obsIndex = observations.indexOf(lastObs);
    double top = alpha(i, t, obs) * stprob[i][j] * beta(j, t+1, obs) * emitprob[j][obsIndex];
    double bottom = 0;
    double innerSum = 0;
    double outterSum = 0;
    for(i = 0; i < n; i++)
    {
        for(j = 0; j < n; j++)
        {
            innerSum += alpha(i, t, obs) * stprob[i][j] * beta(j, t+1, obs) * emitprob[j][obsIndex];
        }
        outterSum += innerSum;
    }

    return top / bottom;
}

最后,为了更新我的状态转换和发射概率数组,我将这些函数实现为aStar和bStar。

public double aStar(int i, int j, ArrayList<String> obs)
{
    double squiggleSum = 0;
    double gammaSum = 0; 
    int T = obs.size()-1;
    for(int t = 0; t < T; t++)
    {
        squiggleSum += squiggle(i, j, t, obs);
        gammaSum += gamma(i, t, obs);
    }
    return squiggleSum / gammaSum;
}

public double bStar(int i, String v, ArrayList<String> obs)
{
    double top = 0;
    double bottom = 0;

    for(int t = 0; t < obs.size()-1; t++)
    {
        if(obs.get(t).equals(v))
        {
            top += gamma(i, t, obs);
        }
        bottom += gamma(i, t, obs);
    }


    return top / bottom;
}

在我的理解中,由于b *函数包含一个返回1或0的分段函数,我认为在“if”语句中实现它,并且只有在字符串等于观察历史时才添加结果是相同的如上所述,由于该函数将调用gamma 0,因此节省了一点计算时间。这是对的吗?

总之,我想让我的数学正确,以确保成功(尽管简单)HMM实施。至于Baum-Welch算法,我无法理解如何实现完整的功能 - 它是否就像在所有状态下运行aStar一样简单(作为n * n FOR循环)和bStar用于所有观察,在一个循环中收敛功能?另外,在没有过度拟合的情况下检查收敛的最佳实践函数是什么?

请让我知道为了做到这一点我需要做的一切。

非常感谢你给我的任何帮助!

1 个答案:

答案 0 :(得分:0)

为避免下溢,应在正向和反向算法中使用比例因子。为了获得正确的结果,请使用嵌套的for循环,并在forward方法中对步骤进行转发。

向后方法类似于向前功能。 您可以通过Baum-Welch算法的方法来调用它们。