NLP输入的字符串匹配

时间:2018-02-10 13:10:46

标签: python nlp string-matching alexa

我和Alexa有点鬼混。我的任务是将用户输入与从Web动态加载的可能答案列表进行匹配。在这种情况下,它是一个电影列表。

当然我无法假设总会有完美的匹配,无论是用户还是Echo设备都不会让它完全正确。我目前克服这个问题的方法是SequenceMatcher函数。所以我衡量用户输入和列表中所有项目的相似性,获胜者可能是用户真正谈论的列表项目:

from difflib import SequenceMatcher

maxi = 0
haystack = ["Die Verurteilten", "Der Pate", "Der Pate 2", "The Dark Knight", "Die zwölf Geschworenen", "Schindlers Liste", "Pulp Fiction", "Der Herr der Ringe - Die Rückkehr des Königs", "Zwei glorreiche Halunken", "Fight Club", "Der Herr der Ringe - Die Gefährten", "Forrest Gump", "Das Imperium schlägt zurück", "Inception", "Der Herr der Ringe - Die zwei Türme", "einer flog über das Kuckucksnest", "GoodFellas - Drei Jahrzehnte in der Mafia", "Matrix", "Die sieben Samurai", "Krieg der Sterne", "City of God", "Sieben", "Das Schweigen der Lämmer", "Ist das Leben nicht schön?", "Das Leben ist schön"]
needle = "Die Gefährten"

for hay in haystack:
    ratio = SequenceMatcher(None, needle, hay).ratio()
    print('%.5f' % ratio + " " + hay)
    if ratio > maxi:
        maxi = ratio
        result = hay

print(result)

大多数时候我对结果感到满意。然而,有时候(有点太频繁)我不是。如果用户可能会像上面的示例中那样要求“DieGefährten”,则会发生这种情况:

# 0.62069 Die Verurteilten
# 0.55319 Der Herr der Ringe - Die Gefährten
# Die Verurteilten

对于这种特殊情况,通过分隔符-拆分列表项可能是一种简单的解决方案,对所有结果部分进行计算并返回最大分数。但由于列表可能是其他任何东西(食谱,书籍,歌曲,游戏......),我想知道是否有更普遍的方法。有什么想法吗?

感谢。

2 个答案:

答案 0 :(得分:3)

就方法而言,此对象的文档并不十分详细,但我的猜测是使用了Levenshtein距离法。

由于额外的“Der Herr Der Ringe”损害了此方法的“得分”,因此有可能在您的用例中失败,因为'Die Verurteilten'需要更少的添加,减法和/或替换以匹配您的查询。

您的问题有两种解决方案:

您可以使用令牌匹配方法,其中“得分”严重依赖于单个匹配单词。所以'DieGefährten'匹配'Der Herr der Ringe - DieGefährte'中的两个单词,将其标记为100%匹配。这可以与其他字符级方法(如levenshtein和ngram字符)结合使用,以便在识别特定令牌匹配和潜在的,关闭令牌匹配时产生平衡结果。

或者你可以将你的干草堆又称为语料库,将其分成'块'代币,以便进行比较。你需要能够比较这些结果的分数,因为你甚至可能只有一个匹配,但你应该能够识别出与'Der Herr der Ringe'中的'DieGefährte'的完全匹配 - DieGefährte'100%匹配。

你基本上需要将你的问题从模糊匹配重构为非结构化文本中的命名实体识别之一,可能有一些模糊匹配来补偿Alexa产生的任何garbledygook。

答案 1 :(得分:0)

根据John的输入,我创建了以下例程。

除了以前的计算,我还会进行单独的单词匹配,并计算Alexa提供的所有单词的平均分数。

总分是两个分数的乘积。

我还试图忽略任何基于字长的假设的填充物。基于非常基本的统计摘要(字数和中间字长),我将忽略长度小于5,4或2个字符的所有字。使用字典可能是一个更好的解决方案,但由于多语言环境,我想避免这种情况。

from difflib import SequenceMatcher
from statistics import median, mean

def getWords(input):
    words = input.split()
    lengths = [ len(x) for x in words if len(x) > 1 ]

    # set the minimum word length based on word count
    # and median of word length to remove presumed fillers
    minLength = 2
    if len(words) >= 3 and median(lengths) > 4:
        minLength = 5
    elif len(words) >= 2 and median(lengths) > 3:
        minLength = 4

    # keep words of minimum length
    answer = list()
    for item in words:
        if len(item) >= minLength:
            answer.append(item) 

    return answer

matchList = ["Die Verurteilten", "Der Pate", "Der Pate 2", "The Dark Knight", "Die zwölf Geschworenen", "Schindlers Liste", "Pulp Fiction", "Der Herr der Ringe - Die Rückkehr des Königs", "Zwei glorreiche Halunken", "Fight Club", "Der Herr der Ringe - Die Gefährten", "Forrest Gump", "Das Imperium schlägt zurück", "Inception", "Der Herr der Ringe - Die zwei Türme", "Einer flog über das Kuckucksnest", "GoodFellas - Drei Jahrzehnte in der Mafia", "Matrix", "Die sieben Samurai", "Krieg der Sterne", "City of God", "Sieben", "Das Schweigen der Lämmer", "Ist das Leben nicht schön?", "Das Leben ist schön"]
userInput = "Die Gefährten"

# find the best match between the user input and the link list
maxi = 0
for matchItem in matchList:

    # ratio of the original item comparison
    fullRatio = SequenceMatcher(None, userInput, matchItem).ratio()

    # every word of the user input will be compared
    # to each word of the list item, the maximum score
    # for each user word will be kept
    wordResults = list()
    for userWord in getWords(userInput):
        maxWordRatio = 0
        for matchWord in getWords(matchItem):
            wordRatio = SequenceMatcher(None, userWord, matchWord).ratio()
            if wordRatio > maxWordRatio:
                maxWordRatio = wordRatio 
        wordResults.append(maxWordRatio)

    # the total score for each list item is the full ratio
    # multiplied by the mean of all single word scores
    itemScore = fullRatio * mean(wordResults)

    # print item result
    print('%.5f' % itemScore, matchItem)

    # keep track of maximum score
    if itemScore > maxi:
        maxi = itemScore
        result = matchItem

# award ceremony
print(result)

此例程的排名输出(更好):

# 0.55319 Der Herr der Ringe - Die Gefährten
# 0.32653 Die zwölf Geschworenen
# 0.29557 Die Verurteilten

广泛的测试将证明这个解决方案的有效性。