DataCollatorWithPadding that pads attention_mask
Author: gui11aume
Pull request: no

## EDIT: `DataCollatorWithPadding` already has the requested feature

# 🚀 Feature request

<!-- A clear and concise description of the feature proposal.
     Please provide a link to the paper and code in case they exist. -->
A `DataCollatorWithPadding` class that can pad `attention_mask` with any tokenizer class compatible with 🤗Transformers.

## Motivation
It is common to have training examples with both `input_ids` and `attention_mask`. For batches of size larger than 1, you need to pad the examples to form congruent tensors. However, [DataCollatorWithPadding](https://huggingface.co/docs/transformers/main_classes/data_collator#transformers.DataCollatorWithPadding) pads `input_ids` but not `attention_mask` and there is apparently no way to instruct it to do so.

<!-- Please outline the motivation for the proposal. Is your feature request
     related to a problem? e.g., I'm always frustrated when [...]. If this is related
     to another GitHub issue, please link here too. -->

## Your contribution
The method `__call__` from `DataCollatorWithPadding` ([code](https://github.com/huggingface/transformers/blob/db7d6a80e82d66127b2a44b6e3382969fdc8b207/src/transformers/data/data_collator.py#L246)) is a wrapper for `self.tokenizer.pad`. For tokenizers of the class [PreTrainedTokenizerBase](https://huggingface.co/docs/transformers/internal/tokenization_utils#transformers.PreTrainedTokenizerBase), the `pad` method ([code](https://github.com/huggingface/transformers/blob/db7d6a80e82d66127b2a44b6e3382969fdc8b207/src/transformers/tokenization_utils_base.py#L2683)) has an argument `return_attention_mask` that cannot be forwarded from the `__call__` method of DataCollatorWithPadding.

Perhaps an easy way to implement the issue without breaking everything is to add to DataCollatorWithPadding an attribute `pad_attention_mask` (maybe set to `False` for compatibility with the current code) and pass `return_attention_mask=self.pad_attention_mask` in the call to `self.tokenizer.pad`.

The current docstring of the `pad` method from PreTrainedTokenizerBase is quite confusing (see [here](https://github.com/huggingface/transformers/blob/db7d6a80e82d66127b2a44b6e3382969fdc8b207/src/transformers/tokenization_utils_base.py#L2740)). It refers to the `return_outputs` attribute, which is not defined anywhere and is not used anywhere in the code. It is perhaps an obsolete feature and I suppose that the issue arose when it was removed.

I could submit a PR, but I suppose that this is best brought to the attention of @sgugger. If not, sorry for bothering you Sylvain.

<!-- Is there any way that you could help, e.g. by submitting a PR?
     Make sure to read the CONTRIBUTING.MD readme:
     https://github.com/huggingface/transformers/blob/master/CONTRIBUTING.md -->


Comments:
Could you show us the code that doesn't run as you expect? Because `DataCollatorWithPadding` definitely pads the `input_ids`, `attention_mask` and `token_type_ids`.

Thanks for attending this so fast, I really appreciate it. Well, this is one of those embarrassing moments... when I crafted a minimal example to reproduce the issue, it turned out that I could not. And trying the code that did not run yesterday... actually ran fine. Not sure what happened but this was definitely on my side. I'll close the issue with this comment and edit the initial post to clarify that there was in reality no issue.

One thing still, I could not find anything about the `return_outputs` attribute that appears in the documentation of `pad` ([here](https://github.com/huggingface/transformers/blob/db7d6a80e82d66127b2a44b6e3382969fdc8b207/src/transformers/tokenization_utils_base.py#L2740)). Does it refer to a real thing?


I think that attribute existed long ago but I don't find it anywhere indeed. The default is determined by whether `"attention_mask"` is in the tokenizer `model_input_names` attribute.

Do you want to make a PR to fix the doc?

Sure! I'd be happy to make something useful out of this issue. I will submit a PR shortly.

@sgugger, while gathering information to edit the doc in a meaningful way, I came across puzzling features for the  `DataCollatorForTokenClassification`. It's a different issue, but since I am about to update the doc I can maybe take care of this as well.

Below is a minimum example of my typical pipeline. I crafted this example on DNA data in order to have a tiny alphabet.

``` python
# Train the tokenizer (usually in a first file).
import tokenizers

tokenizer = tokenizers.Tokenizer(tokenizers.models.WordPiece(unk_token="[UNK]"))
trainer = tokenizers.trainers.WordPieceTrainer(
    vocab_size=16, special_tokens=["[PAD]", "[CLS]", "[SEP]", "[UNK]", "[MASK]"]
)

tokenizer.post_processor = tokenizers.processors.TemplateProcessing(
    single="[CLS] $A [SEP]",
    pair="[CLS] $A [SEP] $B:1 [SEP]:1",
    special_tokens=[
        ("[CLS]", 1), 
        ("[SEP]", 2), 
    ],  
)

tokenizer.train(files=["./text_for_tokenizer.txt"], trainer=trainer)
tokenizer.save("tokenizer_model.json")

# Train the Transformer (usually in a second file).
import transformers

tokenizer = transformers.PreTrainedTokenizerFast(
      tokenizer_file="tokenizer_model.json",
      bos_token="[CLS]", eos_token="[SEP]", unk_token="[UNK]", sep_token="[SEP]",
      pad_token="[PAD]", cls_token="[CLS]", mask_token="[MASK]")

def tokenize_function(examples):
   return tokenizer(
         [" ".join(x.upper()) for x in examples["seq"]],
         return_token_type_ids=False,
         return_attention_mask=False,
         return_special_tokens_mask=True,
         truncation=True,
         max_length=36
   )   

from datasets import load_dataset
raw_dataset = load_dataset('json', data_files={"train": ["./training_data.json"]})

texts = raw_dataset.map(tokenize_function, batched=True)
texts = texts.remove_columns(["seq"])
#texts.set_format("torch")  # <==== This line breaks everything!!

config = transformers.BertConfig()
model = transformers.BertForTokenClassification(config)

#data_collator = transformers.DataCollatorForTokenClassification(tokenizer)
class dbgCollator(transformers.DataCollatorForTokenClassification):
   def torch_call(self, features):
      output = super().torch_call(features) 
      print(output)
      return  output

data_collator = dbgCollator(tokenizer)

training_args = transformers.TrainingArguments(
    output_dir = ".",
    num_train_epochs = 1,
    per_device_train_batch_size = 2,
)

trainer = transformers.Trainer(
    model = model,
    args = training_args,
    data_collator = data_collator,
    train_dataset = texts["train"],
)

trainer.train()
```
There are two data files. The first is `text_for_the_tokenizer.txt`
```
G A T C
```
The second is `training_data.json`
```
{"seq": "A T A T A T", "labels": [0, 1, 0, 1, 0, 1]}
{"seq": "G C T C T", "labels": [0, 1, 1, 1, 1]}
```

With this you should be able to run the code and observe the following (I have replaced `DataCollatorForTokenClassification` by a debug version that prints its output):
1. After tokenization, special tokens have been added to the input ids.
2. The labels were not shifted, even though `special_tokens_mask` was available to the collator.
3. The script runs fine and the user does not get notified that this happened.

So how is the user supposed to deal with this? Do they have to manually add the label padding value at the time they tokenize? Also, shouldn't there be a warning that labels have been assigned to special tokens?

4. If you uncomment the line `texts.set_format("torch")`, there seems to be no way to make the collator work. It suggests padding and truncating, which makes it really hard to discover that this line causes the error (I discovered it by accident).

Do you have an idea of what the problem is and how to document it properly?

Your `tokenize` function should also align the labels: a word can be split in several tokens and so you have to duplicate the labels for that word, so the `input_ids` and `labels` are the same length. See the section on [token classification](https://huggingface.co/course/chapter7/2?fw=pt) in the course.

The data collator is just there to pad the input IDs and labels once this has been done.

Thanks @sgugger, it's very clear. I wish I had discovered the tutorial earlier, it's very well made.

The docstring of `DataCollatorForTokenClassification` is succinct and matter-of-fact but maybe I can add the info somewhere that the labels and the tokens are assumed to be aligned.

What I find confusing is that the collator seems to assume that labels and tokens are aligned (and does not trigger a warning) even if they have different lengths. If the `input_ids` are longer than the `labels` it works fine, but if the `labels` are longer then it seems to crash. There is probably a common case where the `input_ids` are longer but I cannot figure out why this should makes sense. Would you know why this is the case?

And what about point 4 above? If you want I can look into what causes the crash when the format is set to `"torch"`. I am not sure I will fully get it but at least I can try.

We could issue a warning if we detect the labels and input IDs don't have the same length indeed.

Wow, this is information that I definitely needed :) 
So if you want to pass along other entries from tokenized data, say word-level masks, you can add them to the tokenizer like this: 

`tokenizer.model_input_names.append("prediction_mask")` 

Then the datacollator will take care of padding.