From 8a1411cca04666edbba2434b4d3e27de3af0fd08 Mon Sep 17 00:00:00 2001 From: Furkan-rgb <50831308+Furkan-rgb@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:04:52 +0100 Subject: [PATCH 1/4] Fix shape mismatch error in CatCMA sampler when handling categorical parameters. Problem: When using the CatCMA sampler in Optuna with categorical parameters, a shape mismatch error occurs during the optimization process. This error prevents the sampler from functioning correctly, hindering the hyperparameter optimization workflow. Error Details: ValueError: operands could not be broadcast together with shapes (10,33) (6,11) This error arises from the attempt to perform element-wise operations on arrays with incompatible shapes within the CatCMA optimizer. Specifically, the categorical parameters are encoded as ragged arrays (arrays of varying lengths), leading to broadcasting issues. Root Cause: The categorical parameters are being one-hot encoded with differing lengths based on the number of choices per categorical variable. When these ragged arrays are converted to NumPy arrays with dtype=object, they lose their uniform shape, causing mismatches during optimizer computations. Proposed Solution: To resolve the shape mismatch, ensure that all categorical parameters are encoded into fixed-size, flat arrays. This can be achieved by concatenating all one-hot encoded vectors into a single flat array with a consistent length across all trials. Here's how to implement this: Calculate Total Categories: Determine the total number of categories across all categorical variables to establish a fixed size for the concatenated array. total_categories = sum(len(space.choices) for space in categorical_search_space.values()) Encode Categorical Parameters: Replace the ragged array encoding with a flat array approach, ensuring each c has the same shape. for t in solution_trials[:popsize]: assert t.value is not None, "completed trials must have a value" # Convert numerical parameters x = trans.transform({k: t.params[k] for k in numerical_search_space.keys()}) # Initialize a flat array for all categorical parameters c = np.zeros(total_categories) offset = 0 for k in categorical_search_space.keys(): choices = categorical_search_space[k].choices v = t.params.get(k) if v is not None: index = choices.index(v) c[offset + index] = 1 offset += len(choices) y = t.value if study.direction == StudyDirection.MINIMIZE else -t.value solutions.append(((x, c), y)) Impact of the Change: Consistency: Ensures that all categorical parameter arrays have a consistent shape, eliminating broadcasting issues. Compatibility: Aligns with the CatCMA optimizer's expectations, allowing seamless integration of categorical parameters. Robustness: Prevents runtime errors related to array shape mismatches, enhancing the reliability of the optimization process. --- package/samplers/catcma/catcma.py | 36 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/package/samplers/catcma/catcma.py b/package/samplers/catcma/catcma.py index dd335ef8..d4e7716b 100644 --- a/package/samplers/catcma/catcma.py +++ b/package/samplers/catcma/catcma.py @@ -199,25 +199,31 @@ def sample_relative( solution_trials = self._get_solution_trials(completed_trials, optimizer.generation) if len(solution_trials) >= popsize: - solutions: List[Tuple[np.ndarray, float]] = [] + # Calculate the number of categorical variables and maximum number of choices + num_categorical_vars = len(categorical_search_space) + max_num_choices = max(len(space.choices) for space in categorical_search_space.values()) + + # Prepare solutions list + solutions: List[Tuple[Tuple[np.ndarray, np.ndarray], float]] = [] + for t in solution_trials[:popsize]: assert t.value is not None, "completed trials must have a value" - # Convert Optuna's representation to cmaes.CatCma's internal representation. + + # Convert numerical parameters x = trans.transform({k: t.params[k] for k in numerical_search_space.keys()}) - # Convert categorial values to one-hot vectors. - # Example: - # choices = ['a', 'b', 'c'] - # value = 'b' - # one_hot_vec = [False, True, False] - c = np.asarray( - [ - [c == v for c in categorical_search_space[k].choices] - for k, v in t.params.items() - if k in categorical_search_space.keys() - ] - ) + + # Initialize c with the correct shape + c = np.zeros((num_categorical_vars, max_num_choices)) + + for idx, k in enumerate(categorical_search_space.keys()): + choices = categorical_search_space[k].choices + v = t.params.get(k) + if v is not None: + index = choices.index(v) + c[idx, index] = 1 + y = t.value if study.direction == StudyDirection.MINIMIZE else -t.value - solutions.append(((x, c), y)) # type: ignore + solutions.append(((x, c), y)) optimizer.tell(solutions) From 915bdf431ce7ddd368541405e089d3edc45e87f8 Mon Sep 17 00:00:00 2001 From: Furkan-rgb <50831308+Furkan-rgb@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:26:37 +0100 Subject: [PATCH 2/4] Update code comment for categorical values --- package/samplers/catcma/catcma.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/package/samplers/catcma/catcma.py b/package/samplers/catcma/catcma.py index d4e7716b..89f22199 100644 --- a/package/samplers/catcma/catcma.py +++ b/package/samplers/catcma/catcma.py @@ -208,11 +208,16 @@ def sample_relative( for t in solution_trials[:popsize]: assert t.value is not None, "completed trials must have a value" + # Convert Optuna's representation to cmaes.CatCma's internal representation. # Convert numerical parameters x = trans.transform({k: t.params[k] for k in numerical_search_space.keys()}) - - # Initialize c with the correct shape + + # Convert categorial values to one-hot vectors. + # Example: + # choices = ['a', 'b', 'c'] + # value = 'b' + # one_hot_vec = [False, True, False] c = np.zeros((num_categorical_vars, max_num_choices)) for idx, k in enumerate(categorical_search_space.keys()): @@ -223,7 +228,7 @@ def sample_relative( c[idx, index] = 1 y = t.value if study.direction == StudyDirection.MINIMIZE else -t.value - solutions.append(((x, c), y)) + solutions.append(((x, c), y)) # type: ignore optimizer.tell(solutions) From edc30706d0c4ae9a0dc4b9f1df57560ec84fd1fb Mon Sep 17 00:00:00 2001 From: Furkan-rgb <50831308+Furkan-rgb@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:28:11 +0100 Subject: [PATCH 3/4] Small formatting update --- package/samplers/catcma/catcma.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/samplers/catcma/catcma.py b/package/samplers/catcma/catcma.py index 89f22199..0df7c3da 100644 --- a/package/samplers/catcma/catcma.py +++ b/package/samplers/catcma/catcma.py @@ -228,7 +228,7 @@ def sample_relative( c[idx, index] = 1 y = t.value if study.direction == StudyDirection.MINIMIZE else -t.value - solutions.append(((x, c), y)) # type: ignore + solutions.append(((x, c), y)) # type: ignore optimizer.tell(solutions) From 14f6d6aae8ab6da5c95331a2446ee841aeb00dca Mon Sep 17 00:00:00 2001 From: Furkan-rgb <50831308+Furkan-rgb@users.noreply.github.com> Date: Tue, 5 Nov 2024 08:10:12 +0000 Subject: [PATCH 4/4] Linting --- package/samplers/catcma/catcma.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package/samplers/catcma/catcma.py b/package/samplers/catcma/catcma.py index 0df7c3da..371ee9ea 100644 --- a/package/samplers/catcma/catcma.py +++ b/package/samplers/catcma/catcma.py @@ -201,7 +201,9 @@ def sample_relative( if len(solution_trials) >= popsize: # Calculate the number of categorical variables and maximum number of choices num_categorical_vars = len(categorical_search_space) - max_num_choices = max(len(space.choices) for space in categorical_search_space.values()) + max_num_choices = max( + len(space.choices) for space in categorical_search_space.values() + ) # Prepare solutions list solutions: List[Tuple[Tuple[np.ndarray, np.ndarray], float]] = [] @@ -212,7 +214,7 @@ def sample_relative( # Convert numerical parameters x = trans.transform({k: t.params[k] for k in numerical_search_space.keys()}) - + # Convert categorial values to one-hot vectors. # Example: # choices = ['a', 'b', 'c']