본문 바로가기
NLP

Chapter 4. 통계 및 머신러닝을 위한 텍스트 데이터 준비

by Night Fury 2023. 3. 26.
젠스 알브레히트, 시다르트 라마찬드란, 크리스티안 윙클러, 『파이썬 라이브러리를 활용한 텍스트 분석 Blueprints for Text Analytics Using Python』, 심상진, 한빛미디어-OREILLY(2022), p135-175.

Learned

텍스트 데이터의 일반적인 전처리 파이프라인

노이즈 처리 w. regex

  • +, *와 같은 반복자는 유의해서 사용 (텍스트의 많은 부분을 제거할 수 있음)

문자 정규화

  • ex. 악센트와 같은 문자를 다른 토큰으로 인식 -> textacy 라이브러리 활용 가능
  • textacy : spacy와 함께 작동하도록 구축된 라이브러리
    • 언어 부분은 spacy에 맡기고 사전 및 사후 처리에 중점을 둠
  • normalize_hyphenated_words: 줄 바꿈으로 구분된 단어를 다시 조합
  • normalize_quotation_marks: 모든 종류의 따옴표를 ASCII에 해당하는 따옴표로 대체
  • normalize_unicode: 유니코드에서 악센트가 있는 문자를 다른 코드로 변경
  • remove_accents: 악센트가 있는 문자를 ASCII로 바꾸거나 삭제

데이터 마스킹

  • url, email, 주소, 전화번호와 같이 무관한 정보나 개인정보를 보호할 때 적용
  • 마스킹 관련 정규 표현식 -> textacy 참고

토큰화

  • 토큰이란? : 의미론적으로 분석에 유용한 언어 단위
    • 풀어야하는 문제에 따라 토큰화 방식은 달라짐
    • ex. 어떤 문제에서는 이모티콘을 제외해야하고, 감성분석 같은 경우에는 이모티콘을 포함할 수도 있음
  • scikit-learn CountVectorizer는 기본 토큰화에 \w\w+ 패턴을 사용함
# 한글
tokens = re.findall(r'\w\w+', '어떻게 하는지 한 번 봐볼까요? #연습 @테스트 😩😬')
print(*tokens, sep='|') # 어떻게|하는지|봐볼까요|연습|테스트

# 영어
tokens = re.findall(r'\w\w+', "Let's see how you do it. #practice @test 😩😬")
print(*tokens, sep='|') # Let|see|how|you|do|it|practice|test

 

  • 해시태그, 이모지 등 포함하기
RE_TOKEN = re.compile(r"""
               ( [#]?[@\w'’\.\-\:]*\w     # words, hash tags and email adresses
               | [:;<]\-?[\)\(3]          # coarse pattern for basic text emojis
               | [\U0001F100-\U0001FFFF]  # coarse code range for unicode emojis
               )
               """, re.VERBOSE)

def tokenize(text):
    return RE_TOKEN.findall(text)

# 한글
tokens = tokenize('어떻게 하는지 한 번 봐볼까요? #연습 @테스트 😩😬')
print(*tokens, sep='|') # 어떻게|하는지|한|번|봐볼까요|#연습|@테스트|😩|😬

# 영어
tokens = tokenize("Let's see how you do it. #practice @test 😩😬")
print(*tokens, sep='|') # Let's|see|how|you|do|it|#practice|@test|😩|😬

 

  • NLTK
    • wort_tokenize 함수를 활용
    • 내부적으로 PuncktSentenceTokenizer와 TreebankWordTokenizer를 사용
    • 표준 텍스트에서는 잘 작동하지만 해시태그나 텍스트 이모티콘에는 결함이 있음
  • 도메인별 토큰 패턴의 정밀도를 높이려면 사용자 지정 정규 표현식을 사용해야함
    • 오픈 소스 라이브러리 참고 (NLTK, textacy)
  • 맞춤법 검사 -> 품질 향상 체크 필요

Spacy

  • 토큰화, 품사 태거(part-of-speech tagger), 의존성 구문 분석기(dependency parser), 개체명 인식기(named-entitiy recognizer)와 같은 처리 파이프라인을 제공
    • 토큰화: 복잡한 언어 종속 규칙과 정규표현식을 기반 -> 매우 빠름
    • 후속 단계: 사전 훈련된 신경망 모델을 사용 -> 많은 시간을 소비, but GPU에서 실행 가능 - spacy.prefer_gpu()로 확인

  • 파이프라인 수행 중에도 원본 텍스트가 유지됨
  • 단계별 원본 텍스트의 추가 정보가 담긴 정보 계층이 존재
    • ex. Doc: 처리된 텍스트에 대한 주요 정보를 가짐, Token 목록을 포함
  • 다른 라이브러리와 비교했을 때, spacy 모델이 가장 빠름

토큰화 예시

  • lemma_: 원형
  • pos_: 품사 태그
  • dep_: 종속성 태그
  • ent_*: 개체 유형
  • is_*: 특징 플래그 -> 규칙을 기반으로 생성됨
text = "Learning NLP is really funny!"
nlp = spacy.load("en_core_web_sm")
doc = nlp(text)
type(doc) # spacy.tokens.doc.Doc
display_nlp(doc)

 

사용자 정의 토큰화

  • 문제에 맞게 토큰을 알맞게 분리시켜야하는 경우가 있음
  • 중위, 접두사, 접미사 분할에 대한 개별 규칙을 사용해 토큰화를 변형하면 해결할 수 있음
    • 중위 규칙: 단어 사이에 하이픈(-)이 있으면 분리 (compile_infix_regex)
    • 접두사 규칙: 단어 바로 앞에 # or _와 같은 문자가 있으면 분리 (compile_prefix_regex)
    • 접미사 규칙: 단어 바로 뒤에 # or _와 같은 문자가 있으면 분리 (compile_suffix_regex)
import re
import spacy
from spacy.tokenizer import Tokenizer
from spacy.util import compile_prefix_regex, compile_infix_regex, compile_suffix_regex

def custom_tokenizer(nlp):
    
    # use default patterns except the ones matched by re.search
    prefixes = [pattern for pattern in nlp.Defaults.prefixes 
                if pattern not in ['-', '_', '#']]
    suffixes = [pattern for pattern in nlp.Defaults.suffixes
                if pattern not in ['_']]
    infixes  = [pattern for pattern in nlp.Defaults.infixes
                if not re.search(pattern, 'xx-xx')]

    return Tokenizer(vocab          = nlp.vocab, 
                     rules          = nlp.Defaults.tokenizer_exceptions,
                     prefix_search  = compile_prefix_regex(prefixes).search,
                     suffix_search  = compile_suffix_regex(suffixes).search,
                     infix_finditer = compile_infix_regex(infixes).finditer,
                     token_match    = nlp.Defaults.token_match)

text = "@samoyed : test spacy for fun #smiling-sammy _url_ ;-) 😋👍"
nlp = spacy.load('en_core_web_sm')

# 기본 결과 (@samoyed|:|test|spacy|for|fun|#|smiling|-|sammy|_|url|_|;-)|😋|👍|)
doc = nlp(text)
for token in doc:
    print(token, end="|")
print()
    
# 커스터마이징 결과 (@samoyed|:|test|spacy|for|fun|#smiling-sammy|_url_|;-)|😋|👍|)
nlp.tokenizer = custom_tokenizer(nlp)

doc = nlp(text)
for token in doc:
    print(token, end="|")

 

불용어 제거 (Stopwords)

  • is_stop을 기준으로 필터링
  • 불용어 목록은 spacy.lang.en.STOP_WORDS에서 볼 수 있음

불용어 예시

원형 추출 (Lemmatization)

  • 불변형 어근에 매칭하는 과정 (ex. meet, meeting, met -> meet)
  • 원형만 추출하면 어휘량이 적어지므로 모델 품질을 향상시킬 수 있고, 학습 시간과 모델 크기를 줄일 수 있음
  • 명사, 동사, 형용사와 같은 특정 범수로 단어의 유형을 제한하는 것이 좋음 -> pos_ 활용
  • 인칭 대명사(I, me, you)는 -PRON-으로 반환
  • 때로는 원형 추출을 하지 않는 것이 좋을 수도 있음 (ex. 감성 분석)
import textacy

text = "Studying NLP is really fun for smiling sammy."
doc = nlp(text)

# 토큰 추출
tokens = textacy.extract.words(doc, 
            filter_stops = True,           # default True, remove stop words from word list
            filter_punct = True,           # default True, remove punctuation from word list
            filter_nums = True,            # default False, if True -> remove number-like words (e.g. 10, "ten")
            include_pos = ['VERB', 'ADJ', 'NOUN'], # default None = include all
            exclude_pos = None,            # default None = exclude none
            min_freq = 1)                  # minimum frequency of words

print(*[t for t in tokens], sep='|') # Studying|fun|smiling|sammy

# 원형 추출
def extract_lemmas(doc, **kwargs):
    return [t.lemma_ for t in textacy.extract.words(doc, **kwargs)]

lemmas = extract_lemmas(doc, include_pos=['ADJ', 'NOUN'])

print(*lemmas, sep='|') # fun|sammy

 

명사구 추출

  • 품사 태그에 패턴 일치를 적용할 수 있음 (with Spacy, textacy)
    • Spacy에는 규칙 기반 매처가 존재
    • textacy에는 패턴 기반 구문 추출을 위한 래퍼가 존재
def extract_noun_phrases(doc, preceding_pos=['NOUN'], sep='_'):
    patterns = []
    for pos in preceding_pos:
        patterns.append(f"POS:{pos} POS:NOUN:+")

    if textacy.__version__ < '0.11':
        # as in book
        spans = textacy.extract.matches(doc, patterns=patterns)
    else:
        # new textacy version
        spans = textacy.extract.matches.token_matches(doc, patterns=patterns)

    return [sep.join([t.lemma_ for t in s]) for s in spans]

text = "Studying NLP is really fun for smiling sammy."
doc = nlp(text)

print(*extract_noun_phrases(doc, ['VERB', 'ADJ', 'NOUN']), sep='|') # smile_sammy
# NLP는 고유명사로 인식이되어 제외됨

 

개체명 추출 (Name Entity Recognition; NER)

  • 텍스트에서 사람 이름, 주소와 같은 개체를 감지하는 절차
  • ent_type_, ent_iob_ 활용
    • ent_type_: named entity recognizer로 예측된 토큰의 개체명 type
    • ent_iob: 해당 토큰에서 개체가 시작(B), 개체의 일부분(I), 개체가 아닌 경우(O)
from spacy import displacy

text = "Warren Buffett seems to be the greatest investor in history in America."
doc = nlp(text)

for ent in doc.ents:
    print(f"({ent.text}, {ent.label_})", end=" ") # (Warren Buffett, PERSON) (America, GPE) 

displacy.render(doc, style='ent', jupyter=True)

대규모 데이터셋 적용

  • GPU를 활용 가능
  • 일괄 처리 기능을 사용하는 것이 좋음 -> nlp.pipe
  • multiprocessing 라이브러리를 사용하여 파이썬 작업을 병렬화하는 것도 속도 향상에 도움이 됨
    • pandas 작업 병렬화: Dask, Modin, Vaex를 권장
    • pandas에 직접 병렬 적용 연산자를 추가: pandarallel

Reference

반응형

댓글