Repository: keras-team/keras-nlp

Issue:
Raise `RuntimeError` if `oov_token` is not found in vocabulary of `WordPieceTokenizer`
**Describe the bug**
Currently, `WordPieceTokenizer` does not raise any warning if `oov_token` is not provided as parameter. 

[Even though it is `oov_token` supposed to exist in `vocabulary`](https://github.com/keras-team/keras-nlp/blob/master/keras_nlp/tokenizers/word_piece_tokenizer.py#L113), often time vocabulary is computed from training dataset, meaning user can easily forget about adding `oov_token` to `vocabulary`.  

- We don't what `oov_token` user is using. Current implementation can't really guess it from `vocabulary`, nor it is possible. 
- It's not always expected the `oov_token` to `[UNK]`. Is it?


**Additonally** `tensorflow_text` throws barely understable error: 

```
RuntimeError: Cannot find unk_token in the vocab!
```
What is `unk_token` (unknown_token)? User won't really understand this. We need to deal with this before we reach this error.  

**To Reproduce**
Run `WordPieceTokenizer` with a vocabulary not containing `oov_token`, and not passing `oov_token` parameter to contructor.

**Expected behavior**
Give clear error about missing parameter value for `oov_token`.

**Would you like to help us fix it?**
Definitely!

Docstring is enough to let user known he/she needs to add `oov_token` to `vocabulary`, so we'll just give error if `oov_token` is not provided, something like, as probably since the user hasn't passed, he probably didn't even added it to `vocabulary`.

```
`oov_token` parameter must be passed. Found None.
Add `oov_token` to `vocabulary` and pass this token to `oov_token` parameter.
```


Discussion:
Absolutely, we could do a better job with our own error message here instead of relying on tf.text. We should say that we expected to find the oov_token in the vocabulary, show what it is, and ask the user to either pass a vocabulary containing the token, or update the token to something in the vocabulary.

Assigning to you!

So to conclude we need to:
- Confirm that the user passes an `oov_token` to initializer otherwise raise error.
- Check if passed `oov_token` exists in `vocabulary` otherwise raise error.
Oh sorry functionality should not change. I was just try to paraphrase an error message above. We should keep `"[UNK]"` as the default for oov_token.

If we don't find the oov token, the user might need to do one of two things
 - Update the vocabulary to include the oov_token.
 - Update the oov_token from `[UNK]` to an oov token that is already in the vocabulary.

Our error message should make this clear.

Resolving patch:
diff --git a/keras_nlp/tokenizers/word_piece_tokenizer.py b/keras_nlp/tokenizers/word_piece_tokenizer.py
--- a/keras_nlp/tokenizers/word_piece_tokenizer.py
+++ b/keras_nlp/tokenizers/word_piece_tokenizer.py
@@ -198,6 +198,8 @@ def __init__(
                 "Vocabulary must be an file path or list of terms. "
                 f"Received: vocabulary={vocabulary}"
             )
+        if oov_token is None:
+            raise ValueError("`oov_token` cannot be None.")
 
         self.sequence_length = sequence_length
         self.lowercase = lowercase
@@ -207,6 +209,15 @@ def __init__(
         self.suffix_indicator = suffix_indicator
         self.oov_token = oov_token
 
+        if oov_token not in self.vocabulary:
+            raise RuntimeError(
+                f'Cannot find `oov_token="{self.oov_token}"` in the '
+                "vocabulary.\n"
+                "You can either update the vocabulary to include "
+                f'`"{self.oov_token}"`, or pass a different value for '
+                "the `oov_token` argument when creating the tokenizer."
+            )
+
         self._fast_word_piece = tf_text.FastWordpieceTokenizer(
             vocab=self.vocabulary,
             token_out_type=self.compute_dtype,


Test patch:
diff --git a/keras_nlp/tokenizers/word_piece_tokenizer_test.py b/keras_nlp/tokenizers/word_piece_tokenizer_test.py
--- a/keras_nlp/tokenizers/word_piece_tokenizer_test.py
+++ b/keras_nlp/tokenizers/word_piece_tokenizer_test.py
@@ -223,3 +223,25 @@ def test_saving(self):
             model(input_data),
             restored_model(input_data),
         )
+
+    def test_no_oov_token_in_vocabulary(self):
+        vocab_data = ["qu", "@@ick", "br", "@@OWN", "fox"]
+        with self.assertRaises(RuntimeError):
+            WordPieceTokenizer(
+                vocabulary=vocab_data,
+            )
+
+        vocab_data = ["@UNK@", "qu", "@@ick", "br", "@@OWN", "fox"]
+        with self.assertRaises(RuntimeError):
+            WordPieceTokenizer(
+                vocabulary=vocab_data,
+            )
+
+        vocab_data = ["UNK", "qu", "@@ick", "br", "@@OWN", "fox"]
+        with self.assertRaises(RuntimeError):
+            WordPieceTokenizer(
+                vocabulary=vocab_data,
+            )
+
+        with self.assertRaises(ValueError):
+            WordPieceTokenizer(vocabulary=vocab_data, oov_token=None)