前言
翻译记忆库(Translation Memory, TM)是翻译行业的核心基础设施。传统的TM系统基于精确匹配或模糊字符串匹配(如Trados的匹配度算法),但对于PDF翻译场景,我们更需要语义级别的相似度检索------两段文本表达的意思相近就算"命中",不要求字面完全一致。本文分享如何用PostgreSQL+pgvector扩展构建一个面向PDF翻译的语义记忆库,支持向量相似度检索和增量更新。
环境准备
| 组件 | 版本 | 用途 |
|---|---|---|
| PostgreSQL | >=15 | 数据库 |
| pgvector | >=0.5 | 向量类型和索引 |
| Python | >=3.10 | 运行脚本 |
| sentence-transformers | >=2.2 | 文本向量化 |
| psycopg2-binary | >=2.9 | PG驱动 |
| PyPDF2 | >=3.0 | PDF文本提取 |
bash
# 安装pgvector扩展(以Ubuntu为例)
sudo apt install postgresql-15-pgvector
# Python依赖
pip install sentence-transformers psycopg2-binary PyPDF2
数据库设计
Step 1: 创建表结构
sql
-- 启用pgvector扩展
CREATE EXTENSION IF NOT EXISTS vector;
-- 翻译记忆库主表
CREATE TABLE IF NOT EXISTS translation_memory (
id BIGSERIAL PRIMARY KEY,
source_text TEXT NOT NULL, -- 源文本
translated_text TEXT NOT NULL, -- 翻译文本
source_lang VARCHAR(10) NOT NULL, -- 源语言
target_lang VARCHAR(10) NOT NULL, -- 目标语言
source_file VARCHAR(500), -- 来源文件名
page_number INT, -- 页码
embedding VECTOR(384), -- 384维向量(all-MiniLM-L6-v2)
char_count INT, -- 字符数(用于过滤)
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 向量索引(HNSW算法,适合语义检索)
CREATE INDEX IF NOT EXISTS idx_tm_embedding
ON translation_memory
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 语言对+字符数组合索引(加速预过滤)
CREATE INDEX IF NOT EXISTS idx_tm_lang_chars
ON translation_memory (source_lang, target_lang, char_count);
-- 文本哈希索引(防止重复插入)
CREATE INDEX IF NOT EXISTS idx_tm_hash
ON translation_memory (md5(source_text));
Step 2: Python端数据库连接
python
import psycopg2
from psycopg2.extras import execute_values
from sentence_transformers import SentenceTransformer
import hashlib
class TranslationMemoryDB:
"""翻译记忆库数据库操作"""
def __init__(self, dsn: str = "dbname=pdf_tm user=postgres"):
self.conn = psycopg2.connect(dsn)
self.encoder = SentenceTransformer("all-MiniLM-L6-v2")
def _hash_text(self, text: str) -> str:
"""计算文本哈希用于去重"""
return hashlib.md5(text.encode()).hexdigest()
def insert_segment(
self,
source_text: str,
translated_text: str,
source_lang: str,
target_lang: str,
source_file: str = "",
page_number: int = 0,
) -> int:
"""插入一条翻译记忆(自动去重)"""
text_hash = self._hash_text(source_text)
# 检查是否已存在
with self.conn.cursor() as cur:
cur.execute(
"SELECT id FROM translation_memory WHERE md5(source_text) = %s",
(text_hash,),
)
existing = cur.fetchone()
if existing:
# 已存在则更新翻译
cur.execute(
"""UPDATE translation_memory
SET translated_text = %s, updated_at = NOW()
WHERE id = %s""",
(translated_text, existing[0]),
)
self.conn.commit()
return existing[0]
# 新增:计算向量并插入
embedding = self.encoder.encode(source_text).tolist()
cur.execute(
"""INSERT INTO translation_memory
(source_text, translated_text, source_lang, target_lang,
source_file, page_number, embedding, char_count)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
RETURNING id""",
(source_text, translated_text, source_lang, target_lang,
source_file, page_number, str(embedding), len(source_text)),
)
row_id = cur.fetchone()[0]
self.conn.commit()
return row_id
实现步骤
Step 3: 向量相似度检索
核心功能------给定一段新文本,从记忆库中找到语义最相似的翻译记录:
python
class TranslationMemoryDB:
# ... 前面的__init__和insert_segment
def search_similar(
self,
query_text: str,
source_lang: str,
target_lang: str,
top_k: int = 5,
similarity_threshold: float = 0.75,
) -> list:
"""向量相似度检索
Args:
query_text: 待查询的源文本
source_lang: 源语言
target_lang: 目标语言
top_k: 返回条数
similarity_threshold: 相似度阈值(0-1)
Returns:
[(source_text, translated_text, similarity), ...]
"""
query_vec = self.encoder.encode(query_text).tolist()
query_len = len(query_text)
# 预过滤:语言对匹配 + 字符数±50%范围
min_chars = int(query_len * 0.5)
max_chars = int(query_len * 1.5)
sql = """
SELECT source_text, translated_text,
1 - (embedding <=> %s::vector) AS similarity
FROM translation_memory
WHERE source_lang = %s
AND target_lang = %s
AND char_count BETWEEN %s AND %s
ORDER BY embedding <=> %s::vector
LIMIT %s
"""
with self.conn.cursor() as cur:
cur.execute(sql, (
str(query_vec), source_lang, target_lang,
min_chars, max_chars,
str(query_vec), top_k
))
results = []
for row in cur.fetchall():
if row[2] >= similarity_threshold:
results.append({
"source": row[0],
"translation": row[1],
"similarity": round(row[2], 4),
})
return results
Step 4: PDF翻译记忆增量更新
从翻译后的PDF中提取段落对,批量写入记忆库:
python
from PyPDF2 import PdfReader
import re
def extract_segments_from_pdf(pdf_path: str, translated_pdf_path: str):
"""从原文PDF和译文PDF中提取段落对
Returns:
[(source_para, translated_para, page_num), ...]
"""
source_reader = PdfReader(pdf_path)
translated_reader = PdfReader(translated_pdf_path)
segments = []
for i, (src_page, tgt_page) in enumerate(
zip(source_reader.pages, translated_reader.pages)
):
src_text = src_page.extract_text() or ""
tgt_text = tgt_page.extract_text() or ""
# 按段落分割(双换行或句号结尾)
src_paras = [p.strip() for p in re.split(r'\n{2,}', src_text) if len(p.strip()) > 20]
tgt_paras = [p.strip() for p in re.split(r'\n{2,}', tgt_text) if len(p.strip()) > 20]
# 按顺序配对(假设段落顺序一致)
for src, tgt in zip(src_paras, tgt_paras):
segments.append((src, tgt, i + 1))
return segments
def batch_insert_from_pdf(tm_db: TranslationMemoryDB,
source_pdf: str, translated_pdf: str,
source_lang: str, target_lang: str):
"""从PDF翻译对中批量写入翻译记忆"""
segments = extract_segments_from_pdf(source_pdf, translated_pdf)
inserted = 0
updated = 0
for src, tgt, page in segments:
row_id = tm_db.insert_segment(
source_text=src,
translated_text=tgt,
source_lang=source_lang,
target_lang=target_lang,
source_file=source_pdf,
page_number=page,
)
# 判断是新增还是更新(简化判断)
inserted += 1
return {"total": len(segments), "inserted": inserted}
Step 5: 使用翻译记忆辅助新翻译
实际使用场景:翻译新PDF前,先查记忆库,命中的段落直接复用:
python
def translate_with_memory(
tm_db: TranslationMemoryDB,
text_segments: list,
source_lang: str,
target_lang: str,
translate_func=None, # 外部传入的翻译函数
):
"""结合翻译记忆进行翻译
Args:
tm_db: 翻译记忆库实例
text_segments: 待翻译文本段落列表
source_lang/target_lang: 语言对
translate_func: 未命中时的翻译回调
Returns:
[(original, translation, source, similarity), ...]
"""
results = []
tm_hits = 0
for segment in text_segments:
# 先查记忆库
matches = tm_db.search_similar(
segment, source_lang, target_lang,
top_k=1, similarity_threshold=0.85
)
if matches:
# 命中翻译记忆
best = matches[0]
results.append((
segment, best["translation"],
"TM", best["similarity"]
))
tm_hits += 1
elif translate_func:
# 未命中,调用翻译API
translation = translate_func(segment)
results.append((segment, translation, "API", 0))
# 翻译完成后写入记忆库
tm_db.insert_segment(
segment, translation,
source_lang, target_lang
)
else:
results.append((segment, "", "SKIPPED", 0))
hit_rate = tm_hits / len(text_segments) if text_segments else 0
print(f"翻译记忆命中率: {tm_hits}/{len(text_segments)} = {hit_rate:.1%}")
return results
运行效果
python
# 初始化
tm = TranslationMemoryDB("dbname=pdf_tm user=postgres")
# 从已有翻译对中写入记忆
batch_insert_from_pdf(
tm, "report_en.pdf", "report_zh.pdf",
source_lang="en", target_lang="zh"
)
# 输出: {"total": 142, "inserted": 142}
# 翻译新PDF时使用记忆
segments = ["Revenue grew 15% YoY to $2.3B", ...]
results = translate_with_memory(tm, segments, "en", "zh")
# 输出: 翻译记忆命中率: 38/142 = 26.8%
性能优化
HNSW索引参数调优
sql
-- m=16, ef_construction=64 适合10万级数据
-- m=32, ef_construction=128 适合百万级
CREATE INDEX idx_tm_embedding
ON translation_memory
USING hnsw (embedding vector_cosine_ops)
WITH (m = 32, ef_construction = 128);
-- 查询时提高ef_search提升召回率
SET hnsw.ef_search = 100;
预过滤策略
不要对全库做向量检索,先用传统索引缩小范围:
sql
-- 先用语言对+字符数过滤到几百条
-- 再在这几百条里做向量排序
-- 比直接全库HNSW检索快3-5倍
总结
PostgreSQL+pgvector方案构建PDF翻译记忆库有几个优势:
- 语义级匹配:基于sentence-transformers的向量编码,能找到"意思相近但字面不同"的历史翻译
- 增量更新:每次翻译自动写入记忆,库越用越智能
- 一站式部署:不需要额外引入Elasticsearch或Milvus,PostgreSQL单库搞定
- HNSW索引:10万级记忆条目下,检索延迟在50ms以内
标签:PostgreSQL, pgvector, PDF翻译, 向量检索, 翻译记忆库