Skip to content

Commit 2f975b6

Browse files
authored
Speed up viterbi (#93)
1 parent 5ad5d81 commit 2f975b6

File tree

26 files changed

+258
-110
lines changed

26 files changed

+258
-110
lines changed

.github/workflows/benchmark.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ on:
88
jobs:
99
Benchmark:
1010
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
pull-requests: write
1114
if: contains(github.event.pull_request.labels.*.name, 'run benchmark')
1215
steps:
1316
- uses: actions/checkout@v2

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "HiddenMarkovModels"
22
uuid = "84ca31d5-effc-45e0-bfda-5a68cd981f47"
33
authors = ["Guillaume Dalle"]
4-
version = "0.4.1"
4+
version = "0.5.0"
55

66
[deps]
77
ArgCheck = "dce04be8-c92d-5529-be00-80e4d2c0e197"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ Then, you can create your first model as follows:
2424

2525
```julia
2626
using Distributions, HiddenMarkovModels
27-
init = [0.4, 0.6]
28-
trans = [0.9 0.1; 0.2 0.8]
27+
init = [0.6, 0.4]
28+
trans = [0.7 0.3; 0.2 0.8]
2929
dists = [Normal(-1.0), Normal(1.0)]
3030
hmm = HMM(init, trans, dists)
3131
```

benchmark/Manifest.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ version = "0.1.0"
169169
deps = ["ArgCheck", "ChainRulesCore", "DensityInterface", "DocStringExtensions", "FillArrays", "LinearAlgebra", "PrecompileTools", "Random", "SparseArrays", "StatsAPI", "StatsFuns"]
170170
path = ".."
171171
uuid = "84ca31d5-effc-45e0-bfda-5a68cd981f47"
172-
version = "0.4.0"
172+
version = "0.5.0"
173173
weakdeps = ["Distributions"]
174174

175175
[deps.HiddenMarkovModels.extensions]

docs/src/api.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,24 @@ HiddenMarkovModels.forward_backward!
9494
HiddenMarkovModels.baum_welch!
9595
```
9696

97-
## Misc
97+
## Miscellaneous
9898

9999
```@docs
100+
HiddenMarkovModels.valid_hmm
100101
HiddenMarkovModels.rand_prob_vec
101102
HiddenMarkovModels.rand_trans_mat
103+
HiddenMarkovModels.fit_in_sequence!
104+
```
105+
106+
## Internals
107+
108+
```@docs
102109
HiddenMarkovModels.LightDiagNormal
103110
HiddenMarkovModels.LightCategorical
104-
HiddenMarkovModels.fit_in_sequence!
111+
HiddenMarkovModels.log_initialization
112+
HiddenMarkovModels.log_transition_matrix
113+
HiddenMarkovModels.mul_rows_cols!
114+
HiddenMarkovModels.argmaxplus_transmul!
105115
```
106116

107117
## Index

examples/basics.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ rng = StableRNG(63);
1919
# ## Model
2020

2121
#=
22-
The package provides a versatile [`HMM`](@ref) type with three attributes:
23-
- a vector of state initialization probabilities
24-
- a matrix of state transition probabilities
25-
- a vector of observation distributions, one for each state
22+
The package provides a versatile [`HMM`](@ref) type with three main attributes:
23+
- a vector `init` of state initialization probabilities
24+
- a matrix `trans` of state transition probabilities
25+
- a vector `dists` of observation distributions, one for each state
2626
2727
Any scalar- or vector-valued distribution from [Distributions.jl](https://github.com/JuliaStats/Distributions.jl) can be used for the last part, as well as [Custom distributions](@ref).
2828
=#
2929

3030
init = [0.6, 0.4]
31-
trans = [0.7 0.3; 0.3 0.7]
31+
trans = [0.7 0.3; 0.2 0.8]
3232
dists = [MvNormal([-0.5, -0.8], I), MvNormal([0.5, 0.8], I)]
3333
hmm = HMM(init, trans, dists)
3434

@@ -142,7 +142,7 @@ Since it is a local optimization procedure, it requires a starting point that is
142142
=#
143143

144144
init_guess = [0.5, 0.5]
145-
trans_guess = [0.6 0.4; 0.4 0.6]
145+
trans_guess = [0.6 0.4; 0.3 0.7]
146146
dists_guess = [MvNormal([-0.4, -0.7], I), MvNormal([0.4, 0.7], I)]
147147
hmm_guess = HMM(init_guess, trans_guess, dists_guess);
148148

examples/controlled.jl

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ struct ControlledGaussianHMM{T} <: AbstractHMM
3232
end
3333

3434
#=
35-
In state $i$ with a vector of controls $u$, our observation is given by the linear model $y \sim \mathcal{N}(\beta_i^\top u, 1)$.
35+
In state $i$ with a vector of controls $u$, our observation is given by the linear model $y \sim \mathcal{N}(\beta_i^\top u, 1)$.
36+
Controls must be provided to both `transition_matrix` and `obs_distributions` even if they are only used by one.
3637
=#
3738

3839
function HMMs.initialization(hmm::ControlledGaussianHMM)
3940
return hmm.init
4041
end
4142

42-
function HMMs.transition_matrix(hmm::ControlledGaussianHMM)
43+
function HMMs.transition_matrix(hmm::ControlledGaussianHMM, control::AbstractVector)
4344
return hmm.trans
4445
end
4546

@@ -54,8 +55,8 @@ In this case, the transition matrix does not depend on the control.
5455
# ## Simulation
5556

5657
d = 3
57-
init = [0.8, 0.2]
58-
trans = [0.7 0.3; 0.3 0.7]
58+
init = [0.6, 0.4]
59+
trans = [0.7 0.3; 0.2 0.8]
5960
dist_coeffs = [-ones(d), ones(d)]
6061
hmm = ControlledGaussianHMM(init, trans, dist_coeffs);
6162

@@ -122,9 +123,9 @@ end
122123
Now we put it to the test.
123124
=#
124125

125-
init_guess = [0.7, 0.3]
126-
trans_guess = [0.6 0.4; 0.4 0.6]
127-
dist_coeffs_guess = [-0.7 * ones(d), 0.7 * ones(d)]
126+
init_guess = [0.5, 0.5]
127+
trans_guess = [0.6 0.4; 0.3 0.7]
128+
dist_coeffs_guess = [-1.1 * ones(d), 1.1 * ones(d)]
128129
hmm_guess = ControlledGaussianHMM(init_guess, trans_guess, dist_coeffs_guess);
129130

130131
#-
@@ -136,7 +137,7 @@ first(loglikelihood_evolution), last(loglikelihood_evolution)
136137
How did we perform?
137138
=#
138139

139-
cat(transition_matrix(hmm_est), transition_matrix(hmm); dims=3)
140+
cat(hmm_est.trans, hmm.trans; dims=3)
140141

141142
#-
142143

examples/interfaces.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Let's put it to the test.
8282
=#
8383

8484
init = [0.6, 0.4]
85-
trans = [0.7 0.3; 0.3 0.7]
85+
trans = [0.7 0.3; 0.2 0.8]
8686
dists = [StuffDist(-1.0), StuffDist(+1.0)]
8787
hmm = HMM(init, trans, dists);
8888

@@ -104,8 +104,8 @@ If we implement `fit!`, Baum-Welch also works seamlessly.
104104
=#
105105

106106
init_guess = [0.5, 0.5]
107-
trans_guess = [0.6 0.4; 0.4 0.6]
108-
dists_guess = [StuffDist(-0.7), StuffDist(+0.7)]
107+
trans_guess = [0.6 0.4; 0.3 0.7]
108+
dists_guess = [StuffDist(-1.1), StuffDist(+1.1)]
109109
hmm_guess = HMM(init_guess, trans_guess, dists_guess);
110110

111111
#-

examples/temporal.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ end
5555
# ## Simulation
5656

5757
init = [0.6, 0.4]
58-
trans_per = ([0.7 0.3; 0.3 0.7], [0.3 0.7; 0.7 0.3])
58+
trans_per = ([0.7 0.3; 0.2 0.8], [0.3 0.7; 0.8 0.2])
5959
dists_per = ([Normal(-1.0), Normal(-2.0)], [Normal(+1.0), Normal(+2.0)])
6060
hmm = PeriodicHMM(init, trans_per, dists_per);
6161

@@ -152,8 +152,8 @@ Now let's test our procedure with a reasonable guess.
152152
=#
153153

154154
init_guess = [0.7, 0.3]
155-
trans_per_guess = ([0.6 0.4; 0.4 0.6], [0.4 0.6; 0.6 0.4])
156-
dists_per_guess = ([Normal(-0.7), Normal(-1.7)], [Normal(+0.7), Normal(+1.7)])
155+
trans_per_guess = ([0.6 0.4; 0.3 0.7], [0.4 0.6; 0.7 0.3])
156+
dists_per_guess = ([Normal(-1.1), Normal(-2.1)], [Normal(+1.1), Normal(+2.1)])
157157
hmm_guess = PeriodicHMM(init_guess, trans_per_guess, dists_per_guess);
158158

159159
#=

examples/types.jl

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Here we explain why playing with different number and array types can be useful
66

77
using Distributions
88
using HiddenMarkovModels
9+
using HiddenMarkovModels: log_transition_matrix #src
910
using HMMTest #src
1011
using LinearAlgebra
1112
using LogarithmicNumbers
@@ -40,7 +41,7 @@ To give an example, let us first generate some data from a vanilla HMM.
4041
=#
4142

4243
init = [0.6, 0.4]
43-
trans = [0.7 0.3; 0.3 0.7]
44+
trans = [0.7 0.3; 0.2 0.8]
4445
dists = [Normal(-1.0), Normal(1.0)]
4546
hmm = HMM(init, trans, dists)
4647
state_seq, obs_seq = rand(rng, hmm, 100);
@@ -57,6 +58,10 @@ hmm_uncertain = HMM(init, trans, dists_guess)
5758
Every quantity we compute with this new HMM will have propagated uncertainties around it.
5859
=#
5960

61+
logdensityof(hmm, obs_seq)
62+
63+
#-
64+
6065
logdensityof(hmm_uncertain, obs_seq)
6166

6267
#=
@@ -129,7 +134,7 @@ trans_guess = sparse([
129134
0 0.6 0.4
130135
0.4 0 0.6
131136
])
132-
dists_guess = [Normal(1.2), Normal(2.2), Normal(3.2)]
137+
dists_guess = [Normal(1.1), Normal(2.1), Normal(3.1)]
133138
hmm_guess = HMM(init_guess, trans_guess, dists_guess);
134139

135140
#-
@@ -149,10 +154,12 @@ Another useful array type is [StaticArrays.jl](https://github.com/JuliaArrays/St
149154

150155
# ## Tests #src
151156

157+
@test nnz(log_transition_matrix(hmm)) == nnz(transition_matrix(hmm)) #src
158+
152159
seq_ends = cumsum(rand(rng, 100:200, 100)); #src
153-
control_seqs = fill(nothing, length(seq_ends)); #src
160+
control_seq = fill(nothing, last(seq_ends)); #src
154161
test_identical_hmmbase(rng, hmm, 100; hmm_guess) #src
155-
test_coherent_algorithms(rng, hmm, control_seq; seq_ends, hmm_guess, init=false) #src
162+
test_coherent_algorithms(rng, hmm, control_seq; seq_ends, hmm_guess, init=false, atol=0.08) #src
156163
test_type_stability(rng, hmm, control_seq; seq_ends, hmm_guess) #src
157164
# https://github.com/JuliaSparse/SparseArrays.jl/issues/469 #src
158165
@test_skip test_allocations(rng, hmm, control_seq; seq_ends, hmm_guess) #src

0 commit comments

Comments
 (0)