Multi label class encoding
State: open | Author: jmif (NONE)
Labels: name: enhancement

### Feature request

I have a multi label dataset and I'd like to be able to class encode the column and store the mapping directly in the features just as I can with a single label column.  `class_encode_column` currently does not support multi labels.

Here's an example of what I'd like to encode:

```
data = {
    'text': ['one', 'two', 'three', 'four'],
    'labels': [['a', 'b'], ['b'], ['b', 'c'], ['a', 'd']]
}

dataset = Dataset.from_dict(data)
dataset = dataset.class_encode_column('labels')
```

I did some digging into the code base to evaluate the feasibility of this (note I'm very new to this code base) and from what I noticed the `ClassLabel` feature is still stored as an underlying raw data type of int so I thought a `MultiLabel` feature could similarly be stored as a Sequence of ints, thus not requiring significant serialization / conversion work to / from arrow.

I did a POC of this [here](https://github.com/huggingface/datasets/commit/15443098e9ce053943172f7ec6fce3769d7dff6e) and included a simple test case (please excuse all the commented out tests, going for speed of POC here and didn't want to fight IDE to debug a single test).   In the test I just assert that `num_classes` is the same to show that things are properly serializing, but if you break after loading from disk you'll see the dataset correct and the dataset feature is as expected.

After digging more I did notice a few issues
- After loading from disk I noticed type of the `labels` class is `Sequence` not `MultiLabel` (though the added `feature` attribute came through).  This doesn't happen for `ClassLabel` but I couldn't find the encode / decode code paths that handle this.
- I subclass `Sequence` in `MultiLabel` to leverage existing serialization, but this does miss the custom encode logic that `ClassLabel` has.  I'm not sure of the best way to approach this as I haven't fully understood the encode / decode flow for datasets.  I suspect my simple implementation will need some improvement as it'll require a significant amount of repeated logic to mimic `ClassLabel` behavior.



### Motivation

See above - would like to support multi label class encodings.

### Your contribution

This would be a big help for us and we're open to contributing but I'll likely need some guidance on how to implement to fit the encode / decode flow.  Some suggestions on tests / would be great too, I'm guessing in addition to the class encode tests (that I'll need to expand) we'll need encode / decode tests.

Comments: You can use a `Sequence(ClassLabel(...))` feature type to represent a list of labels, and `cast_column`/`cast` to perform the "string to label" conversion  (`class_encode_column` does support nested fields), e.g., in your case:
```python
from datasets import Dataset, Sequence, ClassLabel
data = {
    'text': ['one', 'two', 'three', 'four'],
    'labels': [['a', 'b'], ['b'], ['b', 'c'], ['a', 'd']]
}

dataset = Dataset.from_dict(data)
dataset = dataset.cast_column('labels', Sequence(ClassLabel(names=["a", "b", "c", "d"])))
```

Great!  Can you elaborate on "class_encode_column does support nested fields"?  Do you mean that there is a way to `class_encode_column` on a Sequence?

Yes, exactly! This would be a nice contribution, though.

Sorry, I'm still not following.   Are you saying that there currently exists a way to call `class_encode_column` on a `Sequence(ClassLabel)` type?  Or that the underlying data structures support it and a contribution of a method to do that would be welcome?

`class_encode_column ` currently does not support `Sequence(ClassLabel)`. Implementing support for this would be a nice contribution.

In the meantime, this limitation can be circumvented by fetching (unique) labels and calling `.cast_column(col, Sequence(ClassLabel(names=labels)))`.

Ok makes sense, can you take a look at the POC implementation I did [here](https://github.com/huggingface/datasets/commit/15443098e9ce053943172f7ec6fce3769d7dff6e)?  Happy to take another pass / submit as a PR but would be helpful if I got a thumbs up that this was directionally correct with respect to implementation / architecture. 

There is no need to introduce a new type (`MultiLabel`) for this feature. Also, I think we can keep the logic inside a single method instead of separating the two cases.

Maybe https://github.com/huggingface/datasets/pull/4277 can help with the implementation. We extended `align_labels_with_mapping` to support `Sequence(ClassLabel(...))` in that PR (initially, it only worked with `ClassLabel(...)`)