EduFlow

الوحدة 5 · ما بعد الأساسيات

العنقدة وPCA

21 دقراءة 4 دتمرين Python

الدرس في لمحة

جمّع العملاء في مجموعات باستخدام k-means، وارسم ملامح كل عنقود لترى إن كان يعني شيئًا، واضغط أعمدة كثيرة مترابطة في أعمدة قليلة باستخدام PCA.

  1. 1k-means في حركتين
  2. 2تقسيم عملاء Cartwheel إلى شرائح
  3. 3PCA: أعمدة أقل، ومعظم المعلومات

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

ليس كل سؤال يأتي مع labels. يسأل فريق التسويق في Cartwheel: “ما أنواع العملاء لدينا؟” لم يصنّف أحد العملاء بأنواع، فلا هدف للتنبؤ به. يبحث التعلّم غير الموجَّه (unsupervised learning) عن بنية في الـ features وحدها. والأداتان اللتان ستستخدمهما أكثر من غيرهما هما العنقدة (clustering)، التي تجمّع الصفوف المتشابهة، وتقليل الأبعاد، الذي يلخّص أعمدة كثيرة في أعمدة قليلة.

k-means في حركتين

تحتاج k-means إلى قرار واحد منك: عدد العناقيد، k. ثم تكرّر خطوتين:

  1. الإسناد: ضع كل عميل في العنقود الذي مركزه الأقرب.
  2. التحديث: انقل كل مركز إلى متوسط العملاء المسندين إليه.

حين تتوقف الإسنادات عن التغيّر، ينتهي الأمر. وقد تعطي مراكز البداية المختلفة نتائج مختلفة، لذا يشغّل scikit-learn العملية كلها n_init مرة ويحتفظ بالحل الأكثر إحكامًا.

اللوحة اليسرى: نقاط ملوّنة بحسب أقرب مركز من مركزين، يظهران كحلقتين كبيرتين. اللوحة اليمنى: انتقل كل مركز إلى وسط النقاط المسندة إليه. تتكرّر الخطوتان حتى تتوقف الإسنادات عن التغيّر. 1. الإسناد إلى أقرب مركز 2. نقل المركز إلى المتوسط
جولة واحدة من k-means

لأن “الأقرب” مسافة، فالدرس نفسه من أقرب k جيران ينطبق هنا: وحّد المقاييس أولًا، وإلا حسم total_spent بالآلاف كل شيء.

تقسيم عملاء Cartwheel إلى شرائح

استخدم أعمدة السلوك فقط واترك churned خارجًا: فالتقسيم إلى شرائح يجب أن يصف العملاء، لا نتيجتهم. ستنظر إلى الانقطاع بعد ذلك لترى إن كانت الشرائح تعني شيئًا.

Python
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

churn = pd.read_csv("churn.csv")
behaviour = ["orders", "total_spent", "avg_order_value", "days_since_last_order",
             "tenure_days", "mobile_share"]
X = StandardScaler().fit_transform(churn[behaviour])
for k in range(2, 7):
    labels = KMeans(n_clusters=k, n_init=10, random_state=42).fit_predict(X)
    print(f"k={k}  silhouette {silhouette_score(X, labels):.3f}")

segments = make_pipeline(StandardScaler(), KMeans(n_clusters=4, n_init=10, random_state=42))
churn["segment_id"] = segments.fit_predict(churn[behaviour])
profile = churn.groupby("segment_id").agg(
    customers=("orders", "size"), orders=("orders", "mean"),
    avg_order=("avg_order_value", "mean"), days_since=("days_since_last_order", "mean"),
    tenure=("tenure_days", "mean"), churn_rate=("churned", "mean"))
print(profile.round(2))

أولًا، الجزء الصادق. تقيس قيمة silhouette كم يكون كل عميل أقرب إلى عنقوده منه إلى العنقود التالي، من -1 إلى 1. والقيم في حدود 0.25 لكل k تعني أن العملاء لا ينقسمون إلى مجموعات طبيعية واضحة؛ بل يشكّلون طيفًا متصلًا. وهذا معتاد في البيانات السلوكية. لا يجعل ذلك العنقدة عديمة الفائدة، لكنه يعني أن k اختيارك أنت، تتخذه من أجل الفائدة، لا حقيقة اكتشفتها.

ثم الجزء المفيد. رسم ملامح العناقيد الأربعة يعطي شرائح يستطيع المسوِّق تسميتها:

  • العابرون الغائبون (129): طلبان، آخرهما قبل نحو 310 أيام. انقطاع 79%.
  • مشترو القطع الغالية (41): طلبات قليلة بمتوسط 600 دولار. انقطاع 66%.
  • الزبائن الدائمون (79): ثمانية طلبات، آخرها قبل نحو 100 يوم. انقطاع 33%.
  • الجدد والحديثون (149): عضوية قصيرة، وطلبوا قبل نحو شهرين. انقطاع 44%.

تختلف نِسب الانقطاع بحدّة بين الشرائح مع أن k-means لم ترَ churned قط. هذا دليل على أن الشرائح تلتقط شيئًا حقيقيًا، ويعطي فريق التسويق أربع محادثات مختلفة بدل محادثة واحدة.

ليست k-means الخيار الوحيد. فهي تفترض عناقيد شبه مستديرة ومتشابهة الحجم، وهو افتراض يكفي عادةً للشرائح السلوكية. أما العنقدة الهرمية (AgglomerativeClustering) فتُظهر كيف تندمج المجموعات على مستويات مختلفة، وDBSCAN يجد المناطق الكثيفة بأي شكل ويصنّف القيم الشاذة كضجيج. ابدأ بـ k-means للتقسيم إلى شرائح؛ وبدّل حين لا تناسب افتراضاتها بياناتك بوضوح.

PCA: أعمدة أقل، ومعظم المعلومات

في كثير من مجموعات البيانات عشرات الأعمدة المترابطة. بيانات سرطان الثدي فيها 30 قياسًا لأنوية الخلايا، كثير منها تنويعات على الحجم. يجد تحليل المكوّنات الرئيسية (PCA) محاور جديدة، كلٌّ منها مزيج موزون من الأعمدة الأصلية، مرتّبة بحسب مقدار ما تلتقطه من تباين البيانات. احتفظ بالأولى منها ويصبح لديك ملخّص مضغوط.

Python
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.decomposition import PCA
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_breast_cancer(return_X_y=True)
pca = make_pipeline(StandardScaler(), PCA()).fit(X)
ratios = pca[-1].explained_variance_ratio_
print("first two components keep:", ratios[:2].round(3), "=", round(ratios[:2].sum(), 3))
print("components needed for 90%:", int(np.argmax(np.cumsum(ratios) >= 0.90)) + 1)

coords = make_pipeline(StandardScaler(), PCA(n_components=2)).fit_transform(X)
for label, name in [(0, "malignant"), (1, "benign")]:
    print(name, "average position:", coords[y == label].mean(axis=0).round(2))

يحتفظ مكوّنان بـ 63% من التباين في 30 عمودًا موحّد المقاييس، وتحتفظ سبعة مكوّنات بـ 90%. ارسم الإحداثيين وستجد الأورام الخبيثة والحميدة على جانبين متقابلين من المحور الأول، مع أن PCA لم يرَ الـ labels قط. ولهذا يُعدّ رسم PCA ثنائي الأبعاد نظرة أولى معيارية على أي مجموعة بيانات عريضة.

كلتا التقنيتين هنا كلاسيكية ورخيصة. أما الدرس القادم فعن نماذج تتعلّم features خاصة بها من المدخلات الخام: الشبكات العصبية.

أهم النقاط

  • تتناوب k-means بين خطوتين، إسناد كل نقطة إلى أقرب مركز ونقل كل مركز إلى متوسط نقاطه، حتى لا يتغيّر شيء.
  • تستخدم العنقدة المسافات، فوحّد مقاييس الـ features أولًا، واضبط n_init وrandom_state لنتائج قابلة للتكرار.
  • العناقيد لا تكون مفيدة إلا إذا استطعت وصفها؛ فارسم ملامح كلٍّ منها بمتوسطات المجموعة قبل أن تعطيه اسمًا.
  • قيمة silhouette المنخفضة تعني أن البيانات ليست فيها مجموعات طبيعية واضحة؛ وقد تظل الشرائح مفيدة، لكنها اختيار لا اكتشاف.
  • يجد PCA محاور جديدة تلتقط أكبر قدر من التباين؛ ويخبرك explained_variance_ratio_ كم يحتفظ به كل مكوّن.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

شغّلت k-means على أعمدة الانقطاع الخام دون توحيد المقاييس. أيّ عمود سيهيمن على العناقيد؟

السؤال 2 من 3

قيم silhouette من k = 2 إلى 7 كلها بين 0.25 و0.27. ماذا يخبرك ذلك؟

السؤال 3 من 3

يعطي PCA على features سرطان الثدي الثلاثين قيم explained_variance_ratio_ تساوي 0.44 و0.19 لأول مكوّنين. ماذا يُظهر رسم ثنائي الأبعاد لهما؟

تدريب

تمرين Python

شرائح ومنظر مضغوط

  1. أصلح segments بحيث تُوحَّد مقاييس العملاء قبل k-means (4 عناقيد، n_init=10، random_state=42)، واحصل على label واحد لكل عميل في labels.
  2. churn_by_cluster: نسبة الانقطاع في كل عنقود، كـ Series فهرسها label العنقود. وhighest_rate: أكبر هذه النِّسب.
  3. wine_var_2: حصة التباين التي يحتفظ بها أول مكوّنين رئيسيين للـ features الثلاثة عشر في بيانات النبيذ، بعد توحيد المقاييس.

ملفات البيانات: churn.csv

import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

churn = pd.read_csv("churn.csv")
behaviour = ["orders", "total_spent", "avg_order_value", "days_since_last_order",
             "tenure_days", "mobile_share"]

# 1. Segment customers into 4 clusters: StandardScaler, then KMeans(n_clusters=4, n_init=10, random_state=42)
segments = make_pipeline(KMeans(n_clusters=4, n_init=10, random_state=42))
labels = segments.fit_predict(churn[behaviour])

# 2. Churn rate per cluster (a Series indexed by cluster label), and the highest rate
churn_by_cluster = None
highest_rate = None

# 3. PCA on the wine data (13 scaled features): share of variance kept by the first 2 components
X_wine, y_wine = load_wine(return_X_y=True)
wine_var_2 = None
جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

(لا مخرجات)

الفحوص

  • يوحّد segments المقاييس قبل العنقدة
  • تحمل labels الشرائح الأربع المتوقعة
  • churn_by_cluster وhighest_rate صحيحان
  • wine_var_2 هو التباين الذي يحتفظ به مكوّنان