Product Quantization (PQ)

0 阅读4分钟

🎯 核心思想 把复杂的高维向量"压缩"成简单的编码,就像把一本厚书变成目录索引一样。 📖 基本原理

  1. 分组 (Dimension Splitting) 将原始的高维向量空间分成多个子空间 通常将 d 维向量分为 m 个大小相等的子集,每个子集包含 d/m 维 目的:将复杂的高维问题分解为多个较低维度的问题
  2. 量化编码 (Codebook Generation) 对每个子空间构建一个码本(codebook) 码本由 k 个向量组成,这些向量是该子空间内所有训练向量的聚类中心 通过聚类算法完成,每个子空间的向量被分配到离它最近的聚类中心
  3. 编码 (Encoding) 对每个高维向量,将其在每个子空间上的投影与相应的码本比较 找出距离最近的码本向量,记录其在码本中的索引 原始高维向量转换为长度为 m 且每个元素范围在 0 到 k-1 之间的整数序列

📚 生活化类比 原始方法(高维向量) 描述一个人的外貌特征: 身高:175.3cm 体重:68.7kg 眼睛颜色:深棕色(RGB: 101,67,33) 头发长度:15.2cm 肤色:(RGB: 255,219,172) ... 总共100个精确特征

PQ方法 步骤1:分组 把100个特征分成4组,每组25个特征: 第1组:身材相关(身高、体重、肩宽等) 第2组:面部相关(眼睛、鼻子、嘴巴等) 第3组:头发相关(颜色、长度、质地等) 第4组:肤色相关(颜色、质感等)

步骤2:建立码本 为每组建立"标准模板": 身材码本: 编码0:瘦高型 编码1:标准型 编码2:壮实型 编码3:矮胖型 面部码本: 编码0:圆脸大眼 编码1:方脸小眼 编码2:瓜子脸 编码3:长脸

步骤3:编码 原始数据:100维精确数值 PQ编码:[1, 2, 0, 1] (只需4个数字!)

💻 代码实现

import numpy as np
from sklearn.cluster import KMeans

class SimpleProductQuantization:
    def __init__(self, m=4, k=256):
        """
        m: 子空间数量(分组数)
        k: 每个码本的大小
        """
        self.m = m
        self.k = k
        self.codebooks = []
    
    def train(self, vectors):
        """训练阶段:建立码本"""
        d = vectors.shape[1]  # 向量维度
        subvec_len = d // self.m  # 每个子空间的维度
        
        for i in range(self.m):
            # 提取第i个子空间的数据
            start_idx = i * subvec_len
            end_idx = (i + 1) * subvec_len
            subvectors = vectors[:, start_idx:end_idx]
            
            # 对子空间进行聚类,生成码本
            kmeans = KMeans(n_clusters=self.k, random_state=42)
            kmeans.fit(subvectors)
            self.codebooks.append(kmeans.cluster_centers_)
    
    def encode(self, vector):
        """编码:将向量转换为PQ码"""
        d = len(vector)
        subvec_len = d // self.m
        pq_codes = []
        
        for i in range(self.m):
            # 提取子向量
            start_idx = i * subvec_len
            end_idx = (i + 1) * subvec_len
            subvector = vector[start_idx:end_idx]
            
            # 找到最近的码本向量
            codebook = self.codebooks[i]
            distances = np.linalg.norm(codebook - subvector, axis=1)
            closest_code = np.argmin(distances)
            pq_codes.append(closest_code)
        
        return pq_codes

使用示例

假设有1000个128维的向量

vectors = np.random.randn(1000, 128)

初始化PQ

pq = SimpleProductQuantization(m=4, k=256)

训练

pq.train(vectors)

编码一个新向量

new_vector = np.random.randn(128)
pq_code = pq.encode(new_vector)
print(f"原始向量维度: {len(new_vector)}")
print(f"PQ编码: {pq_code}")
print(f"压缩比: {len(new_vector)}{len(pq_code)}")

🎯 实际效果 压缩效果 原始:128维浮点数 = 128 × 4字节 = 512字节 PQ编码:4个整数 = 4 × 1字节 = 4字节 压缩比:128:1!

应用场景 图像搜索:快速找相似图片 推荐系统:快速匹配用户偏好 向量数据库:节省存储空间

💡 总结 PQ的核心就是**"用少量编码表示大量信息"**,就像用邮政编码代表详细地址一样!通过将高维向量分解为多个低维子空间,并在每个子空间中用聚类中心的索引来表示原始数据,从而实现高效的数据压缩和快速检索。

思想是:把高维向量空间降维。因为低维向量的存储和计算开销都远远低于高维。 大致原理: 分组(Dimension Splitting): 首先,将原始的高维向量空间分成多个子空间,通常是将 d 维向量分为 m 个大小相等的子集,每个子集包含 d / m 维。这样做的目的是将一个复杂的高维问题分解为多个较低维度的问题,便于处理。

量化编码(Codebook Generation): 对于每个子空间,构建一个码本(codebook)。码本是一个由 k 个向量组成的集合,这些向量是该子空间内所有训练向量的聚类中心。这一步通常通过聚类算法完成,每个子空间的向量被分配到离它最近的聚类中心。

编码(Encoding): 对于每一个高维向量,将其在每个子空间上的投影与相应的码本进行比较,找出距离最近的码本向量,并记录下这个向量在码本中的索引。这样,原始的高维向量就被转换为了一个长度为 m 且每个元素范围在 0 到 k - 1 之间的整数序列,即可得到一个紧凑的量化表示。

在这里插入图片描述