用 Milvus 实现电影推荐系统:从 Embedding 生成到向量检索的实战总结
在开发推荐系统时,我们常常会面临这样的挑战:如何高效地处理海量非结构化数据,让推荐结果既符合用户语义偏好,又能结合具体业务条件进行精准筛选?最近在实践中,我发现将 Milvus 向量数据库与 OpenAI 的文本 Embedding 能力结合,能很好地解决这些问题。今天就和大家分享一套完整的电影推荐系统实现方案,适合刚接触向量检索的开发者上手实践。
一、技术准备:搭建基础开发环境
1. 核心依赖安装
首先需要安装四个关键库:
bash
pip install openai pymilvus datasets tqdm
openai:用于生成文本 Embedding,这里我们使用轻量级模型text-embedding-3-small,在保持精度的同时提升处理速度pymilvus:Milvus 的 Python 客户端,支持本地文件、服务器、云服务等多种连接方式datasets:从 Hugging Face 直接加载netflix-shows数据集,包含 8000 + 电影元数据tqdm:显示数据处理进度条,方便监控批量操作状态
2. 配置 OpenAI 密钥
在代码中添加:
python
运行
import os
os.environ["OPENAI_API_KEY"] = "[你的API密钥]" # 注意:密钥需在OpenAI官网申请并妥善保管
首次使用需在 OpenAI 控制台创建 API 密钥,建议通过环境变量管理敏感信息,避免硬编码在代码中。
3. 初始化 Milvus 连接
python
运行
from pymilvus import MilvusClient
# 使用Milvus Lite本地存储(适合小规模数据)
client = MilvusClient("./milvus_demo.db")
# 若部署在服务器,改为:client = MilvusClient(uri="http://服务器IP:19530")
# 云服务版本:client = MilvusClient(uri="Zilliz Cloud端点", token="你的API密钥")
这里推荐先用本地文件模式快速验证功能,数据量超过百万级时再切换到服务器部署。
二、数据处理:从原始数据到向量入库
1. 定义数据结构
我们需要存储电影的基础信息和向量数据,创建包含 6 个字段的集合:
python
运行
from pymilvus import DataType
COLLECTION_NAME = "movie_search"
DIMENSION = 1536 # 与OpenAI模型输出维度一致
# 定义Schema,注意字符串字段需设置最大长度
schema = MilvusClient.create_schema()
schema.add_field("id", DataType.INT64, is_primary=True) # 主键ID
schema.add_field("title", DataType.VARCHAR, 64000) # 电影标题
schema.add_field("type", DataType.VARCHAR, 64000) # 类型(电影/剧集)
schema.add_field("release_year", DataType.INT64) # 发行年份
schema.add_field("rating", DataType.VARCHAR, 64000) # 分级信息
schema.add_field("description", DataType.VARCHAR, 64000) # 剧情描述
schema.add_field("embedding", DataType.FLOAT_VECTOR, DIMENSION) # 向量字段
# 创建集合并设置索引
client.create_collection(COLLECTION_NAME, schema)
client.create_index(
COLLECTION_NAME,
field_name="embedding",
metric_type="IP", # 内积相似度,适合文本场景
index_type="AUTOINDEX" # 自动选择最优索引类型
)
client.load_collection(COLLECTION_NAME) # 加载集合到内存以便查询
关键点:字符串字段必须指定max_length,向量字段维度要与 Embedding 生成结果一致,索引选择AUTOINDEX能简化配置,适合新手入门。
2. 加载 Hugging Face 数据集
直接获取预处理好的 Netflix 电影数据集:
python
运行
from datasets import load_dataset
dataset = load_dataset("hugginglearners/netflix-shows", split="train")
数据包含title、description等关键字段,但需要处理缺失值:
- 数值型字段(如
release_year)用-1填充 - 字符串字段用空字符串
""填充
确保后续 Embedding 生成和数据插入稳定。
3. 批量生成 Embedding 并入库
使用 OpenAI 接口生成剧情描述的向量表示,注意批量处理优化性能:
python
运行
from openai import OpenAI
openai_client = OpenAI()
def emb_texts(texts):
"""批量生成文本Embedding"""
response = openai_client.embeddings.create(
input=texts,
model="text-embedding-3-small"
)
return [data.embedding for data in response.data]
BATCH_SIZE = 1000 # 调整批次大小避免内存溢出
batch = []
for i in tqdm(range(len(dataset))):
item = {
"title": dataset[i].get("title", ""),
"type": dataset[i].get("type", ""),
"release_year": dataset[i].get("release_year", -1),
"rating": dataset[i].get("rating", ""),
"description": dataset[i].get("description", "")
}
batch.append(item)
# 达到批次大小或处理最后一条数据时提交
if len(batch) == BATCH_SIZE or i == len(dataset)-1:
texts = [x["description"] for x in batch]
embeddings = emb_texts(texts) # 生成向量
for x, emb in zip(batch, embeddings):
x["embedding"] = emb # 合并向量数据
client.insert(COLLECTION_NAME, batch) # 插入Milvus
batch = [] # 清空批次
踩坑经验:刚开始没做缺失值处理,导致部分数据生成 Embedding 失败,后来统一用默认值填充解决。建议在数据入库前做严格清洗,避免后续查询异常。
三、智能查询:结合语义检索与条件过滤
核心查询函数设计
实现一个支持语义搜索和元数据过滤的通用函数:
python
运行
import textwrap
def query_movies(query_text, filter_expr, top_k=5):
"""
query_text: 搜索文本(如"关于毛茸茸动物的电影")
filter_expr: 过滤表达式(如"release_year < 2019 and rating like 'PG%'")
top_k: 返回结果数量
"""
# 生成查询文本的Embedding
query_emb = emb_texts([query_text])[0]
# 执行向量搜索,同时应用元数据过滤
result = client.search(
collection_name=COLLECTION_NAME,
data=[query_emb],
filter=filter_expr,
limit=top_k,
output_fields=["title", "type", "release_year", "rating", "description"],
search_params={
"metric_type": "IP",
"params": {}
}
)
# 解析结果并格式化输出
print(f"搜索文本:{query_text}")
print(f"过滤条件:{filter_expr}\n")
for hit_group in result:
for rank, hit in enumerate(hit_group, 1):
entity = hit["entity"]
score = hit["distance"] # 距离越小相似度越高
print(f"排名 {rank} | 相似度:{score:.4f} | 标题:{entity['title']}")
print(f"类型:{entity['type']} | 年份:{entity['release_year']} | 分级:{entity['rating']}")
print("剧情简介:")
print(textwrap.fill(entity['description'], width=80)) # 自动换行
print("-" * 80)
实战查询示例
比如查找 2019 年前适合儿童观看(PG 分级)的毛茸茸动物主题电影:
python
运行
my_query = (
"movie about a fluffy animal", # 英文查询文本(实际可用中文,需模型支持)
"release_year < 2019 and rating like 'PG%'" # Milvus支持的SQL-like过滤语法
)
query_movies(*my_query)
输出结果:
plaintext
搜索文本:movie about a fluffy animal
过滤条件:release_year < 2019 and rating like 'PG%'
排名 1 | 相似度:0.4221 | 标题:The Adventures of Tintin
类型:Movie | 年份:2011 | 分级:PG
剧情简介:
This 3-D motion capture adapts Georges Remi's classic comic strip about the adventures
of fearless young journalist Tintin and his trusty dog, Snowy.
------------------------
...(后续结果)
过滤语法解析
支持常见的 SQL 条件表达式:
- 数值比较:
release_year > 2010 - 字符串匹配:
rating like 'R%'(支持 % 通配符) - 逻辑组合:
(type == 'Movie') and (release_year between 2015 and 2020)
通过元数据过滤,能有效缩小搜索范围,提升推荐相关性。
四、实践总结与优化方向
1. 开发中的关键收获
- 向量数据库优势:Milvus 的 AUTOINDEX 功能自动选择最佳索引(如 IVF-FLAT),无需手动调参,对新手非常友好
- 批量处理技巧:设置合适的 BATCH_SIZE(建议 500-1000),结合 tqdm 进度条,既能提升插入效率,又能监控任务状态
- 数据清洗重要性:缺失值处理不当会导致整个流程失败,建议在数据加载阶段添加严格校验
2. 生产环境优化建议
- 模型升级:若追求更高精度,可切换为
text-embedding-3或自定义微调模型 - 分布式部署:数据量超过 10 万条时,建议使用 Docker 部署 Milvus 集群,提升检索性能
- 用户交互优化:可添加前端界面,支持用户输入中文查询,后端通过翻译 API 转为英文再生成 Embedding(需模型支持多语言)
3. 常见问题解决方案
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 插入数据报错 | 字段类型不匹配 | 检查 Schema 定义,确保字符串长度、数值范围正确 |
| 搜索结果为空 | 过滤条件过严 | 简化过滤表达式,先验证基础功能 |
| 响应速度慢 | 未加载集合到内存 | 确保调用load_collection,或升级硬件 / 使用云服务 |
五、写在最后
这套方案不仅适用于电影推荐,稍作调整就能应用到图书、商品等领域的推荐系统。在实践中,我深刻体会到向量数据库与大语言模型结合的强大潜力 —— 既能处理文本语义,又能通过元数据精准筛选,真正实现 "智能 + 可控" 的推荐效果。
如果你在搭建过程中遇到问题,欢迎在评论区留言交流。觉得文章有用的话,别忘了点击关注和收藏,后续会分享更多 Milvus 进阶技巧,包括大规模数据分片、索引优化、与 LLM 深度整合等实战内容。让我们一起在向量检索的世界里,探索更多可能性!
更多推荐
所有评论(0)