paint-brush
Uso de pgvector para localizar similitudes en datos empresarialespor@johnjvester
805 lecturas
805 lecturas

Uso de pgvector para localizar similitudes en datos empresariales

por John Vester14m2024/03/21
Read on Terminal Reader

Demasiado Largo; Para Leer

Aproveche el poder de los vectores de palabras y las búsquedas de similitudes para ayudar a descubrir correlaciones de datos con datos empresariales para ayudar con una campaña de marketing.
featured image - Uso de pgvector para localizar similitudes en datos empresariales
John Vester HackerNoon profile picture
0-item
1-item


Los ingenieros de software ocupan un lugar apasionante en este mundo. Independientemente del sector tecnológico o de la industria, tenemos la tarea de resolver problemas que contribuyan directamente a las metas y objetivos de nuestros empleadores. Como beneficio adicional, podemos utilizar la tecnología para mitigar cualquier desafío que se nos presente.


Para este ejemplo, quería centrarme en cómo pgvector (una búsqueda de similitudes de vectores de código abierto para Postgres) se puede utilizar para identificar similitudes de datos que existen en los datos empresariales.

Un caso de uso simple

Como ejemplo sencillo, supongamos que el departamento de marketing necesita ayuda para una campaña que planean lanzar. El objetivo es llegar a todas las cuentas de Salesforce que se encuentran en industrias estrechamente alineadas con la industria del software.


Al final, les gustaría centrarse en cuentas de las tres industrias más similares, con la capacidad de utilizar esta herramienta en el futuro para encontrar similitudes para otras industrias. Si es posible, les gustaría tener la opción de proporcionar el número deseado de industrias coincidentes en lugar de devolver siempre las tres primeras.

Diseño de alto nivel

Este caso de uso se centra en realizar una búsqueda de similitud. Si bien es posible completar este ejercicio manualmente, me viene a la mente la herramienta Wikipedia2Vec debido a las incorporaciones previamente entrenadas que ya se han creado para varios idiomas. Las incrustaciones de palabras, también conocidas como vectores, son representaciones numéricas de palabras que contienen información sintáctica y semántica. Al representar palabras como vectores, podemos determinar matemáticamente qué palabras son semánticamente "más cercanas" a otras.


En nuestro ejemplo, también podríamos haber escrito un programa Python simple para crear vectores de palabras para cada industria configurada en Salesforce.


La extensión pgvector requiere una base de datos Postgres. Sin embargo, los datos empresariales de nuestro ejemplo residen actualmente en Salesforce. Afortunadamente, Heroku Connect proporciona una manera sencilla de sincronizar las cuentas de Salesforce con Heroku Postgres, almacenándolas en una tabla llamada salesforce.account . Luego, tendremos otra tabla llamada salesforce.industries que contiene cada industria en Salesforce (como una clave VARCHAR), junto con su vector de palabras asociado.


Con los datos de Salesforce y los vectores de palabras en Postgres, crearemos una API RESTful usando Java y Spring Boot. Este servicio realizará la consulta necesaria y devolverá los resultados en formato JSON.


Podemos ilustrar la vista de alto nivel de la solución de esta manera:


El código fuente residirá en GitLab. Emitir un comando git push heroku activará una implementación en Heroku, introduciendo una API RESTful que el equipo de marketing puede consumir fácilmente.

Construyendo la solución

Una vez implementado el diseño de alto nivel, podemos comenzar a construir una solución. Usando mi inicio de sesión de Salesforce, pude navegar a la pantalla Cuentas para ver los datos de este ejercicio. A continuación se muestra un ejemplo de la primera página de datos empresariales:


Crear una aplicación Heroku

Para este esfuerzo, planeé usar Heroku para resolver la solicitud del equipo de marketing. Inicié sesión en mi cuenta Heroku y utilicé el botón Crear nueva aplicación para establecer una nueva aplicación llamada similarity-search-sfdc :


Después de crear la aplicación, navegué a la pestaña Recursos para encontrar el complemento Heroku Postgres. Escribí "Postgres" en el campo de búsqueda de complementos.


Después de seleccionar Heroku Postgres de la lista, elegí el plan Estándar 0 , pero pgvector está disponible en ofertas de bases de datos de nivel Estándar (o superior) que ejecutan PostgreSQL 15 o la base de datos beta de nivel Esencial .


Cuando confirmé el complemento, Heroku generó y proporcionó una cadena de conexión DATABASE_URL . Encontré esto en la sección Config Vars de la pestaña Configuración de mi aplicación. Utilicé esta información para conectarme a mi base de datos y habilitar la extensión pgvector de esta manera:


 CREATE EXTENSION vector;


A continuación, busqué y encontré el complemento Heroku Connect . Sabía que esto me daría una manera fácil de conectarme a los datos empresariales en Salesforce.


Para este ejercicio, el plan gratuito Demo Edition funciona bien.


En este punto, la pestaña Recursos para la aplicación similarity-search-sfdc tenía este aspecto:


Seguí las instrucciones de " Configuración de Heroku Connect " para vincular mi cuenta de Salesforce a Heroku Connect. Luego, seleccioné el objeto Cuenta para sincronización. Una vez completado, pude ver los mismos datos de la cuenta de Salesforce en Heroku Connect y en la base de datos subyacente de Postgres.


Desde una perspectiva SQL, lo que hice resultó en la creación de una tabla salesforce.account con el siguiente diseño:


 create table salesforce.account ( createddate timestamp, isdeleted boolean, name varchar(255), systemmodstamp timestamp, accountnumber varchar(40), industry varchar(255), sfid varchar(18), id serial primary key, _hc_lastop varchar(32), _hc_err text );


Generar vectores

Para que la búsqueda de similitudes funcionara como se esperaba, necesitaba generar vectores de palabras para cada industria de cuentas de Salesforce:


  • Vestir
  • Bancario
  • Biotecnología
  • Construcción
  • Educación
  • Electrónica
  • Ingeniería
  • Entretenimiento
  • Bebida alimenticia
  • Finanzas
  • Gobierno
  • Cuidado de la salud
  • Hospitalidad
  • Seguro
  • Medios de comunicación
  • Sin fines de lucro
  • Otro
  • Recreación
  • Minorista
  • Envío
  • Tecnología
  • Telecomunicaciones
  • Transporte
  • Utilidades


Dado que el caso de uso principal indicaba la necesidad de encontrar similitudes para la industria del software, también necesitaríamos generar un vector de palabras para esa industria.


Para simplificar las cosas en este ejercicio, ejecuté esta tarea manualmente usando Python 3.9 y un archivo llamado embed.py , que se ve así:


 from wikipedia2vec import Wikipedia2Vec wiki2vec = Wikipedia2Vec.load('enwiki_20180420_100d.pkl') print(wiki2vec.get_word_vector('software').tolist())


Tenga en cuenta que el método get_word_vector() espera una representación en minúsculas de la industria.


La ejecución de python embed.py generó el siguiente vector de palabras para la palabra software :


 [-0.40402618050575256, 0.5711150765419006, -0.7885153293609619, -0.15960034728050232, -0.5692323446273804, 0.005377458408474922, -0.1315757781267166, -0.16840921342372894, 0.6626015305519104, -0.26056772470474243, 0.3681095242500305, -0.453583300113678, 0.004738557618111372, -0.4111144244670868, -0.1817493587732315, -0.9268549680709839, 0.07973367720842361, -0.17835664749145508, -0.2949991524219513, -0.5533796548843384, 0.04348105192184448, -0.028855713084340096, -0.13867013156414032, -0.6649054884910583, 0.03129105269908905, -0.24817068874835968, 0.05968991294503212, -0.24743635952472687, 0.20582349598407745, 0.6240783929824829, 0.3214546740055084, -0.14210252463817596, 0.3178422152996063, 0.7693028450012207, 0.2426985204219818, -0.6515568494796753, -0.2868216037750244, 0.3189859390258789, 0.5168254971504211, 0.11008890718221664, 0.3537853956222534, -0.713259220123291, -0.4132286608219147, -0.026366405189037323, 0.003034653142094612, -0.5275223851203918, -0.018167126923799515, 0.23878540098667145, -0.6077089905738831, 0.5368344187736511, -0.1210874393582344, 0.26415619254112244, -0.3066694438457489, 0.1471938043832779, 0.04954215884208679, 0.2045321762561798, 0.1391817331314087, 0.5286830067634583, 0.5764685273170471, 0.1882934868335724, -0.30167853832244873, -0.2122340053319931, -0.45651525259017944, -0.016777794808149338, 0.45624101161956787, -0.0438646525144577, -0.992512047290802, -0.3771328926086426, 0.04916151612997055, -0.5830298066139221, -0.01255014631897211, 0.21600870788097382, -0.18419665098190308, 0.1754663586616516, -0.1499166339635849, -0.1916201263666153, -0.22884036600589752, 0.17280352115631104, 0.25274306535720825, 0.3511175513267517, -0.20270302891731262, -0.6383468508720398, 0.43260180950164795, -0.21136239171028137, -0.05920517444610596, 0.7145522832870483, 0.7626600861549377, -0.5473887920379639, 0.4523043632507324, -0.1723199188709259, -0.10209759324789047, -0.5577948093414307, -0.10156919807195663, 0.31126976013183594, 0.3604489266872406, -0.13295558094978333, 0.2473849356174469, 0.278846800327301, -0.28618067502975464, 0.00527254119515419]


Crear tabla para industrias

Para almacenar los vectores de palabras, necesitábamos agregar una tabla industries a la base de datos de Postgres usando el siguiente comando SQL:


 create table salesforce.industries ( name varchar not null constraint industries_pk primary key, embeddings vector(100) not null );


Con la tabla industries creada, insertaremos cada uno de los vectores de palabras generados. Hacemos esto con declaraciones SQL similares a las siguientes:


 INSERT INTO salesforce.industries (name, embeddings) VALUES ('Software','[-0.40402618050575256, 0.5711150765419006, -0.7885153293609619, -0.15960034728050232, -0.5692323446273804, 0.005377458408474922, -0.1315757781267166, -0.16840921342372894, 0.6626015305519104, -0.26056772470474243, 0.3681095242500305, -0.453583300113678, 0.004738557618111372, -0.4111144244670868, -0.1817493587732315, -0.9268549680709839, 0.07973367720842361, -0.17835664749145508, -0.2949991524219513, -0.5533796548843384, 0.04348105192184448, -0.028855713084340096, -0.13867013156414032, -0.6649054884910583, 0.03129105269908905, -0.24817068874835968, 0.05968991294503212, -0.24743635952472687, 0.20582349598407745, 0.6240783929824829, 0.3214546740055084, -0.14210252463817596, 0.3178422152996063, 0.7693028450012207, 0.2426985204219818, -0.6515568494796753, -0.2868216037750244, 0.3189859390258789, 0.5168254971504211, 0.11008890718221664, 0.3537853956222534, -0.713259220123291, -0.4132286608219147, -0.026366405189037323, 0.003034653142094612, -0.5275223851203918, -0.018167126923799515, 0.23878540098667145, -0.6077089905738831, 0.5368344187736511, -0.1210874393582344, 0.26415619254112244, -0.3066694438457489, 0.1471938043832779, 0.04954215884208679, 0.2045321762561798, 0.1391817331314087, 0.5286830067634583, 0.5764685273170471, 0.1882934868335724, -0.30167853832244873, -0.2122340053319931, -0.45651525259017944, -0.016777794808149338, 0.45624101161956787, -0.0438646525144577, -0.992512047290802, -0.3771328926086426, 0.04916151612997055, -0.5830298066139221, -0.01255014631897211, 0.21600870788097382, -0.18419665098190308, 0.1754663586616516, -0.1499166339635849, -0.1916201263666153, -0.22884036600589752, 0.17280352115631104, 0.25274306535720825, 0.3511175513267517, -0.20270302891731262, -0.6383468508720398, 0.43260180950164795, -0.21136239171028137, -0.05920517444610596, 0.7145522832870483, 0.7626600861549377, -0.5473887920379639, 0.4523043632507324, -0.1723199188709259, -0.10209759324789047, -0.5577948093414307, -0.10156919807195663, 0.31126976013183594, 0.3604489266872406, -0.13295558094978333, 0.2473849356174469, 0.278846800327301, -0.28618067502975464, 0.00527254119515419] ');


Tenga en cuenta que, si bien creamos un vector de palabras con la representación en minúsculas de la industria del software (software), la columna industries.name debe coincidir con el nombre de la industria en mayúscula (Software).


Una vez que todos los vectores de palabras generados se hayan agregado a la tabla industries , podemos cambiar nuestro enfoque para introducir una API RESTful.

Introducir un servicio Spring Boot

Este fue el punto en el que mi pasión como ingeniero de software se aceleró porque tenía todo en su lugar para resolver el desafío en cuestión.


A continuación, utilizando Spring Boot 3.2.2 y Java (temurin) 17, creé el proyecto similarity-search-sfdc en IntelliJ IDEA con las siguientes dependencias de Maven:


 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.pgvector</groupId> <artifactId>pgvector</artifactId> <version>0.1.4</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>


Creé entidades simplificadas tanto para el objeto Cuenta como para el objeto Industria (incrustación), que se alinearon con las tablas de la base de datos de Postgres creadas anteriormente.


 @AllArgsConstructor @NoArgsConstructor @Data @Entity @Table(name = "account", schema = "salesforce") public class Account { @Id @Column(name = "sfid") private String id; private String name; private String industry; } @AllArgsConstructor @NoArgsConstructor @Data @Entity @Table(name = "industries", schema = "salesforce") public class Industry { @Id private String name; }


Usando la interfaz JpaRepository, agregué las siguientes extensiones para permitir un fácil acceso a las tablas de Postgres:


 public interface AccountsRepository extends JpaRepository<Account, String> { @Query(nativeQuery = true, value = "SELECT sfid, name, industry " + "FROM salesforce.account " + "WHERE industry IN (SELECT name " + " FROM salesforce.industries " + " WHERE name != :industry " + " ORDER BY embeddings <-> (SELECT embeddings FROM salesforce.industries WHERE name = :industry) " + " LIMIT :limit)" + "ORDER BY name") Set<Account> findSimilaritiesForIndustry(String industry, int limit); } public interface IndustriesRepository extends JpaRepository<Industry, String> { }


Observe que el método findSimilaritiesForIndustry() es donde se realizará todo el trabajo pesado para resolver este caso de uso. El método aceptará los siguientes parámetros:


  • industry : la industria para encontrar similitudes.
  • limit : el número máximo de similitudes de la industria para buscar al consultar cuentas


Tenga en cuenta el operador de distancia euclidiana (<->) en nuestra consulta anterior. Este es el operador integrado de la extensión para realizar la búsqueda de similitud.


Con el caso de uso original de la industria de "Software" y un límite establecido para las tres industrias más cercanas, la consulta que se ejecuta se vería así:


 SELECT sfid, name, industry FROM salesforce.account WHERE industry IN (SELECT name FROM salesforce.industries WHERE name != 'Software' ORDER BY embeddings <-> (SELECT embeddings FROM salesforce.industries WHERE name = 'Software') LIMIT 3) ORDER BY name;


A partir de ahí, construí la clase AccountsService para interactuar con los repositorios JPA:


 @RequiredArgsConstructor @Service public class AccountsService { private final AccountsRepository accountsRepository; private final IndustriesRepository industriesRepository; public Set<Account> getAccountsBySimilarIndustry(String industry, int limit) throws Exception { List<Industry> industries = industriesRepository.findAll(); if (industries .stream() .map(Industry::getName) .anyMatch(industry::equals)) { return accountsRepository .findSimilaritiesForIndustry(industry, limit); } else { throw new Exception( "Could not locate '" + industry + "' industry"); } } }


Por último, hice que la clase AccountsController proporcionara un punto de entrada RESTful y me conectara a AccountsService :


 @RequiredArgsConstructor @RestController @RequestMapping(value = "/accounts") public class AccountsController { private final AccountsService accountsService; @GetMapping(value = "/similarities") public ResponseEntity<Set<Account>> getAccountsBySimilarIndustry(@RequestParam String industry, @RequestParam int limit) { try { return new ResponseEntity<>( accountsService .getAccountsBySimilarIndustry(industry, limit), HttpStatus.OK); } catch (Exception e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } }


Implementar en Heroku

Con el servicio Spring Boot listo, agregué el siguiente Procfile al proyecto, permitiéndole a Heroku saber más sobre nuestro servicio:


 web: java $JAVA_OPTS -Dserver.port=$PORT -jar target/*.jar


Para estar seguro, agregué el archivo system.properties para especificar qué versiones de Java y Maven se esperan:


 java.runtime.version=17 maven.version=3.9.5


Usando la CLI de Heroku, agregué un control remoto a mi repositorio de GitLab para el servicio similarity-search-sfdc a la plataforma Heroku:


 heroku git:remote -a similarity-search-sfdc


También configuro el tipo de paquete de compilación para el servicio similarity-search-sfdc mediante el siguiente comando:


 heroku buildpacks:set https://github.com/heroku/heroku-buildpack-java


Finalmente, implementé el servicio similarity-search-sfdc en Heroku usando el siguiente comando:


 git push heroku


Ahora, la pestaña Recursos para la aplicación similarity-search-sfdc apareció como se muestra a continuación:


Búsqueda de similitudes en acción

Con la API RESTful en ejecución, emití el siguiente comando cURL para ubicar las tres principales industrias de Salesforce (y cuentas asociadas) más cercanas a la industria del software :


 curl --location 'https://HEROKU-APP-ROOT-URL/accounts/similarities?industry=Software&limit=3'


La API RESTful devuelve un estado de respuesta 200 OK HTTP junto con la siguiente carga útil:


 [ { "id": "001Kd00001bsP80IAE", "name": "CleanSlate Technology Group", "industry": "Technology" }, { "id": "001Kd00001bsPBFIA2", "name": "CMG Worldwide", "industry": "Media" }, { "id": "001Kd00001bsP8AIAU", "name": "Dev Spotlight", "industry": "Technology" }, { "id": "001Kd00001bsP8hIAE", "name": "Egghead", "industry": "Electronics" }, { "id": "001Kd00001bsP85IAE", "name": "Marqeta", "industry": "Technology" } ]


Como resultado, las industrias de tecnología , medios y electrónica son las industrias más cercanas a la industria del software en este ejemplo.


Ahora, el departamento de marketing tiene una lista de cuentas con las que pueden contactar para su próxima campaña.

Conclusión

Hace años, pasé más tiempo del que me gustaría admitir jugando al videojuego multijugador Team Fortress 2 . Aquí hay una captura de pantalla de un evento de 2012 que fue muy divertido:


Aquellos que estén familiarizados con este aspecto de mi vida podrán decirles que mi elección predeterminada de clase de jugador era el soldado. Esto se debe a que el soldado tiene el mejor equilibrio entre salud, movimiento, velocidad y potencia de fuego.


Siento que los ingenieros de software son la "clase de soldados" del mundo real porque podemos adaptarnos a cualquier situación y centrarnos en brindar soluciones que cumplan con las expectativas de manera eficiente.


Desde hace algunos años, me he centrado en la siguiente declaración de misión, que creo que puede aplicarse a cualquier profesional de TI:


“Concentre su tiempo en ofrecer características/funcionalidades que amplíen el valor de su propiedad intelectual. Aproveche los marcos, productos y servicios para todo lo demás”.

- J. Vester


En el ejemplo de esta publicación, pudimos aprovechar Heroku Connect para sincronizar datos empresariales con una base de datos de Postgres. Después de instalar la extensión pgvector, creamos vectores de palabras para cada industria única a partir de esas cuentas de Salesforce. Finalmente, introdujimos un servicio Spring Boot, que simplificó el proceso de localizar cuentas de Salesforce cuya industria estaba más cercana a otra industria.


Resolvimos este caso de uso rápidamente con tecnologías de código abierto existentes, la adición de un pequeño servicio Spring Boot y Heroku PaaS, cumpliendo plenamente con mi declaración de misión. No puedo imaginar cuánto tiempo se necesitaría sin estos marcos, productos y servicios.


Si está interesado, puede encontrar el código fuente original de este artículo en GitLab:


https://gitlab.com/johnjvester/similarity-search-sfdc


¡Que tengas un gran día!