Skip to content

Commit 344a166

Browse files
Implementation of get_lr_system and get_reference_data (#23)
* First basic implementation * update based on review comments * Add functions to load LR system and reference data from disk * Update .gitignore, remove test_validation_yaml, and add test_api for loading LR systems * Update lir package version to 1.4.0
1 parent c738bee commit 344a166

7 files changed

Lines changed: 123 additions & 20 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ output/
55
lrsystem_output/
66
.testmondata
77
tests/test_model_storage/
8+
tests/saved_models/
89

910
# Byte-compiled / optimized / DLL files
1011
__pycache__/

lrmodule/__init__.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,60 @@
1+
import pickle
12
from pathlib import Path
23

34
import numpy as np
45
from lir.config.lrsystem_architectures import specific_source
56
from lir.data.models import FeatureData, LLRData
7+
from lir.datasets.feature_data_csv import FeatureDataCsvFileParser
68
from lir.lrsystems.lrsystems import LRSystem
79

810
from lrmodule import persistence
911
from lrmodule.data_types import ModelSettings
1012
from lrmodule.lrsystem import get_trained_model
1113

1214

15+
def get_lr_system(lr_system_folder: Path, file_name: str = "model.pkl") -> LRSystem:
16+
"""
17+
Load a trained LR system from disk from a given folder.
18+
19+
It is expected that the folder contains a file named "model.pkl" (or another name specified by file_name),
20+
which is a pickled LRSystem object. The function loads this object and returns it.
21+
22+
The system is returned as an instance of the LRSystem class. This class provides an apply method, which can be used
23+
to calculate LLRs for given features. These features should be contained in a FeatureData object from lir.
24+
25+
Example usage:
26+
```
27+
from lir.data.models import FeatureData
28+
29+
lr_system = get_lr_system(Path("path/to/lr_system_folder/"))
30+
31+
# Create three instances of features, each with two feature values.
32+
features = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]])
33+
feature_data = FeatureData(features=features)
34+
llr_data = lr_system.apply(feature_data)
35+
```
36+
"""
37+
with (lr_system_folder / file_name).open("rb") as f:
38+
return pickle.load(f) # noqa: S301
39+
40+
41+
def get_reference_data(lr_system_folder: Path, file_name: str = "reference_data.csv") -> FeatureData:
42+
"""Load reference data from disk.
43+
44+
It is expected that the folder contains a file named "reference_data.csv" (or another name specified by file_name),
45+
which is a CSV file containing the reference data. The function loads this data and returns it as a FeatureData
46+
object.
47+
48+
If the data has `n` features, the CSV file should have `n+1` columns. One of the columns should be named
49+
"hypothesis", and contain the labels for the data. The other columns should contain the feature values.
50+
51+
The data is returned as an instance of the FeatureData class from lir. This class has a features and a labels
52+
attribute, which can be used to access the feature values and the labels, respectively.
53+
"""
54+
reference_data_file = lr_system_folder / file_name
55+
return FeatureDataCsvFileParser(file=reference_data_file, label_column="hypothesis").get_instances()
56+
57+
1358
def get_model(settings: ModelSettings, training_data: FeatureData, model_storage_path: Path | None) -> LRSystem:
1459
"""
1560
Obtain a model by loading it from disk, or by fitting it from training data.

pdm.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ license = {text = "Apache Software License 2.0"}
1414

1515
lir = {url = "https://github.com/NetherlandsForensicInstitute/lir", rev = "335f8e0dc458b3d969b1a126e492c04cbf8499ab" }
1616
dependencies = [
17-
"lir==1.3.3",
17+
"lir==1.4",
1818
"pandas>=2.3.3",
1919
"pytest-randomly>=4.0.1",
2020
]

tests/test_api.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import os
2+
3+
import confidence
4+
import pytest
5+
from lir.data.models import FeatureData
6+
from lir.lrsystems import LRSystem
7+
from lir.main import initialize_experiments
8+
from lir.util import check_type
9+
10+
from lrmodule import Path, get_lr_system, get_reference_data
11+
12+
# TEST_FOLDER is a folder that contains packages (folders) with everything relevant for that specific model
13+
# (e.g. reference data, trained model, etc.).
14+
TEST_FOLDER = Path(__file__).parent / 'saved_models'
15+
os.makedirs(TEST_FOLDER, exist_ok=True)
16+
17+
# Generate models at import time so that TEST_FOLDER is populated before @pytest.mark.parametrize
18+
# evaluates its argument list during collection.
19+
_yaml_file = Path(__file__).parent.parent / 'validation.yaml'
20+
_cfg = confidence.Configuration(confidence.loadf(_yaml_file), {'output_path': TEST_FOLDER})
21+
_exps, _ = initialize_experiments(_cfg)
22+
for _exp in _exps.values():
23+
_exp.run()
24+
25+
26+
# Test for every folder in TEST_FOLDER that the `get_lr_system` function can load a trained LR system and that it
27+
# returns an LR system object.
28+
@pytest.mark.parametrize("model_folder", [f for f in TEST_FOLDER.iterdir() if f.is_dir()])
29+
def test_get_lr_system_loads_trained_model(model_folder: Path):
30+
"""Check that the `get_lr_system` function can load a trained LR system."""
31+
lr_system = get_lr_system(model_folder)
32+
33+
# We should get an LR system object
34+
assert lr_system is not None
35+
check_type(LRSystem, lr_system)
36+
37+
38+
@pytest.mark.parametrize("model_folder", [f for f in TEST_FOLDER.iterdir() if f.is_dir()])
39+
def test_get_reference_data(model_folder: Path):
40+
"""Check that the `get_reference_data` function can load reference data."""
41+
reference_data = get_reference_data(model_folder)
42+
43+
# We should get reference data
44+
assert reference_data is not None
45+
check_type(FeatureData, reference_data)

tests/test_validation_yaml.py

Lines changed: 0 additions & 16 deletions
This file was deleted.

validation.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ experiments:
152152
- pav
153153
- ece
154154
- llr_interval
155+
- save_model
156+
- method: lrmodule.copy_csv.copy_csv
157+
file: ${data_root}/aperture_shear.csv
158+
new_file_name: reference_data.csv
159+
columns:
160+
- hypothesis
161+
- ccf
155162
# - score_histogram (ongetransformeerd)
156163
# - score_histogram (normalized (area=1))
157164

@@ -167,9 +174,16 @@ experiments:
167174
columns:
168175
- cllr
169176
- cllr_min
177+
- method: lrmodule.copy_csv.copy_csv
178+
file: ${data_root}/firing_pin_impression.csv
179+
new_file_name: reference_data.csv
180+
columns:
181+
- hypothesis
182+
- accf
170183
- pav
171184
- ece
172185
- llr_interval
186+
- save_model
173187

174188
- name: breech_face_impression_model_accf
175189
strategy: single_run
@@ -189,6 +203,12 @@ experiments:
189203
- method: lrmodule.copy_csv.copy_csv
190204
file: ${data_root}/breech_face_impression.csv
191205
new_file_name: source_data.csv
206+
- method: lrmodule.copy_csv.copy_csv
207+
file: ${data_root}/breech_face_impression.csv
208+
new_file_name: reference_data.csv
209+
columns:
210+
- hypothesis
211+
- accf
192212
- save_model
193213

194214

@@ -204,7 +224,15 @@ experiments:
204224
columns:
205225
- cllr
206226
- cllr_min
227+
- method: lrmodule.copy_csv.copy_csv
228+
file: ${data_root}/breech_face_impression.csv
229+
new_file_name: reference_data.csv
230+
columns:
231+
- hypothesis
232+
- cmc
233+
- n
207234
- pav
208235
- ece
209236
- llr_interval
237+
- save_model
210238

0 commit comments

Comments
 (0)