Python

Подходы лемматизации с примерами на Python

Spread the love

Введение

Лемматизация — это процесс преобразования слова в его базовую форму. Разница между стемминг (stemming) и лемматизацией заключается в том, что лемматизация учитывает контекст и преобразует слово в его значимую базовую форму, тогда как стемминг просто удаляет последние несколько символов, что часто приводит к неверному значению и орфографическим ошибкам.

Например, лемматизация правильно определила бы базовую форму «caring» и «care», в то время как стемминг отрезал бы «ing» и преобразовал ее в car.

«Caring» -> Лемматизация -> «Care»
«Caring» -> Стемминг -> «Car»

Кроме того, иногда одно и то же слово может иметь несколько разных лемм. Основываясь на контексте, который вы используете, вы должны определить тег «part-of-speech» (POS) для слова в этом конкретном контексте и извлечь соответствующую лемму. Примеры реализации этого приведены в следующих разделах.

В этой статье мы рассмотри, реализацию лемматизации с помощью следующих пакетов Python.

  • Wordnet Lemmatizer
  • Spacy Lemmatizer
  • TextBlob
  • CLiPS Pattern
  • Stanford CoreNLP
  • Gensim Lemmatizer
  • TreeTagger

Лемматизатор Wordnet из NLTK

Wordnet — это большая, свободно распространяемая и общедоступная лексическая база данных для английского языка с целью установления структурированных семантических отношений между словами. Библиотека также предлагает возможности лемматизации и является одним из самых ранних и наиболее часто используемых лемматизаторов.

Пример использования.

Вначале необходимо загрузить библиотеку

# How to install and import NLTK
# In terminal or prompt:
# pip install nltk

# # Download Wordnet through NLTK in python console:
import nltk
nltk.download('wordnet')

Для того, чтобы лемматизировать, нужно создать экземпляр WordNetLemmatizer() и вызвать функцию lemmatize() для одного слова.

import nltk
from nltk.stem import WordNetLemmatizer 

# Init the Wordnet Lemmatizer
lemmatizer = WordNetLemmatizer()
# Lemmatize Single Word
print(lemmatizer.lemmatize("bats"))
#> bat
print(lemmatizer.lemmatize("are"))
#> are
print(lemmatizer.lemmatize("feet"))
#> foot

Давайте лемматизируем простое предложение. Сначала мы разобъем предложение на слова с помощью nltk.word_tokenize, а затем вызываем lemmatizer.lemmatize() для каждого слова.

# Define the sentence to be lemmatized
sentence = "The striped bats are hanging on their feet for best"

# Tokenize: Split the sentence into words
word_list = nltk.word_tokenize(sentence)
print(word_list)
#> ['The', 'striped', 'bats', 'are', 'hanging', 'on', 'their', 'feet', 'for', 'best']

# Lemmatize list of words and join
lemmatized_output = ' '.join([lemmatizer.lemmatize(w) for w in word_list])
print(lemmatized_output)
#> The striped bat are hanging on their foot for best

Приведенный выше код является простым примером того, как использовать лемматизатор wordnet для слов и предложений.

Обратите внимание, что есть ошибки. Потому что «are» не преобразовалось в «be», а «Hang» не преобразовалось в «hang», как ожидалось. Это можно исправить, если в качестве второго аргумента lemmatize() указать правильный тег «part-of-speech» (POS-тег).

Иногда одно и то же слово может иметь несколько лемм в зависимости от значения / контекста.

print(lemmatizer.lemmatize("stripes", 'v'))  
#> strip

print(lemmatizer.lemmatize("stripes", 'n'))  
#> stripe

Wordnet Lemmatizer с соответствующим POS-тегом

Достаточно сложно вручную проставить соответствующий POS-тег для каждого слова при обработке больших текстов. Поэтому вместо этого мы найдем правильный POS-тег для каждого слова, сопоставим его с правильным входным символом, который принимает WordnetLemmatizer, и передадим его в качестве второго аргумента в lemmatize().

Как получить POS-тег для выбранного слова?

В nltk для этого есть метод nltk.pos_tag(). Он принимает только список (список слов), даже если нужно передать только одно слово.

print(nltk.pos_tag(['feet']))
#> [('feet', 'NNS')]

print(nltk.pos_tag(nltk.word_tokenize(sentence)))
#> [('The', 'DT'), ('striped', 'JJ'), ('bats', 'NNS'), ('are', 'VBP'), ('hanging', 'VBG'), ('on', 'IN'), ('their', 'PRP$'), ('feet', 'NNS'), ('for', 'IN'), ('best', 'JJS')]

nltk.pos_tag() возвращает кортеж с тегом POS. Ключевым моментом здесь является сопоставление POS-тегов NLTK с форматом, принятым лемматизатором wordnet.

# Lemmatize with POS Tag
from nltk.corpus import wordnet

def get_wordnet_pos(word):
    """Map POS tag to first character lemmatize() accepts"""
    tag = nltk.pos_tag([word])[0][1][0].upper()
    tag_dict = {"J": wordnet.ADJ,
                "N": wordnet.NOUN,
                "V": wordnet.VERB,
                "R": wordnet.ADV}

    return tag_dict.get(tag, wordnet.NOUN)


# 1. Init Lemmatizer
lemmatizer = WordNetLemmatizer()

# 2. Lemmatize Single Word with the appropriate POS tag
word = 'feet'
print(lemmatizer.lemmatize(word, get_wordnet_pos(word)))

# 3. Lemmatize a Sentence with the appropriate POS tag
sentence = "The striped bats are hanging on their feet for best"
print([lemmatizer.lemmatize(w, get_wordnet_pos(w)) for w in nltk.word_tokenize(sentence)])
#> ['The', 'strip', 'bat', 'be', 'hang', 'on', 'their', 'foot', 'for', 'best']

spaCy лематтизация

spaCy является относительно новым пакетом и на данный момент считается стандартом в индустрии NLP. Он поставляется с предварительно созданными моделями, которые могут анализировать текст и выполнять различный функционал, связанный с NLP.

Прежде чем мы начнем, установим spaCy и загрузим модель «en».

# Install spaCy (run in terminal/prompt)
import sys
!{sys.executable} -m pip install spacy

# Download spaCy's  'en' Model
!{sys.executable} -m spacy download en

spaCy по умолчанию определяет часть речи и назначает соответствующую лемму.

import spacy

# Initialize spacy 'en' model, keeping only tagger component needed for lemmatization
nlp = spacy.load('en', disable=['parser', 'ner'])

sentence = "The striped bats are hanging on their feet for best"

# Parse the sentence using the loaded 'en' model object `nlp`
doc = nlp(sentence)

# Extract the lemma for each token and join
" ".join([token.lemma_ for token in doc])
#> 'the strip bat be hang on -PRON- foot for good'

Он выполнил все лемматизации, которые так же выполнил лемматизатор Wordnet, с проставленным правильным тегом POS. Плюс к этому также лемматизируется такое слово как «best» превращаясь в «good».

Обратите внимание что символ -PRON- появляется, когда spacy обнаруживает местоимение.

TextBlob Lemmatizer

TextBlob — это мощный, быстрый пакет NLP. Используя объекты Word и TextBlob, довольно просто анализировать и лемматизировать слова и предложения соответственно.

# pip install textblob
from textblob import TextBlob, Word

# Lemmatize a word
word = 'stripes'
w = Word(word)
w.lemmatize()
#> stripe

Чтобы лемматизировать предложение или абзац, мы анализируем его с помощью TextBlob а затем вызвать функцию lemmatize() для проанализированных слов.

# Lemmatize a sentence
sentence = "The striped bats are hanging on their feet for best"
sent = TextBlob(sentence)
" ". join([w.lemmatize() for w in sent.words])
#> 'The striped bat are hanging on their foot for best'

Хотя видно что с самого начала он не справился с работой. Как и NLTK, TextBlob внутри использует Wordnet. Соотвественно, для корректной работы так же требует передачу соответствующего POS-тега методу lemmatize().

TextBlob Lemmatizer с соответствующим POS-тегом

# Define function to lemmatize each word with its POS tag
def lemmatize_with_postag(sentence):
    sent = TextBlob(sentence)
    tag_dict = {"J": 'a', 
                "N": 'n', 
                "V": 'v', 
                "R": 'r'}
    words_and_tags = [(w, tag_dict.get(pos[0], 'n')) for w, pos in sent.tags]    
    lemmatized_list = [wd.lemmatize(tag) for wd, tag in words_and_tags]
    return " ".join(lemmatized_list)

# Lemmatize
sentence = "The striped bats are hanging on their feet for best"
lemmatize_with_postag(sentence)
#> 'The striped bat be hang on their foot for best'

Pattern Lemmatizer

Pattern by CLiPs — это универсальный пакет со многими полезными возможностями NLP.
Его установка:

pip install pattern

Далее пример его использования

import pattern
from pattern.en import lemma, lexeme

sentence = "The striped bats were hanging on their feet and ate best fishes"
" ".join([lemma(wd) for wd in sentence.split()])
#> 'the stripe bat be hang on their feet and eat best fishes'

Можно просмотреть возможные лексемы для каждого слова.

# Lexeme's for each word 
[lexeme(wd) for wd in sentence.split()]

#> [['the', 'thes', 'thing', 'thed'],
#>  ['stripe', 'stripes', 'striping', 'striped'],
#>  ['bat', 'bats', 'batting', 'batted'],
#>  ['be', 'am', 'are', 'is', 'being', 'was', 'were', 'been', 
#> . 'am not', "aren't", "isn't", "wasn't", "weren't"],
#>  ['hang', 'hangs', 'hanging', 'hung'],
#>  ['on', 'ons', 'oning', 'oned'],
#>  ['their', 'theirs', 'theiring', 'theired'],
#>  ['feet', 'feets', 'feeting', 'feeted'],
#>  ['and', 'ands', 'anding', 'anded'],
#>  ['eat', 'eats', 'eating', 'ate', 'eaten'],
#>  ['best', 'bests', 'besting', 'bested'],
#>  ['fishes', 'fishing', 'fishesed']]

Вы также можно получить лемму, анализируя текст.

from pattern.en import parse
print(parse('The striped bats were hanging on their feet and ate best fishes', 
            lemmata=True, tags=False, chunks=False))
#> The/DT/the striped/JJ/striped bats/NNS/bat were/VBD/be hanging/VBG/hang on/IN/on their/PRP$/their 
#>  feet/NNS/foot and/CC/and ate/VBD/eat best/JJ/best fishes/NNS/fish

Stanford CoreNLP

Standford CoreNLP — так же популярный инструмент NLP, который изначально был реализован на Java. Так же был сделано несколько враперов на Python. Тот, который я использую ниже, достаточно удобен в использовании.

Но перед этим вам нужно скачать Java и Standford CoreNLP. Прежде чем перейти к коду лемматизации, убедитесь в следующем:

Шаг 1: установите Java 8

На Mac выполните следующее

brew update
brew install jenv
brew cask install java

Шаг 2: Загрузите Stanford CoreNLP и распакуйте его.

Шаг 3: Запустите Stanford CoreNLP с терминала. Для этого перейдите в папку, которую вы только что распаковали, и запустите следующую команду в терминале:

cd stanford-corenlp-full-2018-02-27
java -mx4g -cp "*" edu.stanford.nlp.pipeline.StanfordCoreNLPServer -annotators "tokenize,ssplit,pos,lemma,parse,sentiment" -port 9000 -timeout 30000

Запустите StanfordCoreNLPServer на порту 9000. Теперь можно извлечь леммы в python.

В пакете stanfordcorenlp лемма встроена в выходные данные метода annotate() объекта StanfordCoreNLP connection (см. Код ниже).

# Run `pip install stanfordcorenlp` to install stanfordcorenlp package
from stanfordcorenlp import StanfordCoreNLP
import json

# Connect to the CoreNLP server we just started
nlp = StanfordCoreNLP('http://localhost', port=9000, timeout=30000)

# Define proporties needed to get lemma
props = {'annotators': 'pos,lemma',
         'pipelineLanguage': 'en',
         'outputFormat': 'json'}


sentence = "The striped bats were hanging on their feet and ate best fishes"
parsed_str = nlp.annotate(sentence, properties=props)
parsed_dict = json.loads(parsed_str)
parsed_dict
#> {'sentences': [{'index': 0,
#>    'tokens': [{'after': ' ',
#>      'before': '',
#>      'characterOffsetBegin': 0,
#>      'characterOffsetEnd': 3,
#>      'index': 1,
#>      'lemma': 'the',      << ----------- LEMMA
#>      'originalText': 'The',
#>      'pos': 'DT',
#>      'word': 'The'},
#>     {'after': ' ',
#>      'before': ' ',
#>      'characterOffsetBegin': 4,
#>      'characterOffsetEnd': 11,
#>      'index': 2,
#>      'lemma': 'striped',  << ----------- LEMMA
#>      'originalText': 'striped',
#>      'pos': 'JJ',
#>      'word': 'striped'},
#>     {'after': ' ',
#>      'before': ' ',
#>      'characterOffsetBegin': 12,
#>      'characterOffsetEnd': 16,
#>      'index': 3,
#>      'lemma': 'bat',      << ----------- LEMMA
#>      'originalText': 'bats',
#>      'pos': 'NNS',
#>      'word': 'bats'}
#> ...
#> ...              

Вывод nlp.annotate() был преобразован в dict с использованием json.loads. Теперь нужная нам лемма встроена в пару слоев внутри parsed_dict. Здесь нам нужно только значение леммы из каждого dict.

lemma_list = [v for d in parsed_dict['sentences'][0]['tokens'] for k,v in d.items() if k == 'lemma']
" ".join(lemma_list)
#> 'the striped bat be hang on they foot and eat best fish'

Обобщим эту замечательную функцию для обработки больших абзацев.

from stanfordcorenlp import StanfordCoreNLP
import json, string

def lemmatize_corenlp(conn_nlp, sentence):
    props = {
        'annotators': 'pos,lemma',
        'pipelineLanguage': 'en',
        'outputFormat': 'json'
    }

    # tokenize into words
    sents = conn_nlp.word_tokenize(sentence)

    # remove punctuations from tokenised list
    sents_no_punct = [s for s in sents if s not in string.punctuation]

    # form sentence
    sentence2 = " ".join(sents_no_punct)

    # annotate to get lemma
    parsed_str = conn_nlp.annotate(sentence2, properties=props)
    parsed_dict = json.loads(parsed_str)

    # extract the lemma for each word
    lemma_list = [v for d in parsed_dict['sentences'][0]['tokens'] for k,v in d.items() if k == 'lemma']

    # form sentence and return it
    return " ".join(lemma_list)


# make the connection and call `lemmatize_corenlp`
nlp = StanfordCoreNLP('http://localhost', port=9000, timeout=30000)
lemmatize_corenlp(conn_nlp=nlp, sentence=sentence)
#> 'the striped bat be hang on they foot and eat best fish'

Gensim Лемматизация

Gensim предоставляет средства лемматизации на основе пакета pattern. Используем метод lemmatize() в модуле utils. По умолчанию lemmatize() допускает только теги «JJ», «VB», «NN» и «RB».

from gensim.utils import lemmatize
sentence = "The striped bats were hanging on their feet and ate best fishes"
lemmatized_out = [wd.decode('utf-8').split('/')[0] for wd in lemmatize(sentence)]
#> ['striped', 'bat', 'be', 'hang', 'foot', 'eat', 'best', 'fish']

TreeTagger

Treetagger является POS тегером для многих языков. И он также предоставляем возможности лемматизации.

Пример его использования

# pip install treetaggerwrapper

import treetaggerwrapper as ttpw
tagger = ttpw.TreeTagger(TAGLANG='en', TAGDIR='/Users/ecom-selva.p/Documents/MLPlus/11_Lemmatization/treetagger')
tags = tagger.tag_text("The striped bats were hanging on their feet and ate best fishes")
lemmas = [t.split('\t')[-1] for t in tags]
#> ['the', 'striped', 'bat', 'be', 'hang', 'on', 'their', 'foot', 'and', 'eat', 'good', 'fish']

Treetagger хорошо справляется с преобразованием «best» в «good», а также в другие слова. Для большей информации о пакете обратитесь к документации TreeTaggerWrapper.

Сравнение NLTK, TextBlob, spaCy, Pattern и Stanford CoreNLP

Давайте запустим лемматизацию, используя 5 пакетов для следующих предложений, и сравним вывод.

sentence = """Following mice attacks, caring farmers were marching to Delhi for better living conditions. 
Delhi police on Tuesday fired water cannons and teargas shells at protesting farmers as they tried to 
break barricades with their cars, automobiles and tractors."""

# NLTK
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
pprint(" ".join([lemmatizer.lemmatize(w, get_wordnet_pos(w)) for w in nltk.word_tokenize(sentence) if w not in string.punctuation]))
# ('Following mouse attack care farmer be march to Delhi for well living '
#  'condition Delhi police on Tuesday fire water cannon and teargas shell at '
#  'protest farmer a they try to break barricade with their car automobile and '
#  'tractor')

# Spacy
import spacy
nlp = spacy.load('en', disable=['parser', 'ner'])
doc = nlp(sentence)
pprint(" ".join([token.lemma_ for token in doc]))
# ('follow mice attack , care farmer be march to delhi for good living condition '
#  '. delhi police on tuesday fire water cannon and teargas shell at protest '
#  'farmer as -PRON- try to break barricade with -PRON- car , automobile and '
#  'tractor .')

# TextBlob
pprint(lemmatize_with_postag(sentence))
# ('Following mouse attack care farmer be march to Delhi for good living '
#  'condition Delhi police on Tuesday fire water cannon and teargas shell at '
#  'protest farmer a they try to break barricade with their car automobile and '
#  'tractor')

# Pattern
from pattern.en import lemma
pprint(" ".join([lemma(wd) for wd in sentence.split()]))
# ('follow mice attacks, care farmer be march to delhi for better live '
#  'conditions. delhi police on tuesday fire water cannon and tearga shell at '
#  'protest farmer a they try to break barricade with their cars, automobile and '
#  'tractors.')

# Stanford
pprint(lemmatize_corenlp(conn_nlp=conn_nlp, sentence=sentence))
# ('follow mouse attack care farmer be march to Delhi for better living '
#  'condition Delhi police on Tuesday fire water cannon and tearga shell at '
#  'protest farmer as they try to break barricade with they car automobile and '
#  'tractor')

Оригинал:
https://www.machinelearningplus.com/nlp/lemmatization-examples-python/

Была ли вам полезна эта статья?
[15 / 4.3]

Spread the love
Editorial Team

View Comments

  • Отличная статья! Спасибо вам. Только вы не прикрепили инструмент по лемматицзации. Я пол часа искал нормальный. Вот https://c-wd.ru/tools/lemma/ может кому пригодится

  • А что по поводу обработки текстов на русском языке? Подскажите, пожалуйста, какие нюансы,кажется все это по умолчанию не работает на русском.

    • Алекс, добрый вечер
      Для обработки текстов на русском языке используется либо отдельные библиотеки, либо в больших библиотеках есть способ настройки лемматизации, стемминга и тд для русского языка
      Касаемо отдельных библиотек, вы можете присмотреться к проекту Natasha от наших СНГ разрабов

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

11 месяцев ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

11 месяцев ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

2 года ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

2 года ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

2 года ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

2 года ago