paint-brush
Usando pgvector para localizar semelhanças em dados corporativospor@johnjvester
805 leituras
805 leituras

Usando pgvector para localizar semelhanças em dados corporativos

por John Vester14m2024/03/21
Read on Terminal Reader

Muito longo; Para ler

Aproveite o poder dos vetores de palavras e das pesquisas por similaridade para ajudar a descobrir correlações de dados com dados corporativos para auxiliar em uma campanha de marketing.
featured image - Usando pgvector para localizar semelhanças em dados corporativos
John Vester HackerNoon profile picture
0-item
1-item


Os engenheiros de software ocupam um lugar emocionante neste mundo. Independentemente da pilha de tecnologia ou da indústria, temos a tarefa de resolver problemas que contribuam diretamente para as metas e objetivos dos nossos empregadores. Como bônus, podemos usar a tecnologia para mitigar quaisquer desafios que surjam em nossa mira.


Neste exemplo, eu queria me concentrar em como o pgvector – uma pesquisa de similaridade vetorial de código aberto para Postgres – pode ser usado para identificar semelhanças de dados que existem em dados corporativos.

Um caso de uso simples

Como exemplo simples, vamos supor que o departamento de marketing precise de assistência para uma campanha que planeja lançar. O objetivo é alcançar todas as contas do Salesforce que estão em setores alinhados com o setor de software.


No final, eles gostariam de se concentrar nas contas dos três principais setores mais semelhantes, com a capacidade de usar esta ferramenta no futuro para encontrar semelhanças para outros setores. Se possível, eles gostariam de ter a opção de fornecer o número desejado de setores correspondentes, em vez de sempre retornar os três primeiros.

Design de alto nível

Este caso de uso gira em torno da realização de uma pesquisa de similaridade. Embora seja possível concluir este exercício manualmente, a ferramenta Wikipedia2Vec vem à mente por causa dos embeddings pré-treinados que já foram criados para vários idiomas. Incorporações de palavras – também conhecidas como vetores – são representações numéricas de palavras que contêm informações sintáticas e semânticas. Ao representar palavras como vetores, podemos determinar matematicamente quais palavras estão semanticamente “mais próximas” de outras.


Em nosso exemplo, também poderíamos ter escrito um programa Python simples para criar vetores de palavras para cada setor configurado no Salesforce.


A extensão pgvector requer um banco de dados Postgres. No entanto, os dados empresariais do nosso exemplo residem atualmente no Salesforce. Felizmente, o Heroku Connect oferece uma maneira fácil de sincronizar as contas do Salesforce com o Heroku Postgres, armazenando-as em uma tabela chamada salesforce.account . Então, teremos outra tabela chamada salesforce.industries que contém cada setor no Salesforce (como uma chave VARCHAR), junto com seu vetor de palavras associado.


Com os dados do Salesforce e vetores de palavras no Postgres, criaremos uma API RESTful usando Java e Spring Boot. Este serviço realizará a consulta necessária e retornará os resultados no formato JSON.


Podemos ilustrar a visão de alto nível da solução assim:


O código-fonte residirá no GitLab. A emissão de um comando git push heroku acionará uma implantação no Heroku, introduzindo uma API RESTful que a equipe de marketing pode consumir facilmente.

Construindo a solução

Com o design de alto nível implementado, podemos começar a construir uma solução. Usando meu login do Salesforce, consegui navegar até a tela Contas para visualizar os dados deste exercício. Aqui está um exemplo da primeira página de dados corporativos:


Crie um aplicativo Heroku

Para esse esforço, planejei usar o Heroku para solucionar a solicitação da equipe de marketing. Entrei em minha conta Heroku e usei o botão Criar novo aplicativo para estabelecer um novo aplicativo chamado similarity-search-sfdc :


Depois de criar o aplicativo, naveguei até a guia Recursos para encontrar o complemento Heroku Postgres. Digitei “Postgres” no campo de pesquisa de complementos.


Depois de selecionar Heroku Postgres na lista, escolhi o plano Standard 0 , mas o pgvector está disponível em ofertas de banco de dados de nível Standard (ou superior) executando PostgreSQL 15 ou o banco de dados beta de nível Essential .


Quando confirmei o complemento, o Heroku gerou e forneceu uma string de conexão DATABASE_URL . Encontrei isso na seção Config Vars da guia Configurações do meu aplicativo. Usei essas informações para conectar ao meu banco de dados e habilitar a extensão pgvector assim:


 CREATE EXTENSION vector;


Em seguida, procurei e encontrei o complemento Heroku Connect . Eu sabia que isso me daria uma maneira fácil de me conectar aos dados corporativos no Salesforce.


Para este exercício, o plano Demo Edition gratuito funciona perfeitamente.


Neste ponto, a guia Recursos do aplicativo similarity-search-sfdc tinha esta aparência:


Segui as instruções de “ Configurando o Heroku Connect ” para vincular minha conta do Salesforce ao Heroku Connect. Em seguida, selecionei o objeto Conta para sincronização. Depois de concluído, consegui ver os mesmos dados da conta do Salesforce no Heroku Connect e no banco de dados Postgres subjacente.


Do ponto de vista SQL, o que fiz resultou na criação de uma tabela salesforce.account com o seguinte design:


 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 );


Gerar vetores

Para que a pesquisa por similaridade funcionasse conforme o esperado, precisei gerar vetores de palavras para cada setor de contas do Salesforce:


  • Vestuário
  • Bancário
  • Biotecnologia
  • Construção
  • Educação
  • Eletrônicos
  • Engenharia
  • Entretenimento
  • Comida & Bebida
  • Finança
  • Governo
  • Assistência médica
  • Hospitalidade
  • Seguro
  • meios de comunicação
  • Sem fins lucrativos
  • Outro
  • Lazer
  • Varejo
  • Envio
  • Tecnologia
  • Telecomunicações
  • Transporte
  • Serviços de utilidade pública


Como o caso de uso principal indicava a necessidade de encontrar semelhanças para a indústria de software, também precisaríamos gerar um vetor de palavras para essa indústria.


Para simplificar as coisas neste exercício, executei manualmente esta tarefa usando Python 3.9 e um arquivo chamado embed.py , que se parece com isto:


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


Observe – o método get_word_vector() espera uma representação da indústria em letras minúsculas.


A execução de python embed.py gerou o seguinte vetor de palavras para a palavra 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]


Criar Tabela para Indústrias

Para armazenar os vetores de palavras, precisávamos adicionar uma tabela industries ao banco de dados Postgres usando o seguinte comando SQL:


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


Com a tabela industries criada, inseriremos cada um dos vetores de palavras gerados. Fazemos isso com instruções SQL semelhantes às seguintes:


 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] ');


Observe – embora tenhamos criado um vetor de palavras com a representação em letras minúsculas da indústria de software (software), a coluna industries.name precisa corresponder ao nome da indústria em letras maiúsculas (software).


Depois que todos os vetores de palavras gerados forem adicionados à tabela industries , podemos mudar nosso foco para a introdução de uma API RESTful.

Apresente um serviço Spring Boot

Foi nesse ponto que minha paixão como engenheiro de software acelerou porque eu tinha tudo pronto para resolver o desafio em questão.


Em seguida, usando Spring Boot 3.2.2 e Java (temurin) 17, criei o projeto similarity-search-sfdc no IntelliJ IDEA com as seguintes dependências do 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>


Criei entidades simplificadas para o objeto Conta e para o objeto Indústria (incorporação), que se alinhavam com as tabelas do banco de dados Postgres criadas 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 a interface JpaRepository, adicionei as seguintes extensões para permitir fácil acesso às tabelas do 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 o método findSimilaritiesForIndustry() é onde todo o trabalho pesado ocorrerá para resolver este caso de uso. O método aceitará os seguintes parâmetros:


  • industry : a indústria para encontrar semelhanças para
  • limit : o número máximo de semelhanças do setor a serem pesquisadas ao consultar contas


Observe o operador de distância euclidiana (<->) em nossa consulta acima. Este é o operador integrado da extensão para realizar a pesquisa de similaridade.


Com o caso de uso original do setor de “Software” e um limite definido para os três setores mais próximos, a consulta executada ficaria assim:


 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 daí, construí a classe AccountsService para interagir com os repositórios 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, fiz com que a classe AccountsController fornecesse um ponto de entrada RESTful e conectasse ao 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); } } }


Implantar no Heroku

Com o serviço Spring Boot pronto, adicionei o seguinte Procfile ao projeto, informando ao Heroku mais sobre nosso serviço:


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


Por segurança, adicionei o arquivo system.properties para especificar quais versões de Java e Maven são esperadas:


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


Usando o Heroku CLI, adicionei um controle remoto ao meu repositório GitLab para o serviço similarity-search-sfdc à plataforma Heroku:


 heroku git:remote -a similarity-search-sfdc


Também defini o tipo de buildpack para o serviço similarity-search-sfdc por meio do seguinte comando:


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


Por fim, implantei o serviço similarity-search-sfdc no Heroku usando o seguinte comando:


 git push heroku


Agora, a guia Recursos do aplicativo similarity-search-sfdc apareceu conforme mostrado abaixo:


Pesquisa de similaridade em ação

Com a API RESTful em execução, emiti o seguinte comando cURL para localizar os três principais setores do Salesforce (e contas associadas) mais próximos do setor de software :


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


A API RESTful retorna um status de resposta 200 OK HTTP junto com a seguinte carga:


 [ { "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, as indústrias de Tecnologia , Mídia e Eletrônica são as indústrias mais próximas da indústria de Software neste exemplo.


Agora, o departamento de marketing tem uma lista de contas com as quais pode entrar em contato para a próxima campanha.

Conclusão

Anos atrás, passei mais tempo do que gostaria de admitir jogando o videogame multijogador Team Fortress 2 . Aqui está uma captura de tela de um evento em 2012 que foi muito divertido:


Aqueles familiarizados com esse aspecto da minha vida poderiam dizer que minha escolha padrão de classe de jogador era o soldado. Isso ocorre porque o soldado tem o melhor equilíbrio entre saúde, movimento, velocidade e poder de fogo.


Sinto que os engenheiros de software são a “classe dos soldados” do mundo real porque podemos nos adaptar a qualquer situação e nos concentrar em fornecer soluções que atendam às expectativas de maneira eficiente.


Há alguns anos, tenho me concentrado na seguinte declaração de missão, que acredito poder ser aplicada a qualquer profissional de TI:


“Concentre seu tempo em fornecer recursos/funcionalidades que ampliem o valor de sua propriedade intelectual. Aproveite estruturas, produtos e serviços para todo o resto.”

- J. Vester


No exemplo desta postagem, conseguimos aproveitar o Heroku Connect para sincronizar dados corporativos com um banco de dados Postgres. Depois de instalar a extensão pgvector, criamos vetores de palavras para cada setor exclusivo dessas contas do Salesforce. Por fim, introduzimos um serviço Spring Boot, que simplificou o processo de localização de contas do Salesforce cujo setor estava mais próximo de outro setor.


Resolvemos esse caso de uso rapidamente com tecnologias de código aberto existentes, a adição de um pequeno serviço Spring Boot e o Heroku PaaS – aderindo totalmente à minha declaração de missão. Não consigo imaginar quanto tempo seria necessário sem essas estruturas, produtos e serviços.


Se estiver interessado, você pode encontrar o código-fonte original deste artigo no GitLab:


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


Tenha um ótimo dia!