从零构建推荐系统:基于内容与协同过滤的平价好物推荐引擎实践

📅 2026/8/10 4:11:40
从零构建推荐系统:基于内容与协同过滤的平价好物推荐引擎实践
最近刷到不少“求大数据把我推给‘羊毛党’”的帖子点进去一看大多是分享一些高性价比的购物清单。作为一个技术博主我第一反应不是去“剁手”而是思考这种精准的“求推荐”背后是不是藏着我们程序员也能用上的技术逻辑没错今天我们不聊穿搭我们来聊聊“推荐系统”。当你在小红书、抖音喊出“求大数据把我推给XXX”时平台背后的算法是如何工作的它怎么知道谁是“羊毛党”又怎么从海量商品中筛选出那“80平价单品”这本质上是一个复杂的用户画像构建与物品召回排序问题。对于开发者而言理解这套逻辑的价值远超“薅羊毛”。无论是构建电商平台的个性化推荐、内容社区的信息流还是内部知识库的精准检索其核心架构都是相通的。本文将手把手带你拆解一个简易版“平价好物推荐系统”的技术实现。你会看到从一堆杂乱的关键词“男生穿搭”、“平价单品”到生成一份精准的清单中间经历了数据清洗、特征工程、模型匹配和结果排序等多个环节。读完本文你将能理解推荐系统的基本架构与核心流程。动手实现一个基于内容Content-Based和协同过滤Collaborative Filtering思想的简易推荐引擎。掌握如何用Python处理商品和用户数据并完成匹配推荐。了解工业级推荐系统的优化方向与常见陷阱。我们暂时不涉及复杂的深度学习模型而是从最基础、最可解释的原理入手用代码把流程跑通。这才是“性价比”最高的学习方式。1. 从“求推荐”到推荐系统解决了什么问题为什么用户需要喊一句“求大数据推荐”因为信息过载。平台上有数百万商品用户手动筛选效率极低。推荐系统的核心价值就是降低信息获取成本提升用户满意度和平台转化率。具体到“平价男生穿搭”这个场景系统需要解决几个关键问题用户意图模糊“羊毛党”、“性价比”是抽象标签需要转化为可计算的特征如价格区间200元、品类T恤、卫裤、品牌风格国潮、基础款。物品冷启动新上架的商品没有历史购买数据如何让它有机会推荐给潜在感兴趣的用户个性化与多样性的平衡不能只推荐极端便宜但质量差的商品也不能只推荐同一款式的衣服需要兼顾“平价”、“男生”、“穿搭多样性”和一定的质量置信度如好评率。实时性优惠信息、库存状态变化快推荐结果需要能快速响应。一个技术方案如果只追求推荐“准确”可能会陷入“信息茧房”如果只追求“新颖”又可能推荐不相关物品。我们的简易系统将重点演示如何平衡这些要素。2. 核心概念推荐系统如何“认识”用户和商品在写代码之前必须统一“语言”。推荐系统通过“特征”来认识一切。概念通俗解释在“平价穿搭”中的示例用户画像用一系列标签或数字描述一个用户。{“性别”: “男” “价格敏感度”: 高 “常逛品类”: [“上衣” “裤装”] “历史点击均价”: 150}物品特征用一系列标签或数字描述一个商品。{“品类”: “卫衣” “价格”: 129 “品牌”: “国潮A” “风格”: “宽松” “标签”: [“春秋” “百搭” “平价”]}协同过滤“物以类聚人以群分”。找到与你相似的用户把他们喜欢而你没看过的物品推荐给你。用户A和B都买了平价T恤和工装裤系统可能会把B刚买的一条平价牛仔裤推荐给A。基于内容的推荐分析物品本身的内容/特征推荐与你历史上喜欢物品相似的商品。你过去点击了很多“纯色”、“宽松”的卫衣系统会推荐具有同样特征的卫衣给你即使它是新上架的商品。召回从海量商品池中快速筛选出一个小候选集比如千级别。从100万商品中根据“男性”、“价格200”、“上衣”等条件快速找出5000个可能相关的商品。排序对召回的小候选集进行精细打分和排序选出最终展示的几十个商品。对5000个候选商品综合计算“平价得分”、“时尚得分”、“好评率”、“库存深度”等选出TOP 80进行展示。我们的简易系统将融合基于内容和协同过滤的思想侧重于“召回”和“排序”流程的演示。3. 环境准备搭建你的推荐实验场我们将使用Python作为主要语言因为它有丰富的数据科学库。请确保你的环境满足以下要求操作系统: Windows 10/11, macOS, 或 Linux (Ubuntu 20.04)Python版本: 3.8 或 3.9 (推荐3.9)包管理工具: pip安装必要的库 我们主要需要pandas进行数据处理scikit-learn进行特征计算numpy进行数值运算。在命令行中执行# 创建并进入项目目录 mkdir simple_recommender cd simple_recommender # 创建虚拟环境可选但推荐 python -m venv venv # Windows 激活: venv\Scripts\activate # macOS/Linux 激活: source venv/bin/activate # 安装核心依赖 pip install pandas scikit-learn numpy # 可选安装jupyter notebook进行交互式实验 pip install jupyter4. 流程拆解四步构建推荐引擎一个完整的推荐流程可以简化为以下四步我们将逐步实现数据准备与清洗构造模拟的用户数据和商品数据。特征工程将文本标签如“男生穿搭”转化为机器可计算的数值特征。召回阶段根据用户画像快速从全量商品中筛选出候选集。排序阶段对候选商品进行精细打分生成最终推荐列表。5. 完整示例从零实现简易推荐系统5.1 第一步模拟数据准备我们首先创建两个CSV文件来模拟平台数据。商品数据 (items.csv)包含商品ID、名称、品类、价格、标签等。# 文件generate_sample_data.py import pandas as pd import numpy as np # 生成模拟商品数据 np.random.seed(42) # 确保结果可复现 num_items 1000 data { item_id: [fitem_{i:04d} for i in range(num_items)], title: [f男士潮流单品{i} for i in range(num_items)], category: np.random.choice([上衣, 裤装, 外套, 鞋履, 配饰], num_items, p[0.3, 0.25, 0.2, 0.15, 0.1]), price: np.random.randint(50, 500, num_items), # 价格在50-500元之间 tags: [], brand_style: np.random.choice([国潮, 快时尚, 运动, 基础款, 设计师], num_items), rating: np.round(np.random.uniform(3.0, 5.0, num_items), 1) } # 为每个商品生成随机标签 tag_pool [平价, 百搭, 春秋, 冬季, 夏季, 宽松, 修身, 潮流, 复古, 简约, 学生党, 上班族, 高品质, 高性价比] for i in range(num_items): num_tags np.random.randint(1, 4) data[tags].append(, .join(np.random.choice(tag_pool, num_tags, replaceFalse))) df_items pd.DataFrame(data) df_items.to_csv(items.csv, indexFalse, encodingutf-8-sig) print(商品数据已生成到 items.csv 共, num_items, 条记录) print(df_items.head())用户行为数据 (user_actions.csv)模拟用户对商品的点击、购买行为。# 继续在 generate_sample_data.py 中 num_users 200 num_actions 5000 user_ids [fuser_{i:03d} for i in range(num_users)] action_types [click, purchase, add_cart] # 让行为更集中在部分“热门”商品上 popular_items df_items.sample(frac0.2).item_id.tolist() actions_data [] for _ in range(num_actions): user np.random.choice(user_ids) # 80%的行为发生在热门商品上模拟“二八定律” if np.random.random() 0.8: item np.random.choice(popular_items) else: item np.random.choice(df_items.item_id) action np.random.choice(action_types, p[0.7, 0.1, 0.2]) # 点击70%购买10%加购20% actions_data.append([user, item, action]) df_actions pd.DataFrame(actions_data, columns[user_id, item_id, action_type]) df_actions.to_csv(user_actions.csv, indexFalse, encodingutf-8-sig) print(\n用户行为数据已生成到 user_actions.csv 共, len(df_actions), 条记录) print(df_actions.head())运行这个脚本你将在项目目录下得到items.csv和user_actions.csv两个数据文件。5.2 第二步特征工程与用户画像构建特征工程是推荐系统的灵魂。我们要把“平价”、“男生穿搭”这些抽象需求变成特征向量。# 文件feature_engineering.py import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.preprocessing import MinMaxScaler # 1. 加载数据 df_items pd.read_csv(items.csv) df_actions pd.read_csv(user_actions.csv) print(原始商品数据示例) print(df_items[[item_id, category, price, tags]].head()) # 2. 商品特征工程 # 2.1 价格特征归一化并创建“平价”得分 (价格越低得分越高) scaler MinMaxScaler(feature_range(0, 1)) # 注意这里我们反转价格使得低价对应高值 df_items[price_norm] 1 - scaler.fit_transform(df_items[[price]]) df_items[price_score] df_items[price_norm] # 直接作为平价得分 # 2.2 标签文本特征使用TF-IDF将标签文本向量化 vectorizer TfidfVectorizer(max_features20) # 只保留最重要的20个标签维度 tag_matrix vectorizer.fit_transform(df_items[tags]).toarray() tag_feature_names vectorizer.get_feature_names_out() tag_df pd.DataFrame(tag_matrix, columns[ftag_{name} for name in tag_feature_names]) df_items pd.concat([df_items.reset_index(dropTrue), tag_df], axis1) # 2.3 品类和风格进行One-Hot编码 df_items pd.get_dummies(df_items, columns[category, brand_style], prefix[cat, style]) print(\n处理后的商品特征维度部分) print(df_items.shape) # 查看新增的特征列名 new_feature_cols [col for col in df_items.columns if col.startswith((tag_, cat_, style_, price_score))] print(新增特征列示例, new_feature_cols[:10]) # 3. 构建目标用户画像假设我们是“羊毛党”用户 # 在实际系统中用户画像是动态更新的。这里我们静态定义一个目标画像。 target_user_profile { desired_tags: [平价, 高性价比, 百搭], # 用户偏好标签 price_preference: 150, # 期望价格中位数 preferred_categories: [上衣, 裤装] # 偏好品类 } # 将用户偏好也转化为特征向量以便与商品特征计算相似度 # 3.1 构建用户标签向量 user_tag_vec vectorizer.transform([, .join(target_user_profile[desired_tags])]).toarray().flatten() # 3.2 构建用户品类偏好向量 (简化处理这里我们手动构造一个与商品特征对齐的向量) # 假设我们的特征列顺序是固定的我们需要创建一个全零向量然后在对应位置置1或权重。 # 为了简化演示我们将在下一步的召回和排序中直接使用逻辑条件而非严格的向量点积。 print(f\n目标用户画像{target_user_profile}) print(f用户标签向量前5维: {user_tag_vec[:5]}, 对应标签: {vectorizer.get_feature_names_out()[:5]})5.3 第三步召回阶段——快速筛选候选商品召回阶段的目标是“快”和“全”避免遗漏潜在相关商品。我们采用多路召回策略。# 文件recall_stage.py import pandas as pd import numpy as np # 加载特征工程后的商品数据假设已保存 # 在实际项目中特征工程后的数据可能存入数据库或特征仓库。这里我们直接使用df_items。 df_items pd.read_csv(items.csv) # 重新加载原始数据用于演示召回逻辑 df_items_featured pd.read_pickle(df_items_featured.pkl) # 假设上一步已将处理后的DataFrame保存 # 为演示清晰我们这里直接基于原始数据做条件过滤来模拟召回 print(开始多路召回...) # 路1基于价格的召回“平价”核心 price_threshold target_user_profile[price_preference] * 1.5 # 价格上限设为期望价格的1.5倍 recall_by_price df_items[df_items[price] price_threshold].copy() print(f【价格召回】 筛选价格 {price_threshold} 的商品得到 {len(recall_by_price)} 个候选。) # 路2基于品类的召回“男生穿搭”中的品类偏好 preferred_cats target_user_profile[preferred_categories] recall_by_category df_items[df_items[category].isin(preferred_cats)].copy() print(f【品类召回】 筛选品类为 {preferred_cats} 的商品得到 {len(recall_by_category)} 个候选。) # 路3基于标签的召回“高性价比”、“百搭”等 # 简单检查tags字段是否包含目标关键词 desired_tags target_user_profile[desired_tags] def contains_any_tag(tags_str, tag_list): if not isinstance(tags_str, str): return False return any(tag in tags_str for tag in tag_list) recall_by_tag df_items[df_items[tags].apply(lambda x: contains_any_tag(x, desired_tags))].copy() print(f【标签召回】 筛选标签包含 {desired_tags} 的商品得到 {len(recall_by_tag)} 个候选。) # 路4基于热门度的召回保证结果有一定流行度解决冷启动问题 # 计算商品的热门度这里用行为数据中的点击购买次数模拟 item_popularity df_actions[df_actions[action_type].isin([click, purchase])].groupby(item_id).size() item_popularity item_popularity.reset_index(namepopularity_score) # 合并到商品数据 df_items_with_pop df_items.merge(item_popularity, onitem_id, howleft) df_items_with_pop[popularity_score] df_items_with_pop[popularity_score].fillna(0) # 取热门度前20%的商品 pop_threshold df_items_with_pop[popularity_score].quantile(0.8) recall_by_popularity df_items_with_pop[df_items_with_pop[popularity_score] pop_threshold].copy() print(f【热门召回】 筛选热门度前20%的商品得到 {len(recall_by_popularity)} 个候选。) # 合并多路召回结果去重 import functools recall_sets [set(recall_by_price[item_id]), set(recall_by_category[item_id]), set(recall_by_tag[item_id]), set(recall_by_popularity[item_id])] # 取并集任何一路召回的商品都进入候选池 candidate_item_ids functools.reduce(lambda x, y: x.union(y), recall_sets) print(f\n【合并召回】 去重后总候选商品数量: {len(candidate_item_ids)}) # 获取候选商品的完整信息 candidate_df df_items[df_items[item_id].isin(candidate_item_ids)].copy() print(\n候选商品示例) print(candidate_df[[item_id, title, category, price, tags]].head(10))5.4 第四步排序阶段——精细打分与最终推荐排序阶段要对召回的上千个商品进行精细打分。我们设计一个简单的加权打分模型。# 文件ranking_stage.py import pandas as pd import numpy as np # 假设 candidate_df 是上一步得到的候选商品DataFrame # 我们需要为它计算一个综合得分 def calculate_item_score(row, target_profile): 为单个商品计算推荐得分。 得分 价格得分 * 权重 标签匹配得分 * 权重 品类匹配得分 * 权重 好评率得分 * 权重 score 0.0 weights {price: 0.4, tag: 0.3, category: 0.2, rating: 0.1} # 权重可调 # 1. 价格得分越便宜得分越高 (归一化到0-1) max_price_in_candidate candidate_df[price].max() min_price_in_candidate candidate_df[price].min() if max_price_in_candidate min_price_in_candidate: price_score 1 - (row[price] - min_price_in_candidate) / (max_price_in_candidate - min_price_in_candidate) else: price_score 1.0 score price_score * weights[price] # 2. 标签匹配得分检查商品标签与用户偏好标签的重合度 item_tags set(str(row[tags]).split(, )) user_tags set(target_profile[desired_tags]) match_count len(item_tags.intersection(user_tags)) # 简单计算匹配比例 tag_score match_count / max(len(user_tags), 1) score tag_score * weights[tag] # 3. 品类匹配得分用户偏好的品类得高分 category_score 1.0 if row[category] in target_profile[preferred_categories] else 0.0 score category_score * weights[category] # 4. 好评率得分归一化评分 rating_score (row[rating] - 3.0) / 2.0 # 假设评分3-5分归一化到0-1 score rating_score * weights[rating] return score # 为候选商品计算得分 candidate_df[recommend_score] candidate_df.apply(lambda row: calculate_item_score(row, target_user_profile), axis1) # 按得分降序排序 final_recommendations candidate_df.sort_values(byrecommend_score, ascendingFalse).reset_index(dropTrue) print(*50) print(最终推荐结果 TOP 20) print(*50) top_n 20 for idx, row in final_recommendations.head(top_n).iterrows(): print(f{idx1:2d}. [{row[category]}] {row[title]:20} | 价格: {row[price]:4d}元 | 标签: {row[tags]:15} | 评分: {row[rating]} | 综合得分: {row[recommend_score]:.3f}) print(f\n成功为‘羊毛党’用户生成了 {len(final_recommendations)} 个候选商品并输出了得分最高的 {top_n} 个。) print(这些商品符合‘平价’、‘男生穿搭’、‘高性价比’等核心诉求。)6. 运行结果与效果验证将上述四个步骤的代码按顺序整合到一个主脚本中或分别运行。最终输出应类似于商品数据已生成到 items.csv 共 1000 条记录 用户行为数据已生成到 user_actions.csv 共 5000 条记录 开始多路召回... 【价格召回】 筛选价格 225.0 的商品得到 632 个候选。 【品类召回】 筛选品类为 [上衣, 裤装] 的商品得到 552 个候选。 【标签召回】 筛选标签包含 [平价, 高性价比, 百搭] 的商品得到 287 个候选。 【热门召回】 筛选热门度前20%的商品得到 200 个候选。 【合并召回】 去重后总候选商品数量: 789 最终推荐结果 TOP 20 1. [上衣] 男士潮流单品123 | 价格: 89元 | 标签: 平价, 百搭, 春秋 | 评分: 4.2 | 综合得分: 0.912 2. [裤装] 男士潮流单品456 | 价格: 129元 | 标签: 高性价比, 修身 | 评分: 4.5 | 综合得分: 0.901 3. [上衣] 男士潮流单品789 | 价格: 150元 | 标签: 平价, 潮流 | 评分: 4.0 | 综合得分: 0.894 ... 20. [裤装] 男士潮流单品234 | 价格: 199元 | 标签: 百搭, 简约 | 评分: 4.3 | 综合得分: 0.821如何验证效果人工评估浏览TOP 20的结果检查是否符合“平价男生穿搭”的直观感受。价格是否集中在较低区间品类是否符合标签是否匹配指标评估简易版价格覆盖率计算推荐列表中价格低于目标价格如150元的商品比例。品类命中率计算推荐列表中属于偏好品类上衣、裤装的商品比例。标签命中率计算推荐列表中包含至少一个偏好标签的商品比例。AB测试思维可以调整ranking_stage.py中的权重(weights)观察推荐列表的变化理解每个特征对最终结果的影响。7. 常见问题与排查思路在实际实现和运行中你可能会遇到以下问题问题现象可能原因排查方式解决方案召回候选集为空或过少1. 召回条件过于严格。2. 数据本身不满足条件。3. 标签匹配函数出错。1. 打印每一步召回的数量。2. 检查原始数据分布价格、品类。3. 调试标签匹配函数打印中间结果。1. 放宽召回阈值如提高价格上限。2. 增加召回通路如基于品牌、风格的召回。3. 修正标签处理逻辑确保字符串匹配正确。推荐结果重复或单一1. 排序模型过度依赖某一特征如价格。2. 数据多样性不足。3. 未对相似结果进行去重或打散。1. 分析TOP N商品的各个特征分布。2. 检查候选集本身的多样性。1. 调整权重引入多样性特征如品类、品牌分散度。2. 在排序后加入打散Shuffle逻辑避免同一品类/品牌连续出现。新商品冷启动永远无法被推荐1. 热门度召回依赖历史行为新商品无数据。2. 基于内容的召回标签、品类权重太低。1. 分析新商品进入候选集的比例。2. 查看新商品的最终得分分布。1. 为冷启动商品设置默认分数或探索流量。2. 提高基于内容特征如标签匹配的权重或设计专门的冷启动召回通道。运行速度慢尤其是数据量大时1. 使用Pandas循环操作如apply处理大数据。2. 未对数据进行索引或向量化计算。1. 使用%%timeit魔法命令或time模块测试代码块耗时。2. 检查数据量级。1. 尽量使用Pandas/Numpy的向量化操作替代循环。2. 对于召回考虑使用数据库索引或倒排索引技术。3. 对于排序考虑将特征计算离线化在线服务只做简单的加权求和。用户画像无法更新或过于静态1. 代码中用户画像是硬编码的。2. 未设计用户行为反馈更新画像的机制。-1. 设计用户画像的存储和更新模块如Redis。2. 根据用户的实时行为点击、购买动态调整画像中的偏好权重。8. 最佳实践与工程建议将上述Demo升级到一个可用的生产级系统还需要考虑很多工程细节数据管道与实时性离线与在线结合商品特征、用户长期画像可以离线计算每天更新。用户实时行为最近1小时的点击需要在线快速更新到短期画像中。消息队列用户行为日志通过Kafka等消息队列异步处理避免阻塞主流程。特征平台化不要在每个推荐任务中重复计算特征。建立统一的特征仓库离线计算好商品特征、用户统计特征在线服务通过ID直接查询。多路召回的工程实现每一路召回可以是一个独立的召回器Retriever例如PriceRetriever,TagRetriever,CFRetriever。使用倒排索引如Elasticsearch加速基于标签、品类的召回。并行执行多路召回最后合并去重提升响应速度。排序模型进阶简单的加权求和只是起点。工业界常用机器学习模型如LR, GBDT, DNN进行排序。需要收集“曝光-点击/转化”数据作为训练样本特征包括用户特征、商品特征、上下文特征。目标可以是CTR点击率、CVR转化率或综合收益。评估与迭代离线评估使用AUC、GAUC、NDCG等指标在历史数据上评估模型性能。在线A/B测试是评估推荐效果的金标准。将用户流量分成多组对比不同推荐策略的核心业务指标如人均点击、人均GMV。人工评估定期抽样评估推荐结果的相关性、新颖性和多样性。系统架构概览用户请求 -- API网关 -- 推荐服务 | |-- 召回阶段 (并行) | | |- 召回器A (Redis/ES) | | |- 召回器B (Faiss) | | |- 召回器C (...) | |-- 合并与粗排 -- 精排模型 (加载离线模型) -- 重排与打散 -- 返回结果整个流程应在百毫秒内完成。9. 总结与后续方向通过这个从“求大数据推荐”引发的项目我们完成了一个简易推荐系统的核心流程实践。你不仅理解了“用户画像”、“召回”、“排序”这些概念更重要的是你亲手用代码实现了它们看到了数据如何一步步转化为推荐列表。这个简易系统与真实工业系统的差距正是你后续可以深入的方向深入排序模型学习使用scikit-learn实现逻辑回归、梯度提升树甚至尝试使用TensorFlow/PyTorch搭建深度学习排序模型如DeepFM, DIN。探索向量召回当商品和用户特征非常复杂时如图片、长文本可以将它们表示为嵌入向量Embedding使用向量数据库如Milvus, Faiss进行最近邻搜索这是解决语义匹配的强大工具。工程化与部署学习如何使用Docker容器化你的推荐服务如何使用Flask/FastAPI构建API如何利用Redis缓存热点数据和用户画像。关注业界动态阅读阿里巴巴、美团、字节跳动等公司公开的推荐系统技术博客了解如Multi-task Learning、强化学习在推荐中的应用等前沿实践。推荐系统是一个融合了机器学习、大数据工程、软件架构和产品思维的复杂领域。从这个小小的“平价好物推荐器”出发保持好奇心不断拆解和实现你就能逐渐掌握这门让“大数据”真正读懂用户需求的核心技术。