PQ (Product Quantization)

188 阅读9分钟

在这里插入图片描述

📋 目录

  1. PQ概述
  2. 训练阶段:构建码本
  3. 编码阶段:量化向量
  4. 查询阶段:快速检索
  5. 完整示例
  6. 核心优势

PQ概述

基本思想

将高维向量分段,每段独立量化,用"中心点编号"代替原始向量,实现压缩存储和快速距离计算。

参数设置(示例)

  • 向量维度:512维
  • 分段数 (m):8段
  • 每段维度:512 ÷ 8 = 64维
  • 每段聚类中心数 (k):256个
  • 编码长度:8字节(每段1字节)

训练阶段:构建码本

目标

为每一段训练出256个"标准中心点",形成码本。

详细步骤

1. 准备训练数据

假设有 100万个训练向量(512维)

向量1:     [0.1, 0.3, ..., 0.5 | 0.2, 0.7, ..., 0.4 | ... | 0.6, 0.1, ..., 0.9]
向量2:     [0.2, 0.4, ..., 0.6 | 0.3, 0.8, ..., 0.5 | ... | 0.7, 0.2, ..., 0.8]
...
向量100万: [0.5, 0.1, ..., 0.3 | 0.9, 0.2, ..., 0.7 | ... | 0.4, 0.6, ..., 0.2]
           └─── 段1(64维) ───┘ └─── 段2(64维) ───┘       └─── 段8(64维) ───┘

2. 切分所有向量

将每个向量切分成8段:

1的所有子向量(100万个64维向量):
  [0.1, 0.3, ..., 0.5]  ← 来自向量1
  [0.2, 0.4, ..., 0.6]  ← 来自向量2
  ...
  [0.5, 0.1, ..., 0.3]  ← 来自向量100万

段2的所有子向量(100万个64维向量):
  [0.2, 0.7, ..., 0.4]
  [0.3, 0.8, ..., 0.5]
  ...

... 共8组子向量集合

3. 对每段独立聚类

对段1聚类(K-means, k=256):

输入:100万个64维子向量
输出:256个64维中心点

段1码本:
  中心点0:   [0.15, 0.32, 0.41, ..., 0.51]
  中心点1:   [0.21, 0.38, 0.29, ..., 0.47]
  ...
  中心点23:  [0.18, 0.25, 0.33, ..., 0.48]
  ...
  中心点255: [0.48, 0.12, 0.35, ..., 0.29]

对段2聚类

输入:100万个64维子向量
输出:256个64维中心点

段2码本:
  中心点0:   [0.22, 0.71, 0.55, ..., 0.39]
  ...
  中心点156: [0.19, 0.73, 0.52, ..., 0.37]
  ...

...依此类推,对8段都做聚类

4. 最终码本结构(矩阵展示)

码本 (Codebook) - 矩阵形式

                      段1码本      段2码本      段3码本      段4码本      段5码本      段6码本      段7码本      段8码本
                     (64维)       (64维)       (64维)       (64维)       (64维)       (64维)       (64维)       (64维)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
中心点0         [0.15,...]   [0.22,...]   [0.18,...]   [0.27,...]   [0.31,...]   [0.24,...]   [0.33,...]   [0.29,...]
中心点1         [0.21,...]   [0.31,...]   [0.29,...]   [0.35,...]   [0.19,...]   [0.42,...]   [0.26,...]   [0.38,...]
中心点2         [0.17,...]   [0.28,...]   [0.35,...]   [0.19,...]   [0.26,...]   [0.38,...]   [0.41,...]   [0.21,...]
...             ...          ...          ...          ...          ...          ...          ...          ...
中心点23        [0.18,...]   [0.25,...]   [0.33,...]   [0.22,...]   [0.28,...]   [0.36,...]   [0.31,...]   [0.24,...]
...             ...          ...          ...          ...          ...          ...          ...          ...
中心点156       [0.20,...]   [0.19,...]   [0.27,...]   [0.31,...]   [0.23,...]   [0.29,...]   [0.35,...]   [0.26,...]
...             ...          ...          ...          ...          ...          ...          ...          ...
中心点255       [0.48,...]   [0.19,...]   [0.41,...]   [0.38,...]   [0.45,...]   [0.29,...]   [0.37,...]   [0.42,...]

总计:256个中心点(纵向) × 8段(横向) = 2048个中心点向量
存储大小:256 × 8 × 64 × 4字节 ≈ 512KB

关键理解

码本 = 集合了所有段的、每一段的所有中心点

  • 横向:8个段(段1到段8)
  • 纵向:256个中心点(中心点0到255)
  • 每个交叉点是一个64维向量

编码阶段:量化向量

目标

将数据库中的每个原始向量用"中心点编号"表示。

详细步骤

1. 切分原始向量

原始向量V (512维):
[0.1, 0.3, ..., 0.5] | [0.2, 0.7, ..., 0.4] | ... | [0.6, 0.1, ..., 0.9]
└─── V段1(64维) ───┘   └─── V段2(64维) ───┘       └─── V段8(64维) ───┘

2. 每段找最近中心点

段1编码

V段1 = [0.1, 0.3, ..., 0.5]

在段1码本中查找最近的中心点:
  距离(V段1, 段1中心点0)   = 2.31
  距离(V段1, 段1中心点1)   = 1.85
  ...
  距离(V段1, 段1中心点23)  = 0.52  ← 最小!
  ...
  距离(V段1, 段1中心点255) = 3.14

编码为: 23

段2编码

V段2 = [0.2, 0.7, ..., 0.4]

在段2码本中查找最近的中心点:
  距离(V段2, 段2中心点0)   = 1.93
  ...
  距离(V段2, 段2中心点156) = 0.83  ← 最小!
  ...

编码为: 156

...对8段都做编码

3. 最终编码结果

原始向量V (512维,2048字节):
[0.1, 0.3, ..., 0.5 | 0.2, 0.7, ..., 0.4 | ... | 0.6, 0.1, ..., 0.9]
 └─── 段1 ───┘       └─── 段2 ───┘              └─── 段8 ───┘

↓ PQ编码

编码向量V (8字节):
[23, 156, 7, 201, 88, 12, 199, 89]
 │   │    │   │    │   │    │    │
 段12345678

压缩比: 2048字节 → 8字节 = 256倍压缩!

关键理解

原始向量的每段被编码为最近的中心点编号

存储时只需保存8个编号(8字节),而不是512个浮点数(2048字节)


查询阶段:快速检索

目标

快速计算查询向量Q与所有候选向量的近似距离。

详细步骤

步骤1:预计算距离表(每个查询一次)

1.1 切分查询向量

查询向量Q (512维):
[0.15, 0.25, ..., 0.48] | [0.18, 0.72, ..., 0.38] | ... | [0.58, 0.13, ..., 0.92]
└──── Q1(64维) ────┘   └──── Q2(64维) ────┘       └──── Q8(64维) ────┘

1.2 计算Q的每段与码本的距离

段1

Q1 = [0.15, 0.25, ..., 0.48]

计算Q1与段1码本所有256个中心点的距离:
  距离(Q段1, 段1中心点0)   = 2.31
  距离(Q段1, 段1中心点1)   = 1.85
  ...
  距离(Q段1, 段1中心点23)  = 0.52
  ...
  距离(Q段1, 段1中心点255) = 3.14

段2

Q2 = [0.18, 0.72, ..., 0.38]

计算Q2与段2码本所有256个中心点的距离:
  距离(Q段2, 段2中心点0)   = 1.93
  距离(Q段2, 段2中心点1)   = 2.71
  ...
  距离(Q段2, 段2中心点156) = 0.83
  ...

...对8段都计算

1.3 构建距离表(矩阵展示)

距离表 (Distance Table) - 矩阵形式

                      段12345678
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
中心点0              2.31       1.93       0.67       1.15       2.56       0.84       1.77       2.09
中心点1              1.85       2.71       1.42       0.89       1.98       2.33       0.95       1.61
中心点2              0.92       1.44       2.15       1.67       0.84       1.91       2.42       0.73
...                  ...        ...        ...        ...        ...        ...        ...        ...
中心点23             0.52       1.26       2.08       1.73       0.91       1.49       2.21       0.78
...                  ...        ...        ...        ...        ...        ...        ...        ...
中心点156            2.17       0.83       1.55       2.44       1.67       0.72       1.38       1.92
...                  ...        ...        ...        ...        ...        ...        ...        ...
中心点255            3.14       0.95       1.31       0.76       2.03       1.88       0.64       1.25

大小:256个中心点(纵向) × 8段(横向) × 4字节 ≈ 8KB

关键理解

距离表 = 查询向量Q 与 码本中所有中心点 的距离

  • 横向:8个段(段1到段8)
  • 纵向:256个中心点(中心点0到255)
  • 距离表[中心点j][段i] = Q的第i段 与 码本[段i][中心点j] 的距离

步骤2:查表计算近似距离(每个候选向量)

2.1 获取候选向量的编码

候选向量V的编码: [23, 156, 7, 201, 88, 12, 199, 89]
                  │   │    │   │    │   │    │    │
                  段1 段2  段3 段4  段5 段6  段7  段8

2.2 查表获取距离

根据编码查距离表:

段1的编码是23  → 查距离表[中心点23][段1] = 0.52
段2的编码是156 → 查距离表[中心点156][段2] = 0.83
段3的编码是7   → 查距离表[中心点7][段3] = 1.42
段4的编码是201 → 查距离表[中心点201][段4] = 0.89
段5的编码是88  → 查距离表[中心点88][段5] = 1.98
段6的编码是12  → 查距离表[中心点12][段6] = 2.33
段7的编码是199 → 查距离表[中心点199][段7] = 0.95
段8的编码是89  → 查距离表[中心点89][段8] = 1.61

只需8次查表!

2.3 计算最终距离

距离² = 0.52² + 0.83² + 1.42² + 0.89² + 1.98² + 2.33² + 0.95² + 1.61²
     = 0.27 + 0.69 + 2.02 + 0.79 + 3.92 + 5.43 + 0.90 + 2.59
     = 16.61

距离 = √16.61 ≈ 4.08

计算量:8次查表 + 8次平方 + 7次加法 + 1次开方

关键理解

原始向量用中心点编号表示,查询时直接查"Q与这些中心点的距离表",就得到了Q与原始向量的近似距离

因为V的段1被近似为段1码本的23号中心点,而距离表[中心点23][段1]存的就是Q的段1与23号中心点的距离


完整示例

场景

  • 数据库:100万个512维向量
  • 查询:1个512维向量Q
  • 目标:找到最相似的Top-10向量

传统方法

对每个候选向量V:
  计算距离(Q, V) → 512维逐一计算
  
总计算量:100万 × 512次运算 = 5.12亿次运算

PQ方法

步骤1:预计算距离表(一次性)
  对8段,每段计算256个距离
  计算量:8 × 256 = 2048次距离计算

步骤2:对每个候选向量查表
  读取编码(8字节)
  查表8次,计算距离
  
总计算量:2048 + 100万 × 8次查表 ≈ 800万次操作

加速比:5.12亿 ÷ 800万 ≈ 64倍!

核心优势

1. 存储压缩

原始存储:512维 × 4字节 = 2048字节/向量
PQ存储:  8字节/向量

压缩比:256倍
100万向量:2GB  8MB

2. 计算加速

传统距离计算:512次浮点运算
PQ距离计算:  8次查表 + 少量运算

加速比:10-100倍

3. 精度损失

PQ是近似算法,会有精度损失
典型召回率:90-95%

可通过调整参数平衡:
- 增加段数m → 提高精度,增加存储
- 增加中心点数k → 提高精度,增加计算

数据结构立体视图

码本的矩阵结构

123    ...    段8  (横向:8段)
            ↓      ↓      ↓             ↓
中心点0    [64维] [64维] [64维]  ...  [64维]
中心点1    [64维] [64维] [64维]  ...  [64维]
中心点2    [64维] [64维] [64维]  ...  [64维]
  ...       ...    ...    ...          ...
中心点23   [64维] [64维] [64维]  ...  [64维]
  ...       ...    ...    ...          ...
中心点255  [64维] [64维] [64维]  ...  [64维]
↑
(纵向:256个中心点)

矩阵大小:256行 × 8列
每个元素:64维向量

距离表的矩阵结构

123    ...    段8  (横向:8段)
            ↓      ↓      ↓             ↓
中心点0    2.31   1.93   0.67   ...   2.09
中心点1    1.85   2.71   1.42   ...   1.61
中心点2    0.92   1.44   2.15   ...   0.73
  ...       ...    ...    ...          ...
中心点23   0.52   1.26   2.08   ...   0.78
  ...       ...    ...    ...          ...
中心点255  3.14   0.95   1.31   ...   1.25
↑
(纵向:256个中心点)

矩阵大小:256行 × 8列
每个元素:标量距离值

总结

PQ三大核心组件

组件时机结构用途
码本训练阶段(离线)256×8矩阵,每个元素64维编码向量、构建距离表
编码向量编码阶段(离线)8个中心点编号压缩存储原始向量
距离表查询阶段(在线)256×8矩阵,每个元素标量快速计算近似距离

PQ工作流程

训练阶段:
  训练数据 → 切分 → 聚类 → 码本(持久化)
                            ↓
                    [256×8矩阵,64维向量]

编码阶段:
  原始向量 → 切分 → 找最近中心点 → 编码(持久化)
                                    ↓
                            [8个中心点编号]

查询阶段:
  查询向量 + 码本 → 预计算距离表(临时)
                      ↓
              [256×8矩阵,标量值]
                      ↓
  编码向量 + 距离表 → 查表 → 近似距离

关键思想

空间换时间 + 量化压缩 + 分段独立 + 查表加速

通过预计算和查表,将高维距离计算转化为简单的表查询,实现了存储和计算的双重优化!