בניית מערכת המלצות מכירה צולבת מבוססת AI למכירות

חוקים סטטיים לא מצליחים לזהות דפוסים נסתרים ברכישות, מה שגורם להחמצת קשרי מכירה צולבת לא ברורים. אנחנו מפתחים מערכות המלצה מבוססות בינה מלאכותית החושפות דפוסים כאלה ומתאימות הצעות אישית לכל לקוח. הצוות שלנו מספק את הפרויקט במפתח מלא—מבדיקת נתונים ועד הטמעה ותמיכה שוטפת.

תחומי פיתוח AI

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

בניית מערכת המלצות חכמה למכירה צולבת (Cross-Sell) מבוססת AI

פנתה אלינו חברת מסחר אלקטרוני עם 50,000 פריטים (SKU): הכלל "מדפסת → קרטון דיו" הניב רק 8% המרה. הם נזקקו למערכת המלצות מבוססת למידת מכונה שמוצאת זוגות לא מובנים מאליהם ומתאימה את עצמה להתנהגות של כל לקוח. יישמנו מערכת היברידית למסחר אלקטרוני ולקמעונאות: כללי אסוציאציה לחבילות בסיסיות + gradient boosting להתאמה אישית. התוצאה: עלייה של 18% בערך סל הקניות הממוצע בחודש הראשון, מה שסיפק גידול משמעותי בהכנסות. כך זה עובד.

בעיות שאנחנו פותרים

כללים סטטיים לא לוכדים דפוסים נסתרים. לדוגמה, ללקוחות שקנו מושב בטיחות לילד יש הסתברות של 22% לקנות מארגן תא מטען—למרות שהפריטים מקטגוריות שונות. למידת מכונה מוצאת דפוסים כאלה באמצעות כללי אסוציאציה (Lift > 2.0). התאמה אישית באמצעות gradient boosting לוקחת בחשבון את היסטוריית הלקוח: אם המשתמש כבר קנה מארגן, ההמלצה עוברת לפריט אחר.

בעיה נפוצה נוספת היא מוצרים עם מחזור צריכה קצר (מוצרים מתכלים, מנויים). כאן נדרשת חיזוי של הרכישה הבאה. מודל המבוסס על התפלגות מרווחי זמן מוציא פריטים שהלקוח עומד להזמין מחדש. כך אנחנו מגדילים מכירות חוזרות ללא מבצעים.

הבעיה השלישית היא התחלה קרה (cold start) למוצרים חדשים. אם למוצר אין היסטוריית רכישות, כללי Apriori לא יעבדו. הפתרון הוא שימוש במאפיינים מבוססי תוכן (קטגוריה, מחיר, מותג) וסינון שיתופי באמצעות embeddings. לשם כך, אנחנו מעשירים את מודל ה-gradient boosting במאפייני מוצר.

איך אנחנו בונים את מודל ה-CrossSellRecommender

ניתוח סל שוק + התאמה אישית

ארכיטקטורה היברידית: ראשית Apriori נותן כללים גולמיים, ולאחר מכן gradient boosting מדרג מועמדים למשתמש ספציפי. השילוב הזה מספק שיפור בהמרה פי 3 גבוה יותר מגישה מבוססת כללים.

import pandas as pd
import numpy as np
from mlxtend.frequent_patterns import apriori, association_rules
from mlxtend.preprocessing import TransactionEncoder
from sklearn.ensemble import GradientBoostingClassifier

class CrossSellRecommender:
    def __init__(self, min_support: float = 0.01, min_confidence: float = 0.1):
        self.min_support = min_support
        self.min_confidence = min_confidence
        self.rules = None
        self.cross_sell_map = {}
        self.personalization_model = None

    def fit_association_rules(self, orders_df: pd.DataFrame, order_col: str = "order_id", item_col: str = "item_id"):
        """Поиск ассоциативных правил через Apriori"""
        # Корзины транзакций
        baskets = orders_df.groupby(order_col)[item_col].apply(list).tolist()
        te = TransactionEncoder()
        te_array = te.fit_transform(baskets)
        df_encoded = pd.DataFrame(te_array, columns=te.columns_)

        # Частые наборы
        frequent_itemsets = apriori(
            df_encoded,
            min_support=self.min_support,
            use_colnames=True,
            max_len=3
        )

        # Правила ассоциации
        self.rules = association_rules(
            frequent_itemsets,
            metric="lift",
            min_threshold=1.2
        )
        self.rules = self.rules[self.rules['confidence'] >= self.min_confidence]
        self.rules = self.rules.sort_values('lift', ascending=False)

        # Маппинг: item → список рекомендаций с метриками
        for _, rule in self.rules.iterrows():
            for antecedent in rule['antecedents']:
                if antecedent not in self.cross_sell_map:
                    self.cross_sell_map[antecedent] = []
                for consequent in rule['consequents']:
                    if antecedent != consequent:
                        self.cross_sell_map[antecedent].append({
                            'item_id': consequent,
                            'confidence': rule['confidence'],
                            'lift': rule['lift'],
                            'support': rule['support']
                        })

        # Сортировка по lift
        for item in self.cross_sell_map:
            self.cross_sell_map[item].sort(key=lambda x: x['lift'], reverse=True)

    def recommend_cross_sell(self, cart_items: list[str], user_history: list[str] = None, n: int = 5) -> list[dict]:
        """Cross-sell для текущей корзины"""
        candidates = {}
        for item_id in cart_items:
            related = self.cross_sell_map.get(item_id, [])
            for rec in related:
                rec_id = rec['item_id']
                # Пропускаем уже в корзине или в истории
                if rec_id in cart_items:
                    continue
                if user_history and rec_id in user_history:
                    continue
                if rec_id not in candidates:
                    candidates[rec_id] = {'score': 0, 'triggers': []}
                candidates[rec_id]['score'] += rec['lift']
                candidates[rec_id]['triggers'].append(item_id)

        # Нормализация
        if not candidates:
            return []
        sorted_candidates = sorted(
            [{'item_id': k, **v} for k, v in candidates.items()],
            key=lambda x: x['score'],
            reverse=True
        )
        return sorted_candidates[:n]

    def get_complementary_categories(self, category: str) -> list[str]:
        """Взаимодополняющие категории"""
        category_rules = self.rules[
            self.rules['antecedents'].apply(lambda x: category in str(x))
        ]['consequents'].apply(lambda x: list(x)).explode().value_counts()
        return category_rules.head(5).index.tolist()

דפוסים זמניים: רכישה הבאה

class NextPurchasePredictor:
    """Предсказание следующей покупки на основе истории"""

    def predict_next_items(self, user_id: str, purchase_history: list[dict], catalog_features: pd.DataFrame) -> list[tuple]:
        """
        purchase_history: [{item_id, date, quantity, category}]
        Возвращает: [(item_id, probability)]
        """
        if len(purchase_history) < 3:
            return []

        # Паттерн повторных покупок
        item_intervals = {}
        for i in range(1, len(purchase_history)):
            item = purchase_history[i]['item_id']
            prev_same = [h for h in purchase_history[:i] if h['item_id'] == item]
            if prev_same:
                days_between = (
                    pd.to_datetime(purchase_history[i]['date']) - pd.to_datetime(prev_same[-1]['date'])
                ).days
                if item not in item_intervals:
                    item_intervals[item] = []
                item_intervals[item].append(days_between)

        # Предсказание повторных покупок
        predictions = []
        last_purchase_date = pd.to_datetime(purchase_history[-1]['date'])
        today = pd.Timestamp.now()
        days_since_last = (today - last_purchase_date).days

        for item_id, intervals in item_intervals.items():
            avg_interval = np.mean(intervals)
            std_interval = np.std(intervals) if len(intervals) > 1 else avg_interval * 0.3

            # Вероятность через normal distribution
            from scipy.stats import norm
            prob = norm.cdf(days_since_last + 7, avg_interval, std_interval + 1)
            prob -= norm.cdf(days_since_last - 7, avg_interval, std_interval + 1)
            prob = min(max(prob, 0), 1)

            if prob > 0.1:
                predictions.append((item_id, prob))

        return sorted(predictions, key=lambda x: x[1], reverse=True)[:10]
מידע נוסף על כוונון Apriori

כללי אסוציאציה עם import pandas as pd import numpy as np from mlxtend.frequent_patterns import apriori, association_rules from mlxtend.preprocessing import TransactionEncoder from sklearn.ensemble import GradientBoostingClassifier class CrossSellRecommender: def __init__(self, min_support: float = 0.01, min_confidence: float = 0.1): self.min_support = min_support self.min_confidence = min_confidence self.rules = None self.cross_sell_map = {} self.personalization_model = None def fit_association_rules(self, orders_df: pd.DataFrame, order_col: str = "order_id", item_col: str = "item_id"): """Поиск ассоциативных правил через Apriori""" # Корзины транзакций baskets = orders_df.groupby(order_col)[item_col].apply(list).tolist() te = TransactionEncoder() te_array = te.fit_transform(baskets) df_encoded = pd.DataFrame(te_array, columns=te.columns_) # Частые наборы frequent_itemsets = apriori( df_encoded, min_support=self.min_support, use_colnames=True, max_len=3 ) # Правила ассоциации self.rules = association_rules( frequent_itemsets, metric="lift", min_threshold=1.2 ) self.rules = self.rules[self.rules['confidence'] >= self.min_confidence] self.rules = self.rules.sort_values('lift', ascending=False) # Маппинг: item → список рекомендаций с метриками for _, rule in self.rules.iterrows(): for antecedent in rule['antecedents']: if antecedent not in self.cross_sell_map: self.cross_sell_map[antecedent] = [] for consequent in rule['consequents']: if antecedent != consequent: self.cross_sell_map[antecedent].append({ 'item_id': consequent, 'confidence': rule['confidence'], 'lift': rule['lift'], 'support': rule['support'] }) # Сортировка по lift for item in self.cross_sell_map: self.cross_sell_map[item].sort(key=lambda x: x['lift'], reverse=True) def recommend_cross_sell(self, cart_items: list[str], user_history: list[str] = None, n: int = 5) -> list[dict]: """Cross-sell для текущей корзины""" candidates = {} for item_id in cart_items: related = self.cross_sell_map.get(item_id, []) for rec in related: rec_id = rec['item_id'] # Пропускаем уже в корзине или в истории if rec_id in cart_items: continue if user_history and rec_id in user_history: continue if rec_id not in candidates: candidates[rec_id] = {'score': 0, 'triggers': []} candidates[rec_id]['score'] += rec['lift'] candidates[rec_id]['triggers'].append(item_id) # Нормализация if not candidates: return [] sorted_candidates = sorted( [{'item_id': k, **v} for k, v in candidates.items()], key=lambda x: x['score'], reverse=True ) return sorted_candidates[:n] def get_complementary_categories(self, category: str) -> list[str]: """Взаимодополняющие категории""" category_rules = self.rules[ self.rules['antecedents'].apply(lambda x: category in str(x)) ]['consequents'].apply(lambda x: list(x)).explode().value_counts() return category_rules.head(5).index.tolist() , class NextPurchasePredictor: """Предсказание следующей покупки на основе истории""" def predict_next_items(self, user_id: str, purchase_history: list[dict], catalog_features: pd.DataFrame) -> list[tuple]: """ purchase_history: [{item_id, date, quantity, category}] Возвращает: [(item_id, probability)] """ if len(purchase_history) < 3: return [] # Паттерн повторных покупок item_intervals = {} for i in range(1, len(purchase_history)): item = purchase_history[i]['item_id'] prev_same = [h for h in purchase_history[:i] if h['item_id'] == item] if prev_same: days_between = ( pd.to_datetime(purchase_history[i]['date']) - pd.to_datetime(prev_same[-1]['date']) ).days if item not in item_intervals: item_intervals[item] = [] item_intervals[item].append(days_between) # Предсказание повторных покупок predictions = [] last_purchase_date = pd.to_datetime(purchase_history[-1]['date']) today = pd.Timestamp.now() days_since_last = (today - last_purchase_date).days for item_id, intervals in item_intervals.items(): avg_interval = np.mean(intervals) std_interval = np.std(intervals) if len(intervals) > 1 else avg_interval * 0.3 # Вероятность через normal distribution from scipy.stats import norm prob = norm.cdf(days_since_last + 7, avg_interval, std_interval + 1) prob -= norm.cdf(days_since_last - 7, avg_interval, std_interval + 1) prob = min(max(prob, 0), 1) if prob > 0.1: predictions.append((item_id, prob)) return sorted(predictions, key=lambda x: x[1], reverse=True)[:10] בדרך כלל מניבים 500-5000 כללים משמעותיים עבור 100K הזמנות. Lift > 2.0 מצביע על אסוציאציה חזקה. לכוונון היפרפרמטרים, אנו משתמשים באימות צולב: פיצול נתונים לפי זמן (אימון/בדיקה) ובחירת min_support=0.01, min_confidence=0.1 שממקסמים את ה-lift על קבוצת האימות.

איך לאמן את ה-CrossSellRecommender על הנתונים שלך

  1. הכנת נתונים. אסוף היסטוריית הזמנות עם עמודות: order_id, item_id, date. מספר מינימלי של עסקאות — 10,000.
  2. הרץ Apriori. השתמש בשיטת min_support עם min_confidence ו-fit_association_rules. זה ייתן בערך 1000 כללים.
  3. כוונן פרמטרים. בדוק את התפלגות ה-lift וה-confidence. אם יש יותר מדי כללים, הגדל את min_support=0.01 ל-0.02 או את min_confidence=0.1 ל-0.2.
  4. הערך איכות. השתמש במדדים precision@k ו-recall@k על קבוצת בדיקה מופרדת. מכירה צולבת מבוססת כללים נותנת עלייה ממוצעת בסל של 15-25%. שילוב עם התאמה אישית (היסטוריית משתמש) מוסיף עוד 5-10% לשיעור הקבלה.

איך למדוד את איכות ההמלצות?

מדדים עיקריים לא מקוונים: precision@k, recall@k, lift@k. אנו משתמשים בסל קניות קטום — מסתירים חלק מהפריטים מכל עסקה ובודקים אם הם מופיעים בהמלצות. מדדים מקוונים: שיעור קבלה, עליית AOV, חלק הסלים עם מכירה צולבת. בבדיקת A/B עם 50% קהל, שיפור אופייני בשיעור הקבלה הוא 10-25%.

למה המודל ההיברידי יעיל יותר ממבוסס כללים?

מבוסס כללים (למשל, "קנה A → הצג B") לא מתאים לקנה מידה של אלפי פריטים: הכללים הופכים לדלילים ולא מתחשבים בהקשר. למידת מכונה מוצאת אסוציאציות עם lift גבוה אפילו לזוגות נדירים. בנוסף, ההיברידית מתאימה אישית את הדירוג: אותו פריט מדורג אחרת עבור משתמשים שונים. בפרויקטים שלנו, שיעור הקבלה של ההיברידית גבוה פי 3 מזה של כללים.

שלבי יישום

שלב מה אנחנו עושים תוצאה משך
1. ביקורת נתונים בדיקת שלמות ואיכות: מספר פריטים, אורך היסטוריה, תדירות הזמנות לוח מחוונים עם מדדים, רשימת פערים 1-2 ימים
2. בניית צינור נתונים ETL לאיסוף נתונים מה-CRM, ניקוי, צבירת סלים מערך נתונים orders_df 3-4 ימים
3. אימון כללים Apriori, כוונון min_support ו-min_confidence באמצעות אימות צולב קובץ כללים (.pkl) 1-2 ימים
4. התאמה אישית אימון gradient boosting על היסטוריית משתמשים מודל דירוג 3-5 ימים
5. בדיקת A/B 50% קהל — מערכת ישנה, 50% — חדשה. מדידת המרה ו-AOV דוח עם שיפור 7-14 ימים
6. פריסה לייצור אינטגרציה דרך REST API, ניטור זמן תגובה p99 תיעוד, לוח מחוונים 2-3 ימים

מה כלול בתוצאה

  • תיעוד: תיאור כללים, מדדים, הוראות לעדכון מודל.
  • קוד מקור: מאגר עם מודולים min_support, min_confidence, קובצי הגדרות ובדיקות.
  • נקודות קצה API: שתי שיטות — CrossSellRecommender (מבוסס על סל נוכחי) ו-NextPurchasePredictor (מבוסס על היסטוריה).
  • הדרכת צוות: אנו מראים כיצד לפרש תוצאות ולאמן מחדש את המודל.
  • אחריות: אנו תומכים בבדיקת A/B עד להשגת שיפור מובהק סטטיסטית.

איך להעריך ROI לעסק שלך

מדד ערך אופייני איך אנחנו מחשבים
עלייה בערך סל קניות ממוצע 15-25% (AOV_test - AOV_control) / AOV_control * 100%
שיעור קבלה 5-12% קליקים על המלצות / חשיפות
חלק הסלים עם מכירה צולבת 30-50% סלים עם ≥1 פריט מומלץ / סה"כ סלים
זמן החזר השקעה 2-4 חודשים עלות יישום / גידול הכנסות חודשי

כדי להעריך את פוטנציאל המכירה הצולבת עבור המגוון שלך, צור איתנו קשר. נבצע ביקורת נתונים ונספק אב טיפוס תוך שבועיים עם אחריות לסודיות. בקש ייעוץ — נדון בפרטי הפרויקט שלך ונכין הצעה מותאמת.