Information Retrieval · Query Understanding
Synonym Expansion at Index Time Versus Query Time
Why expanding a thesaurus into the index and expanding it into the query produce the same postings but different costs, and how each choice changes scoring and phrase matching.
A user searching laptop should probably also find documents that say notebook, and a user searching physician should find doctor. The thesaurus that encodes those equivalences can be applied on either side of the index boundary: expand each document’s terms as it is indexed, or expand each query’s terms before scoring. The two choices produce overlapping result sets and are not equivalent in cost, in scoring, or in what breaks when the thesaurus changes.
Expanding the document stream
Index-time expansion runs a filter in the analysis chain that emits every term of an equivalence class at the same position as the term that triggered it. A document containing laptop also indexes notebook at that token’s position, with a position increment of zero for the added term.
The first consequence is size. Each trigger term multiplies into $k$ postings where $k$ is the class size, so a document vocabulary with heavy synonymy grows proportionally. The growth is bounded by the average class size rather than by document length, because a document that mentions laptop ten times still triggers the same class on each occurrence. In a domain with dense synonymy, a factor of two in postings is plausible; in a general corpus with a small thesaurus, growth is small enough to ignore.
The second consequence is that the thesaurus is baked into the index. Changing a class requires reindexing every document containing its members, or a full rebuild. For a taxonomy that is stable, this is fine. For one that is edited by domain experts on a rolling basis, it is the dominant operational cost of the design.
The third consequence is asymmetric matching. A query for notebook now matches a document about laptops, because the document’s postings contain notebook. A query for laptop matches a document about notebooks, for the same reason in the other direction. Both directions work without any query-side machinery, which is a genuine advantage: the expansion cannot be forgotten in a code path that forgets to apply it.
The scoring consequence is subtle. The expanded term notebook in a laptop document has a document frequency that includes every document with either word. IDF is computed from the index, so notebook’s IDF falls as the synonymy grows, and the contribution of the class to the score is diluted across its members. The score of a document containing only laptop for the query laptop is therefore lower than it would be without expansion, because the query term’s own IDF was computed over a larger effective count.
Expanding the query
Query-time expansion leaves the index alone. The query is analyzed normally, then a rewrite step replaces each term with a disjunction of its class members, usually with the original term retained. The scorer then evaluates a larger boolean expression per query.
The cost model is inverted: index size is unchanged, and per-query work grows with the class sizes of the terms the query actually contains. A query for a rare term with a large class costs more than a query for a common term with none. In a system serving many short queries, this is usually the cheaper direction, because the query contains few terms and most of the vocabulary is never queried.
The rewrite must decide whether the expansion alternatives are required or optional, and that decision changes ranking. Making them a plain disjunction means a document matching only the synonym is scored as if it matched only the synonym, which may rank it below a document matching the original term once. Weighting the original term higher than its expansion is the common correction; the weight is a parameter with no principled default, and setting it to 1 makes the original term equal to any synonym.
The third option is to use the expansion only as a recall net: run the original query first, and if too few results pass a threshold, rerun with the expansion. That keeps precision on queries with good hits and gives the synonyms a chance only when they are needed. The threshold is a tuning knob and behaves differently for navigational and informational queries, which is why query segmentation and intent matters for this decision.
The same postings, different phrase behaviour
Both approaches index or query multiple terms at one position, and both inherit the phrase-matching consequences of zero position increments described under token positions. The difference is where the alternatives live.
With index-time expansion, the document contains alternatives at each slot, so the phrase query "laptop bag" compiles to a sequence requiring a class member at slot $i$ and bag at slot $i+1$. A document reading notebook bag matches, since notebook and laptop are at the same position. That is usually desired.
With query-time expansion, the phrase must be rewritten into a cross-product of alternatives, and the number of alternatives is the product of class sizes across the phrase. A three-word phrase with three-member classes each produces up to 27 variants. Implementations that expand only single-term queries and leave phrases alone are common, and the resulting behaviour, no synonym matching inside quoted phrases, is a defensible policy rather than a bug, but it must be a policy.
def expand_query(terms, thesaurus, weight_original=2.0):
"""terms: list of (term, position). Returns weighted alternatives per position."""
out = []
for term, pos in terms:
alts = [(term, weight_original)]
for syn in thesaurus.get(term, ()):
if syn != term:
alts.append((syn, 1.0))
out.append((pos, alts))
return out
thesaurus = {"laptop": ["notebook"], "physician": ["doctor", "gp"]}
for pos, alts in expand_query([("laptop", 0), ("bag", 1)], thesaurus):
print(pos, alts)
The structure above preserves positions across the expansion, which is what lets a proximity operator still function. Flattening the output into a bag of terms discards the mapping and is the mistake that makes expanded phrase queries match text that was never adjacent.
Which expansion belongs in a thesaurus at all
The quality of the result depends far more on the class contents than on the timing. Three kinds of relation get mixed together in practice and behave differently.
True synonyms, where substitution never changes truth, are safe. laptop and notebook are close to this in consumer electronics, though notebook also names a paper product and a class of ultraportable with a specific meaning in some markets.
Hierarchical relations, where one term is a kind of the other, are asymmetric. Expanding fruit to apple and orange improves recall for a broad query and destroys precision. The standard safeguard is to expand only downward on the query side and only for queries classified as broad, which again requires intent classification.
Related terms, where two concepts co-occur without being substitutable, are the dangerous class. java and coffee co-occur in text about programming; python and snake likewise. A thesaurus built from co-occurrence statistics will contain these relations, and expanding with them produces results the user did not ask for.
The practical constraint is that a thesaurus for retrieval is not a dictionary of synonyms. It is a set of substitutions that have been verified not to change the answer, and the verification is done by looking at the results of queries before and after. Curated classes with a few members each outperform large automatically mined ones for this reason.
Choosing between them
The timing decision turns on four properties of the deployment.
A thesaurus that changes often favours query-time expansion, because the alternative is reindexing. A corpus with many documents per distinct query favours index-time expansion, since the index cost is paid once and the query cost dominates the serving path. A query workload dominated by phrases favours index-time expansion, which handles phrases with no cross-product blowup. A constrained index size favours query-time expansion, since the thesaurus lives outside the index.
A mixed configuration is also coherent: index the curated, high-confidence classes at index time, where the fixed cost is acceptable and phrases stay intact, and apply the volatile or low-confidence relations at query time behind a recall threshold. That splits the thesaurus by stability rather than by type, which is the property that determines which cost each choice pays.
The measurement that settles the question for a specific system is the same as for any ranking change: run a judged query set against both configurations, inspect the top results per query rather than only the aggregate metric, and check whether the queries whose scores moved are the ones the expansion was meant to help. Aggregate averages over a query set hide exactly the per-query damage that a bad class causes.
References
- Introduction to Information Retrieval, Chapter 9: Query Expansion — Manning, Raghavan & Schütze, Cambridge University Press, 2008
- Lucene analysis package: SynonymGraphFilter — Apache Lucene
- The SMART Retrieval System: Experiments in Automatic Document Processing — Salton (ed.), Prentice-Hall, 1971