새소식

부스트캠프 AI Tech 4기

[NLP_KLUE] 2. 한국어 전처리

  • -

자연어처리의 단계

  • Task 설계
  • 필요 데이터 수집
  • 통계학적 분석
    • Token 개수 -> 아웃라이어 제거
    • 빈도 확인 -> 사전(dictionary) 정의
  • 전처리
    • 개행문자 제거
    • 특수문자 제거
    • 공백 제거
    • 중복 표현 제거
    • 이메일, 링크 제거
    • 제목 제거
    • 불용어 제거
    • 조사 제거
    • 띄어쓰기, 문장분리 보정
  • Tagging(라벨링)
  • Tokenizing - 자연어를 어떤 단위로 살펴볼 것인가
    • 어절 tokenizing
    • 형태소 tokenizing
    • WordPiece tokenzing
  • 모델 설계
  • 모델 구현
  • 성능 평가

 

 

한국어 전처리

▮ 전처리를 위한 코퍼스 수집

url 정보만 입력해주면 텍스트를 추출해주는 라이브러리 : newspaper

 

▮ <HTML> 태그 전처리

def remove_html(texts):
    """
    HTML 태그 제거
    """
    preprcessed_text = []
    for text in texts:
        text = re.sub(r"<[^>]+>\s+(?=<)|<[^>]+>", "", text).strip()
        if text:
            preprcessed_text.append(text)
    return preprcessed_text

 

▮ 문장 분리

import kss

sents = []

for sent in context:
    sent = sent.strip()
    if sent:
        splited_sent = kss.split_sentences(sent)
        sents.extend(splited_sent)

 

▮ Normalizing

def remove_email(texts):
    """
    이메일 제거
    """
    preprocessed_text = []
    for text in texts:
        text = re.sub(r"[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+", "", text).strip()
        if text:
            preprocessed_text.append(text)
    return preprocessed_text

 

def remove_hashtag(texts):
    """
    해쉬태그(#) 제거
    """
    preprocessed_text = []
    for text in texts:
        text = re.sub(r"#\S+", "", text).strip()
        if text:
            preprocessed_text.append(text)
    return preprocessed_text

 

def remove_user_mention(texts):
    """
    유저에 대한 멘션(@) 태그 제거
    """
    preprocessed_text = []
    for text in texts:
        text = re.sub(r"@\w+", "", text).strip()
        if text:
            preprocessed_text.append(text)
    return preprocessed_text

 

def remove_url(texts):
    """
    URL을 제거
    """
    preprocessed_text = []
    for text in texts:
        text = re.sub(r"(http|https)?:\/\/\S+\b|www\.(\w+\.)+\S*", "", text).strip()
        text = re.sub(r"pic\.(\w+\.)+\S*", "", text).strip()
        if text:
            preprocessed_text.append(text)
    return preprocessed_text

 

def remove_bad_char(texts):
    """
    문제를 일으킬 수 있는 문자들을 제거
    """
    bad_chars = {"\u200b": "", "…": " ... ", "\ufeff": ""}
    preprcessed_text = []
    for text in texts:
        for bad_char in bad_chars:
            text = text.replace(bad_char, bad_chars[bad_char])
        text = re.sub(r"[\+á?\xc3\xa1]", "", text)
        if text:
            preprcessed_text.append(text)
    return preprcessed_text

 

def remove_press(texts):
    """
    언론 정보 제거
    ``홍길동 기자 (연합뉴스)`` -> ````
    ``(이스탄불=연합뉴스) 하채림 특파원 -> ````
    """
    re_patterns = [
        r"\([^(]*?(뉴스|경제|일보|미디어|데일리|한겨례|타임즈|위키트리)\)",
        r"[가-힣]{0,4} (기자|선임기자|수습기자|특파원|객원기자|논설고문|통신원|연구소장) ",  # 이름 + 기자
        r"[가-힣]{1,}(뉴스|경제|일보|미디어|데일리|한겨례|타임|위키트리)",  # (... 연합뉴스) ..
        r"\(\s+\)",  # (  )
        r"\(=\s+\)",  # (=  )
        r"\(\s+=\)",  # (  =)
    ]

    preprocessed_text = []
    for text in texts:
        for re_pattern in re_patterns:
            text = re.sub(re_pattern, "", text).strip()
        if text:
            preprocessed_text.append(text)    
    return preprocessed_text

 

def remove_copyright(texts):
    """
    뉴스 내 포함된 저작권 관련 텍스트를 제거
    ``(사진=저작권자(c) 연합뉴스, 무단 전재-재배포 금지)`` -> ``(사진= 연합뉴스, 무단 전재-재배포 금지)``
    """
    re_patterns = [
        r"\<저작권자(\(c\)|ⓒ|©|\(Copyright\)|(\(c\))|(\(C\))).+?\>",
        r"저작권자\(c\)|ⓒ|©|(Copyright)|(\(c\))|(\(C\))"
    ]
    preprocessed_text = []
    for text in texts:
        for re_pattern in re_patterns:
            text = re.sub(re_pattern, "", text).strip()
        if text:
            preprocessed_text.append(text)    
    return preprocessed_text

 

def remove_photo_info(texts):
    """
    뉴스 내 포함된 이미지에 대한 label 제거
    ``(사진= 연합뉴스, 무단 전재-재배포 금지)`` -> ````
    ``(출처=청주시)`` -> ````
    """
    preprocessed_text = []
    for text in texts:
        text = re.sub(r"\(출처 ?= ?.+\) |\(사진 ?= ?.+\) |\(자료 ?= ?.+\)| \(자료사진\) |사진=.+기자 ", "", text).strip()
        if text:
            preprocessed_text.append(text)
    return preprocessed_text

 

def remove_useless_breacket(texts):
    """
    괄호 내부에 의미가 없는 정보를 제거
    아무런 정보를 포함하고 있지 않다면, 괄호를 통채로 제거
    ``수학(,)`` -> ``수학``
    ``수학(數學,) -> ``수학(數學)``
    """
    bracket_pattern = re.compile(r"\((.*?)\)")
    preprocessed_text = []
    for text in texts:
        modi_text = ""
        text = text.replace("()", "")  # 수학() -> 수학
        brackets = bracket_pattern.search(text)
        if not brackets:
            if text:
                preprocessed_text.append(text)
                continue
        replace_brackets = {}
        # key: 원본 문장에서 고쳐야하는 index, value: 고쳐져야 하는 값
        # e.g. {'2,8': '(數學)','34,37': ''}
        while brackets:
            index_key = str(brackets.start()) + "," + str(brackets.end())
            bracket = text[brackets.start() + 1 : brackets.end() - 1]
            infos = bracket.split(",")
            modi_infos = []
            for info in infos:
                info = info.strip()
                if len(info) > 0:
                    modi_infos.append(info)
            if len(modi_infos) > 0:
                replace_brackets[index_key] = "(" + ", ".join(modi_infos) + ")"
            else:
                replace_brackets[index_key] = ""
            brackets = bracket_pattern.search(text, brackets.start() + 1)
        end_index = 0
        for index_key in replace_brackets.keys():
            start_index = int(index_key.split(",")[0])
            modi_text += text[end_index:start_index]
            modi_text += replace_brackets[index_key]
            end_index = int(index_key.split(",")[1])
        modi_text += text[end_index:]
        modi_text = modi_text.strip()
        if modi_text:
            preprocessed_text.append(modi_text)
    return preprocessed_text

 

from soynlp.normalizer import *

def remove_repeat_char(texts):
    """
    반복되는 문자 normalizing
    ``ㅋㅋㅋㅋㅋㅋㅋ`` → ``ㅋㅋ``
    """
    preprocessed_text = []
    for text in texts:
        text = repeat_normalize(text, num_repeats=2).strip()
        if text:
            preprocessed_text.append(text)
    return preprocessed_text

 

def clean_punc(texts):
    """
    기호들을 일반화
    """
    punct_mapping = {"‘": "'", "₹": "e", "´": "'", "°": "", "€": "e", "™": "tm", "√": " sqrt ", "×": "x", "²": "2", "—": "-", "–": "-", "’": "'", "_": "-", "`": "'", '“': '"', '”': '"', '“': '"', "£": "e", '∞': 'infinity', 'θ': 'theta', '÷': '/', 'α': 'alpha', '•': '.', 'à': 'a', '−': '-', 'β': 'beta', '∅': '', '³': '3', 'π': 'pi', }

    preprocessed_text = []
    for text in texts:
        for p in punct_mapping:
            text = text.replace(p, punct_mapping[p])
        text = text.strip()
        if text:
            preprocessed_text.append(text)
    return preprocessed_text

 

def remove_repeated_spacing(texts):
    """
    두 개 이상의 연속된 공백을 하나로 치환
    ``오늘은    날씨가   좋다.`` -> ``오늘은 날씨가 좋다.``
    """
    preprocessed_text = []
    for text in texts:
        text = re.sub(r"\s+", " ", text).strip()
        if text:
            preprocessed_text.append(text)
    return preprocessed_text

 

from collections import OrderedDict
def remove_dup_sent(texts):
    """
    중복된 문장 제거
    """
    texts = list(OrderedDict.fromkeys(texts))
    return texts

 

# !pip install git+https://github.com/haven-jeon/PyKoSpacing.git

from pykospacing import Spacing
spacing = Spacing()

def spacing_sent(texts):
    """
    띄어쓰기 보정
    """
    preprocessed_text = []
    for text in texts:
        text = spacing(text)
        if text:
            preprocessed_text.append(text)
    return preprocessed_text

 

# !pip install git+https://github.com/ssut/py-hanspell.git

from hanspell import spell_checker

def spell_check_sent(texts):
    """
    맞춤법 보정
    """
    preprocessed_text = []
    for text in texts:
        try:
            spelled_sent = spell_checker.check(text)
            checked_sent = spelled_sent.checked 
            if checked_sent:
                preprocessed_text.append(checked_sent)
        except:
            preprocessed_text.append(text)
    return preprocessed_text

 

형태소 분석 기반 필터링

# !pip install konlpy
# !bash <(curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh)

from konlpy.tag import Mecab
mecab = Mecab()

def morph_filter(texts):
    """
    명사(NN), 동사(V), 형용사(J)의 포함 여부에 따라 문장 필터링
    """
    NN_TAGS = ["NNG", "NNP", "NNB", "NP"]
    V_TAGS = ["VV", "VA", "VX", "VCP", "VCN", "XSN", "XSA", "XSV"]
    J_TAGS = ["JKS", "J", "JO", "JK", "JKC", "JKG", "JKB", "JKV", "JKQ", "JX", "JC", "JKI", "JKO", "JKM", "ETM"]

    preprocessed_text = []
    for text in texts:
        morphs = mecab.pos(text, join=False)

        nn_flag = False
        v_flag = False
        j_flag = False
        for morph in morphs:
            pos_tags = morph[1].split("+")
            for pos_tag in pos_tags:
                if not nn_flag and pos_tag in NN_TAGS:
                    nn_flag = True
                if not v_flag and pos_tag in V_TAGS:
                    v_flag = True
                if not j_flag and pos_tag in J_TAGS:
                    j_flag = True
            if nn_flag and v_flag and j_flag:
                preprocessed_text.append(text)
                break
    return preprocessed_text

 

def excluded_word_filter(excluded_words, texts):
    """
    특정 단어를 포함하는 문장 필터링
    """
    preprocessed_text = []
    for text in texts:
        include_flag = False
        for word in excluded_words:
            if word in text:
                include_flag = True
                break
        if not include_flag:
            preprocessed_text.append(text)
    return preprocessed_text

 

def remove_stopwords(sents):
    #  큰 의미가 없는 불용어 정의
    stopwords = ['소취요', '-', '조드윅', '포스터', '앓는', '서린']
    preprocessed_text = []
    for sent in sents:
        sent = [w for w in sent.split(' ') if w not in stopwords]# 불용어 제거
        preprocessed_text.append(' '.join(sent))
    return preprocessed_text

 

def min_max_filter(min_len, max_len, texts):
    """
    문장을 최대, 최소 길이로 필터링
    """
    preprocessed_text = []
    for text in texts:
        if min_len < len(text) and len(text) < max_len:
            preprocessed_text.append(text)
    return preprocessed_text

 

 


부스트캠프 AI Tech 교육 자료를 참고하였습니다.

728x90
Contents