תארו לעצמכם השקת אפליקציית חדשות עם מיליוני פרסומים יומיים. ללא התאמה אישית, המשתמשים רואים פוסטים מונוטוניים, מאבדים עניין במהירות ועוזבים. פידים כרונולוגיים מאבדים עד 60% מהמעורבות — נתון מאומת בניסויים. מנוע הדירוג שלנו מטפל במיון מועמדים על סמך מאות מאפיינים, לא ביצירת תוכן אלא בתעדוף שלו. תרחיש טיפוסי: פיד חדשות הופך ללא רלוונטי לאחר שבועיים של שימוש — משתמשים מתלוננים על אחידות. הצינור מתחשב בדינמיקת עניין ומונע בועות סינון. גישה היברידית: סינון שיתופי בתוספת ניתוח סמנטי של תוכן. זה לוכד גם העדפות מפורשות וגם מרומזות. התוצאה: כל משתמש רואה פיד שמתאים עצמו לעניינים המתפתחים שלו בזמן אמת. גישה זו מעלה את שימור המשתמשים ב-30% ומגדילה את זמן השהייה פי 1.5. בואו נצלול לפרטי היישום.
איך אנחנו בונים את צינור הדירוג
פיד מותאם אישית הוא צינור דו-שלבי: שליפת מועמדים ודירוג. בשלב הראשון, אנו בוחרים כמה מאות מועמדים מתוך מיליוני פרסומים באמצעות חיפוש שכנים קרובים בקירוב (ANN) על בסיס מינויים ותחומי עניין. בשלב השני, אנו מדרגים מועמדים אלה עם מודל כבד יותר המשתמש במאות מאפיינים. פיצול השלבים הוא קריטי: מודל הדירוג איטי מדי עבור הקטלוג המלא; מודל השליפה אינו מדויק מספיק עבור הסדר הסופי.
מאפיינים לדירוג
מודל דירוג טוב משתמש בשלוש קבוצות מאפיינים:
- הקשר משתמש: שעה ביום, יום בשבוע, סוג סשן (קר או המשך), פעילות ב-24 השעות האחרונות.
- מאפייני תוכן: גיל הפוסט, שיעור מעורבות (לייקים/צפיות), מהירות צפיות בשעה הראשונה, מספר עוקבים של הכותב ו-CTR היסטורי.
- מפגש משתמש-תוכן: דמיון סמנטי להיסטוריית אינטראקציה, חפיפה נושאית עם תחומי עניין מובילים, היכרות עם הכותב.
# Feature vector для одного кандидата
@dataclass
class RankingFeatures:
# Content features
post_age_hours: float
engagement_rate_24h: float
viral_velocity: float # views_per_hour in first 2 hours
# User-content interaction
topic_affinity: float # cosine sim между профилем юзера и эмбеддингом поста
author_ctr_for_user: float # исторический CTR этого автора у этого юзера
# Context
hour_of_day: int
is_weekend: bool
session_depth: int # сколько постов уже просмотрено в сессии
למה LightGBM במקום רשת נוירונים?
מדרגי רשתות נוירונים מציעים איכות גבוהה יותר, אבל LightGBM עם פונקציית LambdaRank מהיר יותר בזמן הסקה (2–5 אלפיות שנייה עבור 200 מועמדים) וקל יותר לחזרה על תהליך. עבור קנה מידה בינוני, זו הבחירה האופטימלית. עבור קהלים מעל 10 מיליון, מודל דו-מפלסי עם רשת נוירונים לשליפה ו-LightGBM לדירוג עשוי להיות אופטימלי. עלות פיתוח פתרון נוירוני גבוהה יותר אך לעיתים קרובות מוצדקת כלכלית תחת עומס גבוה.
import lightgbm as lgb
model = lgb.LGBMRanker(
objective='lambdarank',
metric='ndcg',
ndcg_eval_at=[5, 10, 20],
n_estimators=500,
learning_rate=0.05,
num_leaves=63
)
model.fit(
X_train, y_train, # y — relevance labels: 0=ignored, 1=viewed, 2=liked, 3=shared
group=train_groups, # размер групп запросов
eval_set=[(X_val, y_val)],
eval_group=[val_groups]
) איך להבטיח גלילה חלקה במובייל?
אנו מיישמים טעינה מוקדמת: כאשר משתמש גולל 70% מהקבוצה הנוכחית, אנו טוענים את 20 הפוסטים הבאים ברקע. באנדרואיד, אנו משתמשים ב-Paging 3 עם # Feature vector для одного кандидата @dataclass class RankingFeatures: # Content features post_age_hours: float engagement_rate_24h: float viral_velocity: float # views_per_hour in first 2 hours # User-content interaction topic_affinity: float # cosine sim между профилем юзера и эмбеддингом поста author_ctr_for_user: float # исторический CTR этого автора у этого юзера # Context hour_of_day: int is_weekend: bool session_depth: int # сколько постов уже просмотрено в сессии . דוגמה:
// Android: Paging 3 с prefetch для персонализированной ленты
class FeedPagingSource(
private val feedApi: FeedApi,
private val userId: String
) : PagingSource<String, FeedPost>() {
override suspend fun load(params: LoadParams<String>): LoadResult<String, FeedPost> {
return try {
val response = feedApi.getPersonalizedFeed(
userId = userId,
cursor = params.key,
pageSize = params.loadSize
)
LoadResult.Page(
data = response.posts,
prevKey = null,
nextKey = response.nextCursor
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
// ViewModel
val feed = Pager(
config = PagingConfig(pageSize = 20, prefetchDistance = 5),
pagingSourceFactory = { FeedPagingSource(feedApi, userId) }
).flow.cachedIn(viewModelScope) היקף העבודה ולוחות זמנים
| שלב | משך | תוצאה |
|---|---|---|
| ביקורת נתונים ואותות | שבוע אחד | מסמך על איכות לוגים והרכב מאפיינים |
| צינור מאפיינים + אימון מודל | 2–3 שבועות | מדרג LightGBM בסיסי עם NDCG@10 > 0.45 |
| פיתוח API שרת וקוח מובייל | שבועיים | טעינה מוקדמת משולבת ובדיקת A/B בייצור |
| בדיקות A/B ואיטרציות | 2–4 שבועות | מדידת שיפור CTR@10 של 15–30% |
אנו מבטיחים מסירה בזמן של כל שלב עם תיעוד מלא והמודל המאומן. העלויות מוערכות באופן אישי על בסיס המחסן הטכנולוגי שלכם ונפח הנתונים.
מלכודות טיפוסיות
ראשית, חוסר גיוון. ללא MPR (רלוונטיות שולית מקסימלית), הפיד הופך חד-צדדי. פתרון: לא יותר מ-2 פוסטים מאותו כותב בעשרת הראשונים, בתוספת הוריסטיקות נושאיות. שנית, תוויות שגויות. שימוש רק בצפיות מטה לכיוון קליקבייט. כללו לייקים ושיתופים. שלישית, התעלמות מהתחלה קרה. משתמש חדש ללא היסטוריה — הציגו תוכן פופולרי ואספו אותות במהירות באמצעות אלגוריתמי bandit. חיסכון במשאבים נובע משימוש ברכיבים מוכנים וספריות קוד פתוח.
הבטחת גיוון תוכן
אנו מיישמים עיבוד MPR לאחר הדירוג: לאחר הדירוג, אנו מדרגים מחדש מועמדים תוך התחשבות בדמיון. בנוסף, אנו משתמשים בהוריסטיקות גלובליות — לדוגמה, כל פוסט עוקב חייב להיות שונה בנושא מהקודם, ומספר הפוסטים מאותו כותב מוגבל.
הערכות לוחות זמנים
מדרג LightGBM עם מאפיינים בסיסיים ו-API — 2–3 שבועות. מערכת מלאה עם שליפה ודירוג דו-שלביים, עיבוד גיוון ו-A/B testing — 6–10 שבועות.
| אפשרות | לוח זמנים | כולל |
|---|---|---|
| בסיסי | 2–3 שבועות | מדרג LightGBM, מאפיינים בסיסיים, API, ללא גיוון |
| סטנדרטי | 4–6 שבועות | בסיסי + ביקורת נתונים, גיוון MPR, בדיקת A/B |
| מלא | 6–10 שבועות | סטנדרטי + צינור דו-מפלסי, שליפה עם למידה עמוקה, bandit להתחלה קרה |
קבלו ייעוץ — אנו נעשה הערכה ראשונית מהירה על בסיס הנתונים שלכם. צרו קשר כדי לדון בפרטים ולייעל את התקציב.







