كل يوم ، يتلقى المستخدمون في جميع أنحاء العالم عددًا كبيرًا من الرسائل البريدية المختلفة - فقط من خلال خدمة MailChimp اليومية التي ترسل مليار رسالة . من هؤلاء ، تم اكتشاف 20.81 ٪ .
كل شهر ، يتلقى مستخدمو مواقعنا رسائل إخبارية تحتوي على مواد يختارها المحرر. حوالي 21 ٪ من القراء فتح هذه الرسائل.
لزيادة هذا العدد ، يمكنك جعلها مخصصة. تتمثل إحدى الطرق في إضافة نظام توصية يحث المواد المثيرة للاهتمام على قارئ معين.
في هذه المقالة سأتحدث عن كيفية تطبيق نظام التوصيات من البداية بناءً على التصفية التعاونية.
يحتوي الجزء الأول من المقالة على الأساس النظري لتنفيذ نظام التوصيات. الرياضيات المدرسية كافية لفهم المواد.
يصف الجزء الثاني تطبيق Python لبيانات موقعنا.
قليلا من نظرية التصفية التعاونية
التصفية التعاونية هي على الأرجح الطريقة الأسهل في أنظمة التوصية. يعتمد ذلك على فكرة أن المستخدمين المتشابهين يحبون الأشياء المشابهة ، مثل المقالات.
ماذا يعني "مستخدمون مشابهون"؟

كيفية تحديد مقدار Vasily يشبه Ivan أو مقالة حول SQL Server لمقال حول PostgreSQL؟
لنلقِ نظرة على مثال. دعنا نقول أن لدينا أربعة مستخدمين: فاسيلي وإيفان وإينا وآنا. يحتوي الموقع على خمس مقالات: المادة 1 ، والمادة 2 ، والمادة 3 ، والمادة 4 ، والمادة 5. في الجدول أدناه ، يكون الرقم عند تقاطع المستخدم والمادة هو تصنيف المستخدم لهذه المادة على مقياس من خمس نقاط. صفر في الجدول هي المقالات التي لم يتم تصنيفها من قبل المستخدم. على سبيل المثال ، أعجب فاسيلي بالمواد 1 و 3 و 4.
الجدول 1
بشكل حدسي ، يمكننا أن نفترض أنه إذا أحب المستخدمون نفس المقالات ، فستتزامن أذواقهم. ما رأيك ، التي تتشابه مصالحها مع مصالح فاسيلي؟
تتشابه اهتمامات فاسيلي مع مصالح إيفان وإينا وأقل تشبه اهتمامات آنا. لماذا - سوف يقال المزيد.
لمزيد من العمل ، من الضروري إضفاء الطابع الرسمي وقياس "تشابه" فاسيلي وإيفان أو إينا وآنا.
أسهل طريقة للقيام بذلك هي النظر في تصنيفات المستخدمين بوصفًا لملف التعريف الخاص بهم. في المثال ، كل صف في الجدول هو وصف لمستخدم واحد. السطر الأول - وصف باسيل - عبارة عن ناقل مكون من خمسة أرقام: [4 ، 0 ، 5 ، 5 ، 0] ؛ الثاني - إيفان - [0 ، 0 ، 4 ، 5 ، 0] ؛ والثالثة هي إينا - [4 ، 2 ، 4 ، 0 ، 0] ؛ الرابع هو آنا - [5 ، 5 ، 0 ، 0 ، 5].
يمكنك الآن تقديم مفهوم أوصاف المستخدم "قياس التشابه".
إحدى طرق قياس "تشابه" المستخدمين هي حساب مسافة جيب التمام بين المتجهات التي تصفهم.

يتم حساب المسافة جيب التمام بواسطة الصيغة:
حيث و - ناقلات وصف المستخدم ؛ - منتج العددية من ناقلات الوصف ؛ . - أطوال ناقلات الوصف.
معنى مسافة جيب التمام هو كما يلي: إذا نواقل حيث و (متجهات وصف المستخدم) "متشابهة" ، ثم تميل الزاوية بينهما إلى الصفر ، وتميل جيب تمام هذه الزاوية إلى الوحدة. في الحالة المثالية ، عندما تتزامن "اهتمامات" المستخدمين ، ستكون مسافة جيب التمام لهما صفراً.
جيب تمام المسافة بين فاسيلي وإيفان:
وبالمثل ، فإن مسافة جيب التمام بين فاسيلي وآنا هي 0.715. أي أن مصالح فاسيلي تشبه مصالح إيفان أكثر من اهتمامات آنا.
كيفية التنبؤ تصنيفات المستخدم؟
هذا الجزء هو الأكثر إثارة للاهتمام. هناك العديد من الخيارات المختلفة. أدناه نعتبر خيارين بسيطين.
توقع التصنيف - متوسط التصنيف بين المستخدمين "مماثلة"
أسهل خيار لحساب التصنيف المتوقع هو معرفة التصنيفات التي وضعها المستخدمون "المشابهون" لهذه المقالة وأخذ التصنيف المتوسط:
في هذه الصيغة:
- هو التقدير المتوقع المادة ال والمستخدم .
- - تصنيف المستخدم إلى المادة ال
- - الكثير من المستخدمين "المتشابهين" ،
- - عدد المستخدمين "المتشابهين".
التصنيف المتوقع - متوسط التصنيف المرجح بين المستخدمين "المتشابهين"
الخيار الأكثر تعقيدًا هو الأخذ في الاعتبار درجة التشابه: يجب أن تؤثر تقييمات المستخدمين الأكثر تشابهًا على التصنيف النهائي أكثر من التصنيفات الأقل تشابهًا:
في هذه الصيغة:
- هو التقدير المتوقع المادة ال والمستخدم .
- - تصنيف المستخدم إلى المادة ال
- - الكثير من المستخدمين "المتشابهين" ،
- - "التشابه" (مسافة جيب التمام) للمستخدمين و .
كيف تقيس جودة التوصيات؟

عند إنشاء أي نظام توصية ، يجب عليك تحديد المقياس الذي يمكنك من خلاله تقييم جودة نموذجنا - إلى أي مدى يقدم النظام للمستخدم مواد جديدة. على سبيل المثال ، الجذر يعني خطأ مربع ( ) هو الجذر التربيعي للخطأ المتوسط لجميع تقييمات المستخدم. بشكل رسمي ، يتم وصف هذا التدبير بالصيغة:
في هذه الصيغة
- - مجموعة من جميع تصنيفات المستخدمين للمقالات ،
- - توقع تصنيف المستخدم مقالة .
- - تصنيف المستخدم الحقيقي مقالة .
في الحالة المثالية ، عندما تتوافق التصنيفات المتوقعة مع المستخدم يساوي الصفر.
النظر في مثال. قدم نظامان مرجعيان تنبؤات لفاسيلي. النتيجة في الجدول أدناه.
ومن الواضح بشكل حدسي أن نظام التوصية الثانية تنبأ بتقييمات أفضل من الأولى. عد :
من المتوقع أن يكون الخطأ في تقييمات نظام التوصية الثانية أقل بكثير.
تطبيق
لدينا تحت تصرفنا معظم البيانات المتعلقة بالمقالات ومستخدمي الموقع: معلومات عن المقالات والعلامات وإعجابات المستخدمين ، إلخ.
لتنفيذ التصفية التعاونية ، تكون تقييمات المستخدم كافية.
تنصلفيما يلي ، يتم كتابة الكود "في المقدمة" لإظهار منطق نظام التوصية. في الحياة الواقعية ، من الأفضل استخدام جميع ميزات numpy
.
import pandas as pd import numpy as np import os ratings_df = pd.read_csv('./input/Ratings.csv') print(' :', ratings_df.shape[0]) print(' :', ratings_df[ratings_df['Rate']].shape[0]) unique_user_ids = ratings_df[ratings_df['Rate']]['UserId'].unique() print(' :', len(unique_user_ids)) ratings_df.head()
الناتج [1]إجمالي البيانات: 15313
التقييمات الإيجابية: 15121
المستخدمون النشطون: 1007
1007 من المستخدمين النشطين أعطوا 15313 "تصنيفات". من هذه ، 15121 "يحب".
تحتوي البيانات على أربعة أعمدة: معرف صف من قاعدة البيانات (عمود المعرف ) ، معرف كائن (عمود DocumentId ) ، علامة على أن المستخدم أعجب بالمقال (عمود معدل ) ، ومعرف مستخدم (عمود UserId ).
للراحة ، أضف العمود RateInt . 1 في هذا العمود تعني أن المستخدم أعجب بالمقال ؛ -1 - هذا لم يعجبه.
ratings_df['RateInt'] = ratings_df['Rate'].apply(lambda x: 1 if x else -1) ratings_df.head()
لمزيد من العمل ، يلزم تقسيم مجموعة البيانات إلى تدريب واختبار: سيتم استخدام التدريب لتدريب النموذج ، وسيحدد الاختبار نوعية التنبؤات.
from sklearn.model_selection import train_test_split train, test = train_test_split(ratings_df, test_size=0.2)
للراحة ، نقوم بتحويل كل مجموعة إلى جدول ، حيث توجد في الصفوف معرفات المستخدمين ، وفي الأعمدة معرفات المقالات عن طريق القياس بالمثال في بداية المقال.
def create_matrix(df): ratings_per_user = [] post_ids = df['DocumentId'].unique() for user_id in tqdm_notebook(all_users_ids, ''): row = {'user_id': user_id} ratings = df[df['UserId'] == user_id]['DocumentId'].values for post_id in post_ids: row[str(post_id)] = 1 if post_id in ratings else 0 ratings_per_user.append(row) return pd.DataFrame(ratings_per_user) train_df = create_matrix(train) test_df = create_matrix(test)
ستتيح لك مصفوفة المطابقة بين المستخدمين والمقالات المفضلة حساب مسافة جيب التمام بين المستخدمين:
from scipy import spatial def cos_distance(x1, x2): return spatial.distance.cosine(x1, x2) at_least_one_fav_post_users = list(train_valuable_df['user_id'].values) def calculate_distances(df): columns = df.columns[:-1] cp = at_least_one_fav_post_users.copy() data = [] for user_id_1 in tqdm_notebook(at_least_one_fav_post_users, ''): row = {'user_id': user_id_1} for user_id_2 in cp: x1 = df[df['user_id'] == user_id_1][columns].values[0] x2 = df[df['user_id'] == user_id_2][columns].values[0] row[str(user_id_2)] = cos_distance(x1, x2) data.append(row) return pd.DataFrame(data) train_distances = calculate_distances(train_valuable_df)
الآن أصبح كل شيء جاهزًا لمطالبة المستخدمين بالمقالات التي يرغبون في رؤيتها.
ننفذ الاستراتيجيتين لحساب التوصيات الموضحة أعلاه: متوسط ومتوسط التصنيفات الموزعة بين المستخدمين المتشابهين.
الطريقة الأولى
نأخذ 10 مستخدمين الأقرب إلى الحالي ونتوقع التقييم كمتوسط للمستخدمين مماثلة لهذه المادة:
from tqdm import tqdm_notebook import heapq def rmse(predicted, actual): return ((predicted - actual) ** 2).mean() ** 0.5 def get_similar(id, n): df = train_distances[train_distances['user_id'] == id] d = df.to_dict('records')[0] top_similar_ids = heapq.nsmallest(n+1, d, key=d.get) top_similar = df[top_similar_ids] return top_similar.to_dict('records')[0] def get_predictions(id, n): top_similar_users = get_similar(id, n) top_similar_users_ids = list([int(x) for x in top_similar_users.keys()]) ratings_for_top_similar = train_df[train_df['user_id'].isin(top_similar_users_ids)] predicted_ratings = {} for article_id in train_df.columns[:-1]: predicted_ratings[article_id] = ratings_for_top_similar[article_id].mean() return predicted_ratings rand_n_users = train_distances.sample(50)['user_id'].values err = 0 for u in tqdm_notebook(rand_n_users): pred = get_predictions(u, 10) err += rmse(test_df[test_df['user_id'] == u][list(pred.keys())].values, pd.DataFrame(pred, index=[0]).values) print(err / len(rand_n_users))
بالنسبة للنهج الأول ، حصلنا على خطأ يساوي 0.855.
الطريقة الثانية
الطريقة الثانية تأخذ في الاعتبار درجة تشابه المستخدمين. تنفيذه مماثل تقريبا لأول:
def get_predictions(id, n): similar_users = get_similar(u, 10) prediction = {} user_ids = list(similar_users.keys()) user_similarities = [] for user_id in user_ids: user_similarities.append(similar_users[user_id]) predicted_ratings = {} for article_id in train_df.columns[:-1]: prediction_for_article = 0 numerator = 0 denominator = 0 for user_id in user_ids: rating = train_df[train_df['user_id'] == int(user_id)][article_id].values[0] numerator += rating * (1 - similar_users[user_id]) denominator += np.abs(similar_users[user_id]) predicted_ratings[article_id] = numerator / denominator return predicted_ratings err = 0 for u in tqdm_notebook(rand_n_users): pred = get_predictions(u, 10) err += rmse(test_df[test_df['user_id'] == u][list(pred.keys())].values, pd.DataFrame(pred, index=[0]).values) print(err / len(rand_n_users))
في هذه الحالة ، حصلوا على خطأ 0.866. الخطأ أكبر قليلاً مما كان عليه في الحالة الأولى.
توصيات لنفس المستخدم العشوائي يمكن استخدام النتائج في سيناريوهات مختلفة. على سبيل المثال ، في النشرات الإخبارية للمقالات الجديدة كل شهر أو أضف على الموقع قسم "قد تكون مهتمًا".
ملخص
في هذه المقالة ، حاولت بالتفصيل ، باستخدام مثال مهمة حقيقية ، لمعرفة كيفية إنشاء نظام توصية يعتمد على التصفية التعاونية.
ميزة هذا النهج هو تعدد الاستخدامات - التوصيات لا تأخذ في الاعتبار الأشياء التي يوصى بها. يمكن استخدام نظام واحد لكل من مقالات المدونة والمنتجات في المتجر عبر الإنترنت.
العيوب تشمل ما يلي:
- في حالة وجود عدد كبير من الكائنات للتوصيات ، تصبح مصفوفة كائن المستخدم متناثرة ، ويصعب العثور على مستخدمين متشابهين بدرجة كافية (عدد أقل من أزواج كائنات المستخدم)
- مشكلة بدء التشغيل البارد - يستحيل على مستخدم جديد العثور على مستخدمين مشابهين (هناك استراتيجيات للتحايل على هذا القيد ، لكنهم ليسوا حلا سحريا)
- يميل النظام القائم على التصفية التعاونية إلى التوصية بالأشياء الشائعة ، لأن الغالبية العظمى من المستخدمين سوف نقدر هذه الأشياء.
في المقالة التالية ، سيتم النظر في مقاربة أخرى - استنادًا إلى تحليل الكائنات نفسها.