用PostgreSQL+pgvector构建PDF翻译记忆库:向量相似度检索+增量更新实战

前言

翻译记忆库(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翻译记忆库有几个优势:

  1. 语义级匹配:基于sentence-transformers的向量编码,能找到"意思相近但字面不同"的历史翻译
  2. 增量更新:每次翻译自动写入记忆,库越用越智能
  3. 一站式部署:不需要额外引入Elasticsearch或Milvus,PostgreSQL单库搞定
  4. HNSW索引:10万级记忆条目下,检索延迟在50ms以内

标签:PostgreSQL, pgvector, PDF翻译, 向量检索, 翻译记忆库

相关推荐
金融小师妹1 小时前
多因子市场推演:油价、英伟达与杰克逊霍尔如何影响资产定价的AI事件预测框架
人工智能·python·深度学习
大海变好AI1 小时前
AI 工作流怎么搭建提升效率
大数据·人工智能·python
叫我:松哥1 小时前
基于flask图书管理系统,技术栈flask+layui+sqlite+Echarts
后端·python·数据分析·flask·echarts·layui
落魄大学生之流水线上谋生计1 小时前
Python Literal[] 类型提示详解
开发语言·python
nanawinona1 小时前
2026年下半年按能力基础选量化工具
人工智能·python
赵民勇1 小时前
Python泛型使用技巧
python
知了一笑1 小时前
项目管理,被AI困在2026年
人工智能·ai·项目管理
裕晟资质规划1 小时前
首次办理军工保密资质:咨询服务选择要点与全流程交付清单(评估框架)
大数据·运维·服务器·网络·数据库·经验分享
jyOverQ1 小时前
LangGraph:子图动态路由与 Agent 工作流设计
python·langchain