the transition descriptor type
26 | */
27 | public class SequenceState {
28 |
29 | public final S state;
30 |
31 | /**
32 | * Null if HMM was started with initial state probabilities and state is the initial state.
33 | */
34 | public final O observation;
35 |
36 | /**
37 | * Null if transition descriptor was not provided.
38 | */
39 | public final D transitionDescriptor;
40 |
41 | /**
42 | * Probability of this state given all observations.
43 | */
44 | public final Double smoothingProbability;
45 |
46 | public SequenceState(S state, O observation, D transitionDescriptor, Double smoothingProbability) {
47 | this.state = state;
48 | this.observation = observation;
49 | this.transitionDescriptor = transitionDescriptor;
50 | this.smoothingProbability = smoothingProbability;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/bmw/hmm/Transition.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2015-2016, BMW Car IT GmbH and BMW AG
3 | * Author: Stefan Holder (stefan.holder@bmw.de)
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.bmw.hmm;
19 |
20 | import java.util.Objects;
21 |
22 | /**
23 | * Represents the transition between two consecutive candidates.
24 | *
25 | * @param the state type
26 | */
27 | public class Transition {
28 | public final S fromCandidate;
29 | public final S toCandidate;
30 |
31 | public Transition(S fromCandidate, S toCandidate) {
32 | this.fromCandidate = fromCandidate;
33 | this.toCandidate = toCandidate;
34 | }
35 |
36 | @Override
37 | public int hashCode() {
38 | return Objects.hash(fromCandidate, toCandidate);
39 | }
40 |
41 | @Override
42 | public boolean equals(Object obj) {
43 | if (this == obj)
44 | return true;
45 | if (obj == null)
46 | return false;
47 | if (getClass() != obj.getClass())
48 | return false;
49 | @SuppressWarnings("unchecked")
50 | Transition other = (Transition) obj;
51 | return Objects.equals(fromCandidate, other.fromCandidate) && Objects.equals(toCandidate,
52 | other.toCandidate);
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return "Transition [fromCandidate=" + fromCandidate + ", toCandidate="
58 | + toCandidate + "]";
59 | }
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/bmw/hmm/Utils.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2015, BMW Car IT GmbH
3 | * Author: Stefan Holder (stefan.holder@bmw.de)
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.bmw.hmm;
19 |
20 | import java.util.LinkedHashMap;
21 | import java.util.Map;
22 |
23 | /**
24 | * Implementation utilities.
25 | */
26 | class Utils {
27 |
28 | static int initialHashMapCapacity(int maxElements) {
29 | // Default load factor of HashMaps is 0.75
30 | return (int)(maxElements / 0.75) + 1;
31 | }
32 |
33 | static Map logToNonLogProbabilities(Map logProbabilities) {
34 | final Map result = new LinkedHashMap<>();
35 | for (Map.Entry entry : logProbabilities.entrySet()) {
36 | result.put(entry.getKey(), Math.exp(entry.getValue()));
37 | }
38 | return result;
39 | }
40 |
41 | /**
42 | * Note that this check must not be used for probability densities.
43 | */
44 | static boolean probabilityInRange(double probability, double delta) {
45 | return probability >= -delta && probability <= 1.0 + delta;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/bmw/hmm/ViterbiAlgorithm.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2015-2016, BMW Car IT GmbH and BMW AG
3 | * Author: Stefan Holder (stefan.holder@bmw.de)
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.bmw.hmm;
19 |
20 | import java.util.ArrayList;
21 | import java.util.Collection;
22 | import java.util.Collections;
23 | import java.util.LinkedHashMap;
24 | import java.util.List;
25 | import java.util.ListIterator;
26 | import java.util.Map;
27 |
28 | /**
29 | * Implementation of the Viterbi algorithm for time-inhomogeneous Markov processes,
30 | * meaning that the set of states and state transition probabilities are not necessarily fixed
31 | * for all time steps. The plain Viterbi algorithm for stationary Markov processes is described e.g.
32 | * in Rabiner, Juang, An introduction to Hidden Markov Models, IEEE ASSP Mag., pp 4-16, June 1986.
33 | *
34 | * Generally expects logarithmic probabilities as input to prevent arithmetic underflows for
35 | * small probability values.
36 | *
37 | *
This algorithm supports storing transition objects in
38 | * {@link #nextStep(Object, Collection, Map, Map, Map)}. For instance if a HMM is
39 | * used for map matching, this could be routes between road position candidates.
40 | * The transition descriptors of the most likely sequence can be retrieved later in
41 | * {@link SequenceState#transitionDescriptor} and hence do not need to be stored by the
42 | * caller. Since the caller does not know in advance which transitions will occur in the most
43 | * likely sequence, this reduces the number of transitions that need to be kept in memory
44 | * from t*n² to t*n since only one transition descriptor is stored per back pointer,
45 | * where t is the number of time steps and n the number of candidates per time step.
46 | *
47 | *
For long observation sequences, back pointers usually converge to a single path after a
48 | * certain number of time steps. For instance, when matching GPS coordinates to roads, the last
49 | * GPS positions in the trace usually do not affect the first road matches anymore.
50 | * This implementation exploits this fact by letting the Java garbage collector
51 | * take care of unreachable back pointers. If back pointers converge to a single path after a
52 | * constant number of time steps, only O(t) back pointers and transition descriptors need to be
53 | * stored in memory.
54 | *
55 | * @param the state type
56 | * @param the observation type
57 | * @param the transition descriptor type. Pass {@link Object} if transition descriptors are not
58 | * needed.
59 | */
60 | public class ViterbiAlgorithm {
61 |
62 | /**
63 | * Stores addition information for each candidate.
64 | */
65 | private static class ExtendedState {
66 |
67 | S state;
68 |
69 | /**
70 | * Back pointer to previous state candidate in the most likely sequence.
71 | * Back pointers are chained using plain Java references.
72 | * This allows garbage collection of unreachable back pointers.
73 | */
74 | ExtendedState backPointer;
75 |
76 | O observation;
77 | D transitionDescriptor;
78 |
79 | ExtendedState(S state,
80 | ExtendedState backPointer,
81 | O observation, D transitionDescriptor) {
82 | this.state = state;
83 | this.backPointer = backPointer;
84 | this.observation = observation;
85 | this.transitionDescriptor = transitionDescriptor;
86 | }
87 | }
88 |
89 | private static class ForwardStepResult {
90 | final Map newMessage;
91 |
92 | /**
93 | * Includes back pointers to previous state candidates for retrieving the most likely
94 | * sequence after the forward pass.
95 | */
96 | final Map> newExtendedStates;
97 |
98 | ForwardStepResult(int numberStates) {
99 | newMessage = new LinkedHashMap<>(Utils.initialHashMapCapacity(numberStates));
100 | newExtendedStates = new LinkedHashMap<>(Utils.initialHashMapCapacity(numberStates));
101 | }
102 | }
103 |
104 | /**
105 | * Allows to retrieve the most likely sequence using back pointers.
106 | */
107 | private Map> lastExtendedStates;
108 |
109 | private Collection prevCandidates;
110 |
111 | /**
112 | * For each state s_t of the current time step t, message.get(s_t) contains the log
113 | * probability of the most likely sequence ending in state s_t with given observations
114 | * o_1, ..., o_t.
115 | *
116 | * Formally, this is max log p(s_1, ..., s_t, o_1, ..., o_t) w.r.t. s_1, ..., s_{t-1}.
117 | * Note that to compute the most likely state sequence, it is sufficient and more
118 | * efficient to compute in each time step the joint probability of states and observations
119 | * instead of computing the conditional probability of states given the observations.
120 | */
121 | private Map message;
122 |
123 | private boolean isBroken = false;
124 |
125 | private ForwardBackwardAlgorithm forwardBackward;
126 |
127 | private List