Parallel corpora are often assembled from documents whose sentences are translated but not aligned by row. Sentence Transformers can place both languages in a shared embedding space, making likely translation pairs recoverable without relying on position or exact wording.
The compact CPU example uses the multilingual MiniLM model because its broad language coverage keeps a first run manageable. The official bitext-mining guidance recommends LaBSE when mining quality matters more than model size and runtime.
Ratio-margin scoring compares a candidate's cosine similarity with the average similarity of its nearby alternatives in both directions. Mutual-best filtering keeps a pair only when each sentence chooses the other, so the duplicated Spanish translation and the unrelated sentence cannot create extra output rows.
Steps to run translated sentence mining with Sentence Transformers:
- Create the bilingual input section in translated_sentence_mining.py inside a Python environment with Sentence Transformers installed.
- translated_sentence_mining.py
from pathlib import Path from sentence_transformers import SentenceTransformer, util source_sentences = [ "Reset the customer password.", "Renew the TLS certificate.", "Export the customer invoices.", "Deploy the billing service.", "Enable multi-factor authentication for administrators.", ] target_sentences = [ "Exporta las facturas de los clientes.", "Despliega el servicio de facturación.", "Restablece la contraseña del cliente.", "Renueva el certificado TLS.", "Habilita la autenticación multifactor para administradores.", "Restablece la contraseña del cliente.", "Programa una reunión de ventas.", ]
The target list is shuffled, repeats one translation, and includes one unrelated sentence, so row position or a one-way match cannot produce the accepted pairs.
Related: How to install Sentence Transformers with pip - Add normalized multilingual embeddings below the sentence lists.
model = SentenceTransformer( "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2", device="cpu", ) source_embeddings = model.encode( source_sentences, convert_to_tensor=True, normalize_embeddings=True, show_progress_bar=False, ) target_embeddings = model.encode( target_sentences, convert_to_tensor=True, normalize_embeddings=True, show_progress_bar=False, )
- Add bidirectional nearest-neighbor searches below the embeddings.
neighbors = 4 source_hits = util.semantic_search( source_embeddings, target_embeddings, top_k=neighbors, score_function=util.dot_score, ) target_hits = util.semantic_search( target_embeddings, source_embeddings, top_k=neighbors, score_function=util.dot_score, )
Four neighbors provide the local comparison used by the margin score. Larger corpora commonly use between 4 and 16 neighbors, with more neighbors increasing search work.
- Add ratio-margin scoring with reciprocal best-neighbor selection below the searches.
source_means = [ sum(hit["score"] for hit in hits) / len(hits) for hits in source_hits ] target_means = [ sum(hit["score"] for hit in hits) / len(hits) for hits in target_hits ] source_to_target_best = { source_id: hits[0]["corpus_id"] for source_id, hits in enumerate(source_hits) } target_to_source_best = { target_id: hits[0]["corpus_id"] for target_id, hits in enumerate(target_hits) } candidates = [] for source_id, target_id in source_to_target_best.items(): if target_to_source_best[target_id] != source_id: continue cosine = source_hits[source_id][0]["score"] neighborhood_mean = ( source_means[source_id] + target_means[target_id] ) / 2 margin = cosine / neighborhood_mean candidates.append((margin, cosine, source_id, target_id)) candidates.sort(reverse=True)
The two mappings make reciprocity explicit: a source-target pair survives only when both searches rank the other sentence first.
- Add threshold filtering with one-to-one assertions below the candidate sort.
minimum_margin = 1.25 accepted = [ candidate for candidate in candidates if candidate[0] >= minimum_margin ] accepted_source_ids = [candidate[2] for candidate in accepted] accepted_target_ids = [candidate[3] for candidate in accepted] assert len(accepted) == len(source_sentences) assert len(set(accepted_source_ids)) == len(accepted) assert len(set(accepted_target_ids)) == len(accepted) print(f"Verified {len(accepted)} reciprocal one-to-one pairs")
The repeated Spanish password sentence would make a target-to-source-only implementation accept six rows and fail these assertions.
- Add TSV export below the assertions.
output_path = Path("mined-translations.tsv") with output_path.open("w", encoding="utf-8") as output: output.write("margin\tcosine\tsource\ttarget\n") for margin, cosine, source_id, target_id in accepted: output.write( f"{margin:.3f}\t{cosine:.3f}\t" f"{source_sentences[source_id]}\t" f"{target_sentences[target_id]}\n" ) print(f"Wrote {len(accepted)} pairs to {output_path}")
A margin of 1.25 is a starting point, not a universal acceptance rule. Production cutoffs should come from labeled or manually reviewed pairs for the real language pair and domain.
- Run the translated sentence mining script to exercise the reciprocal-pair assertions.
$ python translated_sentence_mining.py Verified 5 reciprocal one-to-one pairs Wrote 5 pairs to mined-translations.tsv
The first model load downloads files from Hugging Face before the result appears.
- Verify mined-translations.tsv contains only the five expected bilingual pairs from the shuffled inputs.
$ cat mined-translations.tsv margin cosine source target 2.112 0.943 Renew the TLS certificate. Renueva el certificado TLS. 1.859 0.876 Enable multi-factor authentication for administrators. Habilita la autenticación multifactor para administradores. 1.676 0.805 Export the customer invoices. Exporta las facturas de los clientes. 1.621 0.831 Deploy the billing service. Despliega el servicio de facturación. 1.614 0.869 Reset the customer password. Restablece la contraseña del cliente.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.