Wie mache ich Wortstemming oder Lemmatisierung?


111

Ich habe PorterStemmer und Snowball ausprobiert, aber beide funktionieren nicht bei allen Wörtern, da einige sehr häufig fehlen.

Meine Testwörter sind: " Katzen, die Kakteen-Kakteen-Kakteen-Communitys laufen lassen ", und beide bekommen weniger als die Hälfte richtig.

Siehe auch:


28
Sollte das nicht Kakteen sein?
MSalters

3
Nur um einen Zirkelverweis auf die ursprüngliche Frage zu machen, die auf Reddit veröffentlicht wurde: Wie mache ich programmgesteuert Stemming? (zB "essen" bis "essen", "Kakteen" bis "Kaktus") Hier posten, da die Kommentare nützliche Informationen enthalten.
Renaud Bompuis

Antworten:


143

Wenn Sie Python kennen, verfügt das Natural Language Toolkit (NLTK) über einen sehr leistungsstarken Lemmatizer, der WordNet verwendet .

Beachten Sie, dass Sie den Korpus herunterladen müssen, bevor Sie ihn verwenden, wenn Sie diesen Lemmatizer zum ersten Mal verwenden. Dies kann erfolgen durch:

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

Sie müssen dies nur einmal tun. Angenommen, Sie haben den Korpus jetzt heruntergeladen, funktioniert dies folgendermaßen:

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

Es gibt andere Lemmatizer im Modul nltk.stem , aber ich habe sie selbst nicht ausprobiert.


11
Oh traurig ... bevor ich wusste, dass ich suchen muss, habe ich meine eigene implementiert!
Chris Pfohl

12
Vergessen Sie nicht, den Korpus zu installieren, bevor Sie nltk zum ersten Mal verwenden! velvetcache.org/2010/03/01/…
Mathieu Rodic

1
Nun, dieser verwendet einen nicht deterministischen Algorithmus wie Porter Stemmer, denn wenn Sie ihn ausprobieren dies, erhalten Sie dystattdessen die. Gibt es nicht eine Art fest codiertes Stemmer-Wörterbuch?
SexyBeast

3
Irgendeine Idee, was sind die Wörter, die WordNetLemmatizerfälschlicherweise lemmatisieren?
Alvas

21
nltk WordNetLemmatizer benötigt ein pos-Tag als Argument. Standardmäßig ist es 'n' (steht für Nomen). Es wird also für Verben nicht richtig funktionieren. Wenn keine POS-Tags verfügbar sind, besteht ein einfacher (aber ad-hoc) Ansatz darin, die Lemmatisierung zweimal durchzuführen, eine für 'n' und die andere für 'v' (steht für Verb), und das Ergebnis auszuwählen, das sich von dem unterscheidet Originalwort (normalerweise kürzer, aber 'ran' und 'run' haben die gleiche Länge). Es scheint, dass wir uns keine Sorgen um 'adj', 'adv', 'prep' usw. machen müssen, da sie in gewissem Sinne bereits in der ursprünglichen Form vorliegen.
Fashandge

29

Ich benutze Stanford Nlp , um eine Lemmatisierung durchzuführen. Ich hatte in den letzten Tagen ein ähnliches Problem. Vielen Dank an stackoverflow, um das Problem zu lösen.

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

Es kann auch eine gute Idee sein, Stoppwörter zu verwenden, um Ausgabe-Lemmas zu minimieren, wenn sie später im Klassifikator verwendet werden. Bitte werfen Sie einen Blick auf die von John Conwell geschriebene coreNlp- Erweiterung.


Entschuldigung für die späte Antwort. Ich habe dieses Problem erst jetzt gelöst! :)
CTsiddharth

1
Die Zeile 'Pipeline = neu ...' wird für mich nicht kompiliert. Wenn ich es in 'StanfordCoreNLP pipelne = new ...' ändere, wird es kompiliert. Ist das richtig?
Adam_G

Ja, Sie müssen zuerst die Pipeline-Variable deklarieren. Das Stanford NLP kann auch über die Befehlszeile verwendet werden, sodass Sie keine Programmierung durchführen müssen. Sie erstellen lediglich die Eigenschaftendatei und geben die ausführbaren Dateien damit ein. Lesen Sie die Dokumente: nlp.stanford.edu/software/corenlp.shtml
Jindra Helcl

24

Ich habe Ihre Begriffsliste auf dieser Schneeball-Demo-Site ausprobiert und die Ergebnisse sehen in Ordnung aus.

  • Katzen -> Katze
  • Laufen -> Laufen
  • lief -> lief
  • Kaktus -> Kaktus
  • Kakteen -> Kakteen
  • Gemeinschaft -> Kommuniti
  • Gemeinschaften -> communiti

Ein Stemmer soll gebogene Wortformen auf eine gemeinsame Wurzel reduzieren. Es ist nicht wirklich die Aufgabe eines Stemmers, diese Wurzel zu einem "richtigen" Wörterbuchwort zu machen. Dazu müssen Sie sich morphologische / orthographische Analysegeräte ansehen .

Ich denke, bei dieser Frage geht es um mehr oder weniger dasselbe, und Kaarels Antwort auf diese Frage ist, woher ich den zweiten Link genommen habe.


6
Der Punkt ist, dass Stamm ("Updates") == Stamm ("Update"), was es tut (Update -> Update)
Stompchicken

1
Die Software kann stem (x) == stem (y) ausführen, aber das beantwortet die Frage nicht vollständig
Benutzer

11
Vorsicht mit dem Jargon, ein Stamm ist keine Grundform eines Wortes. Wenn Sie eine Basisform wünschen, benötigen Sie einen Lemmatizer. Ein Stamm ist der größte Teil eines Wortes, der keine Präfixe oder Suffixe enthält. Der Stamm eines Wortupdates ist in der Tat "updat". Die Wörter werden aus Stämmen durch Hinzufügen von Endungen und Suffixen erstellt, z. B. Aktualisierung oder Aktualisierung. ( en.wikipedia.org/wiki/Word_stem )
Jindra Helcl

20

Die Debatten zwischen Stemmer und Lemmatizer gehen weiter. Es geht darum, Präzision der Effizienz vorzuziehen. Sie sollten lemmatisieren, um sprachlich bedeutsame Einheiten zu erhalten, und einen Stamm verwenden, um minimalen Computersaft zu verwenden und dennoch ein Wort und seine Variationen unter demselben Schlüssel zu indizieren.

Siehe Stemmers vs Lemmatizers

Hier ist ein Beispiel mit 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
Wie bereits erwähnt, WordNetLemmatizerist lemmatize()kann ein POS - Tag nehmen. Also aus deinem Beispiel: " ".join([wnl.lemmatize(i, pos=VERB) for i in sent.split()])gibt 'cat run run cactus cactuses cacti community communities'.
Nick Ruiz

@ NickRuiz, ich denke du meintest pos=NOUN? Übrigens: Lange nicht gesehen, hoffentlich treffen wir uns bald in der Konferenz =)
Alvas

eigentlich nein (hoffentlich "Ja" zu Konferenzen). Denn wenn Sie einstellen, machen pos=VERBSie nur Lemmatisierung für Verben. Die Substantive bleiben gleich. Ich musste nur einen Teil meines eigenen Codes schreiben, um mich um die tatsächlichen Penn Treebank-POS-Tags zu drehen und die richtige Lemmatisierung auf jedes Token anzuwenden. Außerdem WordNetLemmatizerstinkt bei lemmatizing nltk die Standard tokenizer. Also Beispiele wie does n'tnicht lemmatisieren do not.
Nick Ruiz

aber, sondern port.stem("this")produziert thiund port.stem("was") wa, auch wenn für jeden die richtige pos vorgesehen ist.
Lerner Zhang

Ein Stemmer gibt keine sprachlich soliden Ausgaben zurück. Es geht nur darum, den Text "dichter" zu machen (dh weniger Vokabeln zu enthalten). Siehe stackoverflow.com/questions/17317418/stemmers-vs-lemmatizers und stackoverflow.com/questions/51943811/…
alvas

8

Die offizielle Seite von Martin Porter enthält einen Porter Stemmer in PHP sowie in anderen Sprachen .

Wenn Sie es wirklich ernst meinen mit gutem Stemming, obwohl Sie mit etwas wie dem Porter-Algorithmus beginnen müssen, verfeinern Sie ihn, indem Sie Regeln hinzufügen, um falsche Fälle zu beheben, die Ihrem Dataset gemeinsam sind, und fügen Sie schließlich viele Ausnahmen zu den Regeln hinzu . Dies kann leicht mit Schlüssel / Wert-Paaren (DBM / Hash / Wörterbücher) implementiert werden, wobei der Schlüssel das nachzuschlagende Wort und der Wert das Stammwort ist, das das Original ersetzt. Eine kommerzielle Suchmaschine, an der ich einmal gearbeitet habe, hatte 800 Ausnahmen von einem modifizierten Porter-Algorithmus.


Eine ideale Lösung würde diese Erwartungen automatisch lernen. Haben Sie Erfahrungen mit einem solchen System gemacht?
Malcolm

Nein. In unserem Fall waren die zu indexierenden Dokumente der Code und die Vorschriften für einen bestimmten Rechtsbereich, und es gab Dutzende von (menschlichen) Redakteuren, die die Indizes auf fehlerhafte Stämme analysierten.
Van Gale


5

Basierend auf verschiedenen Antworten auf Stack Overflow und Blogs, auf die ich gestoßen bin, ist dies die Methode, die ich verwende, und sie scheint ziemlich gut echte Wörter zurückzugeben. Die Idee ist, den eingehenden Text in eine Reihe von Wörtern aufzuteilen (verwenden Sie die gewünschte Methode) und dann die Wortarten (POS) für diese Wörter zu finden und diese zu verwenden, um die Wörter zu stammeln und zu lemmatisieren.

Das obige Beispiel funktioniert nicht so gut, da der POS nicht bestimmt werden kann. Wenn wir jedoch einen echten Satz verwenden, funktionieren die Dinge viel besser.

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

Schauen Sie sich LemmaGen an - Open Source-Bibliothek in C # 3.0.

Ergebnisse für Ihre Testwörter ( http://lemmatise.ijs.si/Services )

  • Katzen -> Katze
  • Laufen
  • ran -> run
  • Kaktus
  • Kakteen -> Kakteen
  • Kakteen -> Kakteen
  • Gemeinschaft
  • Gemeinschaften -> Gemeinschaft

2

Die Top - Python - Pakete (in keiner bestimmten Reihenfolge) für Lemmatisierung sind: spacy, nltk, gensim, pattern, CoreNLPund TextBlob. Ich bevorzuge die Implementierung von spaCy und gensim (basierend auf dem Muster), da sie das POS-Tag des Wortes identifizieren und das entsprechende Lemma automatisch zuweisen. Das gibt relevantere Deckspelzen, wobei die Bedeutung intakt bleibt.

Wenn Sie nltk oder TextBlob verwenden möchten, müssen Sie darauf achten, das richtige POS-Tag manuell und das richtige Lemma zu finden.

Lemmatisierungsbeispiel mit 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'

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

Die obigen Beispiele wurden auf dieser Lemmatisierungsseite ausgeliehen .


1

Suche nach Lucene, ich bin mir nicht sicher, ob es einen PHP-Port gibt, aber ich weiß, dass Lucene für viele Plattformen verfügbar ist. Lucene ist eine OSS-Indizierungs- und Suchbibliothek (von Apache). Natürlich könnten es und Community-Extras etwas Interessantes zu sehen haben. Zumindest können Sie lernen, wie es in einer Sprache gemacht wird, damit Sie die "Idee" in PHP übersetzen können


1

Wenn ich meine Antwort auf die Frage zitieren darf, die StompChicken erwähnt hat:

Das Kernproblem hierbei ist, dass Stemming-Algorithmen auf phonetischer Basis arbeiten, ohne die Sprache, mit der sie arbeiten, wirklich zu verstehen.

Da sie die Sprache nicht verstehen und nicht aus einem Wörterbuch von Begriffen hervorgehen, haben sie keine Möglichkeit, unregelmäßige Fälle wie "run" / "ran" zu erkennen und angemessen darauf zu reagieren.

Wenn Sie unregelmäßige Fälle behandeln müssen, müssen Sie entweder einen anderen Ansatz wählen oder Ihr Stemming mit Ihrem eigenen benutzerdefinierten Korrekturwörterbuch ergänzen, das ausgeführt wird, nachdem der Stemmer seine Aufgabe erledigt hat.



1

Sie könnten den Morpha-Stemmer verwenden. UW hat morpha stemmer in Maven central hochgeladen, wenn Sie es von einer Java-Anwendung aus verwenden möchten. Es gibt einen Wrapper, der die Verwendung erheblich vereinfacht. Sie müssen es nur als Abhängigkeit hinzufügen und die edu.washington.cs.knowitall.morpha.MorphaStemmerKlasse verwenden. Instanzen sind threadsicher (der ursprüngliche JFlex hatte unnötigerweise Klassenfelder für lokale Variablen). Instanziiere eine Klasse und renne morphaund das Wort, das du aufhalten möchtest.

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

0

.Net Lucene hat einen eingebauten Porter Stemmer. Sie können das versuchen. Beachten Sie jedoch, dass Porter Stemming bei der Ableitung des Lemmas den Wortkontext nicht berücksichtigt. (Gehen Sie den Algorithmus und seine Implementierung durch und Sie werden sehen, wie er funktioniert.)


0

Martin Porter schrieb Snowball (eine Sprache für Stemming-Algorithmen) und schrieb den "English Stemmer" in Snowball neu. Es gibt einen englischen Stemmer für C und Java.

Er erklärt ausdrücklich, dass der Porter Stemmer nur aus historischen Gründen neu implementiert wurde. Wenn Sie also die Richtigkeit des Stemmers gegen den Porter Stemmer testen, erhalten Sie Ergebnisse, die Sie bereits kennen (sollten).

Von http://tartarus.org/~martin/PorterStemmer/index.html (Schwerpunkt Mine)

Der Porter-Stemmer sollte als " gefroren " angesehen werden, dh streng definiert und nicht für weitere Modifikationen geeignet. Als Stemmer ist er dem daraus abgeleiteten Snowball English- oder Porter2-Stemmer etwas unterlegen und wird gelegentlich verbessert. Für die praktische Arbeit wird daher der neue Schneeball-Stemmer empfohlen. Der Porter-Stemmer eignet sich für IR-Forschungsarbeiten mit Stemming, bei denen die Experimente genau wiederholbar sein müssen.

Dr. Porter schlägt vor, die englischen oder Porter2-Stemmer anstelle des Porter-Stemmers zu verwenden. Der englische Stemmer wird tatsächlich auf der Demo-Site verwendet, wie @StompChicken zuvor geantwortet hat.


0

In Java benutze ich Tartargus-Schneeball, um Wörter aufzuhalten

Maven:

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

Beispielcode:

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

Versuchen Sie dies hier: http://www.twinword.com/lemmatizer.php

Ich habe Ihre Anfrage in die Demo eingegeben "cats running ran cactus cactuses cacti community communities"und ["cat", "running", "run", "cactus", "cactus", "cactus", "community", "community"]mit dem optionalen Flag erhalten ALL_TOKENS.

Beispielcode

Dies ist eine API, mit der Sie von jeder Umgebung aus eine Verbindung herstellen können. So könnte der PHP REST-Aufruf aussehen.

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

Ich empfehle dringend die Verwendung von Spacy (Parsen und Markieren von Basistext ) und Textacy (Textverarbeitung auf höherer Ebene, die auf Spacy aufbaut ).

Lemmatisierte Wörter sind in Spacy standardmäßig als Token- .lemma_Attribut verfügbar , und Text kann lemmatisiert werden, während viele andere Texte mit Textacy vorverarbeitet werden. Zum Beispiel beim Erstellen einer Tasche mit Begriffen oder Wörtern oder im Allgemeinen kurz vor dem Ausführen einer Verarbeitung, die dies erfordert.

Ich würde Sie ermutigen, beide zu überprüfen, bevor Sie Code schreiben, da dies Ihnen viel Zeit sparen kann!


-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')))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.