Unexpected behavior of SpaCy matcher with negation

Viewed 129

Somehow I have trouble understanding the negation in SpaCy matchers. I tried this code:

import spacy
from spacy.matcher import Matcher
import json



nlp = spacy.load('en_core_web_sm')
    #from spacy.tokenizer import Tokenizer
matcher = Matcher(nlp.vocab)

Sentence = "The cat is black"

negative_sentence = "The cat is not black"

test_pattern = '''
[
  [
    {
      "TEXT": "cat"
    },
    {
      "LEMMA": "be"
    },
    {
      "LOWER": "not",
      "OP": "!"
      
    },
    {
      "LOWER": "black"
    }
  ]
]
''' 

db = json.loads(test_pattern)

matcher.add("TEST_PATTERNS", db)


'''*********************Validate matcher on positive sentence******************'''
doc = nlp(Sentence, matcher)

matches = matcher(doc)

if matches != []:
    print('Positive sentence identified')
else:
    print('Nothing found for positive sentence')

'''*********************Validate matcher on negative sentence******************'''
doc = nlp(negative_sentence, matcher)
matches = matcher(doc)

if matches != []:
    print('Negative sentence identified')
else:
    print('Nothing found for negative sentence')
    

The result is:

  • Nothing found for positive sentence
  • Nothing found for negative sentence

I would expect that the sentence "The cat is black" would be a match. Furthermore, when I replace the ! with any other sign ("*", "?", or "+") it works as expected:

import spacy
from spacy.matcher import Matcher
import json



nlp = spacy.load('en_core_web_sm')
    #from spacy.tokenizer import Tokenizer
matcher = Matcher(nlp.vocab)

Sentence = "The cat is black"

negative_sentence = "The cat is not black"

test_pattern = '''
[
  [
    {
      "TEXT": "cat"
    },
    {
      "LEMMA": "be"
    },
    {
      "LOWER": "not",
      "OP": "?"
      
    },
    {
      "LOWER": "black"
    }
  ]
]
''' 

db = json.loads(test_pattern)

matcher.add("TEST_PATTERNS", db)


'''*********************Validate matcher on positive sentence******************'''
doc = nlp(Sentence, matcher)

matches = matcher(doc)

if matches != []:
    print('Positive sentence identified')
else:
    print('Nothing found for positive sentence')

'''*********************Validate matcher on negative sentence******************'''
doc = nlp(negative_sentence, matcher)
matches = matcher(doc)

if matches != []:
    print('Negative sentence identified')
else:
    print('Nothing found for negative sentence')

Result:

  • Positive sentence identified
  • Negative sentence identified

How can I use the negation and only identify "The cat is black" and not "The cat is not black".

The reason why like to of the "OP" is because there might also other words between "is" and "black" (e.g., "The cat is kind and black" and not "The cat is not kind and black" ).

Any help on understanding negation with SpaCy matchers is highly appreciated.

1 Answers

Each dictionary in your match pattern corresponds to a token by default. With the ! operator it still corresponds to one token, just in a negative sense. With the * operator it corresponds to zero or more tokens, with + it's one or more tokens.

Looking at your original pattern, these are your tokens:

  • text: cat
  • lemma: be
  • text: not, op: !
  • lower: cat

Given the sentence "The cat is black", the match process works like this:

  1. "the" matches nothing so we skip it.
  2. "cat" matches your first token.
  3. "is" matches your second token.
  4. "black" matches your third token because it is not "not"
  5. The sentence ends so there is no "cat" token, so the match fails.

When debugging patterns it's helpful to step through them like above.

For the other ops... * and ? work because "not" matches zero times. I would not expect + to work in the positive case.

The way you are trying to avoid matching negated things is kind of tricky. I would recommend you match all sentences with the relevant words first, ignoring negation, and then check if there is negation using the dependency parse.

Related