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:

  1. 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

  2. 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,
    )
  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.