# 推荐系统
Typesense 可以根据用户在特定会话中的行为生成推荐,这是通过 向量搜索 功能实现的。
这需要构建一个机器学习模型来生成 嵌入向量(embeddings),将其存储在 Typesense 中,然后在 Typesense 中进行最近邻搜索。
本文将介绍如何使用 Starspace (opens new window) 机器学习模型来生成嵌入向量。
Transformers4Rec (opens new window) 是另一个可以用于此场景的机器学习模型(还有其他适用模型)。
# 应用场景
本文将以电商产品数据集为例进行说明,但以下概念适用于任何领域(例如:推荐文章、电影或存储在 Typesense 中的任何类型记录)。
# 第一步:准备训练数据集
我们将使用 Starspace (opens new window) 来构建机器学习模型。
Starspace 要求训练数据集采用以下格式 - 每行代表一个用户会话以及该会话中用户交互过的商品集合。
在上面的示例中,第一行表示某个用户在一个会话中与商品 apple
、orange
、banana
、broccoli
和 mango
进行了交互(查看、购买、加入购物车等)。
另一个用户(也可能是同一个用户)在另一个会话中与商品 cereals
、soda
、bread
、nuts
和 cookies
进行了交互。
TIP
为了让本文更易读,我们在这个示例中使用了 product_name
。
在生产环境中,你应该在训练数据集中使用商品的 ID 或 SKU。
# 第二步:设置 Starspace
# 安装系统依赖
确保你安装了 C++11 编译器(gcc-4.6.3 或更新版本,或 Visual Studio 2015,或 clang-3.3 或更新版本)。
在 macOS 上需要安装 XCode,在 Linux 发行版上需要通过发行版的包管理器安装 build-essential
包。
# 克隆 Starspace 源代码
git clone https://github.com/facebookresearch/Starspace.git
cd Starspace
# 设置 Boost
Boost 是 Starspace 所需的库。
在上述 Starspace
目录中运行以下命令:
curl -LO https://boostorg.jfrog.io/artifactory/main/release/1.82.0/source/boost_1_82_0.tar.gz
tar -xzvf boost_1_82_0.tar.gz
# 编译 Starspace
在 Starspace
目录中运行以下命令:
make -e BOOST_DIR=boost_1_82_0 && \
make embed_doc -e BOOST_DIR=boost_1_82_0
要验证 Starspace 是否正常工作,运行 ./starspace
后应看到类似以下输出:
$ ./starspace
用法:需要指定是训练还是测试模式
"starspace train ..." 或 "starspace test ..."
...
# 步骤 3:训练 Starspace 模型
将步骤 1 中的训练数据集文件命名为 session-data.txt
。
然后运行以下命令训练模型:
./starspace train \
-trainFile <path/to/session-data.txt> \
-model productsModel \
-label '' \
-trainMode 1 \
-epoch 25 \
-dim 100
命令运行完成后,将生成两个文件:
- 二进制文件
productsModel
- 包含模型权重的 TSV 文件
productsModel.tsv
建议在用户使用网站/应用收集到新会话数据时定期重新执行此训练步骤。
# 步骤 4:生成嵌入向量
首先从训练数据集中提取所有唯一商品:
export unique_items=$(tr ' ' '\n' < session-data.txt | sed '/^$/d' | sort -u)
export output_jsonl_file="products-with-embeddings.jsonl"
为每个商品生成嵌入向量并存储到 JSONL 文件中:
echo -n > ${output_jsonl_file}
while read -r item; do
embedding=$(echo "${item}" | ./embed_doc productsModel | tail -1 | tr ' ' ',')
echo "{\"id\":\"${item}\",\"embedding\":[${embedding%?}]}" >> "${output_jsonl_file}"
done <<< "${unique_items}"
这将生成如下所示的 JSONL 文件:
现在我们可以将这个 JSONL 文件导入到 Typesense 中。
# 步骤5:在Typesense中索引嵌入向量
export TYPESENSE_API_KEY=xyz
export TYPESENSE_URL='https://xyz.a1.typesense.net'
创建集合:
curl "${TYPESENSE_URL}/collections" \
-X POST \
-H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-d '{
"name": "products",
"fields": [
{"name": "name", "type": "string", "optional": true},
{"name": "description", "type": "string", "optional": true},
{"name": "price", "type": "float", "optional": true},
{"name": "categories", "type": "string[]", "optional": true},
{"name": "embedding", "type": "float[]", "num_dim": 100 }
]
}'
注意我们设置了 num_dim: 100
。这对应着我们训练 Starspace 模型时设置的 -dim 100
参数。
将包含嵌入向量的JSONL文件导入集合:
curl -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-X POST \
-T products-with-embeddings.jsonl \
"${TYPESENSE_URL}/collections/products/documents/import?action=emplace"
这里我们只插入了每个产品的嵌入向量。你也可以使用相同的 id
字段作为参考,分别导入其他值如名称、描述、价格、类别等,以填充产品记录的其他部分。
# 步骤6:生成推荐结果
假设现在有一位用户访问我们的网站/应用,并在会话中与以下产品进行了交互:
mango broccoli milk
为了基于这些会话数据生成推荐,我们首先需要生成嵌入向量:
export embedding=$(echo "mango broccoli milk" | ./embed_doc productsModel | tail -1 | tr ' ' ',')
echo ${embedding}
-0.0862846,0.127956,0.0558543,0.0745331,0.02449,-0.131018,0.0886827,-0.0571893,-0.0398686,-0.0116799,-0.0164978,-0.173818,0.0478985,0.109211,-0.0826394,-0.177671,-0.219366,0.180478,-0.0140154,-0.0237589,-0.010896,0.115979,-0.044924,0.129452,-0.0111529,-0.0978542,-0.121468,-0.0700872,-0.0190036,0.116127,0.0617186,-0.0463324,-0.172141,0.0302211,0.0610366,-0.0831281,0.04558,-0.00370933,-0.107602,-0.0394414,0.0334175,0.0429023,0.133572,-0.124658,0.225743,-0.0156787,-0.284864,0.148183,-0.0508378,0.175489,-0.0417769,-0.0920536,-0.0443016,-0.0838343,-0.0694042,-0.0333535,-0.108574,-0.0894618,-0.022049,-0.0500605,-0.0234268,0.00732048,0.0817547,0.00764651,0.0285933,0.100818,-0.229398,0.0508415,0.117766,-0.0289333,-0.0493134,0.167664,0.0696889,0.115228,-0.0609508,-0.12562,-0.0450054,-0.0648439,0.0817176,0.169663,0.133255,-0.111001,-0.0467052,-0.0373238,0.005385,0.111311,-0.0171787,0.0311545,0.0474074,-0.0301008,-0.0555648,0.0776044,-0.0287841,-0.162136,-0.0511268,0.174767,-0.0169033,-0.0223623,-0.140496,0.154727
现在我们可以将这个用户会话数据生成的嵌入向量作为 vector_query
发送给 Typesense,进行最近邻搜索,这将返回推荐给该用户的产品列表:
curl "${TYPESENSE_URL}/multi_search" \
-X POST \
-H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-d '{
"searches": [
{
"q": "*",
"collection": "products",
"vector_query": "embedding:(['${embedding%?}'], k:10, distance_threshold: 1)"
}
]
}' | jq '.results[0].hits[] | .document.id'
这将返回以下推荐结果,我们可以在过滤掉用户在当前会话中已经看过的商品后,将其展示在用户界面中:
"broccoli"
"mango"
"banana"
"apple"
"orange"
"tissue"
"detergent"
"cheese"
"milk"
"butter"
:::提示 本文中我们使用产品名称是为了便于阅读。 在实际生产环境中,你应该在训练数据集中使用产品ID或SKU,并按以下方式生成嵌入向量:
sku_1 sku_4 sku_5
sku_5 sku_8 sku_1 sku_2 sku_10
sku_5 sku_1 sku_4 sku_21 sku_22
:::