nervaluate

repository·main·Indexed 42 days ago

https://github.com/mantisai/nervaluate

A Python module for advanced Named Entity Recognition (NER) evaluation version 1.2.1. It implements entity-level metrics following SemEval 2013 standards, accounting for partial boundary matches and entity type mismatches. It provides an Evaluator class to calculate precision, recall, and F1-score across four schemas: Strict, Exact, Partial, and Type.

Tokens
4.9K
Snippets
16
Records
19
Agent score
52%

What's inside nervaluate

  1. Understand NER evaluation scenarios and schemas

    main

    Unlike simple token-level evaluation, nervaluate uses entity-level evaluation to account for multi-token entities. It categorizes errors into five types:

    • Correct (COR): Both boundaries and type match.
    • Incorrect (INC): Output and gold annotation do not match.
    • Partial (PAR): Boundaries are somewhat similar but not identical.
    • Missing (MIS): A gold annotation is not captured by the system.
    • Spurious (SPU): The system produces an entity that doesn't exist in the gold annotation.

    These are measured using four evaluation schemas:

    • Strict: Requires exact boundary match AND exact entity type.
    • Exact: Requires exact boundary match, regardless of type.
    • Partial: Allows partial boundary match, regardless of type.
    • Type: Requires some overlap between the system tagged entity and the gold annotation.

    Precision, recall, and F1-score are calculated differently depending on whether you are using an Exact Match schema (Strict, Exact) or a Partial Match schema (Partial, Type).

  2. Extend nervaluate to support new data formats

    main

    You can add support for new data formats by implementing a custom loader class. This is useful if your NER data is not in one of the supported formats (Nested lists, CoNLL style tab-delimited strings, or prodi.gy style lists of spans).

    To implement a new loader:

    1. Create a new class in nervaluate/loaders.py.
    2. Inherit from the DataLoader base class.
    3. Implement the load method. The load method must return a list of entity lists, where each entity is a dictionary containing the keys: label, start, and end.
    4. Register the new loader by adding it to the _setup_loaders method in the Evaluator class.

    Once registered, you can use the new format by passing the loader name to the loader argument when instantiating the Evaluator class.

    # Conceptual implementation of a new loader
    from nervaluate.loaders import DataLoader
    
    class MyCustomLoader(DataLoader):
        def load(self, data):
            # Must return a list of entity lists
            # Each entity is: {'label': str, 'start': int, 'end': int}
            return [[{'label': 'ORG', 'start': 0, 'end': 5}]]
  3. How different NER evaluation strategies work

    main

    Nervaluate provides several strategies to score Named Entity Recognition (NER) predictions. Choosing a strategy depends on whether you want to penalize label mismatches or boundary inaccuracies.

    1. StrictEvaluation

    Requires an exact match of both boundaries (start and end) and the entity label.

    • Correct: Perfect boundary and label match.
    • Spurious: Predicted entity matches nothing.
    • Missed: True entity matches nothing.
    • Incorrect: Predicted entity overlaps a true entity but does not match perfectly.

    2. PartialEvaluation

    Focuses on boundary overlap and ignores labels.

    • Correct: Perfect boundary match.
    • Partial: Predicted entity has sufficient overlap with a true entity but boundaries differ.
    • Spurious: Predicted entity matches nothing.
    • Missed: True entity matches nothing.

    3. EntityTypeEvaluation

    Checks for label matches and uses overlap to find the best candidate.

    • Correct: Sufficient overlap and matching label. If multiple true entities overlap, the one with the closest boundaries (minimum sum of start/end offsets) is chosen.
    • Incorrect: Sufficient overlap but the label is wrong.
    • Spurious: Predicted entity matches nothing.
    • Missed: True entity matches nothing.

    4. ExactEvaluation

    Focuses on exact boundaries and ignores labels.

    • Correct: Perfect boundary match, regardless of label.
    • Incorrect: Predicted entity overlaps a true entity but boundaries differ.
    • Spurious: Predicted entity matches nothing.
    • Missed: True entity matches nothing.
  4. Supported Data Loaders

    main

    The Evaluator uses different loaders to parse input data. If loader="default" is passed, the class attempts to infer the correct loader based on the type of the true input:

    • conll: Triggered if true is a str. Used for CoNLL-formatted data.
    • list: Triggered if true is a list containing other list objects (e.g., [[...], [...]]). Note: For list loaders, the number of documents and the length of each document must match between true and pred.
    • dict: Triggered if true is a list of lists where the inner elements are dict objects.

    You can explicitly specify a loader using the loader argument in the Evaluator constructor.

  5. Generate evaluation summary reports

    main

    The Evaluator provides a summary_report() method to view performance metrics.

    • By default, evaluator.summary_report() returns a report for a specific scenario (e.g., 'all') showing metrics for all entity types combined.
    • Use evaluator.summary_report(mode='entities') to get an aggregated report broken down by individual entity types under a specific evaluation schema (e.g., 'strict').
    # Summary for all entities in a specific scenario
    print(evaluator.summary_report())
    
    # Summary broken down by entity type (e.g., DATE, LOC, ORG, PER)
    print(evaluator.summary_report(mode='entities'))
  6. Use the Evaluator class for NER evaluation

    main

    To evaluate NER models, use the Evaluator class from nervaluate.evaluator. You provide the ground truth labels (true) and the model predictions (pred) as lists of lists, where each inner list contains token labels for a sentence. You must also specify the tags (entity types) you want to evaluate and the loader type (e.g., "list").

    from nervaluate.evaluator import Evaluator
    
    true = [
        ['O', 'B-PER', 'I-PER', 'O', 'O', 'O', 'B-ORG', 'I-ORG'],
        ['O', 'B-LOC', 'B-PER', 'I-PER', 'O', 'O', 'B-DATE'],
    ]
      
    pred = [
        ['O', 'O', 'B-PER', 'I-PER', 'O', 'O', 'B-ORG', 'I-ORG'],
        ['O', 'B-LOC', 'I-LOC', 'B-PER', 'O', 'O', 'B-DATE'],
    ]
       
    evaluator = Evaluator(true, pred, tags=['PER', 'ORG', 'LOC', 'DATE'], loader="list")
  7. Configure the minimum overlap threshold for evaluation strategies

    main

    When initializing an EvaluationStrategy (or its subclasses), you can set a min_overlap_percentage to define how much overlap is required between a predicted entity and a true entity to be considered a match or an incorrect overlap. The value must be between 1.0 and 100.0.

    # Example of initializing a strategy with a 50% overlap requirement
    strategy = StrictEvaluation(min_overlap_percentage=50.0)
  8. Run NER evaluation

    main

    Call the .evaluate() method to execute the evaluation across all configured strategies.

    Returns a dictionary containing:

    • "overall": Results for each evaluation strategy (e.g., strict, partial, ent_type, exact).
    • "entities": Per-entity type results for each strategy.
    • "overall_indices": The indices (document index and entity index) for each error category in the overall results.
    • "entity_indices": The indices for each error category, broken down by entity type.
    results = evaluator.evaluate()
    # Access overall strict results
    strict_results = results["overall"]["strict"]
  9. Load CoNLL format data with ConllLoader

    main

    Use ConllLoader to parse Named Entity Recognition (NER) data provided in CoNLL format. The input must be a string where documents are separated by double newlines (\n\n) and tokens within a document are separated by single newlines. Each line in a document must contain at least two parts separated by a tab (\t), where the second part is the BIO-tagged label (e.g., B-PER, I-ORG, or O).

    Error Conditions:

    • Raises ValueError if input is not a string.
    • Raises ValueError if a line does not contain a tab separator.
    • Raises ValueError if a tag does not follow the B- or I- prefix convention (unless it is O).
    from nervaluate.loaders import ConllLoader
    
    loader = ConllLoader()
    conll_data = "token1\tB-PER\ntoken2\tI-PER\n\ntoken3\tO\ntoken4\tB-LOC"
    entities = loader.load(conll_data)
  10. Initialize the Evaluator class

    main

    The Evaluator class is the primary entry point for performing Named Entity Recognition (NER) model evaluation. It compares ground truth entities against predicted entities using various evaluation strategies.

    Initialization Parameters

    • true: The ground truth entities. Supports multiple formats (see Data Loaders).
    • pred: The predicted entities. Supports multiple formats.
    • tags: A List[str] of valid entity tags to be used during evaluation.
    • loader: (Optional) The name of the loader to use. Options: "conll", "list", "dict". If set to "default", the loader is inferred from the input type.
    • min_overlap_percentage: (Optional) A float (1-100) representing the minimum overlap required for a match to be considered a partial match.
    from nervaluate import Evaluator
    
    true_data = [...] # Your ground truth
    pred_data = [...] # Your predictions
    tags = ["PER", "ORG", "LOC"]
    
    evaluator = Evaluator(
        true=true_data,
        pred=pred_data,
        tags=tags,
        loader="default",
        min_overlap_percentage=80.0
    )
  11. Load entity dictionaries with DictLoader

    main

    Use DictLoader to parse NER data provided as a list of documents, where each document is a list of dictionaries representing individual entities. This is the most direct way to load pre-structured entity data.

    Input Format: List[List[Dict[str, Any]]] where each dictionary must contain the following keys:

    • label: (str) The entity type.
    • start: (int) The starting index.
    • end: (int) The ending index.

    Error Conditions:

    • Raises ValueError if input is not a list.
    • Raises ValueError if a document is not a list of dictionaries.
    • Raises ValueError if an entity dictionary is missing label, start, or end.
    • Raises ValueError if label is not a string or if start/end are not integers.
    from nervaluate.loaders import DictLoader
    
    loader = DictLoader()
    dict_data = [[{"label": "PER", "start": 0, "end": 2}, {"label": "LOC", "start": 5, "end": 8}]]
    entities = loader.load(dict_data)