实体解析实施的复杂性

                                                实体的艺术表现斯特凡·伯克纳

一、说明

        实体解析是确定数据集中的两条或多条记录是否引用同一现实世界实体(通常是个人或公司)的过程。乍一看,实体分辨率可能看起来像一个相对简单的任务:例如,给定一张人物的两张照片,即使是一个小孩子也可以确定它是否以相当高的精度显示同一个人。计算机也是如此:比较包含姓名、地址、电子邮件等属性的两条记录可以很容易地完成。然而,深入探讨该主题,它就越具有挑战性:需要评估各种匹配算法,处理数百万或数十亿条记录意味着二次复杂性,更不用说实时和数据删除用例了。

二、模糊文本匹配

        让我们从比较著名艺术家文森特梵高的两张唱片开始——还是梵高?

        第二条记录中有一些错误(除了一个世纪后出生和电子邮件地址):姓名拼写错误,出生日期混淆,邮政编码丢失,电子邮件地址略有不同。

        那么我们如何比较这些值呢?如果,假设名称相等,那么对这些值进行简单的字符串比较就足够了。由于情况并非如此,我们需要一些更高级的模糊匹配。有许多不同的算法可用于基于文本的模糊匹配,它们可以大致分为三组。语音算法侧重于文本的发音相似程度。最著名的算法是Soundex和Metaphone,它们主要用于英语文本,但其他语言也存在这些算法的变体,例如德语的Kölner Phonetik(科隆语音)。文本距离算法通常定义文本需要更改多少个字符才能到达其他文本。Levenshtein和Hamming距离是该组中两个众所周知的算法。相似性算法,如余弦相似性或杰卡德指数,计算文本的结构相似性,通常以百分比表示相似性。

        出于本文的目的,我们将使用一种非常简单的方法,仅使用名称上的Levenshtein距离和城市的相等性。此示例和以下所有示例将使用 golang 作为编程语言,并尽可能使用现有库。将其转换为python,java或任何其他语言应该是微不足道的。此外,它只会对 name 属性执行匹配。添加更多属性甚至使其可配置不是本文的目的。

package mainimport ("fmt""github.com/hbollon/go-edlib"
)type Record struct {ID intName stringCity string
}func matches(a, b Record) bool {distance := edlib.LevenshteinDistance(a.Name, b.Name)return distance <= 3 && a.City == b.City
}func main() {a := Record{Name: "Vincent Van Gogh",City: "Paris",}b := Record{Name: "Vince Van Gough",City: "Paris",}if matches(a, b) {fmt.Printf("%s and %s are probably the same person\n", a.Name, b.Name)} else {fmt.Printf("%s and %s are probably not the same person\n", a.Name, b.Name)}
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        两个名字之间的列文森距离正好是3。这是因为,还有三个附加字符(名字中的“en”和姓氏中的“u”)。请注意,这适用于此特定输入。然而,它离完美还很远。例如,“Joe Smith”和“Amy Smith”这两个名字的Levenshtein距离也是三个,但显然不是同一个人。将距离算法与语音算法相结合可以解决这个问题,但这超出了本文的范围。

        使用基于规则的方法而不是基于 ML 的方法时,选择为您的使用案例产生最佳结果的正确算法是业务成功的最关键方面。这是您应该花费大部分时间的地方。不幸的是,正如我们现在将发现的那样,如果您决定自己开发实体解析引擎,还有很多其他事情会分散您优化这些规则的注意力。

三、朴素实体解析

        现在我们知道了如何比较两个记录,我们需要找到彼此匹配的所有记录。最简单的方法是简单地将每条记录与所有其他记录进行比较。出于此示例的目的,我们使用随机选择的名称和城市。对于名称,我们最多强制三个错误(用 x 替换任何字符)。

var firstNames = [...]string{"Wade", "Dave", "Seth", "Ivan", "Riley", "Gilbert", "Jorge", "Dan", "Brian", "Roberto", "Daisy", "Deborah", "Isabel", "Stella", "Debra", "Berverly", "Vera", "Angela", "Lucy", "Lauren"}
var lastNames = [...]string{"Smith", "Jones", "Williams", "Brown", "Taylor"}func randomName() string {fn := firstNames[rand.Intn(len(firstNames))]ln := lastNames[rand.Intn(len(lastNames))]name := []byte(fmt.Sprintf("%s %s", fn, ln))errors := rand.Intn(4)for i := 0; i < errors; i++ {name[rand.Intn(len(name))] = 'x'}return string(name)
}var cities = [...]string{"Paris", "Berlin", "New York", "Amsterdam", "Shanghai", "San Francisco", "Sydney", "Cape Town", "Brasilia", "Cairo"}func randomCity() string {return cities[rand.Intn(len(cities))]
}func loadRecords(n int) []Record {records := make([]Record, n)for i := 0; i < n; i++ {records[i] = Record{ID:   i,Name: randomName(),City: randomCity(),}}return records
}func compare(records []Record) (comparisons, matchCount int) {for _, a := range records {for _, b := range records {if a == b {continue // don't compare with itself}comparisons++if matches(a, b) {fmt.Printf("%s and %s are probably the same person\n", a.Name, b.Name)matchCount++}}}return comparisons, matchCount
}func main() {records := loadRecords(100)comparisons, matchCount := compare(records)fmt.Printf("made %d comparisons and found %d matches\n", comparisons, matchCount)
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        您应该看到一些类似的输出(如果您没有得到随机数据的任何匹配项,您可能需要多次运行它):

Daisy Williams and Dave Williams are probably the same person
Deborax Browx and Debra Brown are probably the same person
Riley Brown and RxxeyxBrown are probably the same person
Dan Willxams and Dave Williams are probably the same person
made 9900 comparisons and found 16 matches

        如果幸运的话,您还会得到像“黛西”和“戴夫”这样的不匹配。这是因为我们使用的 Levenshtein 距离为 3,作为短名称的唯一模糊算法,这是高的方式。请随时自行改进。

        性能方面,真正有问题的一点是获得结果所需的 9,900 次比较,因为输入量加倍将大约使所需比较量翻两番。39 条记录需要 800,200 次比较。对于只有 100,000 条记录的少量数据,这意味着需要近 10 亿次比较。无论您的系统有多大,随着数据量的增长,系统都将无法在可接受的时间内完成此操作。

        一个快速但几乎无用的优化是不对每个组合进行两次比较。我们将 A 与 B 进行比较或将 B 与 A 进行比较应该无关紧要。然而,这只会减少因子 2 所需的比较量,由于二次增长,这是可以忽略的。

四、通过阻塞降低复杂性

        如果我们查看我们创建的规则,我们很容易注意到,如果城市不同,我们将永远不会有匹配。所有这些比较都是完全浪费的,应该加以防止。将您怀疑相似的记录放入公共存储桶中,而将其他不相同的记录放入另一个存储桶中,在实体解析中称为阻塞。由于我们想使用城市作为我们的阻塞键,因此实现相当简单。

func block(records []Record) map[string][]Record {blocks := map[string][]Record{}for _, record := range records {blocks[record.City] = append(blocks[record.City], record)}return blocks
}func main() {records := loadRecords(100)blocks := block(records)comparisons := 0matchCount := 0for _, blockRecords := range blocks {c, m := compare(blockRecords)comparisons += cmatchCount += m}fmt.Printf("made %d comparisons and found %d matches\n", comparisons, matchCount)
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        现在的结果将是相同的,但我们只有大约十分之一的比较,因为我们有十个不同的城市。在实际应用中,由于城市的差异要大得多,这种影响会大得多。此外,每个块可以独立于其他块进行处理,例如在相同或不同的服务器上并行处理。

        找到正确的阻止密钥本身就是一个挑战。使用像城市这样的属性可能会导致分布不均匀,因此会导致一个巨大的区块(例如大城市)比所有其他区块花费更长的时间。或者城市包含微小的拼写错误,不再被视为有效匹配。使用多个属性和/或使用拼音键或 q-gram 作为阻塞键可以解决这些问题,但会增加软件的复杂性。

五、从匹配项到实体

        到目前为止,关于我们的记录,我们只能说,其中两个是否匹配。对于非常基本的用例,这可能已经足够了。但是,在大多数情况下,您想知道属于同一实体的所有匹配项。这可以从简单的星形模式,其中 A 与 B、C 和 D 匹配,到 A 匹配 B、B 匹配 C 和 C 匹配 D 的链状模式,到非常复杂的图形模式。这种所谓的传递记录链接可以使用连接的组件算法轻松实现,只要所有数据都适合单个服务器上的内存。同样,在实际应用中,这更具挑战性。

func compare(records []Record) (comparisons int, edges [][2]int) {for _, a := range records {for _, b := range records {if a == b {continue // don't compare with itself}comparisons++if matches(a, b) {edges = append(edges, [2]int{a.ID, b.ID})}}}return comparisons, edges
}func connectedComponents(edges [][2]int) [][]int {components := map[int][]int{}nextIdx := 0idx := map[int]int{}for _, edge := range edges {a := edge[0]b := edge[1]aIdx, aOk := idx[a]bIdx, bOk := idx[b]switch {case aOk && bOk && aIdx == bIdx: // in same componentcontinuecase aOk && bOk && aIdx != bIdx: // merge two componentscomponents[nextIdx] = append(components[aIdx], components[bIdx]...)delete(components, aIdx)delete(components, bIdx)for _, x := range components[nextIdx] {idx[x] = nextIdx}nextIdx++case aOk && !bOk: // add b to component of aidx[b] = aIdxcomponents[aIdx] = append(components[aIdx], b)case bOk && !aOk: // add a to component of bidx[a] = bIdxcomponents[bIdx] = append(components[bIdx], a)default: // create new component with a and bidx[a] = nextIdxidx[b] = nextIdxcomponents[nextIdx] = []int{a, b}nextIdx++}}cc := make([][]int, len(components))i := 0for k := range components {cc[i] = components[k]i++}return cc
}func main() {records := loadRecords(100)blocks := block(records)comparisons := 0edges := [][2]int{}for _, blockRecords := range blocks {c, e := compare(blockRecords)comparisons += cedges = append(edges, e...)}cc := connectedComponents(edges)fmt.Printf("made %d comparisons and found %d matches and %d entities\n", comparisons, len(edges), len(cc))for _, component := range cc {names := make([]string, len(component))for i, id := range component {names[i] = records[id].Name}fmt.Printf("found the following entity: %s from %s\n", strings.Join(names, ", "), records[component[0]].City)}
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        连接的组件功能遍历所有边,然后创建新组件、将新 id 添加到现有组件或将两个组件合并为一个组件。结果如下所示:

made 1052 comparisons and found 6 matches and 2 entities
found the following entity: Ivan Smxth, Ixan Smith, Ivax Smitx from Cairo
found the following entity: Brxan Williams, Brian Williams from Cape Town

        保持这些边缘给我们带来了一些优势。我们可以使用它们来使生成的实体易于理解和解释,理想情况下,即使有一个漂亮的 UI 来显示实体的记录是如何连接的。或者,在使用实时实体解析系统时,我们可以使用边缘在删除数据时拆分实体。或者,您在构建图神经网络 (GNN) 时使用它们,从而获得更好的 ML 结果,而不仅仅是记录。

实体的可视化表示(作者图片)

        当有很多非常相似的记录时,可能会出现一个来自边缘的问题。例如,如果 A 与 B 匹配,B 与 C 匹配,则 C 也可能与 A 匹配,具体取决于使用的规则。如果 D、E、F 等也与现有记录匹配,那么我们又回到了二次增长问题,很快导致如此多的边变得不再可处理。

        还记得我们是如何构建阻塞桶的吗?惊喜!对于非常相似的数据,这些数据最终都集中在几个巨大的桶中,计算性能再次急剧下降——即使您遵循了之前从多个属性创建桶的建议。

        这种不相同的重复项的典型示例是有人定期在同一家商店订购,但具有来宾访问权限(抱歉,没有很好的客户 ID)。该人可能几乎总是使用相同的送货地址,并且大多能够正确写下自己的名字。因此,应以特殊方式处理这些记录,以确保稳定的系统性能,但这本身就是一个主题。

        在你对所获得的知识感到太舒服并想开始实施自己的解决方案之前,让我快速粉碎你的梦想。我们还没有讨论实时执行任何操作的挑战。即使你认为你不需要一个总是最新的实体(显而易见的好处),实时方法也会产生进一步的价值:你不需要一遍又一遍地做同样的计算,而只需要对新数据。另一方面,实现起来要复杂得多。想要阻止?将新记录与其所属存储桶的所有记录进行比较,但这可能需要一段时间,并且可以被视为增量批处理。同样在最终完成之前,还有大量新记录等待处理。想要使用连接的组件计算实体?当然,将整个图形保留在内存中,只需添加新的边。但是不要忘记跟踪由于新记录而刚刚合并在一起的两个实体。

        因此,您仍然愿意自己实现这一点。你做出了(在这种情况下)明智的决定,不存储边缘,不支持实时。因此,您成功地运行了包含所有数据的第一个实体解析批处理作业。这花了一段时间,但你每个月只会这样做一次,所以这很好。当您看到您的数据保护官跑到拐角处并告诉您由于GDPR投诉而从数据集中删除该人时,可能就在那时。因此,您再次为单个已删除的实体运行整个批处理作业 — 耶。

六、结论

        进行实体解析乍一看可能相当简单,但它包含许多重大的技术挑战。其中一些可以简化和/或忽略,但其他问题需要解决以获得良好的性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/104169.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

natapp内网穿透-将本地运行的程序/服务器通过公网IP供其它人访问

文章目录 1.几个基本概念1.1 局域网1.2 内网1.3 内网穿透1.4 Natapp 2.搭建内网穿透环境3.本地服务测试 1.几个基本概念 1.1 局域网 LAN&#xff08;Local Area Network&#xff0c;局域网&#xff09;是一个可连接住宅&#xff0c;学校&#xff0c;实验室&#xff0c;大学校…

2核4G游戏服务器推荐(阿里云/腾讯云/华为云)

2核4G游戏服务器推荐&#xff0c;首选腾讯云2核4G5M带宽轻量应用服务器218元一年、阿里云2核4G4M带宽轻量应用服务器297元一年&#xff0c;华为云2核2G3M云耀L服务器95元一年&#xff0c;阿腾云来详细说下2核4G游戏服务器推荐配置大全&#xff1a; 目录 2核4G游戏服务器推荐 …

前端 js导出excel

一、导出效果 二、导出代码 download() {let data [{name: aaaa,age: 10,address: 广东,phone: 1,wechart: 1},{name: bbbb,age: 11,address: 广西,phone: 2,wechart: 2},{name: cccc,age: 12,address: 山东,phone: 3,wechart: 3}]if (!data.length) {this.$Message.warnin…

暴力递归转动态规划(九)

题目 题有点难&#xff0c;但还挺有趣 有一个咖啡机数组arr[]&#xff0c;其中arr[i]代表每一个咖啡机冲泡咖啡所需的时间&#xff0c;有整数N&#xff0c;代表着准备冲咖啡的N个人&#xff08;假设这个人拿到咖啡后喝完的时间为0&#xff0c;拿手里咖啡杯即变空&#xff09;&a…

postman和jmete接口测试的用法与区别

前言 前阶段做了一个小调查&#xff0c;发现软件测试行业做功能测试和接口测试的人相对比较多。在测试工作中&#xff0c;有高手&#xff0c;自然也会有小白&#xff0c;但有一点我们无法否认&#xff0c;就是每一个高手都是从小白开始的&#xff0c;所以今天我们就来谈谈一大…

【Qt】字体更大的富文本

使用size属性只能生成7个等级的字号&#xff0c;超过7的都视作为7。 当需要更加夸张的字号时则需要使用style属性&#xff0c;除此之外利用该属性可以生成更加逆天丰富的样式&#xff0c;(style属性是CSS样式表。 稍微跑题一下&#xff1a;似乎有安全性的考量&#xff0c;不少…

如何快速区分GPT-3.5 与GPT-4?

GPT 3.5 和 GPT-4 有什么区别&#xff1f; GPT-3.5 在经过大量数据训练后&#xff0c;成功地发展到可以考虑 1750 亿个参数以响应提示。这使其具备令人印象深刻的语言技能&#xff0c;以非常人性化的方式回应各种查询。然而&#xff0c;GPT-4 在更为庞大的训练数据基础上进行了…

【Qt】对话框QDialog

文章目录 **对话框**QDialog**基本概念**对话框分类标准对话框自定义消息框模态对话框非模态对话框 案例&#xff1a;点击新建按钮弹出对话框消息对话框其它标准对话框 对话框QDialog 基本概念 对话框是 GUI 程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组…

pycharm2020无法打开,点击无反应

pycharm 2020 无法打开&#xff0c;点击无反应&#xff0c;今天我碰到这现象&#xff0c;总结大体原因 C:\Users\ygw\AppData\Roaming\JetBrains &#xff08;删除该目录即可&#xff0c;一般由于升级安装 或 安装两个不同版本 会存在老旧文件影响导致&#xff09;

SpringSecurity + jwt + vue2 实现权限管理 , 前端Cookie.set() 设置jwt token无效问题(已解决)

问题描述 今天也是日常写程序的一天 , 还是那个熟悉的IDEA , 还是那个熟悉的Chrome浏览器 , 还是那个熟悉的网站 , 当我准备登录系统进行登录的时候 , 发现会直接重定向到登录页 , 后端也没有报错 , 前端也没有报错 , 于是我得脸上又多了一张痛苦面具 , 紧接着在前端疯狂debug…

dpdk/spdk/网络协议栈/存储/网关开发/网络安全/虚拟化/ 0vS/TRex/dpvs技术专家成长体系教程

课程围绕安全&#xff0c;网络&#xff0c;存储&#xff0c;云原生4个维度去讲解核心技术点。 6个专栏组成&#xff1a;dpdk网络专栏、存储技术专栏、安全与网关开发专栏、虚拟化与云原生专栏、测试工具专栏、性能测试专栏 一、dpdk网络 dpdk基础知识 多队列网卡&#xff0…

采集EtherNET/IP转Profinet在西门子plc中的应用

远创智控网关YC-EIPM-PN&#xff0c;让你的设备和云平台实时连接&#xff01; 远创智控YC-EIPM-PN网关产品支持各种数据接口&#xff0c;无论是工业领域的仪表、PLC、计量设备&#xff0c;还是设备数据&#xff0c;都能实时采集并整合。它将这些设备中的运行数据、状态数据等信…

学习pytorch13 神经网络-搭建小实战Sequential的使用

神经网络-搭建小实战&Sequential的使用 官网模型结构根据模型结构和数据的输入shape&#xff0c;计算用在模型中的超参数coderunning log网络结构可视化 B站小土堆pytorch视频学习 官网 https://pytorch.org/docs/stable/generated/torch.nn.Sequential.html#torch.nn.Se…

布朗大学发现GPT-4存在新问题,可通过非常见语言绕过限制

&#x1f989; AI新闻 &#x1f680; 布朗大学发现GPT-4存在新漏洞&#xff0c;可通过非常见语言绕过限制 摘要&#xff1a;布朗大学计算机科学研究人员发现了OpenAI的GPT-4存在新漏洞&#xff0c;利用不太常见的语言如祖鲁语和盖尔语可以绕过各种限制。研究人员测试了GPT-4对…

gitlab docker部署,备份,恢复。附踩坑记录

本次安装在CentOS7下进行 1、安装yum 检查是否已经安装yum yum --version如果未安装 sudo yum install -y yum-utils添加镜像源&#xff1a; 国外镜像源&#xff1a;yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo阿里镜像源&am…

最新AI创作系统ChatGPT源码+详细搭建部署教程,支持AI绘画/支持OpenAI-GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统&#xff0c;支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…

AOMEI PXE Boot Free

两台电脑网线直连&#xff0c;不用设置固定IP&#xff0c;该软件包含DHCP。 名称: 3H3AOMEIPXEBootFree.rar 大小: 13068734 字节 (12 MiB) SHA1: 1e606c8c1ee3badf8af9a87f61fdf2e332b773e6 名称: PXEBoot.exe 大小: 13124928 字节 (12 MiB) SHA1: 95286ac18e9b81c2a68412c40…

代理IP在保护跨境商家网络安全中的重要作用

在当前全球化的背景下&#xff0c;跨境电商成为一种重要的商业模式&#xff0c;越来越多的商家涌入国际市场&#xff0c;商家们通过互联网平台将商品远销国外&#xff0c;但网络安全风险随之而来。跨境商家因为需要处理大量的在线交易和产品数据&#xff0c;如果未能对这些敏感…

day27--AJAX(bootstrap之modal,toast;接口文档的一些用法;AJAX原理)

目录 Bootstrap之Modal&#xff1a; 显示和隐藏方法 通过自定义属性&#xff1a; 使用JS来控制弹框&#xff1a; Bootstrap之Toast&#xff1a; 接口文档一些用法&#xff1a; 删除图书&#xff1a; 图片上传&#xff1a; 图片上传步骤&#xff1a; 修改头像&#xf…

CVE-2017-15715 apache换行解析文件上传漏洞

影响范围 httpd 2.4.0~2.4.29 复现环境 vulhub/httpd/CVE-2017-15715 docker-compose 漏洞原理 在apache2的配置文件&#xff1a; /etc/apache2/conf-available/docker-php.conf 中&#xff0c;php的文件匹配以正则形式表达 ".php$"的正则匹配模式意味着以.ph…