الوحدة 5 · ما بعد الأساسيات
العنقدة وPCA
21 دقراءة 4 دتمرين Python
الدرس في لمحة
جمّع العملاء في مجموعات باستخدام k-means، وارسم ملامح كل عنقود لترى إن كان يعني شيئًا، واضغط أعمدة كثيرة مترابطة في أعمدة قليلة باستخدام PCA.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
ليس كل سؤال يأتي مع labels. يسأل فريق التسويق في Cartwheel: “ما أنواع العملاء لدينا؟” لم يصنّف أحد العملاء بأنواع، فلا هدف للتنبؤ به. يبحث التعلّم غير الموجَّه (unsupervised learning) عن بنية في الـ features وحدها. والأداتان اللتان ستستخدمهما أكثر من غيرهما هما العنقدة (clustering)، التي تجمّع الصفوف المتشابهة، وتقليل الأبعاد، الذي يلخّص أعمدة كثيرة في أعمدة قليلة.
k-means في حركتين
تحتاج k-means إلى قرار واحد منك: عدد العناقيد، k. ثم تكرّر خطوتين:
- الإسناد: ضع كل عميل في العنقود الذي مركزه الأقرب.
- التحديث: انقل كل مركز إلى متوسط العملاء المسندين إليه.
حين تتوقف الإسنادات عن التغيّر، ينتهي الأمر. وقد تعطي مراكز البداية المختلفة نتائج مختلفة، لذا يشغّل scikit-learn العملية كلها n_init مرة ويحتفظ بالحل الأكثر إحكامًا.
لأن “الأقرب” مسافة، فالدرس نفسه من أقرب k جيران ينطبق هنا: وحّد المقاييس أولًا، وإلا حسم total_spent بالآلاف كل شيء.
تقسيم عملاء Cartwheel إلى شرائح
استخدم أعمدة السلوك فقط واترك churned خارجًا: فالتقسيم إلى شرائح يجب أن يصف العملاء، لا نتيجتهم. ستنظر إلى الانقطاع بعد ذلك لترى إن كانت الشرائح تعني شيئًا.
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) محاور جديدة، كلٌّ منها مزيج موزون من الأعمدة الأصلية، مرتّبة بحسب مقدار ما تلتقطه من تباين البيانات. احتفظ بالأولى منها ويصبح لديك ملخّص مضغوط.
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_كم يحتفظ به كل مكوّن.