Skip to content

Commit ba62fed

Browse files
blckmaximaanntzer
authored andcommitted
Fix Error with parenthesis, and simplify computation of lognormalized gaussian probabilities
Fix strict setting for Variational HMMs Fix a bug in the lower bound computation. Simplify term1 of the Variational Gaussian likelihood Update tests per the variational lower bound change Back-out added strict parameter for convergence monitor, and rely on warnings instead
1 parent 1935f9b commit ba62fed

File tree

4 files changed

+22
-30
lines changed

4 files changed

+22
-30
lines changed

doc/source/tutorial.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ ConvergenceMonitor(
174174
history=[...],
175175
iter=15,
176176
n_iter=100,
177-
strict=False,
178177
tol=0.01,
179178
verbose=False,
180179
)

lib/hmmlearn/base.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class ConvergenceMonitor:
5555

5656
_template = "{iter:>10d} {log_prob:>16.8f} {delta:>+16.8f}"
5757

58-
def __init__(self, tol, n_iter, verbose, *, strict=False):
58+
def __init__(self, tol, n_iter, verbose):
5959
"""
6060
Parameters
6161
----------
@@ -67,14 +67,10 @@ def __init__(self, tol, n_iter, verbose, *, strict=False):
6767
Maximum number of iterations to perform.
6868
verbose : bool
6969
Whether per-iteration convergence reports are printed.
70-
strict : bool
71-
Whether to enforce that the values reported are strictly
72-
increasing.
7370
"""
7471
self.tol = tol
7572
self.n_iter = n_iter
7673
self.verbose = verbose
77-
self.strict = strict
7874
self.history = deque()
7975
self.iter = 0
8076

@@ -110,9 +106,14 @@ def report(self, log_prob):
110106
message = self._template.format(
111107
iter=self.iter + 1, log_prob=log_prob, delta=delta)
112108
print(message, file=sys.stderr)
113-
if self.strict and self.history and log_prob < self.history[-1]:
114-
raise ValueError(f"Model is not converging. Current: {log_prob}"
115-
f" is not greater than {self.history[-1]}.")
109+
110+
# Allow for some wiggleroom based on precision.
111+
precision = np.finfo(float).eps ** (1/2)
112+
if self.history and (log_prob - self.history[-1]) < -precision:
113+
delta = log_prob - self.history[-1]
114+
_log.warning(f"Model is not converging. Current: {log_prob}"
115+
f" is not greater than {self.history[-1]}."
116+
f" Delta is {delta}")
116117
self.history.append(log_prob)
117118
self.iter += 1
118119

@@ -1020,15 +1021,8 @@ def __init__(self, n_components=1,
10201021

10211022
self.startprob_prior = startprob_prior
10221023
self.transmat_prior = transmat_prior
1023-
# For the case of updating all model components
1024-
# at each iteration, we can be strict with the convergence
1025-
# monitory - we know that the Lower Bound will improve
1026-
# at each iteration. (https://arxiv.org/abs/1601.00670)
1027-
# During testing we did not see non-strict improvements due to
1028-
# floating point slop, though that doesn't mean they couldn't
1029-
# happy in the future.
10301024
self.monitor_ = ConvergenceMonitor(
1031-
self.tol, self.n_iter, self.verbose, strict=(params == "ste"))
1025+
self.tol, self.n_iter, self.verbose)
10321026

10331027
def _init(self, X, lengths=None):
10341028
"""

lib/hmmlearn/tests/test_variational_gaussian.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,6 @@ def test_random_fit(self, implementation, params='stmc', n_features=3,
5656
covariance_type=self.covariance_type,
5757
implementation=implementation)
5858

59-
# Depending on the random seed, the model may converge rather quickly,
60-
# and throw an assertion in this test, as the function we call
61-
# computes each iteration independently by calling fit() `n_iter`
62-
# times.
6359
assert_log_likelihood_increasing(model, X, lengths, n_iter=10)
6460

6561
@pytest.mark.parametrize("implementation", ["scaling", "log"])
@@ -344,7 +340,7 @@ def test_initialization(self, implementation):
344340

345341

346342
class TestSpherical(_TestGaussian):
347-
test_fit_mcgrory_titterington1d_mean = 1.40653312
343+
test_fit_mcgrory_titterington1d_mean = 1.4105851867634462
348344
covariance_type = "spherical"
349345

350346
def new_for_init(self, implementation):
@@ -429,7 +425,7 @@ def test_initialization(self, implementation):
429425

430426

431427
class TestDiagonal(_TestGaussian):
432-
test_fit_mcgrory_titterington1d_mean = 1.40653312
428+
test_fit_mcgrory_titterington1d_mean = 1.410585186763446
433429
covariance_type = "diag"
434430

435431
def new_for_init(self, implementation):

lib/hmmlearn/vhmm.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -647,21 +647,24 @@ def _compute_subnorm_log_likelihood(self, X):
647647
# In general, things are neater if we pretend the covariance is
648648
# full / tied. Or, we could treat each case separately, and reduce
649649
# the number of operations. That's left for the future :-)
650-
term1 = np.zeros_like(self.dof_posterior_, dtype=float)
651-
for d in range(1, self.n_features+1):
652-
term1 += special.digamma(.5 * self.dof_posterior_ + 1 - d)
650+
nf = self.n_features
651+
652+
term1 = special.digamma(
653+
.5 * (self.dof_posterior_ - np.arange(0, nf)[:, None])
654+
).sum(axis=0)
655+
653656
scale_posterior_ = self.scale_posterior_
654657
if self.covariance_type in ("diag", "spherical"):
655658
scale_posterior_ = fill_covars(self.scale_posterior_,
656659
self.covariance_type, self.n_components, self.n_features)
657660
W_k = np.linalg.inv(scale_posterior_)
658-
term1 += self.n_features * np.log(2) + _utils.logdet(W_k)
659-
term1 /= 2
661+
term1 += nf * np.log(2) + _utils.logdet(W_k)
662+
term1 /= 2.
660663

661664
# We ignore the constant that is typically excluded in the literature
662665
# term2 = self.n_features * log(2 * M_PI) / 2
663666
term2 = 0
664-
term3 = self.n_features / self.beta_posterior_
667+
term3 = nf / self.beta_posterior_
665668

666669
# (X - Means) * W_k * (X-Means)^T * self.dof_posterior_
667670
delta = (X - self.means_posterior_[:, None])
@@ -784,7 +787,7 @@ def _compute_lower_bound(self, log_prob):
784787
if self.covariance_type != "full":
785788
scale_posterior_ = fill_covars(self.scale_posterior_,
786789
self.covariance_type, self.n_components, self.n_features)
787-
scale_prior_ = fill_covars(self.scale_posterior_,
790+
scale_prior_ = fill_covars(self.scale_prior_,
788791
self.covariance_type, self.n_components, self.n_features)
789792

790793
W_k = np.linalg.inv(scale_posterior_)

0 commit comments

Comments
 (0)