From bbe9bec0177307ac9a2c4002ed0249b46d959cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Bj=C3=B6rklund?= <--get-all> Date: Thu, 24 Mar 2022 02:19:04 +0200 Subject: [PATCH 1/9] replace get_params() with coefficients() --- slise/__init__.py | 15 +++++++++++++-- slise/slise.py | 26 +++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/slise/__init__.py b/slise/__init__.py index b8a3ee1..8feb077 100644 --- a/slise/__init__.py +++ b/slise/__init__.py @@ -21,7 +21,7 @@ constraints used to generate the data, e.g., the laws of physics). - More in-depth details about the algorithm can be found in the paper: + More in-depth details about the algorithm can be found in the papers: Björklund A., Henelius A., Oikarinen E., Kallonen K., Puolamäki K. Sparse Robust Regression for Explaining Classifiers. @@ -29,8 +29,19 @@ Lecture Notes in Computer Science, vol 11828, Springer. https://doi.org/10.1007/978-3-030-33778-0_27 + Björklund A., Henelius A., Oikarinen E., Kallonen K., Puolamäki K. + Robust regression via error tolerance. + Data Mining and Knowledge Discovery (2022). + https://doi.org/10.1007/s10618-022-00819-2 + """ -from slise.slise import SliseRegression, regression, SliseExplainer, explain +from slise.slise import ( + SliseRegression, + regression, + SliseExplainer, + explain, + SliseWarning, +) from slise.utils import limited_logit as logit from slise.data import normalise_robust diff --git a/slise/slise.py b/slise/slise.py index 1ef6f0c..1d20577 100644 --- a/slise/slise.py +++ b/slise/slise.py @@ -8,7 +8,7 @@ from warnings import warn import numpy as np -from matplotlib.pyplot import Figure, yscale +from matplotlib.pyplot import Figure from scipy.special import expit as sigmoid from slise.data import ( @@ -310,6 +310,18 @@ def fit( def get_params(self, normalised: bool = False) -> np.ndarray: """Get the coefficients of the linear model. + Args: + normalised (bool, optional): If the data is normalised within SLISE, return a linear model ftting the normalised data. Defaults to False. + + Returns: + np.ndarray: The coefficients of the linear model. + """ + warn("Use `coefficients()` instead of `get_params()`.", SliseWarning) + return self._alpha if normalised else self._coefficients + + def coefficients(self, normalised: bool = False) -> np.ndarray: + """Get the coefficients of the linear model. + Args: normalised (bool, optional): If the data is normalised within SLISE, return a linear model ftting the normalised data. Defaults to False. @@ -679,6 +691,18 @@ def explain( def get_params(self, normalised: bool = False) -> np.ndarray: """Get the explanation as the coefficients of a linear model (approximating the black box model). + Args: + normalised (bool, optional): If the data is normalised within SLISE, return a linear model fitting the normalised data. Defaults to False. + + Returns: + np.ndarray: The coefficients of the linear model (the first scalar in the vector is the intercept). + """ + warn("Use `coefficients()` instead of `get_params().", SliseWarning) + return self._alpha if normalised else self._coefficients + + def coefficients(self, normalised: bool = False) -> np.ndarray: + """Get the explanation as the coefficients of a linear model (approximating the black box model). + Args: normalised (bool, optional): If the data is normalised within SLISE, return a linear model fitting the normalised data. Defaults to False. From 32f72845ac6cca869d0ba6196edec9b0c8c969c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Bj=C3=B6rklund?= <--get-all> Date: Thu, 24 Mar 2022 20:17:38 +0200 Subject: [PATCH 2/9] num_threads and check threading_layer --- slise/optimisation.py | 29 ++++++++++++++++++++++++++++- slise/slise.py | 25 ++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/slise/optimisation.py b/slise/optimisation.py index d2390d8..80a612a 100644 --- a/slise/optimisation.py +++ b/slise/optimisation.py @@ -6,7 +6,7 @@ import numpy as np from lbfgs import LBFGSError, fmin_lbfgs -from numba import jit +from numba import jit, get_num_threads, set_num_threads, threading_layer from scipy.optimize import brentq from slise.utils import ( @@ -375,6 +375,33 @@ def debug_log( ) +def set_threads(num: int = -1) -> int: + """Set the number of numba threads + + Args: + num (int, optional): The number of threads. Defaults to -1. + + Returns: + int: The old number of theads (or -1 if unchanged). + """ + if num > 0: + old = get_num_threads() + set_num_threads(num) + return old + return -1 + + +def check_threading_layer(): + """Check which numba threading_layer is active, and warn if it is "workqueue". + """ + loss_residuals(np.ones(1), np.ones(1), 1) + if threading_layer() == "workqueue": + warn( + 'Using `numba.threading_layer()=="workqueue"` can be devastatingly slow!', + SliseWarning, + ) + + def graduated_optimisation( alpha: np.ndarray, X: np.ndarray, diff --git a/slise/slise.py b/slise/slise.py index 1d20577..efcbc46 100644 --- a/slise/slise.py +++ b/slise/slise.py @@ -19,7 +19,12 @@ remove_constant_columns, ) from slise.initialisation import initialise_candidates, initialise_fixed -from slise.optimisation import graduated_optimisation, loss_sharp +from slise.optimisation import ( + check_threading_layer, + graduated_optimisation, + loss_sharp, + set_threads, +) from slise.plot import plot_2d, plot_dist, plot_dist_single, plot_image, print_slise from slise.utils import SliseWarning, limited_logit, mat_mul_inter @@ -41,6 +46,7 @@ def regression( max_approx: float = 1.15, max_iterations: int = 300, debug: bool = False, + num_threads: int = -1, ) -> SliseRegression: """Use SLISE for robust regression @@ -68,6 +74,7 @@ def regression( max_approx (float, optional): Approximation ratio when selecting the next beta. Defaults to 1.15. max_iterations (int, optional): Maximum number of OWL-QN iterations. Defaults to 300. debug (bool, optional): Print debug statements each graduated optimisation step. Defaults to False. + num_threads (int, optional): The number of threads to use for the optimisation. Defaults to -1. Returns: SliseRegression: Object containing the regression result. @@ -83,6 +90,7 @@ def regression( max_approx=max_approx, max_iterations=max_iterations, debug=debug, + num_threads=num_threads, ).fit(X=X, Y=Y, weight=weight, init=init) @@ -105,6 +113,7 @@ def explain( max_approx: float = 1.15, max_iterations: int = 300, debug: bool = False, + num_threads: int = -1, ) -> SliseExplainer: """Use SLISE for explaining outcomes from black box models. @@ -138,6 +147,7 @@ def explain( max_approx (float, optional): Approximation ratio when selecting the next beta. Defaults to 1.15. max_iterations (int, optional): Maximum number of OWL-QN iterations. Defaults to 300. debug (bool, optional): Print debug statements each graduated optimisation step. Defaults to False. + num_threads (int, optional): The number of threads to use for the optimisation. Defaults to -1. Returns: SliseExplainer: Object containing the explanation. @@ -155,6 +165,7 @@ def explain( max_approx=max_approx, max_iterations=max_iterations, debug=debug, + num_threads=num_threads, ).explain(x=x, y=y, weight=weight, init=init) @@ -179,6 +190,7 @@ def __init__( max_approx: float = 1.15, max_iterations: int = 300, debug: bool = False, + num_threads: int = -1, ): """Use SLISE for robust regression. @@ -202,6 +214,7 @@ def __init__( max_approx (float, optional): Approximation ratio when selecting the next beta. Defaults to 1.15. max_iterations (int, optional): Maximum number of OWL-QN iterations. Defaults to 300. debug (bool, optional): Print debug statements each graduated optimisation step. Defaults to False. + num_threads (int, optional): The number of threads to use for the optimisation. Defaults to -1. """ assert epsilon > 0.0, "`epsilon` must be positive!" assert lambda1 >= 0.0, "`lambda1` must not be negative!" @@ -225,6 +238,8 @@ def __init__( self._weight = None self._alpha = None self._coefficients = None + self.num_threads = num_threads + check_threading_layer() def fit( self, @@ -270,6 +285,7 @@ def fit( if self._intercept: X = add_intercept_column(X) # Initialisation + threads = set_threads(self.num_threads) if init is None: alpha, beta = self.init_fn(X, Y, self.epsilon, self._weight) else: @@ -289,6 +305,7 @@ def fit( max_iterations=self.max_iterations, debug=self.debug, ) + set_threads(threads) self._alpha = alpha if self._normalise: alpha2 = self._scale.unscale_model(alpha) @@ -533,6 +550,7 @@ def __init__( max_approx: float = 1.15, max_iterations: int = 300, debug: bool = False, + num_threads: int = -1, ): """Use SLISE for explaining outcomes from black box models. @@ -563,6 +581,7 @@ def __init__( max_approx (float, optional): Approximation ratio when selecting the next beta. Defaults to 1.15. max_iterations (int, optional): Maximum number of OWL-QN iterations. Defaults to 300. debug (bool, optional): Print debug statements each graduated optimisation step. Defaults to False. + num_threads (int, optional): The number of threads to use for the optimisation. Defaults to -1. """ assert epsilon > 0.0, "`epsilon` must be positive!" assert lambda1 >= 0.0, "`lambda1` must not be negative!" @@ -609,6 +628,8 @@ def __init__( self._scale = None self._X2 = X self._Y2 = Y + self.num_threads = num_threads + check_threading_layer() def explain( self, @@ -655,6 +676,7 @@ def explain( y = self._scale.scale_y(y) X = self._X2 - x[None, :] Y = self._Y2 - y + threads = set_threads(self.num_threads) if init is None: alpha, beta = self.init_fn(X, Y, self.epsilon, self._weight) else: @@ -673,6 +695,7 @@ def explain( max_iterations=self.max_iterations, debug=self.debug, ) + set_threads(threads) alpha = np.concatenate( (y - np.sum(alpha * x, dtype=x.dtype, keepdims=True), alpha) ) From 57b881359905c07aab5d98b92f3e699a29c9bf31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Bj=C3=B6rklund?= <--get-all> Date: Thu, 24 Mar 2022 21:15:13 +0200 Subject: [PATCH 3/9] refine coefficients and normalised plus add some warnings --- slise/slise.py | 125 +++++++++++++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 45 deletions(-) diff --git a/slise/slise.py b/slise/slise.py index efcbc46..59d5149 100644 --- a/slise/slise.py +++ b/slise/slise.py @@ -333,31 +333,46 @@ def get_params(self, normalised: bool = False) -> np.ndarray: Returns: np.ndarray: The coefficients of the linear model. """ - warn("Use `coefficients()` instead of `get_params()`.", SliseWarning) + warn("Use `coefficients` instead of `get_params()`.", SliseWarning) return self._alpha if normalised else self._coefficients - def coefficients(self, normalised: bool = False) -> np.ndarray: + @property + def coefficients(self) -> np.ndarray: """Get the coefficients of the linear model. + Returns: + np.ndarray: The coefficients of the linear model (the first scalar in the vector is the intercept). + """ + if self._coefficients is None: + warn("Fit the model before retrieving coefficients", SliseWarning) + return self._coefficients + + def normalised(self, all_columns: bool = True) -> Optional[np.ndarray]: + """Get coefficients for normalised data (if the data is normalised within SLISE). + Args: - normalised (bool, optional): If the data is normalised within SLISE, return a linear model ftting the normalised data. Defaults to False. + all_columns (bool, optional): Add coefficients for constant columns. Defaults to True. Returns: - np.ndarray: The coefficients of the linear model. + Optional[np.ndarray]: The normalised coefficients or None. """ - return self._alpha if normalised else self._coefficients - - @property - def normalised(self): + if self._alpha is None: + warn("Fit the model before retrieving coefficients", SliseWarning) if self._normalise: - return add_constant_columns( - self._alpha, self._scale.columns, self._intercept - ) + if all_columns: + return add_constant_columns(self._alpha, self._scale.columns, True) + else: + return self._alpha else: return None @property - def scaled_epsilon(self): + def scaled_epsilon(self) -> float: + """Espilon fitting unnormalised data (if the data is normalised). + + Returns: + float: Scaled epsilon. + """ if self._normalise: return self.epsilon * self._scale.y_scale else: @@ -373,9 +388,9 @@ def predict(self, X: Union[np.ndarray, None] = None) -> np.ndarray: np.ndarray: Predicted response. """ if X is None: - return mat_mul_inter(self._X, self._coefficients) + return mat_mul_inter(self._X, self.coefficients) else: - return mat_mul_inter(X, self._coefficients) + return mat_mul_inter(X, self.coefficients) def score( self, X: Union[np.ndarray, None] = None, Y: Union[np.ndarray, None] = None @@ -389,6 +404,8 @@ def score( Returns: float: The loss. """ + if self._alpha is None: + warn("Fit the model before calculating the score", SliseWarning) if X is None or Y is None: X = self._X Y = self._Y @@ -400,6 +417,7 @@ def score( ) loss = score + value = score def subset( self, X: Union[np.ndarray, None] = None, Y: Union[np.ndarray, None] = None @@ -416,7 +434,7 @@ def subset( if X is None or Y is None: X = self._X Y = self._Y - Y2 = mat_mul_inter(X, self._coefficients) + Y2 = mat_mul_inter(X, self.coefficients) return (Y2 - Y) ** 2 < self.scaled_epsilon ** 2 def print( @@ -433,7 +451,7 @@ def print( decimals (int, optional): Precision to use for printing. Defaults to 3. """ print_slise( - self._coefficients, + self.coefficients, self._intercept, self.subset(), self.score(), @@ -442,7 +460,7 @@ def print( "SLISE Regression", decimals, num_var, - alpha=self.normalised, + alpha=self.normalised(), ) def plot_2d( @@ -468,7 +486,7 @@ def plot_2d( plot_2d( self._X, self._Y, - self._coefficients, + self.coefficients, self.scaled_epsilon, None, None, @@ -498,9 +516,9 @@ def plot_dist( plot_dist( self._X, self._Y, - self._coefficients, + self.coefficients, self.subset(), - self.normalised, + self.normalised(), None, None, None, @@ -720,29 +738,46 @@ def get_params(self, normalised: bool = False) -> np.ndarray: Returns: np.ndarray: The coefficients of the linear model (the first scalar in the vector is the intercept). """ - warn("Use `coefficients()` instead of `get_params().", SliseWarning) + warn("Use `coefficients` instead of `get_params().", SliseWarning) return self._alpha if normalised else self._coefficients - def coefficients(self, normalised: bool = False) -> np.ndarray: + @property + def coefficients(self) -> np.ndarray: """Get the explanation as the coefficients of a linear model (approximating the black box model). - Args: - normalised (bool, optional): If the data is normalised within SLISE, return a linear model fitting the normalised data. Defaults to False. - Returns: np.ndarray: The coefficients of the linear model (the first scalar in the vector is the intercept). """ - return self._alpha if normalised else self._coefficients + if self._coefficients is None: + warn("Fit an explanation before retrieving coefficients", SliseWarning) + return self._coefficients - @property - def normalised(self): + def normalised(self, all_columns: bool = True) -> Optional[np.ndarray]: + """Get coefficients for normalised data (if the data is normalised within SLISE). + + Args: + all_columns (bool, optional): Add coefficients for constant columns. Defaults to True. + + Returns: + Optional[np.ndarray]: The normalised coefficients or None. + """ + if self._alpha is None: + warn("Fit an explanation before retrieving coefficients", SliseWarning) if self._normalise: - return add_constant_columns(self._alpha, self._scale.columns, True) + if all_columns: + return add_constant_columns(self._alpha, self._scale.columns, True) + else: + return self._alpha else: return None @property - def scaled_epsilon(self): + def scaled_epsilon(self) -> float: + """Espilon fitting unnormalised data (if the data is normalised). + + Returns: + float: Scaled epsilon. + """ if self._normalise: return self.epsilon * self._scale.y_scale else: @@ -758,9 +793,9 @@ def predict(self, X: Union[np.ndarray, None] = None) -> np.ndarray: np.ndarray: Prediction vector. """ if X is None: - Y = mat_mul_inter(self._X, self._coefficients) + Y = mat_mul_inter(self._X, self.coefficients) else: - Y = mat_mul_inter(X, self._coefficients) + Y = mat_mul_inter(X, self.coefficients) if self._logit: Y = sigmoid(Y) return Y @@ -777,6 +812,8 @@ def score( Returns: float: The loss. """ + if self._alpha is None: + warn("Fit an explanation before calculating the score", SliseWarning) x = self._x y = self._y if self._logit: @@ -806,6 +843,7 @@ def score( ) loss = score + value = score def subset( self, X: Union[np.ndarray, None] = None, Y: Union[np.ndarray, None] = None @@ -824,7 +862,7 @@ def subset( Y = self._Y if self._logit: Y = limited_logit(Y) - res = mat_mul_inter(X, self._coefficients) - Y + res = mat_mul_inter(X, self.coefficients) - Y return res ** 2 < self.scaled_epsilon ** 2 def get_impact( @@ -843,13 +881,10 @@ def get_impact( if x is None: x = self._x if normalised and self._normalise: - return add_constant_columns( - add_intercept_column(self._scale.scale_x(x)) * self._alpha, - self._scale.columns, - True, - ) + x = add_constant_columns(self._scale.scale_x(x), self._scale.columns, False) + return add_intercept_column(x) * self.coefficients else: - return add_intercept_column(x) * self._coefficients + return add_intercept_column(x) * self.coefficients def print( self, @@ -867,7 +902,7 @@ def print( decimals (int, optional): Precision to use for printing. Defaults to 3. """ print_slise( - self._coefficients, + self.coefficients, True, self.subset(), self.score(), @@ -880,7 +915,7 @@ def print( unscaled_y=self._y, impact=self.get_impact(False), scaled=None if self._scale is None else self._scale.scale_x(self._x, False), - alpha=self.normalised, + alpha=self.normalised(), scaled_impact=None if self._scale is None else self.get_impact(True), classes=classes, unscaled_preds=self._Y, @@ -910,7 +945,7 @@ def plot_2d( plot_2d( self._X, self._Y, - self._coefficients, + self.coefficients, self.scaled_epsilon, self._x, self._y, @@ -947,7 +982,7 @@ def plot_image( self._x, self._y, self._Y, - self._coefficients, + self.coefficients, width, height, saturated, @@ -982,9 +1017,9 @@ def plot_dist( plot_dist( self._X, self._Y, - self._coefficients, + self.coefficients, self.subset(), - self.normalised, + self.normalised(), self._x, self._y, self.get_impact(False), From d15034ca1117e30293f0ebc13e4668cc234f3be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Bj=C3=B6rklund?= <--get-all> Date: Thu, 24 Mar 2022 21:24:57 +0200 Subject: [PATCH 4/9] add threading tests --- tests/test_optim.py | 24 +++++++++++------------- tests/test_slise.py | 14 +++++++++++++- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/tests/test_optim.py b/tests/test_optim.py index 13bbe99..4cd0c0b 100644 --- a/tests/test_optim.py +++ b/tests/test_optim.py @@ -1,24 +1,19 @@ import numpy as np import pytest from pytest import approx -from slise.utils import ( - sigmoid, - log_sigmoid, - sparsity, - log_sum_exp, - log_sum_special, -) from slise.optimisation import ( - loss_smooth, - loss_sharp, - loss_numba, - optimise_loss, + check_threading_layer, graduated_optimisation, - regularised_regression, + log_approximation_ratio, + loss_numba, + loss_sharp, + loss_smooth, matching_epsilon, next_beta, - log_approximation_ratio, + optimise_loss, + regularised_regression, ) +from slise.utils import log_sigmoid, log_sum_exp, log_sum_special, sigmoid, sparsity from .utils import * @@ -260,3 +255,6 @@ def test_weights(): atol=1e-4, ) + +def test_check_threading_layer(): + check_threading_layer() diff --git a/tests/test_slise.py b/tests/test_slise.py index 75ef6a7..487bf08 100644 --- a/tests/test_slise.py +++ b/tests/test_slise.py @@ -1,5 +1,6 @@ from warnings import catch_warnings +import numba import numpy as np from pytest import approx from scipy.special import expit as sigmoid @@ -119,9 +120,20 @@ def test_slise_reg(): print("Testing slise regression") X, Y, mod = data_create2(40, 5) w = np.random.uniform(size=40) + 0.5 + threads = numba.get_num_threads() reg1 = regression( - X, Y, epsilon=0.1, lambda1=1e-4, lambda2=1e-4, intercept=True, normalise=True, + X, + Y, + epsilon=0.1, + lambda1=1e-4, + lambda2=1e-4, + intercept=True, + normalise=True, + num_threads=1, ) + assert ( + threads == numba.get_num_threads() + ), "Numba threads not reset correctly after optimisation" reg1.print() Yp = mat_mul_inter(X, reg1.get_params()) Yn = reg1._scale.scale_y(Y) From 59c556f7d4004c2335ca6f7c005ddb99ecfab2b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Bj=C3=B6rklund?= <--get-all> Date: Thu, 24 Mar 2022 21:32:36 +0200 Subject: [PATCH 5/9] test normalised --- tests/test_slise.py | 45 +++++++++++++++++++++++++++++++++------------ tests/utils.py | 7 +++++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/tests/test_slise.py b/tests/test_slise.py index 487bf08..1715976 100644 --- a/tests/test_slise.py +++ b/tests/test_slise.py @@ -120,20 +120,9 @@ def test_slise_reg(): print("Testing slise regression") X, Y, mod = data_create2(40, 5) w = np.random.uniform(size=40) + 0.5 - threads = numba.get_num_threads() reg1 = regression( - X, - Y, - epsilon=0.1, - lambda1=1e-4, - lambda2=1e-4, - intercept=True, - normalise=True, - num_threads=1, + X, Y, epsilon=0.1, lambda1=1e-4, lambda2=1e-4, intercept=True, normalise=True ) - assert ( - threads == numba.get_num_threads() - ), "Numba threads not reset correctly after optimisation" reg1.print() Yp = mat_mul_inter(X, reg1.get_params()) Yn = reg1._scale.scale_y(Y) @@ -228,3 +217,35 @@ def test_slise_exp(): assert reg.score() <= 0, f"Slise loss should usually be <=0 ({reg.score():.2f})" assert Y2[20] == approx(reg.predict(X[20])) assert 1.0 >= reg.subset().mean() > 0.0 + + +def test_normalised(): + np.random.seed(49) + X, Y, mod = data_create2(40, 5) + x = np.random.normal(size=5) + y = np.random.normal() + reg = explain(X, Y, 0.1, x, y, lambda1=1e-4, lambda2=1e-4, normalise=True) + assert reg.coefficients.shape == reg.normalised(True).shape + assert reg.coefficients.shape[0] > reg.normalised(False).shape[0] + assert np.allclose( + reg.coefficients, reg._scale.unscale_model(reg.normalised(False)) + ) + threads = numba.get_num_threads() + reg1 = regression( + X, + Y, + epsilon=0.1, + lambda1=1e-4, + lambda2=1e-4, + intercept=True, + normalise=True, + num_threads=1, + ) + assert ( + threads == numba.get_num_threads() + ), "Numba threads not reset correctly after optimisation" + assert reg.coefficients.shape == reg.normalised(True).shape + assert reg.coefficients.shape[0] > reg.normalised(False).shape[0] + assert np.allclose( + reg.coefficients, reg._scale.unscale_model(reg.normalised(False)) + ) diff --git a/tests/utils.py b/tests/utils.py index c0eb2dc..a8e88b0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,8 +1,9 @@ +from typing import Tuple import numpy as np import numpy.random as npr -def data_create(n: int, d: int, c: int = 2) -> (np.ndarray, np.ndarray): +def data_create(n: int, d: int, c: int = 2) -> Tuple[np.ndarray, np.ndarray]: X = npr.normal(npr.normal(size=d)[np.newaxis,], 1.0, [n, d]) if d > c: X[:, c] = 0 @@ -10,7 +11,9 @@ def data_create(n: int, d: int, c: int = 2) -> (np.ndarray, np.ndarray): return X, Y -def data_create2(n: int, d: int, c: int = 2) -> (np.ndarray, np.ndarray, np.ndarray): +def data_create2( + n: int, d: int, c: int = 2 +) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: X = npr.normal(npr.normal(size=d)[np.newaxis,], 1.0, [n, d]) mod = npr.normal(size=d) if d > c: From 5565eb41368f36d9f93cf032e05143a8575bca48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Bj=C3=B6rklund?= <--get-all> Date: Thu, 24 Mar 2022 21:40:26 +0200 Subject: [PATCH 6/9] version bump --- setup.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ceb63a5..4f4332b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = slise -version = 2.0.0 +version = 2.1.0 author = Anton Björklund author_email = anton.bjorklund@helsinki.fi description = The SLISE algorithm for robust regression and explanations of black box models @@ -35,3 +35,6 @@ install_requires = exclude = examples tests + +[options.extras_require] +tbb = tbb From e5aa0b90f384e86ffc35d026ad1488eaeb9eb37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Bj=C3=B6rklund?= <--get-all> Date: Thu, 24 Mar 2022 21:44:34 +0200 Subject: [PATCH 7/9] add link to warning --- slise/optimisation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slise/optimisation.py b/slise/optimisation.py index 80a612a..3037467 100644 --- a/slise/optimisation.py +++ b/slise/optimisation.py @@ -397,7 +397,7 @@ def check_threading_layer(): loss_residuals(np.ones(1), np.ones(1), 1) if threading_layer() == "workqueue": warn( - 'Using `numba.threading_layer()=="workqueue"` can be devastatingly slow!', + 'Using `numba.threading_layer()=="workqueue"` can be devastatingly slow! See https://numba.pydata.org/numba-doc/latest/user/threading-layer.html for alternatives.', SliseWarning, ) From d984537be8f46900b0f3f3ff1dd27d963aee8e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Bj=C3=B6rklund?= <--get-all> Date: Thu, 24 Mar 2022 21:55:02 +0200 Subject: [PATCH 8/9] replace get_params --- tests/test_slise.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_slise.py b/tests/test_slise.py index 1715976..edb6c4a 100644 --- a/tests/test_slise.py +++ b/tests/test_slise.py @@ -124,7 +124,7 @@ def test_slise_reg(): X, Y, epsilon=0.1, lambda1=1e-4, lambda2=1e-4, intercept=True, normalise=True ) reg1.print() - Yp = mat_mul_inter(X, reg1.get_params()) + Yp = mat_mul_inter(X, reg1.coefficients) Yn = reg1._scale.scale_y(Y) Ynp = mat_mul_inter(reg1._scale.scale_x(X), reg1._alpha) Ypn = reg1._scale.scale_y(Yp) From fdccc21cf354b1072aac79e35de0912b216fc951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Bj=C3=B6rklund?= <--get-all> Date: Thu, 24 Mar 2022 22:16:48 +0200 Subject: [PATCH 9/9] is "on push" needed to trigger "on pull_request"? --- .github/workflows/python-pytest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python-pytest.yml b/.github/workflows/python-pytest.yml index 4f819aa..8945928 100644 --- a/.github/workflows/python-pytest.yml +++ b/.github/workflows/python-pytest.yml @@ -4,6 +4,8 @@ name: Test Python Package on: + push: + branches: [ master ] pull_request: branches: [ master ]