将名称和地址矢量化以进行相似性搜索的最佳方法?

数据挖掘 nlp 词嵌入 k-nn 搜索引擎 弹性搜索
2022-02-18 11:18:17

我有一个包含大约 900 万人姓名和地址的大型数据集。鉴于用于获取数据的过程的怪癖,一个人很可能不止一次出现在数据集中,每条记录之间都有细微的差异。我想用某种置信度指标来识别一个人和他们的“相似”角色,以确定替代记录。

我对一种方法的初步想法是使用词嵌入将每个名称和地址矢量化为连接字符串,将它们全部加载到 Elasticsearch 中,然后使用 KNN 搜索功能来“聚类”相似的记录并使用集群中每个点之间的欧几里得距离作为相似度度量。

现在我考虑到这一点,我认为它不会起作用,因为词嵌入会基于语义关系,并且根据定义,名称和地址在语义上是中性的。还有其他向量化方法,例如词袋、n-gram 和 TF-IDF,但这些方法会产生大量高维稀疏向量,无法很好地与 KNN 配合使用,而 Elasticsearch 使用 TF-IDF 进行开箱即用的搜索那么为什么要搞乱向量呢?

我的问题是:

  1. 这种方法听起来是否过于工程化?
  2. 如果没有,是否有更好的矢量化方法(例如散列)?
  3. 如果以上是肯定的,我至少在正确的路线上是一种有效的方法吗?

这更像是一个健全的董事会帖子,但任何意见都会非常有帮助。谢谢!

3个回答

您描述的问题通常称为记录链接,特别是概率记录链接

如果同一实体的不同角色经常同时出现,则对嵌入进行聚类将起作用。每个项目都必须用元数据进行标记,因此聚类只会返回相同类型的信息(例如,只有人名)。

您最初使用嵌入的想法是正确的方法。距离和 TF-IDF 等令牌级别的相似性指标只会带您到此为止。你最终会追逐拼写的变化,无论它们是可接受的类型(Road vs RD.)还是只是拼写错误(Mississippi vs Missippi)。您可以尝试构建字典、使用词形还原器等,但要让它们都足够正确,这将是一场激烈的战斗。

尝试使用 SentenceBert ( https://www.sbert.net/ )。它可以使用不同的模型,所有模型都在不同的数据集和不同的向量大小上进行训练。所以需要一些实验。

例子

例如,我运行了一个小样本,比较了以下“句子”的嵌入

地址 地标
1第一街,NE华盛顿特区20543 美国最高法院
1600 宾夕法尼亚大道 NW,华盛顿特区,20500 白宫(地址)
白宫 白宫(姓名)
20 W 34th St, 纽约, NY 10001 帝国大厦

并看到了以下余弦相似度

斯科特斯 WH(地址) WH ESB
斯科特斯 100.00% 83.06% 42.30% 68.60%
WH(地址) 83.06% 100.00% 43.95% 69.73%
WH 42.30% 43.95% 100.00% 36.20%
ESB 68.60% 69.73% 36.20% 100.00%

结果

正如你所看到的,“白宫”这个名字和它的地址是 100% 匹配的。没有基于令牌的方法会给我们这种级别的匹配。我们在最高法院的地址和 WH 地址之间也有很好的匹配,但在名称上较低。

Elasticsearch、KNN 等##

使用 Elasticsearch 的 YMMV 取决于您对结果的处理方式。此外,KNN、聚类等也是如此。聚类将带来一个完全不同的维度,您必须先了解每个集群的含义,然后才能使用结果。

当您说词嵌入等可能会产生“糟糕”的结果时,我认为您是对的,因为名称和地址是高度个性化的。

根据您的问题类型,您可能需要比较1:n,这是昂贵的。由于每个条目只需要一次查看彼此的条目,因此您可以通过使用itertools.combinations(lst, 2)生成唯一对来减少工作量。但是,有 9 mio。条目这仍然会提供大约 40,500,000,000,000 对来检查(包括“自我”作为参考)。

如果您不想使用string distances,一种可能的解决方案是将单个矢量化条目与所有其他条目进行比较。这可以基于计数矢量化器来完成n字符级别的-grams。通过这样做,您可以比较“相似”的名称、地址等。冗余文本在这里似乎不是一个大问题。

from sklearn.feature_extraction.text import CountVectorizer

true = ["Mister Example 10 Liversidge St Acton ACT 2601 Australia"] 
alt1 = ["Mr Example Liversidge St 10 2601 Acton ACT Australia"]
alt2 = ["Mister Example 10 Liversidge St Acton ACT 2601 Australia and some redundant text here"]
alt3 = ["Ms Catlady 11 Liversidge St Acton ACT 2601 Australia"]
alt4 = ["Mr Entirely Different Other Street 123 9031 Anywhere USA"]

vec = CountVectorizer(analyzer="char_wb", ngram_range=(3,3))
x = vec.fit(true)

print(x.get_feature_names())

print(vec.transform(true).todense().sum()) # "truth" is the reference here
print(vec.transform(alt1).todense().sum())
print(vec.transform(alt2).todense().sum())
print(vec.transform(alt3).todense().sum())
print(vec.transform(alt4).todense().sum())
# The single arguments true, alt1 etc can also be packt into a vector and transformed in one step

这将给出:

48 # truth as reference value
42 # minor variations to truth
48 # truth with redundant text
33 # similar address
3  # different address