Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере с помощью JupyterLite или Binder.
Пример векторного квантования
В этом примере показано, как с помощью KBinsDiscretizer выполнить векторное квантование набора учебных изображений — изображения лица енота.
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
Исходное изображение
Для начала загрузим изображение лица енота из SciPy. Также проверим некоторые сведения об изображении, например его форму и тип данных, используемый для его хранения.
from scipy.datasets 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.5625, 46.6875, 77.8125, 108.9375, 140.0625, 171.1875,
202.3125, 233.4375])
_, 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.90934343, 53.33478066, 82.59678424, 109.22385188,
134.67876527, 159.67877978, 184.72384803, 223.17132867])
_, 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 минут 1.868 секунды)
Связанные примеры
© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/auto_examples/cluster/plot_face_compress.html