比较名称的相似度

8

我需要针对一些基于姓名的数据进行交叉验证。

我面临的问题是,根据不同的来源,姓名会有轻微的变化,例如:

L & L AIR CONDITIONING   vs L & L AIR CONDITIONING Service

BEST ROOFING vs ROOFING INC

我有几千条记录,如果手动处理会非常耗时,我希望尽可能自动化这个过程。

由于有额外的单词,仅将名称转换为小写不足以解决问题。

哪些算法适合处理这种情况?

也许可以计算相关性,对“INC”或“Service”等单词给予低权重。

编辑:

我尝试了difflib库。

difflib.SequenceMatcher(None,name_1.lower(),name_2.lower()).ratio()

我用它得到了不错的结果。


1
这是一个非常棘手的问题。 - maxymoo
4个回答

8
我会使用余弦相似度来实现相同的功能。它将给出一个匹配分数,表示字符串之间的接近程度。
以下是帮助您完成此操作的代码(我记得几个月前从Stackoverflow上获取了这段代码,现在找不到链接了)。
import re, math
from collections import Counter

WORD = re.compile(r'\w+')

def get_cosine(vec1, vec2):
    # print vec1, vec2
    intersection = set(vec1.keys()) & set(vec2.keys())
    numerator = sum([vec1[x] * vec2[x] for x in intersection])

    sum1 = sum([vec1[x]**2 for x in vec1.keys()])
    sum2 = sum([vec2[x]**2 for x in vec2.keys()])
    denominator = math.sqrt(sum1) * math.sqrt(sum2)

    if not denominator:
        return 0.0
    else:
        return float(numerator) / denominator

def text_to_vector(text):
    return Counter(WORD.findall(text))

def get_similarity(a, b):
    a = text_to_vector(a.strip().lower())
    b = text_to_vector(b.strip().lower())

    return get_cosine(a, b)

get_similarity('L & L AIR CONDITIONING', 'L & L AIR CONDITIONING Service') # returns 0.9258200997725514

我发现另外一个版本也很有用,稍微基于 NLP 技术,我是作者。
import re, math
from collections import Counter
from nltk.corpus import stopwords
from nltk.stem.porter import *
from nltk.corpus import wordnet as wn

stop = stopwords.words('english')

WORD = re.compile(r'\w+')
stemmer = PorterStemmer()

def get_cosine(vec1, vec2):
    # print vec1, vec2
    intersection = set(vec1.keys()) & set(vec2.keys())
    numerator = sum([vec1[x] * vec2[x] for x in intersection])

    sum1 = sum([vec1[x]**2 for x in vec1.keys()])
    sum2 = sum([vec2[x]**2 for x in vec2.keys()])
    denominator = math.sqrt(sum1) * math.sqrt(sum2)

    if not denominator:
        return 0.0
    else:
        return float(numerator) / denominator

def text_to_vector(text):
    words = WORD.findall(text)
    a = []
    for i in words:
        for ss in wn.synsets(i):
            a.extend(ss.lemma_names())
    for i in words:
        if i not in a:
            a.append(i)
    a = set(a)
    w = [stemmer.stem(i) for i in a if i not in stop]
    return Counter(w)

def get_similarity(a, b):
    a = text_to_vector(a.strip().lower())
    b = text_to_vector(b.strip().lower())

    return get_cosine(a, b)

def get_char_wise_similarity(a, b):
    a = text_to_vector(a.strip().lower())
    b = text_to_vector(b.strip().lower())
    s = []

    for i in a:
        for j in b:
            s.append(get_similarity(str(i), str(j)))
    try:
        return sum(s)/float(len(s))
    except: # len(s) == 0
        return 0

get_similarity('I am a good boy', 'I am a very disciplined guy')
# Returns 0.5491201525567068

你可以调用get_similarityget_char_wise_similarity来查看哪个更适合你的用例。我都使用了 - 普通相似度来筛选出非常接近的,然后再使用字符级相似度来筛选出足够接近的。然后剩下的就需要手动处理了。


我尝试了difflib库,得到了不错的结果,你如何将你的函数与这个库进行比较? - Luis Ramon Ramirez Rodriguez

4
你可以尝试使用“模糊字符串匹配”。你可以使用这个Python Library (https://github.com/seatgeek/fuzzywuzzy)。它内部使用Levenshtein距离(如@user3080953所建议的)来计算两个单词/短语之间的相似度。
fuzz.ratio("hello", "hello")
Out: 100

fuzz.ratio("L & L AIR CONDITIONING", "L & L AIR CONDITIONING Service")
Out: 85

您可以设置一个阈值,当两个单词/短语的相似度高于该阈值时,您会认为它们是相似的。


模糊字符串匹配比difflib更好吗? - Luis Ramon Ramirez Rodriguez
这是同一件事情。它只是基于 difflib 构建的。 - KartikKannapur

3

你可以尝试使用 Levenshtein距离 来计算两个字符串之间的差异,这是一种很好的方法。


2

您需要弄清楚名称相似意味着什么。

稍微的拼写差异可能很难处理,我建议不要太过关注这个问题。

假设有三种变化:

  • 大写/小写
  • 附加单词
  • 标点符号与空格

我建议将每个字符串都分解成单词,无论是以连字符、空格、句号等方式分隔的单词。将每个字符串转换为数组,元素是单词,按顺序排列。

然后,将所有内容都改为相同的大小写。

下一步取决于您的数据。您可以从较长的字符串中删除短词,删除特定关键字,检查数组中长单词是否匹配大多数...... 根据您选择的方法,可能会计算密集。

我相信已经有现成的工具可以完成这种操作。这取决于您的目标是什么。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接