מבוא
אשכול K-Means הוא אחד האלגוריתמים הנפוצים ביותר של למידת מכונה ללא פיקוח היוצרים אשכולות של נתונים המבוססים על הדמיון בין מופעי נתונים.
במדריך זה, נסקור תחילה דוגמה פשוטה כדי להבין כיצד פועל אלגוריתם 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. לאחר מכן, נעביר אותו ל-Skikit-Learn.
מה תהיה הדרך הטובה ביותר לקבוע אם יש שתיים או שלוש קבוצות של נקודות? דרך פשוטה אחת תהיה פשוט לבחור מספר אחד של קבוצות - למשל שתיים - ואז לנסות לקבץ נקודות על סמך הבחירה הזו.
נניח שהחלטנו שיש שתי קבוצות של החנויות שלנו (נקודות). כעת, עלינו למצוא דרך להבין אילו נקודות שייכות לאיזו קבוצה. ניתן לעשות זאת על ידי בחירת נקודה אחת לייצג קבוצה 1 ואחד לייצג קבוצה 2. נקודות אלו ישמשו כאסמכתא בעת מדידת המרחק מכל שאר הנקודות לכל קבוצה.
בצורה כזו, תגיד נקודה (5, 3)
בסופו של דבר שייך לקבוצה 1, ונקודה (79, 60)
לקבוצה 2. כאשר מנסים להקצות נקודה חדשה (6, 3)
לקבוצות, עלינו למדוד את המרחק שלו לשתי הנקודות הללו. במקרה של הנקודה (6, 3)
is קרוב יותר אל ה (5, 3)
, לכן הוא שייך לקבוצה המיוצגת על ידי אותה נקודה - קבוצה 1. כך נוכל לקבץ בקלות את כל הנקודות לקבוצות מתאימות.
בדוגמה זו, מלבד קביעת מספר הקבוצות (אשכולות) – אנחנו גם בוחרים כמה נקודות להיות א הפניה של מרחק עבור נקודות חדשות של כל קבוצה.
זה הרעיון הכללי להבין את קווי הדמיון בין החנויות שלנו. בואו ליישם את זה בפועל - אנחנו יכולים לבחור תחילה את שתי נקודות ההתייחסות ב אקראי. נקודת ההתייחסות של קבוצה 1 יהיה (5, 3)
ונקודת ההתייחסות של קבוצה 2 יהיה (10, 15)
. אנחנו יכולים לבחור את שתי הנקודות שלנו numpy
מערך לפי [0]
ו [1]
אינדקסים ולאחסן אותם g1
(קבוצה 1) ו g2
משתנים (קבוצה 2):
g1 = points[0]
g2 = points[1]
לאחר שנעשה זאת, עלינו לחשב את המרחק מכל שאר הנקודות לנקודות הייחוס הללו. זה מעלה שאלה חשובה - איך למדוד את המרחק הזה. למעשה, אנו יכולים להשתמש בכל מדידת מרחק, אך לצורך המדריך הזה, הבה נשתמש ב-Euclidian Distance_.
זה יכול להיות שימושי לדעת שמידת המרחק האוקלידית מבוססת על משפט פיתגורס:
$$
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's scatterplot()
עם group
בתור hue
טענה:
import seaborn as sns
sns.scatterplot(x=points[:, 0], y=points[:, 1], hue=group)
ניכר בבירור שרק הנקודה הראשונה שלנו מוקצית לקבוצה 1, וכל שאר הנקודות הוקצו לקבוצה 2. התוצאה הזו שונה ממה שדמיינו בהתחלה. בהתחשב בהבדל בין התוצאות שלנו לציפיות הראשוניות שלנו - האם יש דרך לשנות זאת? נראה שיש!
גישה אחת היא לחזור על התהליך ולבחור נקודות שונות שיהוו התייחסות לקבוצות. זה ישנה את התוצאות שלנו, בתקווה, יותר בקנה אחד עם מה שחזינו בהתחלה. בפעם השנייה, יכולנו לבחור אותם לא באקראי כפי שעשינו בעבר, אלא על ידי קבלת א אומר מכל הנקודות שכבר מקובצות שלנו. כך, ניתן יהיה למקם את הנקודות החדשות הללו באמצע הקבוצות המקבילות.
למשל, אם לקבוצה השנייה היו רק נקודות (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, יצירת שתי נקודות מרכזיות חדשות (צנטרואידים) לקבוצות שלנו והקצאתן מחדש על סמך מרחק.
בואו גם ניצור פונקציה לעדכון ה-centroids. ניתן לצמצם את כל התהליך כעת למספר קריאות של פונקציה זו:
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-Means. בקצרה, קבענו את מספר הקבוצות/אשכולות, בחרנו באקראי נקודות ראשוניות ועדכנו מוקדים בכל איטרציה עד שהאשכולות התכנסו. בעצם ביצענו את כל האלגוריתם ביד - בצענו בקפידה כל שלב.
השמיים K ב-K-Means מגיע מה מספר אשכולות שיש להגדיר לפני תחילת תהליך האיטרציה. במקרה שלנו K = 2. מאפיין זה נתפס לפעמים כ שלילי בהתחשב שישנן שיטות אשכולות אחרות, כמו אשכול היררכי, שאינן צריכות להיות עם מספר קבוע של אשכולות מראש.
בשל השימוש באמצעים, גם K-means הופך רגיש לחריגות וערכים קיצוניים - הם משפרים את השונות ומקשים על הסנטרואידים שלנו למלא את תפקידם. לכן, היו מודעים לצורך לבצע ערכים קיצוניים וניתוח חריגים לפני ביצוע אשכול באמצעות אלגוריתם K-Means.
כמו כן, שימו לב שהנקודות שלנו היו מפולחות בחלקים ישרים, אין עקומות בעת יצירת האשכולות. זה יכול להיות גם חסרון של אלגוריתם K-Means.
הערה: כאשר אתה צריך שזה יהיה גמיש יותר וניתן להתאמה לאליפסות וצורות אחרות, נסה להשתמש ב-a מודל תערובת גאוסית כללית של K-פירושו. מודל זה יכול להסתגל לאשכולות פילוח אליפטיים.
גם ל-K-Means יש הרבה יתרונות! זה מתפקד טוב על מערכי נתונים גדולים דבר שעלול להיות קשה לטיפול אם אתה משתמש בכמה סוגים של אלגוריתמי אשכול היררכי. זה גם מבטיחה התכנסות, ויכול בקלות לְהַכלִיל ו להסתגל. חוץ מזה, זה כנראה אלגוריתם האשכולות הנפוץ ביותר.
כעת, לאחר שעברנו על כל השלבים שבוצעו באלגוריתם K-Means, והבנו את כל היתרונות והחסרונות שלו, נוכל סוף סוף ליישם את K-Means באמצעות ספריית Scikit-Learn.
כיצד ליישם אלגוריתם K-Means באמצעות סקיקיט-למד
כדי לבדוק שוב את התוצאה שלנו, בוא נעשה את התהליך הזה שוב, אבל עכשיו נשתמש ב-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-Means++ עדיין בוחר את 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 = sum((5,3) – (16.8, 17.0))^2
$$
$$
WCSS = sum((5-16.8) + (3-17.0))^2
$$
$$
WCSS = sum((-11.8) + (-14.0))^2
$$
$$
WCSS = sum((-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 שימוש סקיקיט-למד
למרבה המזל, אנחנו לא צריכים לחשב ידנית את ה-WCSS עבור כל אחד מהם K. לאחר ביצוע אשכול K-Means עבור מספר האשכולות הנתון, נוכל להשיג את ה-WCSS שלו באמצעות inertia_
תְכוּנָה. עכשיו, אנחנו יכולים לחזור ל-K-Means שלנו 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, אנו משתמשים ב- שיטת המרפק כדי לבחור את מספר Ks. וה שנבחרה 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
- שגיאת סכום הריבועים (SSE)
- רדיוס מקסימלי - מודד את המרחק הגדול ביותר מנקודה למרכז שלה
- רדיוס ממוצע – סכום המרחק הגדול ביותר מנקודה למרכזה חלקי מספר האשכולות.
מומלץ להתנסות ולהכיר כל אחד מהם שכן בהתאם לבעיה, חלק מהחלופות יכולות להיות ישימות יותר מהמדדים הנפוצים ביותר. (WCSS וציון צללית).
בסופו של דבר, כמו באלגוריתמים רבים של מדעי הנתונים, אנו רוצים לצמצם את השונות בתוך כל אשכול ולמקסם את השונות בין אשכולות שונים. אז יש לנו אשכולות מוגדרים יותר וניתנים להפרדה.
החלת K-Means על מערך נתונים אחר
בואו נשתמש במה שלמדנו על מערך נתונים אחר. הפעם, ננסה למצוא קבוצות של יינות דומים.
הערה: אתה יכול להוריד את מערך הנתונים כאן.
אנחנו מתחילים בייבוא pandas
לקרוא את wine-clustering
CSV (ערכים מופרדים בפסיק) התיק לתוך Dataframe
מבנה you
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
, ובדוק את שיטת המרפק עבור מערך נתונים זה.
הערה: בעת שימוש בעמודות נוספות של מערך הנתונים, יהיה צורך בשרטוט ב-3 ממדים או בהקטנת הנתונים ל- רכיבים עיקריים (שימוש ב-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- אמצעים clustering הוא אלגוריתם למידת מכונה פשוט אך יעיל מאוד ללא פיקוח לאשכול נתונים. הוא מקבץ נתונים המבוססים על המרחק האוקלידי בין נקודות נתונים. לאלגוריתם K-Means אשכולות יש שימושים רבים לקיבוץ מסמכי טקסט, תמונות, סרטונים ועוד הרבה יותר.