reorganized training methods, fix to baum-welch

The Model.train method is now responsible to returning the total improvement for the training algorithm. Baum-Welch now only repeats for the correct number of iterations, instead of running once to get the score the first time. The training methods have all been switched to void methods, and do not return a log probability anymore. A Model.log_probability( sequences, path=None ) method has been added. This is a wrapper for model.forward( sequence )[ len(sequence), model.end_index ] basically, except it also works for infinite HMMs, can take in multiple sequences, and if you give a path for your sequences, will calculate the probability of the sequence given the path.
fixed another baum-welch

Fixed an error with Model.log_probability, where the wrong indices where being used. Int j was supposed to iterate through the entire path, but would iterate through the path up until the length of the sequence instead. Also, pair_lse was the wrong approach, should be simple addition.

Made a change to Baum-Welch which will allow for summary statistics in the future-- in that instead of using a slice of the emission weights matrix for training, it will go through this matrix, determine which symbols are paired with which states, and append the symbol and weight to a growing list for that state. A modification to the tied state modification was made, such that instead of changing all the entries in the emission matrix, just gather the correct probability and use that, leaving the underlying matrix untouched.
edge specific pseudocounts added

Model.add_transition now has an optional argument, pseudocount, at the end. This specifies the edge specific pseudocount which can be toggled by flipping "use_pseudocount=True" in the train method. The transition_pseudocount argument adds a flat pseudocount to each edge, which will cause the model to converge to equal weighting on each edge, which may not be desirable in some situations. Currently, the default pseudocount for each edge is equal to the probability of that edge, which means that over time, the model will converge back to the original instantiation of the edge weights if no other data is provided. 

You can use both transition_pseudocounts and use_pseudocounts at the same time, if you wish to get a slightly more equitable model if no data is present. "use_pseudocounts" only refers to the edge specific ones-- this does not not need to be called if you want to use the transition_pseudocount method.
training done in batches now

Previously, during training, a matrix of n_states by n_total_observations was created. If we have either a lot of training data, or a big model, or both, this creates a huge matrix. This is not the best way to go about this, especially if we also have tied states, as is likely on large models. To fix this, for every sequence, observations are appended to a list for each state if they pass a threshold of weighting, default of 1e-8. Tied states are properly calculated per sequence, and only one list of symbols and weights are calculated for each set of tied states to save memory.

This removed both emitted_symbols and emission_weights, two large matrices which scaled proportional to the total number of characters summed across all sequences. Speed does not appear to be significantly affected, but memory usage is down.
batch training for BW removed

It was causing significant problems which I can't figure out right now. This still works for viterbi and labeled training though. I will revisit the issue at another time, but this is stable.
maximum a posteriori and fb upgrades

Forward-Backward now allows you to specify the keyword parameter "tie", which can be either true or false. If you specify true, it will replace the emission log probability of an observation given a state in a tied state group, with the sum across the entire group. Meaning basically, if there's a 0.01 probability of it being in one of the tied states, and a 0.54 probability of it being in another of the same set of tied states, it'll replace both values with 0.55. If you set it to false, it will ignore tied states.

Maximum-A-Posteriori used to be somewhat slow because it would call forward-backward, and then only use the emission weights. It was also purely in Python. I cythonized it, and made it calculate the emission weights inside the method instead of calling forward-backward, in order to not waste time calculating the transitions. Emission weights will always take into account tied states, since that matters for the decoding.
added weighted hidden states

When defining a state, you can now give an optional parameter for the state weight. For example, if you previously wrote s1 = State( NormalDistribution( 5, 2 ), name="s1" ), you can now write s1 = State( NormalDistribution( 5, 2 ), name="s1", weight=100 ). Weights can be any positive number (so not 0), with weights above 1 adding a bonus and weights below 1 adding a penalty when aligning any symbol to this state. This is useful if you have a state which you either really want to, or really don't want to, go to. This is the same as modifying transitions to be more favorable to this state, without having to actually do it.

This effects all algorithms. By default, it does nothing, and the weight argument for states is optional.
minor change to maximum a posteriori

Maximum a Posteriori performs the calculation of the emission weights inside the function, instead of calling Model.forward_backward in order to save time from calculating the transition matrix, which is not used. However, in writing it, tied states were included. This will throw off the ML path, as any state in the set of tied states will be equally likely according to the algorithm. Tied states are only useful in training, and so tied states are not considered in MAP, as it should be.
added multivariate distribution

Multivariate Distribution now allows you to pass in multiple dimensions per point, instead of requiring univariate distributions. These distributions must be independent though. Decoding, probability calculation, and training all work normally via dovetyping.

However, the model.log_probability() method had to be modified to only ever take a single sequence, in order to be able to distinguish between multiple sequences and multiple features per observation. Now use sum( model.log_probability( sequence ) for sequence in sequences ) to get the same result.
Merge pull request #14 from jmschrei/training-improvements

Training improvements