Данный материал является дополнением к лекциям проф. Клюшин Д. А. по распознаванию образов, а именно — к лекции о линейном дискриминаторе Фишера.
В данном разделе, мы глубже рассмотрим данный алгоритм на примере проблемы снижения размерности данных MNIST, и так же другие методы:
Первые два метода являются классическими, в то время как последний является одним из самых популярных методов снижения размерности.
На практике, зачастую приходится работать с данными очень больших размерностей. Так, например, изображения размером 224×224 пикселей — суть вектора в ; данные финансовых изменений по времени — тоже вектора огромных размерностей.
Вопрос. Можно ли преобразовать/спроецировать наши данные на пространство меньшей размерности, чтоб лучше понять: отделимы ли наши данные? Если да, то как?
На практике, классы методов снижения размерности используются для таких целей:
MNIST (Modified National Institute of Standards and Technology) — объёмная база данных образцов рукописного написания цифр (изображения размером (28, 28)
. Является одним из стандатрных датасетов как для тестирования новых алгоритмов, так и для демонстрации методов компьютерного зрения.
Из-за своей популярности, почти все современные библиотеки для машинного обучения позволяют автоматически загрузить эту базу. Сделаем это с помощью sklearn.datasets
, после чего сделаем нормализацию данных:
from sklearn.datasets import fetch_openml
images, labels = fetch_openml('mnist_784', version=1, return_X_y=True)
Перед тем как приступить к работе с данными, всегда полезно глянуть глазами как оно вообще выглядит.
import matplotlib.pyplot as plt
import numpy as np
plt.figure(figsize=(15,6))
for digit in range(10):
cols = []
for col in range(5):
digit_indices = label_indices[digit][col*5:(col+1)*5]
cols.append(np.concatenate(images[digit_indices].reshape(5, 28, 28)))
vis = np.concatenate(cols, axis=1)
plt.subplot(2, 5, digit + 1), plt.title('Рукописные '+str(digit))
plt.xticks([]), plt.yticks([])
plt.imshow(vis, 'gray')
Перед тем как приступать с следующим действиям, неплохо было бы нормализировать наши данные. Ведь они пока лежат в диапазоне 0..255
— неочень удобный диапазон. Также, поскольку у нас ограниченное вычислительные мощности, ограничимся лишь 6000
семплами датасета.
from sklearn.preprocessing import StandardScaler
X_std = StandardScaler().fit_transform(X)
X, Y = images[:6000], Y = labels[:6000]
PCA (Principal Component Analysis) — один из простейших, но самых распостранённых линейных методов для снижения размерности данных, потеряв при этом наименьшее количество информации.
Пусть — матрица размером наших изображений, выравненные в вектора (в нашем случаи ). Посчитаем емпирическую матрицу ковариации
а затем посчитаем его собственные вектора и собственные значения . Посмотрим на значения (чёрные точки на графике) и их кумулятивную сумму (жёлтые точки на графике), где — сортировка индексов по убыванию собственных значений:
# Calculating Eigenvectors and eigenvalues of Cov matirx
cov_mat = np.cov(X_std.T)
eig_vals, eig_vecs = np.linalg.eigh(cov_mat)
# Sort indices by the descendance of eigenvalues
idx = np.flip(np.argsort(eig_vals))
# Calculation of Explained Variance from the eigenvalues
var_exp = 100 * eig_vals[idx[:100]] / eig_vals.sum() # Individual explained variance
cum_var_exp = np.cumsum(var_exp) # Cumulative explained variance
Можем заметить следующее: собственные значения (чёрные точки) быстро уменьшаются. Это означает, что некоторые направления намного “важнее” других — если представить множество наших точек (изображений), то такое множество наиболее “растянуто” по этим направлениям.
Давайте посмотрим, как же выглядят собственные вектора в порядке уменьшения их собственных значений:
n_row, n_col = 4, 8
plt.figure(figsize=(15,8))
for i in range(n_row * n_col):
plt.subplot(n_row, n_col, i+1)
plt.imshow(eig_vecs.T[idx][i].reshape(28, 28))
plt.title('Eigenvector {}'.format(i+1), size=8)
plt.xticks(()), plt.yticks(())
Визуально можно увидеть, что вектора с наибольшими собственными значениями выглядит как “шаблон” для некоторых цифр, в то время как последние вектора с меньшими собственными значениями визуально не несут никакого семантического значения.
Идея метода главных компонент (PCA) заключается в том, что, посчитав собственные вектора матрицы , спроецировать наши точки на подпространство, порождаемое первыми собственными векторами с наибольшим собственным значением:
где — матрица векторов с наибольшими собственными значениями ( — сортировка индексов по убыванию собственных значений):
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(X_std)
X_nd = pca.transform(X_std)
По визуализации сверху видим, что PCA неплохо справляется с задачей поиска наиболее выразительной проекции — можем чётко видеть отдельные кластеры цифр.
Метод -средних — один из наиболее популярных методов клаастеризации, зачастую используют вместе с методом главных компонент для выявления возможных кластеров. Данный метод разделяет наше множество точек на кластеров, таким образом, чтоб минимизировать дисперцию внутри этих кластеров:
где — полученные кластеры, . Это эквивалентно минимизации суммарного квадратичного отклонения точек кластеров от центров этих кластеров:
где обозначает центры масс всех векторов из кластера . Разбиение после применения PCA к нашему набору изображений рукописных символов выглядит следующим образом:
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=10)
X_clustered = kmeans.fit_predict(X_std)
Следует заметить, что связку PCA + KMeans используют тогда, когда мы примерно знаем количество классов, но не имеем никакой информации о наших данных. К тому же, KMeans работает плохо для тесно расположенных кластеров, как показано сверху.
LDA (Linear Ddiscriminant Analysis) — один из простейших методов уменьшения размерности данных с учитыванием разметки класса.
Несмотря на то что данный метод изначально был создан для классификации Рональдом Фишером в 1936м году, ныне его наиболее часто используют для уменьшения размерности данных. Обобщённый алгоритм метода LDA для нескольких классов, предложенный Rao C. R. (1948), выглядит следующим образом:
1. Посчёт суммы матриц рассеивания внутри классов. Пусть у нас есть классы — множества векторов (изображений) в каждом классе. Посчитаем такую матрицу:
где — матрица рассеивания для класса , который определяется следующим образом:
где — среднее значение векторов в . По сути матрица рассевания это просто матрица ковариации, умноженное на количество векторов. Таким образом, в нашем случаи, классы с большим количеством представителей (изображений, векторов) будут больше влиять на конечную проекцию.
2. Подсчёт матрицы рассеивания между классами. Считается таким образом:
где — среднее по всем векторам, а и — среднее по классу и количество элементов в классе .
3. Подсчёт и выбор направлении наибольшей сепарации. Фишер определял значение сепарации между классами по направлению как соотношение дисперции между классами и суммарно внутри классов по этому направлению:
Это означает, что когда — собственный вектор, то его собственное значение будет равна значению сепарации по его направлению. Наша задача поиска “хорошей” проекции сводится к поиску направлений наибольшей сепарации, что эквивалентно поиску собственных значений:
после чего отсортируем вектора по возрастанию собственных значений. Пользуясь формулой , спроецируем наше множество векторов (изображений) на первые собственных значений из .
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
lda = LDA(n_components=2)
X_LDA_2D = lda.fit_transform(X_std, Y)
Можно увидеть на этом графике, что кластеры более чётко отделены и сгруппированы по сравнению с PCA. Это ожидаемо — ведь PCA не требует информации о классах, и свои плюсы проявляет как раз там, где неизвестны разметки данных. Выбираем нужный инструмент в зависимости от задачи.
Короче говоря — PCA проецирует на собственные компоненты, которые максимизируют ковариацию, в то время как LDA максимизирует показатель сепарации между классами.
t-SNE (-distributed Stochastic Neighbor Embedding) — один из наиболее популярных нелинейных методов для снижение размерности и визуализации данных. Данный метод был опубликован в 2008 году, исследовательской группой Джеффи Хинтона — лауреата Премии Тюринга 2018, один из основателей современной теории глубинных нейронных сетей и свёрточных сетей.
Пусть у нас есть точки . Наша цель — найти такую биекцию, чтоб образы в некотором смысле сохраняли такую же структуру как и свои прообразы. А именно — если две точки были близкими, то и их образы должны быть тоже близкими. Рассмотрим следующую условную меру близости между двумя точками и :
где — евклидова норма. Условная дисперсия индивидуальна для каждой точки и задаётся таким образом, чтоб точкам в плотных областей соответствовало меньшее значение. Более подробное описание метода задания можно найти в оригинальной статье. Следует заметить, что пропорционально плотности гауссиана с центром в точке .
Теперь, мы можем задать симметричную условную меру близости между точками и :
где — количество точек в нашем датасете (базе данных). Теперь, зададим условную меру близости между образами :
В отличие от , мера близости между образами имеет распределение Стьюдента с 1 степенем свободы. Данный выбор вида меры близости был сделан для балансировки проблемы сгущения — в объём шара радиусом растёт как , а значит, при больших случайно выбранная точка скорее всего будет ближе к поверхности сферы. Это и было проблемой метода SNE (Hinton, 2002). У распределения Стьюдента более тяжелые “хвосты” (т.е. оно не “кучкуется” вокруг центра как гауссиан), что позволяет компенсировать изначальную несбалансированность.
Алгоритм метода t-SNE идейно проста — выбираем образы таким образом, чтоб минимизировать дивергенцию Кульбака – Лейбнера между и :
градиент данной дивергенции можно найти аналитически, что позволяет нам использовать градиентные методы:
Следует заметить, что \ref{tsne-grad} является задачей невыпуклой оптимизации и не гарантирует нахождения оптимального решения. Более того, градиентный спуск в чистом виде для данной конкретной задачи зачастую застревает в локальном минимуме, в связи с чем в статье 2008 года было предложено несколько эвристических трюков, которые помогают с этим эффективно бороться. Детали об этих трюках выходит за рамки данного обзора, и любопытному читателю предлагается самостоятельно прочитать статью.
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2)
tsne_results = tsne.fit_transform(X_std)
Несмотря на то что t-SNE относится к классу алгоритмов без учителя, данный метод успешно выделило основные кластеры изображения рукописных цифр. Следует заметить, что он, как и PCA, ничего не знает о настоящей разметке данных.
Методы снижения размерности является важным классом методов в распознавании образов. В данном обзоре, мы перечислили типичные сценарии использования этих методов на практике в разделе 1.1. Потом, мы ознакомились с классическими и современными методами — PCA (1.3), LDA (1.4), и t-SNE (1.5). Мы так же рассмотрили минусы и плюсы этих методов, и обсудили в каких случаях лучше какой использовать.