BIO Tagging › BIO Utilities in Practice
Language AI HandbookAll chapters BIO Tagging › BIO Utilities in PracticeOne section · Sections in this chapter9 / 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
Let's consolidate our functions into a reusable class and demonstrate a complete end-to-end workflow integrating with spaCy's NER system:
class BIOConverter:
"""Utility class for BIO tagging operations."""
@staticmethod
def spans_to_bio(tokens, spans):
"""Convert span annotations to BIO tags."""
tags = ["O"] * len(tokens)
for start, end, entity_type in sorted(spans, key=lambda x: x[0]):
if 0 <= start < end <= len(tokens):
tags[start] = f"B-{entity_type}"
for i in range(start + 1, end):
tags[i] = f"I-{entity_type}"
return tags
@staticmethod
def bio_to_spans(tokens, tags):
"""Extract entity spans from BIO tags with graceful error handling."""
spans = []
current = None
for i, (token, tag) in enumerate(zip(tokens, tags)):
if tag.startswith("B-"):
if current:
spans.append((*current, i))
current = (i, tag[2:])
elif tag.startswith("I-"):
if current is None or current[1] != tag[2:]:
if current:
spans.append((*current, i))
current = (i, tag[2:])
else:
if current:
spans.append((*current, i))
current = None
if current:
spans.append((*current, len(tokens)))
return [(s, e, t, " ".join(tokens[s:e])) for s, t, e in spans]
@staticmethod
def is_valid(tags):
"""Return True if the BIO sequence contains no tagging errors."""
prev = "O"
for tag in tags:
if tag.startswith("I-"):
if prev == "O":
return False
if prev[0] in "BI" and prev[2:] != tag[2:]:
return False
prev = tag
return True
# Full demonstration: spans -> BIO -> validate -> decode -> verify
demo_tokens = ["Apple", "CEO", "Tim", "Cook", "announced", "iPhone", "15"]
demo_spans = [(0, 1, "ORG"), (2, 4, "PER"), (5, 7, "PRODUCT")]
converter = BIOConverter()
demo_tags = converter.spans_to_bio(demo_tokens, demo_spans)
is_valid = converter.is_valid(demo_tags)
recovered_spans = converter.bio_to_spans(demo_tokens, demo_tags)BIO Converter: End-to-End Demonstration ======================================================= Input spans: [0:1] 'Apple' -> ORG [2:4] 'Tim Cook' -> PER [5:7] 'iPhone 15' -> PRODUCT Generated BIO tags: Apple -> B-ORG CEO -> O Tim -> B-PER Cook -> I-PER announced -> O iPhone -> B-PRODUCT 15 -> I-PRODUCT Sequence valid: True Recovered spans (round-trip check): [0:1] 'Apple' -> ORG [2:4] 'Tim Cook' -> PER [5:7] 'iPhone 15' -> PRODUCT Round-trip fidelity: True
Integration with spaCy
Real NER systems output entity spans that we can convert to BIO format for analysis or evaluation:
import spacy
# spacy.cli.download("en_core_web_sm") # Run once locally, then comment out
nlp = spacy.load("en_core_web_sm")
text = "Microsoft announced that Satya Nadella will visit London next week."
doc = nlp(text)
# Extract tokens and entity spans from spaCy output
spacy_tokens = [token.text for token in doc]
spacy_spans = []
for ent in doc.ents:
# Map character offsets to token indices
start_tok = None
end_tok = None
for i, token in enumerate(doc):
if token.idx == ent.start_char:
start_tok = i
if token.idx + len(token.text) == ent.end_char:
end_tok = i + 1
if start_tok is not None and end_tok is not None:
spacy_spans.append((start_tok, end_tok, ent.label_))
# Convert spaCy output to BIO tags
spacy_bio_tags = BIOConverter.spans_to_bio(spacy_tokens, spacy_spans)spaCy NER Output Converted to BIO: -------------------------------------------------- Text: Microsoft announced that Satya Nadella will visit London next week. Token BIO Tag -------------------------------------------------- Microsoft B-ORG announced O that O Satya B-PERSON Nadella I-PERSON will O visit O London B-GPE next B-DATE week I-DATE . O Entities detected by spaCy: 'Microsoft' -> ORG 'Satya Nadella' -> PERSON 'London' -> GPE 'next week' -> DATE
The BIO representation supports token-level evaluation metrics, comparison across different taggers, and training data preparation for sequence models.
Converting spaCy output to BIO format serves a broader purpose: interoperability. Different NLP tools use different internal representations for entity annotations. spaCy uses character-offset spans with Python objects. Stanford NER uses whitespace-delimited CoNLL-style BIO text files. Hugging Face models produce token-level logits that require a decoding step. A shared BIO format provides a common currency for comparing outputs, combining predictions from multiple systems, and routing data through evaluation pipelines regardless of which library produced it.