mirror of
				https://github.com/explosion/spaCy.git
				synced 2025-11-04 01:48:04 +03:00 
			
		
		
		
	entities on doc_annotation, parse links and check their offsets against the entities. unit test works
This commit is contained in:
		
							parent
							
								
									3aed177a35
								
							
						
					
					
						commit
						880dccf93e
					
				| 
						 | 
				
			
			@ -581,6 +581,10 @@ class Errors(object):
 | 
			
		|||
 | 
			
		||||
    # TODO: fix numbering after merging develop into master
 | 
			
		||||
 | 
			
		||||
    E983 = ("Each link annotation should refer to a dictionary with at most one "
 | 
			
		||||
            "identifier mapping to 1.0, and all others to 0.0.")
 | 
			
		||||
    E984 = ("The offsets of the annotations for 'links' need to refer exactly "
 | 
			
		||||
            "to the offsets of the 'entities' annotations.")
 | 
			
		||||
    E985 = ("The 'ent_iob' attribute of a Token should be an integer indexing "
 | 
			
		||||
            "into {values}, but found {value}.")
 | 
			
		||||
    E986 = ("Could not create any training batches: check your input. "
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,12 +85,28 @@ cdef class NewExample:
 | 
			
		|||
        return self.x.text
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _annot2array(strings, tok_annot, doc_annot):
 | 
			
		||||
def _annot2array(vocab, tok_annot, doc_annot):
 | 
			
		||||
    attrs = []
 | 
			
		||||
    values = []
 | 
			
		||||
 | 
			
		||||
    for key, value in doc_annot.items():
 | 
			
		||||
        if key == "entities":
 | 
			
		||||
            words = tok_annot["ORTH"]
 | 
			
		||||
            ent_iobs, ent_types = _parse_ner_tags(vocab, words, value)
 | 
			
		||||
            tok_annot["ENT_IOB"] = ent_iobs
 | 
			
		||||
            tok_annot["ENT_TYPE"] = ent_types
 | 
			
		||||
        elif key == "links":
 | 
			
		||||
            entities = doc_annot.get("entities", {})
 | 
			
		||||
            if value and not entities:
 | 
			
		||||
                raise ValueError(Errors.E984)
 | 
			
		||||
            ent_kb_ids = _parse_links(vocab, words, value, entities)
 | 
			
		||||
            tok_annot["ENT_KB_ID"] = ent_kb_ids
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError(f"Unknown doc attribute: {key}")
 | 
			
		||||
 | 
			
		||||
    for key, value in tok_annot.items():
 | 
			
		||||
        if key not in IDS:
 | 
			
		||||
            raise ValueError(f"Unknown attr: {key}")
 | 
			
		||||
            raise ValueError(f"Unknown token attribute: {key}")
 | 
			
		||||
        elif key == "ORTH":
 | 
			
		||||
            pass
 | 
			
		||||
        elif key == "HEAD":
 | 
			
		||||
| 
						 | 
				
			
			@ -108,10 +124,8 @@ def _annot2array(strings, tok_annot, doc_annot):
 | 
			
		|||
                raise ValueError(Errors.E985.format(values=iob_strings, value=values))
 | 
			
		||||
        else:
 | 
			
		||||
            attrs.append(key)
 | 
			
		||||
            values.append([strings.add(v) for v in value])
 | 
			
		||||
    # TODO: Calculate token.ent_kb_id from doc_annot["links"].
 | 
			
		||||
    # We need to fix this and the doc.ents thing, both should be doc
 | 
			
		||||
    # annotations.
 | 
			
		||||
            values.append([vocab.strings.add(v) for v in value])
 | 
			
		||||
 | 
			
		||||
    array = numpy.asarray(values, dtype="uint64")
 | 
			
		||||
    return attrs, array.T
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -129,8 +143,10 @@ def _fix_legacy_dict_data(predicted, example_dict):
 | 
			
		|||
    for key, value in example_dict.items():
 | 
			
		||||
        if key in ("token_annotation", "doc_annotation"):
 | 
			
		||||
            pass
 | 
			
		||||
        elif key in ("cats", "links"):
 | 
			
		||||
        elif key in ("cats", "links") and value:
 | 
			
		||||
            doc_dict[key] = value
 | 
			
		||||
        elif key in ("ner", "entities") and value:
 | 
			
		||||
            doc_dict["entities"] = value
 | 
			
		||||
        else:
 | 
			
		||||
            token_dict[key] = value
 | 
			
		||||
    # Remap keys
 | 
			
		||||
| 
						 | 
				
			
			@ -149,12 +165,6 @@ def _fix_legacy_dict_data(predicted, example_dict):
 | 
			
		|||
    for key, value in old_token_dict.items():
 | 
			
		||||
        if key in remapping:
 | 
			
		||||
            token_dict[remapping[key]] = value
 | 
			
		||||
        elif key in ("ner", "entities") and value:
 | 
			
		||||
            # Arguably it would be smarter to put this in the doc annotation?
 | 
			
		||||
            words = token_dict.get("words", [t.text for t in predicted])
 | 
			
		||||
            ent_iobs, ent_types = _parse_ner_tags(predicted, words, value)
 | 
			
		||||
            token_dict["ENT_IOB"] = ent_iobs
 | 
			
		||||
            token_dict["ENT_TYPE"] = ent_types
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError(f"Unknown attr: {key}")
 | 
			
		||||
    return {
 | 
			
		||||
| 
						 | 
				
			
			@ -163,16 +173,13 @@ def _fix_legacy_dict_data(predicted, example_dict):
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _parse_ner_tags(predicted, words, biluo_or_offsets):
 | 
			
		||||
def _parse_ner_tags(vocab, words, biluo_or_offsets):
 | 
			
		||||
    if isinstance(biluo_or_offsets[0], (list, tuple)):
 | 
			
		||||
        # Convert to biluo if necessary
 | 
			
		||||
        # This is annoying but to convert the offsets we need a Doc
 | 
			
		||||
        # that has the target tokenization.
 | 
			
		||||
        reference = Doc(
 | 
			
		||||
            predicted.vocab,
 | 
			
		||||
            words=words
 | 
			
		||||
        )
 | 
			
		||||
        biluo = biluo_tags_from_offsets(predicted, biluo_or_offsets)
 | 
			
		||||
        reference = Doc(vocab, words=words)
 | 
			
		||||
        biluo = biluo_tags_from_offsets(reference, biluo_or_offsets)
 | 
			
		||||
    else:
 | 
			
		||||
        biluo = biluo_or_offsets
 | 
			
		||||
    ent_iobs = []
 | 
			
		||||
| 
						 | 
				
			
			@ -185,6 +192,37 @@ def _parse_ner_tags(predicted, words, biluo_or_offsets):
 | 
			
		|||
            ent_types.append("")
 | 
			
		||||
    return ent_iobs, ent_types
 | 
			
		||||
 | 
			
		||||
def _parse_links(vocab, words, links, entities):
 | 
			
		||||
    reference = Doc(vocab, words=words)
 | 
			
		||||
 | 
			
		||||
    starts = {token.idx: token.i for token in reference}
 | 
			
		||||
    ends = {token.idx + len(token): token.i for token in reference}
 | 
			
		||||
    ent_kb_ids = ["" for _ in reference]
 | 
			
		||||
    entity_map = [(ent[0], ent[1]) for ent in entities]
 | 
			
		||||
 | 
			
		||||
    # links annotations need to refer 1-1 to entity annotations - throw error otherwise
 | 
			
		||||
    for index, annot_dict in links.items():
 | 
			
		||||
        start_char, end_char = index
 | 
			
		||||
        if (start_char, end_char) not in entity_map:
 | 
			
		||||
            raise ValueError(Errors.E984)
 | 
			
		||||
 | 
			
		||||
    for index, annot_dict in links.items():
 | 
			
		||||
        true_kb_ids = []
 | 
			
		||||
        for key, value in annot_dict.items():
 | 
			
		||||
            if value == 1.0:
 | 
			
		||||
                true_kb_ids.append(key)
 | 
			
		||||
        if len(true_kb_ids) > 1:
 | 
			
		||||
            raise ValueError(Errors.E983)
 | 
			
		||||
 | 
			
		||||
        if len(true_kb_ids) == 1:
 | 
			
		||||
            start_char, end_char = index
 | 
			
		||||
            start_token = starts.get(start_char)
 | 
			
		||||
            end_token = ends.get(end_char)
 | 
			
		||||
            for i in range(start_token, end_token+1):
 | 
			
		||||
                ent_kb_ids[i] = true_kb_ids[0]
 | 
			
		||||
 | 
			
		||||
    return ent_kb_ids
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Example:
 | 
			
		||||
    def get_aligned(self, field):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,33 +41,6 @@ def test_Example_from_dict_with_tags(annots):
 | 
			
		|||
        assert token.tag_ == annots["tags"][i]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.xfail(reason="TODO - fix")
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "annots",
 | 
			
		||||
    [
 | 
			
		||||
        {
 | 
			
		||||
            "words": ["I", "like", "New", "York", "and", "Berlin", "."],
 | 
			
		||||
            "entities": [(7, 15, "LOC"), (20, 26, "LOC")],
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
def test_Example_from_dict_with_entities(annots):
 | 
			
		||||
    vocab = Vocab()
 | 
			
		||||
    predicted = Doc(vocab, words=annots["words"])
 | 
			
		||||
    eg = Example.from_dict(predicted, annots)
 | 
			
		||||
    assert len(list(eg.reference.ents)) == 2
 | 
			
		||||
    assert eg.reference[0].ent_iob_ == "O"
 | 
			
		||||
    assert eg.reference[1].ent_iob_ == "O"
 | 
			
		||||
    assert eg.reference[2].ent_iob_ == "B"
 | 
			
		||||
    assert eg.reference[3].ent_iob_ == "I"
 | 
			
		||||
    assert eg.reference[4].ent_iob_ == "O"
 | 
			
		||||
    assert eg.reference[5].ent_iob_ == "B"
 | 
			
		||||
    assert eg.reference[6].ent_iob_ == "O"
 | 
			
		||||
    assert eg.reference[2].ent_type_ == "LOC"
 | 
			
		||||
    assert eg.reference[3].ent_type_ == "LOC"
 | 
			
		||||
    assert eg.reference[5].ent_type_ == "LOC"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "annots",
 | 
			
		||||
    [
 | 
			
		||||
| 
						 | 
				
			
			@ -147,13 +120,39 @@ def test_Example_from_dict_with_cats(annots):
 | 
			
		|||
    assert eg.reference.cats["cat3"] == 0.5
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.xfail(reason="TODO - fix")
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "annots",
 | 
			
		||||
    [
 | 
			
		||||
        {
 | 
			
		||||
            "words": ["Russ", "Cochran", "made", "reprints"],
 | 
			
		||||
            "links": {(0, 12): {"Q7381115": 1.0, "Q2146908": 0.0}},
 | 
			
		||||
            "words": ["I", "like", "New", "York", "and", "Berlin", "."],
 | 
			
		||||
            "entities": [(7, 15, "LOC"), (20, 26, "LOC")],
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
def test_Example_from_dict_with_entities(annots):
 | 
			
		||||
    vocab = Vocab()
 | 
			
		||||
    predicted = Doc(vocab, words=annots["words"])
 | 
			
		||||
    eg = Example.from_dict(predicted, annots)
 | 
			
		||||
    assert len(list(eg.reference.ents)) == 2
 | 
			
		||||
    assert eg.reference[0].ent_iob_ == "O"
 | 
			
		||||
    assert eg.reference[1].ent_iob_ == "O"
 | 
			
		||||
    assert eg.reference[2].ent_iob_ == "B"
 | 
			
		||||
    assert eg.reference[3].ent_iob_ == "I"
 | 
			
		||||
    assert eg.reference[4].ent_iob_ == "O"
 | 
			
		||||
    assert eg.reference[5].ent_iob_ == "B"
 | 
			
		||||
    assert eg.reference[6].ent_iob_ == "O"
 | 
			
		||||
    assert eg.reference[2].ent_type_ == "LOC"
 | 
			
		||||
    assert eg.reference[3].ent_type_ == "LOC"
 | 
			
		||||
    assert eg.reference[5].ent_type_ == "LOC"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "annots",
 | 
			
		||||
    [
 | 
			
		||||
        {
 | 
			
		||||
            "words": ["I", "like", "New", "York", "and", "Berlin", "."],
 | 
			
		||||
            "entities": [(7, 15, "LOC"), (20, 26, "LOC")],
 | 
			
		||||
            "links": {(7, 15): {"Q60": 1.0, "Q64": 0.0}, (20, 26): {"Q60": 0.0, "Q64": 1.0}},
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -161,7 +160,28 @@ def test_Example_from_dict_with_links(annots):
 | 
			
		|||
    vocab = Vocab()
 | 
			
		||||
    predicted = Doc(vocab, words=annots["words"])
 | 
			
		||||
    eg = Example.from_dict(predicted, annots)
 | 
			
		||||
    assert eg.reference[0].ent_kb_id_ == "Q7381115"
 | 
			
		||||
    assert eg.reference[1].ent_kb_id_ == "Q7381115"
 | 
			
		||||
    assert eg.reference[2].ent_kb_id_ == ""
 | 
			
		||||
    assert eg.reference[3].ent_kb_id_ == ""
 | 
			
		||||
    assert eg.reference[0].ent_kb_id_ == ""
 | 
			
		||||
    assert eg.reference[1].ent_kb_id_ == ""
 | 
			
		||||
    assert eg.reference[2].ent_kb_id_ == "Q60"
 | 
			
		||||
    assert eg.reference[3].ent_kb_id_ == "Q60"
 | 
			
		||||
    assert eg.reference[4].ent_kb_id_ == ""
 | 
			
		||||
    assert eg.reference[5].ent_kb_id_ == "Q64"
 | 
			
		||||
    assert eg.reference[6].ent_kb_id_ == ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize(
 | 
			
		||||
    "annots",
 | 
			
		||||
    [
 | 
			
		||||
        {
 | 
			
		||||
            "words": ["I", "like", "New", "York", "and", "Berlin", "."],
 | 
			
		||||
            "entities": [(7, 15, "LOC"), (20, 26, "LOC")],
 | 
			
		||||
            "links": {(0, 1): {"Q7381115": 1.0, "Q2146908": 0.0}},
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
def test_Example_from_dict_with_links_invalid(annots):
 | 
			
		||||
    vocab = Vocab()
 | 
			
		||||
    predicted = Doc(vocab, words=annots["words"])
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        Example.from_dict(predicted, annots)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user