Skip to content

Commit

Permalink
Fix tolerance argument in LatentClass Model (#190)
Browse files Browse the repository at this point in the history
* FIX: forgotten remainings of renaming of tolerance argument into lbfgs_tolerance

* ADD: updates notebook

* ADD: basic LC model tests

* ADD: tolerance

* ADD: eagerly exec for SimpleMNL
  • Loading branch information
VincentAuriau authored Nov 29, 2024
1 parent f8222bf commit a9db338
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 22 deletions.
8 changes: 4 additions & 4 deletions choice_learn/models/latent_class_base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def __init__(
batch_size=128,
optimizer=None,
add_exit_choice=False,
tolerance=1e-6,
lbfgs_tolerance=1e-6,
lr=0.001,
):
"""Instantiate of the model mixture.
Expand All @@ -44,7 +44,7 @@ class of models to get a mixture of
Name of the tf.keras.optimizers to be used if one is used, by default None
add_exit_choice : bool, optional
Whether or not to add an exit choice, by default False
tolerance: float, optional
lbfgs_tolerance: float, optional
Tolerance for the L-BFGS optimizer if applied, by default 1e-6
lr: float, optional
Learning rate for the optimizer if applied, by default 0.001
Expand All @@ -65,7 +65,7 @@ class of models to get a mixture of

self.epochs = epochs
self.add_exit_choice = add_exit_choice
self.tolerance = tolerance
self.lbfgs_tolerance = lbfgs_tolerance
self.optimizer = optimizer
self.lr = lr
self.batch_size = batch_size
Expand Down Expand Up @@ -472,7 +472,7 @@ def _fit_with_lbfgs(self, choice_dataset, sample_weight=None, verbose=0):
initial_position=init_params,
max_iterations=epochs,
tolerance=-1,
f_absolute_tolerance=self.tolerance,
f_absolute_tolerance=self.lbfgs_tolerance,
f_relative_tolerance=-1,
x_tolerance=-1,
)
Expand Down
22 changes: 11 additions & 11 deletions choice_learn/models/latent_class_mnl.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(
epochs=100,
batch_size=128,
add_exit_choice=False,
tolerance=1e-6,
lbfgs_tolerance=1e-6,
intercept=None,
optimizer="Adam",
lr=0.001,
Expand All @@ -37,7 +37,7 @@ def __init__(
Number of epochs
add_exit_choice : bool, optional
Whether to normalize probabilities with exit choice, by default False
tolerance : float, optional
lbfgs_tolerance : float, optional
LBFG-S tolerance, by default 1e-6
intercept : str, optional
Type of intercept to include in the SimpleMNL.
Expand All @@ -49,24 +49,24 @@ def __init__(
"""
self.n_latent_classes = n_latent_classes
self.intercept = intercept
model_coefficients = {
model_parameters = {
"add_exit_choice": add_exit_choice,
"intercept": intercept,
"optimizer": optimizer,
"batch_size": batch_size,
"tolerance": tolerance,
"lbfgs_tolerance": lbfgs_tolerance,
"lr": lr,
"epochs": 1000,
}

super().__init__(
model_class=SimpleMNL,
model_parameters=model_coefficients,
model_parameters=model_parameters,
n_latent_classes=n_latent_classes,
fit_method=fit_method,
epochs=epochs,
add_exit_choice=add_exit_choice,
tolerance=tolerance,
lbfgs_tolerance=lbfgs_tolerance,
optimizer=optimizer,
lr=lr,
**kwargs,
Expand Down Expand Up @@ -133,7 +133,7 @@ def __init__(
coefficients=None,
epochs=100,
add_exit_choice=False,
tolerance=1e-6,
lbfgs_tolerance=1e-6,
optimizer="Adam",
lr=0.001,
**kwargs,
Expand All @@ -156,7 +156,7 @@ def __init__(
Number of epochs
add_exit_choice : bool, optional
Whether to normalize probabilities with exit choice, by default False
tolerance : float, optional
lbfgs_tolerance : float, optional
LBFG-S tolerance, by default 1e-6
optimizer : str, optional
tf.keras.optimizers to be used, by default "Adam"
Expand All @@ -168,15 +168,15 @@ def __init__(
self.coefficients = coefficients
self.epochs = epochs
self.add_exit_choice = add_exit_choice
self.tolerance = tolerance
self.lbfgs_tolerance = lbfgs_tolerance
self.optimizer = optimizer
self.lr = lr

model_coefficients = {
"coefficients": self.coefficients,
"add_exit_choice": self.add_exit_choice,
"optimizer": self.optimizer,
"tolerance": self.tolerance,
"lbfgs_tolerance": self.lbfgs_tolerance,
"lr": self.lr,
"epochs": self.epochs,
}
Expand All @@ -188,7 +188,7 @@ def __init__(
fit_method=fit_method,
epochs=epochs,
add_exit_choice=add_exit_choice,
tolerance=tolerance,
lbfgs_tolerance=lbfgs_tolerance,
optimizer=optimizer,
lr=lr,
**kwargs,
Expand Down
13 changes: 7 additions & 6 deletions notebooks/models/latent_class_model.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"metadata": {},
"outputs": [],
"source": [
"lc_model = LatentClassSimpleMNL(n_latent_classes=3, fit_method=\"mle\", optimizer=\"lbfgs\", epochs=1000, tolerance=1e-20)\n",
"lc_model = LatentClassSimpleMNL(n_latent_classes=3, fit_method=\"mle\", optimizer=\"lbfgs\", epochs=1000, lbfgs_tolerance=1e-20)\n",
"hist, results = lc_model.fit(elec_dataset, verbose=1)"
]
},
Expand Down Expand Up @@ -134,7 +134,7 @@
" fit_method=\"mle\",\n",
" optimizer=\"lbfgs\",\n",
" epochs=1000,\n",
" tolerance=1e-12)"
" lbfgs_tolerance=1e-12)"
]
},
{
Expand Down Expand Up @@ -246,7 +246,8 @@
" n_latent_classes=3,\n",
" fit_method=\"mle\",\n",
" epochs=1000,\n",
" optimizer=\"lbfgs\"\n",
" optimizer=\"lbfgs\",\n",
" lbfgs_tolerance=1e-12\n",
" )\n",
"manual_lc.instantiate(n_items=4,\n",
" n_shared_features=0,\n",
Expand All @@ -260,7 +261,7 @@
"metadata": {},
"outputs": [],
"source": [
"manual_lc.evaluate(elec_dataset) * len(elec_dataset)"
"print(manual_lc.evaluate(elec_dataset) * len(elec_dataset))"
]
},
{
Expand All @@ -280,7 +281,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"display_name": "tf_env",
"language": "python",
"name": "python3"
},
Expand All @@ -294,7 +295,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.18"
"version": "3.11.4"
}
},
"nbformat": 4,
Expand Down
68 changes: 68 additions & 0 deletions tests/integration_tests/models/test_latent_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Tests basic stuff for the latent class models."""

import tensorflow as tf

from choice_learn.datasets import load_electricity
from choice_learn.models.latent_class_base_model import BaseLatentClassModel
from choice_learn.models.latent_class_mnl import LatentClassConditionalLogit, LatentClassSimpleMNL
from choice_learn.models.simple_mnl import SimpleMNL

elec_dataset = load_electricity(as_frame=False)


def test_latent_simple_mnl():
"""Test the simple latent class model fit() method."""
tf.config.run_functions_eagerly(True)
lc_model = LatentClassSimpleMNL(
n_latent_classes=3, fit_method="mle", optimizer="lbfgs", epochs=1000, lbfgs_tolerance=1e-20
)
_, _ = lc_model.fit(elec_dataset)

assert lc_model.evaluate(elec_dataset).numpy() < 1.15


def test_latent_clogit():
"""Test the conditional logit latent class model fit() method."""
tf.config.run_functions_eagerly(True)
lc_model = LatentClassConditionalLogit(
n_latent_classes=3, fit_method="mle", optimizer="lbfgs", epochs=1000, lbfgs_tolerance=1e-12
)
lc_model.add_shared_coefficient(
coefficient_name="pf", feature_name="pf", items_indexes=[0, 1, 2, 3]
)
lc_model.add_shared_coefficient(
coefficient_name="cl", feature_name="cl", items_indexes=[0, 1, 2, 3]
)
lc_model.add_shared_coefficient(
coefficient_name="loc", feature_name="loc", items_indexes=[0, 1, 2, 3]
)
lc_model.add_shared_coefficient(
coefficient_name="wk", feature_name="wk", items_indexes=[0, 1, 2, 3]
)
lc_model.add_shared_coefficient(
coefficient_name="tod", feature_name="tod", items_indexes=[0, 1, 2, 3]
)
lc_model.add_shared_coefficient(
coefficient_name="seas", feature_name="seas", items_indexes=[0, 1, 2, 3]
)
_, _ = lc_model.fit(elec_dataset)

assert lc_model.evaluate(elec_dataset).numpy() < 1.15


def test_manual_lc():
"""Test manual specification of Latent Class Simple MNL model."""
tf.config.run_functions_eagerly(True)
manual_lc = BaseLatentClassModel(
model_class=SimpleMNL,
model_parameters={"add_exit_choice": False},
n_latent_classes=3,
fit_method="mle",
epochs=1000,
optimizer="lbfgs",
lbfgs_tolerance=1e-12,
)

manual_lc.instantiate(n_items=4, n_shared_features=0, n_items_features=6)
_ = manual_lc.fit(elec_dataset)
assert manual_lc.evaluate(elec_dataset) < 1.15
5 changes: 4 additions & 1 deletion tests/integration_tests/models/test_simple_mnl.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Tests SimpleMNL."""

import tensorflow as tf

from choice_learn.datasets import load_swissmetro
from choice_learn.models import SimpleMNL

Expand All @@ -18,10 +20,11 @@ def test_simple_mnl_lbfgs_fit_with_lbfgs():

def test_simple_mnl_lbfgs_fit_with_adam():
"""Tests that SimpleMNL can fit with Adam."""
tf.config.run_functions_eagerly(True)
global dataset

model = SimpleMNL(epochs=20, optimizer="adam", batch_size=256)
model.fit(dataset)
model.fit(dataset, get_report=True)
model.evaluate(dataset)
assert model.evaluate(dataset) < 1.0

Expand Down

0 comments on commit a9db338

Please sign in to comment.