ChatGPT如何颠覆传统推荐系统?5种实战方法解析(附代码)
如果你最近在技术社区里逛,会发现一个有趣的现象:越来越多的团队开始讨论,如何让ChatGPT这类大语言模型(LLM)来“干推荐系统的活儿”。这听起来有点跨界,毕竟推荐系统长久以来是协同过滤、矩阵分解、深度神经网络的天下,而LLM是处理语言的专家。但当你看到一些实验报告,比如用ChatGPT做零样本推荐,效果能逼近甚至在某些场景下超越传统模型时,你就知道,事情正在起变化。
这种变化的核心,在于LLM带来的范式转移。传统推荐系统本质上是“模式匹配”引擎,在海量用户-物品交互数据中寻找规律。而像ChatGPT这样的生成式大语言模型,则是一个压缩了海量世界知识的“推理引擎”。它不仅能理解用户和物品的文本描述,还能进行常识推理、逻辑演绎,甚至模拟对话。这意味着,推荐不再仅仅是“猜你喜欢”,而可以升级为“懂你所需”的个性化服务。
这篇文章,就是为那些希望将这股浪潮落地的开发者和产品经理准备的。我们不谈空泛的理论综述,而是聚焦于实战。我会结合具体的代码示例,拆解五种将ChatGPT能力融入推荐系统的核心方法。这些方法覆盖了从快速原型验证到深度集成的不同阶段,你可以根据自身的数据、算力和业务需求进行选择和组合。我们的目标是:让你看完就能动手,在实践中感受LLM如何为你的推荐系统注入新的活力。
1. 零样本推荐:无需训练,直接“提问”获得推荐
这是最直接、门槛最低的尝试方式。其核心思想是:将推荐任务重新表述为一个自然语言问题,然后直接向ChatGPT(或同级别的LLM API,如GPT-4、Claude等)提问,利用其强大的零样本(Zero-Shot)或少量示例的少样本(Few-Shot)能力来获得推荐结果。
1.1 核心原理与Prompt设计
为什么这能行得通?因为ChatGPT在预训练阶段“阅读”了几乎整个互联网,其中包含了海量关于商品、电影、书籍、音乐的描述、评价和讨论。它已经内化了这些物品的属性、风格、受众以及它们之间的关联。当你用清晰的指令描述一个用户和他的历史行为时,模型能够基于其内部知识进行推理和生成。
成功的关键在于Prompt(提示)工程。一个糟糕的Prompt可能得到无关的答案,而一个精心设计的Prompt则能引导模型输出结构化的推荐。一个有效的推荐Prompt通常包含以下几个部分:
- 角色设定:明确告诉模型它现在扮演的角色。
- 任务描述:清晰定义需要它完成的推荐任务。
- 用户信息:提供用户画像或历史行为序列。
- 候选/约束条件:可选。提供候选物品列表或对推荐结果的限制(如类别、数量)。
- 输出格式:严格要求模型以特定格式(如JSON、列表)输出,便于程序解析。
下面是一个为电影推荐设计的Prompt示例:
你是一个专业的电影推荐系统。你的任务是根据用户的观影历史,推荐他们可能喜欢的下一部电影。
用户“Alex”最近观看并喜欢的电影列表如下:
- 《盗梦空间》(科幻,悬疑)
- 《星际穿越》(科幻,剧情)
- 《记忆碎片》(悬疑,剧情)
请基于以上信息,为Alex推荐5部他可能喜欢的电影。
请严格按照以下JSON格式输出,不要输出任何其他解释:
{
"recommendations": [
{"title": "电影名1", "reason": "简要推荐理由"},
{"title": "电影名2", "reason": "简要推荐理由"},
...
]
}
1.2 实战代码:调用OpenAI API实现
假设你已经有了OpenAI的API密钥,我们可以用Python快速实现一个零样本推荐函数。
import openai
import json
# 设置你的API密钥
openai.api_key = 'your-api-key-here'
def zero_shot_movie_recommendation(user_history):
"""
基于用户观影历史进行零样本电影推荐。
Args:
user_history (list): 用户喜欢的电影列表,每个元素是字符串。
Returns:
dict: 解析后的推荐结果JSON。
"""
# 构建Prompt
history_str = "\n".join([f"- 《{movie}》" for movie in user_history])
prompt = f"""
你是一个专业的电影推荐系统。你的任务是根据用户的观影历史,推荐他们可能喜欢的下一部电影。
用户“Alex”最近观看并喜欢的电影列表如下:
{history_str}
请基于以上信息,为Alex推荐5部他可能喜欢的电影。
请专注于推荐在风格、导演、演员或主题上与上述电影相似的作品。
请严格按照以下JSON格式输出,不要输出任何其他解释:
{{
"recommendations": [
{{"title": "电影名1", "reason": "简要推荐理由"}},
{{"title": "电影名2", "reason": "简要推荐理由"}},
{{"title": "电影名3", "reason": "简要推荐理由"}},
{{"title": "电影名4", "reason": "简要推荐理由"}},
{{"title": "电影名5", "reason": "简要推荐理由"}}
]
}}
"""
try:
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo", # 或 "gpt-4"
messages=[
{"role": "system", "content": "你是一个有帮助的电影推荐助手。"},
{"role": "user", "content": prompt}
],
temperature=0.7, # 控制创造性,推荐任务可以稍低一些,如0.3-0.7
max_tokens=500
)
# 提取回复内容
content = response.choices[0].message.content
# 尝试解析JSON
# 模型有时会在JSON外加引号或代码块标记,这里做简单清理
content = content.strip()
if content.startswith("```json"):
content = content[7:]
if content.startswith("```"):
content = content[3:]
if content.endswith("```"):
content = content[:-3]
content = content.strip()
result = json.loads(content)
return result
except json.JSONDecodeError as e:
print(f"JSON解析失败,原始返回内容:\n{content}")
return {"error": "Failed to parse model response", "raw_output": content}
except Exception as e:
print(f"API调用失败:{e}")
return {"error": str(e)}
# 示例调用
if __name__ == "__main__":
history = ["盗梦空间", "星际穿越", "记忆碎片"]
recommendations = zero_shot_movie_recommendation(history)
print(json.dumps(recommendations, indent=2, ensure_ascii=False))
提示:在实际生产环境中,你需要考虑API调用的成本、延迟和稳定性。对于大规模候选集,直接为每个用户调用API是不现实的。通常的做法是先用传统模型召回Top-N候选,再用LLM对这小部分候选进行精排或解释生成。
1.3 适用场景与局限性
- 适用场景:
- 冷启动:为新用户或新物品提供初始推荐。
- 探索性功能:快速验证某个推荐想法或进行A/B测试。
- 小规模或长尾推荐:对于物品库不大,或传统模型难以覆盖的长尾物品,LLM的知识广度能发挥优势。
- 局限性:
- 成本与延迟:每次推荐都需要调用外部API,费用和响应时间较高。
- 不可控性:模型可能“胡言乱语”,生成不存在的物品或给出不合逻辑的理由。
- 缺乏个性化深度:仅依赖文本描述和少量历史,难以捕捉复杂的、隐式的用户偏好。
- 位置与流行度偏差:LLM在生成列表时,可能对Prompt中靠前的位置或训练数据中更流行的物品有偏好。
尽管有这些限制,零样本推荐作为一个快速验证和补充工具,其价值是毋庸置疑的。它让我们以极低的工程成本,触摸到了“智能推荐”的另一种可能。
2. 解释生成:让推荐结果“会说话”
“为什么给我推荐这个?”这是用户心中常有的疑问。传统的推荐系统往往是个黑盒,而LLM强大的文本生成能力,恰好能为这个黑盒打开一扇窗,提供自然、流畅、个性化的推荐解释。
2.1 从模板到生成:解释的进化
传统的解释生成依赖于预定义的模板,例如“因为您购买过A,所以为您推荐相关的B”。这种方式虽然可控,但生硬且千篇一律。LLM带来的变革在于,它能够动态生成解释,结合具体的用户上下文和物品特征,创造出更丰富、更有说服力的理由。
生成式解释可以涵盖多个维度:
- 特征关联:“这款咖啡机与您之前购买的磨豆机品牌相同,搭配使用效果更好。”
- 风格延续:“您收藏的这几幅画作都属于现代极简风格,这位新锐艺术家的作品延续了这种视觉语言。”
- 场景补全:“您正在策划一次海岛旅行,这本《东南亚潜水指南》可以帮助您发现更多潜点。”
- 趋势结合:“结合最近的流行趋势和您的身材特点,这款剪裁的西装可能会很适合您。”
2.2 实战代码:构建解释生成服务
我们可以设计一个两阶段管道:第一阶段用传统推荐模型(如协同过滤、深度学习排序模型)生成候选物品列表;第二阶段调用LLM为每个推荐结果生成解释。
import openai
from typing import List, Dict
# 假设我们有一个传统的推荐模型,这里用模拟函数代替
def get_traditional_recommendations(user_id: str, top_k: int = 3) -> List[Dict]:
"""
模拟传统推荐模型返回的推荐结果。
实际中这里会是你的TensorFlow/PyTorch模型或协同过滤服务。
"""
# 示例数据:物品ID, 物品名称, 物品特征/标签
mock_items = [
{"item_id": "1001", "name": "胶囊咖啡机", "tags": ["便捷", "意式", "家用", "Nespresso兼容"]},
{"item_id": "1002", "name": "手冲咖啡壶", "tags": ["精品咖啡", "手工", "玻璃材质", "控制感"]},
{"item_id": "1003", "name": "咖啡豆研磨机", "tags": ["电动", "多档研磨", "不锈钢", "静音"]},
]
# 模拟返回Top-K结果
return mock_items[:top_k]
def generate_personalized_explanation(user_profile: Dict, item: Dict, user_history: List[Dict]) -> str:
"""
为单个推荐物品生成个性化解释。
Args:
user_profile: 用户画像,如 {"name": "Alex", "interest": ["科技", "烹饪"]}
item: 推荐物品信息
user_history: 用户近期交互历史(如浏览、购买)
"""
# 构建用户历史文本摘要
history_summary = "无近期历史记录。"
if user_history:
history_summary = ", ".join([f"对《{h['name']}》表现出兴趣" for h in user_history[-3:]]) # 取最近3条
prompt = f"""
你是一个贴心的购物助手,需要为用户推荐商品并给出令人信服的理由。
用户信息:
- 用户名:{user_profile.get('name', '用户')}
- 兴趣标签:{', '.join(user_profile.get('interest', []))}
- 近期行为:{history_summary}
需要解释的推荐商品:
- 商品名称:{item['name']}
- 商品特点/标签:{', '.join(item['tags'])}
请生成一段简短、自然、亲切的推荐解释,直接面向用户说,长度在1-2句话内。
解释应关联用户的兴趣或历史行为,并突出商品的特点。
不要使用“根据您的...”这样的开头,直接说理由。
例如:“这款胶囊咖啡机操作非常简便,早上快速做一杯意式浓缩正好适合您快节奏的生活。而且它兼容您可能已有的Nespresso胶囊,不用更换习惯。”
现在请为上面的商品生成解释:
"""
try:
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "你是一个善于沟通、语气亲切的购物助手。"},
{"role": "user", "content": prompt}
],
temperature=0.8, # 解释可以稍具创造性
max_tokens=100
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"生成解释时出错:{e}"
def enhanced_recommendation_pipeline(user_id: str):
"""
增强版推荐流水线:传统模型推荐 + LLM生成解释。
"""
# 1. 获取传统推荐结果
recommendations = get_traditional_recommendations(user_id, top_k=3)
# 2. 模拟获取用户上下文信息(实际中从数据库或用户画像系统获取)
user_profile = {"name": "Alex", "interest": ["科技", "烹饪", "效率工具"]}
user_history = [
{"name": "智能音箱", "action": "浏览"},
{"name": "精酿啤酒入门套装", "action": "购买"},
]
# 3. 为每个推荐物品生成解释
enhanced_results = []
for item in recommendations:
explanation = generate_personalized_explanation(user_profile, item, user_history)
enhanced_item = item.copy()
enhanced_item['personalized_explanation'] = explanation
enhanced_results.append(enhanced_item)
return enhanced_results
# 运行示例
if __name__ == "__main__":
results = enhanced_recommendation_pipeline("user_123")
for res in results:
print(f"商品:{res['name']}")
print(f"解释:{res['personalized_explanation']}")
print("-" * 40)
这个例子展示了如何将LLM作为“解释器”无缝嵌入现有推荐流程。你甚至可以更进一步,让LLM根据解释的 persuasiveness(说服力)对推荐结果进行微调排序。
2.3 价值与注意事项
生成解释的价值远超“让界面更友好”。它能:
- 增加透明度与信任:用户理解了推荐逻辑,更可能接受推荐。
- 提供发现价值:解释本身可能包含用户未知的物品信息或使用场景。
- 收集反馈:如果用户对解释表示否定(如“我不喜欢这个风格”),这本身就是宝贵的负反馈信号。
注意:生成的解释必须真实、可验证。避免让LLM编造不存在的物品功能或虚假的用户关联,这会严重损害信任。一种最佳实践是,解释所依据的特征(如物品标签、用户历史)必须来自可信的数据源,LLM只负责组织语言和建立合理的关联。
3. 对话式推荐:从“一次性猜你喜欢”到“多轮交互探索”
这是LLM颠覆传统推荐最令人兴奋的领域之一。传统的推荐是静态的、单向的:系统输出一个列表,用户被动接受或拒绝。对话式推荐(Conversational Recommendation)则将这个过程转变为动态的、双向的对话。用户可以通过自然语言表达模糊的需求、给出实时反馈(“这个太贵了”、“我想要更休闲一点的”),系统则像一位专业的导购,通过多轮问答逐步澄清需求,最终锁定目标。
3.1 系统架构设计
构建一个对话式推荐系统,远不止是接一个ChatGPT API那么简单。它通常需要一个精心设计的智能体(Agent)架构,LLM作为其“大脑”。一个典型的架构包含以下模块:
| 模块 | 功能 | 实现方式 |
|---|---|---|
| 对话理解与管理 | 解析用户意图,维护对话状态(如当前讨论的物品、已明确的偏好、待澄清的点)。 | LLM + 预设的意图分类/槽位填充(Slot Filling)。 |
| 推荐核心 | 根据当前对话状态,从物品库中检索或排序候选物品。 | 传统推荐模型(协同过滤、向量检索)或LLM自身生成。 |
| 知识/工具调用 | 获取LLM外部知识,如实时库存、价格、详细规格、用户历史数据。 | 函数调用(Function Calling)、API查询、向量数据库检索。 |
| 响应生成 | 将推荐结果、物品信息、解释整合成自然、连贯的回复。 | LLM, 结合对话历史和工具调用结果。 |
3.2 实战示例:一个简单的电影对话推荐机器人
下面我们用Python模拟一个简化版的对话推荐流程,重点展示状态管理和工具调用的概念。
import openai
import json
# 模拟一个电影数据库“工具函数”
def search_movies_by_preferences(genre=None, director=None, actor=None, decade=None, top_n=5):
"""
根据偏好搜索电影。实际中这里会连接真实的数据库或搜索引擎。
"""
# 这是一个模拟的微型数据库
movie_db = [
{"title": "盗梦空间", "genre": ["科幻", "悬疑", "动作"], "director": "克里斯托弗·诺兰", "year": 2010},
{"title": "星际穿越", "genre": ["科幻", "剧情", "冒险"], "director": "克里斯托弗·诺兰", "year": 2014},
{"title": "蝙蝠侠:黑暗骑士", "genre": ["动作", "犯罪", "剧情"], "director": "克里斯托弗·诺兰", "year": 2008},
{"title": "教父", "genre": ["犯罪", "剧情"], "director": "弗朗西斯·福特·科波拉", "year": 1972},
{"title": "低俗小说", "genre": ["犯罪", "剧情"], "director": "昆汀·塔伦蒂诺", "year": 1994},
{"title": "机器人总动员", "genre": ["动画", "科幻", "冒险"], "director": "安德鲁·斯坦顿", "year": 2008},
{"title": "千与千寻", "genre": ["动画", "奇幻", "冒险"], "director": "宫崎骏", "year": 2001},
]
results = []
for movie in movie_db:
match = True
if genre and not any(g in movie['genre'] for g in (genre if isinstance(genre, list) else [genre])):
match = False
if director and director.lower() not in movie['director'].lower():
match = False
if decade:
start_year = int(decade[:-1]) * 10
if not (start_year <= movie['year'] < start_year + 10):
match = False
if match:
results.append(movie)
return results[:top_n]
class ConversationalRecommender:
def __init__(self):
self.conversation_history = []
self.user_preferences = {} # 用于存储从对话中提取的偏好
def _call_llm(self, messages, tools=None):
"""调用LLM,支持函数调用。"""
params = {
"model": "gpt-3.5-turbo",
"messages": messages,
"temperature": 0.7,
}
if tools:
params["tools"] = tools
params["tool_choice"] = "auto"
response = openai.ChatCompletion.create(**params)
return response
def _extract_preferences_from_message(self, user_message):
"""
一个简单的规则/关键词匹配来提取偏好。
在实际复杂系统中,可以用更精细的NLU模型或让LLM通过函数调用来提取。
"""
preferences = {}
message_lower = user_message.lower()
# 非常简单的关键词匹配(仅为示例)
genre_keywords = {
'科幻': ['科幻', '太空', '未来'],
'喜剧': ['喜剧', '搞笑', '幽默'],
'动作': ['动作', '打斗', '冒险'],
'剧情': ['剧情', '文艺', '感人'],
'动画': ['动画', '卡通', '动漫']
}
for genre, keywords in genre_keywords.items():
if any(kw in message_lower for kw in keywords):
preferences['genre'] = genre
break
if '诺兰' in user_message:
preferences['director'] = '克里斯托弗·诺兰'
if '90年代' in user_message or '九十年代' in user_message:
preferences['decade'] = '1990s'
return preferences
def recommend_and_chat(self, user_input):
"""
处理用户输入,返回系统回复。
"""
# 1. 更新对话历史
self.conversation_history.append({"role": "user", "content": user_input})
# 2. 提取用户偏好(简化版)
new_prefs = self._extract_preferences_from_message(user_input)
self.user_preferences.update(new_prefs)
# 3. 准备系统消息和工具
system_message = """
你是一个友好的电影推荐助手。你的目标是帮助用户找到他们想看的电影。
你可以通过询问用户的偏好(如类型、导演、年代)来澄清需求。
当你掌握了足够的信息(比如类型或导演),你应该调用`search_movies_by_preferences`工具来查找电影,然后根据结果生成回复。
回复要自然、热情,并解释为什么推荐这些电影。
如果信息不足,就继续提问。
"""
tools = [
{
"type": "function",
"function": {
"name": "search_movies_by_preferences",
"description": "根据用户指定的偏好搜索电影",
"parameters": {
"type": "object",
"properties": {
"genre": {"type": "string", "description": "电影类型,如'科幻'、'喜剧'"},
"director": {"type": "string", "description": "导演姓名"},
"decade": {"type": "string", "description": "年代,如'1990s'"},
"top_n": {"type": "integer", "description": "返回结果数量,默认5"}
},
"required": []
}
}
}
]
messages = [{"role": "system", "content": system_message}] + self.conversation_history
# 4. 调用LLM
response = self._call_llm(messages, tools)
message = response.choices[0].message
# 5. 处理工具调用
if message.get("tool_calls"):
tool_call = message.tool_calls[0]
if tool_call.function.name == "search_movies_by_preferences":
# 解析参数
args = json.loads(tool_call.function.arguments)
# 调用我们的模拟工具函数
movie_results = search_movies_by_preferences(**args)
# 将工具调用结果追加到对话历史
self.conversation_history.append(message) # 添加包含工具调用的assistant消息
self.conversation.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(movie_results, ensure_ascii=False)
})
# 再次调用LLM,让它根据搜索结果生成回复
final_response = self._call_llm([{"role": "system", "content": system_message}] + self.conversation_history)
assistant_reply = final_response.choices[0].message.content
else:
assistant_reply = "我尝试搜索了,但似乎遇到了问题。"
else:
assistant_reply = message.content
# 6. 将助手回复加入历史,并返回
self.conversation_history.append({"role": "assistant", "content": assistant_reply})
return assistant_reply
# 模拟对话
if __name__ == "__main__":
bot = ConversationalRecommender()
print("助手:你好!我是电影推荐助手。你喜欢什么类型的电影呢?")
# 模拟几轮对话
user_inputs = [
"我喜欢科幻片,最好是诺兰导演的。",
"有没有90年代的经典科幻电影?",
]
for user_input in user_inputs:
print(f"\n用户:{user_input}")
reply = bot.recommend_and_chat(user_input)
print(f"助手:{reply}")
这个示例虽然简化,但勾勒出了对话式推荐的核心循环:理解用户输入 -> 更新状态/偏好 -> 决定行动(提问或调用工具搜索)-> 生成回复。在实际应用中,你需要构建更强大的工具集(如实时票房查询、相似演员查找、用户评分数据库查询)和更鲁棒的对话状态管理机制。
3.3 挑战与未来
对话式推荐的挑战显而易见:
- 状态管理复杂:多轮对话中,如何准确理解指代(“这个”、“那个”)、处理偏好变更是一大难题。
- 评估困难:如何评估对话推荐系统的效果?不仅看最终推荐是否准确,还要看对话效率、用户体验。
- 冷启动与探索:在对话初期,如何快速有效地探索用户兴趣?
尽管如此,其前景广阔。它使得推荐系统从“静态货架”变成了“智能导购”,极大地提升了交互性和用户满意度。随着LLM推理能力和工具调用能力的增强,以及LangChain、AutoGPT等框架的成熟,构建高质量的对话推荐系统正变得越来越可行。
4. 特征增强与表示学习:用LLM为传统模型“赋能”
前三种方法主要让LLM“走上前台”直接生成推荐或内容。而第四种方法则让LLM“退居幕后”,发挥其强大的语义理解能力,为传统的推荐模型生成更丰富的特征(Feature),或学习更好的用户/物品表示(Representation)。这是一种更易于与现有工业级推荐系统融合的思路。
4.1 方法论:LLM作为特征工程引擎
传统推荐模型(如矩阵分解、深度神经网络)的输入特征往往是稀疏的ID、类别标签或简单的统计特征。LLM可以对这些原始特征进行深度加工:
- 物品侧特征增强:
- 文本摘要与标签生成:对于商品标题、描述、评论,用LLM生成简洁的摘要、提取关键属性标签、情感倾向。
- 跨模态理解:结合图片生成文本描述(通过VLM多模态模型),或将文本描述转化为结构化属性。
- 知识图谱构建:从文本中抽取实体(品牌、成分、风格)和关系,丰富物品的知识表示。
- 用户侧特征增强:
- 兴趣摘要:将用户的历史行为序列(浏览、购买、搜索词)输入LLM,让其生成一段描述用户兴趣偏好的自然语言摘要。
- 会话意图理解:分析用户当前会话内的行为,用LLM判断其当前意图(如“比价”、“寻找礼物”、“解决特定问题”)。
这些由LLM生成的高质量、稠密的语义特征,可以作为额外的输入信号,注入到传统的双塔模型、 Wide & Deep 模型或序列模型中,显著提升模型对冷门物品和新用户的理解能力。
4.2 实战示例:用LLM生成物品嵌入与用户兴趣摘要
我们来看两个具体的子任务:为物品生成文本嵌入,以及为用户历史生成兴趣摘要。
任务一:生成物品的文本嵌入(Text Embedding)
OpenAI提供了专门的嵌入模型(如text-embedding-3-small),可以将一段文本转化为一个高维向量。这个向量可以作为物品的语义表示。
import openai
import numpy as np
def get_item_embedding(item_text):
"""
使用OpenAI的嵌入模型为物品文本生成向量表示。
"""
# item_text 可以是物品标题、描述、标签的组合,例如:
# “Nespresso胶囊咖啡机,Inissia型号,红色,便捷式,适合家庭办公室使用”
response = openai.Embedding.create(
model="text-embedding-3-small", # 性价比高,维度可调
input=item_text
)
embedding = response['data'][0]['embedding']
return np.array(embedding)
# 示例:计算两个物品的相似度
item1_text = "便携蓝牙音箱,JBL品牌,防水,续航12小时,低音强劲"
item2_text = "家用智能音箱,带屏幕,支持视频通话,可控制智能家居"
item3_text = "运动无线耳机,入耳式,降噪,续航30小时,适合跑步"
emb1 = get_item_embedding(item1_text)
emb2 = get_item_embedding(item2_text)
emb3 = get_item_embedding(item3_text)
# 计算余弦相似度
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(f"音箱1 vs 音箱2 相似度: {cosine_similarity(emb1, emb2):.3f}")
print(f"音箱1 vs 耳机 相似度: {cosine_similarity(emb1, emb3):.3f}")
# 输出可能显示 item1 和 item3 更相似(都是便携音频设备),尽管文字描述不同。
生成的嵌入向量可以:
- 直接用于向量检索,做基于内容的相似推荐。
- 作为特征输入到深度学习排序模型,与ID类特征concat。
- 用于物品聚类,发现潜在品类。
任务二:生成用户兴趣摘要 我们可以定期(如每天)用LLM处理用户的近期行为日志,生成一段动态的兴趣摘要。
def summarize_user_interest(user_id, recent_actions):
"""
根据用户近期行为生成兴趣摘要。
recent_actions: list of dict, e.g., [{"item": "书名", "action": "购买"}, ...]
"""
actions_str = "\n".join([f"- {act['action']}了《{act['item']}》" for act in recent_actions[-10:]]) # 取最近10条
prompt = f"""
请根据以下用户近期的互动行为,总结该用户的兴趣偏好或潜在需求。
请用一段简短、连贯的中文描述,不要罗列物品。
聚焦于兴趣领域、品类偏好、消费档次或生活风格等抽象层面。
用户行为记录:
{actions_str}
兴趣摘要:
"""
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.5,
max_tokens=150
)
summary = response.choices[0].message.content.strip()
return summary
# 示例调用
actions = [
{"item": "《Python编程:从入门到实践》", "action": "购买"},
{"item": "《机器学习实战》", "action": "浏览"},
{"item": "罗技MX Master 3鼠标", "action": "购买"},
{"item": "《深入理解计算机系统》", "action": "加入购物车"},
{"item": "站立办公桌", "action": "收藏"},
]
summary = summarize_user_interest("user_456", actions)
print(f"用户兴趣摘要:{summary}")
# 可能输出:“该用户近期对编程和计算机科学类书籍表现出浓厚兴趣,同时关注提升工作效率的硬件设备(如高端鼠标和站立办公桌),可能是一名程序员或技术爱好者,注重工作环境的舒适性与效率。”
生成的兴趣摘要可以作为用户画像的文本特征,与其他数值特征一起输入推荐模型。它比单纯的物品ID列表包含了更丰富的语义信息。
4.3 优势与集成策略
这种“赋能”模式的优势在于:
- 可离线处理:特征生成和摘要生成可以离线批量进行,避免线上推理的高延迟和高成本。
- 易于集成:生成的特征可以直接插入现有推荐系统的特征管道,无需大规模重构架构。
- 效果可衡量:可以通过A/B测试,直接观察加入LLM特征后,推荐指标(CTR、转化率)的提升。
一个常见的集成策略是建立异步特征管道:
- 物品上新或描述更新时,触发LLM进行特征提取(嵌入、标签)。
- 用户行为日志累积到一定量时,触发LLM生成/更新兴趣摘要。
- 将这些处理好的特征存入特征仓库(Feature Store)。
- 推荐模型训练和线上服务时,从特征仓库读取这些LLM增强的特征。
5. 指令微调与领域适配:打造专属的推荐大模型
前四种方法主要基于通用的、未经针对性训练的LLM(如ChatGPT)。虽然强大,但它们在特定推荐任务上的表现可能不够精准,且存在生成格式不稳定、可能产生幻觉等问题。第五种方法则更进一步:对开源的大语言模型进行指令微调(Instruction Tuning)或领域适配(Domain Adaptation),打造一个专属于你推荐任务的、更可控、更高效的“领域专家”模型。
5.1 为什么要微调?
- 控制输出格式:确保模型始终以你需要的JSON、列表等格式输出,便于下游系统解析。
- 融入领域知识:让模型更熟悉你业务内的物品命名、属性、用户行为术语。
- 遵循业务规则:训练模型避免推荐禁售品、遵守价格策略等。
- 降低成本与延迟:微调后的较小模型(如7B、13B参数)在专用任务上可能达到接近超大通用模型的效果,且可以部署在自有GPU上,摆脱API依赖。
- 保护数据隐私:所有训练和推理都在内部完成,敏感用户数据无需出境。
5.2 微调数据准备与实战流程
微调的核心是准备高质量的指令-输出对数据集。对于推荐任务,数据可能长这样:
[
{
"instruction": "根据用户的历史购买记录,推荐3个可能喜欢的相关商品。用户历史:购买过‘无线降噪耳机’、‘手机支架’。请以JSON列表格式输出,包含‘product_name’和‘reason’字段。",
"input": "",
"output": "[{\"product_name\": \"蓝牙音频接收器\", \"reason\": \"可将有线音箱变为蓝牙音箱,与您的无线耳机配套使用。\"}, {\"product_name\": \"车载手机充电支架\", \"reason\": \"与您已有的手机支架互补,满足驾车时的充电和导航需求。\"}, {\"product_name\": \"运动臂包\", \"reason\": \"适合在跑步或健身时携带手机和无线耳机,解放双手。\"}]"
},
{
"instruction": "为以下电影生成一段吸引人的推荐理由,面向喜欢悬疑和心理剧的观众。",
"input": "电影:《禁闭岛》",
"output": "《禁闭岛》不仅仅是一部悬疑片,它是一次深入扭曲心智的惊悚之旅。莱昂纳多·迪卡普里奥饰演的联邦警官在调查孤岛精神病院失踪案时,逐步发现自己记忆的裂痕与现实的诡异交织。导演马丁·斯科塞斯用冷峻的色调和层层递进的叙事,构建了一个令人窒息的谜团。对于喜欢心理博弈和结局反转的观众来说,这部电影是一场不容错过的烧脑盛宴。"
}
]
你可以从现有日志(搜索词->点击物品)、人工标注、或用大模型(如GPT-4)辅助生成来构建这个数据集。
接下来,我们可以使用诸如 QLoRA 等高效微调技术,在消费级GPU上对像 Llama 3、Qwen 这样的开源大模型进行微调。以下是使用 peft 和 transformers 库进行QLoRA微调的简化代码框架:
# 注意:这是一个高度简化的示例框架,实际运行需要安装相应库并准备数据。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType
from trl import SFTTrainer
from datasets import Dataset
# 1. 加载基础模型和分词器
model_name = "meta-llama/Llama-3-8B-Instruct" # 示例,需确保有权访问
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token # 设置填充token
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.bfloat16,
device_map="auto"
)
# 2. 配置LoRA
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8, # LoRA秩
lora_alpha=32,
lora_dropout=0.1,
target_modules=["q_proj", "v_proj"] # 针对Llama架构
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # 查看可训练参数,通常只有原模型的0.1%左右
# 3. 准备训练数据(假设已加载为`train_data`列表,格式如上文JSON)
def format_instruction(example):
# 将指令、输入、输出格式化为模型接受的文本
text = f"### Instruction:\n{example['instruction']}\n\n"
if example['input']:
text += f"### Input:\n{example['input']}\n\n"
text += f"### Response:\n{example['output']}"
return {"text": text}
dataset = Dataset.from_list(train_data)
dataset = dataset.map(format_instruction)
# 4. 配置训练参数
training_args = TrainingArguments(
output_dir="./llama-3-8b-rec-finetuned",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
num_train_epochs=3,
logging_steps=10,
save_steps=100,
learning_rate=2e-4,
fp16=True, # 根据硬件选择
push_to_hub=False, # 可设置为True上传到Hugging Face Hub
)
# 5. 创建Trainer并开始训练
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
tokenizer=tokenizer,
max_seq_length=512, # 根据你的数据调整
)
trainer.train()
# 6. 保存微调后的模型
model.save_pretrained("./my-finetuned-rec-llama")
tokenizer.save_pretrained("./my-finetuned-rec-llama")
训练完成后,你就可以加载这个微调后的模型,像使用任何本地模型一样进行推理,让它执行你定义好的推荐、解释生成等任务。
5.3 技术选型与考量
选择这条路径前,需要权衡:
- 成本:需要准备高质量的训练数据、拥有或租用GPU资源。
- 技术栈:需要团队具备深度学习、大模型微调的相关经验。
- 收益:对于高度垂直、有特殊规则的业务(如金融产品推荐、医疗信息推荐),微调带来的精准度和可控性提升可能是决定性的。
从“直接用API”到“微调专属模型”,这是一个投入逐渐加深、但控制力和个性化程度也显著增强的频谱。对于大多数团队,可以从方法1(零样本)和方法4(特征增强)开始尝试,快速验证价值。随着业务场景的明确和数据资产的积累,再逐步向方法2、3、5演进。
在我自己的项目中,最初也是从用ChatGPT API做解释生成开始的。很快我们发现,通用模型对产品细节的描述有时不够准确。于是我们转向了方法4,用LLM批量生成标准化的产品特征标签和摘要,注入到排序模型里,带来了稳定的CTR提升。现在,我们正在小范围试验方法5,针对客服场景的推荐话术进行微调,目标是让AI推荐的语气和逻辑更贴合我们品牌的调性。这条路没有银弹,关键是在成本和收益之间找到最适合自己当前阶段的那个平衡点。
&spm=1001.2101.3001.5002&articleId=150411922&d=1&t=3&u=cea05240cbfa43b6ac82de762e06bb29)
8274

被折叠的 条评论
为什么被折叠?



