y¿Y si tu tienda en línea supiera lo que un cliente quería antes de que lo hicieran?
y
¿Y si tu tienda en línea supiera lo que un cliente quería antes de que lo hicieran?
Most recommendation engines are like helpful but slightly clueless assistants:sugieren artículos “populares” o “similares” basados en datos limitados y desactualizados.strugglecuando los usuarios son nuevos (el problema de inicio frío), y raramente se adaptan lo suficiente rápidamente cuando las preferencias de un usuario cambian en tiempo real.
Pero, ¿y si el sistema puedeEn realidad piensancomo un merchandiser —combinando datos estáticos de producto y comportamiento de usuario en tiempo real a la superficieLos elementos correctos¿En el momento adecuado?
En realidad piensan
This guide walks you through building a modern recommendation engineUsoSuperlinked, uno que supera estas deficiencias tradicionales al convertir sus datos en perfiles de usuario actuables y evolucionando utilizando una infraestructura vectorial nativa.
(Quieres saltar directamente al código? Compruebe el código de código abierto en GitHub aquí. Listo para probar sistemas de recomendar para su propio caso de uso? Obtenga una demostración aquí.)
AquíAquíAquíAquí
También puedes seguir el tutorial en el navegador con nuestroEl colab.
TD y DR:
La mayoría de los recomendadores de comercio electrónico son demasiado estáticos (basados en reglas) o demasiado en la caja negra (modelos ML opacos). Superlinked ofrece un camino medio: recomendaciones flexibles en tiempo real que pueden adaptarse a los usuarios de inicio frío combinando metadatos con el comportamiento en vivo - todo sin retraer los modelos ML.
Lograr la personalización a pesar de los retos de incorporación vectorial de RecSys
Mientras que las incorporaciones vectoriales pueden mejorar enormemente los sistemas de recomendación, implementarlos de manera efectiva requiere abordar varios desafíos, incluyendo:
- y
- Calidad y relevancia: El proceso de generación de embudo, la arquitectura y los datos deben considerarse cuidadosamente. y
- Datos escasos y ruidosos: las incorporaciones son menos eficaces cuando tienen entradas incompletas o ruidosas. y
- Escalabilidad: Se necesitan métodos eficientes para grandes conjuntos de datos; de lo contrario, la latencia será un problema. y
Superlinked le permite abordar estos desafíos combinando todos los datos disponibles sobre usuarios y productos en ricos vectores multimodales.En nuestro ejemplo de comercio electrónico RecSys a continuación, lo hacemos utilizando los siguientes elementos de la biblioteca Superlinked:
- y
- Spaces de número min_max: para comprender las opiniones de los clientes y la información de precios y
- texto-similaridad Espacio: para la comprensión semántica de la información del producto y
- Esquema de eventos y efectos para modificar vectores y
- pesos de tiempo de consulta: para definir cómo desea que los datos se traten cuando ejecuta la consulta, lo que le permite optimizar y escalar sin reincorporar todo el conjunto de datos (latencia) y
Al incorporar nuestros inicialmente escasos datos específicos del usuario (preferencia inicial del producto del usuario), podemos manejar el problema de inicio frío.Hiper-Personalizar las recomendaciones mediante la incorporación de estos datos de evento, creando un ciclo de retroalimentación que le permite actualizar vectores con preferencias de usuario en tiempo real.Además, los pesos de tiempo de consulta de Superlinked le permiten ajustar sus resultados de búsqueda, biasándolos para coincidir con preferencias de usuario específicas.
Let's get started!
Construir un motor de recomendación de comercio electrónico con Superlinked
Al comienzo del desarrollo, tenemos lo siguiente:product data: de
- y
- Número de revisores y
- Clases de productos y
- Descripción textual y
- Nombre del producto (normalmente contiene el nombre de marca) y
- Categorías y
También tenemos el siguientedata about users and products: de
- y
- cada usuario elige uno de los tres productos ofrecidos al registrarse (es decir, datos de preferencias de productos) y
- comportamiento del usuario (después del registro) proporciona datos adicionales de eventos - preferencias para características textuales de los productos (descripción, nombre, categoría) y
Además, la economía clásica nos dice que, en promedio, todos los usuarios ceteris paribus prefieren productos que:
- y
- Cuesta menos y
- Tiene muchas reseñas y
- Tienen calificaciones más altas y
Podemos configurar nuestros espacios para tener en cuenta estos datos, de modo que nuestro RecSys funcione en escenarios de inicio frío - recomendando artículos para usuarios de los que sabemos muy poco.Una vez que nuestro RecSys esté en funcionamiento, también tendremos datos de comportamiento: los usuarios van a hacer clic en determinados productos, comprar determinados productos, etc. Podemos capturar y usar estos datos de eventos para crear flujos de retroalimentación, actualizar nuestros vectores para reflejar las preferencias de los usuarios y mejorar la calidad de la recomendación.
Creación de Superlink
Primero, necesitamos instalar la biblioteca Superlinked y importar las clases.
%pip install superlinked==6.0.0
import altair as alt
import os
import pandas as pd
import sys
from superlinked.framework.common.embedding.number_embedding import Mode
from superlinked.framework.common.schema.schema import schema
from superlinked.framework.common.schema.event_schema import event_schema
from superlinked.framework.common.schema.schema_object import String, Integer
from superlinked.framework.common.schema.schema_reference import SchemaReference
from superlinked.framework.common.schema.id_schema_object import IdField
from superlinked.framework.common.parser.dataframe_parser import DataFrameParser
from superlinked.framework.dsl.executor.in_memory.in_memory_executor import (
InMemoryExecutor,
InMemoryApp,
)
from superlinked.framework.dsl.index.index import Index
from superlinked.framework.dsl.index.effect import Effect
from superlinked.framework.dsl.query.param import Param
from superlinked.framework.dsl.query.query import Query
from superlinked.framework.dsl.source.in_memory_source import InMemorySource
from superlinked.framework.dsl.space.text_similarity_space import TextSimilaritySpace
from superlinked.framework.dsl.space.number_space import NumberSpace
alt.renderers.enable(get_altair_renderer())
pd.set_option("display.max_colwidth", 190)
También definimos nuestros conjuntos de datos, y creamos una constante para almacenar los 10 primeros elementos - véaseCélula 3en el cuaderno.
Ahora que se han identificado las ubicaciones de la biblioteca, las clases importadas y los conjuntos de datos, podemos echar un vistazo a nuestro conjunto de datos para informar de la forma en que configuramos nuestros espacios. Inicialmente, tenemos datos de registro de usuarios, es decir, cuál de los tres productos eligió user_1 y user_2.
# the user preferences come from the user being prompted to select a product out of 3 - those will be the initial preferences
# this is done in order to give somewhat personalised recommendations
user_df: pd.DataFrame = pd.read_json(USER_DATASET_URL)
user_df
También podemos establecer un examen detallado de los datos de distribución de nuestros productos - véaseCélula 5Esto le da una idea de cuántos productos están en diferentes puntos de precio, tienen diferentes cuentas de comentarios y tienen diferentes calificaciones (incluyendo donde la mayoría de los productos se encuentran en estos rangos).
Los precios de los productos están principalmente por debajo del punto de precio de $1000. Es posible que deseemos establecer el rango de espacio a 25-1000 para que sea representativo, no distorsionado por los valores externos. Las cuentas de revisión de productos se distribuyen uniformemente, y las calificaciones de revisión se distribuyen relativamente uniformemente, por lo que no se requiere tratamiento adicional.Células 7-9. el
Desarrollar el índice para la búsqueda vectorial
La biblioteca de Superlinked contiene un conjunto de bloques de construcción de núcleo que utilizamos para construir el índice y gestionar la recuperación.Aquí. el
Pongamos los bloques de construcción de esta biblioteca para usar en nuestro EComm RecSys.define your SchemaPara informar al sistema sobre sus datos.
# schema is the way to describe the input data flowing into our system - in a typed manner
@schema
class ProductSchema:
description: String
name: String
category: String
price: Integer
review_count: Integer
review_rating: Integer
id: IdField
@schema
class UserSchema:
preference_description: String
preference_name: String
preference_category: String
id: IdField
@event_schema
class EventSchema:
product: SchemaReference[ProductSchema]
user: SchemaReference[UserSchema]
event_type: String
id: IdField
# we instantiate schemas as follows
product = ProductSchema()
user = UserSchema()
event = EventSchema()
A continuación, utiliza espacios para decir cómo desea tratar cada parte de los datos cuando se incorpora. En definiciones de espacio, describimos cómo incorporar las entradas para que reflejen las relaciones semánticas en nuestros datos. Cada espacio está optimizado para incorporar los datos para devolver la más alta calidad posible de resultados de recuperación.
# textual inputs are embedded in a text similarity space powered by a sentence_transformers model
description_space = TextSimilaritySpace(
text=[user.preference_description, product.description],
model="sentence-transformers/all-distilroberta-v1",
)
name_space = TextSimilaritySpace(
text=[user.preference_name, product.name],
model="sentence-transformers/all-distilroberta-v1",
)
category_space = TextSimilaritySpace(
text=[user.preference_category, product.category],
model="sentence-transformers/all-distilroberta-v1",
)
# NumberSpaces encode numeric input in special ways to reflect a relationship
# here we express relationships to price (lower the better), or ratings and review counts (more/higher the better)
price_space = NumberSpace(
number=product.price, mode=Mode.MINIMUM, min_value=25, max_value=1000
)
review_count_space = NumberSpace(
number=product.review_count, mode=Mode.MAXIMUM, min_value=0, max_value=100
)
review_rating_space = NumberSpace(
number=product.review_rating, mode=Mode.MAXIMUM, min_value=0, max_value=4
)
# create the index using the defined spaces
product_index = Index(
spaces=[
description_space,
name_space,
category_space,
price_space,
review_count_space,
review_rating_space,
]
)
# parse our data into the schemas - not matching column names can be conformed to schemas using the mapping parameter
product_df_parser = DataFrameParser(schema=product)
user_df_parser = DataFrameParser(
schema=user, mapping={user.preference_description: "preference_desc"}
)
# setup our application
source_product: InMemorySource = InMemorySource(product, parser=product_df_parser)
source_user: InMemorySource = InMemorySource(user, parser=user_df_parser)
executor: InMemoryExecutor = InMemoryExecutor(
sources=[source_product, source_user], indices=[product_index]
)
app: InMemoryApp = executor.run()
# load the actual data into our system
source_product.put([products_df])
source_user.put([user_df])
Ahora que tienes tus datos definidos en espacios, estás listo para jugar con tus datos y optimizar los resultados.Lo que podemos hacer sin eventos- Nuestra solución de inicio frío.
Solucionar el problema de inicio frío de RecSys
Aquí, definimos una consulta de usuario que busca con sólo el vector de preferencia del usuario.Tenemos control de configuración sobre la importancia (pesos) de cada tipo de entrada (Espacio).
user_query = (
Query(
product_index,
weights={
description_space: Param("description_weight"),
name_space: Param("name_weight"),
category_space: Param("category_weight"),
price_space: Param("price_weight"),
review_count_space: Param("review_count_weight"),
review_rating_space: Param("review_rating_weight"),
},
)
.find(product)
.with_vector(user, Param("user_id"))
.limit(Param("limit"))
)
# simple recommendations for our user_1
# these are based only on the initial product the user chose when first entering our site
simple_result = app.query(
user_query,
user_id="user_1",
description_weight=1,
name_weight=1,
category_weight=1,
price_weight=1,
review_count_weight=1,
review_rating_weight=1,
limit=TOP_N,
)
simple_result.to_pandas()
Los resultados de esta consulta reflejan el hecho de que user_1 eligió una bolsa cuando se registró por primera vez en nuestro sitio de ecomm.
También es posible recomendar productos a user_1 que sonEn generalatractivo - es decir, basado en su precio siendo bajo, y teniendo un montón de buenas críticas.Nuestros resultados ahora reflejarán tanto la elección de producto de user_1 al registrarseyla popularidad general de los productos. (También podemos jugar con estos pesos para desviar los resultados en la dirección de un espacio u otro.)
general_result = app.query(
user_query,
user_id="user_1",
description_weight=0,
name_weight=0,
category_weight=0,
price_weight=1,
review_count_weight=1,
review_rating_weight=1,
limit=TOP_N,
)
general_result.to_pandas()
La búsqueda de un nuevo usuario introduce el texto de la consulta como una entrada para nuestros resultados de recomendación - véaseCélula 20. el
En nuestro caso de ejemplo, user_1 buscó "camisetas de mujer".Podemos optimizar nuestros resultados dandoadditional weight to the category space(en elcategory_weight = 10
), para recomendar más productos de "camisetas de ropa femenina".
women_cat_result = app.query(
search_query,
user_id="user_1",
query_text="women clothing jackets",
description_weight=1,
name_weight=1,
category_weight=10,
price_weight=1,
review_count_weight=1,
review_rating_weight=1,
limit=TOP_N,
)
women_cat_result.to_pandas()
Nuestro peso de categoría adicional produce más resultados de ropa de mujer.
También podemos bias nuestras recomendaciones a los productos de primera clasificación (review_rating_weight=5
Los resultados ahora reflejan la preferencia inicial del usuario_1 por bolsas y artículos que son generalmente populares, mientras que los productos con calificaciones bajas se eliminan por completo.Célula 22. el
Usar datos de eventos para crear experiencias personalizadas
Nuestros usuarios han interactuado con nuestra plataforma - user_1 más, user_2 menos así.behavioral data(ver más abajo), representados como eventos:
- y
- un usuario interesado en productos caseros y de ocio (user_2) y
- un usuario interesado en productos elegantes para salir y ocasiones formales de trabajo (user_1) y
events_df = (
pd.read_json(EVENT_DATASET_URL)
.reset_index()
.rename(columns={"index": "id"})
.head(NROWS)
)
events_df = events_df.merge(
products_df[["id"]], left_on="product", right_on="id", suffixes=("", "r")
).drop("idr", axis=1)
events_df = events_df.assign(created_at=1715439600)
events_df
Vamos a ponderar acciones específicas para registrar el nivel de interés del usuario en un producto en particular, y ajustar la configuración para tener en cuenta los eventos al realizar la recuperación.
event_weights = {
"clicked_on": 0.2,
"buy": 1,
"put_to_cart": 0.5,
"removed_from_cart": -0.5,
}
# adjust the setup to events
product_index_with_events = Index(
spaces=[
description_space,
category_space,
name_space,
price_space,
review_count_space,
review_rating_space,
],
effects=[
Effect(
description_space,
event.user,
event_weight * event.product,
event.event_type == event_type,
)
for event_type, event_weight in event_weights.items()
]
+ [
Effect(
category_space,
event.user,
event_weight * event.product,
event.event_type == event_type,
)
for event_type, event_weight in event_weights.items()
]
+ [
Effect(
name_space,
event.user,
event_weight * event.product,
event.event_type == event_type,
)
for event_type, event_weight in event_weights.items()
],
)
event_df_parser: DataFrameParser = DataFrameParser(schema=event)
source_event: InMemorySource = InMemorySource(schema=event, parser=event_df_parser)
executor_with_events: InMemoryExecutor = InMemoryExecutor(
sources=[source_product, source_user, source_event],
indices=[product_index_with_events],
)
app_with_events: InMemoryApp = executor_with_events.run()
Ahora creamos un nuevo índice para tener en cuenta los eventos de los usuarios, y luego personalizamos las recomendaciones a cada usuario en consecuencia.
# for a new index, all data has to be put into the source again
source_product.put([products_df])
source_user.put([user_df])
source_event.put([events_df])
# a query only searching with the user's vector the preferences are now much more personalised thanks to the events
personalised_query = (
Query(
product_index_with_events,
weights={
description_space: Param("description_weight"),
category_space: Param("category_weight"),
name_space: Param("name_weight"),
price_space: Param("price_weight"),
review_count_space: Param("review_count_weight"),
review_rating_space: Param("review_rating_weight"),
},
)
.find(product)
.with_vector(user, Param("user_id"))
.limit(Param("limit"))
)
Podemos observar el impacto de incorporar eventos en nuestros RecSys ponderando la personalizaciónSólo ligeramenteopesadoPrimero, veamos el efecto (en comparación con la base) de ponderar los espacios que están influenciados por estos eventos (datos de comportamiento).
# with small weight on event-affected spaces, we mainly just alter the results below position 4
general_event_result = app_with_events.query(
personalised_query,
user_id="user_1",
description_weight=1,
category_weight=1,
name_weight=1,
price_weight=1,
review_count_weight=1,
review_rating_weight=1,
limit=TOP_N,
)
general_event_result.to_pandas().join(
simple_result.to_pandas(), lsuffix="", rsuffix="_base"
)[["description", "id", "description_base", "id_base"]]
Con muy poco peso puesto en los espacios afectados por los eventos, observamos un cambio pero principalmente sólo en la segunda mitad de nuestro top 10, en comparación con los resultados anteriores ("id_base", a la derecha).
Pero si pesamos más los espacios afectados por el evento, superponemos elementos completamente nuevos en nuestra lista de recomendaciones.
# with larger weight on the the event-affected spaces, more totally new items appear in the TOP10
event_weighted_result = app_with_events.query(
personalised_query,
user_id="user_1",
query_text="",
description_weight=5,
category_weight=1,
name_weight=1,
price_weight=1,
review_count_weight=1,
review_rating_weight=1,
limit=TOP_N,
)
event_weighted_result.to_pandas().join(
simple_result.to_pandas(), lsuffix="", rsuffix="_base"
)[["description", "id", "description_base", "id_base"]]
También podemos, por supuesto, utilizar pesos para personalizar nuestras recomendaciones basadas en el comportamiento de un usuario en particular (datos de eventos) ypriorizar simultáneamente otros atributos del productoPor ejemplo, el precio (verCélula 31) de
Conclusión
La implementación de eComm RecSys de la biblioteca Superlinked (abajo) le muestra cómo realizar el poder de las incorporaciones vectoriales incorporando el significado semántico de las consultas de los usuarios y los datos de comportamiento. Usando nuestros espacios de número min_max y similitud de texto, esquema de eventos y efectos, y pesos de tiempo de consulta, puede abordar los desafíos de inicio frío, calidad y relevancia y escalabilidad de RecSys y proporcionar recomendaciones altamente precisas y personalizadas para el usuario en la producción.
¡Ahora es tu turno!Trate de implementar la biblioteca Superlinked usted mismo usando nuestro cuaderno. el
Try It Yourself – Get the Code & Demo!
¡Pruébalo tú mismo - ¡Get the Code & Demo!- y
- Grab the Code: Compruebe la implementación completa en nuestro repo de GitHub aquí.Fork, ajuste y hazlo tuyo! y
- Verlo en Acción: ¿Quieres ver esto funcionando en un mundo real? Reserva una demostración rápida y explora cómo Superlinked puede sobrecargar tus recomendaciones. y
Los motores de recomendación están moldeando la forma en que descubrimos el contenido, ya sean pantalones populares, música u otros productos.vector search is the future—y ahora tienes las herramientas para construir tu propia.