Почему я получаю TypeError: тип unhashable при использовании лемматизатора NLTK в предложении? - PullRequest
0 голосов
/ 19 февраля 2020

В настоящее время я работаю над леммантизацией предложения, одновременно применяя 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'

Я понимаю, что списки не подлежат изменению, но я не уверен, как почини это. Я могу изменить списки на кортеж или я чего-то не понимаю?

1 Ответ

1 голос
/ 19 февраля 2020

Давайте пройдемся по коду и посмотрим, как получить желаемый результат.

Сначала выполняется импорт, у вас есть

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, а затем списка лемм по отдельности звучит логично, но, поскольку функция, наконец, только возвращает функцию, вы должны быть в состоянии объединиться pos_tag(word_tokenize(...)) и выполняйте итерацию по ней, чтобы вы могли получить POS-тег и токены, например:

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

[out]:

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

Теперь мы знаем, что есть несоответствие между выходами pos_tag и POS, которые ожидает WordNetLemmatizer. От 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))

[out]:

I
love
run
angry

Эй, что ты там делал? Ваш код работает, а мой нет!

Хорошо, теперь мы знаем, как получить желаемый результат. Подведем итоги.

  • Сначала мы очищаем импорт
  • Затем мы очищаем предварительную обработку (без сохранения промежуточных переменных)
  • Затем мы "функционализировали" преобразование POS теги от Penn -> Morphy
  • Наконец, мы применили те же условия if / else и запустили лемматизатор.

Но почему мой код не работает? не работает?!

Хорошо, давайте проработаем ваш код, чтобы понять, почему вы получаете ошибку.

Сначала давайте проверим каждый вывод, который вы получаете в функции findTag, напечатав тип выхода и вывода

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

[out]:

<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)

[out]:

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

Теперь мы видим, что sentence = [i.strip(" ") for i in sentence] фактически бессмысленно, учитывая пример предложения.

В: Но правда ли, что у всех токенов, выводимых word_tokenize, не будет пробелов / пробелов, которые i.strip(' ') пытается сделать?

A: Да, похоже, что так. Затем NLTK сначала выполняет операции регулярного выражения над строками, а затем вызывает функцию str.split(), которая бы убрала пробелы в заголовке / конце между токенами, см. 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)

[out]:

<class 'str'>
p

Q: Подождите минуту, где pos_label только одна строка?

Q: А что такое POS-тег p?

A: Давайте посмотрим ближе, что происходит в nltk.pos_tag(sentence)[0][1][0].lower()

Обычно, когда вам нужно сделать такой [0][1][0] поиск вложенного индекса, его склонность к ошибкам. Нам нужно спросить, что такое [0][1][0]?

Мы знаем, что это предложение теперь после того, как sentence = word_tokenize(sentence) стало списком строк. И pos_tag(sentence) вернет список кортежей строк, где первый элемент в кортеже является токеном, а второй - тегом 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)

[out]:

<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, pos) для первого токена.

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

И thing[0][1] выводит POS для первого токена.

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

Ахсо, [0][1][0] ищет первый символ POS первого токена.

Итак, вопрос в том, что желаемое поведение? Если так, то почему вы смотрите только на 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)

[out]:

-------------------------------------------------------------------------
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: Тогда вы упустите способ отладки вашего код и сделать его лучше =)

...