Self-Instruct › Filtering Strategies
Language AI HandbookAll chapters Self-Instruct › Filtering StrategiesOne section · Sections in this chapter7 / 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
Raw generated data contains significant noise: duplicate instructions, low-quality outputs, formatting errors, and examples that are too similar to existing ones. Filtering removes these problematic cases before they contaminate the task pool. Without rigorous filtering, the iterative nature of Self-Instruct would cause errors to compound over time, degrading the quality of each successive generation. Think of the filtering stage as the immune system of the pipeline: it cannot prevent all errors from being generated, but it prevents them from taking hold and multiplying.
The filtering pipeline operates across multiple dimensions simultaneously, each catching a different class of problematic data. ROUGE similarity filtering addresses redundancy. This keeps the dataset grows in new directions rather than repeatedly covering the same ground. Length and format filters address structural quality, catching cases where the model's generation drifted from the expected format. Keyword filters address semantic quality, removing instructions that reference capabilities the model lacks or contexts that cannot be reliably completed without additional information.
A necessary design principle in the filtering architecture is that each filter should be fast and cheap. The entire point of Self-Instruct is to automate data creation at scale; if the filters require expensive model calls, the cost savings evaporate. ROUGE similarity computation is a simple string comparison algorithm. Length checking is a word count. Keyword filtering is substring matching. These cheap filters catch the majority of problematic cases before any expensive model-based evaluation is applied. Reserve expensive filtering for edge cases that heuristics cannot handle.
The composition of filters also matters. Applying them in the right order maximizes efficiency. Keyword filtering should come first because it is the cheapest operation: a simple string check that eliminates instructions referencing images or URLs before any further processing. Length filtering comes next, catching structurally invalid instances before the more expensive ROUGE similarity computation. Similarity filtering comes last because it requires comparing against the entire existing pool, which makes it the most computationally intensive operation. This ordering ensures that the expensive operations see only the candidates that have already passed cheaper checks.
ROUGE-Based Similarity Filtering
The most important filter prevents near-duplicate instructions from entering the pool. New instructions that are too similar to existing ones add no diversity and waste annotation budget. If two instructions are nearly identical, including both provides little additional signal during training. Self-Instruct uses ROUGE-L similarity to measure instruction overlap, comparing the longest common subsequence between instruction texts.
ROUGE-L (Recall-Oriented Understudy for Gisting Evaluation, using Longest Common Subsequence) was originally developed as an automatic evaluation metric for text summarization, but it serves a different purpose here: measuring lexical overlap between instructions to detect near-duplicates. The LCS-based approach is more sensitive to word order and phrase-level matching than bag-of-words alternatives, which makes it effective for catching paraphrases and minor rewrites of existing instructions. An instruction like "Summarize the article in three sentences" scores high ROUGE-L against "Provide a three-sentence summary of the article" because both share most of the same key words in similar order.
The ROUGE-L score between two texts and is computed as an F1 measure that combines precision and recall of the longest common subsequence. Let denote the length of the longest common subsequence between the token sequences of and , denote the number of tokens in , and denote the number of tokens in . The score is:
where:
- : the fraction of 's tokens that participate in the longest common subsequence
- : the fraction of 's tokens that participate in the longest common subsequence
- The F1 combination of the two balances the tendency of precision alone to reward short matches and recall alone to reward long matches
Intuitively, a high ROUGE-L score means the two texts share a long sequence of tokens in the same relative order, suggesting they convey similar content. A score close to 1.0 means the texts are nearly identical. A score close to 0.0 means they share very few tokens in common order.
def compute_rouge_l(reference, candidate):
"""
Compute ROUGE-L score between two texts.
ROUGE-L uses longest common subsequence.
"""
ref_tokens = reference.lower().split()
cand_tokens = candidate.lower().split()
# Compute LCS length using dynamic programming
m, n = len(ref_tokens), len(cand_tokens)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if ref_tokens[i - 1] == cand_tokens[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
lcs_length = dp[m][n]
if lcs_length == 0:
return 0.0
# Compute precision, recall, and F1
precision = lcs_length / n if n > 0 else 0
recall = lcs_length / m if m > 0 else 0
if precision + recall == 0:
return 0.0
f1 = 2 * precision * recall / (precision + recall)
return f1def filter_by_similarity(new_instruction, existing_instructions, threshold=0.7):
"""
Check if a new instruction is too similar to existing ones.
Returns True if the instruction should be kept (is sufficiently novel).
"""
for existing in existing_instructions:
similarity = compute_rouge_l(existing, new_instruction)
if similarity > threshold:
return False
return True# Test similarity filtering
existing = [
"Summarize the given article in three sentences.",
"Translate the text from English to French.",
"Write a poem about nature.",
]
candidates = [
"Summarize the given paragraph in three sentences.", # Too similar
"Write a haiku about the ocean.", # Different enough
"Classify the sentiment of the review.", # Novel
]
filter_results = []
for cand in candidates:
is_novel = filter_by_similarity(cand, existing)
# Find the most similar existing instruction
max_sim = max(compute_rouge_l(e, cand) for e in existing)
filter_results.append((cand, is_novel, max_sim))Similarity filtering results (threshold=0.7): ---------------------------------------------------------------------- [REJECT] (sim=0.86) Summarize the given paragraph in three sentences. [KEEP ] (sim=0.55) Write a haiku about the ocean. [KEEP ] (sim=0.15) Classify the sentiment of the review.
The threshold of 0.7 ROUGE-L similarity balances diversity against strictness. Lower thresholds enforce more diversity but risk rejecting valid variations that might provide useful training signal. Higher thresholds allow more similar instructions, potentially reducing dataset diversity. The choice of 0.7 reflects empirical testing showing it effectively removes near-duplicates while accepting distinct instructions. An instruction that scores 0.71 against an existing task is likely a minor rewrite; one that scores 0.65 is typically different enough to contribute novel information.

Length and Format Filtering
Simple heuristics catch many problematic generations without requiring complex analysis. These filters check basic structural properties that valid instructions should satisfy. A well-formed instruction must be long enough to communicate a meaningful task but short enough to be actionable without becoming a paragraph-length specification. Outputs must exist and be within a reasonable length range. These seemingly obvious constraints catch a surprising number of failures in practice, because language models occasionally generate degenerate outputs: a single word, an incomplete sentence, or a multi-page stream-of-consciousness.
Length filters also enforce indirect quality signals. Instructions shorter than three words are almost certainly too vague to be useful: "Write." or "Explain something." provide no meaningful task specification. Instructions longer than 150 words are typically compound tasks that would be better split into separate, more focused instructions. Outputs longer than 1000 words suggest the model has generated a lengthy text in response to what should have been a shorter task, possibly because the prompt was misinterpreted or the model entered a generation loop.
def apply_length_filters(instruction, input_text, output_text):
"""
Apply length-based quality filters.
Returns (is_valid, reason) tuple.
"""
# Instruction should be meaningful but not excessively long
inst_words = len(instruction.split())
if inst_words < 3:
return False, "Instruction too short"
if inst_words > 150:
return False, "Instruction too long"
# Output should exist and be reasonable length
if not output_text.strip():
return False, "Empty output"
out_words = len(output_text.split())
if out_words > 1000:
return False, "Output too long"
# Input can be empty (for tasks like "Write a poem about...")
# but shouldn't be excessively long
if input_text:
inp_words = len(input_text.split())
if inp_words > 500:
return False, "Input too long"
return True, "Passed"def apply_format_filters(instruction, input_text, output_text):
"""
Filter based on formatting issues.
"""
# Check for incomplete generations (common failure mode)
if output_text.rstrip().endswith("..."):
return False, "Incomplete output"
# Filter obvious repetitions
words = output_text.lower().split()
if len(words) > 10:
unique_ratio = len(set(words)) / len(words)
if unique_ratio < 0.3:
return False, "Too much repetition"
# Check instruction doesn't start with prohibited phrases
prohibited_starts = [
"write a program", # Often leads to code that can't be verified
"create a code",
"as an ai",
"i cannot",
]
inst_lower = instruction.lower()
for phrase in prohibited_starts:
if inst_lower.startswith(phrase):
return False, f"Prohibited start: {phrase}"
return True, "Passed"# Test filtering pipeline
test_instances = [
{
"instruction": "Summarize the article.",
"input": "Long article text here...",
"output": "This article discusses...",
},
{
"instruction": "Hi", # Too short
"input": "",
"output": "Hello",
},
{
"instruction": "Write a poem about rain.",
"input": "",
"output": "the the the the the the the the the the the", # Repetitive
},
{
"instruction": "Explain quantum computing.",
"input": "",
"output": "", # Empty output
},
]
filter_outcomes = []
for inst in test_instances:
length_ok, length_reason = apply_length_filters(
inst["instruction"], inst["input"], inst["output"]
)
format_ok, format_reason = apply_format_filters(
inst["instruction"], inst["input"], inst["output"]
)
if not length_ok:
filter_outcomes.append(
(inst["instruction"][:40], "REJECT", length_reason)
)
elif not format_ok:
filter_outcomes.append(
(inst["instruction"][:40], "REJECT", format_reason)
)
else:
filter_outcomes.append(
(inst["instruction"][:40], "KEEP", "All filters passed")
)Filter results: ---------------------------------------------------------------------- [REJECT] Summarize the article. | Incomplete output [REJECT] Hi | Instruction too short [REJECT] Write a poem about rain. | Too much repetition [REJECT] Explain quantum computing. | Empty output
The filters successfully accept valid instructions while rejecting those that are too short, repetitive, or empty, preventing low-quality data from polluting the pool. These simple checks are computationally cheap but catch a surprising number of problematic cases, making them an efficient first line of defense. The repetition filter in particular catches a common failure mode where models fall into generation loops, repeating the same word or phrase indefinitely. These outputs look valid superficially (they have the right format) but would provide no training signal.
Keyword-Based Filtering
Certain keywords indicate problematic instructions that should be excluded. These include self-references, meta-commentary, and requests for capabilities the model lacks. Keyword filtering acts as a semantic safety net, catching issues that structural filters would miss. An instruction that passes all length and format checks can still be semantically invalid: "Describe the image shown above" has a perfectly reasonable length and format but cannot be completed by a text-only model.
The keyword list reflects the types of instructions that appear reasonable in isolation but become problematic in the context of a text-only instruction-following dataset. References to images and audio, as well as video indicate multimodal tasks that cannot be completed with text alone. URLs and links indicate tasks that require accessing external resources, which the model cannot do at inference time. References to specific model names or previous conversations indicate tasks that assume context not present in the instruction-input-output structure. Filtering all these cases ensures that every instance in the final dataset represents a self-contained, completable task.
BLOCKED_KEYWORDS = [
"image",
"images",
"picture",
"graph",
"figure", # Visual content
"video",
"audio",
"voice", # Non-text modalities
"http",
"www",
"link",
"url", # External resources
"gpt",
"chatgpt",
"openai",
"anthropic", # Self-reference
"previous conversation",
"our earlier", # Context dependency
]
def filter_by_keywords(instruction):
"""
Filter instructions containing problematic keywords.
"""
inst_lower = instruction.lower()
for keyword in BLOCKED_KEYWORDS:
if keyword in inst_lower:
return False, f"Contains blocked keyword: {keyword}"
return True, "Passed"keyword_test_cases = [
"Describe what you see in the image.",
"Summarize the main points of the article.",
"Based on our previous conversation, continue the story.",
"Explain the theory of relativity.",
]
keyword_results = [filter_by_keywords(inst) for inst in keyword_test_cases]Keyword filter results: [REJECT] Describe what you see in the image.... | Contains blocked keyword: image [KEEP ] Summarize the main points of the article.... | Passed [REJECT] Based on our previous conversation, continue the s... | Contains blocked keyword: previous conversation [KEEP ] Explain the theory of relativity.... | Passed
These results demonstrate that the keyword filter effectively catches blocked terms like "image" or context-dependent phrases that would be inappropriate for a standalone instruction dataset. Instructions referencing images cannot be completed by text-only models, and those depending on previous conversation lack the necessary context. By filtering these early, we avoid generating instances that would be useless or misleading during training.
The keyword list requires careful maintenance as the instruction domain expands. A list designed for a general-purpose instruction dataset may need extensions when applying Self-Instruct to specialized domains. A medical instruction dataset might block terms like "examine the X-ray" or "review the MRI scan." A coding instruction dataset might relax the prohibition on code-related terms while adding blocks for instructions that require running external programs. The core principle, blocking instructions that cannot be reliably completed within the constraints of the dataset format, remains constant across domains.
