كانت توصياتنا عشوائية وبلا هدف: كيف أنقذتنا خوارزمية ‘الجوار الأقرب’ (k-NN) من جحيم التخمين؟

أذكر جيداً ذلك المساء، كنت أجلس مع فريق العمل في مكتبي الصغير، وأكواب الشاي بالنعناع تفوح منها رائحة اليأس أكثر من النعناع. كنا نعمل على منصة تجارة إلكترونية ناشئة، وكان “نظام التوصيات” الذي بنيناه كارثياً بكل ما تحمله الكلمة من معنى. مستخدم يشتري حذاء رياضي، فنقترح عليه شراء… مقلاة! مستخدمة تبحث عن فساتين، فتظهر لها توصيات بشراء ألعاب أطفال. كانت الشكاوى تتراكم، والإدارة تضغط، ونحن غارقون في بحر من التخمينات.

في خضم النقاش المحتدم، صرخت فيهم مازحاً بيأس: “يا زلمة، شو هالتخبيص؟ إحنا بنشتغل مبرمجين ولا منجّمين؟”. كان نظامنا يعتمد على مجموعة من قواعد `if-else` الساذجة التي كتبناها بأيدينا، محاولين توقع ما قد يريده المستخدم. كان جحيماً حقيقياً من التخمين اليدوي، وكلما أضفنا منتجاً جديداً، زادت الفوضى.

في تلك اللحظة، قررت أننا يجب أن نتوقف عن محاولة “التفكير” نيابة عن المستخدم، ونجعل البيانات هي التي تفكر وتقرر. وهنا، لمعت في ذهني فكرة تطبيق واحدة من أبسط وأجمل خوارزميات تعلم الآلة: خوارزمية الجار الأقرب (k-NN). كانت تلك هي اللحظة التي بدأنا فيها الخروج من جحيم التخمين إلى نور القرارات المبنية على البيانات.

لماذا تفشل التوصيات المبنية على “التخمين”؟

قبل أن نغوص في تفاصيل الحل، دعونا نفهم أصل المشكلة. الأنظمة التي تعتمد على قواعد ثابتة (Rule-based Systems) تفشل لعدة أسباب جوهرية:

  • صعوبة التوسع (Scalability): مع كل 100 منتج جديد، قد تحتاج إلى كتابة 1000 قاعدة جديدة. هذا عمل لا ينتهي ومصدر دائم للأخطاء.
  • الجمود: هذه الأنظمة لا تتكيف مع سلوك المستخدم المتغير. إذا تغيرت الموضة أو ظهر اهتمام جديد، ستبقى القواعد القديمة عديمة الفائدة.
  • التحيز البشري: نحن كمبرمجين نضع افتراضاتنا وتحيزاتنا الشخصية في هذه القواعد، والتي قد لا تعكس الواقع أبداً.

باختصار، كنا نحاول حل مشكلة ديناميكية ومعقدة بأداة ثابتة وبدائية. كان الأمر أشبه بمحاولة رسم لوحة فنية باستخدام مطرقة وإزميل.

المنقذ: ما هي خوارزمية الجار الأقرب (k-NN)؟

الاسم قد يبدو معقداً، لكن الفكرة خلف k-NN بسيطة وعبقرية وتعتمد على حكمة قديمة نقولها دائماً: “قل لي من تصاحب، أقل لك من أنت”.

ببساطة شديدة، خوارزمية k-NN تفترض أن الأشياء المتشابهة (أو المستخدمين المتشابهين) تميل إلى التواجد بالقرب من بعضها البعض. فإذا أردنا أن نعرف شيئاً عن نقطة بيانات جديدة (مثلاً، مستخدم جديد)، فإننا ننظر إلى أقرب “جيران” لها في فضاء البيانات، ونجعلها تتبع سلوكهم.

لفهم الفكرة، تخيل أنك في حي جديد وتريد أن تجد مطعماً جيداً. ماذا تفعل؟ ستسأل جيرانك الأقربين عن مطاعمهم المفضلة. إذا أجمع 3 من أصل 5 من جيرانك على أن مطعم “القدس” هو الأفضل، فعلى الأغلب ستكون هذه توصيتك الأولى. هذا هو جوهر k-NN!

  • k: هو عدد الجيران الذين ستسألهم (في مثالنا، k=5).
  • Nearest (الأقرب): هو مقياس المسافة الذي يحدد من هم “جيرانك” فعلاً.

من النظرية إلى التطبيق: بناء نظام التوصيات خطوة بخطوة

حسناً يا أبو عمر، كلام جميل، لكن كيف نطبق هذا فعلياً على نظام التوصيات؟ دعوني أشرح لكم الخطوات التي اتبعناها بالضبط.

الخطوة الأولى: تمثيل البيانات (تحويل المستخدمين والمنتجات إلى أرقام)

الخوارزميات لا تفهم “فساتين” أو “أحذية”، هي تفهم أرقاماً فقط. لذا، كانت خطوتنا الأولى هي تحويل سلوك المستخدمين إلى مصفوفة رقمية. أبسط طريقة هي إنشاء مصفوفة (matrix) تمثل تقييمات المستخدمين للمنتجات.

تخيل أن لدينا 4 مستخدمين و 5 أفلام، وتقييماتهم من 1 إلى 5 (0 يعني لم يشاهده):


        | فيلم1 | فيلم2 | فيلم3 | فيلم4 | فيلم5
-------------------------------------------------
أحمد   |   5   |   4   |   0   |   1   |   5
فاطمة  |   4   |   5   |   1   |   0   |   4
علي    |   1   |   0   |   5   |   4   |   1
سارة   |   0   |   1   |   4   |   5   |   0

الآن، كل مستخدم أصبح مجرد سطر من الأرقام (vector). أحمد هو المتجه `[5, 4, 0, 1, 5]` وعلي هو `[1, 0, 5, 4, 1]`. أصبح لدينا الآن خريطة رقمية لسلوك المستخدمين.

الخطوة الثانية: حساب المسافة (من هو “قريبك” في عالم البيانات؟)

الآن بعد أن أصبح لكل مستخدم إحداثيات رقمية، يمكننا حساب “المسافة” بين أي مستخدمين. أشهر مقياس للمسافة هو المسافة الإقليدية (Euclidean Distance)، وهي ببساطة طول الخط المستقيم بين نقطتين.

عندما نقارن أحمد بفاطمة، نجد أن تقييماتهم متشابهة جداً (كلاهما يحب فيلم1 وفيلم2 وفيلم5). رياضياً، ستكون المسافة بينهما صغيرة. أما عند مقارنة أحمد بعلي، سنجد أن أذواقهم مختلفة تماماً، وبالتالي المسافة بينهما ستكون كبيرة. هذا يعني أن فاطمة “جارة” قريبة لأحمد، بينما علي “جار” بعيد جداً.

الخطوة الثالثة: العثور على الجيران وتقديم التوصية

هنا يأتي سحر k-NN. لنفترض أننا نريد أن نوصي بفيلم جديد لـ “أحمد”.

  1. نحدد قيمة k. لنقل أننا سننظر إلى أقرب جارين، إذن k=2.
  2. نحسب المسافة بين أحمد وكل المستخدمين الآخرين.
  3. نجد أن أقرب جارين له هما “فاطمة” و”سارة” (لنفترض ذلك كمثال).
  4. الآن ننظر إلى الأفلام التي شاهدها جيران أحمد (فاطمة وسارة) وأعجبتهم، ولكن أحمد لم يشاهدها بعد.
  5. فاطمة شاهدت “فيلم3” وأعطته تقييم 1 (سيء)، وسارة شاهدته وأعطته تقييم 4 (جيد). أحمد لم يشاهده (تقييمه 0).
  6. سارة شاهدت “فيلم4” وأعطته تقييم 5 (ممتاز)، وأحمد أعطاه تقييم 1 (سيء).
  7. فاطمة أحبت “فيلم5” وأحمد كذلك.

بتحليل سلوك الجيران، يمكن للنظام أن يستنتج أن “فيلم3” قد يكون توصية جيدة لأحمد لأن أحد جيرانه المقربين (سارة) أحبه بشدة. وبهذه الطريقة، يبدأ النظام في تقديم توصيات ذكية مبنية على التشابه الفعلي في الأذواق، وليس على قواعد جامدة.

مثال عملي بالكود (Python)

دعونا نرى كيف يمكن تطبيق هذا ببضع أسطر من كود بايثون باستخدام مكتبة `scikit-learn` الرائعة.


import pandas as pd
from sklearn.neighbors import NearestNeighbors

# 1. إنشاء بيانات وهمية (نفس مصفوفة الأفلام)
data = {
    'أحمد':  [5, 4, 0, 1, 5],
    'فاطمة': [4, 5, 1, 0, 4],
    'علي':   [1, 0, 5, 4, 1],
    'سارة':  [0, 1, 4, 5, 0]
}
movies = ['فيلم1', 'فيلم2', 'فيلم3', 'فيلم4', 'فيلم5']
df = pd.DataFrame(data, index=movies).T # .T لقلب الصفوف والأعمدة

# 2. تهيئة وتدريب موديل k-NN
# n_neighbors=3 يعني أننا نبحث عن المستخدم نفسه + أقرب جارين (k=2)
# algorithm='brute' يعني أنه سيقارن كل نقطة بكل النقاط الأخرى (مناسب للبيانات الصغيرة)
knn_model = NearestNeighbors(n_neighbors=3, algorithm='brute', metric='euclidean')
knn_model.fit(df)

# 3. البحث عن توصيات لمستخدم معين (مثلاً أحمد)
target_user = 'أحمد'
target_user_vector = df.loc[[target_user]]

# سيقوم النموذج بإرجاع المسافات والindices (مؤشرات) لأقرب الجيران
distances, indices = knn_model.kneighbors(target_user_vector)

# indices[0] يحتوي على مؤشرات أقرب الجيران في الـ DataFrame
# أول جار هو المستخدم نفسه، لذلك نتجاهله
nearest_neighbors_indices = indices[0][1:]
nearest_neighbors = df.index[nearest_neighbors_indices].tolist()

print(f"المستخدم المستهدف: {target_user}")
print(f"أقرب جيران له هم: {nearest_neighbors}")

# 4. استخراج التوصيات
recommendations = set()
for neighbor in nearest_neighbors:
    # الحصول على تقييمات الجار
    neighbor_ratings = df.loc[neighbor]
    # الحصول على تقييمات المستخدم المستهدف
    target_user_ratings = df.loc[target_user]

    # البحث عن أفلام قيمها الجار تقييماً عالياً (>= 4) ولم يشاهدها المستخدم المستهدف (تقييم == 0)
    for movie, rating in neighbor_ratings.items():
        if rating >= 4 and target_user_ratings[movie] == 0:
            recommendations.add(movie)

print(f"أفلام نوصي بها لأحمد: {list(recommendations)}")

عند تشغيل هذا الكود، ستجد أن النظام يقترح “فيلم3” و “فيلم4” بناءً على أذواق جيرانه، وهو بالضبط ما كنا نهدف إليه!

نصائح من “كيس” أبو عمر: تحديات واجهتنا وحلولها

الطريق لم يكن مفروشاً بالورود طبعاً. واجهتنا بعض التحديات العملية مع k-NN، وهذه بعض النصائح من خبرتي لتجاوزها:

تحدي اختيار قيمة ‘k’ المثالية

اختيار قيمة k مهم جداً. قيمة صغيرة (مثل k=1) تجعل النموذج حساساً جداً للضوضاء والشواذ. قيمة كبيرة جداً قد تجعل النموذج يأخذ في الحسبان جيراناً غير متشابهين فعلاً، مما يضعف دقة التوصيات.

نصيحتي العملية: ابدأ بقيمة k صغيرة وفردية (مثل 3 أو 5) لتجنب التعادل في الأصوات. لا توجد قيمة سحرية، أفضل طريقة هي التجربة. قم بتقييم دقة التوصيات مع قيم k مختلفة (مثلاً 3, 5, 7, …, 21) واختر القيمة التي تعطيك أفضل النتائج لمجموعة بياناتك.

تحدي “لعنة الأبعاد” (Curse of Dimensionality)

عندما يكون لديك عدد هائل من المنتجات (الأبعاد)، تصبح كل النقاط بعيدة عن بعضها البعض، ويفقد مفهوم “المسافة” معناه. هذا يجعل من الصعب العثور على جيران “حقيقيين”.

نصيحتي العملية: قبل تطبيق k-NN على بيانات ذات أبعاد عالية جداً (آلاف المنتجات)، فكر في استخدام تقنيات تقليل الأبعاد (Dimensionality Reduction) مثل PCA. هذه التقنيات تساعد على ضغط أهم المعلومات من آلاف الأبعاد إلى عدد أقل بكثير (مثلاً 100 بعد)، مما يجعل حساب المسافة أكثر فعالية ودقة.

تحدي الأداء مع البيانات الضخمة

خوارزمية k-NN تُعرف بأنها “متعلم كسول” (Lazy Learner). هي لا “تتعلم” أي شيء مسبقاً، بل تقوم بكل الحسابات (حساب المسافات) في لحظة طلب التوصية. هذا قد يكون بطيئاً جداً إذا كان لديك ملايين المستخدمين.

نصيحتي العملية:

  • للبداية، مكتبة `scikit-learn` تستخدم هياكل بيانات ذكية (مثل KD-Tree أو Ball-Tree) لتسريع البحث عن الجيران بدلاً من مقارنة كل نقطة بجميع النقاط الأخرى.
  • في الأنظمة الضخمة جداً، قد لا تكون k-NN هي الحل الأمثل لوحدها. يمكن استخدامها لإنشاء نموذج أولي سريع، ثم الانتقال إلى خوارزميات أكثر تعقيداً وقابلية للتوسع مثل (Matrix Factorization) أو استخدام أنظمة هجينة تجمع بين عدة نماذج.

الخلاصة: من التخمين إلى التمكين بالبيانات 🚀

رحلتنا مع خوارزمية k-NN كانت نقطة تحول حقيقية. انتقلنا من فريق محبط يغرق في التخمينات العشوائية، إلى فريق يبني حلولاً ذكية تستمع إلى البيانات وتفهم سلوك المستخدمين. تحسنت التوصيات بشكل جذري، وزاد تفاعل المستخدمين، والأهم من ذلك، استعدنا ثقتنا بأنفسنا كمطورين قادرين على حل المشاكل المعقدة.

k-NN ليست دائماً الخوارزمية الأقوى أو الأكثر دقة في كل السيناريوهات، لكنها بلا شك واحدة من أفضل نقاط البداية في عالم تعلم الآلة. فهي بسيطة، سهلة الفهم والتفسير، ونتائجها غالباً ما تكون جيدة بشكل مدهش.

نصيحتي الأخيرة لك: لا تخف أبداً من بساطة الحل. في كثير من الأحيان، تكون أبسط الخوارزميات هي الأقوى إذا تم تطبيقها بشكل صحيح. ابدأ صغيراً، افهم بياناتك جيداً، ثم ابنِ عليها. ودائماً، خلي كاسة الشاي جنبك، بتلهم وبتصحصح! 😉

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

​معمارية البرمجيات

خدماتنا تتحدث بلغات مختلفة: كيف أنقذ نمط BFF (الواجهة الخلفية للواجهات الأمامية) مشروعنا من فوضى الـ API؟

أشارككم قصة حقيقية من قلب الميدان، حين كادت تطبيقاتنا تنهار بسبب فوضى التواصل بين الخدمات المصغرة والواجهات الأمامية. اكتشفوا معنا كيف كان نمط "الواجهة الخلفية...

13 مايو، 2026 قراءة المزيد
ذكاء اصطناعي

كان نموذجنا اللغوي يهلوس بلا توقف: كيف أنقذنا ‘التوليد المعزز بالاسترجاع’ (RAG) من جحيم الإجابات الخاطئة؟

واجه فريقنا كابوساً حقيقياً مع نموذجنا اللغوي الكبير الذي كان يخترع إجابات من وحي الخيال. في هذه المقالة، أشارككم قصة كيف انتشلنا مشروعنا من حافة...

12 مايو، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

كان تطبيقنا حصناً منيعاً أمام المكفوفين: كيف أنقذتنا سمات ARIA من جحيم الإقصاء الرقمي؟

في أحد الأيام، وصلني بريد إلكتروني هزّ كياني كمبرمج، وكشف لي أن التطبيق الذي بنيناه بفخر كان بمثابة جدار أصم أمام فئة كاملة من المستخدمين....

12 مايو، 2026 قراءة المزيد
برمجة وقواعد بيانات

كانت صفحاتنا تستغرق دهراً للتحميل: كيف أنقذنا ‘التحميل المسبق’ (Eager Loading) من جحيم مشكلة N+1؟

أشارككم قصة حقيقية عن كيفية تحول أحد مشاريعنا من البطء الشديد إلى السرعة الفائقة. اكتشفوا معنا مشكلة N+1 الخبيثة، وكيف كان التحميل المسبق (Eager Loading)...

12 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كانت إجاباتي في المقابلات عشوائية: كيف أنقذني إطار STAR من جحيم الأسئلة السلوكية؟

أشاركك تجربتي الشخصية مع الأسئلة السلوكية الكارثية في المقابلات التقنية وكيف تحولت من مطور مشتت إلى مرشح واثق باستخدام إطار STAR. هذه المقالة هي دليلك...

12 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

قاعدة بياناتنا المونوليثية كانت قنبلة موقوتة: كيف أنقذنا ‘التقسيم’ (Sharding) من جحيم عنق الزجاجة؟

كانت ليلة لا تُنسى، ليلة انفجرت فيها قنبلة قاعدة بياناتنا المونوليثية. في هذه المقالة، أسرد لكم يا جماعة كيف انتقلنا من حافة الانهيار إلى نظام...

12 مايو، 2026 قراءة المزيد
البودكاست