Dual-head transformer model that jointly predicts start/end span logits from sequence output and answer type from pooled CLS output.
Scanned 9/12/2026
Install to Claude Code
npx -y skills add wenmin-wu/ds-skills --skill joint-span-answer-type-head --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Joint Span Answer Type Head?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/wenmin-wu-joint-span-answer-type-head)More formats (shields.io, HTML) on the badges page.
---
name: nlp-joint-span-answer-type-head
description: >
Dual-head transformer model that jointly predicts start/end span logits from sequence output and answer type from pooled CLS output.
---
# Joint Span + Answer Type Head
## Overview
Open-domain QA requires both extracting an answer span and classifying the answer type (short answer, long answer, yes/no, or unanswerable). A joint model shares the transformer backbone and adds two heads: (1) a token-level Dense(2) split into start/end logits for span extraction, and (2) a Dense(N) on the CLS pooled output for answer type classification. Joint training improves both tasks through shared representations.
## Quick Start
```python
import torch
import torch.nn as nn
from transformers import AutoModel
class JointQAModel(nn.Module):
def __init__(self, model_name, n_answer_types=5):
super().__init__()
self.backbone = AutoModel.from_pretrained(model_name)
hidden = self.backbone.config.hidden_size
self.span_head = nn.Linear(hidden, 2) # start + end logits
self.type_head = nn.Linear(hidden, n_answer_types)
def forward(self, input_ids, attention_mask):
outputs = self.backbone(input_ids, attention_mask=attention_mask)
sequence_output = outputs.last_hidden_state # (B, seq_len, H)
pooled_output = outputs.last_hidden_state[:, 0] # CLS token
span_logits = self.span_head(sequence_output) # (B, seq_len, 2)
start_logits, end_logits = span_logits.split(1, dim=-1)
start_logits = start_logits.squeeze(-1) # (B, seq_len)
end_logits = end_logits.squeeze(-1)
type_logits = self.type_head(pooled_output) # (B, n_types)
return start_logits, end_logits, type_logits
```
## Workflow
1. Pass input through shared transformer backbone
2. Apply Dense(2) to sequence output, split into start/end logits
3. Apply Dense(N) to CLS pooled output for answer type
4. Train with combined loss: span CE + type CE (weighted)
5. At inference, use type prediction to decide whether to emit a span
## Key Decisions
- **Answer types**: Typically 5 classes — UNKNOWN, YES, NO, SHORT, LONG
- **Loss weighting**: Equal weights work; tune if one task dominates
- **Pooled output**: Use CLS token or mean pooling for type head
- **Span constraints**: Enforce start < end and max span length at decode time
## References
- [BERT Joint Baseline Notebook](https://www.kaggle.com/code/prokaj/bert-joint-baseline-notebook)
- [TensorFlow 2.0 - Bert YES/NO Answers](https://www.kaggle.com/code/mmmarchetti/tensorflow-2-0-bert-yes-no-answers)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!