Clarify a BERT Keyphrase Span-Extraction Task
FigmaMachine Learning Engineer · Technical ScreenLast updated
Implement and clarify the post-processing decoder for a BERT-based span-extraction model that maps token-level start and end logits back to non-overlapping character-level keyphrases.
Scenario
In collaborative tools like FigJam, users write unstructured thoughts across sticky notes and comments. You are building an ML pipeline that extracts keyphrase spans from user notes using a BERT pointer model (similar to SQuAD-style span extraction). The model produces unnormalized start and end logits for each token.
Your task is to implement the post-processing span decoder that converts raw token logits and Hugging Face-style offset_mapping into clean, non-overlapping keyphrase text spans in the original string.
Interface
def extract_keyphrases(
raw_text: str,
token_offsets: list[tuple[int, int]],
start_logits: list[float],
end_logits: list[float],
max_span_length: int = 5,
top_k: int = 3,
min_score: float = 0.0
) -> list[dict]:
"""
Returns a list of up to `top_k` extracted keyphrases sorted by score descending.
"""
pass
Requirements and Rules
- Special Tokens: Any token whose offset is
(0, 0)represents a special token (e.g.,[CLS],[SEP], or padding) and cannot begin or end a valid span. - Span Validity: A candidate span is defined by token index pair
(i, j)wherei = min_scoreare considered. - Character Span: The raw text span begins at
char_start = token_offsets[i][0]and ends atchar_end = token_offsets[j][1]. The extracted text israw_text[char_start:char_end]. - Deduplication / Non-overlap: Candidate spans must not overlap in character offsets. Use a greedy approach: process candidate spans sorted by
scoredescending, keeping a span only if its character range[char_start, char_end)does not overlap with any previously accepted span. - Return Format: List of dictionaries:
[{"text": str, "char_start": int, "char_end": int, "score": float}, ...]truncated to at mosttop_kitems.
Example
raw_text = "Design systems scale UI components."
token_offsets = [(0, 0), (0, 6), (7, 14), (15, 20), (21, 23), (24, 34), (34, 35), (0, 0)]
# Tokens: [CLS], Design, systems, scale, UI, components, ., [SEP]
start_logits = [0.0, 3.5, 0.2, 0.1, 2.8, 0.4, -1.0, 0.0]
end_logits = [0.0, 0.1, 3.2, 0.0, 0.5, 3.0, -1.0, 0.0]
result = extract_keyphrases(
raw_text, token_offsets, start_logits, end_logits, max_span_length=3, top_k=2
)
# Output:
# [
# {"text": "Design systems", "char_start": 0, "char_end": 14, "score": 6.7},
# {"text": "UI components", "char_start": 21, "char_end": 34, "score": 5.8}
# ]
Constraints
len(token_offsets) == len(start_logits) == len(end_logits) = 1
Hints
Hint 1
Identify valid token indices first by excluding indices where offset_mapping is (0, 0) before enumerating (start, end) pairs.
Hint 2
Generate all candidate spans satisfying i <= j and j - i + 1 <= max_span_length, then filter by min_score and sort them descending by score.
Hint 3
To enforce non-overlapping spans greedily, track accepted intervals [char_start, char_end) and check for intersection: max(s1, s2) < min(e1, e2).
Follow-up questions
- How would you handle documents longer than BERT's 512-token limit using a sliding window with stride, and how should overlapping window logits be aggregated?
- How does subword tokenization (e.g., WordPiece tokenizing 'unbelievable' into 'un', '##believ', '##able') affect valid span boundaries if you want to prevent partial word selections?
- What are the latency and precision trade-offs between pointer-network span extraction versus sequence-labeling (BIO tagging) for multi-keyphrase extraction?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.