From 2dbc2c1c0234763a009380446b8060113cc6edd4 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 6 Aug 2023 16:44:38 +0200 Subject: [PATCH 1/5] Move general functionality from conftest to a separate module (to enable application in scripts that are not tests) --- tests/conftest.py | 73 +---------------------------------- tests/model_test_case.py | 82 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 72 deletions(-) create mode 100644 tests/model_test_case.py diff --git a/tests/conftest.py b/tests/conftest.py index 9d3f615d..9c4d7843 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,56 +2,20 @@ import os import sys -import pandas as pd import pytest -import sklearn.datasets -from sensai import InputOutputData, VectorClassificationModel, VectorRegressionModel -from sensai.evaluation import VectorClassificationModelEvaluator, VectorRegressionModelEvaluator, VectorRegressionModelEvaluatorParams +from model_test_case import IrisDataSet, ClassificationTestCase, RegressionTestCase, DiabetesDataSet, RESOURCE_DIR sys.path.append(os.path.abspath(".")) -from config import topLevelDirectory log = logging.getLogger(__name__) -RESOURCE_DIR = os.path.join(topLevelDirectory, "tests", "resources") - - @pytest.fixture(scope="session") def testResources(): return RESOURCE_DIR -class IrisDataSet: - _iod = None - - @classmethod - def getInputOutputData(cls): - if cls._iod is None: - d = sklearn.datasets.load_iris() - inputs = pd.DataFrame(d["data"], columns=d["feature_names"]) - targetNames = d["target_names"] - outputs = pd.DataFrame({"class": [targetNames[x] for x in d["target"]]}) - cls._iod = InputOutputData(inputs, outputs) - return cls._iod - - -class ClassificationTestCase: - def __init__(self, data: InputOutputData): - self.data = data - - def testMinAccuracy(self, model: VectorClassificationModel, minAccuracy: float, fit=True): - ev = VectorClassificationModelEvaluator(self.data, testFraction=0.2) - if fit: - ev.fitModel(model) - resultData = ev.evalModel(model) - stats = resultData.getEvalStats() - #stats.plotConfusionMatrix().savefig("cmat.png") - log.info(f"Results for {model.getName()}: {stats}") - assert stats.getAccuracy() >= minAccuracy - - @pytest.fixture(scope="session") def irisDataSet(): return IrisDataSet() @@ -62,41 +26,6 @@ def irisClassificationTestCase(irisDataSet): return ClassificationTestCase(irisDataSet.getInputOutputData()) -class RegressionTestCase: - def __init__(self, data: InputOutputData): - self.data = data - self.evaluatorParams = VectorRegressionModelEvaluatorParams(fractionalSplitTestFraction=0.2, fractionalSplitShuffle=True, - fractionalSplitRandomSeed=42) - - def testMinR2(self, model: VectorRegressionModel, minR2: float, fit=True): - ev = VectorRegressionModelEvaluator(self.data, params=self.evaluatorParams) - if fit: - ev.fitModel(model) - resultData = ev.evalModel(model) - stats = resultData.getEvalStats() - - #stats.plotScatterGroundTruthPredictions() - #from matplotlib import pyplot as plt; plt.show() - #resultDataTrain = ev.evalModel(model, onTrainingData=True); log.info(f"on train: {resultDataTrain.getEvalStats()}") - - log.info(f"Results for {model.getName()}: {stats}") - assert stats.getR2() >= minR2 - - -class DiabetesDataSet: - """ - Classic diabetes data set (downloaded from https://www4.stat.ncsu.edu/~boos/var.select/diabetes.tab.txt) - """ - _iod = None - - @classmethod - def getInputOutputData(cls): - if cls._iod is None: - df = pd.read_csv(os.path.join(RESOURCE_DIR, "diabetes.tab.txt"), sep="\t") - return InputOutputData.fromDataFrame(df, "Y") - return cls._iod - - @pytest.fixture(scope="session") def diabetesDataSet(): return DiabetesDataSet() diff --git a/tests/model_test_case.py b/tests/model_test_case.py new file mode 100644 index 00000000..aa8669aa --- /dev/null +++ b/tests/model_test_case.py @@ -0,0 +1,82 @@ +import os +from pathlib import Path + +import pandas as pd +import sklearn.datasets + +from sensai import InputOutputData, VectorClassificationModel, VectorRegressionModel +from sensai.evaluation import VectorClassificationModelEvaluator, VectorRegressionModelEvaluatorParams, VectorRegressionModelEvaluator +from sensai.util import logging + +log = logging.getLogger(__name__) +RESOURCE_PATH = Path(__file__).resolve().parent / "resources" +RESOURCE_DIR = str(RESOURCE_PATH) + + +class IrisDataSet: + _iod = None + + @classmethod + def getInputOutputData(cls): + if cls._iod is None: + d = sklearn.datasets.load_iris() + inputs = pd.DataFrame(d["data"], columns=d["feature_names"]) + targetNames = d["target_names"] + outputs = pd.DataFrame({"class": [targetNames[x] for x in d["target"]]}) + cls._iod = InputOutputData(inputs, outputs) + return cls._iod + + +class ClassificationTestCase: + def __init__(self, data: InputOutputData): + self.data = data + + def testMinAccuracy(self, model: VectorClassificationModel, minAccuracy: float, fit=True): + ev = VectorClassificationModelEvaluator(self.data, testFraction=0.2) + if fit: + ev.fitModel(model) + resultData = ev.evalModel(model) + stats = resultData.getEvalStats() + #stats.plotConfusionMatrix().savefig("cmat.png") + log.info(f"Results for {model.getName()}: {stats}") + assert stats.getAccuracy() >= minAccuracy + + +class RegressionTestCase: + def __init__(self, data: InputOutputData): + self.data = data + self.evaluatorParams = VectorRegressionModelEvaluatorParams(fractionalSplitTestFraction=0.2, fractionalSplitShuffle=True, + fractionalSplitRandomSeed=42) + + def createEvaluator(self) -> VectorRegressionModelEvaluator: + return VectorRegressionModelEvaluator(self.data, params=self.evaluatorParams) + + def testMinR2(self, model: VectorRegressionModel, minR2: float, fit=True): + ev = self.createEvaluator() + if fit: + ev.fitModel(model) + resultData = ev.evalModel(model) + stats = resultData.getEvalStats() + + #stats.plotScatterGroundTruthPredictions() + #from matplotlib import pyplot as plt; plt.show() + #resultDataTrain = ev.evalModel(model, onTrainingData=True); log.info(f"on train: {resultDataTrain.getEvalStats()}") + + log.info(f"Results for {model.getName()}: {stats}") + assert stats.getR2() >= minR2 + + +class DiabetesDataSet: + """ + Classic diabetes data set (downloaded from https://www4.stat.ncsu.edu/~boos/var.select/diabetes.tab.txt) + """ + _iod = None + categorical_features = ["SEX"] + numeric_features = ["AGE", "BMI", "BP", "S1", "S2", "S3", "S4", "S5", "S6"] + + @classmethod + def getInputOutputData(cls): + if cls._iod is None: + df = pd.read_csv(os.path.join(RESOURCE_DIR, "diabetes.tab.txt"), sep="\t") + return InputOutputData.fromDataFrame(df, "Y") + return cls._iod From c70877c5d8087d9269952183daca0eef720b3d00 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 6 Aug 2023 16:45:41 +0200 Subject: [PATCH 2/5] dumpPickle: Support passing Path objects --- src/sensai/util/pickle.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sensai/util/pickle.py b/src/sensai/util/pickle.py index dfc5b593..18dba775 100644 --- a/src/sensai/util/pickle.py +++ b/src/sensai/util/pickle.py @@ -1,7 +1,8 @@ import logging import os import pickle -from typing import List, Dict, Any, Iterable +from pathlib import Path +from typing import List, Dict, Any, Iterable, Union import joblib @@ -29,7 +30,9 @@ def readFile(f): return readFile(f) -def dumpPickle(obj, picklePath, backend="pickle", protocol=pickle.HIGHEST_PROTOCOL): +def dumpPickle(obj, picklePath: Union[str, Path], backend="pickle", protocol=pickle.HIGHEST_PROTOCOL): + if isinstance(picklePath, Path): + picklePath = str(picklePath) def openFile(): if isS3Path(picklePath): return S3Object(picklePath).openFile("wb") From f10b84bc9a1ab2818c87ddd1c65743b7fdc33ff4 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 6 Aug 2023 16:45:14 +0200 Subject: [PATCH 3/5] Add script for the creation of regression models for backward compatibility tests --- tests/backwardscompat/create_test_models.py | 58 +++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/backwardscompat/create_test_models.py diff --git a/tests/backwardscompat/create_test_models.py b/tests/backwardscompat/create_test_models.py new file mode 100644 index 00000000..21e41b61 --- /dev/null +++ b/tests/backwardscompat/create_test_models.py @@ -0,0 +1,58 @@ +import sys + +from sensai.data_transformation import DFTNormalisation, DFTOneHotEncoder, SkLearnTransformerFactoryFactory +from sensai.featuregen import FeatureCollector, FeatureGeneratorTakeColumns +from sensai.sklearn.sklearn_regression import SkLearnLinearRegressionVectorRegressionModel, SkLearnRandomForestVectorRegressionModel, \ + SkLearnMultiLayerPerceptronVectorRegressionModel +from sensai.util import logging +from sensai.util.pickle import dumpPickle +from tests.model_test_case import DiabetesDataSet, RegressionTestCase, RESOURCE_PATH + + +def create_regression_models_for_backward_compatibility_test(version): + """ + :param version: version with which the files are created + """ + dataset = DiabetesDataSet() + + fc = FeatureCollector( + FeatureGeneratorTakeColumns(dataset.categorical_features, categoricalFeatureNames=dataset.categorical_features, + normalisationRuleTemplate=DFTNormalisation.RuleTemplate(unsupported=True)), + FeatureGeneratorTakeColumns(dataset.numeric_features, + normalisationRuleTemplate=DFTNormalisation.RuleTemplate(independentColumns=True))) + + modelLinear = SkLearnLinearRegressionVectorRegressionModel() \ + .withFeatureCollector(fc) \ + .withFeatureTransformers( + DFTOneHotEncoder(fc.getCategoricalFeatureNameRegex()), + DFTNormalisation(fc.getNormalisationRules(), defaultTransformerFactory=SkLearnTransformerFactoryFactory.RobustScaler())) \ + .withName("Linear") + + modelRF = SkLearnRandomForestVectorRegressionModel(n_estimators=10, min_samples_leaf=10) \ + .withFeatureCollector(fc) \ + .withFeatureTransformers(DFTOneHotEncoder(fc.getCategoricalFeatureNameRegex())) \ + .withName("RandomForest") + + modelMLP = SkLearnMultiLayerPerceptronVectorRegressionModel(hidden_layer_sizes=(20,20), solver="adam", max_iter=1000, batch_size=32, early_stopping=True) \ + .withFeatureCollector(fc) \ + .withFeatureTransformers( + DFTOneHotEncoder(fc.getCategoricalFeatureNameRegex()), + DFTNormalisation(fc.getNormalisationRules(), defaultTransformerFactory=SkLearnTransformerFactoryFactory.RobustScaler())) \ + .withName("SkLearnMLP") + + testCase = RegressionTestCase(dataset.getInputOutputData()) + ev = testCase.createEvaluator() + for model in [modelLinear, modelRF, modelMLP]: + ev.fitModel(model) + eval_data = ev.evalModel(model) + eval_stats = eval_data.getEvalStats() + print(eval_stats) + r2 = eval_stats.getR2() + persisted_data = {"R2": r2, "model": model} + dumpPickle(persisted_data, RESOURCE_PATH / "backward_compatibility" / f"regression_model_{model.getName()}.{version}.pickle") + + +if __name__ == '__main__': + logging.configureLogging() + sys.path.append("../..") + create_regression_models_for_backward_compatibility_test("v0.2.0") From be2c86050633d17e389ecc7994f17b1122b8a65c Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Sun, 6 Aug 2023 16:51:41 +0200 Subject: [PATCH 4/5] Add test for backward compatibility of v0.2.0 regression models --- tests/backwardscompat/test_models.py | 46 ++++++------------ .../regression_model_Linear.v0.2.0.pickle | Bin 0 -> 4470 bytes ...egression_model_RandomForest.v0.2.0.pickle | Bin 0 -> 28494 bytes .../regression_model_SkLearnMLP.v0.2.0.pickle | Bin 0 -> 33146 bytes 4 files changed, 14 insertions(+), 32 deletions(-) create mode 100644 tests/resources/backward_compatibility/regression_model_Linear.v0.2.0.pickle create mode 100644 tests/resources/backward_compatibility/regression_model_RandomForest.v0.2.0.pickle create mode 100644 tests/resources/backward_compatibility/regression_model_SkLearnMLP.v0.2.0.pickle diff --git a/tests/backwardscompat/test_models.py b/tests/backwardscompat/test_models.py index 44862ccc..076a05b0 100644 --- a/tests/backwardscompat/test_models.py +++ b/tests/backwardscompat/test_models.py @@ -1,15 +1,15 @@ import os +from glob import glob + +import pytest import sensai from sensai import VectorModel -from sensai.data_transformation import DFTNormalisation, SkLearnTransformerFactoryFactory, DFTOneHotEncoder -from sensai.featuregen import FeatureGeneratorTakeColumns, FeatureCollector -from sensai.sklearn.sklearn_regression import SkLearnLinearRegressionVectorRegressionModel, SkLearnRandomForestVectorRegressionModel, \ - SkLearnMultiLayerPerceptronVectorRegressionModel -from tests.conftest import RegressionTestCase +from sensai.util.pickle import loadPickle +from tests.conftest import RESOURCE_DIR -def test_modelCanBeLoaded(testResources, irisClassificationTestCase): +def test_classification_model_backward_compatibility_v0_0_4(testResources, irisClassificationTestCase): # The model file was generated with tests/frameworks/torch/test_torch.test_MLPClassifier at commit f93c6b11d # NOTE: This test fails with scikit-learn 0.23 because of a change in StandardScaler modelPath = os.path.join(testResources, "torch_mlp.pickle") @@ -18,29 +18,11 @@ def test_modelCanBeLoaded(testResources, irisClassificationTestCase): irisClassificationTestCase.testMinAccuracy(model, 0.8, fit=False) -def createRegressionModelsForBackwardsCompatibilityTest(testCase: RegressionTestCase): - fc = FeatureCollector(FeatureGeneratorTakeColumns(categoricalFeatureNames=["SEX"], - normalisationRuleTemplate=DFTNormalisation.RuleTemplate(independentColumns=False))) - - modelLinear = SkLearnLinearRegressionVectorRegressionModel() \ - .withFeatureCollector(fc) \ - .withFeatureTransformers( - DFTOneHotEncoder(fc.getCategoricalFeatureNameRegex())) - #DFTNormalisation(fc.getNormalisationRules(), defaultTransformerFactory=SkLearnTransformerFactoryFactory.RobustScaler())) - - modelRF = SkLearnRandomForestVectorRegressionModel() \ - .withFeatureCollector(fc) \ - .withFeatureTransformers(DFTOneHotEncoder(fc.getCategoricalFeatureNameRegex())) - - modelMLP = SkLearnMultiLayerPerceptronVectorRegressionModel(hidden_layer_sizes=(10, 10), solver="lbfgs") \ - .withFeatureCollector(fc) \ - .withFeatureTransformers( - DFTOneHotEncoder(fc.getCategoricalFeatureNameRegex()), - DFTNormalisation(fc.getNormalisationRules(), defaultTransformerFactory=SkLearnTransformerFactoryFactory.RobustScaler())) - - return modelMLP - - -def test_backward_compatibility_v020(diabetesRegressionTestCase): - model = createRegressionModelsForBackwardsCompatibility(diabetesRegressionTestCase) - diabetesRegressionTestCase.testMinR2(model, 0.5, fit=True) \ No newline at end of file +@pytest.mark.parametrize("pickle_file", glob(f"{RESOURCE_DIR}/backward_compatibility/regression_model_*.v0.2.0.pickle")) +def test_regression_model_backward_compatibility_v0_2_0(pickle_file, diabetesRegressionTestCase): + """ + Tests for compatibility with models created with v0.2.0 using create_test_models.py + """ + d = loadPickle(pickle_file) + r2, model = d["R2"], d["model"] + diabetesRegressionTestCase.testMinR2(model, r2-0.02, fit=False) diff --git a/tests/resources/backward_compatibility/regression_model_Linear.v0.2.0.pickle b/tests/resources/backward_compatibility/regression_model_Linear.v0.2.0.pickle new file mode 100644 index 0000000000000000000000000000000000000000..121e393d045940898b233485af41d014a102d493 GIT binary patch literal 4470 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

N7IPF z7o5%@!~aXw>KPAwVwUKPNzjJ0z3K*k#l@oK0hRC}Q7_Qfe=FahyNs^;NA6$uVnvmO z^6s$d4$HMkH?i)@}|CiMLAG4R#lw{A}K)E<`ODfW#WIFx7&n*Al iW+cwMm~ Date: Thu, 3 Aug 2023 22:36:25 +0200 Subject: [PATCH 5/5] Ignore .vscode --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5c5766d2..ab76546d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /.idea +/.vscode *.iml *.pyc *.pyc.* @@ -26,4 +27,4 @@ data *.code-workspace /TODO.txt notebooks/temp.ipynb -/docs/build \ No newline at end of file +/docs/build