BIO Tagging › Tag Consistency and Validation

Michael BrenndoerferUpdated January 3, 20266 min read
Language AI HandbookAll chapters
Part 6 of 67Sequence Labeling
Chapter 38BIO Tagging › Tag Consistency and Validation
Reading section 6 of 14 · Tag Consistency and Validation · 6 minSection 6 of 14 · 6 min

Book contents

Section 6 of 14 · 6 min read

BIO Tagging › Tag Consistency and ValidationOne section · Sections in this chapter6 / 14 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Real-world tagging systems produce inconsistent output. A well-designed pipeline includes validation to detect problems and, where possible, repair them automatically.

Validation matters at two distinct pipeline stages. Before training, you validate annotation data to catch inconsistencies introduced during the labeling process. Annotators may disagree on boundaries, tools may produce off-by-one errors, and automatic pre-annotation may generate malformed sequences that human annotators did not catch. Feeding invalid sequences to a sequence model during training can produce subtle bugs: the model might learn spurious patterns from incorrectly tagged tokens, or the loss computation might silently ignore malformed positions.

After inference, you validate model output to ensure it can be parsed correctly downstream. Even a model trained entirely on valid sequences may occasionally predict invalid ones, particularly on out-of-distribution inputs where the model is uncertain. The application reading NER output may crash or produce wrong results if it assumes valid sequences and receives invalid ones.

In[27]:
Code
def validate_bio_sequence(tags):
    """
    Validate a BIO tag sequence and report all errors.

    Returns list of (position, error_type, description) tuples.
    """
    errors = []
    prev_tag = "O"

    for i, tag in enumerate(tags):
        if tag == "O":
            prev_tag = tag
            continue

        if not (tag.startswith("B-") or tag.startswith("I-")):
            errors.append((i, "INVALID_TAG", f"Unrecognized tag: {tag}"))
            continue

        prefix = tag[0]
        entity_type = tag[2:] if len(tag) > 2 else ""

        if not entity_type:
            errors.append(
                (i, "MISSING_TYPE", f"Tag missing entity type: {tag}")
            )

        if prefix == "I":
            if prev_tag == "O":
                errors.append(
                    (i, "ORPHAN_I", f"I tag without preceding B: {tag}")
                )
            elif prev_tag.startswith("B-") or prev_tag.startswith("I-"):
                prev_type = prev_tag[2:]
                if prev_type != entity_type:
                    errors.append(
                        (
                            i,
                            "TYPE_MISMATCH",
                            f"I-{entity_type} follows {prev_tag}",
                        )
                    )

        prev_tag = tag

    return errors


# Test with a problematic sequence
problematic_tags = ["O", "I-PER", "I-PER", "B-LOC", "I-ORG", "O", "B-PER", "O"]

validation_errors = validate_bio_sequence(problematic_tags)
Out[28]:
Console
Sequence Validation:
-------------------------------------------------------
Tags: ['O', 'I-PER', 'I-PER', 'B-LOC', 'I-ORG', 'O', 'B-PER', 'O']

Found 2 error(s):
  Position 1: [ORPHAN_I] I tag without preceding B: I-PER
  Position 4: [TYPE_MISMATCH] I-ORG follows B-LOC

Once errors are identified, we can apply heuristic repairs:

In[29]:
Code
def repair_bio_sequence(tags):
    """
    Repair common BIO sequence errors.

    Strategies applied:
    - Convert orphan I tags to B tags
    - Resolve type mismatches by starting new entities

    Returns (repaired_tags, repair_log).
    """
    repaired = tags.copy()
    repairs = []
    prev_tag = "O"

    for i, tag in enumerate(repaired):
        if tag == "O":
            prev_tag = tag
            continue

        if tag.startswith("I-"):
            entity_type = tag[2:]

            if prev_tag == "O":
                # Orphan I: convert to B
                repaired[i] = f"B-{entity_type}"
                repairs.append((i, tag, repaired[i], "orphan I -> B"))

            elif prev_tag[0] in "BI" and prev_tag[2:] != entity_type:
                # Type mismatch: start new entity
                repaired[i] = f"B-{entity_type}"
                repairs.append((i, tag, repaired[i], "type mismatch -> new B"))

        prev_tag = repaired[i]

    return repaired, repairs


repaired_tags, repair_log = repair_bio_sequence(problematic_tags)
Out[30]:
Console
Sequence Repair:
-------------------------------------------------------
Original: ['O', 'I-PER', 'I-PER', 'B-LOC', 'I-ORG', 'O', 'B-PER', 'O']
Repaired: ['O', 'B-PER', 'I-PER', 'B-LOC', 'B-ORG', 'O', 'B-PER', 'O']

Applied 2 repair(s):
  Position 1: I-PER -> B-PER (orphan I -> B)
  Position 4: I-ORG -> B-ORG (type mismatch -> new B)

The repair function converts orphan I tags into B tags and creates new entity boundaries at type mismatches. These are the two most common patterns in model output, where the model may predict the correct type but miss a boundary.

Out[31]:
Visualization
Colored tag boxes for the original sequence showing errors at positions 1 and 4.
Original BIO tag sequence containing two errors: an orphan I-PER at position 1 (no preceding B tag) and an I-ORG type mismatch at position 4 (follows a B-LOC). Both positions receive standard dark borders.
Colored tag boxes for the repaired sequence with orange-bordered tags at changed positions.
Repaired BIO sequence after applying heuristic fixes. Positions 1 and 4 (highlighted with orange borders) were converted from invalid I tags to B tags, creating clean entity boundaries.

The repair visualization highlights positions that changed (shown with orange borders). Position 1 was an orphan I-PER that became B-PER. Position 4 was a type mismatch I-ORG that became B-ORG. Both repairs create clean entity boundaries that the decoder can now process correctly.

It is important to log all repairs rather than applying them silently. When you repair sequences during training data preparation, the log tells you whether your annotation pipeline has systematic problems. If 20% of sequences require repairs, something is wrong with your annotation tool or guidelines. If only 0.1% require repairs, you have an isolated edge case. The repair log is also a diagnostic tool during model development: when a model's output requires many repairs at inference time, it signals the model is confused about boundary detection and may need targeted fine-tuning on boundary examples.