Как мне выполнить словесный стемминг или лемматизацию?


111

Я пробовал PorterStemmer и Snowball, но оба работают не со всеми словами, пропустив некоторые очень распространенные.

Мои тестовые слова: « кошки, бегущие, бегали, кактусы, кактусы, сообщества кактусов », и оба получают менее половины правильных ответов.

Смотрите также:


28
Разве это не должны быть кактусы?
MSalters

3
Просто чтобы сделать круговую ссылку на исходный вопрос, опубликованный на Reddit: как программно выполнить стемминг? (например, «есть», чтобы «поесть», «кактусы», чтобы «кактус»). Разместите это здесь, потому что комментарии содержат полезную информацию.
Renaud Bompuis

Ответы:


143

Если вы знаете Python, в The Natural Language Toolkit (NLTK) есть очень мощный лемматизатор, использующий WordNet .

Обратите внимание, что если вы впервые используете этот лемматизатор, вы должны загрузить корпус перед его использованием. Это можно сделать:

>>> import nltk
>>> nltk.download('wordnet')

Вам нужно сделать это только один раз. Предполагая, что вы скачали корпус, он работает следующим образом:

>>> from nltk.stem.wordnet import WordNetLemmatizer
>>> lmtzr = WordNetLemmatizer()
>>> lmtzr.lemmatize('cars')
'car'
>>> lmtzr.lemmatize('feet')
'foot'
>>> lmtzr.lemmatize('people')
'people'
>>> lmtzr.lemmatize('fantasized','v')
'fantasize'

В модуле nltk.stem есть и другие лемматизаторы , но я сам их не пробовал.


11
О, грустно ... прежде чем я научился искать ТАК, я реализовал свою собственную!
Крис Пфол,

12
Не забудьте установить корпус перед первым использованием nltk ! velvetcache.org/2010/03/01/…
Матье Родич

1
Что ж, здесь используется какой-то недетерминированный алгоритм, такой как Porter Stemmer, потому что, если вы попробуете его dies, он даст вам dyвместо die. Нет ли какого-нибудь жестко запрограммированного стеммерного словаря?
SexyBeast 07

3
любая идея, какие слова WordNetLemmatizerнеправильно лемматизируются?
alvas

21
nltk WordNetLemmatizer требует в качестве аргумента тег pos. По умолчанию это «n» (вместо существительного). Так что для глаголов это не сработает. Если теги POS недоступны, простой (но специальный) подход состоит в том, чтобы выполнить лемматизацию дважды, один для 'n', а другой для 'v' (что означает глагол), и выбрать результат, отличный от исходное слово (обычно короче по длине, но «бег» и «бег» имеют одинаковую длину). Похоже, нам не нужно беспокоиться о 'adj', 'adv', 'prepare' и т. Д., Поскольку они в некотором смысле уже находятся в исходной форме.
Fashandge

29

Для лемматизации я использую stanford nlp. Я сталкивался с похожей проблемой в последние несколько дней. Все благодаря stackoverflow, который помог мне решить проблему.

import java.util.*; 
import edu.stanford.nlp.pipeline.*;
import edu.stanford.nlp.ling.*; 
import edu.stanford.nlp.ling.CoreAnnotations.*;  

public class example
{
    public static void main(String[] args)
    {
        Properties props = new Properties(); 
        props.put("annotators", "tokenize, ssplit, pos, lemma"); 
        pipeline = new StanfordCoreNLP(props, false);
        String text = /* the string you want */; 
        Annotation document = pipeline.process(text);  

        for(CoreMap sentence: document.get(SentencesAnnotation.class))
        {    
            for(CoreLabel token: sentence.get(TokensAnnotation.class))
            {       
                String word = token.get(TextAnnotation.class);      
                String lemma = token.get(LemmaAnnotation.class); 
                System.out.println("lemmatized version :" + lemma);
            }
        }
    }
}

Также может быть хорошей идеей использовать стоп-слова, чтобы минимизировать выходные леммы, если они будут использоваться позже в классификаторе. Пожалуйста, обратите внимание на расширение coreNlp, написанное Джоном Конвеллом.


извините за поздний ответ .. я решил эту проблему только сейчас! :)
CTsiddharth 02

1
Строка pipeline = new ... у меня не компилируется. Если я изменю его на «StanfordCoreNLP pipelne = new ...», он компилируется. Это правильно?
Adam_G

Да, сначала вы должны объявить конвейер var. Стэнфордское НЛП также можно использовать из командной строки, поэтому вам не нужно программировать, вы просто создаете файл свойств и загружаете им исполняемые файлы. Прочтите документы: nlp.stanford.edu/software/corenlp.shtml
Jindra Helcl,

24

Я попробовал ваш список терминов на этом демо-сайте снежного кома, и результаты выглядят нормально ....

  • кошки -> кошка
  • бег -> бег
  • побежал -> побежал
  • кактус -> кактус
  • кактусы -> кактусы
  • сообщество -> communiti
  • сообщества -> communiti

Предполагается, что стеммер сводит изменяемые формы слов к некоторому общему корню. Сделать этот корень «правильным» словарным словом - не совсем задача стеммера. Для этого вам нужно взглянуть на морфологические / орфографические анализаторы .

Я думаю, что этот вопрос примерно об одном и том же, и Каарел ответил на этот вопрос, откуда я взял вторую ссылку.


6
Дело в том, что stem ("updates") == stem ("update"), что он и делает (update -> updat)
Stompchicken

1
Программа может выполнять функцию stem (x) == stem (y), но это не дает полного ответа на вопрос
пользователь

11
Будьте осторожны с жаргоном, основа не является базовой формой слова. Если вам нужна базовая форма, вам понадобится лемматизатор. Основа - это самая большая часть слова, не содержащая префиксов или суффиксов. Основа слова update действительно «обновлено». Слова создаются из корней путем добавления окончаний и суффиксов, например, updat-e или updat-ing. ( en.wikipedia.org/wiki/Word_stem )
Jindra Helcl

20

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

См. « Стеммеры против лемматизаторов»

Вот пример с Python NLTK:

>>> sent = "cats running ran cactus cactuses cacti community communities"
>>> from nltk.stem import PorterStemmer, WordNetLemmatizer
>>>
>>> port = PorterStemmer()
>>> " ".join([port.stem(i) for i in sent.split()])
'cat run ran cactu cactus cacti commun commun'
>>>
>>> wnl = WordNetLemmatizer()
>>> " ".join([wnl.lemmatize(i) for i in sent.split()])
'cat running ran cactus cactus cactus community community'

3
Как упоминалось ранее, WordNetLemmatizer«s lemmatize()может принимать POS тег. Итак, из вашего примера: " ".join([wnl.lemmatize(i, pos=VERB) for i in sent.split()])дает 'cat run run cactus cactuses cacti community communities'.
Ник Руис

@NickRuiz, я думаю, вы имели в виду pos=NOUN? BTW: Давно не виделись, надеюсь, мы скоро встретимся на конференции =)
alvas

на самом деле, нет (хотя, надеюсь, «да» конференциям). Потому что, если вы установите, pos=VERBвы выполняете лемматизацию только для глаголов. Существительные остались прежними. Мне просто нужно было написать немного моего собственного кода, чтобы вращаться вокруг реальных тегов POS Penn Treebank, чтобы применить правильную лемматизацию к каждому токену. Кроме того, WordNetLemmatizerнеприятно лемматизировать токенизатор по умолчанию nltk. Так что примеры вроде does n'tне лемматизировать do not.
Ник Руис,

но, но port.stem("this")производит thiи port.stem("was") wa, даже если для каждого предоставлен правильный pos.
Lerner Zhang

Стеммер не возвращает лингвистически правильные результаты. Просто чтобы текст был более «плотным» (т.е. содержал меньше словарного запаса). См. Stackoverflow.com/questions/17317418/stemmers-vs-lemmatizers и stackoverflow.com/questions/51943811/…
alvas

8

Официальная страница Мартина Портера содержит Porter Stemmer на PHP, а также на других языках .

Если вы действительно серьезно относитесь к хорошему стеммингу, хотя вам нужно будет начать с чего-то вроде алгоритма Портера, доработайте его, добавив правила для исправления неправильных случаев, общих для вашего набора данных, а затем, наконец, добавьте к правилам множество исключений. . Это может быть легко реализовано с помощью пар ключ / значение (dbm / hash / dictionaries), где ключ - это слово для поиска, а значение - это слово с корнем для замены оригинала. Коммерческая поисковая машина, над которой я когда-то работал, закончила с 800 некоторыми исключениями из модифицированного алгоритма Портера.


Идеальное решение могло бы узнать эти ожидания автоматически. Был ли у вас опыт работы с такой системой?
Malcolm

Нет. В нашем случае индексируемыми документами были кодекс и постановления для определенной области права, и были десятки (людей) редакторов, анализирующих индексы на предмет любых плохих основ.
Ван Гейл


5

Основываясь на различных ответах на Stack Overflow и блогах, с которыми я сталкивался, это метод, который я использую, и, похоже, он довольно хорошо возвращает реальные слова. Идея состоит в том, чтобы разбить входящий текст на массив слов (используйте тот метод, который вам нравится), а затем найти части речи (POS) для этих слов и использовать их, чтобы помочь определить и лемматизировать слова.

Вышеупомянутый образец не работает слишком хорошо, потому что POS не может быть определен. Однако, если использовать реальное предложение, все работает намного лучше.

import nltk
from nltk.corpus import wordnet

lmtzr = nltk.WordNetLemmatizer().lemmatize


def get_wordnet_pos(treebank_tag):
    if treebank_tag.startswith('J'):
        return wordnet.ADJ
    elif treebank_tag.startswith('V'):
        return wordnet.VERB
    elif treebank_tag.startswith('N'):
        return wordnet.NOUN
    elif treebank_tag.startswith('R'):
        return wordnet.ADV
    else:
        return wordnet.NOUN


def normalize_text(text):
    word_pos = nltk.pos_tag(nltk.word_tokenize(text))
    lemm_words = [lmtzr(sw[0], get_wordnet_pos(sw[1])) for sw in word_pos]

    return [x.lower() for x in lemm_words]

print(normalize_text('cats running ran cactus cactuses cacti community communities'))
# ['cat', 'run', 'ran', 'cactus', 'cactuses', 'cacti', 'community', 'community']

print(normalize_text('The cactus ran to the community to see the cats running around cacti between communities.'))
# ['the', 'cactus', 'run', 'to', 'the', 'community', 'to', 'see', 'the', 'cat', 'run', 'around', 'cactus', 'between', 'community', '.']


2

Это выглядит интересно: MIT Java WordnetStemmer: http://projects.csail.mit.edu/jwi/api/edu/mit/jwi/morph/WordnetStemmer.html


3
Добро пожаловать в SO, и спасибо за ваш пост, +1. Было бы замечательно, если бы вы могли сделать несколько комментариев по поводу использования, производительности этого стеммера и т. Д. Просто ссылка обычно не считается хорошим ответом.
jogojapan

2

Взгляните на LemmaGen - библиотеку с открытым исходным кодом, написанную на C # 3.0.

Результаты для ваших тестовых слов ( http://lemmatise.ijs.si/Services )

  • кошки -> кошка
  • Бег
  • побежал -> беги
  • кактус
  • кактусы -> кактусы
  • кактусы -> кактус
  • сообщество
  • сообщества -> сообщество

2

Пакеты верхнего питона (в произвольном порядке) для лемматизации являются: spacy, nltk, gensim, pattern, CoreNLPиTextBlob . Я предпочитаю реализацию spaCy и gensim (основанную на шаблоне), потому что они идентифицируют POS-тег слова и автоматически присваивают соответствующую лемму. Приводятся более релевантные леммы, сохраняя смысл.

Если вы планируете использовать nltk или TextBlob, вам нужно позаботиться о том, чтобы вручную найти правильный тег POS и найти правильную лемму.

Пример лемматизации с помощью spaCy:

# Run below statements in terminal once. 
pip install spacy
spacy download en

import spacy

# Initialize spacy 'en' model
nlp = spacy.load('en', disable=['parser', 'ner'])

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

# Parse
doc = nlp(sentence)

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

Пример лемматизации с помощью Gensim:

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']

Приведенные выше примеры были заимствованы из этой страницы лемматизации .


1

Сделайте поиск по Lucene, я не уверен, есть ли порт PHP, но я знаю, что Lucene доступен для многих платформ. Lucene - это библиотека индексации и поиска OSS (от Apache). Естественно, у него и у статистов сообщества есть на что посмотреть. По крайней мере, вы можете узнать, как это делается на одном языке, чтобы вы могли перевести «идею» на PHP.


1

Могу я процитировать свой ответ на вопрос, упомянутый StompChicken:

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

Поскольку они не понимают язык и не используют словарь терминов, у них нет возможности распознать и отреагировать соответствующим образом на нестандартные случаи, такие как «бегать» / «бегать».

Если вам нужно обрабатывать нерегулярные случаи, вам нужно либо выбрать другой подход, либо дополнить свой стемминг собственным пользовательским словарем исправлений, которые будут запускаться после того, как стеммер сделает свое дело.



1

Вы можете использовать стеммер Морфа. UW загрузил морфа-стеммер в Maven Central, если вы планируете использовать его из Java-приложения. Есть обертка, которая значительно упрощает использование. Вам просто нужно добавить его как зависимость и использовать edu.washington.cs.knowitall.morpha.MorphaStemmerкласс. Экземпляры являются потокобезопасными (в исходном JFlex без необходимости были поля классов для локальных переменных). Создайте экземпляр класса, запустите morphaи слово, которое вы хотите остановить.

new MorphaStemmer().morpha("climbed") // goes to "climb"

0

.Net lucene имеет встроенный стеммер портера. Вы можете попробовать это. Но обратите внимание, что портер при выводе леммы не учитывает контекст слова. (Просмотрите алгоритм и его реализацию, и вы увидите, как он работает)


0

Мартин Портер написал Snowball (язык для алгоритмов стемминга) и переписал «English Stemmer» на Snowball. Есть английский Stemmer для C и Java.

Он прямо заявляет, что Porter Stemmer был повторно реализован только по историческим причинам, поэтому проверка корректности стемминга против Porter Stemmer даст вам результаты, которые вы (должны) уже знать.

Из http://tartarus.org/~martin/PorterStemmer/index.html (выделено мной)

Стеммер Porter следует рассматривать как « замороженный », то есть строго определенный и не подлежащий дальнейшей модификации. В качестве стеммера он немного уступает стеммеру Snowball English или Porter2, производному от него, и который время от времени подвергается улучшениям. Поэтому для практической работы рекомендуется новый стеммер Snowball. Стеммер Портера подходит для исследований IR, включающих стемминг, где эксперименты должны быть точно повторяемыми.

Доктор Портер предлагает использовать стеммеры English или Porter2 вместо стеммера Porter. Английский стеммер - это то, что фактически используется на демонстрационном сайте, как ранее ответил @StompChicken.


0

В Java я использую tartargus-snowball для слов

Maven:

<dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-snowball</artifactId>
        <version>3.0.3</version>
        <scope>test</scope>
</dependency>

Образец кода:

SnowballProgram stemmer = new EnglishStemmer();
String[] words = new String[]{
    "testing",
    "skincare",
    "eyecare",
    "eye",
    "worked",
    "read"
};
for (String word : words) {
    stemmer.setCurrent(word);
    stemmer.stem();
    //debug
    logger.info("Origin: " + word + " > " + stemmer.getCurrent());// result: test, skincar, eyecar, eye, work, read
}

0

Попробуйте вот это: http://www.twinword.com/lemmatizer.php

Я ввел ваш запрос в демонстрацию "cats running ran cactus cactuses cacti community communities"и получил ["cat", "running", "run", "cactus", "cactus", "cactus", "community", "community"]необязательный флагALL_TOKENS .

Образец кода

Это API, поэтому вы можете подключиться к нему из любой среды. Вот как может выглядеть вызов PHP REST.

// These code snippets use an open-source library. http://unirest.io/php
$response = Unirest\Request::post([ENDPOINT],
  array(
    "X-Mashape-Key" => [API KEY],
    "Content-Type" => "application/x-www-form-urlencoded",
    "Accept" => "application/json"
  ),
  array(
    "text" => "cats running ran cactus cactuses cacti community communities"
  )
);

0

Я настоятельно рекомендую использовать Spacy (базовый анализ текста и теги) и Textacy. (обработка текста более высокого уровня, построенная на основе Spacy).

Лемматизированные слова доступны по умолчанию в Spacy в качестве .lemma_атрибута токена, а текст может быть лемматизирован при выполнении множества других операций предварительной обработки текста с помощью текстового преобразования. Например, при создании набора терминов или слов или, как правило, непосредственно перед выполнением некоторой обработки, которая требует этого.

Я рекомендую вам проверить оба, прежде чем писать какой-либо код, так как это может сэкономить вам много времени!


-1
df_plots = pd.read_excel("Plot Summary.xlsx", index_col = 0)
df_plots
# Printing first sentence of first row and last sentence of last row
nltk.sent_tokenize(df_plots.loc[1].Plot)[0] + nltk.sent_tokenize(df_plots.loc[len(df)].Plot)[-1]

# Calculating length of all plots by words
df_plots["Length"] = df_plots.Plot.apply(lambda x : 
len(nltk.word_tokenize(x)))

print("Longest plot is for season"),
print(df_plots.Length.idxmax())

print("Shortest plot is for season"),
print(df_plots.Length.idxmin())



#What is this show about? (What are the top 3 words used , excluding the #stop words, in all the #seasons combined)

word_sample = list(["struggled", "died"])
word_list = nltk.pos_tag(word_sample)
[wnl.lemmatize(str(word_list[index][0]), pos = word_list[index][1][0].lower()) for index in range(len(word_list))]

# Figure out the stop words
stop = (stopwords.words('english'))

# Tokenize all the plots
df_plots["Tokenized"] = df_plots.Plot.apply(lambda x : nltk.word_tokenize(x.lower()))

# Remove the stop words
df_plots["Filtered"] = df_plots.Tokenized.apply(lambda x : (word for word in x if word not in stop))

# Lemmatize each word
wnl = WordNetLemmatizer()
df_plots["POS"] = df_plots.Filtered.apply(lambda x : nltk.pos_tag(list(x)))
# df_plots["POS"] = df_plots.POS.apply(lambda x : ((word[1] = word[1][0] for word in word_list) for word_list in x))
df_plots["Lemmatized"] = df_plots.POS.apply(lambda x : (wnl.lemmatize(x[index][0], pos = str(x[index][1][0]).lower()) for index in range(len(list(x)))))



#Which Season had the highest screenplay of "Jesse" compared to "Walt" 
#Screenplay of Jesse =(Occurences of "Jesse")/(Occurences of "Jesse"+ #Occurences of "Walt")

df_plots.groupby("Season").Tokenized.sum()

df_plots["Share"] = df_plots.groupby("Season").Tokenized.sum().apply(lambda x : float(x.count("jesse") * 100)/float(x.count("jesse") + x.count("walter") + x.count("walt")))

print("The highest times Jesse was mentioned compared to Walter/Walt was in season"),
print(df_plots["Share"].idxmax())
#float(df_plots.Tokenized.sum().count('jesse')) * 100 / #float((df_plots.Tokenized.sum().count('jesse') + #df_plots.Tokenized.sum().count('walt') + #df_plots.Tokenized.sum().count('walter')))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.