Làm thế nào để tôi làm từ Stemming hoặc Lemmatization?


111

Tôi đã thử PorterStemmer và Snowball nhưng cả hai đều không hoạt động trên tất cả các từ, thiếu một số từ rất phổ biến.

Từ thử nghiệm của tôi là: " mèo chạy cây xương rồng xương rồng xương rồng cộng đồng cộng đồng ", và cả hai đều đúng chưa đến một nửa.

Xem thêm:


28
Đó không phải là xương rồng?
MSalters

3
Chỉ để tạo tham chiếu vòng tròn cho câu hỏi ban đầu được đăng trên Reddit: Làm cách nào để tạo gốc theo chương trình? (ví dụ: "ăn" thành "ăn", "xương rồng" thành "xương rồng") Đăng nó ở đây vì các bình luận bao gồm thông tin hữu ích.
Renaud Bompuis

Câu trả lời:


143

Nếu bạn biết Python, The Natural Language Toolkit (NLTK) có lemmatizer rất mạnh mẽ mà làm cho việc sử dụng WordNet .

Lưu ý rằng nếu bạn đang sử dụng lemmatizer này lần đầu tiên, bạn phải tải về kho tài liệu trước khi sử dụng nó. Điều này có thể được thực hiện bằng cách:

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

Bạn chỉ phải làm điều này một lần duy nhất. Giả sử rằng bây giờ bạn đã tải xuống tập tài liệu, nó hoạt động như sau:

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

Có những loại chanh khác trong mô-đun nltk.stem , nhưng tôi chưa tự mình thử chúng.


11
Oh buồn ... trước khi tôi biết để tìm kiếm VÌ ​​tôi đã thực hiện của riêng tôi!
Chris Pfohl

12
Đừng quên cài đặt kho ngữ liệu trước khi sử dụng nltk lần đầu tiên! velvetcache.org/2010/03/01/…
Mathieu Rodic

1
Chà, cái này sử dụng một số thuật toán không xác định như Porter Stemmer, vì nếu bạn thử nó dies, nó sẽ cho bạn dythay vì die. Không phải có một số loại từ điển mã gốc mã cứng?
SexyBeast

3
bất kỳ ý tưởng nào là những từ WordNetLemmatizersai lemmatize?
alvas

21
nltk WordNetLemmatizer yêu cầu thẻ pos làm đối số. Theo mặc định nó là 'n' (viết tắt của danh từ). Vì vậy, nó sẽ không hoạt động chính xác cho động từ. Nếu thẻ POS không có sẵn, một cách tiếp cận đơn giản (nhưng đặc biệt) là thực hiện bổ ngữ hai lần, một cho 'n' và một cho 'v' (đại diện cho động từ) và chọn kết quả khác với từ gốc (thường có độ dài ngắn hơn, nhưng 'ran' và 'run' có cùng độ dài). Có vẻ như chúng ta không cần phải lo lắng về 'adj', 'adv', 'prep', v.v., vì chúng đã ở dạng ban đầu theo một nghĩa nào đó.
Fashandge

29

Tôi sử dụng stanford nlp để thực hiện lemmatization. Tôi đã bị mắc kẹt với một vấn đề tương tự trong vài ngày qua. Tất cả nhờ stackoverflow để giúp tôi giải quyết vấn đề.

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

Cũng có thể là một ý tưởng hay khi sử dụng các từ dừng để giảm thiểu bổ đề đầu ra nếu nó được sử dụng sau này trong trình phân loại. Hãy xem phần mở rộng coreNlp được viết bởi John Conwell.


xin lỗi vì trả lời muộn .. tôi chỉ giải quyết được vấn đề này bây giờ! :)
CTsiddharth

1
Dòng 'pipe = new ...' không biên dịch cho tôi. Nếu tôi thay đổi nó thành 'StanfordCoreNLP pipelne = new ...' thì nó sẽ biên dịch. Os này đúng?
Adam_G

Có, bạn phải khai báo var đường ống trước. Stanford NLP cũng có thể được sử dụng từ dòng lệnh, vì vậy bạn không phải lập trình bất kỳ, bạn chỉ cần tạo tệp thuộc tính và cung cấp các tệp thực thi với nó. Đọc tài liệu: nlp.stanford.edu/software/corenlp.shtml
Jindra Helcl

24

Tôi đã thử danh sách các thuật ngữ của bạn trên trang web demo quả cầu tuyết này và kết quả có vẻ ổn ....

  • mèo -> con mèo
  • đang chạy -> chạy
  • ran -> ran
  • xương rồng -> xương rồng
  • cây xương rồng -> cây xương rồng
  • cộng đồng -> cộng đồng
  • cộng đồng -> cộng đồng

Một người viết gốc phải chuyển các dạng từ được chọn lọc xuống một số gốc chung. Việc làm cho từ gốc đó trở thành một từ điển 'thích hợp' không thực sự là công việc của một người khai thác. Để làm được điều đó, bạn cần phải xem xét các máy phân tích hình thái học / chỉnh hình .

Tôi nghĩ câu hỏi này ít nhiều cũng giống nhau, và câu trả lời của Kaarel cho câu hỏi đó là tôi lấy liên kết thứ hai từ đâu.


6
Vấn đề là gốc ( "Cập nhật") == ngăn chặn ( "cập nhật"), mà nó (cập nhật -> updat)
Stompchicken

1
Phần mềm này có thể làm gốc (x) == ngăn chặn (y) nhưng điều đó không trả lời câu hỏi hoàn toàn
sử dụng

11
Cẩn thận với biệt ngữ, gốc không phải là dạng cơ sở của một từ. Nếu bạn muốn có dạng nền, bạn cần có lemmatizer. Thân là phần lớn nhất của từ không chứa tiền tố hoặc hậu tố. Nội dung cập nhật từ thực sự là "updat". Các từ được tạo ra từ các gốc bằng cách thêm phần cuối và hậu tố, ví dụ: updat-e, hoặc updat-ing. ( en.wikipedia.org/wiki/Word_stem )
Jindra Helcl

20

Các cuộc tranh luận về stemmer và lemmatizer vẫn tiếp diễn. Đó là vấn đề thích độ chính xác hơn hiệu quả. Bạn nên lemmatize để đạt được các đơn vị có ý nghĩa về mặt ngôn ngữ và bắt đầu sử dụng hiệu quả tính toán tối thiểu mà vẫn lập chỉ mục một từ và các biến thể của nó trong cùng một khóa.

Xem Stemmers vs Lemmatizers

Đây là một ví dụ với 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
Như đã đề cập trước đó, WordNetLemmatizer's lemmatize()có thể mất một thẻ POS. Vì vậy, từ ví dụ của bạn: " ".join([wnl.lemmatize(i, pos=VERB) for i in sent.split()])cho 'cat run run cactus cactuses cacti community communities'.
Nick Ruiz

@NickRuiz, tôi nghĩ ý bạn là pos=NOUNgì? BTW: Lâu rồi không gặp, hy vọng chúng ta sẽ sớm gặp nhau trong hội nghị =)
alvas

thực sự là không (Tuy nhiên, hy vọng là 'có' với các hội nghị). Bởi vì nếu bạn đặt pos=VERBbạn chỉ làm lemmatization trên động từ. Các danh từ vẫn giữ nguyên. Tôi chỉ phải viết một số mã của riêng mình để xoay quanh các thẻ POS của Penn Treebank thực tế để áp dụng đúng sự cố cho mỗi mã thông báo. Ngoài ra, WordNetLemmatizermùi hôi khi làm lemmatizing tokenizer mặc định của nltk. Vì vậy, các ví dụ như does n'tkhông bổ sung cho do not.
Nick Ruiz

nhưng, nhưng port.stem("this")tạo ra thiport.stem("was") wa, ngay cả khi vị trí phù hợp được cung cấp cho mỗi.
Lerner Zhang

Trình tạo gốc không trả lại đầu ra âm thanh theo ngôn ngữ. Nó chỉ để làm cho văn bản trở nên "dày đặc" hơn (tức là chứa ít vocab hơn). Xem stackoverflow.com/questions/17317418/stemmers-vs-lemmatizersstackoverflow.com/questions/51943811/…
alvas

8

Trang chính thức của Martin Porter có chứa Porter Stemmer bằng PHP cũng như các ngôn ngữ khác .

Nếu bạn thực sự nghiêm túc về việc tạo gốc tốt mặc dù bạn sẽ cần bắt đầu với một cái gì đó như Thuật toán Porter, hãy tinh chỉnh nó bằng cách thêm các quy tắc để sửa các trường hợp không chính xác thường gặp đối với tập dữ liệu của bạn và cuối cùng thêm nhiều ngoại lệ vào các quy tắc . Điều này có thể dễ dàng thực hiện với các cặp khóa / giá trị (dbm / băm / từ điển) trong đó khóa là từ cần tra cứu và giá trị là từ gốc để thay thế từ gốc. Một công cụ tìm kiếm thương mại mà tôi đã từng làm việc đã kết thúc với 800 ngoại lệ đối với thuật toán Porter được sửa đổi.


Một giải pháp lý tưởng sẽ tự động tìm hiểu những mong đợi này. Bạn đã có bất kỳ kinh nghiệm với một hệ thống?
Malcolm

Không. Trong trường hợp của chúng tôi, các tài liệu đang được lập chỉ mục là mã & quy định cho một lĩnh vực luật cụ thể và có hàng chục biên tập viên (con người) đang phân tích các chỉ mục để tìm bất kỳ điểm xấu nào.
Van Gale


5

Dựa trên các câu trả lời khác nhau trên Stack Overflow và các blog mà tôi đã xem qua, đây là phương pháp tôi đang sử dụng và nó có vẻ trả về các từ thực khá tốt. Ý tưởng là chia văn bản đến thành một mảng từ (sử dụng bất kỳ phương pháp nào bạn muốn), sau đó tìm các phần của giọng nói (POS) cho những từ đó và sử dụng nó để giúp bắt nguồn và bổ sung các từ.

Mẫu của bạn ở trên không hoạt động quá tốt, vì không thể xác định được POS. Tuy nhiên, nếu chúng ta sử dụng một câu thực, mọi thứ hoạt động tốt hơn nhiều.

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

Điều này có vẻ thú vị: MIT Java WordnetStemmer: http://projects.csail.mit.edu/jwi/api/edu/mit/jwi/morph/WordnetStemmer.html


3
Chào mừng bạn đến với SO, và cảm ơn bài đăng của bạn, +1. Sẽ thật tuyệt nếu bạn có thể đưa ra một vài nhận xét về cách sử dụng, hiệu suất của ứng dụng gốc này, v.v. Chỉ cần một liên kết thường không được coi là một câu trả lời tốt.
jogojapan 29/10/12

2

Hãy xem LemmaGen - thư viện mã nguồn mở được viết bằng C # 3.0.

Kết quả cho các từ kiểm tra của bạn ( http://lemmatise.ijs.si/Services )

  • mèo -> con mèo
  • đang chạy
  • ran -> run
  • cây xương rồng
  • cây xương rồng -> cây xương rồng
  • xương rồng -> xương rồng
  • cộng đồng
  • cộng đồng -> cộng đồng

2

Các gói đầu trăn (không theo thứ tự cụ thể) cho lemmatization là: spacy, nltk, gensim, pattern, CoreNLPTextBlob. Tôi thích cách triển khai của spaCy và gensim (dựa trên mẫu) vì họ xác định thẻ POS của từ và tự động gán bổ đề thích hợp. Các bổ đề phù hợp hơn, giữ nguyên ý nghĩa.

Nếu bạn định sử dụng nltk hoặc TextBlob, bạn cần quan tâm đến việc tìm thẻ POS phù hợp theo cách thủ công và tìm bổ đề phù hợp.

Ví dụ bổ sung hóa với 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'

Ví dụ bổ sung hóa với 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']

Các ví dụ trên được mượn từ trang lemmatization này.


1

Thực hiện tìm kiếm trên Lucene, tôi không chắc có cổng PHP hay không nhưng tôi biết Lucene có sẵn cho nhiều nền tảng. Lucene là một thư viện tìm kiếm và lập chỉ mục OSS (từ Apache). Đương nhiên nó và các tính năng bổ sung của cộng đồng có thể có một cái gì đó thú vị để xem xét. Ít nhất bạn có thể học cách nó được thực hiện bằng một ngôn ngữ để bạn có thể dịch "ý tưởng" sang PHP


1

Nếu tôi có thể trích dẫn câu trả lời của tôi cho câu hỏi mà StompChicken đã đề cập:

Vấn đề cốt lõi ở đây là các thuật toán gốc hoạt động trên cơ sở ngữ âm mà không có hiểu biết thực sự về ngôn ngữ mà chúng đang làm việc.

Vì họ không hiểu ngôn ngữ và không sử dụng từ điển thuật ngữ, họ không có cách nào để nhận ra và phản ứng thích hợp với các trường hợp bất thường, chẳng hạn như "run" / "ran".

Nếu bạn cần xử lý các trường hợp bất thường, bạn sẽ cần phải chọn một cách tiếp cận khác hoặc tăng cường nguồn gốc của bạn với từ điển sửa chữa tùy chỉnh của riêng bạn để chạy sau khi trình gốc đã hoàn thành công việc của mình.



1

Bạn có thể sử dụng máy cắt gốc Morpha. UW đã tải morpha stemmer lên Maven center nếu bạn định sử dụng nó từ một ứng dụng Java. Có một trình bao bọc giúp bạn sử dụng dễ dàng hơn nhiều. Bạn chỉ cần thêm nó như một phụ thuộc và sử dụng edu.washington.cs.knowitall.morpha.MorphaStemmerlớp. Các phiên bản là luồng an toàn (JFlex ban đầu có các trường lớp cho các biến cục bộ một cách không cần thiết). Khởi tạo một lớp và chạy morphavà từ bạn muốn xuất phát.

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

0

.Net lucene có một gốc gác porter sẵn có. Bạn có thể thử điều đó. Nhưng lưu ý rằng gốc porter không xem xét ngữ cảnh từ khi suy ra bổ đề. (Xem qua thuật toán và cách triển khai của nó và bạn sẽ thấy nó hoạt động như thế nào)


0

Martin Porter đã viết Snowball (một ngôn ngữ cho các thuật toán gốc) và viết lại "English Stemmer" trong Snowball. Có một English Stemmer cho C và Java.

Ông tuyên bố rõ ràng rằng Porter Stemmer đã được thực hiện lại chỉ vì những lý do lịch sử, vì vậy thử nghiệm bắt nguồn đúng đắn chống lại Porter Stemmer sẽ giúp bạn có được kết quả mà bạn (nên) đã biết.

Từ http://tartarus.org/~martin/PorterStemmer/index.html (tôi nhấn mạnh)

Porter gốc nên được coi là ' đông lạnh ', có nghĩa là, được xác định chặt chẽ và không thể sửa đổi thêm. Là một trình gốc, nó hơi kém hơn một chút so với trình tạo gốc Snowball English hoặc Porter2, bắt nguồn từ nó và thỉnh thoảng được cải tiến. Do đó, đối với công việc thực tế, nên sử dụng máy tạo gốc Snowball mới. Porter gốc thích hợp cho công việc nghiên cứu IR liên quan đến việc tạo gốc nơi các thí nghiệm cần phải được lặp lại chính xác.

Tiến sĩ Porter đề nghị sử dụng máy tạo gốc tiếng Anh hoặc Porter2 thay vì máy tạo gốc Porter. Trình gốc tiếng Anh thực sự được sử dụng trong trang web demo như @StompChicken đã trả lời trước đó.


0

Trong Java, tôi sử dụng tartargus-snowball để bắt nguồn từ

Maven:

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

Mã mẫu:

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

Hãy thử cái này tại đây: http://www.twinword.com/lemmatizer.php

Tôi đã nhập truy vấn của bạn trong bản trình diễn "cats running ran cactus cactuses cacti community communities"và có ["cat", "running", "run", "cactus", "cactus", "cactus", "community", "community"]cờ tùy chọn ALL_TOKENS.

Mã mẫu

Đây là một API để bạn có thể kết nối với nó từ mọi môi trường. Đây là lệnh gọi PHP REST có thể trông như thế nào.

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

Tôi thực sự khuyên bạn nên sử dụng Spacy (phân tích và gắn thẻ văn bản cơ sở) và Textacy (xử lý văn bản cấp cao hơn được xây dựng trên Spacy).

Các từ được bổ sung hóa có sẵn theo mặc định trong Spacy như một .lemma_thuộc tính của mã thông báo và văn bản có thể được bổ sung hóa trong khi thực hiện nhiều quá trình xử lý trước văn bản khác với textacy. Ví dụ: trong khi tạo một túi thuật ngữ hoặc từ hoặc nói chung ngay trước khi thực hiện một số xử lý yêu cầu nó.

Tôi khuyến khích bạn kiểm tra cả hai trước khi viết bất kỳ mã nào, vì điều này có thể giúp bạn tiết kiệm rất nhiều thời gian!


-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')))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.