Fix fast tokenization problems
Author: qqaatw
Pull request: yes

# What does this PR do?

This PR addresses two problems of fast tokenization:

1. The special tokens specified directly from kwargs were not sanitized. (not added through `tokenizer._add_tokens` function.)
**Edited: We finally decided to not include this change in this PR.**
```python
from transformers import AlbertTokenizerFast

tokenizer = AlbertTokenizerFast("tests/fixtures/spiece.model", mask_token="[OAO]")
print(tokenizer._mask_token)
print(tokenizer.tokenize('[OAO]'))

# Outputs:
# [OAO]
# ['▁[', 'o', 'ao', ']']

# Fixed outputs:
# [OAO]
# ['[OAO]']
```
2.  The special handling of `Albert`'s `[MASK]` token incorrectly normalizes texts to be matched. (Special tokens should exactly match original text not the normalized one.)

```python
from transformers import AlbertTokenizerFast

tokenizer = AlbertTokenizerFast("tests/fixtures/spiece.model")

print(tokenizer._mask_token)
print(tokenizer.tokenize('[MASK]'))

# Outputs:
# [MASK]
# ['▁[', 'mask', ']']

# Fixed outputs:
# [MASK]
# ['[MASK]']
```



## Who can review?

@LysandreJik @sgugger @SaulLu 


Comments:
> I'll let @SaulLu decide on tis since she is the one who suggested the changes :-) (FYI she's on vacation until the end of next week.)

Thanks, no problem.

Hi @SaulLu,

Thank you very much for your detailed reply! I'm sorry if I didn't explain the purpose of these changes clearly.

For the first change, please temporarily ignore the inconsistency between the slow and fast ones as I just used the fast one as an example.

You can see in the below snippet, the problem is that currently if we specify a new cls token, it will not be treated as a token as it does not exist in the vocabulary. However, after saving this tokenizer and loading it again through `from_pretrained` method, it will surprisingly be treated as a token because the `from_pretrained` method calls `tokenizer.sanitize_special_tokens()` as the below link shows.

Therefore, I think this is a weird behavior that tokenizer states before reloaded and after reloaded are not the same. What do you think about this?  

https://github.com/huggingface/transformers/blob/65659a29cf5a079842e61a63d57fa24474288998/src/transformers/tokenization_utils_base.py#L1930

```
tokenizer = AlbertTokenizerFast("tests/fixtures/spiece.model", cls_token="[OAO]")
print(tokenizer.tokenize('[OAO]')) # Output: ['▁[', 'o', 'ao', ']']

tokenizer.save_pretrained("./test_storage")

tokenizer = AlbertTokenizerFast.from_pretrained("./test_storage")
print(tokenizer.tokenize('[OAO]')) # Output: ['[OAO]']
```

Also, I was actually not aware of the workflow docstring you mentioned, maybe we can add this to constructor docstring or another place for a better noticeability? (Users might not be aware of this workflow if they don't use `from_pretrained` method.)

For the second change, I will reply on your review threads.

Thank you again for taking care of this!

Hi @qqaatw,

Thank you so much for your detailed answers! :hugs:  I perfectly understand why that this behavior surprises you now! 

Your proposal seems to me very relevant but I am still a bit worried about the side effects that this could bring because it is a change that will affect all tokenizers and I personnaly need some time to examine its impact - in particular given the previous workflow recommended in the docstring. 

On this subject, if this change is accepted, it will probably require dedicated tests. In order not to block the rest, maybe the best thing would be to divide this PR in two: the change of the arguments for the mask token on one side and the change of the value of a special token in the init on the other. What do you think about it?

Hello @SaulLu,

Thanks for your reply!

Sure, I can separate these to different PRs.