Stemming(어간 추출) vs Lemmatization(표제어 추출) in 자연어 처리

김지원·2022년 11월 13일
1

NLP(자연어 처리)

목록 보기
1/15

✨ Natural Languages Processiong(NLP)

NLP의 목표는 컴퓨터가 인간과 같은 방식으로 대량의 자연어 데이터를 처리하고 분석하도록 프로그래밍하는 것이다.

❓텍스트 전처리는 어떻게 하는가?

  • Text cleaning - 숫자, (.), (,)와 같은 문자, double 공백, 소문자화와 같은 작업
  • Stop words removal - 불용어 제거, 유용한 정보를 주지 않는 자주 등장하는 단어를 제거함
  • Lemmatization - 단어를 기본 형태로(base form), 즉 어근을 추출하는 작업, 예를 들어 "studying", "studies", "studied" 를 "study"로 바꿔준다.
  • Stemming - 어간 추출로, base 형태 또는 root 형태로 바꿔준다.

🔎 Tokenizing, 토큰화

토큰화는 NLP 처리 파이프라인의 첫 번째 단계인 경우가 많다.

영어의 경우 NLTK(Natural Language Toolkit)Spacy가 토크나이징에 많이 쓰이는 대표적인 라이브러리로, 영어 텍스트 전처리 및 분석을 위한 도구로 많이 사용된다.

💻word_tokenize : 단어 단위 토크나이징

예제 1)

import nltk

# nltk.download([
#     "punkt"
# ])

text = "For some quick analysis, creating a corpus could be overkill. If all you need is a word list, there are simpler ways to achieve that goal."

print(nltk.word_tokenize(text))

결과

['For', 'some', 'quick', 'analysis', ',', 'creating', 'a', 'corpus', 'could', 'be', 'overkill', '.', 'If', 'all', 'you', 'need', 'is', 'a', 'word', 'list', ',', 'there', 'are', 'simpler', 'ways', 'to', 'achieve', 'that', 'goal', '.']

모두 단어로 구분돼 있고, 특수 문자의 경우 따로 구분된다.

🔎 어간 추출(Stemming) 및 표제어 추출(Lemmatization)

NLP 분야에서 Stemming과 Lemmatization은 텍스트 전처리 작업에 해당되는 과정이다.
영어를 Inflected Language 라고 하는데,
예를 들어 영어에서 "historical" 이라는 단어는 "history" 라는 단어에서 약간 변형된 형태이다.
이처럼 원형을 살짝 바꿔주는 형태소들을 Inflectional morpheme(굴절 형태소)이라고 한다.
그 종류는 Declension - 성(Gender), 수(Number), 격(Case) / Conjungation - 시제(tense), 인칭(person), 수(Number), 서법(mood) 가 있다.

결국, 이 원형을 추출하기 위해 StemmingLemmatization을 활용할 수 있다.

📌 어간 추출(Stemming)

: 문맥정보를 고려하지 않고 어근을 찾는다.

"어간"이란 단어의 기본 또는 어근 형태이며 원형 단어일 필요는 없다.
예를 들어 Porter 알고리즘은 단어 'argue', 'argued', 'argues', 'arguing'을 원형 단어가 아닌 'argu' 어간으로 줄여준다.

❓ 어간 추출(Stemming)은 어떤 원리로 동작하는가?

일부 간단한 방법은 접두사와 접미사만 인식하고 이를 제거한다. 하지만 많은 경우 잘못된 접두사와 접미사를 제거하므로, 오류가 발생하기 쉽다.
또한 불규칙 동사(irregular verbs)와 같은 일부 단어 형태를 처리하는 것이 어려울 수 있다. (예를 들어 see의 과거시제인 saw의 경우)

따라서 조금 더 정교한 방식의 어간 추출은 접미사 및 접두사와 함께 다양한 Lookup Table을 함께 사용한다. 이러한 방식 중 일부는 먼저 단어의 품사(Part of Speech)를 결정한 다음 각 품사에 대해 다른 정규화(normalization) 규칙을 적용한다.

🔎 Porter Stemming 알고리즘

Porter Stemmer은 접미사 제거 알고리즘이다.
간단히 말해서, 사전 정의된 규칙을 사용하여 단어를 기본 형식으로 바꿔준다.

모든 단어는 일련의 자음과 모음으로 나타낼 수 있다.
자음을 "c"로, 모음을 "v"로 표시하고, 두 경우 모두 시퀀스가 0보다 크면 "C", "V"로 표시한다고 해보자. 그러면 모든 단어는 네 가지 형식 중 하나를 갖는다.
(모음: A, E, I, O, U)

  • CVCV...C
  • CVCV...V
  • VCVC...C
  • VCVC...C
    또는 단일 형태로
  • [C]VCVC...[V]
    대괄호는 있을수도, 없을수도 있음을 나타낸다. 위 식을 아래와 같이도 쓸 수 있다.

[C][VC]^{m}[V]

여기서 m은 measure of word라고 한다. m 값이 달라짐에 따른 예시는 아래와 같다.

  • m=0 (tree, by, why) ccvv, cv, ccv
  • m=1 (oats, trees, ivy) vvcc, vvccv, cvv
  • m=2 (private, oaten, banana) ccvcvcv, vvcvc, cvcvcv

Porter Stemmer는 5단계로 그룹화 될 수 있는 하위 50개 이상의 규칙을 기반으로 동작한다. 모든 규칙에는 아래와 같은 형식이 있다.

[condition] S1 -> S2

즉, 단어에 접미사 S1이 있고 접미사(stem) 앞 부분이 조건을 만족하는 경우 S1S2로 대체한다. 또한, 일부 규칙에는 조건이 없기도 하다.

  • S -> (cats->cat)
  • (m > 0) EED -> EE (agreed -> agree, feed -> feed)
  • (m > 0) ATOR -> ATE (operator -> operate)
  • (m > 1) ER -> (airliner -> airlin)
  • (m > 1 and (*S or *T)) ION -> EE (adoption -> adopt)

🔎 Lancaster Stemming 알고리즘

단어를 over stem 하는 경향이 있다. Porter stemmer와 같이 Lancaster stemmer로 삭제 또는 치환을 수행하는 일련의 규칙을 가지고 있다. 또한, 일부 규칙은 intact word로 제한되기도 한다.

보다 엄격한 규칙을 가지고 있는 알고리즘으로, 두 가지 추가 조건이 있다.

  • 단어가 모음으로 시작하는 경우 stemming 후에 최소한 두 글자가 남아 있어야 한다.
    (owing -> ow, 그러나 ear -> e는 제외)
  • 단어가 자음으로 시작하면 stemming 후에 최소 3개의 문자가 남아 있어야 하며 이 중 적어도 하나는 모음 또는 "y"이어야 한다.
    (saying -> say, 하지만 string -> str 는 안 됨)

Lancaster는 Porter의 약 2배인 100개 이상의 규칙이 있다.
각 규칙에는 5개의 components가 있으며 그 중 2개는 선택 사항이다.

[ending in reverse][optional intact flag “*”][remove total letters][optional append string][continuation symbol, “>” or “.”]

아래와 같은 example이 있다.

  • sei3y> - 단어가 "ies"로 끝나는 경우 마지막 세 글자를 'y'로 바꾼 다음 잘린 형태에 stemming를 다시 적용한다.
    (studies -> studyyy -> study)
  • mu*2 - 단어가 "um"으로 끝나고 단어가 intact 한 경우 마지막 2글자를 제거하고 종료한다.
  • nois4j> - "sion을 "j"로 바꾸고 stemming을 다시 적용한다.

PorterStemmerLancasterStemmer 비교

import nltk
from nltk import PorterStemmer, LancasterStemmer, word_tokenize

text = "For some quick analysis, creating a corpus could be overkill. If all you need is a word list, there are simpler ways to achieve that goal."

def get_keyword(text):
    text = word_tokenize(text)

    porter = PorterStemmer()
    porter_text=[porter.stem(t) for t in text]
    
    lancaster = LancasterStemmer()
    lancaster_text=[lancaster.stem(t) for t in text]

    for i in range(len(text)):
        if text[i]!=porter_text[i]!=lancaster_text[i]:
            print("origin is: ", text[i])
            print("porter: ", porter_text[i])
            print("lancaster:", lancaster_text[i])
            print("-------------")

get_keyword(text)

결과

origin is:  analysis
porter:  analysi
lancaster: analys
-------------
origin is:  creating
porter:  creat
lancaster: cre
-------------
origin is:  corpus
porter:  corpu
lancaster: corp
-------------

📌 표제어 추출(Lemmatization): 문맥정보를 고려하여 어근을 찾는다.

그 단어가 문장 속에서 어떤 품사로 쓰였는지까지 판단한다. Stemmer과 달린 의미 있는 단어를 제공해준다.
하지만 Lemmatization은 의미 있는 단어/표현을 찾기 때문에 Stemmer에 비해 시간이 더 걸린다는 단점이 있다.

Stemmer은 Sentiment Analysis 와 같은 작업에,
Lemmatization은 Chatbot에 적용될 수 있다.

WordNetLemmatizer는 WordNet 데이터베이스에서 단어를 탐색한다.

text = "For some quick analysis, creating a corpus could be overkill. If all you need is a word list, there are simpler ways to achieve that goal."

def get_keyword(text):
    text = word_tokenize(text)

    porter = PorterStemmer()
    porter_text=[porter.stem(t) for t in text]
    
    lancaster = LancasterStemmer()
    lancaster_text=[lancaster.stem(t) for t in text]

    for i in range(len(text)):
    	# 어간 추출로 먼저 확인
        if text[i]!=porter_text[i]!=lancaster_text[i]:
            print("origin is: ", text[i])
            print("porter: ", porter_text[i])
            print("lancaster:", lancaster_text[i])
            print("-------------")
            # 표제어 추출로 확인
            # Lemmatization
            print("Let's check lemmatization")
            lemmatizer = WordNetLemmatizer()
            lemmatizer_text = lemmatizer.lemmatize(text[i], pos='v')

            print("lemmatize: ", lemmatizer_text)
            print("-------------")
        else:            
            print("It's same, ", text[i])

get_keyword(text)

❓궁금한 점

lemmatizer 사용 시, pos argument로 품사를 따로 지정해줘야 하는데,
이걸 수동으로 말고 문맥에 따라 자동으로 품사 지정해줄 수 있는 방법이 없을까?

❗있다!!!

lemmatizer 는 real word 를 추출해주지만, 품사를 직접 지정해주지 않으면 이 단어가 noun인지, verb인지 구별을 하지 못해서 적절한 결과가 나오지 않을 수 있다.
예를 들어 womenpos='v'로 지정해줄 경우 내가 원하는 결과인 woman이 나오지 않고, 그대로 women이 return 된다.

이러한 문제를 위해 이미 NLTK에서는 POS 태그를 자동으로 생성해주는 함수를 제공한다. nltk.pos_tag 함수를 사용하면 된다.

import nltk
from nltk import PorterStemmer, LancasterStemmer, WordNetLemmatizer
from nltk.tokenize import word_tokenize
from nltk.corpus import wordnet

text= "women are strong."
tokens=word_tokenize(text)
pos_tokens=nltk.pos_tag(tokens)
# 결과
[('women', 'NNS'), ('are', 'VBP'), ('strong', 'JJ'), ('.', '.')]

❓하지만 nltk.pos_tag의 pos_tag를 lemmatizer가 인식하지 못한다.

nltk.pos_tag의 Tag 종류는 아래와 같지만,

AbbreviationMeaning
CCcoordinating
CDcardinal digit
DTdeterminer
EXexistential there
FWforeign word
INpreposition/subordinating conjunction
JJThis NLTK POS Tag is an adjective (large)
JJRadjective, comparative (larger)
JJSadjective, superlative (largest)
LSlist market
MDmodal (could, will)
NNnoun, singular (cat, tree)
NNSnoun plural (desks)
NNPproper noun, singular (sarah)
NNPSproper noun, plural (indians or americans)
PDTpredeterminer (all, both, half)
POSpossessive ending (parent\ ‘s)
PRPpersonal pronoun (hers, herself, him, himself)
PRP$possessive pronoun (her, his, mine, my, our )
RBadverb (occasionally, swiftly)
RBRadverb, comparative (greater)
RBSadverb, superlative (biggest)
RPparticle (about)
TOinfinite marker (to)
UHinterjection (goodbye)
VBverb (ask)
VBGverb gerund (judging)
VBDverb past tense (pleaded)
VBNverb past participle (reunified)
VBPverb, present tense not 3rd person singular(wrap)
VBZverb, present tense with 3rd person singular (bases)
WDTwh-determiner (that, what)
WPwh- pronoun (who)
WRBwh- adverb (how)

lemmatizer가 사용하는 pos_tag는 wordnet의 Tag로, 아래와 같다.

  • n: noun
  • v: verb
  • a: adjective
  • s: adjective satellite
    antonym이 없는 단어를 의미한다. 특이한 경우인데, wn에서 adjective의 경우는 (adjective, verb, antonym)의 triple의 형태로 관리되는데, anotynym이 없을 경우만 따로 adjective satellite 로 관리한다.
  • r: adverb

따라서 lemmatizer에 자동으로 품사 태깅을 해주기 위해서는 nltk의 pos_tag return 값 => wordnet의 tag 값으로 변환해주는 과정이 필요하다.

def get_wordnet_pos(treebank_tag):
    """
    return WORDNET POS compliance to WORDENT lemmatization (a,n,r,v) 
    """
    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:
        # As default pos in lemmatization is Noun
        return wordnet.NOUN

https://www.nltk.org/howto/stem.html
https://modulabs.co.kr/blog/nlp-2/
https://www.analyticsvidhya.com/blog/2022/06/stemming-vs-lemmatization-in-nlp-must-know-differences/
https://m.blog.naver.com/sonss1992/221563248899
https://marcobonzanini.com/2015/01/26/stemming-lemmatisation-and-pos-tagging-with-python-and-nltk/
https://www.baeldung.com/cs/porter-vs-lancaster-stemming-algorithms

profile
Make your lives Extraordinary!

0개의 댓글