备注
Go to the end 下载完整的示例代码。或者通过浏览器中的MysterLite或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个像素的2D阵列。每个值都是一个8位无符号整数,这意味着图像使用每个像素8位进行编码。图像的总内存使用量为786千字节(1字节等于8位)。
使用8位无符号integer意味着图像最多使用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-means等集群策略来找到更优化的映射。
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位浮点数组。这意味着它需要更多的内存。然而,我们使用这种64位浮点表示来编码8个值。事实上,只有当我们将压缩图像转换为3位整数数组时,我们才会节省内存。我们可以用这个方法 numpy.ndarray.astype
.然而,3比特的整元表示并不存在,为了编码8个值,我们还需要使用8比特无符号的整元表示。
在实践中,观察内存增加需要原始图像采用64位浮点表示。
Total running time of the script: (0分1.486秒)
相关实例
Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>
_