Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement hypothesis-based tests for linear models #4952

Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fe6d204
Implement first prototype for hypothesis-based linear model testing.
csadorf Oct 26, 2022
cb98126
Fix copyright years.
csadorf Oct 27, 2022
fed4b6c
Adjust comments spacing to make flake8 happy.
csadorf Oct 27, 2022
ff26882
Fix and clarify assumptions.
csadorf Oct 27, 2022
01ee031
Remove max_examples override.
csadorf Oct 27, 2022
0b1a5d8
Document search strategies.
csadorf Oct 27, 2022
d7af241
Mention hypothesis strategies in developer guide.
csadorf Oct 27, 2022
dedc1ba
Datasets output values shape is either (n_samples,) or (n_samples, n_…
csadorf Oct 27, 2022
80ae1cb
fixup! Datasets output values shape is either (n_samples,) or (n_samp…
csadorf Oct 27, 2022
fab7c72
Load hypothesis profiles based on test configuration.
csadorf Oct 28, 2022
23966fa
Increase min number of samples for split_datasets() default datasets.
csadorf Oct 28, 2022
8f18663
Slightly adjust strategy definition and extend documentation.
csadorf Nov 1, 2022
fa69180
Extend and further clarify needed assumptions.
csadorf Nov 1, 2022
31346db
Declare expected failure for test_linear_regression_model_default test.
csadorf Nov 1, 2022
53dcf8f
Drop assumption for unused variable.
csadorf Nov 1, 2022
f2a0953
Improve customizability of hypothesis regression_datasets() strategy.
csadorf Nov 4, 2022
c8c100b
Adjust hypothesis profiles for more reasonable runtimes.
csadorf Nov 2, 2022
bf71841
Suppress data_too_large hypothesis healthcheck.
csadorf Nov 4, 2022
258b233
Use a more sensible strategy for n_informative.
csadorf Nov 4, 2022
79ad0cd
Improve implementation of the split_datasets() strategy.
csadorf Nov 4, 2022
3dd7036
Adjust default sizes for standard_regression_datasets strategy.
csadorf Nov 4, 2022
e9beee9
fixup! Improve customizability of hypothesis regression_datasets() st…
csadorf Nov 4, 2022
c17c991
Add additional test for standard_dataset strategy.
csadorf Nov 4, 2022
35dfbf1
fixup! Improve implementation of the split_datasets() strategy.
csadorf Nov 7, 2022
af49c89
Find hypothesis strategy to pass test_linear_regression_model_default.
csadorf Nov 7, 2022
779a23f
Refactor required assumptions into dedicated functions.
csadorf Nov 2, 2022
af41aa2
Implement test_linear_regression_model_default_generalized as stop-gap.
csadorf Nov 7, 2022
1f99c1f
Minor fixup of the documentation.
csadorf Nov 8, 2022
22fcb64
Suppress too_slow healtcheck for flaky test.
csadorf Nov 8, 2022
117496a
Merge branch 'branch-22.12' into fea-hypothesis-based-testing-for-cpu…
csadorf Nov 9, 2022
706f1ac
Suppress too_slow healthcheck for test_split_regression_datasets.
csadorf Nov 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions python/cuml/testing/strategies.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2019-2022, NVIDIA CORPORATION.
# Copyright (c) 2022, NVIDIA CORPORATION.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -14,7 +14,8 @@
#
from hypothesis import assume
from hypothesis.extra.numpy import arrays, floating_dtypes
from hypothesis.strategies import booleans, composite, floats, integers
from hypothesis.strategies import (booleans, composite, floats, integers,
sampled_from)
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split

Expand All @@ -26,19 +27,62 @@ def datasets(
n_samples=integers(min_value=0, max_value=200),
n_features=integers(min_value=0, max_value=200),
):
"""
Generic datasets that can serve as an input to an estimator.

Parameters
----------
dtypes: SearchStrategy[np.dtype]
Returned arrays will have a dtype drawn from these types.
n_samples: SearchStrategy[int]
Returned arrays will have number of rows drawn from these values.
n_features: SearchStrategy[int]
Returned arrays will have number of columns drawn from these values.

Returns
-------
X: SearchStrategy[array] (n_samples, n_features)
The search strategy for input samples.
y: SearchStrategy[array] (n_samples,) or (n_samples, n_targets)
The search strategy for output samples.

"""
xs = draw(n_samples)
ys = draw(n_features)
X = arrays(dtype=dtypes, shape=(xs, ys))
y = arrays(dtype=dtypes, shape=(xs, 1))
y = arrays(dtype=dtypes, shape=(xs, draw(sampled_from((1, ys)))))
return draw(X), draw(y)


@composite
def split_datasets(
draw,
datasets=datasets(),
datasets=datasets(n_samples=integers(min_value=10, max_value=200)),
train_sizes=floats(min_value=0.1, max_value=1.0, exclude_max=True),
):
"""
Split a generic search strategy for datasets into test and train subsets.

Note: This function uses the sklearn.model_selection.train_test_split
function.

See also:
datasets(): A search strategy for datasets that can serve as input to this
strategy.

Parameters
----------
datasets: SearchStrategy[dataset]
A search strategy for datasets.
train_sizes: SearchStrategy[float]
A search strategy for the train size. Must be provided as float and is
limited by valid inputs to sklearn's train_test_split() function.

Returns
-------
splitting: list, length=2 * len(arrays)
List with a drawn train-test split of the drawn dataset.
"""
X, y = draw(datasets)
ts = draw(train_sizes)
assume(int(len(X) * ts) > 0) # train_test_split limitation
Expand All @@ -54,6 +98,31 @@ def regression_datasets(
n_informatives=integers(min_value=0, max_value=200),
is_normal=booleans(),
):
"""
Generic datasets that can serve as an input to an estimator.

See also:
datasets(): Generate generic datasets.
split_datasets(): Split dataset into test-train subsets.

Parameters
----------
dtypes: SearchStrategy[np.dtype]
Returned arrays will have a dtype drawn from these types.
n_samples: SearchStrategy[int]
Returned arrays will have number of rows drawn from these values.
n_features: SearchStrategy[int]
Returned arrays will have number of columns drawn from these values.
n_informatives: SearchStrategy[int]
Determines the number of informative features in a normal dataset.
is_normal: SearchStrategy[bool]
Whether the returned dataset is considered normal or more random.

Returns
-------
(X, y): tuple(SearchStrategy[array], SearchStrategy[array])
A tuple of search strategies for the requested arrays.
"""
if draw(is_normal):
dtype_ = draw(dtypes)
X, y = make_regression(
Expand Down
24 changes: 24 additions & 0 deletions python/cuml/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import numpy as np
import cupy as cp
import hypothesis

from math import ceil
from sklearn.datasets import fetch_20newsgroups
Expand All @@ -34,6 +35,20 @@
pytest_plugins = ("cuml.testing.plugins.quick_run_plugin")


# Configure hypothesis profiles

hypothesis.settings.register_profile(
name="quality",
parent=hypothesis.settings.get_profile("default"),
)

hypothesis.settings.register_profile(
name="stress",
parent=hypothesis.settings.get_profile("quality"),
max_examples=1000
)


def pytest_addoption(parser):
# Any custom option, that should be available at any time (not just a
# plugin), goes here.
Expand Down Expand Up @@ -101,6 +116,15 @@ def pytest_configure(config):
pytest.max_gpu_memory = get_gpu_memory()
pytest.adapt_stress_test = 'CUML_ADAPT_STRESS_TESTS' in os.environ

# Load special hypothesis profiles for either quality or stress tests.
# Note that the profile can be manually overwritten with the
# --hypothesis-profile command line option in which case the settings
# specified here will be ignored.
if config.getoption("--run_stress"):
hypothesis.settings.load_profile("stress")
elif config.getoption("--run_quality"):
hypothesis.settings.load_profile("quality")


@pytest.fixture(scope="module")
def nlp_20news():
Expand Down
16 changes: 7 additions & 9 deletions python/cuml/tests/test_linear_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,25 +217,23 @@ def test_linear_regression_single_column():
)
@example(small_regression_dataset(np.float32))
@example(small_regression_dataset(np.float64))
@settings(
deadline=5000, max_examples=20
) # TODO: re-evaluate max_examples after benchmarking
@settings(deadline=5000)
def test_linear_regression_model_default(dataset):

X_train, X_test, y_train, y_test = dataset
n_rows, n_cols = X_train.shape

## Required assumptions:
# sklinearRegression:
assume(n_cols >= 1)
# Required assumptions:
# sklinearRegression:
assume((X_train > 0).any())
assume((y_train > 0).any())
assume(np.isfinite(X_train).all())
assume(np.isfinite(y_train).all())
# cuml.LinearRegression:
# cuml.LinearRegression:
assume(n_rows >= 2)
# both:
# both:
assume(n_cols >= 1)
# w/o the next assumption sklearn complains and cuml hangs(!):
wphicks marked this conversation as resolved.
Show resolved Hide resolved
assume(np.isfinite(X_train).all())

# Initialization of cuML's linear regression model
cuols = cuLinearRegression()
Expand Down
2 changes: 1 addition & 1 deletion python/cuml/tests/test_strategies.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2019-2022, NVIDIA CORPORATION.
# Copyright (c) 2022, NVIDIA CORPORATION.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 2 additions & 0 deletions wiki/python/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Examples subject to numerical imprecision, or that can't be reproduced consisten
## Testing and Unit Testing
We use [https://docs.pytest.org/en/latest/]() for writing and running tests. To see existing examples, refer to any of the `test_*.py` files in the folder `cuml/tests`.

Some tests are run against inputs generated with [hypothesis](https://hypothesis.works/). See the `cuml/testing/strategies.py` module for custom strategies that can be used to test cuml estimators with diverse inputs. For example, use the `regression_datasets()` strategy to test random regression problems.

## Device and Host memory allocations
TODO: talk about enabling RMM here when it is ready

Expand Down