ChatGPT如何颠覆传统推荐系统?5种实战方法解析(附代码)

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通常包含以下几个部分:

  1. 角色设定:明确告诉模型它现在扮演的角色。
  2. 任务描述:清晰定义需要它完成的推荐任务。
  3. 用户信息:提供用户画像或历史行为序列。
  4. 候选/约束条件:可选。提供候选物品列表或对推荐结果的限制(如类别、数量)。
  5. 输出格式:严格要求模型以特定格式(如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 更相似(都是便携音频设备),尽管文字描述不同。

生成的嵌入向量可以:

  1. 直接用于向量检索,做基于内容的相似推荐。
  2. 作为特征输入到深度学习排序模型,与ID类特征concat。
  3. 用于物品聚类,发现潜在品类。

任务二:生成用户兴趣摘要 我们可以定期(如每天)用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、转化率)的提升。

一个常见的集成策略是建立异步特征管道

  1. 物品上新或描述更新时,触发LLM进行特征提取(嵌入、标签)。
  2. 用户行为日志累积到一定量时,触发LLM生成/更新兴趣摘要。
  3. 将这些处理好的特征存入特征仓库(Feature Store)。
  4. 推荐模型训练和线上服务时,从特征仓库读取这些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 3Qwen 这样的开源大模型进行微调。以下是使用 pefttransformers 库进行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推荐的语气和逻辑更贴合我们品牌的调性。这条路没有银弹,关键是在成本和收益之间找到最适合自己当前阶段的那个平衡点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值