Вступ
Кластеризація K-середніх це один із найпоширеніших алгоритмів неконтрольованого машинного навчання, який формує кластери даних на основі подібності між екземплярами даних.
У цьому посібнику ми спочатку розглянемо простий приклад, щоб зрозуміти, як працює алгоритм K-Means, перш ніж реалізувати його за допомогою Scikit-Learn. Потім ми обговоримо, як визначити кількість кластерів (Ks) у K-Means, а також охопимо показники відстані, дисперсію, а також переваги та мінуси K-Means.
мотивація
Уявіть собі наступну ситуацію. Одного разу, прогулюючись околицями, ви помітили 10 міні-маркетів і почали думати, які магазини схожі – ближче один до одного. Шукаючи відповіді на це запитання, ви натрапили на цікавий підхід, який ділить магазини на групи на основі їхніх координат на карті.
Наприклад, якби один магазин був розташований на 5 км на захід і 3 км на північ – ви б призначили (5, 3)
координати до нього та зобразіть його на графіку. Давайте зобразимо цю першу точку, щоб візуалізувати, що відбувається:
import matplotlib.pyplot as plt
plt.title("Store With Coordinates (5, 3)")
plt.scatter(x=5, y=3)
Це лише перший момент, щоб ми могли зрозуміти, як ми можемо представити магазин. Скажімо, ми вже маємо 10 координат для 10 зібраних магазинів. Після організації їх в а numpy
масиву, ми також можемо побудувати їх розташування:
import numpy as np
points = np.array([[5, 3], [10, 15], [15, 12], [24, 10], [30, 45], [85, 70], [71, 80], [60, 78], [55, 52],[80, 91]])
xs = points[:,0]
ys = points[:,1]
plt.title("10 Stores Coordinates")
plt.scatter(x=xs, y=ys)
Як вручну реалізувати алгоритм K-Means
Тепер ми можемо поглянути на 10 магазинів на графіку, і головна проблема полягає в тому, щоб знайти спосіб, яким їх можна розділити на різні групи на основі близькості? Просто швидко поглянувши на графік, ми, ймовірно, помітимо дві групи магазинів – одна – це нижня точка зліва, а інша – верхня права точка. Можливо, ми навіть можемо виділити ці дві точки посередині в окрему групу – отже, творити три різні групи.
У цьому розділі ми розглянемо процес кластеризації точок вручну – поділу їх на задану кількість груп. Таким чином ми, по суті, уважно розглянемо всі кроки Алгоритм кластеризації K-Means. До кінця цього розділу ви отримаєте як інтуїтивне, так і практичне розуміння всіх кроків, які виконуються під час кластеризації K-Means. Після цього ми передамо його Scikit-Learn.
Який найкращий спосіб визначити наявність двох чи трьох груп точок? Одним із простих способів було б просто вибрати одну кількість груп (наприклад, дві), а потім спробувати згрупувати точки на основі цього вибору.
Скажімо, ми вирішили, що є дві групи наших магазинів (точок). Тепер нам потрібно знайти спосіб зрозуміти, які точки до якої групи належать. Це можна зробити, вибравши одну точку для представлення група 1 і один представляти група 2. Ці точки будуть використовуватися як еталонні під час вимірювання відстані від усіх інших точок до кожної групи.
Таким чином, скажіть точку (5, 3)
закінчується належністю до групи 1, і точка (79, 60)
до групи 2. При спробі призначити нову точку (6, 3)
до груп, нам потрібно виміряти його відстань до цих двох точок. У випадку з точкою (6, 3)
is ближче до (5, 3)
, тому він належить до групи, представленої цією точкою – група 1. Таким чином ми можемо легко згрупувати всі точки у відповідні групи.
У цьому прикладі, окрім визначення кількості груп (кластерів) – ми також вибираємо деякі моменти для a посилання відстані для нових точок кожної групи.
Це загальна ідея для розуміння подібності між нашими магазинами. Давайте застосуємо це на практиці – ми можемо спочатку вибрати дві контрольні точки в випадковий. Орієнтир о група 1 буде (5, 3)
і орієнтир група 2 буде (10, 15)
. Ми можемо вибрати обидві точки numpy
масив по [0]
та [1]
індекси та зберігати їх у g1
(1 група) і g2
(група 2) змінні:
g1 = points[0]
g2 = points[1]
Після цього нам потрібно обчислити відстань від усіх інших точок до цих контрольних точок. Це викликає важливе питання – як виміряти цю відстань. По суті, ми можемо використовувати будь-яку міру відстані, але для цілей цього посібника давайте використаємо Евклідову відстань_.
Може бути корисно знати, що евклідова міра відстані базується на теоремі Піфагора:
$$
c^2 = a^2 + b^2
$$
При адаптації до точок на площині – (a1, b1)
та (a2, b2)
, попередня формула стає такою:
$$
c^2 = (a2-a1)^2 + (b2-b1)^2
$$
Відстань буде квадратним коренем з c
, тому ми також можемо записати формулу так:
$$
euclidean_{dist} = sqrt[2][(a2 – a1)^2 + (b2 – b1) ^2)]
$$
Примітка: Ви також можете узагальнити формулу евклідової відстані для багатовимірних точок. Наприклад, у тривимірному просторі точки мають три координати – наша формула відображає це таким чином:
$$
euclidean_{dist} = sqrt[2][(a2 – a1)^2 + (b2 – b1) ^2 + (c2 – c1) ^2)]
$$
Той самий принцип дотримується незалежно від кількості вимірів простору, в якому ми працюємо.
Наразі ми вибрали точки для представлення груп і знаємо, як обчислювати відстані. Тепер давайте об’єднаємо відстані та групи, призначивши кожну зібрану точку магазину групі.
Щоб краще уявити це, ми оголосимо три списки. Хто першим запам'ятає бали першої групи – points_in_g1
. Другий для зберігання балів з групи 2 – points_in_g2
, а останній – group
, Щоб етикетка точки як будь-який 1
(належить до 1 групи) або 2
(належить до 2 групи):
points_in_g1 = []
points_in_g2 = []
group = []
Тепер ми можемо перебирати наші точки та обчислювати евклідову відстань між ними та кожною з наших групових посилань. Кожна точка буде ближче до однієї з двох груп – залежно від того, яка група є найближчою, ми призначимо кожну точку до відповідного списку, а також додамо 1
or 2
до group
список:
for p in points:
x1, y1 = p[0], p[1]
euclidean_distance_g1 = np.sqrt((g1[0] - x1)**2 + (g1[1] - y1)**2)
euclidean_distance_g2 = np.sqrt((g2[0] - x1)**2 + (g2[1] - y1)**2)
if euclidean_distance_g1 < euclidean_distance_g2:
points_in_g1.append(p)
group.append('1')
else:
points_in_g2.append(p)
group.append('2')
Давайте подивимося на результати цієї ітерації, щоб побачити, що сталося:
print(f'points_in_g1:{points_in_g1}n
npoints_in_g2:{points_in_g2}n
ngroup:{group}')
В результаті чого:
points_in_g1:[array([5, 3])]
points_in_g2:[array([10, 15]), array([15, 12]),
array([24, 10]), array([30, 45]),
array([85, 70]), array([71, 80]),
array([60, 78]), array([55, 52]),
array([80, 91])]
group:[1, 2, 2, 2, 2, 2, 2, 2, 2, 2]
Ми також можемо побудувати графік результату кластеризації з різними кольорами на основі призначених груп, використовуючи Seaborn scatterplot()
з group
як hue
Аргумент:
import seaborn as sns
sns.scatterplot(x=points[:, 0], y=points[:, 1], hue=group)
Чітко видно, що лише наша перша точка віднесена до групи 1, а всі інші точки були віднесені до групи 2. Цей результат відрізняється від того, що ми передбачали на початку. Враховуючи різницю між нашими результатами та нашими початковими очікуваннями, чи є спосіб змінити це? Здається є!
Один із підходів полягає в тому, щоб повторити процес і вибрати різні точки, які будуть посиланнями для груп. Це змінить наші результати, сподіваємося, більше відповідатимуть тим, що ми передбачали на початку. Цього разу вдруге ми могли вибрати їх не навмання, як робили раніше, а отримавши a значити усіх наших уже згрупованих точок. Таким чином, ці нові точки можуть бути розташовані в середині відповідних груп.
Наприклад, якщо в другій групі були лише бали (10, 15)
, (30, 45)
. Нові центральний крапка була б (10 + 30)/2
та (15+45)/2
– що дорівнює (20, 30)
.
Оскільки ми помістили наші результати в списки, ми можемо спочатку конвертувати їх у numpy
масиви, виберіть їх xs, ys, а потім отримайте значити:
g1_center = [np.array(points_in_g1)[:, 0].mean(), np.array(points_in_g1)[:, 1].mean()]
g2_center = [np.array(points_in_g2)[:, 0].mean(), np.array(points_in_g2)[:, 1].mean()]
g1_center, g2_center
Поради: Спробуйте використовувати numpy
і масивів NumPy якомога більше. Вони оптимізовані для підвищення продуктивності та спрощення багатьох операцій лінійної алгебри. Кожного разу, коли ви намагаєтесь розв’язати якусь задачу лінійної алгебри, вам обов’язково слід поглянути на numpy
документацію, щоб перевірити її наявність numpy
метод, призначений для вирішення вашої проблеми. Шанс є!
Щоб повторити процес із нашими новими центральними точками, давайте перетворимо наш попередній код у функцію, виконаємо її та подивимося, чи були якісь зміни в групуванні точок:
def assigns_points_to_two_groups(g1_center, g2_center):
points_in_g1 = []
points_in_g2 = []
group = []
for p in points:
x1, y1 = p[0], p[1]
euclidean_distance_g1 = np.sqrt((g1_center[0] - x1)**2 + (g1_center[1] - y1)**2)
euclidean_distance_g2 = np.sqrt((g2_center[0] - x1)**2 + (g2_center[1] - y1)**2)
if euclidean_distance_g1 < euclidean_distance_g2:
points_in_g1.append(p)
group.append(1)
else:
points_in_g2.append(p)
group.append(2)
return points_in_g1, points_in_g2, group
Примітка: Якщо ви помітили, що повторюєте один і той самий код знову і знову, вам слід обернути цей код в окрему функцію. Вважається найкращою практикою організувати код у функції, особливо тому, що вони полегшують тестування. Тестувати ізольований фрагмент коду легше, ніж повний код без будь-яких функцій.
Давайте викличемо функцію та збережемо її результати points_in_g1
, points_in_g2
та group
змінні:
points_in_g1, points_in_g2, group = assigns_points_to_two_groups(g1_center, g2_center)
points_in_g1, points_in_g2, group
А також побудуйте діаграму розсіювання з кольоровими точками, щоб візуалізувати розподіл груп:
sns.scatterplot(x=points[:, 0], y=points[:, 1], hue=group)
Здається, кластеризація наших точок є стає краще. Але все ж у середині графіка є дві точки, які можна віднести до будь-якої групи, враховуючи їхню близькість до обох груп. Розроблений нами алгоритм призначає обидві ці точки до другої групи.
Це означає, що ми, ймовірно, можемо повторити процес ще раз, взявши середні Xs і Ys, створивши дві нові центральні точки (центроїди) до наших груп і перепризначаючи їх залежно від відстані.
Давайте також створимо функцію для оновлення центроїдів. Весь процес тепер можна звести до кількох викликів цієї функції:
def updates_centroids(points_in_g1, points_in_g2):
g1_center = np.array(points_in_g1)[:, 0].mean(), np.array(points_in_g1)[:, 1].mean()
g2_center = np.array(points_in_g2)[:, 0].mean(), np.array(points_in_g2)[:, 1].mean()
return g1_center, g2_center
g1_center, g2_center = updates_centroids(points_in_g1, points_in_g2)
points_in_g1, points_in_g2, group = assigns_points_to_two_groups(g1_center, g2_center)
sns.scatterplot(x=points[:, 0], y=points[:, 1], hue=group)
Зверніть увагу, що після цієї третьої ітерації кожна з точок тепер належить до різних кластерів. Здається, результати стають кращими – давайте повторимо це ще раз. Тепер іду до четверта ітерація нашого методу:
g1_center, g2_center = updates_centroids(points_in_g1, points_in_g2)
points_in_g1, points_in_g2, group = assigns_points_to_two_groups(g1_center, g2_center)
sns.scatterplot(x=points[:, 0], y=points[:, 1], hue=group)
Це четвертий раз ми отримали той самий результат як і попередній. Отже, здається, що наші бали більше не змінюватимуться у групах, наш результат досяг певної стабільності – він перейшов у незмінний стан, або збіглися. Крім того, ми маємо точно такий самий результат, як ми передбачали для 2 груп. Ми також можемо побачити, чи має сенс цей досягнутий поділ.
Давайте коротко нагадаємо, що ми зробили досі. Ми географічно розділили наші 10 магазинів на дві секції: одні розташовані в нижньому південно-західному регіоні, інші — на північному сході. Може бути цікаво зібрати більше даних, окрім тих, що ми вже маємо – дохід, щоденну кількість клієнтів і багато іншого. Таким чином ми можемо провести детальніший аналіз і, можливо, отримати цікавіші результати.
Подібні кластеризаційні дослідження можна проводити, коли вже створений бренд хоче вибрати територію для відкриття нового магазину. У цьому випадку крім розташування враховується багато інших змінних.
Що все це має робити з алгоритмом K-Means?
Виконуючи ці кроки, ви, можливо, задавалися питанням, яке відношення вони мають до алгоритму K-Means. Процес, який ми проводили досі, є Алгоритм K-середніх. Коротше кажучи, ми визначили кількість груп/кластерів, випадковим чином вибрали початкові точки та оновили центроїди на кожній ітерації, доки кластери не зійшлися. В основному ми виконали весь алгоритм вручну, ретельно виконуючи кожен крок.
Команда K в K-Means походить від кількість кластерів які потрібно встановити до початку процесу ітерації. У нашому випадку К = 2. Ця характеристика іноді розглядається як негативний враховуючи, що існують інші методи кластеризації, такі як ієрархічна кластеризація, для яких не потрібно заздалегідь мати фіксовану кількість кластерів.
Завдяки його використанню засобів, K-засобів також стає чутливий до викидів і екстремальних значень – вони підвищують мінливість і ускладнюють виконання наших центроїдів своєї ролі. Отже, усвідомлюйте необхідність виконання екстремальні значення та аналіз викидів перед проведенням кластеризації за допомогою алгоритму K-Means.
Крім того, зверніть увагу, що наші точки були сегментовані на прямі частини, немає кривих при створенні кластерів. Це також може бути недоліком алгоритму K-Means.
Примітка: Якщо вам потрібно, щоб він був більш гнучким і адаптованим до еліпсів та інших форм, спробуйте використати a узагальнена K-середня модель суміші Гауса. Ця модель може адаптуватися до кластерів еліптичної сегментації.
K-Means також має багато Переваги! Він добре працює на великі набори даних з якими може стати важко впоратися, якщо ви використовуєте деякі типи алгоритмів ієрархічної кластеризації. Це також гарантує конвергенцію, і може легко узагальнювати та пристосовувати. Крім того, це, ймовірно, найбільш використовуваний алгоритм кластеризації.
Тепер, коли ми переглянули всі кроки, виконані в алгоритмі K-Means, і зрозуміли всі його плюси і мінуси, ми нарешті можемо реалізувати K-Means за допомогою бібліотеки Scikit-Learn.
Як реалізувати алгоритм K-Means за допомогою scikit-learn
Щоб подвійно перевірити наш результат, давайте повторимо цей процес, але тепер з використанням 3 рядків коду з sklearn
:
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=2, random_state=42)
kmeans.fit(points)
kmeans.labels_
Тут мітки такі ж, як у попередніх групах. Давайте просто швидко побудуємо результат:
sns.scatterplot(x = points[:,0], y = points[:,1], hue=kmeans.labels_)
Отриманий графік такий самий, як і в попередньому розділі.
Ознайомтеся з нашим практичним практичним посібником із вивчення Git з передовими методами, прийнятими в галузі стандартами та включеною шпаргалкою. Припиніть гуглити команди Git і фактично вчитися це!
Примітка: Лише подивившись на те, як ми виконали алгоритм K-Means за допомогою Scikit-Learn, ви можете скласти враження, що це безпроблемно, і вам не потрібно про це надто турбуватися. Лише 3 рядки коду виконують усі кроки, які ми обговорювали в попередньому розділі, коли ми крок за кроком розглядали алгоритм K-Means. але, диявол криється в деталях в цьому випадку! Якщо ви не розумієте всіх етапів і обмежень алгоритму, ви, швидше за все, зіткнетеся з ситуацією, коли алгоритм K-Means дає вам результати, яких ви не очікували.
За допомогою Scikit-Learn ви також можете ініціалізувати K-Means для швидшої конвергенції, встановивши init='k-means++'
аргумент. У ширшому плані, K-означає++ все ще вибирає k початкові центри кластерів випадковим чином дотримуючись рівномірного розподілу. Потім кожен наступний центр кластера вибирається з решти точок даних не шляхом обчислення лише міри відстані, а з використанням ймовірності. Використання ймовірності пришвидшує роботу алгоритму, і це корисно при роботі з дуже великими наборами даних.
Метод ліктя – вибір найкращої кількості груп
Все йде нормально! Ми кластеризували 10 магазинів на основі евклідової відстані між точками та центроїдами. Але як щодо тих двох точок у середині графіка, які трохи важче згрупувати? Чи не могли вони теж окрему групу створити? Невже ми помилилися, обравши? K = 2 групи? Можливо, ми й справді мали K = 3 групи? Ми навіть могли мати більше трьох груп і не знати про це.
Тут ставиться питання як визначити кількість груп (K) у K-Means. Щоб відповісти на це запитання, нам потрібно зрозуміти, чи існував би «кращий» кластер для іншого значення K.
Наївний спосіб знайти це шляхом кластеризації точок з різними значеннями K, отже, для K=2, K=3, K=4 і так далі:
for number_of_clusters in range(1, 11):
kmeans = KMeans(n_clusters = number_of_clusters, random_state = 42)
kmeans.fit(points)
Але точки кластеризації різні Ks тільки не вистачить щоб зрозуміти, чи ми вибрали ідеальне значення для K. Нам потрібен спосіб оцінити якість кластеризації для кожного K ми вибрали.
Розрахунок вручну Сума квадратів кластера (WCSS)
Ось ідеальне місце для вимірювання того, наскільки наші згруповані точки близькі одна до одної. По суті, це описує, скільки дисперсія ми маємо всередині одного кластера. Ця міра називається Сума квадратів у кластеріабо WCSS для короткого. Чим менше WCSS, тим ближче наші точки, тому ми маємо більш добре сформований кластер. Формулу WCSS можна використовувати для будь-якої кількості кластерів:
$$
WCSS = sum(Pi_1 – Centroid_1)^2 + cdots + sum(Pi_n – Centroid_n)^2
$$
Примітка: У цьому посібнику ми використовуємо Евклідова відстань щоб отримати центроїд, але інші міри відстані, такі як Манхеттен, також можна використовувати.
Тепер ми можемо припустити, що ми вибрали два кластери і спробуємо реалізувати WCSS, щоб краще зрозуміти, що таке WCSS і як ним користуватися. Як стверджує формула, нам потрібно підсумувати квадрати різниць між усіма точками кластера та центроїдами. Отже, якщо наша перша точка з першої групи (5, 3)
і наш останній центроїд (після конвергенції) першої групи (16.8, 17.0)
, WCSS буде:
$$
WCSS = сума((5,3) – (16.8, 17.0))^2
$$
$$
WCSS = сума((5-16.8) + (3-17.0))^2
$$
$$
WCSS = сума((-11.8) + (-14.0))^2
$$
$$
WCSS = сума((-25.8))^2
$$
$$
WCSS = 335.24
$$
Цей приклад ілюструє, як ми обчислюємо WCSS для однієї точки з кластера. Але кластер зазвичай містить більше однієї точки, і ми повинні брати їх до уваги при розрахунку WCSS. Ми зробимо це, визначивши функцію, яка отримує кластер точок і центроїдів і повертає суму квадратів:
def sum_of_squares(cluster, centroid):
squares = []
for p in cluster:
squares.append((p - centroid)**2)
ss = np.array(squares).sum()
return ss
Тепер ми можемо отримати суму квадратів для кожного кластера:
g1 = sum_of_squares(points_in_g1, g1_center)
g2 = sum_of_squares(points_in_g2, g2_center)
І підсумуйте результати, щоб отримати підсумок WCSS:
g1 + g2
Це призводить до:
2964.3999999999996
Отже, в нашому випадку коли K дорівнює 2, загальна WCSS дорівнює 2964.39. Тепер ми можемо змінити Ks і обчислити WCSS для всіх них. Таким чином ми зможемо зрозуміти, що K ми повинні вибрати, щоб наша кластеризація працювала найкраще.
Розрахунок WCSS використання scikit-learn
На щастя, нам не потрібно вручну обчислювати WCSS для кожного K. Після виконання кластеризації K-Means для заданої кількості кластерів ми можемо отримати його WCSS за допомогою inertia_
атрибут. Тепер ми можемо повернутися до наших K-середніх for
використовуйте його для перемикання кількості кластерів і списку відповідних значень WCSS:
wcss = []
for number_of_clusters in range(1, 11):
kmeans = KMeans(n_clusters = number_of_clusters, random_state = 42)
kmeans.fit(points)
wcss.append(kmeans.inertia_)
wcss
Зверніть увагу, що друге значення в списку точно таке ж, яке ми обчислювали раніше K = 2:
[18272.9, # For k=1
2964.3999999999996, # For k=2
1198.75, # For k=3
861.75,
570.5,
337.5,
175.83333333333334,
79.5,
17.0,
0.0]
Щоб візуалізувати ці результати, давайте побудуємо наш графік Ks разом зі значеннями WCSS:
ks = [1, 2, 3, 4, 5 , 6 , 7 , 8, 9, 10]
plt.plot(ks, wcss)
Існує перерва на сюжеті, коли x = 2
, нижня точка лінії, і ще нижча, коли x = 3
. Зверніть увагу, що це нагадує нам про форма ліктя. Зображуючи Ks разом із WCSS, ми використовуємо Метод ліктя вибрати кількість Кс. І вибране K є точно найнижчою точкою ліктя, так би було 3
замість 2
, в нашому випадку:
ks = [1, 2, 3, 4, 5 , 6 , 7 , 8, 9, 10]
plt.plot(ks, wcss);
plt.axvline(3, linestyle='--', color='r')
Ми можемо знову запустити кластерний алгоритм K-Means, щоб побачити, як виглядатимуть наші дані три скупчення:
kmeans = KMeans(n_clusters=3, random_state=42)
kmeans.fit(points)
sns.scatterplot(x = points[:,0], y = points[:,1], hue=kmeans.labels_)
Ми вже були задоволені двома кластерами, але згідно з методом ліктя, три кластери краще підходили б для наших даних. У цьому випадку ми мали б три види магазинів замість двох. До використання ліктьового методу ми думали про південно-західний і північно-східний кластери магазинів, тепер у нас також є магазини в центрі. Можливо, це було б гарним місцем для відкриття іншого магазину, оскільки поблизу було б менше конкуренції.
Альтернативні кластерні показники якості
Існують також інші показники, які можна використовувати при оцінці якості кластера:
- Силуетна оцінка – аналізує не тільки відстань між внутрішньокластерними точками, а й між самими кластерами
- Сума квадратів між кластерами (BCSS) – метрика, яка доповнює WCSS
- Помилка суми квадратів (ESS)
- Максимальний радіус – вимірює найбільшу відстань від точки до її центроїда
- Середній радіус – сума найбільшої відстані від точки до її центроїда, поділена на кількість кластерів.
Рекомендується поекспериментувати та ознайомитися з кожним із них, оскільки залежно від проблеми деякі з альтернатив можуть бути більш застосовними, ніж найбільш широко використовувані показники (WCSS та оцінка силуету).
Зрештою, як і з багатьма алгоритмами науки про дані, ми хочемо зменшити дисперсію всередині кожного кластера та максимізувати дисперсію між різними кластерами. Отже, ми маємо більше визначених і роздільних кластерів.
Застосування K-середніх до іншого набору даних
Давайте використаємо те, що ми дізналися, на іншому наборі даних. Цього разу ми спробуємо знайти групи подібних вин.
Примітка: Ви можете завантажити набір даних тут.
Ми починаємо з імпорту pandas
читати wine-clustering
CSV (Значення, розділені комами) файл у Dataframe
структура:
import pandas as pd
df = pd.read_csv('wine-clustering.csv')
Після завантаження давайте поглянемо на перші п’ять записів даних за допомогою head()
метод:
df.head()
Це призводить до:
Alcohol Malic_Acid Ash Ash_Alcanity Magnesium Total_Phenols Flavanoids Nonflavanoid_Phenols Proanthocyanins Color_Intensity Hue OD280 Proline
0 14.23 1.71 2.43 15.6 127 2.80 3.06 0.28 2.29 5.64 1.04 3.92 1065
1 13.20 1.78 2.14 11.2 100 2.65 2.76 0.26 1.28 4.38 1.05 3.40 1050
2 13.16 2.36 2.67 18.6 101 2.80 3.24 0.30 2.81 5.68 1.03 3.17 1185
3 14.37 1.95 2.50 16.8 113 3.85 3.49 0.24 2.18 7.80 0.86 3.45 1480
4 13.24 2.59 2.87 21.0 118 2.80 2.69 0.39 1.82 4.32 1.04 2.93 735
У нас є багато вимірювань речовин, присутніх у винах. Тут нам також не потрібно буде перетворювати категоричні стовпці, оскільки всі вони числові. Тепер давайте подивимося на описову статистику з describe()
метод:
df.describe().T
Таблиця опису:
count mean std min 25% 50% 75% max
Alcohol 178.0 13.000618 0.811827 11.03 12.3625 13.050 13.6775 14.83
Malic_Acid 178.0 2.336348 1.117146 0.74 1.6025 1.865 3.0825 5.80
Ash 178.0 2.366517 0.274344 1.36 2.2100 2.360 2.5575 3.23
Ash_Alcanity 178.0 19.494944 3.339564 10.60 17.2000 19.500 21.5000 30.00
Magnesium 178.0 99.741573 14.282484 70.00 88.0000 98.000 107.0000 162.00
Total_Phenols 178.0 2.295112 0.625851 0.98 1.7425 2.355 2.8000 3.88
Flavanoids 178.0 2.029270 0.998859 0.34 1.2050 2.135 2.8750 5.08
Nonflavanoid_Phenols 178.0 0.361854 0.124453 0.13 0.2700 0.340 0.4375 0.66
Proanthocyanins 178.0 1.590899 0.572359 0.41 1.2500 1.555 1.9500 3.58
Color_Intensity 178.0 5.058090 2.318286 1.28 3.2200 4.690 6.2000 13.00
Hue 178.0 0.957449 0.228572 0.48 0.7825 0.965 1.1200 1.71
OD280 178.0 2.611685 0.709990 1.27 1.9375 2.780 3.1700 4.00
Proline 178.0 746.893258 314.907474 278.00 500.500 673.500 985.0000 1680.00
Дивлячись на таблицю, видно, що є мінливість даних – для деяких колонок, наприклад Alchool
є ще, і для інших, як напр Malic_Acid
, менше. Тепер ми можемо перевірити, чи є вони null
або NaN
значення в нашому наборі даних:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 178 entries, 0 to 177
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Alcohol 178 non-null float64
1 Malic_Acid 178 non-null float64
2 Ash 178 non-null float64
3 Ash_Alcanity 178 non-null float64
4 Magnesium 178 non-null int64
5 Total_Phenols 178 non-null float64
6 Flavanoids 178 non-null float64
7 Nonflavanoid_Phenols 178 non-null float64
8 Proanthocyanins 178 non-null float64
9 Color_Intensity 178 non-null float64
10 Hue 178 non-null float64
11 OD280 178 non-null float64
12 Proline 178 non-null int64
dtypes: float64(11), int64(2)
memory usage: 18.2 KB
Немає необхідності видаляти або вводити дані, оскільки в наборі даних немає порожніх значень. Ми можемо використати Seaborn pairplot()
щоб побачити розподіл даних і перевірити, чи набір даних утворює пари стовпців, які можуть бути цікавими для кластеризації:
sns.pairplot(df)
Дивлячись на парний графік, два стовпці здаються перспективними для цілей кластеризації – Alcohol
та OD280
(це метод визначення концентрації білка у винах). Здається, що на ділянках є 3 окремі кластери, які поєднують два з них.
Є й інші стовпці, які, здається, також корелюються. Дуже помітно Alcohol
та Total_Phenols
та Alcohol
та Flavanoids
. Вони мають чудові лінійні зв’язки, які можна спостерігати на парному графіку.
Оскільки ми зосереджені на кластеризації за допомогою K-Means, давайте, скажімо, виберемо одну пару стовпців Alcohol
та OD280
і протестуйте метод ліктя для цього набору даних.
Примітка: При використанні більшої кількості стовпців набору даних виникне необхідність або побудувати тривимірний графік, або зменшити дані до основні компоненти (використання PCA). Це дійсний і більш поширений підхід, просто переконайтеся, що ви вибрали основні компоненти на основі того, наскільки вони пояснюють, і майте на увазі, що при зменшенні розмірів даних відбувається деяка втрата інформації, тому графік є наближення реальних даних, а не того, як вони є насправді.
Давайте побудуємо діаграму розсіювання з цими двома стовпцями, встановленими як її вісь, щоб ближче розглянути точки, які ми хочемо розділити на групи:
sns.scatterplot(data=df, x='OD280', y='Alcohol')
Тепер ми можемо визначити наші стовпці та використовувати метод ліктя для визначення кількості кластерів. Ми також запустимо алгоритм з kmeans++
просто щоб переконатися, що він сходиться швидше:
values = df[['OD280', 'Alcohol']]
wcss_wine = []
for i in range(1, 11):
kmeans = KMeans(n_clusters = i, init = 'k-means++', random_state = 42)
kmeans.fit(values)
wcss_wine.append(kmeans.inertia_)
Ми розрахували WCSS, тому ми можемо побудувати результати:
clusters_wine = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
plt.plot(clusters_wine, wcss_wine)
plt.axvline(3, linestyle='--', color='r')
Відповідно до ліктьового методу тут має бути 3 кластери. Для останнього кроку давайте згрупуємо наші точки на 3 кластери та нанесемо на графік ці кластери, позначені кольорами:
kmeans_wine = KMeans(n_clusters=3, random_state=42)
kmeans_wine.fit(values)
sns.scatterplot(x = values['OD280'], y = values['Alcohol'], hue=kmeans_wine.labels_)
Ми можемо бачити кластери 0
, 1
та 2
на графіку. На основі нашого аналізу, група 0 містить вина з більшим вмістом білка та меншим вмістом алкоголю, група 1 містить вина з високим вмістом алкоголю та низьким вмістом білка, а також група 2 має високий вміст білка та алкоголю у своїх винах.
Це дуже цікавий набір даних, і я закликаю вас продовжити аналіз, кластеризуючи дані після нормалізації та PCA, а також інтерпретуючи результати та знаходячи нові зв’язки.
Висновок
K-засоби кластеризація — це простий, але дуже ефективний алгоритм машинного навчання без нагляду для кластеризації даних. Він кластеризує дані на основі евклідової відстані між точками даних. Алгоритм кластеризації K-Means має багато застосувань для групування текстових документів, зображень, відео та багато іншого.