diff --git a/python/cuml/cuml/cluster/dbscan.pyx b/python/cuml/cuml/cluster/dbscan.pyx index 73de161853..0b042a98f8 100644 --- a/python/cuml/cuml/cluster/dbscan.pyx +++ b/python/cuml/cuml/cluster/dbscan.pyx @@ -386,7 +386,7 @@ class DBSCAN(Base, cdef float eps = self.eps cdef int min_samples = self.min_samples - cdef level_enum verbose = self.verbose + cdef level_enum verbose = self._verbose_level cdef bool multi_gpu = self._multi_gpu cdef size_t max_mbytes_per_batch = self.max_mbytes_per_batch or 0 diff --git a/python/cuml/cuml/cluster/kmeans.pyx b/python/cuml/cuml/cluster/kmeans.pyx index e90b35324f..7c4e3af371 100644 --- a/python/cuml/cuml/cluster/kmeans.pyx +++ b/python/cuml/cuml/cluster/kmeans.pyx @@ -25,7 +25,6 @@ from libcpp cimport bool from pylibraft.common.handle cimport handle_t cimport cuml.cluster.cpp.kmeans as lib -from cuml.internals.logger cimport level_enum from cuml.metrics.distance_type cimport DistanceType @@ -36,7 +35,7 @@ cdef _kmeans_init_params(kmeans, lib.KMeansParams& params): params.n_clusters = kmeans.n_clusters params.max_iter = kmeans.max_iter params.tol = kmeans.tol - params.verbosity = (kmeans.verbose) + params.verbosity = kmeans._verbose_level params.metric = DistanceType.L2Expanded params.batch_samples = int(kmeans.max_samples_per_batch) params.oversampling_factor = kmeans.oversampling_factor diff --git a/python/cuml/cuml/ensemble/randomforest_common.pyx b/python/cuml/cuml/ensemble/randomforest_common.pyx index 5b5d4a8578..7535c89d17 100644 --- a/python/cuml/cuml/ensemble/randomforest_common.pyx +++ b/python/cuml/cuml/ensemble/randomforest_common.pyx @@ -187,7 +187,6 @@ class BaseRandomForestModel(Base, InteropMixin): "min_impurity_decrease", "max_batch_size", "random_state", - "criterion", "n_streams", "oob_score", "handle", @@ -421,7 +420,7 @@ class BaseRandomForestModel(Base, InteropMixin): cdef uintptr_t y_ptr = y.ptr cdef int n_rows = X.shape[0] cdef int n_cols = X.shape[1] - cdef level_enum verbose = self.verbose + cdef level_enum verbose = self._verbose_level cdef int n_classes = self.n_classes_ if is_classifier else 0 if self.max_depth <= 0: diff --git a/python/cuml/cuml/experimental/linear_model/lars.pyx b/python/cuml/cuml/experimental/linear_model/lars.pyx index 12b1b67a30..6455fe17ee 100644 --- a/python/cuml/cuml/experimental/linear_model/lars.pyx +++ b/python/cuml/cuml/experimental/linear_model/lars.pyx @@ -262,13 +262,13 @@ class Lars(Base, RegressorMixin): larsFit(handle_[0], X_ptr, n_rows, self.n_cols, y_ptr, beta_ptr, active_idx_ptr, alphas_ptr, &n_active, Gram_ptr, - max_iter, coef_path_ptr, self.verbose, ld_X, + max_iter, coef_path_ptr, self._verbose_level, ld_X, ld_G, self.eps) else: larsFit(handle_[0], X_ptr, n_rows, self.n_cols, y_ptr, beta_ptr, active_idx_ptr, alphas_ptr, &n_active, Gram_ptr, - max_iter, coef_path_ptr, self.verbose, + max_iter, coef_path_ptr, self._verbose_level, ld_X, ld_G, self.eps) self.n_active = n_active self.n_iter_ = n_active diff --git a/python/cuml/cuml/internals/base.py b/python/cuml/cuml/internals/base.py index 1ef59fa82a..959e4d1b5a 100644 --- a/python/cuml/cuml/internals/base.py +++ b/python/cuml/cuml/internals/base.py @@ -14,8 +14,6 @@ import cuml.internals.input_utils import cuml.internals.logger as logger import cuml.internals.nvtx as nvtx -from cuml.internals import api_context_managers -from cuml.internals.global_settings import GlobalSettings from cuml.internals.input_utils import determine_array_type from cuml.internals.mixins import TagsMixin from cuml.internals.output_type import ( @@ -24,40 +22,6 @@ ) -class VerbosityDescriptor: - """Descriptor for ensuring correct type is used for verbosity - - This descriptor ensures that when the 'verbose' attribute of a cuML - estimator is accessed external to the cuML API, an integer is returned - (consistent with Scikit-Learn's API for verbosity). Internal to the API, an - enum is used. Scikit-Learn's numerical values for verbosity are the inverse - of those used by spdlog, so the numerical value is also inverted internal - to the cuML API. This ensures that cuML code treats verbosity values as - expected for an spdlog-based codebase. - """ - - def __get__(self, obj, cls=None): - if api_context_managers.in_internal_api(): - return logger._verbose_to_level(obj._verbose) - else: - return obj._verbose - - def __set__(self, obj, value): - if api_context_managers.in_internal_api(): - assert isinstance(value, logger.level_enum), ( - "The log level should always be provided as a level_enum, " - "not an integer" - ) - obj._verbose = logger._verbose_from_level(value) - else: - if isinstance(value, logger.level_enum): - raise ValueError( - "The log level should always be provided as an integer, " - "not using the enum" - ) - obj._verbose = value - - class Base(TagsMixin, metaclass=cuml.internals.BaseMetaClass): """ Base class for all the ML algos. It handles some of the common operations @@ -191,28 +155,10 @@ def __init__( verbose=False, output_type=None, ): - """ - Constructor. All children must call init method of this base class. - - """ self.handle = ( pylibraft.common.handle.Handle() if handle is None else handle ) - - # The following manipulation of the root_cm ensures that the verbose - # descriptor sees any set or get of the verbose attribute as happening - # internal to the cuML API. Currently, __init__ calls do not take place - # within an api context manager, so setting "verbose" here would - # otherwise appear to be external to the cuML API. This behavior will - # be corrected with the update of cuML's API context manager - # infrastructure in https://github.com/rapidsai/cuml/pull/6189. - GlobalSettings().prev_root_cm = GlobalSettings().root_cm - GlobalSettings().root_cm = True - self.verbose = logger._verbose_to_level(verbose) - # Please see above note on manipulation of the root_cm. This should be - # rendered unnecessary with https://github.com/rapidsai/cuml/pull/6189. - GlobalSettings().root_cm = GlobalSettings().prev_root_cm - + self.verbose = verbose self.output_type = _check_output_type_str( cuml.global_settings.output_type if output_type is None @@ -224,8 +170,6 @@ def __init__( if nvtx_benchmark and nvtx_benchmark.lower() == "true": self.set_nvtx_annotations() - verbose = VerbosityDescriptor() - def __repr__(self): """ Pretty prints the arguments of a class using Scikit-learn standard :) @@ -250,6 +194,11 @@ def __repr__(self): output += " " return output + @property + def _verbose_level(self): + """The current `verbose` setting as a `logger.level_enum`""" + return logger._verbose_to_level(self.verbose) + @classmethod def _get_param_names(cls): """ @@ -267,20 +216,7 @@ def get_params(self, deep=True): need anything other than what is there in this method, then it doesn't have to override this method """ - params = dict() - variables = self._get_param_names() - for key in variables: - var_value = getattr(self, key, None) - # We are currently internal to the cuML API, but the value we - # return will immediately be returned external to the API, so we - # must perform the translation from enum to integer before - # returning the value. Ordinarily, this is handled by - # VerbosityDescriptor for direct access to the verbose - # attribute. - if key == "verbose": - var_value = logger._verbose_from_level(var_value) - params[key] = var_value - return params + return {name: getattr(self, name) for name in self._get_param_names()} def set_params(self, **params): """ @@ -291,15 +227,13 @@ def set_params(self, **params): """ if not params: return self - variables = self._get_param_names() + valid_params = self._get_param_names() for key, value in params.items(): - if key not in variables: - raise ValueError("Bad param '%s' passed to set_params" % key) - else: - # Switch verbose to enum since we are now internal to cuML API - if key == "verbose": - value = logger._verbose_to_level(value) - setattr(self, key, value) + if key not in valid_params: + raise ValueError( + f"Invalid parameter {key!r} for `{type(self).__name__}`" + ) + setattr(self, key, value) return self def _set_output_type(self, inp): diff --git a/python/cuml/cuml/internals/logger.pyx b/python/cuml/cuml/internals/logger.pyx index 96159aa26c..e0f9fdb83d 100644 --- a/python/cuml/cuml/internals/logger.pyx +++ b/python/cuml/cuml/internals/logger.pyx @@ -2,11 +2,6 @@ # SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION. # SPDX-License-Identifier: Apache-2.0 # - -# distutils: language = c++ - - -import logging import sys @@ -23,6 +18,7 @@ cdef void _log_callback(int lvl, const char * msg) with gil: """ print(msg.decode('utf-8'), end='') + cdef void _log_flush() with gil: """ Default spdlogs callback function to flush logs @@ -38,12 +34,7 @@ def _verbose_to_level(verbose: bool | int) -> level_enum: elif verbose is False: return level_enum.info else: - return level_enum(6 - verbose) - - -def _verbose_from_level(level: level_enum) -> int: - """Convert a `level_enum` back into an equivalent `verbose` parameter value.""" - return 6 - int(level) + return level_enum(min(max(6 - verbose, 0), 6)) cdef class LogLevelSetter: @@ -179,7 +170,7 @@ def should_log_for(level): return default_logger().should_log(level) -def _log(level_enum lvl, msg, default_func): +def _log(level_enum lvl, msg): """ Internal function to log a message at a given level. @@ -189,8 +180,6 @@ def _log(level_enum lvl, msg, default_func): Logging level to be set. msg : str Message to be logged. - default_func : function - Default logging function to be used if GPU build is disabled. """ cdef string s = msg.encode("UTF-8") default_logger().log(lvl, s) @@ -200,115 +189,73 @@ def trace(msg): """ Logs a trace message, if it is enabled. - Examples - -------- - - .. code-block:: python - - logger.trace("Hello world! This is a trace message") - Parameters ---------- msg : str Message to be logged. """ # No trace level in Python so we use the closest thing, debug. - _log(level_enum.trace, msg, logging.debug) + _log(level_enum.trace, msg) def debug(msg): """ Logs a debug message, if it is enabled. - Examples - -------- - - .. code-block:: python - - logger.debug("Hello world! This is a debug message") - Parameters ---------- msg : str Message to be logged. """ - _log(level_enum.debug, msg, logging.debug) + _log(level_enum.debug, msg) def info(msg): """ Logs an info message, if it is enabled. - Examples - -------- - - .. code-block:: python - - logger.info("Hello world! This is a info message") - Parameters ---------- msg : str Message to be logged. """ - _log(level_enum.info, msg, logging.info) + _log(level_enum.info, msg) def warn(msg): """ Logs a warning message, if it is enabled. - Examples - -------- - - .. code-block:: python - - logger.warn("Hello world! This is a warning message") - Parameters ---------- msg : str Message to be logged. """ - _log(level_enum.warn, msg, logging.warn) + _log(level_enum.warn, msg) def error(msg): """ Logs an error message, if it is enabled. - Examples - -------- - - .. code-block:: python - - logger.error("Hello world! This is a error message") - Parameters ---------- msg : str Message to be logged. """ - _log(level_enum.error, msg, logging.error) + _log(level_enum.error, msg) def critical(msg): """ Logs a critical message, if it is enabled. - Examples - -------- - - .. code-block:: python - - logger.critical("Hello world! This is a critical message") - Parameters ---------- msg : str Message to be logged. """ - _log(level_enum.critical, msg, logging.critical) + _log(level_enum.critical, msg) def flush(): diff --git a/python/cuml/cuml/linear_model/elastic_net.py b/python/cuml/cuml/linear_model/elastic_net.py index 26ae869616..b6dfe2c40e 100644 --- a/python/cuml/cuml/linear_model/elastic_net.py +++ b/python/cuml/cuml/linear_model/elastic_net.py @@ -265,7 +265,7 @@ def fit( max_iter=self.max_iter, tol=self.tol, penalty_normalized=False, - verbose=self.verbose, + verbose=self._verbose_level, handle=self.handle, ) coef = CumlArray(data=coef.to_output("cupy").flatten()) diff --git a/python/cuml/cuml/linear_model/logistic_regression.py b/python/cuml/cuml/linear_model/logistic_regression.py index 86c6cabf2d..8dd1921d6f 100644 --- a/python/cuml/cuml/linear_model/logistic_regression.py +++ b/python/cuml/cuml/linear_model/logistic_regression.py @@ -312,7 +312,7 @@ def fit( max_iter=self.max_iter, tol=self.tol, linesearch_max_iter=self.linesearch_max_iter, - verbose=self.verbose, + verbose=self._verbose_level, handle=self.handle, lbfgs_memory=self.lbfgs_memory, penalty_normalized=self.penalty_normalized, diff --git a/python/cuml/cuml/linear_model/logistic_regression_mg.pyx b/python/cuml/cuml/linear_model/logistic_regression_mg.pyx index 5d57a1f235..890e274669 100644 --- a/python/cuml/cuml/linear_model/logistic_regression_mg.pyx +++ b/python/cuml/cuml/linear_model/logistic_regression_mg.pyx @@ -186,7 +186,7 @@ class LogisticRegressionMG(MGFitMixin, LogisticRegression): linesearch_max_iter=self.linesearch_max_iter, lbfgs_memory=self.lbfgs_memory, penalty_normalized=self.penalty_normalized, - verbose=self.verbose, + verbose=self._verbose_level, ) # Allocate outputs diff --git a/python/cuml/cuml/manifold/t_sne.pyx b/python/cuml/cuml/manifold/t_sne.pyx index be534adbc7..ee1a9029e2 100644 --- a/python/cuml/cuml/manifold/t_sne.pyx +++ b/python/cuml/cuml/manifold/t_sne.pyx @@ -253,7 +253,7 @@ cdef _init_params(self, int n_samples, TSNEParams ¶ms): params.pre_momentum = pre_momentum params.post_momentum = post_momentum params.random_state = seed - params.verbosity = self.verbose + params.verbosity = self._verbose_level params.square_distances = self.square_distances params.algorithm = algo params.init = init diff --git a/python/cuml/cuml/manifold/umap/umap.pyx b/python/cuml/cuml/manifold/umap/umap.pyx index a0a28c16a2..acac8abda2 100644 --- a/python/cuml/cuml/manifold/umap/umap.pyx +++ b/python/cuml/cuml/manifold/umap/umap.pyx @@ -337,7 +337,7 @@ cdef init_params(self, lib.UMAPParams ¶ms, n_rows, is_sparse=False, is_fit=T params.repulsion_strength = self.repulsion_strength params.negative_sample_rate = self.negative_sample_rate params.transform_queue_size = self.transform_queue_size - params.verbosity = self.verbose + params.verbosity = self._verbose_level params.a = self._a params.b = self._b params.target_n_neighbors = self.target_n_neighbors diff --git a/python/cuml/cuml/solvers/qn.pyx b/python/cuml/cuml/solvers/qn.pyx index 6b0c4cab0c..ccc548ef4f 100644 --- a/python/cuml/cuml/solvers/qn.pyx +++ b/python/cuml/cuml/solvers/qn.pyx @@ -561,7 +561,7 @@ class QN(Base): lbfgs_memory=self.lbfgs_memory, penalty_normalized=self.penalty_normalized, init_coef=init_coef, - verbose=self.verbose, + verbose=self._verbose_level, handle=self.handle, ) self.coef_ = coef diff --git a/python/cuml/cuml/svm/linear.pyx b/python/cuml/cuml/svm/linear.pyx index 552cdf0cae..c9ab8f6815 100644 --- a/python/cuml/cuml/svm/linear.pyx +++ b/python/cuml/cuml/svm/linear.pyx @@ -99,7 +99,7 @@ def fit( C, tol, epsilon, - verbose, + level_enum verbose, ): """Perform a Linear SVR or SVC fit. @@ -173,7 +173,7 @@ def fit( params.epsilon = epsilon params.grad_tol = tol params.change_tol = 0.1 * tol - params.verbose = verbose + params.verbose = verbose # Extract dimensions cdef size_t n_rows = X.shape[0] diff --git a/python/cuml/cuml/svm/linear_svc.py b/python/cuml/cuml/svm/linear_svc.py index ab699af0b4..45f2b48de6 100644 --- a/python/cuml/cuml/svm/linear_svc.py +++ b/python/cuml/cuml/svm/linear_svc.py @@ -281,7 +281,7 @@ def fit( C=self.C, tol=self.tol, epsilon=0.0, - verbose=self.verbose, + verbose=self._verbose_level, ) self.coef_ = coef self.intercept_ = intercept diff --git a/python/cuml/cuml/svm/linear_svr.py b/python/cuml/cuml/svm/linear_svr.py index 52df6537c6..62798eef57 100644 --- a/python/cuml/cuml/svm/linear_svr.py +++ b/python/cuml/cuml/svm/linear_svr.py @@ -252,7 +252,7 @@ def fit( C=self.C, tol=self.tol, epsilon=self.epsilon, - verbose=self.verbose, + verbose=self._verbose_level, ) self.coef_ = coef self.intercept_ = intercept diff --git a/python/cuml/cuml/svm/svm_base.pyx b/python/cuml/cuml/svm/svm_base.pyx index 99815bca67..33fc453f31 100644 --- a/python/cuml/cuml/svm/svm_base.pyx +++ b/python/cuml/cuml/svm/svm_base.pyx @@ -397,7 +397,7 @@ class SVMBase(Base, param.cache_size = self.cache_size param.nochange_steps = self.nochange_steps param.tol = self.tol - param.verbosity = self.verbose + param.verbosity = self._verbose_level param.epsilon = self.epsilon param.svmType = lib.SvmType.C_SVC if is_classifier else lib.SvmType.EPSILON_SVR diff --git a/python/cuml/tests/test_hdbscan.py b/python/cuml/tests/test_hdbscan.py index f86e4e9785..c6d876c8ac 100644 --- a/python/cuml/tests/test_hdbscan.py +++ b/python/cuml/tests/test_hdbscan.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: Copyright (c) 2021-2025, NVIDIA CORPORATION. # SPDX-License-Identifier: Apache-2.0 # - import cupy as cp import hdbscan import numpy as np @@ -20,7 +19,6 @@ membership_vector, ) from cuml.cluster.hdbscan.hdbscan import _condense_hierarchy, _extract_clusters -from cuml.internals import logger from cuml.metrics import adjusted_rand_score from cuml.testing.datasets import make_pattern from cuml.testing.utils import array_equal @@ -166,7 +164,6 @@ def test_hdbscan_blobs( ) cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, allow_single_cluster=allow_single_cluster, min_samples=min_samples, max_cluster_size=max_cluster_size, @@ -225,7 +222,6 @@ def test_hdbscan_sklearn_datasets( X = supervised_learning_dataset cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, allow_single_cluster=allow_single_cluster, gen_min_span_tree=True, min_samples=min_samples, @@ -326,7 +322,6 @@ def test_hdbscan_cluster_patterns( X, y = make_pattern(dataset, nrows)[0] cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, allow_single_cluster=allow_single_cluster, min_samples=min_samples, max_cluster_size=max_cluster_size, @@ -526,7 +521,6 @@ def test_all_points_membership_vectors_blobs( ) cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, allow_single_cluster=allow_single_cluster, max_cluster_size=max_cluster_size, min_cluster_size=min_cluster_size, @@ -576,7 +570,6 @@ def test_all_points_membership_vectors_moons( X, y = datasets.make_moons(n_samples=nrows, noise=0.05, random_state=42) cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, min_samples=min_samples, allow_single_cluster=allow_single_cluster, max_cluster_size=max_cluster_size, @@ -632,7 +625,6 @@ def test_all_points_membership_vectors_circles( ) cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, min_samples=min_samples, allow_single_cluster=allow_single_cluster, max_cluster_size=max_cluster_size, @@ -704,7 +696,6 @@ def test_approximate_predict_blobs( ) cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, allow_single_cluster=allow_single_cluster, max_cluster_size=max_cluster_size, min_cluster_size=min_cluster_size, @@ -762,7 +753,6 @@ def test_approximate_predict_moons( X_test = X[nrows:] cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, allow_single_cluster=allow_single_cluster, min_samples=min_samples, max_cluster_size=max_cluster_size, @@ -827,7 +817,6 @@ def test_approximate_predict_circles( X_test = X[nrows:] cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, allow_single_cluster=allow_single_cluster, min_samples=min_samples, max_cluster_size=max_cluster_size, @@ -893,7 +882,6 @@ def test_approximate_predict_digits( ) cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, allow_single_cluster=allow_single_cluster, min_samples=min_samples, max_cluster_size=max_cluster_size, @@ -967,7 +955,6 @@ def test_membership_vector_blobs( ) cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, allow_single_cluster=allow_single_cluster, max_cluster_size=max_cluster_size, min_cluster_size=min_cluster_size, @@ -1030,7 +1017,6 @@ def test_membership_vector_moons( X_test = X[nrows:] cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, min_samples=min_samples, allow_single_cluster=allow_single_cluster, max_cluster_size=max_cluster_size, @@ -1094,7 +1080,6 @@ def test_membership_vector_circles( X_test = X[nrows:] cuml_agg = HDBSCAN( - verbose=logger.level_enum.info, min_samples=min_samples, allow_single_cluster=allow_single_cluster, max_cluster_size=max_cluster_size, diff --git a/python/cuml/tests/test_logger.py b/python/cuml/tests/test_logger.py index 8827da59da..1c31427d1d 100644 --- a/python/cuml/tests/test_logger.py +++ b/python/cuml/tests/test_logger.py @@ -11,22 +11,23 @@ @pytest.mark.parametrize( - "verbose, level, verbose_numeric", + "verbose, level", [ - (False, logger.level_enum.info, 4), - (True, logger.level_enum.debug, 5), - (0, logger.level_enum.off, 0), - (1, logger.level_enum.critical, 1), - (2, logger.level_enum.error, 2), - (3, logger.level_enum.warn, 3), - (4, logger.level_enum.info, 4), - (5, logger.level_enum.debug, 5), - (6, logger.level_enum.trace, 6), + (False, logger.level_enum.info), + (True, logger.level_enum.debug), + (-1, logger.level_enum.off), + (0, logger.level_enum.off), + (1, logger.level_enum.critical), + (2, logger.level_enum.error), + (3, logger.level_enum.warn), + (4, logger.level_enum.info), + (5, logger.level_enum.debug), + (6, logger.level_enum.trace), + (10, logger.level_enum.trace), ], ) -def test_verbose_to_from_level(verbose, level, verbose_numeric): +def test_verbose_to_level(verbose, level): assert logger._verbose_to_level(verbose) == level - assert logger._verbose_from_level(level) == verbose_numeric def test_logger(): diff --git a/python/cuml/tests/test_umap.py b/python/cuml/tests/test_umap.py index 053d8fe38f..a66c48dc34 100644 --- a/python/cuml/tests/test_umap.py +++ b/python/cuml/tests/test_umap.py @@ -19,7 +19,7 @@ from sklearn.neighbors import NearestNeighbors import cuml -from cuml.internals import GraphBasedDimRedCallback, logger +from cuml.internals import GraphBasedDimRedCallback from cuml.manifold.umap import UMAP as cuUMAP from cuml.metrics import pairwise_distances from cuml.testing.utils import ( @@ -181,7 +181,6 @@ def test_umap_transform_on_digits_sparse( fitter = cuUMAP( n_neighbors=15, - verbose=logger.level_enum.info, init="random", n_epochs=0, min_dist=0.01, @@ -219,7 +218,6 @@ def test_umap_transform_on_digits(target_metric): fitter = cuUMAP( n_neighbors=15, - verbose=logger.level_enum.debug, init="random", n_epochs=0, min_dist=0.01,