Реализация AI-рекомендательной системы с гибридным подходом в мобильном приложении
Чистый Collaborative Filtering ломается на холодном старте. Чистый Content-Based игнорирует социальные сигналы и застревает в filter bubble. Гибридные системы комбинируют оба подхода — и именно они стоят за лентами крупных платформ: Netflix, Spotify, Amazon.
Главный инженерный вопрос не «нужен ли гибрид», а «как именно комбинировать».
Три стратегии комбинирования
Weighted Hybrid — взвешенная сумма скоров
Самый простой вариант: финальный скор = α × CF_score + (1−α) × CB_score. Параметр α можно делать динамическим — для нового пользователя α = 0.2 (CF слабый, доверяем CB), для опытного α = 0.7.
class WeightedHybridRecommender:
def __init__(self, cf: CFRecommender, cb: CBRecommender):
self.cf = cf
self.cb = cb
def recommend(self, user: User, candidates: list[str], n: int = 20) -> list[str]:
alpha = self._compute_alpha(user.interaction_count)
cf_scores = self.cf.score(user.id, candidates) # dict[item_id -> float]
cb_scores = self.cb.score(user.profile, candidates)
hybrid_scores = {
item_id: alpha * cf_scores.get(item_id, 0) + (1 - alpha) * cb_scores.get(item_id, 0)
for item_id in candidates
}
return sorted(hybrid_scores, key=hybrid_scores.get, reverse=True)[:n]
def _compute_alpha(self, interaction_count: int) -> float:
# линейный рост: 0.2 при 0 взаимодействиях, 0.8 при 100+
return min(0.2 + (interaction_count / 100) * 0.6, 0.8)
Switching Hybrid — выбор стратегии по контексту
Вместо смешивания скоров — переключение между рекомендерами целиком. Логика переключения может быть сложной: CF для залогиненных пользователей с историей, CB для гостей, popularity-based для новых пользователей без onboarding.
// Android: стратегия по контексту пользователя
sealed class RecommendationContext {
object Guest : RecommendationContext()
data class NewUser(val preferences: List<String>) : RecommendationContext()
data class ActiveUser(val userId: String, val interactionCount: Int) : RecommendationContext()
}
class HybridRecommenderRepository(
private val cfApi: CFRecommendationApi,
private val cbApi: CBRecommendationApi,
private val popularApi: PopularityApi
) {
suspend fun getRecommendations(context: RecommendationContext): List<Product> {
return when (context) {
is RecommendationContext.Guest ->
popularApi.getTopProducts(count = 20)
is RecommendationContext.NewUser ->
cbApi.getByPreferences(context.preferences, count = 20)
is RecommendationContext.ActiveUser -> {
if (context.interactionCount < 15) {
// смешиваем: 30% CF + 70% CB
mergeRecommendations(
cfApi.getPersonalized(context.userId, count = 6),
cbApi.getSimilarToHistory(context.userId, count = 14)
)
} else {
cfApi.getPersonalized(context.userId, count = 20)
}
}
}
}
}
Feature-level Hybrid через нейросеть (двухбашенная модель)
Продвинутый вариант: CF-эмбеддинги пользователя и товара конкатенируются с CB-признаками и подаются в shallow нейросеть (2–3 dense слоя). Модель обучается end-to-end предсказывать вероятность клика. Это Two-Tower архитектура, которую используют Google и Airbnb.
# Two-Tower модель (упрощённо)
class TwoTowerModel(nn.Module):
def __init__(self, user_emb_dim=64, item_emb_dim=64, cb_features_dim=50):
super().__init__()
self.user_tower = nn.Sequential(
nn.Linear(user_emb_dim, 128), nn.ReLU(),
nn.Linear(128, 64)
)
self.item_tower = nn.Sequential(
nn.Linear(item_emb_dim + cb_features_dim, 128), nn.ReLU(),
nn.Linear(128, 64)
)
def forward(self, user_emb, item_emb, cb_features):
user_out = self.user_tower(user_emb)
item_input = torch.cat([item_emb, cb_features], dim=-1)
item_out = self.item_tower(item_input)
return torch.sigmoid((user_out * item_out).sum(dim=-1))
На мобильном клиенте Two-Tower serving работает через предвычисление item-эмбеддингов + FAISS ANN.
Мобильная интеграция: кэширование и обновление рекомендаций
// iOS: кэш рекомендаций с TTL и фоновым обновлением
class RecommendationCache {
private let cache = NSCache<NSString, CachedRecommendations>()
private let ttl: TimeInterval = 300 // 5 минут
func get(userId: String) -> [Product]? {
guard let cached = cache.object(forKey: userId as NSString),
Date().timeIntervalSince(cached.timestamp) < ttl
else { return nil }
return cached.products
}
func set(userId: String, products: [Product]) {
cache.setObject(
CachedRecommendations(products: products, timestamp: Date()),
forKey: userId as NSString
)
}
}
Рекомендации обновляются в фоне через BGAppRefreshTask (iOS) / WorkManager (Android) — пользователь видит свежий контент при открытии приложения без ожидания.
Процесс работы
Аудит данных: доступность CF-сигналов, качество CB-метаданных, объём пользовательской базы.
Выбор стратегии комбинирования исходя от сложности задачи и ресурсов.
Разработка serving-слоя с кэшированием на клиенте.
A/B тест: гибрид vs лучший одиночный рекомендер → CTR, время в приложении, конверсия.
Ориентиры по срокам
Weighted Hybrid с готовыми CF и CB компонентами — 1–2 недели. Two-Tower модель с нуля — 4–6 недель включая сбор обучающих данных, обучение и деплой.







