Come si esprimono le parole o le lemmatizzazioni?


111

Ho provato PorterStemmer e Snowball ma entrambi non funzionano su tutte le parole, mancandone alcune molto comuni.

Le mie parole di prova sono: " gatti che corrono gestivano cactus, cactus, comunità di cactus ", ed entrambi hanno meno della metà di ragione.

Guarda anche:


28
Non dovrebbero essere cactus?
MSalters

3
Solo per fare un riferimento circolare alla domanda originale pubblicata su Reddit: come faccio a stemming a livello di codice? (es. "mangiare" per "mangiare", "cactus" per "cactus") Inserendolo qui perché i commenti includono informazioni utili.
Renaud Bompuis

Risposte:


143

Se conosci Python, The Natural Language Toolkit (NLTK) ha un lemmatizer molto potente che fa uso di WordNet .

Nota che se stai usando questo lemmatizer per la prima volta, devi scaricare il corpus prima di usarlo. Questo può essere fatto da:

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

Devi farlo solo una volta. Supponendo che tu abbia scaricato il corpus, funziona in questo modo:

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

Ci sono altri lemmatizzatori nel modulo nltk.stem , ma non li ho provati da solo.


11
Oh triste ... prima che sapessi di cercare COSÌ ho implementato il mio!
Chris Pfohl

12
Non dimenticare di installare il corpus prima di utilizzare nltk per la prima volta! velvetcache.org/2010/03/01/…
Mathieu Rodic

1
Bene, questo utilizza un algoritmo non deterministico come Porter Stemmer, perché se lo provi dies, ti dà dyinvece di die. Non esiste una sorta di dizionario con stemmer hardcoded?
SexyBeast

3
qualche idea su quali siano le parole che WordNetLemmatizerlemmatizzano erroneamente?
alvas

21
nltk WordNetLemmatizer richiede un tag pos come argomento. Di default è 'n' (che sta per nome). Quindi non funzionerà correttamente per i verbi. Se i tag POS non sono disponibili, un approccio semplice (ma ad hoc) consiste nell'eseguire la lemmatizzazione due volte, una per "n" e l'altra per "v" (che sta per verbo), e scegliere il risultato che è diverso dal parola originale (di solito di lunghezza inferiore, ma "run" e "run" hanno la stessa lunghezza). Sembra che non dobbiamo preoccuparci di "adj", "adv", "prep", ecc., Poiché in un certo senso sono già nella forma originale.
Fashandge

29

Uso stanford nlp per eseguire la lemmatizzazione. Negli ultimi giorni ho riscontrato un problema simile. Tutto grazie a stackoverflow per aiutarmi a risolvere il problema.

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

Potrebbe anche essere una buona idea usare le parole non significative per ridurre al minimo i lemmi di output se viene utilizzato successivamente nel classificatore. Dai un'occhiata all'estensione coreNlp scritta da John Conwell.


scusa per la risposta tardiva .. ho risolto questo problema solo ora! :)
CTsiddharth

1
La riga "pipeline = new ..." non viene compilata per me. Se lo cambio in "StanfordCoreNLP pipelne = new ..." viene compilato. Os questo corretto?
Adam_G

Sì, devi prima dichiarare la pipeline var. L'NLP di Stanford può essere utilizzato anche dalla riga di comando, quindi non è necessario eseguire alcuna programmazione, è sufficiente creare il file delle proprietà e alimentare gli eseguibili con esso. Leggi i documenti: nlp.stanford.edu/software/corenlp.shtml
Jindra Helcl

24

Ho provato il tuo elenco di termini su questo sito demo a palle di neve e i risultati sembrano a posto ...

  • gatti -> gatto
  • correre -> correre
  • corse -> corse
  • cactus -> cactus
  • cactus -> cactus
  • community -> communiti
  • comunità -> communiti

Uno stemmer dovrebbe trasformare le forme flesse di parole in una radice comune. Non è proprio compito di uno stemmer rendere quella radice una parola del dizionario "corretta". Per questo è necessario guardare gli analizzatori morfologici / ortografici .

Penso che questa domanda sia più o meno la stessa cosa, e la risposta di Kaarel a questa domanda è da dove ho preso il secondo collegamento.


6
Il punto è quella radice ("updates") == stem ("update"), che fa (update ->
updat

1
Il software può eseguire stem (x) == stem (y) ma questo non risponde completamente alla domanda
utente

11
Attento al gergo, una radice non è la forma base di una parola. Se vuoi una forma base, hai bisogno di un lemmatizzatore. Una radice è la parte più grande di una parola che non contiene prefissi o suffissi. La radice di una parola aggiornamento è effettivamente "updat". Le parole vengono create dalle radici aggiungendo desinenze e suffissi, ad esempio updat-e o updat-ing. ( en.wikipedia.org/wiki/Word_stem )
Jindra Helcl

20

Il dibattito tra stemmer e lemmatizer continua. Si tratta di preferire la precisione all'efficienza. Dovresti lemmatizzare per ottenere unità linguisticamente significative e gambi per usare il minimo succo di calcolo e comunque indicizzare una parola e le sue variazioni nella stessa chiave.

Vedi Stemmers vs Lemmatizers

Ecco un esempio con 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
Come accennato prima, WordNetLemmatizer's lemmatize()può prendere un tag POS. Quindi dal tuo esempio: " ".join([wnl.lemmatize(i, pos=VERB) for i in sent.split()])'cat run run cactus cactuses cacti community communities'.
Nick Ruiz

@ NickRuiz, penso che intendevi pos=NOUN? BTW: è molto tempo che non ci vediamo, speriamo di incontrarci presto in conferenza =)
alvas

in realtà, no (si spera 'sì' alle conferenze, però). Perché se imposti pos=VERBfai la lemmatizzazione solo sui verbi. I nomi rimangono gli stessi. Ho solo dovuto scrivere un po 'del mio codice per ruotare attorno ai tag POS Penn Treebank effettivi per applicare la lemmatizzazione corretta a ciascun token. Inoltre, fa WordNetLemmatizerschifo a lemmatizzare il tokenizer predefinito di nltk. Quindi esempi come does n'tnon lemmatizzare a do not.
Nick Ruiz

ma, but port.stem("this")produce thie port.stem("was") wa, anche quando viene fornita la posizione corretta per ciascuno.
Lerner Zhang

Uno stemmer non restituisce output linguisticamente validi. Serve solo per rendere il testo più "denso" (cioè contenere meno vocaboli). Vedere stackoverflow.com/questions/17317418/stemmers-vs-lemmatizers e stackoverflow.com/questions/51943811/...
alvas

8

La pagina ufficiale di Martin Porter contiene un Porter Stemmer in PHP e in altre lingue .

Se sei davvero seriamente intenzionato a ottenere un buon stemming, anche se dovrai iniziare con qualcosa come l'algoritmo di Porter, perfezionalo aggiungendo regole per correggere i casi errati comuni al tuo set di dati e infine aggiungi molte eccezioni alle regole . Questo può essere facilmente implementato con coppie chiave / valore (dbm / hash / dizionari) dove la chiave è la parola da cercare e il valore è la parola con la radice per sostituire l'originale. Un motore di ricerca commerciale su cui ho lavorato una volta si è concluso con 800 eccezioni a un algoritmo di Porter modificato.


Una soluzione ideale apprenderebbe automaticamente queste aspettative. Hai avuto esperienza con un sistema del genere?
Malcolm

No. Nel nostro caso i documenti indicizzati erano il codice e i regolamenti per un'area specifica del diritto e c'erano dozzine di redattori (umani) che analizzavano gli indici per eventuali radici errate.
Van Gale


5

Sulla base di varie risposte su Stack Overflow e sui blog che ho incontrato, questo è il metodo che sto usando e sembra restituire parole reali abbastanza bene. L'idea è di dividere il testo in arrivo in una matrice di parole (usa il metodo che desideri), quindi trova le parti del discorso (POS) per quelle parole e usale per aiutare a arginare e lemmatizzare le parole.

Il tuo esempio sopra non funziona troppo bene, perché il POS non può essere determinato. Tuttavia, se usiamo una frase vera, le cose funzionano molto meglio.

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

Dai un'occhiata a LemmaGen - libreria open source scritta in C # 3.0.

Risultati per le tue parole di prova ( http://lemmatise.ijs.si/Services )

  • gatti -> gatto
  • in esecuzione
  • run -> run
  • cactus
  • cactus -> cactus
  • cactus -> cactus
  • Comunità
  • comunità -> comunità

2

I pacchetti superiore pitone (in nessun ordine specifico) per lemmatizzazione sono: spacy, nltk, gensim, pattern, CoreNLPe TextBlob. Preferisco l'implementazione di spaCy e gensim (basata su pattern) perché identificano il tag POS della parola e assegnano automaticamente il lemma appropriato. Il dà lemmi più rilevanti, mantenendo intatto il significato.

Se prevedi di utilizzare nltk o TextBlob, devi occuparti di trovare manualmente il tag POS corretto e di trovare il lemma giusto.

Esempio di lemmatizzazione con 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'

Esempio di lemmatizzazione con 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']

Gli esempi precedenti sono stati presi in prestito da questa pagina di lemmatizzazione .


1

Fai una ricerca per Lucene, non sono sicuro che ci sia una porta PHP ma so che Lucene è disponibile per molte piattaforme. Lucene è una libreria di indicizzazione e ricerca OSS (da Apache). Naturalmente, e gli extra della comunità potrebbero avere qualcosa di interessante da guardare. Per lo meno puoi imparare come è fatto in una lingua in modo da poter tradurre l '"idea" in PHP


1

Se posso citare la mia risposta alla domanda menzionata da StompChicken:

Il problema principale qui è che gli algoritmi di stemming operano su base fonetica senza una reale comprensione della lingua con cui stanno lavorando.

Poiché non hanno la comprensione della lingua e non corrono da un dizionario di termini, non hanno modo di riconoscere e rispondere in modo appropriato a casi irregolari, come "run" / "run".

Se hai bisogno di gestire casi irregolari, dovrai scegliere un approccio diverso o aumentare il tuo stemming con il tuo dizionario personalizzato di correzioni da eseguire dopo che lo stemmer ha fatto la sua cosa.



1

Potresti usare lo stemmer Morpha. UW ha caricato lo stemmer morpha su Maven central se si prevede di utilizzarlo da un'applicazione Java. C'è un involucro che lo rende molto più facile da usare. Devi solo aggiungerlo come dipendenza e usare la edu.washington.cs.knowitall.morpha.MorphaStemmerclasse. Le istanze sono thread-safe (il JFlex originale aveva campi di classe per variabili locali inutilmente). Crea un'istanza di una classe e corri morphae la parola che vuoi arginare.

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

0

.Net lucene ha una diraspatrice porter incorporata. Puoi provarlo. Ma si noti che lo stemming di porter non considera il contesto della parola quando deriva il lemma. (Esamina l'algoritmo e la sua implementazione e vedrai come funziona)


0

Martin Porter ha scritto Snowball (un linguaggio per gli algoritmi di stemming) e ha riscritto "English Stemmer" in Snowball. C'è uno stemmer inglese per C e Java.

Afferma esplicitamente che il Porter Stemmer è stato reimplementato solo per ragioni storiche, quindi testare la correttezza dello stemmer contro il Porter Stemmer ti darà risultati che (dovresti) già conoscere.

Da http://tartarus.org/~martin/PorterStemmer/index.html (enfasi mia)

Lo stemmer Porter deve essere considerato " congelato ", cioè rigorosamente definito e non suscettibile di ulteriori modifiche. Come stemmer è leggermente inferiore allo Snowball English o Porter2, che ne deriva, e che subisce occasionali migliorie. Per il lavoro pratico, quindi, si consiglia il nuovo stemmer Snowball. Lo stemmer Porter è appropriato per il lavoro di ricerca IR che coinvolge lo stemmer in cui gli esperimenti devono essere esattamente ripetibili.

Il dottor Porter suggerisce di utilizzare gli stemmer English o Porter2 invece dello stemmer Porter. Lo stemmer inglese è ciò che viene effettivamente utilizzato nel sito demo come @StompChicken ha risposto in precedenza.


0

In Java, utilizzo tartargus-snowball per arginare le parole

Esperto di:

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

Codice di esempio:

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

Prova questo qui: http://www.twinword.com/lemmatizer.php

Ho inserito la tua richiesta nella demo "cats running ran cactus cactuses cacti community communities"e ho ottenuto ["cat", "running", "run", "cactus", "cactus", "cactus", "community", "community"]con il flag opzionale ALL_TOKENS.

Codice di esempio

Questa è un'API così puoi connetterti ad essa da qualsiasi ambiente. Ecco come può apparire la chiamata 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

Consiglio vivamente di utilizzare Spacy (analisi e codifica del testo di base) e Textacy (elaborazione del testo di livello superiore basata su Spacy).

Le parole lemmatizzate sono disponibili per impostazione predefinita in Spacy come .lemma_attributo di un token e il testo può essere lemmatizzato mentre si eseguono molte altre pre-elaborazioni del testo con textacy. Ad esempio durante la creazione di un insieme di termini o parole o in genere appena prima di eseguire un'elaborazione che lo richiede.

Ti incoraggio a controllare entrambi prima di scrivere qualsiasi codice, in quanto ciò potrebbe farti risparmiare un sacco di tempo!


-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')))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.