paint-brush
使用 pgvector 查找企业数据中的相似之处经过@johnjvester
805 讀數
805 讀數

使用 pgvector 查找企业数据中的相似之处

经过 John Vester14m2024/03/21
Read on Terminal Reader

太長; 讀書

利用词向量和相似性搜索的力量来帮助发现数据与企业数据的相关性,以协助开展营销活动。
featured image - 使用 pgvector 查找企业数据中的相似之处
John Vester HackerNoon profile picture
0-item
1-item


软件工程师在这个世界上占据着令人兴奋的地位。无论技术堆栈或行业如何,我们的任务都是解决直接有助于实现雇主目标的问题。作为奖励,我们可以使用技术来缓解我们遇到的任何挑战。


对于此示例,我想重点介绍pgvector (Postgres 的开源矢量相似性搜索)如何用于识别企业数据中存在的数据相似性。

一个简单的用例

举一个简单的例子,我们假设营销部门需要为他们计划发起的活动提供帮助。目标是覆盖与软件行业密切相关的行业中的所有Salesforce帐户。


最后,他们希望重点关注前三个最相似行业的账户,以便将来能够使用此工具来查找其他行业的相似之处。如果可能的话,他们希望能够选择提供所需数量的匹配行业,而不是总是返回前三名。

高层设计

该用例以执行相似性搜索为中心。虽然可以手动完成此练习,但由于已经为多种语言创建了预先训练的嵌入,因此我想到了Wikipedia2Vec工具。词嵌入(也称为向量)是包含句法和语义信息的词的数字表示。通过将单词表示为向量,我们可以从数学上确定哪些单词在语义上与其他单词“更接近”。


在我们的示例中,我们还可以编写一个简单的 Python 程序来为 Salesforce 中配置的每个行业创建词向量。


pgvector扩展需要 Postgres 数据库。但是,我们示例的企业数据当前驻留在 Salesforce 中。幸运的是, Heroku Connect提供了一种将 Salesforce 帐户与 Heroku Postgres 同步的简单方法,将其存储在名为salesforce.account的表中。然后,我们将有另一个名为salesforce.industries的表,其中包含 Salesforce 中的每个行业(作为 VARCHAR 键)及其关联的词向量。


借助 Postgres 中的 Salesforce 数据和词向量,我们将使用 Java 和 Spring Boot 创建 RESTful API。该服务将执行必要的查询并以 JSON 格式返回结果。


我们可以这样说明该解决方案的高级视图:


源代码将驻留在 GitLab 中。发出git push heroku命令将触发 Heroku 中的部署,引入营销团队可以轻松使用的 RESTful API。

构建解决方案

高层设计到位后,我们就可以开始构建解决方案了。使用我的 Salesforce 登录名,我能够导航到“帐户”屏幕以查看此练习的数据。以下是企业数据首页的示例:


创建 Heroku 应用程序

为此,我计划使用 Heroku 来解决营销团队的请求。我登录到 Heroku 帐户并使用“创建新应用程序”按钮建立一个名为similarity-search-sfdc的新应用程序:


创建应用程序后,我导航到“资源”选项卡以查找 Heroku Postgres 插件。我在附加组件搜索字段中输入“Postgres”。


从列表中选择Heroku Postgres后,我选择了Standard 0计划,但pgvector 可在运行 PostgreSQL 15 或 beta Essential 层数据库的标准层(或更高)数据库产品上使用


当我确认该附加组件时,Heroku 生成并提供了DATABASE_URL连接字符串。我在应用程序的“设置选项卡的“配置变量”部分中找到了它。我使用此信息连接到我的数据库并启用 pgvector 扩展,如下所示:


 CREATE EXTENSION vector;


接下来,我搜索并找到了Heroku Connect插件。我知道这将为我提供一种连接到 Salesforce 中的企业数据的简单方法。


对于本练习,免费的演示版计划效果很好。


此时, similarity-search-sfdc应用程序的“资源”选项卡如下所示:


我按照“设置 Heroku Connect ”说明将我的 Salesforce 帐户链接到 Heroku Connect。然后,我选择了帐户对象进行同步。完成后,我能够在 Heroku Connect 和底层 Postgres 数据库中看到相同的 Salesforce 帐户数据。


从 SQL 角度来看,我所做的结果是创建了一个具有以下设计的salesforce.account表:


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


生成向量

为了使相似性搜索按预期发挥作用,我需要为每个 Salesforce 帐户行业生成词向量:


  • 服饰
  • 银行业
  • 生物技术
  • 建造
  • 教育
  • 电子产品
  • 工程
  • 娱乐
  • 食品与饮料
  • 金融
  • 政府
  • 卫生保健
  • 款待
  • 保险
  • 媒体
  • 不以营利为目的
  • 其他
  • 娱乐
  • 零售
  • 船运
  • 技术
  • 电信
  • 运输
  • 公用事业


由于主要用例表明需要找到软件行业的相似之处,因此我们也需要为该行业生成一个词向量。


为了使本练习保持简单,我使用 Python 3.9 和一个名为embed.py的文件手动执行此任务,如下所示:


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


请注意 – get_word_vector()方法需要行业的小写表示。


运行 python embed.pysoftware单词生成以下词向量:


 [-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]


为行业创建表

为了存储词向量,我们需要使用以下 SQL 命令向 Postgres 数据库添加industries表:


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


创建industries表后,我们将插入每个生成的词向量。我们使用类似于以下的 SQL 语句来执行此操作:


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


请注意 - 虽然我们使用软件行业(软件)的小写表示创建了词向量,但 Industries.name industries.name需要与大写的行业名称(软件)相匹配。


一旦所有生成的词向量都添加到industries表中,我们就可以将重点转向引入 RESTful API。

引入Spring Boot服务

这就是我作为软件工程师的热情爆发的时刻,因为我已经准备好了解决手头挑战的一切。


接下来,使用 Spring Boot 3.2.2 和 Java (temurin) 17,我在 IntelliJ IDEA 中创建了similarity-search-sfdc项目,并具有以下 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>


我为Account对象和Industry (嵌入)对象创建了简化的实体,它们与之前创建的 Postgres 数据库表对齐。


 @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; }


使用 JpaRepository 接口,我添加了以下扩展,以便轻松访问 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> { }


请注意, findSimilaritiesForIndustry()方法是解决此用例的所有繁重工作的地方。该方法将接受以下参数:


  • industry :寻找相似之处的行业
  • limit :查询帐户时要搜索的行业相似性的最大数量


请注意上面查询中的欧几里得距离运算符 (<->)。这是扩展的内置运算符,用于执行相似性搜索。


使用原始的“软件”行业用例并将限制设置为三个最接近的行业,执行的查询将如下所示:


 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;


从那里,我构建了AccountsService类来与 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"); } } }


最后,我让AccountsController类提供 RESTful 入口点并连接到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); } } }


部署到 Heroku

准备好 Spring Boot 服务后,我将以下Procfile添加到项目中,让 Heroku 了解更多有关我们服务的信息:


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


为了安全起见,我添加了system.properties文件来指定所需的 Java 和 Maven 版本:


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


使用 Heroku CLI,我将一个远程添加到我的 GitLab 存储库,以便为 Heroku 平台提供similarity-search-sfdc服务:


 heroku git:remote -a similarity-search-sfdc


我还通过以下命令设置similarity-search-sfdc服务的构建包类型:


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


最后,我使用以下命令将similarity-search-sfdc服务部署到 Heroku:


 git push heroku


现在, similarity-search-sfdc应用程序的“资源”选项卡出现,如下所示:


相似性搜索的实际应用

在 RESTful API 运行的情况下,我发出以下 cURL 命令来定位最接近软件行业的前三个 Salesforce 行业(以及关联帐户):


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


RESTful API 返回200 OK HTTP响应状态以及以下有效负载:


 [ { "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" } ]


因此,在此示例中,技术媒体电子行业是与软件行业最接近的行业。


现在,营销部门有了一份可以联系以开展下一次营销活动的客户列表。

结论

多年前,我花在《军团要塞 2》多人视频游戏上的时间比我愿意承认的还要多。这是 2012 年一次非常有趣的活动的屏幕截图:


那些熟悉我生活这方面的人可能会告诉你,我默认选择的玩家职业是士兵。这是因为士兵拥有健康、移动、速度和火力的最佳平衡。


我觉得软件工程师就是现实世界中的“士兵阶层”,因为我们可以适应任何情况,专注于高效地提供满足期望的解决方案。


几年来,我一直专注于以下使命宣言,我认为它可以适用于任何 IT 专业人员:


“将时间集中在提供可扩展知识产权价值的特性/功能上。利用框架、产品和服务来完成其他一切。”

- J.维斯特


在本文的示例中,我们能够利用 Heroku Connect 将企业数据与 Postgres 数据库同步。安装 pgvector 扩展后,我们从这些 Salesforce 帐户中为每个独特的行业创建了词向量。最后,我们引入了 Spring Boot 服务,该服务简化了查找行业与另一个行业最接近的 Salesforce 帐户的过程。


我们利用现有的开源技术、添加的小型 Spring Boot 服务和 Heroku PaaS 快速解决了这个用例——完全遵循我的使命宣言。我无法想象如果没有这些框架、产品和服务,需要多少时间。


如果您有兴趣,可以在 GitLab 上找到本文的原始源代码:


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


祝你有美好的一天!