用户喜欢选项,但过多的选项可能导致选择困难症。
这就是推荐系统的用武之地。这些工具已经取得了长足的进步,使企业更容易提供丰富的选项而不会让用户感到不知所措。这是两全其美:既有多样性,又不会造成决策疲劳。
让我们来详细了解基于内容的过滤(它是许多类型推荐系统的核心部分),解释其工作原理的数据科学技术,深入探讨其优点和缺点,并通过一个教程向您展示如何使用 Redis 构建个性化推荐。 您可以在此处克隆此仓库。
基于内容的过滤是一种推荐技术,它利用机器学习根据项目的特征(即内容)向用户推荐项目。使用基于内容的过滤的推荐系统会分析项目特征和用户偏好,以构建用户画像,系统可以将用户画像与适合该画像的新项目进行匹配。
基于内容的过滤方法将用户和项目分解为元数据。例如,IMDB 的推荐系统可以按类型标签(如喜剧、恐怖或爱情)对电影进行分类。它还会捕获用户行为信息,例如您点击的电影或您当前使用的搜索词,从而构建用户画像,以保持推荐的相关性并支持持续推荐。
元数据是基于内容的过滤的基础,但推荐算法才是其神奇之处。
许多推荐系统依赖于 k 近邻 (k-NN) 模型。这种机器学习模型会找到给定输入的最近数据点(即邻居),并根据这些邻居的属性进行预测。在 IMDB 的例子中,k-NN 模型会知道某个用户点击了一部带有“快节奏”、“群星出演”和“PG 级”标签的电影,然后推荐一部具有类似属性的新电影。
当然,这种基本的数据科学方法不仅适用于电影
基于内容的过滤并非没有局限性,因此它经常与协同过滤结合使用。
完全使用基于内容的过滤技术的推荐系统往往受限于可用元数据的范围和质量。
当元数据有限时,即使最好的算法也可能表现不佳。推荐可能不准确,让人感觉没有说服力或不相关。如果元数据缺乏深度,系统很可能会提供过于相似的建议,让用户感到无聊和缺乏灵感。
由于这些原因(以及其他原因,我们将在下一节介绍),公司转向协同过滤——无论是为了取代基于内容的过滤还是作为补充。
协同过滤方法依赖于用户互动,例如用户评分、点赞或购买,来进行推荐。推荐系统允许用户通过隐式反馈相互“协作”。然后,它利用其他用户的反馈来做出明智的推荐。
例如,如果某人给某部电影很高的评分,或者持续购买并评论某些服装品牌,协同过滤系统就会将这些电影和品牌推荐给类似的用户。
基于内容的过滤有其自身的权衡,但通过将其与添加了协同过滤的混合推荐系统相结合,其中许多权衡可以得到平衡。无论如何,如果您正在构建推荐系统,您需要了解如何利用其优点并避免其缺点。
基于内容的过滤具有众多优点,使其成为许多推荐系统的核心部分
基于内容的过滤的成功或失败往往取决于您的元数据的质量和广度。元数据越丰富,实现基于内容的过滤就越容易,结果也越好。
基于内容的过滤有一些局限性,其中一些比另一些更难克服。
基于内容的过滤在处理复杂或非结构化内容(如图像、音乐或视频)的领域也可能遇到困难。但如果您有特征提取方法,例如计算机视觉或基于视觉的 LLM 模型,您可以使用 RedisVL 等工具将这一缺点转化为优势。
基于内容的过滤广泛应用于各个行业和应用场景,因为如果没有个性化的过滤和推荐,近乎无限的选项可能会成为一种负担。这就是为什么,仔细观察,您几乎可以在网上随处找到基于内容的过滤。让我们探讨一些应用场景
在实体店中,企业主展示新产品的空间有限。在线上,公司可以展示更多选项,前提是他们使用正确的推荐系统来帮助用户找到最适合他们的选择。
使用 Redis,构建基于内容的过滤系统非常简单。在这里,我们将逐步介绍如何使用 RedisVL 和 IMDB 电影数据集,构建一个由基于内容的过滤支持的电影推荐系统。 您可以在此处克隆此仓库。
从高层面来看,我们将使用 RedisVL 从每部电影的标题、描述和关键词生成语义嵌入向量,然后使用向量相似性搜索存储和查询向量,以找到语义相似的电影。然后,我们将使用额外的字段,例如类型和上映年份,来增强结果。
首先导入所需的库并定义您的 Redis URL。
Python
import pandas as pd
import ast
import os
import pickle
import requests
# Replace values below with your own if using Redis Cloud instance
REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
REDIS_PORT = os.getenv("REDIS_PORT", "6379")
REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", "")
# If SSL is enabled on the endpoint, use rediss:// as the URL prefix
REDIS_URL = f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}"
我们使用的是来自 IMDB 的约 25,000 部电影的数据集。与任何数据任务一样,第一步是清理我们的数据。这包括填充缺失值、将某些字段转换为列表以及删除不必要的列。
Python
try:
df = pd.read_csv("datasets/content_filtering/25k_imdb_movie_dataset.csv")
except:
# download the file
url = 'https://redis-ai-resources.s3.us-east-2.amazonaws.com/recommenders/datasets/content-filtering/25k_imdb_movie_dataset.csv'
r = requests.get(url)
#save the file as a csv
if not os.path.exists('./datasets/content_filtering'):
os.makedirs('./datasets/content_filtering')
with open('./datasets/content_filtering/25k_imdb_movie_dataset.csv', 'wb') as f:
f.write(r.content)
df = pd.read_csv("datasets/content_filtering/25k_imdb_movie_dataset.csv")
Python
roman_numerals = ['(I)','(II)','(III)','(IV)', '(V)', '(VI)', '(VII)', '(VIII)', '(IX)', '(XI)', '(XII)', '(XVI)', '(XIV)', '(XXXIII)', '(XVIII)', '(XIX)', '(XXVII)']
def replace_year(x):
if x in roman_numerals:
return 1998 # the average year of the dataset
else:
return x
df.drop(columns=['runtime', 'writer', 'path'], inplace=True)
Python
df['year'] = df['year'].apply(replace_year) # replace roman numerals with average year
df['genres'] = df['genres'].apply(ast.literal_eval) # convert string representation of list to list
df['keywords'] = df['keywords'].apply(ast.literal_eval) # convert string representation of list to list
df['cast'] = df['cast'].apply(ast.literal_eval) # convert string representation of list to list
df = df[~df['overview'].isnull()] # drop rows with missing overviews
df = df[~df['overview'].isin(['none'])] # drop rows with 'none' as the overview
我们推荐系统的核心是根据电影的描述确定它们之间的相似性。为此,我们使用 HuggingFace 的预训练语言模型 为每部电影的概述和关键词生成向量嵌入。此步骤将花费一些时间,但只需为整个数据集执行一次。
如果您不想等待,可以跳过此单元格,加载我们已经为您预先生成到文件中的向量。
Python
# add a column to the dataframe with all the text we want to embed
df["full_text"] = df["title"] + ". " + df["overview"] + " " + df['keywords'].apply(lambda x: ', '.join(x))
from redisvl.utils.vectorize import HFTextVectorizer
vectorizer = HFTextVectorizer(model = 'sentence-transformers/paraphrase-MiniLM-L6-v2')
df['embedding'] = df['full_text'].apply(lambda x: vectorizer.embed(x, as_buffer=False))
pickle.dump(df['embedding'], open('datasets/content_filtering/text_embeddings.pkl', 'wb'))
Python
try:
with open('datasets/content_filtering/text_embeddings.pkl', 'rb') as vector_file:
df['embedding'] = pickle.load(vector_file)
except:
embeddings_url = 'https://redis-ai-resources.s3.us-east-2.amazonaws.com/recommenders/datasets/content-filtering/text_embeddings.pkl'
r = requests.get(embeddings_url)
with open('./datasets/content_filtering/text_embeddings.pkl', 'wb') as f:
f.write(r.content)
with open('datasets/content_filtering/text_embeddings.pkl', 'rb') as vector_file:
df['embedding'] = pickle.load(vector_file)
接下来,我们为 RedisVL 定义一个模式,以指定每部电影将拥有的字段,包括向量维度、距离度量以及任何额外字段,如年份、类型或评分。我们将从一个 yaml 文件 content_filtering_schema.yaml 加载此模式。
取消设置
index:
name: movies_recommendation
prefix: movie
storage_type: json
fields:
- name: title
type: text
- name: rating
type: numeric
- name: rating_count
type: numeric
- name: genres
type: tag
- name: overview
type: text
- name: keywords
type: tag
- name: cast
type: tag
- name: writer
type: text
- name: year
type: numeric
- name: full_text
type: text
- name: embedding
type: vector
attrs:
dims: 384
distance_metric: cosine
algorithm: flat
dtype: float32
Python
movie_schema = IndexSchema.from_yaml("content_filtering_schema.yaml")
index = SearchIndex(movie_schema, redis_client=client)
index.create(overwrite=True, drop=True)
data = df.to_dict(orient='records')
keys = index.load(data)
现在我们的数据已存储在 Redis 中,我们可以使用向量相似性搜索来查找彼此相似的电影。例如,要查找与经典影片《海底两万里》相似的电影,我们检索其向量嵌入并用它来搜索相似电影。
Python
from redisvl.query import RangeQuery
query_vector = df[df['title'] == '20,000 Leagues Under the Sea']['embedding'].values[0]
query = RangeQuery(vector=query_vector,
vector_field_name='embedding',
num_results=3,
distance_threshold=0.7,
return_fields = ['title', 'overview', 'vector_distance'])
results = index.query(query)
查询结果如下所示。
Python
[{'id': 'movie:b64fc099d6af440a891e1dd8314e5af7',
'vector_distance': '0.584870040417',
'title': 'The Odyssey',
'overview': 'The aquatic adventure of the highly influential and fearlessly ambitious pioneer, innovator, filmmaker, researcher, and conservationist, Jacques-Yves Cousteau, covers roughly thirty years of an inarguably rich in achievements life.'},
{'id': 'movie:2fbd7803b51a4bf9a8fb1aa79244ad64',
'vector_distance': '0.63329231739',
'title': 'The Inventor',
'overview': 'Inventing flying contraptions, war machines and studying cadavers, Leonardo da Vinci tackles the meaning of life itself with the help of French princess Marguerite de Nevarre.'},
{'id': 'movie:224a785ca7ea4006bbcdac8aad5bf1bc',
'vector_distance': '0.658123672009',
'title': 'Ruin',
'overview': 'The film follows a nameless ex-Nazi captain who navigates the ruins of post-WWII Germany determined to atone for his crimes during the war by hunting down the surviving members of his former SS Death Squad.'}]
在现实世界的推荐系统中,用户通常喜欢应用自己的过滤器——例如按类型缩小范围或使用特定关键词搜索。我们可以轻松扩展我们的系统,将模式中设置的这些(或任何其他字段)过滤器包含在内。
添加这些过滤器没有一刀切的方法,因为每个内容推荐应用都会有不同的字段,这就是 Redis 支持多种过滤器类型的原因,包括标签、文本模糊匹配、数字范围和地理半径。尝试为模式中定义的其他字段添加过滤器,看看结果如何变化。
Python
from redisvl.query.filter import Tag, Num, Text
def make_filter(genres=None, release_year=None, keywords=None):
flexible_filter = (
(Num("year") > release_year) & # only show movies released after this year
(Tag("genres") == genres) & # only show movies that match at least one in list of genres
(Text("full_text") % keywords) # only show movies that contain at least one of the keywords
)
return flexible_filter
def get_recommendations(movie_vector, num_results=5, distance=0.6, filter=None):
query = RangeQuery(vector=movie_vector,
vector_field_name='embedding',
num_results=num_results,
distance_threshold=distance,
return_fields = ['title', 'overview', 'genres'],
filter_expression=filter,
)
recommendations = index.query(query)
return recommendations
现在,您已经了解了基于内容的过滤的基础知识、该技术的优点和缺点、其广泛的应用场景,以及如何使用 RedisVL 自行构建基于内容的推荐系统。
借助 Redis 作为向量数据库的强大功能,您可以生成相关的推荐,从而改善用户体验并提高转化率(以及许多其他好处)。无论您是推荐产品、音乐、电影还是图书,RedisVL 的灵活性和性能使其成为构建可扩展推荐系统的绝佳选择。