Why do I get TypeError: unhashable type when using NLTK lemmatizer on sentence?

Why do I get TypeError: unhashable type when using NLTK lemmatizer on sentence?

我目前正在对一个句子进行词形还原,同时也在应用 pos_tags。这是我目前所拥有的

import nltk
from nltk.tokenize import word_tokenize
from nltk.corpus import wordnet
from nltk.stem import WordNetLemmatizer
from nltk import pos_tag

lem = WordNetLemmatizer()

def findTag(sentence):
    sentence = word_tokenize(sentence)
    sentence = [i.strip(" ") for i in sentence]
    pos_label = nltk.pos_tag(sentence)[0][1][0].lower()

    if pos_label == "j":
        pos_label == "a"

    if pos_label in ["a", "n", "v"]:
        print(lem.lemmatize(word, pos = pos_label))
    elif pos_label in ['r']: 
        print(wordnet.synset(sentence+".r.1").lemmas()[0].pertainyms()[0].name())
    else:
        print(lem.lemmatize(sentence))


findTag("I love running angrily")

但是,当我用这个输入一个句子时,我得到了错误

Traceback (most recent call last):
  File "spoilerDetect.py", line 25, in <module>
    findTag("I love running angrily")
  File "spoilerDetect.py", line 22, in findTag
    print(lem.lemmatize(sentence))
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/nltk/stem/wordnet.py", line 41, in lemmatize
    lemmas = wordnet._morphy(word, pos)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/nltk/corpus/reader/wordnet.py", line 1905, in _morphy
    if form in exceptions:
TypeError: unhashable type: 'list'

我知道列表是不可散列的,但不确定如何解决这个问题。我是将列表更改为元组还是有什么我不理解的地方?

让我们浏览一下代码,看看如何获​​得所需的输出。

首先是导入,你有

import nltk
from nltk import pos_tag

然后你在使用

pos_label = nltk.pos_tag(...)

由于您已经在使用 from nltk import pos_tag,因此 pos_tag 已经在全局命名空间中,只需执行以下操作:

pos_label = pos_tag(...)

按照惯例,应该稍微清理一下导入,使其看起来像这样:

from nltk import word_tokenize, pos_tag
from nltk.corpus import wordnet as wn
from nltk.stem import WordNetLemmatizer

wnl = WordNetLemmatizer()

接下来实际保留标记化单词列表,然后分别保留 pos 标签列表和引理列表听起来合乎逻辑,但由于函数最终只有 return 函数,您应该能够链接启动 pos_tag(word_tokenize(...)) 函数并遍历它,以便您可以检索 POS 标签和标记,即

sentence = "I love running angrily"
for word, pos in pos_tag(word_tokenize(sentence)):
    print(word, '|', pos)

[出]:

I | PRP
love | VBP
running | VBG
angrily | RB

现在,我们知道 pos_tag 的输出与 WordNetLemmatizer 期望的 POS 之间存在不匹配。从 https://github.com/alvations/pywsd/blob/master/pywsd/utils.py#L124 开始,有一个函数调用 penn2morphy 看起来像这样:

def penn2morphy(penntag, returnNone=False, default_to_noun=False) -> str:
    """
    Converts tags from Penn format (input: single string) to Morphy.
    """
    morphy_tag = {'NN':'n', 'JJ':'a', 'VB':'v', 'RB':'r'}
    try:
        return morphy_tag[penntag[:2]]
    except:
        if returnNone:
            return None
        elif default_to_noun:
            return 'n'
        else:
            return ''

一个例子:

>>> penn2morphy('JJ')
'a'
>>> penn2morphy('PRP')
''

如果我们使用这些转换后的标签作为 WordNetLemmatizer 的输入并重新使用您的 if-else 条件:

sentence = "I love running angrily"
for token, pos in pos_tag(word_tokenize(sentence)):
    morphy_pos = penn2morphy(pos)
    if morphy_pos in ["a", "n", "v"]:
        print(wnl.lemmatize(token, pos=morphy_pos))
    elif morphy_pos in ['r']: 
        print(wn.synset(token+".r.1").lemmas()[0].pertainyms()[0].name())
    else:
        print(wnl.lemmatize(token))

[出]:

I
love
run
angry

嘿,你在那里做什么?您的代码有效,但我的代码无效!

好的,现在我们知道如何获得所需的输出了。让我们回顾一下。

  • 首先,我们清理导入
  • 然后,我们清理预处理(不保留中间变量)
  • 然后,我们"functionalized"从Penn -> Morphy
  • 转换POS标签
  • 最后,我们应用了相同的 if/else 条件和 运行 词形还原器。

但是我的代码怎么不起作用?!

好的,让我们检查一下您的代码,看看为什么会出现错误。

首先让我们检查您在 findTag 函数中获得的每个输出,打印输出类型和输出

sentence = "I love running angrily"
sentence = word_tokenize(sentence)
print(type(sentence))
print(sentence)

[出]:

<class 'list'>
['I', 'love', 'running', 'angrily']

sentence = word_tokenize(sentence) 处,您已经将原始变量覆盖到函数中,通常这是稍后出现错误的标志 =)

现在让我们看下一行:

sentence = "I love running angrily"
sentence = word_tokenize(sentence)
sentence = [i.strip(" ") for i in sentence]

print(type(sentence))
print(sentence)

[出]:

<class 'list'>
['I', 'love', 'running', 'angrily']

现在我们看到sentence = [i.strip(" ") for i in sentence]实际上是没有意义的例句。

问:但是 word_tokenize 输出的所有标记都没有 i.strip(' ') 试图做的 trailing/heading 空格是真的吗?

A:对,好像是这样。然后 NLTK 首先对字符串进行正则表达式操作,然后调用 str.split() function which would have removed heading/trailing spaces between tokens, see https://github.com/nltk/nltk/blob/develop/nltk/tokenize/destructive.py#L141

让我们继续:

sentence = "I love running angrily"
sentence = word_tokenize(sentence)
sentence = [i.strip(" ") for i in sentence]
pos_label = nltk.pos_tag(sentence)[0][1][0].lower()

print(type(pos_label))
print(pos_label)

[出]:

<class 'str'>
p

问:等一下,pos_label只有一个字符串在哪里?

问:什么是 POS 标签 p

A:让我们仔细看看 nltk.pos_tag(sentence)[0][1][0].lower()

中发生了什么

通常,当您必须执行这样的 [0][1][0] 嵌套索引检索时,它很容易出错。我们需要问什么是 [0][1][0]?

我们知道sentence = word_tokenize(sentence)之后的那句话现在变成了一个字符串列表。 pos_tag(sentence) 将 return 一个字符串元组列表,其中元组中的第一项是标记,第二项是 POS 标记,即

sentence = "I love running angrily"
sentence = word_tokenize(sentence)
sentence = [i.strip(" ") for i in sentence]
thing = pos_tag(sentence)

print(type(thing))
print(thing)

[出]:

<class 'list'>
[('I', 'PRP'), ('love', 'VBP'), ('running', 'VBG'), ('angrily', 'RB')]

现在,如果我们知道 thing = pos_tag(word_tokenize("I love running angrily")),输出上面的内容,让我们用它来查看 [0][1][0] 正在访问什么。

>>> thing = [('I', 'PRP'), ('love', 'VBP'), ('running', 'VBG'), ('angrily', 'RB')]
>>> thing[0][1]
('I', 'PRP')

所以thing[0]输出第一个token的(token, pos)的元组。

>>> thing = [('I', 'PRP'), ('love', 'VBP'), ('running', 'VBG'), ('angrily', 'RB')]
>>> thing[0][1]
'PRP'

并且thing[0][1]输出第一个token的POS。

>>> thing = [('I', 'PRP'), ('love', 'VBP'), ('running', 'VBG'), ('angrily', 'RB')]
>>> thing[0][1][0]
'P'

接下来,[0][1][0] 查找第一个标记的 POS 的第一个字符。

所以问题是期望的行为?如果是,为什么只看第一个词的词性?


不管我在看什么。您的解释仍然没有告诉我 TypeError: unhashable type: 'list' 发生的原因。不要再分散我的注意力,告诉我如何解决 TypeError!!

好的,我们继续前进,现在我们知道 thing = pos_tag(word_tokenize("I love running angrily"))thing[0][1][0].lower() = 'p'

鉴于您的 if-else 条件,

if pos_label in ["a", "n", "v"]:
    print(lem.lemmatize(word, pos = pos_label))
elif pos_label in ['r']: 
    print(wordnet.synset(sentence+".r.1").lemmas()[0].pertainyms()[0].name())
else:
    print(lem.lemmatize(sentence))

我们发现 'p' 值会转到其他地方,即 print(lem.lemmatize(sentence)) 但等一下,请记住 sentence 在您修改后变成了什么:

>>> sentence = word_tokenize("I love running angrily")
>>> sentence = [i.strip(" ") for i in sentence]
>>> sentence 
['I', 'love', 'running', 'angrily']

那么如果我们忽略所有其余代码并专注于此会发生什么:

from nltk.stem import WordNetLemmatizer

lem = WordNetLemmatizer()
sentence = ['I', 'love', 'running', 'angrily']

lem.lemmatize(sentence)

[出]:

-------------------------------------------------------------------------
TypeError                               Traceback (most recent call last)
<ipython-input-34-497ae98ecaa3> in <module>
      4 sentence = ['I', 'love', 'running', 'angrily']
      5 
----> 6 lem.lemmatize(sentence)

~/Library/Python/3.6/lib/python/site-packages/nltk/stem/wordnet.py in lemmatize(self, word, pos)
     39 
     40     def lemmatize(self, word, pos=NOUN):
---> 41         lemmas = wordnet._morphy(word, pos)
     42         return min(lemmas, key=len) if lemmas else word
     43 

~/Library/Python/3.6/lib/python/site-packages/nltk/corpus/reader/wordnet.py in _morphy(self, form, pos, check_exceptions)
   1903         # 0. Check the exception lists
   1904         if check_exceptions:
-> 1905             if form in exceptions:
   1906                 return filter_forms([form] + exceptions[form])
   1907 

TypeError: unhashable type: 'list'

啊哈!!这就是错误发生的地方!!!

这是因为 WordNetLemmatizer 需要单个字符串输入,而您输入的是字符串列表。用法示例:

from nltk.stem import WordNetLemmatizer

wnl = WordNetLemmatizer()
token = 'words'
wnl.lemmatize(token, pos='n')

问:为什么不说正题?!

A: 那么你会错过如何调试你的代码并使其变得更好=)