注意
转到结尾 下载完整的示例代码。或通过JupyterLite或Binder在您的浏览器中运行此示例
向量量化示例#
此示例演示如何使用KBinsDiscretizer
对一组玩具图像(浣熊脸)执行向量量化。
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
原始图像#
我们首先从SciPy加载浣熊脸图像。我们还将检查有关图像的一些信息,例如用于存储图像的形状和数据类型。
请注意,根据SciPy的版本,我们必须调整导入,因为返回图像的函数不在同一个模块中。此外,SciPy >= 1.10需要安装pooch
包。
try: # Scipy >= 1.10
from scipy.datasets import face
except ImportError:
from scipy.misc import face
raccoon_face = face(gray=True)
print(f"The dimension of the image is {raccoon_face.shape}")
print(f"The data used to encode the image is of type {raccoon_face.dtype}")
print(f"The number of bytes taken in RAM is {raccoon_face.nbytes}")
The dimension of the image is (768, 1024)
The data used to encode the image is of type uint8
The number of bytes taken in RAM is 786432
因此,图像是一个二维数组,高度为768像素,宽度为1024像素。每个值都是一个8位无符号整数,这意味着图像使用每像素8位进行编码。图像的总内存使用量为786千字节(1字节等于8位)。
使用8位无符号整数意味着图像最多使用256种不同的灰度阴影进行编码。我们可以检查这些值的分布。
import matplotlib.pyplot as plt
fig, ax = plt.subplots(ncols=2, figsize=(12, 4))
ax[0].imshow(raccoon_face, cmap=plt.cm.gray)
ax[0].axis("off")
ax[0].set_title("Rendering of the image")
ax[1].hist(raccoon_face.ravel(), bins=256)
ax[1].set_xlabel("Pixel value")
ax[1].set_ylabel("Count of pixels")
ax[1].set_title("Distribution of the pixel values")
_ = fig.suptitle("Original image of a raccoon face")
通过向量量化进行压缩#
通过向量量化进行压缩的思想是减少表示图像的灰度等级。例如,我们可以使用8个值而不是256个值。因此,这意味着我们可以有效地使用3位而不是8位来编码单个像素,从而将内存使用量减少大约2.5倍。我们稍后将讨论此内存使用情况。
编码策略#
可以使用KBinsDiscretizer
进行压缩。我们需要选择一种策略来定义要进行子采样的8个灰度值。最简单的策略是将它们定义为等距分布,这对应于设置strategy="uniform"
。从之前的直方图,我们知道这种策略肯定不是最佳的。
from sklearn.preprocessing import KBinsDiscretizer
n_bins = 8
encoder = KBinsDiscretizer(
n_bins=n_bins,
encode="ordinal",
strategy="uniform",
random_state=0,
)
compressed_raccoon_uniform = encoder.fit_transform(raccoon_face.reshape(-1, 1)).reshape(
raccoon_face.shape
)
fig, ax = plt.subplots(ncols=2, figsize=(12, 4))
ax[0].imshow(compressed_raccoon_uniform, cmap=plt.cm.gray)
ax[0].axis("off")
ax[0].set_title("Rendering of the image")
ax[1].hist(compressed_raccoon_uniform.ravel(), bins=256)
ax[1].set_xlabel("Pixel value")
ax[1].set_ylabel("Count of pixels")
ax[1].set_title("Sub-sampled distribution of the pixel values")
_ = fig.suptitle("Raccoon face compressed using 3 bits and a uniform strategy")
从质量上看,我们可以发现一些小的区域,在那里我们可以看到压缩的效果(例如,右下角的叶子)。但总的来说,生成的图像仍然看起来不错。
我们观察到像素值的分布已被映射到8个不同的值。我们可以检查这些值与原始像素值之间的对应关系。
bin_edges = encoder.bin_edges_[0]
bin_center = bin_edges[:-1] + (bin_edges[1:] - bin_edges[:-1]) / 2
bin_center
array([ 15.625, 46.875, 78.125, 109.375, 140.625, 171.875, 203.125,
234.375])
_, ax = plt.subplots()
ax.hist(raccoon_face.ravel(), bins=256)
color = "tab:orange"
for center in bin_center:
ax.axvline(center, color=color)
ax.text(center - 10, ax.get_ybound()[1] + 100, f"{center:.1f}", color=color)
如前所述,均匀采样策略并非最佳。例如,请注意,映射到值7的像素将编码相当少的信息,而映射值3将表示大量的计数。我们可以改用k均值等聚类策略来找到更优的映射。
encoder = KBinsDiscretizer(
n_bins=n_bins,
encode="ordinal",
strategy="kmeans",
random_state=0,
)
compressed_raccoon_kmeans = encoder.fit_transform(raccoon_face.reshape(-1, 1)).reshape(
raccoon_face.shape
)
fig, ax = plt.subplots(ncols=2, figsize=(12, 4))
ax[0].imshow(compressed_raccoon_kmeans, cmap=plt.cm.gray)
ax[0].axis("off")
ax[0].set_title("Rendering of the image")
ax[1].hist(compressed_raccoon_kmeans.ravel(), bins=256)
ax[1].set_xlabel("Pixel value")
ax[1].set_ylabel("Number of pixels")
ax[1].set_title("Distribution of the pixel values")
_ = fig.suptitle("Raccoon face compressed using 3 bits and a K-means strategy")
bin_edges = encoder.bin_edges_[0]
bin_center = bin_edges[:-1] + (bin_edges[1:] - bin_edges[:-1]) / 2
bin_center
array([ 18.90885631, 53.34346583, 82.64447187, 109.28225276,
134.70763101, 159.78681467, 185.17226834, 224.02069427])
_, ax = plt.subplots()
ax.hist(raccoon_face.ravel(), bins=256)
color = "tab:orange"
for center in bin_center:
ax.axvline(center, color=color)
ax.text(center - 10, ax.get_ybound()[1] + 100, f"{center:.1f}", color=color)
现在,箱中的计数更加均衡,它们的中心不再等距分布。请注意,我们可以通过使用strategy="quantile"
而不是strategy="kmeans"
来强制每个箱具有相同的像素数量。
内存占用#
我们之前说过,我们应该节省8倍的内存。让我们验证一下。
print(f"The number of bytes taken in RAM is {compressed_raccoon_kmeans.nbytes}")
print(f"Compression ratio: {compressed_raccoon_kmeans.nbytes / raccoon_face.nbytes}")
The number of bytes taken in RAM is 6291456
Compression ratio: 8.0
令人惊讶的是,我们的压缩图像比原始图像占用8倍的内存。这与我们的预期恰恰相反。原因主要是由于用于编码图像的数据类型。
print(f"Type of the compressed image: {compressed_raccoon_kmeans.dtype}")
Type of the compressed image: float64
实际上,KBinsDiscretizer
的输出是64位浮点数数组。这意味着它占用了8倍的内存。但是,我们使用这个64位浮点数表示来编码8个值。实际上,只有在我们将压缩图像转换为3位整数数组时,我们才能节省内存。我们可以使用numpy.ndarray.astype
方法。但是,3位整数表示不存在,为了编码8个值,我们也需要使用8位无符号整数表示。
实际上,观察到内存增益需要原始图像以64位浮点数表示。
脚本总运行时间:(0分钟2.087秒)
相关示例