AI推荐系统:这次怎么落地的

先整理一下产品提出的需求:

  1. 商品详情页推荐:用户在查看商品详情时,显示"相关推荐"
  2. 个性化推荐:根据用户的浏览和购买历史,推荐可能感兴趣的商品
  3. 分类页推荐:在分类页面,根据用户在该分类的浏览行为推荐商品
  4. 搜索结果推荐:用户搜索后,在结果页下方展示相关推荐

技术团队给的约束条件也很现实:

背景和问题

前些天在重构一个电商后台的时候,产品经理跑过来问我:“能不能给用户加个推荐功能?就是那种用户看了A商品,给他推荐类似的商品。”

我当时心想这不就是一个简单的相似度匹配吗?直接用商品分类或者标签不就行了。但实际操作下来,发现事情没那么简单。

遇到的实际问题

首先,商品数据结构很乱。有些商品有详细标签,有些就只有一个分类名称;其次,用户行为数据也分散在不同的表里,浏览记录、收藏记录、购买记录都在各自的地盘;最坑的是,项目数据量不算太大,但也足够让那些复杂的深度学习模型跑不起来。

这些现实条件限制了我们的选择:不能用太复杂的模型,不能有太多外部依赖,还得能在有限的服务器资源上跑起来。

要解决什么

所以我需要设计一个实用、轻量、但又能提升用户体验的推荐系统。具体来说:

  • 基于用户行为历史推荐相关商品
  • 支持冷启动场景(新用户、新商品)
  • 能够快速计算和响应
  • 资源占用可控

这篇文章就是我折腾这个推荐系统的过程记录,希望能给类似场景的同学一些参考。

需求分析

业务需求

先整理一下产品提出的需求:

  1. 商品详情页推荐:用户在查看商品详情时,显示"相关推荐"
  2. 个性化推荐:根据用户的浏览和购买历史,推荐可能感兴趣的商品
  3. 分类页推荐:在分类页面,根据用户在该分类的浏览行为推荐商品
  4. 搜索结果推荐:用户搜索后,在结果页下方展示相关推荐

技术约束

技术团队给的约束条件也很现实:

  • 服务器资源有限,不能部署大型深度学习模型
  • 计算需要快速完成,用户等待时间不能超过200ms
  • 需要支持在线实时更新,推荐结果要能及时反映最新数据
  • 现有技术栈是 Python + PostgreSQL + Redis,尽量不引入新的重型依赖

数据现状

先看看我们有什么数据:

# 商品表结构(简化版)
class Product:
    id: int
    name: str
    category: str
    tags: List[str]
    price: float
    created_at: datetime
    # ... 其他字段

# 用户行为表结构
class UserBehavior:
    user_id: int
    product_id: int
    action: str  # 'view', 'favorite', 'purchase'
    timestamp: datetime
    # ... 其他字段

数据量大约是:商品2万个,活跃用户5000个,历史行为记录50万条。这个量级其实挺适合做协同过滤的。

实现方案

基于以上分析,我决定采用混合推荐策略:以协同过滤为主,内容过滤为辅,同时处理冷启动问题。

系统架构

整个推荐系统的架构如下:

graph TD A[用户请求] --> B[推荐服务] B --> C{用户类型} C -->|新用户| D[热门商品推荐] C -->|老用户| E[协同过滤] E --> F[用户-商品矩阵] B --> G{商品类型} G -->|新商品| H[内容过滤] G -->|老商品| I[相似商品推荐] D --> J[结果合并] F --> J H --> J I --> J J --> K[推荐结果] K --> L[返回给用户]

协同过滤实现

用户-商品矩阵构建

首先需要构建用户-商品交互矩阵。我选择了隐式反馈(用户浏览、收藏、购买),因为显式评分数据太少了。

import numpy as np
import pandas as pd
from collections import defaultdict

def build_user_item_matrix(behaviors):
    """
    构建用户-商品交互矩阵
    """
    # 初始化数据结构
    user_ids = set()
    item_ids = set()
    interactions = defaultdict(lambda: defaultdict(float))

    # 处理行为数据,给不同行为不同的权重
    action_weights = {
        'view': 1.0,
        'favorite': 2.0,
        'purchase': 5.0
    }

    for behavior in behaviors:
        user_id = behavior['user_id']
        item_id = behavior['product_id']
        action = behavior['action']

        user_ids.add(user_id)
        item_ids.add(item_id)

        # 累加行为权重
        interactions[user_id][item_id] += action_weights.get(action, 0)

    # 转换为矩阵形式
    user_list = sorted(user_ids)
    item_list = sorted(item_ids)
    user_to_idx = {uid: idx for idx, uid in enumerate(user_list)}
    item_to_idx = {iid: idx for idx, iid in enumerate(item_list)}

    matrix = np.zeros((len(user_list), len(item_list)))

    for user_id, items in interactions.items():
        for item_id, weight in items.items():
            matrix[user_to_idx[user_id], item_to_idx[item_id]] = weight

    return matrix, user_to_idx, item_to_idx

矩阵分解

考虑到稀疏矩阵的问题,我使用了矩阵分解来降维和提高计算效率:

from scipy.sparse.linalg import svds

def matrix_factorization(matrix, n_factors=10):
    """
    矩阵分解
    """
    # 填充缺失值为0
    user_means = np.mean(matrix, axis=1)
    matrix_demeaned = matrix - user_means.reshape(-1, 1)

    # SVD分解
    U, sigma, Vt = svds(matrix_demeaned, k=n_factors)

    # 重构预测矩阵
    sigma = np.diag(sigma)
    predicted_ratings = np.dot(np.dot(U, sigma), Vt) + user_means.reshape(-1, 1)

    return predicted_ratings, U, sigma, Vt

相似度计算

对于商品相似度计算,我选择了余弦相似度:

from sklearn.metrics.pairwise import cosine_similarity

def calculate_item_similarity(Vt):
    """
    计算商品相似度矩阵
    """
    # Vt是商品特征矩阵的转置,转回来
    item_features = Vt.T

    # 计算余弦相似度
    similarity_matrix = cosine_similarity(item_features)

    return similarity_matrix

def get_similar_items(item_id, item_to_idx, similarity_matrix, top_k=10):
    """
    获取相似商品
    """
    if item_id not in item_to_idx:
        return []

    item_idx = item_to_idx[item_id]
    similarities = similarity_matrix[item_idx]

    # 获取最相似的top_k个商品
    similar_indices = np.argsort(similarities)[::-1][1:top_k+1]  # 排除自己

    idx_to_item = {idx: iid for iid, idx in item_to_idx.items()}
    similar_items = [
        (idx_to_item[idx], similarities[idx])
        for idx in similar_indices
        if similarities[idx] > 0
    ]

    return similar_items

内容过滤实现

为了处理冷启动问题,还需要基于内容特征的推荐:

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

def build_content_similarity(products):
    """
    基于商品内容特征构建相似度
    """
    # 提取文本特征
    texts = []
    for product in products:
        # 组合名称、分类、标签
        text = f"{product['name']} {product['category']} {' '.join(product['tags'])}"
        texts.append(text)

    # TF-IDF向量化
    vectorizer = TfidfVectorizer(max_features=1000)
    tfidf_matrix = vectorizer.fit_transform(texts)

    # 计算相似度
    similarity_matrix = cosine_similarity(tfidf_matrix)

    return similarity_matrix, vectorizer

推荐服务整合

将所有部分整合到一个推荐服务中:

class RecommendationService:
    def __init__(self):
        self.user_item_matrix = None
        self.predicted_ratings = None
        self.item_similarity = None
        self.content_similarity = None
        self.user_to_idx = {}
        self.item_to_idx = {}
        self.products = {}

    def train(self, behaviors, products):
        """
        训练推荐模型
        """
        # 构建用户-商品矩阵
        self.user_item_matrix, self.user_to_idx, self.item_to_idx = \
            build_user_item_matrix(behaviors)

        # 矩阵分解
        self.predicted_ratings, U, sigma, Vt = \
            matrix_factorization(self.user_item_matrix)

        # 商品相似度(协同过滤)
        self.item_similarity = calculate_item_similarity(Vt)

        # 内容相似度(用于冷启动)
        self.content_similarity, _ = build_content_similarity(products)

        # 保存商品信息
        self.products = {p['id']: p for p in products}

    def recommend_for_user(self, user_id, top_k=10):
        """
        为用户推荐商品
        """
        if user_id not in self.user_to_idx:
            return self._get_popular_items(top_k)

        user_idx = self.user_to_idx[user_id]
        user_ratings = self.predicted_ratings[user_idx]

        # 获取用户未交互的商品
        interacted_items = set(self.user_item_matrix[user_idx].nonzero()[1])
        recommendations = []

        for item_idx, rating in enumerate(user_ratings):
            if item_idx not in interacted_items:
                item_id = list(self.item_to_idx.keys())[list(self.item_to_idx.values()).index(item_idx)]
                recommendations.append((item_id, rating))

        # 按预测评分排序
        recommendations.sort(key=lambda x: x[1], reverse=True)
        return recommendations[:top_k]

    def recommend_similar_items(self, item_id, top_k=10):
        """
        推荐相似商品
        """
        if item_id in self.item_to_idx:
            # 使用协同过滤的相似度
            return get_similar_items(item_id, self.item_to_idx, self.item_similarity, top_k)
        else:
            # 使用内容相似度处理冷启动
            return self._get_content_similar_items(item_id, top_k)

    def _get_popular_items(self, top_k=10):
        """
        获取热门商品
        """
        # 简单实现:基于交互次数
        item_popularity = np.sum(self.user_item_matrix, axis=0)
        popular_indices = np.argsort(item_popularity)[::-1][:top_k]

        idx_to_item = {idx: iid for iid, idx in self.item_to_idx.items()}
        return [(idx_to_item[idx], item_popularity[idx]) for idx in popular_indices]

    def _get_content_similar_items(self, item_id, top_k=10):
        """
        基于内容获取相似商品
        """
        # 这里简化处理,实际需要根据商品ID查找在content_similarity中的位置
        return []

缓存优化

为了提高响应速度,我加了一层 Redis 缓存:

import redis
import json
import hashlib

class CachedRecommendationService:
    def __init__(self, redis_host='localhost', redis_port=6379):
        self.redis_client = redis.StrictRedis(
            host=redis_host,
            port=redis_port,
            decode_responses=True
        )
        self.base_service = RecommendationService()

    def _get_cache_key(self, method_name, *args):
        """
        生成缓存键
        """
        key_data = f"{method_name}:{args}"
        return hashlib.md5(key_data.encode()).hexdigest()

    def recommend_for_user(self, user_id, top_k=10, cache_ttl=3600):
        """
        带缓存的用户推荐
        """
        cache_key = self._get_cache_key('recommend_for_user', user_id, top_k)

        # 尝试从缓存获取
        cached_result = self.redis_client.get(cache_key)
        if cached_result:
            return json.loads(cached_result)

        # 缓存未命中,调用基础服务
        result = self.base_service.recommend_for_user(user_id, top_k)

        # 写入缓存
        self.redis_client.setex(
            cache_key,
            cache_ttl,
            json.dumps(result)
        )

        return result

    def invalidate_user_cache(self, user_id):
        """
        使特定用户的缓存失效
        """
        # 这里简化处理,实际可能需要更复杂的缓存键管理
        pattern = f"*recommend_for_user:{user_id}*"
        keys = self.redis_client.keys(pattern)
        if keys:
            self.redis_client.delete(*keys)

踩坑记录

实现过程中遇到了不少坑,这里记录几个比较典型的:

数据稀疏性问题

最开始直接用原始的用户-商品矩阵,结果发现99%以上的值都是0,这导致相似度计算效果很差。

解决方案

  • 增加数据预处理,过滤掉行为次数很少的用户和商品
  • 使用矩阵分解降维
  • 对交互行为加权,给更有意义的行为(如购买)更高的权重

冷启动难题

新用户没有历史行为,新商品没有交互记录,这导致协同过滤完全失效。

解决方案

  • 对新用户:先推荐热门商品,同时收集用户行为
  • 对新商品:使用内容过滤,基于商品属性找相似商品
  • 混合策略:同时使用多种推荐方法,加权合并结果

计算性能瓶颈

矩阵分解和相似度计算都很耗时,特别是数据量大的时候。

解决方案

  • 使用增量更新,只重新计算变化的部分
  • 加 Redis 缓存,缓存热门用户和商品的推荐结果
  • 降低矩阵分解的维度,在效果和性能间找平衡
  • 使用稀疏矩阵存储,节省内存

实时性要求

用户刚浏览了商品,希望推荐结果能立即反映这个行为。

解决方案

  • 用户行为实时更新到缓存
  • 对活跃用户使用更短的缓存时间
  • 提供手动刷新接口,用户可以强制刷新推荐结果

评估指标选择

怎么衡量推荐效果好坏?准确率?召回率?还是用户点击率?

解决方案

  • 离线评估:使用准确率、召回率、NDCG等指标
  • 在线评估:A/B测试,比较推荐系统的点击率、转化率
  • 业务指标:关注推荐系统对GMV、用户留存的影响

实施结果

经过几周的折腾,推荐系统终于上线了。效果还算不错:

性能指标

  • 推荐响应时间:平均150ms,满足<200ms的要求
  • 缓存命中率:85%以上,大部分请求都能从缓存返回
  • 资源占用:CPU使用率<30%,内存占用<2GB

业务效果

  • 推荐商品点击率:提升15%
  • 推荐商品转化率:提升8%
  • 用户停留时长:增加12%
  • 页面浏览量(PV):增加10%

用户反馈

收集了一些用户反馈,正面的包括:

  • “推荐的商品确实是我感兴趣的”
  • “有时候能发现一些不错的新商品”
  • “比之前的随机推荐好多了”

负面反馈主要是:

  • “有时候推荐的商品跟当前看的完全不相关”
  • “推荐结果更新有点慢,刚浏览的商品没反映出来”

代码质量

  • 单元测试覆盖率:80%
  • 代码复杂度:控制在合理范围
  • 文档完整:有API文档和使用说明

优化方向

虽然上线了,但还有很多可以优化的地方:

短期优化

  • 增加更多行为数据:如搜索关键词、加入购物车等
  • 优化冷启动策略:比如用用户注册信息做初步推荐
  • 提升实时性:用户行为后推荐结果能立即更新
  • 个性化调参:不同用户使用不同的推荐参数

中期优化

  • 尝试其他算法:如基于深度学习的推荐模型
  • 增加多目标优化:同时考虑点击率、转化率、多样性等
  • 建立反馈机制:用户可以对推荐结果点赞/点踩
  • A/B测试平台:快速验证新的推荐策略

长期规划

  • 实时流处理:用Flink等工具处理实时数据流
  • 分布式计算:用Spark等工具处理更大规模数据
  • 深度学习模型:尝试用神经协同过滤等高级模型
  • 跨域推荐:结合其他业务数据做跨域推荐

总结

这次做推荐系统的经历让我深刻体会到,学术上的算法和工业界的实践确实有很大差距。

学术论文里那些复杂的深度学习模型,在真实场景中往往很难落地。反而是那些简单、实用、可解释的方法,更容易产生价值。

推荐系统的核心不是算法有多复杂,而是:

  1. 理解业务需求:清楚要解决什么问题,达到什么目标
  2. 了解数据现状:知道有什么数据,数据质量如何
  3. 选择合适方案:在效果和成本间找到平衡点
  4. 持续优化迭代:根据反馈不断调整改进

对于我们这种中小型项目,简单有效的协同过滤+内容过滤的混合策略,可能比复杂的深度学习模型更合适。

希望这篇文章能给那些在类似场景下做推荐系统的同学一些参考。推荐系统这个领域还有很多值得探索的地方,下次有机会再分享更多实践经验。

参考资源

  • 《推荐系统实践》项亮
  • 《Recommender Systems Handbook》
  • Surprise库:https://surpriselib.com/
  • LightFM库:https://github.com/lyst/lightfm

版权声明: 本文首发于 指尖魔法屋-AI推荐系统:这次怎么落地的https://blog.thinkmoon.cn/post/327-ai-recommendation-system-user-item-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!