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

Categorical search spaces #863

Merged
merged 5 commits into from
Aug 8, 2024
Merged

Conversation

uri-granta
Copy link
Collaborator

@uri-granta uri-granta commented Jul 31, 2024

Related issue(s)/PRs:

Summary

Add support for Categorical search spaces.

Modelling support will come in a future PR (see #862).

Fully backwards compatible: yes

PR checklist

  • The quality checks are all passing
  • The bug case / new feature is covered by tests
  • Any new features are well-documented (in docstrings or notebooks)

Copy link
Collaborator

@khurram-ghani khurram-ghani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments on the main changes. I haven't looked at the unit testing yet.

trieste/space.py Outdated Show resolved Hide resolved
trieste/space.py Show resolved Hide resolved
trieste/space.py Outdated Show resolved Hide resolved
Comment on lines +644 to +646
return tf.stack(
[tf.gather(tf.constant(self._tags[i]), row[i]) for i in range(len(row))]
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can probably use tf.gather_nd instead of tf.gather and a for loop. Not sure, but you might be able to also get rid of tf.map_fn.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that's possible, as self._tags is a non-rectangular Sequence[Sequence[str], not a tensor.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay 👍 .

There might be a way (see example) to use ragged tensors with batch_dims arg to tf.gather, but that might not be more efficient than a for loop. Happy for you to ignore this :-)

trieste/space.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@hstojic hstojic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done a quick pass, looks ok to me, no major comments
I'll let @khurram-ghani approve it


def __repr__(self) -> str:
""""""
return f"DiscreteSearchSpace({self._points!r})"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.__class__.__name__ instead of a string?

@uri-granta uri-granta mentioned this pull request Aug 7, 2024
3 tasks
Copy link
Collaborator

@khurram-ghani khurram-ghani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

However, my personal preference is to not put the one_hot_encoder in the categorical and product search space classes. This could be a separate class that takes a search space as an __init__ argument.

I realise that these are just default implementations and users can always write separate implementations. As we know, there would be cases where we want to encode in different ways, especially for the product space (perhaps the categorical-only case is fine). I just think it is better to have the implied flexibility and not burn a default implementation in the spaces. I guess it just depends on our opinion on what is the most likely use case.

Comment on lines +635 to +637
encoded = tf.concat(
[encoder(column) for encoder, column in zip(encoders, columns)], axis=1
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given x is now a float type, do we need to do some checking/casting similar to to_tags? I'm not sure if tf.keras.layers.CategoryEncoding allows non-integer inputs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only the underling self.points in GeneralDiscreteSearchSpace that are now floats. The one hot encoding uses self.tags directly so isn't affected. (And this is all tested.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The x passed in by the user are the underlying indices, which can be float. We then call the encoder (after flattening) with these. From what I can see, the testing uses integer indices only, but I could have easily missed that. But in any case, it doesn't matter too much as I just tried out tf.keras.layers.CategoryEncoding with float types and it silently converts them into integers (in a weird way, see below). But perhaps we want to be explicit with our checking/assertions and not rely on tensorflow.

> e = tf.keras.layers.CategoryEncoding(4, output_mode="one_hot")

> e(tf.constant([3.0, 2.999999], dtype=tf.float32))
<tf.Tensor: shape=(2, 4), dtype=float32, numpy=
array([[0., 0., 0., 1.],
       [0., 0., 1., 0.]], dtype=float32)>

> e(tf.constant([3.0, 2.9999999], dtype=tf.float32))
<tf.Tensor: shape=(2, 4), dtype=float32, numpy=
array([[0., 0., 0., 1.],
       [0., 0., 0., 1.]], dtype=float32)>

@uri-granta uri-granta merged commit 43e6f01 into develop Aug 8, 2024
12 checks passed
@uri-granta uri-granta deleted the uri/categorical_search_spaces branch August 8, 2024 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants