原理
Product quantization,国内有人直译为乘积量化,这里的乘积是指笛卡尔积(Cartesian product),意思是指把原来的向量空间分解为若干个低维向量空间的笛卡尔积,并对分解得到的低维向量空间分别做量化(quantization)。这样每个向量就能由多个低维空间的量化code组合表示。算法如下图所示。
PQ算法把D维向量分成m组, 每组进行Kmeans聚类算法.
- m组子向量的Kmeans算法可以并行求解
2)可以将D维的特征压缩成m维,压缩率D/M
代码
训练
def fit(self, vecs, iter=20, seed=123):
"""Given training vectors, run k-means for each sub-space and create
codewords for each sub-space.
This function should be run once first of all.
Args:
vecs (np.ndarray): Training vectors with shape=(N, D) and dtype=np.float32.
iter (int): The number of iteration for k-means
seed (int): The seed for random process
Returns:
object: self
"""
assert vecs.dtype == np.float32
assert vecs.ndim == 2
N, D = vecs.shape
assert self.Ks < N, "the number of training vector should be more than Ks"
assert D % self.M == 0, "input dimension must be dividable by M"
self.Ds = int(D / self.M)
np.random.seed(seed)
if self.verbose:
print("iter: {}, seed: {}".format(iter, seed))
# [m][ks][ds]: m-th subspace, ks-the codeword, ds-th dim
self.codewords = np.zeros((self.M, self.Ks, self.Ds), dtype=np.float32)
for m in range(self.M):
if self.verbose:
print("Training the subspace: {} / {}".format(m, self.M))
vecs_sub = vecs[:, m * self.Ds : (m + 1) * self.Ds]
self.codewords[m], _ = kmeans2(vecs_sub, self.Ks, iter=iter, minit="points")
return self
训练的目的是生成codewords码本,self.codewords = np.zeros((self.M, self.Ks, self.Ds), dtype=np.float32)
M是划分子空间的个数,KS是kmean聚类的中心数,Ds是每个子空间向量的维度。码本的生成主要是靠kmean算法对每一个子空间m训练得到的。
编码
def encode(self, vecs):
"""Encode input vectors into PQ-codes.
Args:
vecs (np.ndarray): Input vectors with shape=(N, D) and dtype=np.float32.
Returns:
np.ndarray: PQ codes with shape=(N, M) and dtype=self.code_dtype
"""
assert vecs.dtype == np.float32
assert vecs.ndim == 2
N, D = vecs.shape
self.Ds=int(2048/self.M)
assert D == self.Ds * self.M, "input dimension must be Ds * M"
# codes[n][m] : code of n-th vec, m-th subspace
codes = np.empty((N, self.M), dtype=self.code_dtype)
for m in range(self.M):
if self.verbose:
print("Encoding the subspace: {} / {}".format(m, self.M))
vecs_sub = vecs[:, m * self.Ds : (m + 1) * self.Ds]
codes[:, m], _ = vq(vecs_sub, self.codewords[m])
return codes
编码使用vq,vq是将连续的信号转化成离散的codewords,常和kmean一起使用,kmean生成的聚类中心,就是vq的codewords
解码
def decode(self, codes):
"""Given PQ-codes, reconstruct original D-dimensional vectors
approximately by fetching the codewords.
Args:
codes (np.ndarray): PQ-cdoes with shape=(N, M) and dtype=self.code_dtype.
Each row is a PQ-code
Returns:
np.ndarray: Reconstructed vectors with shape=(N, D) and dtype=np.float32
"""
assert codes.ndim == 2
N, M = codes.shape
assert M == self.M
assert codes.dtype == self.code_dtype
self.Ds = int(2048 / self.M)
vecs = np.empty((N, self.Ds * int(self.M)), dtype=np.float32)
for m in range(self.M):
vecs[:, m * self.Ds : (m + 1) * self.Ds] = self.codewords[m][codes[:, m], :]
return vecs
解码直接利用保存的self.codewords数组读取其中的数值即可。