본문 바로가기
AI/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

반응형

댓글