بتذكر مرة، كنت قاعد مع مدير منتج في شركة ناشئة بقطاع الإعلام. الزلمة كان شابك إيديه وحاططهم على راسه، وبقلي بحسرة: “يا أبو عمر، تعبنا! بنعرض للمستخدمين أكتر فيديوهات عليها مشاهدات، بس ما حدا بعجبه اشي، نسبة النقر (CTR) بالأرض”. كان واضح إنه محبط وشايف إن كل جهدهم في إنتاج المحتوى بروح عالفاضي.
ابتسمت ابتسامة خفيفة وقلتله: “اسمعني يا صاحبي، مشكلتك مش بالمحتوى، مشكلتك إنك بتعامل كل الناس كأنهم شخص واحد. أنت بتفترض إنه اللي بعجب ألف واحد، لازم يعجب الكل. شو رأيك نخلّي الرياضيات تحكيلنا كل مستخدم شو بحب عنجد؟ شو رأيك نبني نظام توصية شخصي بفهم أذواق الناس؟”. لمعت عيونه للحظة، ومن هداك اليوم، بلشت رحلتنا مع عالم ال-Matrix Factorization اللي غيرت قواعد اللعبة عندهم.
في هالمقالة، رح آخدكم معي في نفس الرحلة، ونبني سوا فهم عميق وعملي لأنظمة التوصية الحديثة. يلا نبلش.
ليش التوصيات التقليدية ما بتزبط؟ قصة مصفوفة التفاعلات الفارغة
تخيل معي عندك منصة فيديوهات زي يوتيوب أو نتفليكس، أو متجر إلكتروني زي أمازون. عشان تفهم علاقة المستخدمين بالمنتجات (أو الفيديوهات)، ممكن تبني مصفوفة (matrix) ضخمة: الصفوف بتمثل المستخدمين، والأعمدة بتمثل المنتجات.
لو المستخدم “أحمد” شاهد الفيلم “Inception”، بنحط علامة (مثلاً رقم 1) في الخلية اللي بتقاطع فيها صف “أحمد” مع عمود “Inception”. هسا المشكلة وين؟
المشكلة إنه هالمصفوفة رح تكون فارغة بمعظمها (Sparse). لو عندك مليون مستخدم ومليون فيلم، معظم المستخدمين ما شافوا إلا عدد قليل جداً من الأفلام. يعني 99.99% من خلايا المصفوفة رح تكون أصفار! وهذا هو التحدي الأكبر.
عيوب الحلول السطحية
لما تكون المصفوفة هيك، أول حل بيخطر عالبال هو الحلول البسيطة:
- “الأكثر مبيعاً” أو “الأكثر مشاهدة”: هذا الحل بسيط، لكنه غير شخصي بالمرة. بيعطي نفس التوصيات لكل الناس، وبتجاهل الأذواق الفردية تماماً.
- تأثير “الأغنياء يزدادون غنىً”: هاي الخوارزمية بتضلها ترشح المنتجات المشهورة أصلاً، فبتصير أشهر وأشهر، بينما المنتجات الجديدة أو المتخصصة (Long-tail) ما بتاخد فرصة للظهور، مع إنها ممكن تكون مناسبة جداً لفئة معينة من المستخدمين.
هون بيجي دور الحلول الأذكى، اللي بتقدر تقرأ ما بين السطور وتفهم الأنماط المخفية في هاي المصفوفة الفارغة.
سحر تفكيك المصفوفات (Matrix Factorization): كيف نحول الفراغ إلى معرفة
الفكرة عبقرية وبسيطة في جوهرها. بدل ما نتعامل مع المصفوفة الضخمة والفارغة مباشرة، رح نحاول “نحلّلها” أو “نفككها” لمصفوفتين أصغر حجماً بكثير، بحيث لو ضربناهم ببعض، يرجعوا يعطونا تقدير قريب للمصفوفة الأصلية.
رياضياً، لو مصفوفة التفاعلات اسمها R (بحجم: عدد المستخدمين × عدد المنتجات)، إحنا بدنا نلاقي مصفوفتين:
- U (مصفوفة المستخدمين، بحجم: عدد المستخدمين × k)
- V (مصفوفة المنتجات، بحجم: عدد المنتجات × k)
بحيث يتحقق التالي: R ≈ U * VT
طيب شو هدول المصفوفتين U و V؟ وشو يعني k؟
هون السحر الحقيقي. كل صف في المصفوفة U هو عبارة عن “متّجه” (vector) بمثل مستخدم معين. وكل صف في المصفوفة V هو متجه بمثل منتج معين. الرقم k هو عدد “الأبعاد” أو “السمات الخفية” (Latent Features) اللي بتوصف المستخدمين والمنتجات. هاي السمات الخفية الخوارزمية هي اللي بتكتشفها لحالها. ممكن تكون سمة زي “مدى حب المستخدم لأفلام الأكشن”، والبعد المقابل في متجه الفيلم هو “مدى احتواء الفيلم على أكشن”.
لما نعمل هيك، التوصية بتصير بسيطة: عشان نعرف مدى احتمالية إعجاب مستخدم u بمنتج i، كل اللي علينا نعمله هو نحسب حاصل الضرب النقطي (Dot Product) بين متجه المستخدم u ومتجه المنتج i. كل ما كان الرقم أعلى، كل ما كانت التوصية أفضل.
لماذا هذا حل حقيقي وفعّال؟
- يتعامل مع الفراغ (Sparsity) بكفاءة: الخوارزمية ما بتتأثر بالأصفار الكثيرة، بل هدفها هو التنبؤ بالقيم المفقودة بناءً على القيم الموجودة.
- قابل للتوسع (Scalable): حجم المتجهات k (مثلاً 50 أو 100) أصغر بكثير من عدد المستخدمين أو المنتجات (اللي ممكن يكونوا بالملايين)، وهذا بخلي الحسابات ممكنة وعملية.
- يُنتج توصيات شخصية: كل مستخدم بياخد توصيات مبنية على متجه السمات الخاص فيه، مش على توجهات الجمهور العام.
يلا نطبق سوا: بناء نظام توصية لمنصة فيديوهات خطوة بخطوة
خلينا نرجع لسيناريو منصة الفيديوهات. هدفنا نقترح فيديوهات جديدة لكل مستخدم بناءً على سجل مشاهداته.
الخطوة 1: تجهيز البيانات وبناء مصفوفة التفاعلات (R)
أول خطوة هي جمع بيانات التفاعلات. لنفترض إنه عنا جدول فيه (user_id, video_id, interaction_type). التفاعل ممكن يكون مشاهدة، إعجاب، تعليق، إلخ. للتبسيط، رح نعتبر أي مشاهدة هي تفاعل إيجابي ونعطيها قيمة 1.
الناتج النهائي لازم يكون مصفوفة разреженная (sparse matrix) حجمها num_users x num_items. مكتبات زي scipy.sparse في بايثون ممتازة لهي المهمة لأنها ما بتخزن الأصفار في الذاكرة.
الخطوة 2: تطبيق الخوارزمية: تفكيك المصفوفة باستخدام ALS
في عدة خوارزميات لعمل Matrix Factorization، أشهرها ALS (Alternating Least Squares) و SGD (Stochastic Gradient Descent). خوارزمية ALS مشهورة جداً في أنظمة البيانات الضخمة (زي Apache Spark) لأنها سهلة الموازاة (parallelizable).
فكرتها ببساطة:
- تبدأ بقيم عشوائية للمصفوفتين U و V.
- تثبّت مصفوفة المستخدمين U، وتستخدمها عشان تحسب أفضل قيم لمصفوفة المنتجات V (هاي خطوة Least Squares).
- تثبّت مصفوفة المنتجات V اللي حسبتها، وتستخدمها عشان تحسب أفضل قيم لمصفوفة المستخدمين U.
- تكرر الخطوتين 2 و 3 عدد من المرات حتى تتقارب القيم وتستقر.
رح نستخدم مكتبة بايثون ممتازة اسمها implicit، هي متخصصة في هذا النوع من الخوارزميات للتوصيات المبنية على التفاعلات الضمنية (implicit feedback).
# pip install implicit
import numpy as np
import scipy.sparse as sparse
from implicit.als import AlternatingLeastSquares
# لنفترض أننا جهزنا بياناتنا في مصفوفة разреженная
# user_items: scipy.sparse.csr_matrix of shape (num_users, num_items)
# قيمها 1 إذا شاهد المستخدم الفيديو، و 0 إذا لم يشاهده
# For example:
# num_users = 1000
# num_items = 5000
# data = ... (load from your database)
# rows = data['user_id_numeric']
# cols = data['item_id_numeric']
# values = np.ones(len(rows)) # All interactions are 1
# user_items = sparse.csr_matrix((values, (rows, cols)), shape=(num_users, num_items))
# 1. تهيئة النموذج
# factors: هو البعد k (عدد السمات الخفية)
# regularization: لمنع الـ overfitting
# iterations: عدد مرات تكرار عملية الـ ALS
model = AlternatingLeastSquares(factors=64, regularization=0.01, iterations=50)
# 2. تدريب النموذج
# نمرر له مصفوفة التفاعلات (يفضل أن تكون الأعمدة هي المنتجات)
model.fit(user_items.T)
# 3. استخراج متجهات المستخدمين والمنتجات
user_vectors = model.user_factors
item_vectors = model.item_factors
print(f"User vectors shape: {user_vectors.shape}")
print(f"Item vectors shape: {item_vectors.shape}")
نصيحة من أبو عمر: اختيار قيمة
factors(أو k) هو فن وعلم. قيمة صغيرة جداً قد لا تلتقط كل الأنماط، وقيمة كبيرة جداً قد تسبب overfitting وتكون بطيئة. ابدأ بقيمة مثل 50-100 وجرب قيم مختلفة وقِس الأداء.
الخطوة 3: توليد التوصيات: من المتجهات إلى قائمة المشاهدة
الآن بعد ما درّبنا النموذج وصار عنا متجهات لكل مستخدم وكل فيديو، عملية توليد التوصيات صارت سهلة وسريعة.
# ID المستخدم اللي بدنا نولد له توصيات
user_id = 15
# استدعاء دالة التوصية
# N: عدد التوصيات المطلوبة
# user_items[user_id]: نمرر لها الفيديوهات اللي شاهدها المستخدم عشان ما ترجع تترشحله
recommendations = model.recommend(user_id, user_items[user_id], N=10)
# طباعة النتائج (id الفيديو, درجة الثقة)
print("التوصيات للمستخدم رقم 15:")
for item_id, score in recommendations:
# هنا ممكن تحول item_id إلى اسم الفيديو الحقيقي من قاعدة البيانات
print(f" - فيديو رقم: {item_id}, بنسبة ثقة: {score:.2f}")
# ممكن كمان نلاقي فيديوهات مشابهة لفيديو معين
video_id = 100
similar_items = model.similar_items(video_id, N=5)
print(f"nفيديوهات مشابهة للفيديو رقم 100:")
for item_id, score in similar_items:
print(f" - فيديو رقم: {item_id}, بنسبة تشابه: {score:.2f}")
الخطوة 4: نصيحة أبو عمر: التخزين المؤقت (Caching) هو مفتاح السرعة
عملية تدريب النموذج (model.fit) ممكن تاخد ساعات لو البيانات ضخمة، لكنها عملية بتصير في الخلفية (offline). أما عملية توليد التوصيات (model.recommend) لازم تكون سريعة جداً عشان المستخدم ما يستنى.
الحل الأمثل هو عدم حساب التوصيات لحظة بلحظة لكل مستخدم. بدل هيك:
- الحل الأول (الأسهل): قم بحساب التوصيات بشكل دوري (مثلاً كل ليلة) لكل المستخدمين النشطين، وخزن النتائج (قائمة IDs المنتجات الموصى بها) في نظام تخزين مؤقت سريع جداً مثل Redis أو Memcached. لما المستخدم يفتح التطبيق، أنت فقط بتقرأ القائمة الجاهزة من الكاش.
- الحل الثاني (الأكثر مرونة): قم بتخزين مصفوفتي المتجهات U و V في خدمة خاصة (Vector Database أو حتى Redis). عند طلب التوصية، تقوم بجلب متجه المستخدم المطلوب، ثم تبحث عن أقرب متجهات المنتجات له (Nearest Neighbor Search). هذا الحل أكثر مرونة إذا أردت تحديث التوصيات بشكل شبه آني.
كيف نعرف إنه شغلنا صح؟ قياس جودة التوصيات
بنينا النظام، بس كيف نعرف إذا هو أفضل من نظام “الأكثر مشاهدة”؟ لازم نقيس الأداء.
مقاييس الجودة (Offline Metrics)
قبل إطلاق النظام للمستخدمين، بنعمل تقييم داخلي. بنقسم بياناتنا لمجموعة تدريب (training set) ومجموعة اختبار (test set). بنخفي بعض تفاعلات المستخدمين في مجموعة التدريب، وبندرب النموذج، وبعدين بنشوف هل النموذج قدر يتنبأ بالتفاعلات اللي خفيناها؟
أشهر المقاييس:
- Precision@K: من بين أفضل K توصية قدمناها، كم واحدة منها كانت فعلاً ذات صلة (المستخدم تفاعل معها في مجموعة الاختبار)؟
- Recall@K: من كل العناصر ذات الصلة في مجموعة الاختبار، كم نسبة اللي قدرنا نجيبهم ضمن أفضل K توصية؟
- NDCG (Normalized Discounted Cumulative Gain): مقياس معقد شوي، بس فكرته إنه بيعطي نقاط أعلى لو التوصيات الصحيحة ظهرت في المراتب الأولى في القائمة.
مقاييس الأداء (Online Metrics)
القياس الحقيقي هو على أرض الواقع. هون بنستخدم تقنية اسمها A/B Testing:
- نقسم المستخدمين لمجموعتين:
- المجموعة A (Control): بتشوف التوصيات القديمة (مثلاً، الأكثر مشاهدة).
- المجموعة B (Treatment): بتشوف التوصيات الجديدة من نظام Matrix Factorization.
- نقارن أداء المجموعتين على مدار فترة زمنية (أسبوع مثلاً) بناءً على مؤشرات أداء رئيسية (KPIs) مثل:
- معدل النقر على التوصيات (Click-Through Rate – CTR): هل المجموعة B بتضغط على التوصيات أكتر؟
- معدل التحويل (Conversion Rate): هل بيشتروا أكتر أو بيقضوا وقت أطول؟
– زمن الاستجابة (Latency): هل صفحة التوصيات بتحمّل بسرعة؟
إذا كانت النتائج إيجابية لصالح المجموعة B، بنعرف إنه شغلنا جاب نتيجة وبنقدر نعمم النظام الجديد على كل المستخدمين.
الخلاصة: من مصفوفة فارغة إلى تجربة مستخدم ثرية 🚀
بالمختصر المفيد يا جماعة، بناء نظام توصية شخصي ما عاد سحر أسود. خوارزميات مثل Matrix Factorization حولت مشكلة “المصفوفة الفارغة” من تحدي كبير إلى فرصة لاكتشاف الأنماط الخفية في سلوك المستخدمين.
الرحلة بتبلش بجمع البيانات الصحيحة، ثم تطبيق خوارزمية مناسبة (مثل ALS) لاستخراج متجهات السمات، وأخيراً قياس الأثر الحقيقي على تجربة المستخدم. تذكروا دايماً إن الهدف مش مجرد تطبيق خوارزمية معقدة، بل هو تقديم قيمة حقيقية للمستخدم وللبيزنس.
نصيحتي الأخيرة: ابدأ بسيط، قيس كل شيء، وحسّن بشكل مستمر. الشغل المرتب بيجي خطوة خطوة. بالتوفيق في بناء أنظمة التوصية الخاصة فيكم!