Comment faire une racine ou une lemmatisation de mots?


111

J'ai essayé PorterStemmer et Snowball, mais les deux ne fonctionnent pas sur tous les mots, il en manque certains très courants.

Mes mots de test sont: "les chats qui courent ont couru les communautés communautaires de cactus cactus cactus ", et les deux ont moins de la moitié raison.

Voir également:


28
Cela ne devrait-il pas être des cactus?
MSalters

3
Juste pour faire une référence circulaire à la question originale publiée sur Reddit: Comment puis-je faire une racine par programmation? (par exemple «manger» pour «manger», «cactus» pour «cactus») Le poster ici car les commentaires contiennent des informations utiles.
Renaud Bompuis

Réponses:


143

Si vous connaissez Python, The Natural Language Toolkit (NLTK) a un lemmatizer très puissant qui utilise WordNet .

Notez que si vous utilisez ce lemmatiseur pour la première fois, vous devez télécharger le corpus avant de l'utiliser. Cela peut être fait par:

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

Vous ne devez le faire qu'une seule fois. En supposant que vous avez maintenant téléchargé le corpus, cela fonctionne comme ceci:

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

Il existe d'autres lemmatiseurs dans le module nltk.stem , mais je ne les ai pas essayés moi-même.


11
Oh triste ... avant que je sache chercher, alors j'ai implémenté le mien!
Chris Pfohl

12
N'oubliez pas d'installer le corpus avant d'utiliser nltk pour la première fois! velvetcache.org/2010/03/01/…
Mathieu Rodic

1
Eh bien, celui-ci utilise un algorithme non déterministe comme Porter Stemmer, car si vous l'essayez avec dies, il vous donne à la dyplace de die. N'existe-t-il pas une sorte de dictionnaire radical codé en dur?
SexyBeast

3
une idée quels sont les mots qui WordNetLemmatizerlemmatisent à tort?
alvas

21
nltk WordNetLemmatizer nécessite une balise pos comme argument. Par défaut, c'est 'n' (représentant nom). Cela ne fonctionnera donc pas correctement pour les verbes. Si les balises POS ne sont pas disponibles, une approche simple (mais ad hoc) consiste à effectuer la lemmatisation deux fois, une pour 'n' et l'autre pour 'v' (représentant le verbe), et de choisir le résultat qui est différent du mot original (généralement plus court, mais «run» et «run» ont la même longueur). Il semble que nous n'ayons pas à nous soucier de 'adj', 'adv', 'prep', etc., puisqu'ils sont déjà dans leur forme originale dans un certain sens.
Fashandge

29

J'utilise stanford nlp pour effectuer la lemmatisation. J'ai été confronté à un problème similaire ces derniers jours. Tout cela grâce à stackoverflow pour m'aider à résoudre le problème.

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

Il peut également être judicieux d'utiliser des mots vides pour minimiser les lemmes de sortie s'ils sont utilisés plus tard dans le classificator. Veuillez jeter un œil à l' extension coreNlp écrite par John Conwell.


désolé pour la réponse tardive .. je n'ai résolu ce problème que maintenant! :)
CTsiddharth

1
La ligne 'pipeline = new ...' ne se compile pas pour moi. Si je le change en 'StanfordCoreNLP pipelne = new ...', il se compile. Est-ce correct?
Adam_G

Oui, vous devez d'abord déclarer le pipeline var. Le PNL de Stanford peut également être utilisé à partir de la ligne de commande afin que vous n'ayez pas à faire de programmation, il vous suffit de créer le fichier de propriétés et d'alimenter les exécutables avec. Lisez la documentation: nlp.stanford.edu/software/corenlp.shtml
Jindra Helcl

24

J'ai essayé votre liste de termes sur ce site de démonstration de boule de neige et les résultats semblent corrects ....

  • chats -> chat
  • courir -> courir
  • couru -> couru
  • cactus -> cactus
  • cactus -> cactus
  • communauté -> communiti
  • communautés -> communiti

Un radical est censé transformer les formes fléchies des mots en une racine commune. Ce n'est pas vraiment un travail de radical de faire de cette racine un mot du dictionnaire «approprié». Pour cela, vous devez vous pencher sur des analyseurs morphologiques / orthographiques .

Je pense que cette question concerne plus ou moins la même chose, et la réponse de Kaarel à cette question est de savoir d'où j'ai pris le deuxième lien.


6
Le point est que la tige ("mises à jour") == tige ("mise à jour"), ce qu'elle fait (mise à jour -> mise à jour)
Stompchicken

1
Le logiciel peut faire tige (x) == tige (y) mais cela ne répond pas complètement à la question
utilisateur

11
Attention au jargon, une racine n'est pas une forme de base d'un mot. Si vous voulez une forme de base, vous avez besoin d'un lemmatiseur. Un radical est la plus grande partie d'un mot qui ne contient pas de préfixes ou de suffixes. La racine d'un mot mise à jour est en effet "updat". Les mots sont créés à partir de stems en ajoutant des terminaisons et des suffixes, par exemple updat-e ou updat-ing. ( en.wikipedia.org/wiki/Word_stem )
Jindra Helcl

20

Les débats entre le stemmer et le lemmatizer se poursuivent. Il s'agit de préférer la précision à l'efficacité. Vous devez lemmatiser pour obtenir des unités linguistiquement significatives et utiliser un minimum de jus de calcul tout en indexant un mot et ses variations sous la même clé.

Voir Stemmers vs Lemmatizers

Voici un exemple avec 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
Comme mentionné précédemment, WordNetLemmatizerles lemmatize()peuvent prendre une étiquette POS. Donc à partir de votre exemple: " ".join([wnl.lemmatize(i, pos=VERB) for i in sent.split()])donne 'cat run run cactus cactuses cacti community communities'.
Nick Ruiz

@NickRuiz, je pense que vous vouliez dire pos=NOUN? BTW: Long time no see, j'espère que nous nous rencontrerons bientôt en conférence =)
alvas

en fait, non (avec un peu de chance, oui aux conférences, cependant). Parce que si vous définissez, pos=VERBvous ne faites de la lemmatisation que sur les verbes. Les noms restent les mêmes. Je devais juste écrire une partie de mon propre code pour pivoter autour des balises Penn Treebank POS pour appliquer la lemmatisation correcte à chaque jeton. En outre, WordNetLemmatizerpue la lemmatisation du tokenizer par défaut de nltk. Donc, des exemples comme does n'tne lemmatisent pas do not.
Nick Ruiz

mais, mais port.stem("this")produit thiet port.stem("was") wa, même lorsque la bonne position est fournie pour chacun.
Lerner Zhang

Un stemmer ne renvoie pas de sorties sonores linguistiquement. C'est juste pour rendre le texte plus "dense" (c'est-à-dire contenir moins de vocabulaire). Voir stackoverflow.com/questions/17317418/stemmers-vs-lemmatizers et stackoverflow.com/questions/51943811/…
alvas

8

La page officielle de Martin Porter contient un Porter Stemmer en PHP ainsi que d' autres langues .

Si vous êtes vraiment sérieux au sujet d'un bon dérivation, bien que vous deviez commencer par quelque chose comme l'algorithme Porter, affinez-le en ajoutant des règles pour corriger les cas incorrects communs à votre ensemble de données, puis enfin ajouter de nombreuses exceptions aux règles. . Cela peut être facilement implémenté avec des paires clé / valeur (dbm / hash / dictionnaires) où la clé est le mot à rechercher et la valeur est le mot dérivé pour remplacer l'original. Un moteur de recherche commercial sur lequel j'ai travaillé une fois s'est retrouvé avec 800 exceptions à un algorithme de Porter modifié.


Une solution idéale apprendrait automatiquement ces attentes. Avez-vous eu une expérience avec un tel système?
Malcolm du

Non. Dans notre cas, les documents indexés étaient le code et les règlements pour un domaine spécifique du droit et il y avait des dizaines d'éditeurs (humains) analysant les index pour les mauvaises souches.
Van Gale


5

Sur la base de diverses réponses sur Stack Overflow et des blogs que j'ai rencontrés, c'est la méthode que j'utilise, et elle semble assez bien renvoyer de vrais mots. L'idée est de diviser le texte entrant en un tableau de mots (utilisez la méthode de votre choix), puis de trouver les parties du discours (POS) pour ces mots et de l'utiliser pour aider à résumer et à lemmatiser les mots.

Votre échantillon ci-dessus ne fonctionne pas très bien, car le point de vente ne peut pas être déterminé. Cependant, si nous utilisons une vraie phrase, les choses fonctionnent beaucoup mieux.

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

Jetez un œil à LemmaGen - bibliothèque open source écrite en C # 3.0.

Résultats pour vos mots de test ( http://lemmatise.ijs.si/Services )

  • chats -> chat
  • fonctionnement
  • couru -> couru
  • cactus
  • cactus -> cactus
  • cactus -> cactus
  • communauté
  • communautés -> communauté

2

Les paquets de python top (sans ordre spécifique) pour lemmatisation sont: spacy, nltk, gensim, pattern, CoreNLPet TextBlob. Je préfère l'implémentation de spaCy et gensim (basée sur un modèle) car ils identifient la balise POS du mot et attribuent automatiquement le lemme approprié. Le donne des lemmes plus pertinents, en gardant le sens intact.

Si vous prévoyez d'utiliser nltk ou TextBlob, vous devez prendre soin de trouver la bonne balise POS manuellement et de trouver le bon lemme.

Exemple de lemmatisation avec 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'

Exemple de lemmatisation avec 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']

Les exemples ci-dessus ont été empruntés à cette page de lemmatisation .


1

Faites une recherche pour Lucene, je ne sais pas s'il y a un port PHP mais je sais que Lucene est disponible pour de nombreuses plates-formes. Lucene est une bibliothèque d'indexation et de recherche OSS (d'Apache). Naturellement, cela et les extras de la communauté peuvent avoir quelque chose d'intéressant à regarder. À tout le moins, vous pouvez apprendre comment cela se fait dans une langue afin de pouvoir traduire «l'idée» en PHP


1

Si je peux citer ma réponse à la question mentionnée par StompChicken:

Le problème central ici est que les algorithmes de dérivation fonctionnent sur une base phonétique sans aucune compréhension réelle de la langue avec laquelle ils travaillent.

Comme ils n'ont aucune compréhension de la langue et ne fonctionnent pas à partir d'un dictionnaire de termes, ils n'ont aucun moyen de reconnaître et de répondre de manière appropriée aux cas irréguliers, tels que «courir» / «courir».

Si vous avez besoin de gérer des cas irréguliers, vous devrez soit choisir une approche différente, soit augmenter votre tige avec votre propre dictionnaire personnalisé de corrections à exécuter une fois que la tige a fait son travail.



1

Vous pouvez utiliser la tige Morpha. UW a téléchargé morpha stemmer sur Maven central si vous prévoyez de l'utiliser à partir d'une application Java. Il y a un wrapper qui le rend beaucoup plus facile à utiliser. Il vous suffit de l'ajouter en tant que dépendance et d'utiliser la edu.washington.cs.knowitall.morpha.MorphaStemmerclasse. Les instances sont threadsafe (le JFlex d'origine avait des champs de classe pour les variables locales inutilement). Instanciez une classe et exécutez morphaet le mot que vous voulez dériver.

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

0

.Net lucene a une tige de porteur intégrée. Vous pouvez essayer ça. Mais notez que la racine porteuse ne tient pas compte du contexte des mots lors de la dérivation du lemme. (Parcourez l'algorithme et sa mise en œuvre et vous verrez comment cela fonctionne)


0

Martin Porter a écrit Snowball (un langage pour les algorithmes dérivés) et réécrit le "English Stemmer" dans Snowball. Il existe un Stemmer anglais pour C et Java.

Il déclare explicitement que le Porter Stemmer n'a été réimplémenté que pour des raisons historiques, donc tester l'exactitude des racines contre le Porter Stemmer vous donnera des résultats que vous (devriez) déjà savoir.

De http://tartarus.org/~martin/PorterStemmer/index.html (c'est moi qui souligne)

L'égouttoir Porter doit être considéré comme « gelé », c'est-à-dire strictement défini, et ne peut pas être modifié ultérieurement. En tant que tige, elle est légèrement inférieure à la tige Snowball English ou Porter2, qui en dérive, et qui subit des améliorations ponctuelles. Pour les travaux pratiques, par conséquent, la nouvelle tige de tige Snowball est recommandée. La tige de Porter est appropriée aux travaux de recherche IR impliquant la tige où les expériences doivent être exactement répétables.

Le Dr Porter suggère d'utiliser les souches anglais ou Porter2 au lieu de la tige Porter. Le stemmer anglais est ce qui est réellement utilisé dans le site de démonstration, comme @StompChicken a répondu plus tôt.


0

En Java, j'utilise tartargus-snowball pour dériver les mots

Maven:

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

Exemple de code:

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

Essayez celui-ci ici: http://www.twinword.com/lemmatizer.php

J'ai entré votre requête dans la démo "cats running ran cactus cactuses cacti community communities"et j'ai obtenu ["cat", "running", "run", "cactus", "cactus", "cactus", "community", "community"]le drapeau facultatif ALL_TOKENS.

Exemple de code

Il s'agit d'une API afin que vous puissiez vous y connecter à partir de n'importe quel environnement. Voici à quoi peut ressembler l'appel 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

Je recommande vivement d'utiliser Spacy (analyse et marquage de texte de base) et Textacy (traitement de texte de plus haut niveau construit sur Spacy).

Les mots lemmatisés sont disponibles par défaut dans Spacy en tant que jetons.lemma_ qu'attribut et le texte peut être lemmatisé tout en effectuant de nombreux autres prétraitements de texte avec textacy. Par exemple, lors de la création d'un sac de termes ou de mots ou généralement juste avant d'effectuer un traitement qui le nécessite.

Je vous encourage à vérifier les deux avant d'écrire un code, car cela peut vous faire gagner beaucoup de temps!


-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')))
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.