Repository: flairNLP/flair

Issue:
SequenceTagger.predict() fails over empty sentences
This code produces an error: 

```python
tagger = SequenceTagger.load('ner')

# empty sentence
sentence: Sentence = Sentence('   ')
tagger.predict(sentence)
```
Need more robust handling of empty sentences.



Discussion:
Same for text classifier. Should also be tackled there.

Resolving patch:
diff --git a/flair/models/sequence_tagger_model.py b/flair/models/sequence_tagger_model.py
--- a/flair/models/sequence_tagger_model.py
+++ b/flair/models/sequence_tagger_model.py
@@ -1,4 +1,5 @@
 import warnings
+import logging
 
 import torch.autograd as autograd
 import torch.nn
@@ -13,6 +14,9 @@
 
 from flair.training_utils import clear_embeddings
 
+log = logging.getLogger(__name__)
+
+
 START_TAG: str = '<START>'
 STOP_TAG: str = '<STOP>'
 
@@ -169,7 +173,6 @@ def load_from_file(cls, model_file):
         return model
 
     def forward(self, sentences: List[Sentence]):
-
         self.zero_grad()
 
         # first, sort sentences by number of tokens
@@ -420,11 +423,13 @@ def predict(self, sentences: Union[List[Sentence], Sentence], mini_batch_size=32
         if type(sentences) is Sentence:
             sentences = [sentences]
 
+        filtered_sentences = self._filter_empty_sentences(sentences)
+
         # remove previous embeddings
-        clear_embeddings(sentences)
+        clear_embeddings(filtered_sentences)
 
         # make mini-batches
-        batches = [sentences[x:x + mini_batch_size] for x in range(0, len(sentences), mini_batch_size)]
+        batches = [filtered_sentences[x:x + mini_batch_size] for x in range(0, len(filtered_sentences), mini_batch_size)]
 
         for batch in batches:
             scores, predicted_ids = self._predict_scores_batch(batch)
@@ -467,6 +472,13 @@ def _predict_scores_batch(self, sentences: List[Sentence]):
 
         return all_confidences, all_tags_seqs
 
+    @staticmethod
+    def _filter_empty_sentences(sentences: List[Sentence]) -> List[Sentence]:
+        filtered_sentences = [sentence for sentence in sentences if sentence.tokens]
+        if len(sentences) != len(filtered_sentences):
+            log.warning('Ignore {} sentence(s) with no tokens.'.format(len(sentences) - len(filtered_sentences)))
+        return filtered_sentences
+
     @staticmethod
     def load(model: str):
         model_file = None
diff --git a/flair/models/text_classification_model.py b/flair/models/text_classification_model.py
--- a/flair/models/text_classification_model.py
+++ b/flair/models/text_classification_model.py
@@ -1,4 +1,5 @@
 import warnings
+import logging
 from typing import List, Union
 
 import torch
@@ -9,6 +10,9 @@
 from flair.training_utils import convert_labels_to_one_hot, clear_embeddings
 
 
+log = logging.getLogger(__name__)
+
+
 class TextClassifier(nn.Module):
     """
     Text Classification Model
@@ -109,7 +113,9 @@ def predict(self, sentences: Union[Sentence, List[Sentence]], mini_batch_size: i
         if type(sentences) is Sentence:
             sentences = [sentences]
 
-        batches = [sentences[x:x + mini_batch_size] for x in range(0, len(sentences), mini_batch_size)]
+        filtered_sentences = self._filter_empty_sentences(sentences)
+
+        batches = [filtered_sentences[x:x + mini_batch_size] for x in range(0, len(filtered_sentences), mini_batch_size)]
 
         for batch in batches:
             scores = self.forward(batch)
@@ -122,6 +128,13 @@ def predict(self, sentences: Union[Sentence, List[Sentence]], mini_batch_size: i
 
         return sentences
 
+    @staticmethod
+    def _filter_empty_sentences(sentences: List[Sentence]) -> List[Sentence]:
+        filtered_sentences = [sentence for sentence in sentences if sentence.tokens]
+        if len(sentences) != len(filtered_sentences):
+            log.warning('Ignore {} sentence(s) with no tokens.'.format(len(sentences) - len(filtered_sentences)))
+        return filtered_sentences
+
     def calculate_loss(self, scores: List[List[float]], sentences: List[Sentence]) -> float:
         """
         Calculates the loss.
diff --git a/flair/training_utils.py b/flair/training_utils.py
--- a/flair/training_utils.py
+++ b/flair/training_utils.py
@@ -71,8 +71,8 @@ def to_empty_tsv():
         return '_\t_\t_\t_\t_\t_\t_\t_'
 
     def __str__(self):
-        return '{0:<10}\ttp: {1} - fp: {2} - fn: {3} - precision: {4:.4f} - recall: {5:.4f} - accuracy: {6:.4f} - f1-score: {7:.4f}'.format(
-            self.name, self._tp, self._fp, self._fn, self.precision(), self.recall(), self.accuracy(), self.f_score())
+        return '{0:<10}\ttp: {1} - fp: {2} - fn: {3} - tn: {4} - precision: {5:.4f} - recall: {6:.4f} - accuracy: {7:.4f} - f1-score: {8:.4f}'.format(
+            self.name, self._tp, self._fp, self._fn, self._tn, self.precision(), self.recall(), self.accuracy(), self.f_score())
 
 
 class WeightExtractor(object):


Test patch: