Довольно «неглубокий» и простой подход к глубокой кластеризации многомерных данных с использованием Keras и множественного обучения за 3 простых шага

1. Введение

Обычно мы группируем данные, чтобы найти или узнать о взаимосвязях и структурах, которые существуют в них, особенно когда наши данные слишком сложны или слишком многомерны для простой описательной статистики. Когда дело доходит до многомерных данных, иногда поражает, насколько информативным и полезным может быть простой алгоритм кластеризации. Но кластеризация или неконтролируемое обучение в целом часто бывает намного сложнее, чем выбор правильного алгоритма или подхода (а это совсем не просто), потому что хороший результат кластеризации обычно зависит от того, как данные организованы и представлены . Действительно, я видел больше проблем кластеризации, решаемых с помощью творческого способа представления или организации одного и того же набора данных, чем с помощью какого-либо инновационного алгоритма.

Одной из наиболее доминирующих характеристик нейронных сетей (если не * самой * *) является их способность эффективно изучать способы представления многомерных данных таким образом, чтобы выявлять именно те сложные отношения и структуры, которые их характеризуют. Вот почему нейронные сети так широко используются для задач уменьшения размерности и распознавания образов. Неудивительно, что за последние несколько лет было также предложено и показано, что нейронные сети могут помочь в задачах кластеризации, обеспечивая лучшее (или более информативное) представление данных до их кластеризации (см. Здесь для некоторого хорошего обзора DEC и DCN. - два наиболее распространенных подхода).

Многие из предложенных архитектур, которые следуют этому подходу, основаны на некоторой форме предварительной задачи кодирования-декодирования (или автокодировщика), которая направлена ​​на создание более точного представления данных перед их кластеризацией. Очень кратко и просто, автокодировщик - это сеть, которая учится реконструировать входные данные, которые он получает, создавая низкоразмерные представления данных, которые (если все сделано правильно) сохраняет наиболее важную информацию о них, а затем использует эти низкоразмерные представления, чтобы восстановить его. Причина, по которой автоэнкодеры подходят для этой задачи, заключается в том, что если они хорошо справляются с реконструкцией своих входных данных, то это означает, что они изучили представление данных в более низком измерении, которое, вероятно, хорошо фиксирует структуры и взаимосвязи внутри него, и, следовательно, , использование этих представлений для кластеризации данных даст лучший результат.

К сожалению, многие из этих предложенных архитектур совсем не очевидны или просты в реализации, конечно, не для чего-то вроде практического POC, и некоторые из них требовали относительно больших ресурсов по сравнению с их стоимостью. Однако пару месяцев назад я наткнулся на статью N2d: (не слишком) глубокая кластеризация через кластеризацию локального многообразия автоматически закодированного вложения ». В статье, которая, похоже, не получила особой поддержки, по сути, предлагается гораздо более простой и довольно простой в реализации подход к глубокой кластеризации. Поэкспериментировав с ним некоторое время и получив очень хорошие результаты, я подумал, что было бы неплохо создать это краткое и практичное вступление, которое позволит вам быстро протестировать его и заставить работать.

Вкратце идея основана на 3 этапах:

(1) Создайте автоэнкодер, который будет изучать представление данных в более низком измерении, которое, мы надеемся, будет захватывать наиболее важную информацию и структуры внутри него;

(2) Примените разнообразный метод обучения (например, UMAP или TSNE) для дальнейшего уменьшения размеров данных и создания более точных представлений, которые улучшат производительность алгоритма кластеризации.

(3) Сгруппируйте данные.

Я оставлю теорию и объяснения для статьи (в которой на самом деле есть много практических примеров) и сразу перейду к немного измененной и более короткой реализации, которая в основном основана на коде, опубликованном авторами.

2. Используйте автоэнкодер для создания встраивания и уменьшения размеров.

Данные, которые я буду использовать, представляют собой довольно разреженную матрицу пользователей и количество раз, когда они посещали различные области пользовательского интерфейса определенного веб-сайта. Моя цель - объединить пользователей в группы, используя их действия на веб-сайте, чтобы лучше понять их предпочтения и поведение в группе.
Данные нормализованы на векторном уровне, имеют 50 измерений / функций и около 20 тысяч пользователей. Вы можете увидеть отрывок ниже.

Первым делом мы создадим архитектуру автокодировщика. Следуя архитектуре, представленной в документе, автоэнкодер расширит количество измерений, а затем создаст узкое место, которое уменьшит размеры до 10 (обычная практика с автоэнкодерами, см. Здесь)

Автоэнкодер может быть легко сконструирован с использованием следующей вспомогательной функции (также приведенной в документе). Функция просто принимает структуру сети кодирования, которую вы хотите создать, в виде списка (в нашем случае это будет [50, 500, 500, 2000, 10], но с этим нужно поэкспериментировать, и вы, безусловно, можете использовать меньший) и верните модель.

Таким образом, следующий фрагмент кода дает нам полноценный автоэнкодер, который кодирует набор данных размером 50 пикселей в 10 измерений.

# X = matrix shown above
encoded_dimensions = 10
shape = [X.shape[-1], 500, 500, 2000, encoded_dimensions]
autoencoder = get_autoencoder(shape) 

Напомним, что интересующий нас плотный слой - это слой «узкого места», который захватывает нижнее (10D) представление наших данных (выделено выше). Точнее, нам здесь нужны только слои кодировщика, которые мы будем использовать для встраивания наших данных в нижнюю размерность. Итак, план состоит в том, чтобы обучить автокодировщик, а затем просто использовать слои кодировщика (с обученными весами) для уточнения наших данных. (Это звучит сложно, но если вы проверите записную книжку в моем репо, вы увидите, что это более или менее просто)

# this will just give us the label we gave the encoder layer
# 'encoder_3'
encoded_layer = f'encoder_{(len(shape) - 2)}'
#we take the layer that we are interested in
hidden_encoder_layer = autoencoder.get_layer(name=encoded_layer).output
# create just the encoder model that we can use after the autoencoder will be trained
encoder = Model(inputs=autoencoder.input, outputs=hidden_encoder_layer)

Итак, теперь все, что осталось, - это fit () autoencoder, а затем использовать encoder, который уже ссылается на свои веса, для кодирования наших данных в представление более низкой размерности.

autoencoder.compile(loss='mse', optimizer='adam')
autoencoder.fit(
    X_train,
    X_train,
    batch_size=batch_size,
    epochs=pretrain_epochs,
    verbose=1,
    validation_data=(X_test, X_test)
)
# use the weights learned by the encoder to encode the data to a representation (embedding)
X_encoded = encoder.predict(X)

3. Изучите многообразия

Второй этап - использовать многообразное обучение для уточнения кластеров и дальнейшего уменьшения размеров. Авторы пишут, что причиной добавления сложной задачи обучения к закодированному встраиванию является то, что автокодер обычно не принимает во внимание локальные структуры и что «путем дополнения автокодировщика разнообразной техникой обучения, которая явно принимает локальную структуру в счет, мы можем повысить качество полученного представления с точки зрения кластеризации ». Хотя есть веские причины в пользу и против использования многообразного обучения в качестве благоприятного шага перед кластеризацией, я считаю, что авторы правы (и они показывают это), что это, вероятно, улучшит производительность алгоритмов, основанных на плотности, и поможет создать более плотные «окрестности». . » Но это действительно то, с чем вам нужно экспериментировать в каждом конкретном случае.

В этом случае я буду следовать за авторами в статье и использовать UMAP для этой задачи, используя (слегка измененную) вспомогательную функцию ниже.

X_reduced = learn_manifold(X_encoded, umap_neighbors=30, umap_dim=int(encoded_dimensions/2))

И теперь у нас должен быть набор дат, готовый к кластеризации.

4. Кластер

Следующий шаг довольно прост: просто выберите свой любимый метод кластеризации на основе плотности и позвольте ему обработать данные. Просто обратите внимание, что когда данные должны быть визуализированы в 2d, тогда лучше уменьшить размер вложения перед визуализацией, чем встраивание после обучения многообразию. Теперь данные должны быть сгруппированы в достаточно плотные формы, чтобы упростить кластеризацию.

# this is the data that we need to cluster
labels = hdbscan.HDBSCAN(
    min_samples=100,
    min_cluster_size=1000
).fit_predict(X_reduced)
# lower dim to 2d so we can plot it
reducer = umap.UMAP(n_components=2)
embedding = reducer.fit_transform(X_encoded)

Для сравнения, это результат выполнения HDBSCAN для данных до того, как они были дополнительно уточнены с использованием множественного обучения. Как вы можете видеть, с точно такими же гиперпараметрами HDBSCAN захватил только 2 основных кластера, вероятно, из-за менее детализированных окрестностей по сравнению с теми, которые создаются UMAP.

5. Заключение

Это звучит интуитивно понятно, но исследователи иногда забывают, что хороший результат кластеризации часто больше зависит от того, как данные организованы или представлены, чем от выбранного алгоритма кластеризации и его гиперпараметров. Гораздо сложнее найти способы реорганизовать сложный набор данных, но в большинстве случаев это окупается.

Методы глубокого обучения обычно превосходны в эффективном обучении и создании встроенных представлений данных, и поэтому они иногда используются в качестве этапа предварительной обработки для задач кластеризации, нацеленных на создание менее размерного и более кластеризованного представления данных. Хотя подходы глубокого обучения к кластеризации зачастую сложнее реализовать, когда данные имеют большие размеры, нельзя упускать из виду предлагаемый ими прирост производительности.

В этом посте я попытался создать краткое и очень практичное введение в довольно простой и понятный подход (названный авторами N2D). Хотя N2D аналогичным образом направлен на создание более дружественного к кластеризации закодированного встраивания данных большой размерности с использованием DL, я показал, что в отличие от других подходов, N2D может дать довольно впечатляющие результаты и повышение производительности с гораздо более простой архитектурой и относительно небольшими усилиями по реализации. Хотя мой набор данных был основан на разреженных данных, что само по себе представляет отдельную проблему, авторы статьи показывают довольно впечатляющие результаты, используя этот подход со многими распространенными наборами данных кластеризации. Подводя итог, я считаю, что N2D - очень хорошее дополнение к набору инструментов кластеризации, особенно на предварительных этапах исследования, которое может быстро и эффективно дать некоторые результаты для работы.

Надеюсь, это будет полезно.

Ноутбук доступен в моем репо здесь