Як зробити слово «Стермінг» чи «лематизацію»?


111

Я спробував PorterStemmer і Snowball, але обидва не працюють над усіма словами, пропускаючи деякі дуже поширені.

Мої тестові слова: " коти, що бігали, спіткали кактуси спільнот кактусів ", і обидва отримують менше половини.

Дивитися також:


28
Не повинні це бути кактуси?
MSalters

3
Просто зробити кругле посилання на оригінальне запитання, розміщене на Reddit: Як я можу програматично робити створіння? (наприклад, "їсти", щоб "з'їсти", "кактуси" - "кактуси") Опублікувати його тут, оскільки коментарі містять корисну інформацію.
Рено Бомпуа

Відповіді:


143

Якщо ви знаєте Python, Інструментарій з природних мов (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
Ну, цей використовує якийсь недетермінований алгоритм, як Портер Стеммер, бо якщо ви спробуєте це dies, він дає вам dyзамість цього die. Хіба не існує якогось словника з твердим кодом
SexyBeast

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

21
nltk WordNetLemmatizer вимагає тег pos як аргумент. За замовчуванням це 'n' (означає іменник). Так це не спрацює правильно для дієслів. Якщо теги POS недоступні, простий (але ad-hoc) підхід полягає в тому, щоб зробити лематизацію двічі, один для 'n', а другий для 'v' (стоїть за дієсловом), і вибрати результат, який відрізняється від оригінальне слово (як правило, коротше за довжиною, але "біг" і "біг" мають однакову довжину). Здається, нам не потрібно турбуватися про "adj", "adv", "prep" тощо, оскільки вони вже є в початковому вигляді.
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

1
Рядок "трубопровід = новий ..." для мене не складається. Якщо я зміню його на 'StanfordCoreNLP pipelne = new ...', він компілюється. Os це правильно?
Adam_G

Так, спочатку потрібно оголосити вар. Трубопроводу. Stanford NLP можна використовувати і з командного рядка, тому вам не потрібно робити програмування, ви просто створіть файл властивостей і подавайте ним виконуючі файли. Прочитайте документи: nlp.stanford.edu/software/corenlp.shtml
Jindra Helcl

24

Я спробував ваш список термінів на цьому демо-сайті Snowball, і результати виглядають нормально ....

  • коти -> кіт
  • біг -> біг
  • побіг -> побіг
  • кактус -> кактус
  • кактуси -> кактуси
  • спільнота -> комуніти
  • громади -> комуніти

Стовбур повинен перетворити складні форми слів до якогось загального кореня. Насправді завдання стовбура - зробити цей корінь «правильним» словниковим словом. Для цього вам потрібно переглянути морфологічні / ортографічні аналізатори .

Я вважаю, що це питання стосується більш-менш того самого, і відповідь Каарела на це запитання полягає в тому, звідки я взяв друге посилання.


6
Справа в тому, що стовбур ("оновлення") == стовбур ("оновлення"), який він робить (оновлення -> оновлення)
Stompchicken

1
Програмне забезпечення може робити ствол (x) == stem (y), але це не відповідає на питання повністю
користувач

11
Обережно з лінгвом, стебло не є базовою формою слова. Якщо ви хочете базову форму, вам потрібен лематизатор. Стебло - це найбільша частина слова, яка не містить префіксів і суфіксів. Ствол оновлення слова справді "оновлений". Слова утворені від стовбурів, додаючи закінчення та суфікси, наприклад, updat-e чи оновлення. ( 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: Давно не
бачимось

насправді, ні (сподіваюся, "так" для конференцій, хоча). Тому що якщо ви встановите, pos=VERBви робите лише лематизацію на дієсловах. Іменники залишаються однаковими. Мені просто довелося написати якийсь власний код, щоб обернутися навколо власних тегів POS Penn Treebank POS, щоб застосувати правильну лематизацію до кожного маркера. Також WordNetLemmatizerсмердить на лематизацію токенізатора за замовчуванням nltk. Тому приклади на кшталт does n'tне лематизуються do not.
Нік Руїз

але, але port.stem("this")виробляє thiі port.stem("was") waнавіть тоді, коли для кожного передбачено правильну позицію.
Лернер Чжан

Зустріч не повертає лінгвістично звукових результатів. Просто зробити текст більш "щільним" (тобто містити менше vocab). Див stackoverflow.com/questions/17317418/stemmers-vs-lemmatizers і stackoverflow.com/questions/51943811 / ...
Alvas

8

Офіційна сторінка Мартіна Портера містить Портер Стеммер на PHP , а також інших мовах .

Якщо ви дійсно серйозно ставитеся до хорошого виходу, хоча вам потрібно буде почати щось із на зразок алгоритму Портера, уточнити його, додавши правила, щоб виправити неправильні випадки, загальні для вашого набору даних, а потім, нарешті, додати масу винятків із правил . Це можна легко реалізувати за допомогою пар ключів / значень (dbm / хеш / словники), де ключовим є слово, яке слід шукати, а значення - стовбурне слово для заміни оригіналу. Комерційна пошукова система, над якою я працював одного разу, закінчилася 800 винятками з модифікованого алгоритму Портера.


Ідеальне рішення дізналося б ці очікування автоматично. Чи мали ви досвід роботи з такою системою?
Малькольм

Ні. У нашому випадку документи, що індексуються, були кодексами та нормами для певної галузі права, і було десятки (людських) редакторів, які аналізували індекси на предмет поганих результатів.
Van Gale


5

На основі різних відповідей на переповнення стека та блоги, які я натрапив, це метод, який я використовую, і, здається, повертає реальні слова досить добре. Ідея полягає в тому, щоб розділити текст, що вводиться, на масив слів (використовуйте той спосіб, який ви хочете), а потім знайти частини мови (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

Ви можете використати стовбур Morpha. Компанія 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.

Він прямо зазначає, що штемпер Портера був повторно доповнений лише з історичних причин, тому тестування коректності на кореневій носії отримає результати, про які ви (повинні) вже знаєте.

З http://tartarus.org/~martin/PorterStemmer/index.html (моє наголос)

Стебла Портера слід розцінювати як « заморожений », тобто суворо визначений і не підлягає подальшій модифікації. Що стосується стовбура, він трохи поступається Англійському снігуру або Портер2, що походить від нього, і який зазнає періодичних удосконалень. Для практичної роботи, отже, рекомендується новий стовбур Snowball. Стовбур Портера підходить для ІР-досліджень, пов'язаних із встановленням результатів, де експерименти потрібно точно повторювати.

Доктор Портер пропонує використовувати англійські або Porter2 стовбури замість стомера Портера. Англійська стовбур - це те, що насправді використовується на демонстраційному сайті, як @StompChicken відповів раніше.


0

На Яві я використовую татаргус-сніжок, щоб переслідувати слова

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.