在 python 中获得准确的文本相似度以比较单个单词或二元组的最佳方法是什么?

rom*_*rom 7 python similarity nltk sentence-similarity spacy

products_a我在数组和数组中都有类似的产品数据products_b

products_a = [{color: "White", size: "2' 3\""}, {color: "Blue", size: "5' 8\""} ]
products_b = [{color: "Black", size: "2' 3\""}, {color: "Sky blue", size: "5' 8\""} ]
Run Code Online (Sandbox Code Playgroud)

我希望能够准确地判断两个数组中颜色之间的相似性,得分在0和 之间1。例如,比较"Blue"应该"Sky blue"得分接近1.00(可能相似0.78或相似)。

空间相似度

我尝试使用spacy来解决这个问题:

import spacy
nlp = spacy.load('en_core_web_sm')

def similarityscore(text1, text2 ):
    doc1 = nlp( text1 )
    doc2 = nlp( text2 )
    similarity = doc1.similarity( doc2 )
    return similarity
Run Code Online (Sandbox Code Playgroud)

是的,好吧,当传球反对时,"Blue"得分"Sky blue"0.6545742918773636。好的,但是当传入"White"反对时会发生什么"Black"?分数是0.8176945362451089......正如 spacy 所说的"White"反对"Black"是 ~81%相似!当试图确保产品颜色不相似时,这是失败的。

杰卡德相似度

我尝试Jaccard Similarity反对"White"使用"Black"这个,并得到了分数0.0(也许在单个单词上有点过分,但为未来更大的语料库留出了空间):

# remove punctuation and lowercase all words function
def simplify_text(text):
    for punctuation in ['.', ',', '!', '?', '"']:
        text = text.replace(punctuation, '')
    return text.lower()

# Jaccard function
def jaccardSimilarity(text_a, text_b ):
    word_set_a, word_set_b = [set(self.simplify_text(text).split())
                                for text in [text_a, text_b]]
    num_shared = len(word_set_a & word_set_b)
    num_total = len(word_set_a | word_set_b)
    jaccard = num_shared / num_total
    return jaccard
Run Code Online (Sandbox Code Playgroud)

获得不同的分数对我来说0.0是不可接受的。我一直在寻找更准确的方法来解决这个问题。即使取两者的平均值也是不准确的。如果您有更好的方法请告诉我。0.8176945362451089"White""Black"

Nic*_*ick 2

NLP 包可能更适合较长的文本片段和更复杂的文本分析。

正如您在“黑色”和“白色”中发现的那样,它们对相似性做出的假设在简单的产品列表中是不正确的。

相反,您可以将其视为数据转换问题,而不是 NLP 问题。这就是我要解决的方法。

要获取两个列表中唯一的颜色列表,请对两个产品列表中找到的颜色使用集合操作。“集合理解”从每个产品列表中获取一组唯一的颜色,然后union()这两个集合上的 a 从两个产品列表中获取唯一的颜色,没有重复。(对于 4 个产品来说并不是真正需要,但对于 400 或 4000 个产品非常有用。)

products_a = [{'color': "White", 'size': "2' 3\""}, {'color': "Blue", 'size': "5' 8\""} ]
products_b = [{'color': "Black", 'size': "2' 3\""}, {'color': "Sky blue", 'size': "5' 8\""} ]

products_a_colors = {product['color'].lower() for product in products_a}
products_b_colors = {product['color'].lower() for product in products_b}
unique_colors = products_a_colors.union(products_b_colors)
print(unique_colors)
Run Code Online (Sandbox Code Playgroud)

颜色是小写的,因为在 Python 中'Blue' != 'blue',这两种拼写都可以在您的产品列表中找到。

上面的代码找到了这些独特的颜色:

{'black', 'white', 'sky blue', 'blue'}
Run Code Online (Sandbox Code Playgroud)

下一步是构建一个空的颜色图。

colormap = {color: '' for color in unique_colors}
import pprint
pp = pprint.PrettyPrinter(indent=4, width=10, sort_dicts=True)
pp.pprint(colormap)
Run Code Online (Sandbox Code Playgroud)

结果:

{
    'sky blue': '',
    'white': '',
    'black': '',
    'blue': ''
}
Run Code Online (Sandbox Code Playgroud)

将空地图粘贴到您的代码中,并填写复杂颜色(例如“天蓝色”)的映射。删除简单的颜色,如“白色”、“黑色”和“蓝色”。您将在下面看到原因。

这是一个示例,假设产品范围稍大一些,颜色更复杂或不寻常:

colormap = {
    'sky blue': 'blue',
    'dark blue': 'blue',
    'bright red': 'red',
    'dark red': 'red',
    'burgundy': 'red'
}
Run Code Online (Sandbox Code Playgroud)

此功能可帮助您根据颜色图将相似的颜色分组在一起。函数color()将复杂的颜色映射到基色上,并将所有内容都变成小写,以使“蓝色”被视为与“蓝色”相同。(注意:字典colormap的键中只能使用小写字母。)

def color(product_color):
    return colormap.get(product_color.lower(), product_color).lower()
Run Code Online (Sandbox Code Playgroud)

例子:

>>> color('Burgundy')
'red'
>>> color('Sky blue')
'blue'
>>> color('Blue')
'blue'
Run Code Online (Sandbox Code Playgroud)

如果颜色在颜色图中没有键,则它会保持不变地传递,只是转换为小写:

>>> color('Red')
'red'
>>> color('Turquoise')
'turquoise'
Run Code Online (Sandbox Code Playgroud)

这是计分部分。标准库中的函数product用于将来自 的项目product_a与来自 的项目配对product_b。每对都使用编号enumerate(),因为稍后会清楚,一对的分数的形式为(pair_id, score)。这样每一对就可以有多个分数。

“笛卡尔积”只是一个数学名称itertools.product()。我已将其重命名以避免与product_a和混淆product_bitertools.product()返回两个列表之间所有可能的对。

from itertools import product as cartesian_product
product_pairs = {
    pair_id: product_pair for pair_id, product_pair
    in enumerate(cartesian_product(products_a, products_b))
}
print(product_pairs)
Run Code Online (Sandbox Code Playgroud)

结果:

{0: ({'color': 'White', 'size': '2\' 3"'}, {'color': 'Black', 'size': '2\' 3"'}),
 1: ({'color': 'White', 'size': '2\' 3"'}, {'color': 'Sky blue', 'size': '5\' 8"'}),
 2: ({'color': 'Blue', 'size': '5\' 8"'}, {'color': 'Black', 'size': '2\' 3"'}),
 3: ({'color': 'Blue', 'size': '5\' 8"'}, {'color': 'Sky blue', 'size': '5\' 8"'})
}
Run Code Online (Sandbox Code Playgroud)

如果您有数百种产品,则列表会更长。

然后,您可以按以下方式编译颜色分数:

color_scores = [(pair_id, 0.8) for pair_id, (product_a, product_b)
                in product_pairs.items()
                if color(product_a['color']) == color(product_b['color'])]
print(color_scores)
Run Code Online (Sandbox Code Playgroud)

在示例数据中,一对产品通过以下color()函数进行匹配:对编号 3,其中“蓝色”产品在 中product_a,“天蓝色”项目在 中product_b。当color()函数将“天蓝色”和“蓝色”评估为值“蓝色”时,该对将获得分数 0.8:

[(3, 0.8)]
Run Code Online (Sandbox Code Playgroud)

“深度拆包”用于提取商品详细信息以及当前商品对的“pair id”,并将其放入局部变量中进​​行处理或显示。这里有一篇关于“深度拆包”的很好的教程文章。

以上是其他规则的蓝图。例如,您可以根据大小编写一条规则,并给出不同的分数,例如 0.5:

size_scores = [(pair_id, 0.5) for pair_id, (product_a, product_b)
               in product_pairs.items()
               if product_a['size'] == product_b['size']]
print(size_scores)

Run Code Online (Sandbox Code Playgroud)

这是基于'size'属性的结果分数。

[(0, 0.5), (3, 0.5)]
Run Code Online (Sandbox Code Playgroud)

这意味着第 0 组得分为 0.5,第 3 组得分为 0.5,因为它们的大小完全匹配。

要获得产品对的总分,您可以对颜色和尺寸得分进行平均:

print()
print("Totals")
score_sources = [color_scores, size_scores]  # add more scores to this list
all_scores = sorted(itertools.chain(*score_sources))
pair_scores = itertools.groupby(all_scores, lambda x: x[0])
for pair_id, pairs in pair_scores:
    scores = [score for _, score in pairs]
    average = sum(scores) / len(scores)
    print(f"Pair {pair_id}: score {average}")
    for n, product in enumerate(product_pairs[pair_id]):
        print(f"  --> Item {n+1}: {product}")
Run Code Online (Sandbox Code Playgroud)

结果:

Totals
Pair 0: score 0.5
  --> Item 1: {'color': 'White', 'size': '2\' 3"'}
  --> Item 2: {'color': 'Black', 'size': '2\' 3"'}
Pair 3: score 0.65
  --> Item 1: {'color': 'Blue', 'size': '5\' 8"'}
  --> Item 2: {'color': 'Sky blue', 'size': '5\' 8"'}
Run Code Online (Sandbox Code Playgroud)

匹配颜色和尺寸的配对 3 得分最高,而仅匹配尺寸的配对 0 得分较低。另外两对没有得分。