Zero Shot NER models Comparision - Spacy, Gliner2, Stanza | AI Engineering

Taxonomy and Ontology are the two famous words of the modern AI era powered by everything is connected philosophy -  It is a foundational principle of neural networks of our brain and how we store information and retrieve them and widely powers modern storage and retrieval systems.

Let it be adding a bit of determinism to your RAG project or Storing and retrieving information such as  Long term and Short Term Memories in a structured way for Agents

GraphRAGs, Knowledge Graphs, Knowledge Management Systems, Semantic memories, Portion of Context Engineering Systems and many more modern innovations are all powered by these foundational philosophies

In fact, Ontology or Taxonomy are not new innovation, they have been into existence since Aristotle and Carl Linneaus have created the Systematic classification of Animals and Biology

From then to now for any complex information that are connected - we use these techniques and in the base of modern Knowledge Ontology - there are triplets contains three foundational elements such as

  • Subject: The person, place, or thing that does the action or is the focus of the sentence. It is usually a noun or pronoun.
  • Predicate: The part of the sentence that contains the verb and tells what the subject is doing or describes its state.
  • Object: The noun or pronoun inside the predicate that receives the action done by the subject.

Systems like Wikipedia have been successfully implementing and using Graph Databases part of their systems. Neo4j, MemGraph and various other GraphDB providers were betting on it even before AI gives them a new life and light.

With all that said, Extracting these triplets or entities from any unstructured data is a major stepping stone of AI Engineering. or Knowledge Engineering.

What is NER and Triplets - how they are connected

Take the sentence: "Google acquired Android in 2005."

  • NER sees: [Google] (Organization), [Android] (Product), [2005] (Date).
  • The Triplet sees: (Google, acquired, Android).

In a simple English, NER(Named Entity Recognition) finds the nouns and triplet connects the noun with relationships

Zero Shot NER extraction with Spacy, Gliner2 and Stanza

Now how do we extract Entities what are our options.  While LLMs are good at extracting Triplets and Entities and relationships and widely used nowadays in production business logic.

For Deterministic outcomes and control/customization, we use libraries that are built on top of some intuitive approaches proposed by some white papers - it is often act like a pipeline and gets the job done.

Now we are going to see three NER extraction libraries that we can reliably use in production use cases

  • Spacy
  • Gliner2
  • Stanza

spaCy and Stanza ship with NER heads trained on a FIXED label set (OntoNotes-style: PERSON, ORG, GPE, DATE, ...). That vocabulary is baked into the model weights, so no labels are passed at inference time.

GLiNER2 is a zero-shot / open-vocabulary span classifier. It has no fixed output vocabulary at all – you must supply the label strings you want it to score spans against. There is no "give me everything" mode; the label list you pass IS what defines what it looks for. To make the comparison closer to apples-to-apples, we pass GLiNER2 a broad generic label set that approximates spaCy/Stanza's default OntoNotes coverage.

"""
Compare NER extraction across spaCy, Stanza, and GLiNER2 on the same text.

Why these three behave differently:
- spaCy and Stanza ship with NER heads trained on a FIXED label set
  (OntoNotes-style: PERSON, ORG, GPE, DATE, ...). That vocabulary is baked
  into the model weights, so no labels are passed at inference time.
- GLiNER2 is a zero-shot / open-vocabulary span classifier. It has no fixed
  output vocabulary at all – you must supply the label strings you want it
  to score spans against. There is no "give me everything" mode; the label
  list you pass IS what defines what it looks for. To make the comparison
  closer to apples-to-apples, we pass GLiNER2 a broad generic label set that
  approximates spaCy/Stanza's default OntoNotes coverage.

Install (pick lightweight/fast models to keep this quick):
    pip install spacy stanza "gliner2[local]"
    python -m spacy download en_core_web_sm
    python -c "import stanza; stanza.download('en')"

Usage:
    python compare_ner.py "Apple CEO Tim Cook announced the new iPhone 15 in Cupertino, California on September 12, 2023."
    python compare_ner.py            # uses a built-in sample sentence
"""

import sys

SAMPLE_TEXT = (
    """subject: Life Insurance Corporation of India: Communication in respect of\n Tax Deduction at Source (TDS) on Dividend\nfrom: Life Insurance Corporation of India <[email protected]> │ │ \ndomain: kfintech.com\n\nLIFE INSURANCE CORPORATION OF INDIA (constituted under the Life Insurance Corporation Act, 1956) IRDAI Registration No. 512 Central Office: 'Yogakshema', Je │ │ evan Bima Marg, Mumbai, Maharashtra - 400 021 Tel. No.: 022 - 2202 2079 Email: [email protected]; website: www.licindia.in Date: July 07, 2023 Ref: Folio / DP Id & Client Id No: │ │ 5207424 Name of the Shareholder: THOMAS MARTIN Dear Member, We are pleased to inform you that the Board of Directors of Life Insurance Corporation of India ("LIC" or "LICI" o │ │ r "the Corporation") in its meeting held on May 24, 2023, has recommended a final dividend of ₹ 3.00 (Three Rupees) per equity share of the face value of ₹ 10/- each (30%), for the F │ │ inancial Year 2022-23, subject to the approval of members of the Corporation at the ensuing Annual General Meeting ("AGM") scheduled to be held on Tuesday, August 22, 2023. The recor │ │ d date for the purpose of final dividend would be Friday, July 21, 2023. The dividend would be paid to the eligible members within a period of 30 days from the date of AGM, i.e., on │ │ or before September 20, 2023, electronically, through various online modes or any other permissible modes to those members who have updated their bank account details with their Depo │ │ sitory Participants ("DPs"). As per the Income tax Act, 1961, as amended, dividend declared and paid by an entity is taxable in the hands of its members w.e.f. April 1, 2020, and acc │ │ ordingly Corporation is required to deduct tax at source ("TDS") from dividend paid to the members at the applicable rates. Table 1: TDS to be deducted at higher rate in case of non- │ │ filers of Return of Income (Section 206AB): ParticularApplicable TDS rateSection 206AB of the Income Tax Act, 1961 ("IT Act"), effective from July 1, 2021, higher of rates of tax wou │ │ ld be deducted in case of payments to 'Specified Persons'At twice the rate specified in the relevant provision of the Act; Or At the rate of 5% 'Specified Person' means a person who │ │ has: not filed the income tax return for the previous year immediately prior to the financial year in which tax is required to be deducted, for which the time limit for filing the re │ │ turn of income under Section 139(1) of the Act has expired; and the aggregate of tax deducted at source ('TDS') and tax collected at source ('TCS') is INR 50,000 or more in that prev │ │ ious year. A Non-resident who does not have the permanent establishment in India is excluded from the scope of a Specified person. For specified persons who have not submitted their │ │ Permanent Account Number ('PAN') as well as not filed their return of income tax shall be deducted at the higher of the two rates prescribed under Sections 206AA and 206AB of the Act │ │ . Further as per Section 139AA of the IT Act, every person who has been allotted a PAN and who is eligible to obtain Aadhaar, shall be required to link the PAN with Aadhaar. In case │ │ of failure to comply with this, the PAN allotted shall be deemed to be invalid/inoperative and he shall be liable to all consequences under the Act and tax shall be deducted at highe │ │ r rates as prescribed under the Act. The Tables below summarize the applicable TDS provisions in accordance with the provisions of the IT Act, for various member categories, includin │ │ g Resident/Non-Resident members. Table 2: Resident Members: For Financial Year 2023-24 taxes shall be deducted at source under Section 194 of the IT Act as follows:- SectionCategory │ │ of MembersApplicable Tax rateExemption applicability/ Documentation requirements194Members having valid PAN10% or as notified by the Government of IndiaUpdate valid PAN if not alread │ │ y done with respective depositories206AA and 206ABMembers not having PAN / invalid PAN; and Members who have not filed their Income-tax returns in the last financial year (Specified │ │ Person as per Section 206AB of the Income-tax Act)20%Update valid PAN if not already done with respective depositories However, no tax shall be deducted on the dividend payable to a │ │ Resident Member (Indiv"""
)

# Generic label set GLiNER2 needs since it has no built-in vocabulary.
# Chosen to roughly mirror spaCy/Stanza's default OntoNotes entity types.
GLINER2_LABELS = [
    "person", "organization", "location", "date", "time",
    "money", "percent", "product", "event", "law",
    "language", "nationality", "facility", "work_of_art",
    "quantity", "ordinal", "cardinal",
]


def run_spacy(text):
    import spacy
    try:
        nlp = spacy.load("en_core_web_sm")
    except OSError:
        return [("ERROR", f"Model not found. Run: python -m spacy download en_core_web_sm")]
    doc = nlp(text)
    return [(ent.text, ent.label_) for ent in doc.ents]


def run_stanza(text):
    import stanza
    try:
        nlp = stanza.Pipeline("en", processors="tokenize,ner", verbose=False, download_method=None)
    except Exception:
        return [("ERROR", "Model not found. Run: python -c \"import stanza; stanza.download('en')\"")]
    doc = nlp(text)
    return [(ent.text, ent.type) for ent in doc.ents]


def run_gliner2(text):
    from gliner2 import GLiNER2
    extractor = GLiNER2.from_pretrained("fastino/gliner2-base-v1")
    result = extractor.extract_entities(text, GLINER2_LABELS)
    flat = []
    for label, values in result.get("entities", {}).items():
        for v in values:
            flat.append((v, label))
    return flat


def print_table(name, entities):
    print(f"\n=== {name} ===")
    if not entities:
        print("  (no entities found)")
        return
    width = max(len(t) for t, _ in entities) + 2
    for text, label in entities:
        print(f"  {text.ljust(width)} {label}")


def main():
    text = sys.argv[1] if len(sys.argv) > 1 else SAMPLE_TEXT
    print(f"Text:\n  {text}")

    for name, fn in [("spaCy", run_spacy), ("Stanza", run_stanza), ("GLiNER2", run_gliner2)]:
        try:
            entities = fn(text)
        except ImportError as e:
            entities = [("ERROR", f"Not installed: {e}")]
        except Exception as e:
            entities = [("ERROR", str(e))]
        print_table(name, entities)


if __name__ == "__main__":
    main()

 

In further articles, we will explore the Gliner-Relax and other relationship and triplet extraction techniques like rebel etc

Cheers
Sarav