또르르's 개발 Story

[Stage 2 - 02] 한국어 전처리 / 토크나이징 본문

[P Stage 2] KLUE/프로젝트

[Stage 2 - 02] 한국어 전처리 / 토크나이징

또르르21 2021. 4. 14. 10:43

 1️⃣ Goal

 

  • [BaseLine 작성] (추가 : 4/12, 새로운 Baseline code)

  • [Data Processing]

    - Exploratory Data Analysis (추가 : 4/12, 기간 : 4/12 ~ )


    - Cross-validation 사용 (추가 : 4/12)

    - 데이터 불균형 해소 (추가 : 4/12)

    - 한국어 전처리 (추가 : 4/13, 기간 : 4/13 ~ )

    - 새로운 tokenizer 사용 (추가 : 4/12, 기간 : 4/13 ~ ) 

    - 형태소 분류기 -> BERT wordpiece (추가 : 4/13)

  • [Model]

    - BERT 모델 사용 (추가 : 4/12)

    - GPT 모델 사용 (추가 : 4/12)

    - ELECTRA 모델 사용 (추가 : 4/12)

    - KoBERT 모델 사용 (추가 : 4/12)

  • [Training]

    - 앙상블 시도 (추가 : 4/12)

    - Hyperparameter 변경 (추가 : 4/12)

    - Learning Schedular 사용 (추가 : 4/12)

    - 좋은 위치에서 Checkpoint 만들기 (추가 : 4/12)

    - NNI (Auto ML) 사용 (추가 : 4/12)

  • [Deploy]

    - Python 모듈화 (추가 : 4/12)

 

 

 

 

2️⃣ Learning

dororo21.tistory.com/129

 

[Stage 2 - 이론] 한국어 토큰화

1️⃣ 자연어 처리 단계 Task 설계 필요 데이터 수집 통계학적 분석 - Token 개수 -> 아웃라이어 제거 - 빈도 확인 -> 사전(dictionary) 정읟 전처리 - 개행 문자 / 특수 문자 제거 - 공백 제거 - 중복 표현

dororo21.tistory.com

[임베딩 측정 방법]

 

  • WordSim353 : annotator를 사용해 단어의 유사성과 관련성을 비교합니다.
h	tiger	cat	7.35
i	tiger	tiger	10.00
t	book	paper	7.46
M	computer	keyboard	7.62
t	computer	internet	7.58
S	plane	car	5.77
S	train	car	6.31

 

  • Spearman's correlation : 두 변수 의 순위 사이의 통계적 의존성을 측정하는 비모수적인 척도

    즉, 순위가 매겨진 변수 간의 피어슨 상관계수로 정의됩니다.

피어슨 상관계수는 다음과 같이 정의됩니다.

 

https://ko.wikipedia.org/wiki/%ED%94%BC%EC%96%B4%EC%8A%A8_%EC%83%81%EA%B4%80_%EA%B3%84%EC%88%98

 

이 피어슨 상관계수를 순위로 매긴 것이 Spearman's correlation입니다.

 

https://ko.wikipedia.org/wiki/%EC%8A%A4%ED%94%BC%EC%96%B4%EB%A8%BC_%EC%83%81%EA%B4%80_%EA%B3%84%EC%88%98

 

  • Analogy test : 두 단어의 관계를 유추하는 테스트

    A:B의 관계가 주어지고, C가 주어졌을 때 D를 유추하는 방법

https://www.google.com/url?sa=i&url=https%3A%2F%2Fenglish-analogy-test-with-answers.liyanarachchialappuzha.online%2F&psig=AOvVaw1gAz4XPRc6EaB7HCaZBNbk&ust=1618452445230000&source=images&cd=vfe&ved=0CAMQjB1qFwoTCMjrpq7T_O8CFQAAAAAdAAAAABAD

 

 

3️⃣ Main Task

1) 한국어 전처리

1-1) 뉴스 기사 크롤링 (newparaper3k)

 

newspaper3k는 url 정보만 입력하면 텍스트를 추출해줍니다.

!pip install newspaper3k

 

newspapaer 라이브러리는 한국어 크롤링도 지원해줍니다.

import newspaper

>>> newspaper.languages()
Your available languages are:

input code		full name
  bg			  Bulgarian
  hu			  Hungarian
  ro			  Romanian
  ar			  Arabic
  et			  Estonian
  no			  Norwegian
  ko			  Korean
  nb			  Norwegian (Bokmål)
  be			  Belarusian
  sw			  Swahili
  sv			  Swedish
  vi			  Vietnamese
  hr			  Croatian
  ru			  Russian
  el			  Greek
  zh			  Chinese
  fr			  French
  fa			  Persian
  fi			  Finnish
  nl			  Dutch
  ja			  Japanese
  hi			  Hindi
  it			  Italian
  sr			  Serbian
  de			  German
  es			  Spanish
  pl			  Polish
  id			  Indonesian
  en			  English
  tr			  Turkish
  mk			  Macedonian
  da			  Danish
  sl			  Slovenian
  uk			  Ukrainian
  he			  Hebrew
  pt			  Portuguese

 

뉴스 크롤링은 다음과 같습니다.

from newspaper import Article

news_url = [NEWS_URL]	# news url 적기

article = Article(news_url, language='ko')
article.download()

article.parse()


>>> print('title:', article.title)

>>> print('context:', article.text)

 

 

1-2) 전처리

이후 다음과 같은 전처리를 수행할 수 있습니다.

 

  • HTML 태그 제거

    regex를 사용해 HTML 태그를 제거할 수 있습니다.
re.sub(r"<[^>]+>\s+(?=<)|<[^>]+>", "", text).strip()

 

  • 문장 분리

    한국어 문장분리기 중 kss 라이브러리를 사용해서 문장을 분리할 수 있습니다.
!pip install kss
import kss
splited_sent = kss.split_sentences(sent)

 

  • Email 제거

    regex를 통해 Email를 제거할 수 있습니다.
 text = re.sub(r"[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+", "", text).strip()

 

  • Hashtag 제거

    regex를 통해 hashtag를 제거할 수 있습니다.
text = re.sub(r"#\S+", "", text).strip()

 

  • Mention(@) 제거

    regex를 통해 유저에 대한 멘션(@)을 제거할 수 있습니다.
text = re.sub(r"@\w+", "", text).strip()

 

  • URL 제거

    regex를 통해 본문에 포함된 URL을 제거할 수 있습니다.
text = re.sub(r"(http|https)?:\/\/\S+\b|www\.(\w+\.)+\S*", "", text).strip()

text = re.sub(r"pic\.(\w+\.)+\S*", "", text).strip()

 

  • bad char 제거

    regex를 통해 문제가 나타날 수 있는 문자들을 제거합니다.
text = re.sub(r"[\+á?\xc3\xa1]", "", text)

 

  • 언론 정보 제거

    regex를 통해 언론사, 언론 정보를 제거합니다.

 

  • 저작권 관련 텍스트 제거

    regex를 통해 저작권 관련 텍스트를 제거합니다.
re_patterns = [

        r"\<저작권자(\(c\)|ⓒ|©|\(Copyright\)|(\(c\))|(\(C\))).+?\>",
        
        r"저작권자\(c\)|ⓒ|©|(Copyright)|(\(c\))|(\(C\))"
        
    ]

 

  • 이미지 label 제거

    regex를 통해 뉴스에 포함된 이미지 label을 제거합니다.
text = re.sub(r"\(출처 ?= ?.+\) |\(사진 ?= ?.+\) |\(자료 ?= ?.+\)| \(자료사진\) |사진=.+기자 ", "", text).strip()

 

  • 반복된 문자 줄이기

    soynlp를 사용해서 중복된 문자를 줄입니다.
!pip install soynlp
from soynlp.normalizer import *

>>> print(repeat_normalize('미히히히히히히히힛', num_repeats=2))    # 반복되는 횟수

미히히힛

 

  • 특수문자 제거

    특수문자 mapping을 사용해서 replace 합니다.
punc_mapping = {"‘": "'", "₹": "e", "´": "'", "°": "", "€": "e", "™": "tm", "√": " sqrt ", "×": "x", "²": "2", "—": "-", "–": "-", "’": "'", "_": "-", "`": "'", '“': '"', '”': '"', '“': '"', "£": "e", '∞': 'infinity', 'θ': 'theta', '÷': '/', 'α': 'alpha', '•': '.', 'à': 'a', '−': '-', 'β': 'beta', '∅': '', '³': '3', 'π': 'pi', }

 

  • 연속된 공백 제거

    두 개 이상의 연속된 공백을 하나로 만듭니다.

 

  • 중복된 문장 제거

    dictionary로 중복을 제거합니다.
texts = list(OrderedDict.fromkeys(texts))

 

  • 띄어쓰기 추가

    띄어쓰기가 안되어있는 문장은 pykospacing을 통해 띄어쓰기 시킬 수 있습니다.
!pip install git+https://github.com/haven-jeon/PyKoSpacing.git
from pykospacing import spacing
>>> spacing("안녕하세요저는제임스입니다.")

안녕하세요 저는 제임스입니다.

 

  • 맞춤법 검사

    py-hanspell을 사용해서 맞춤법을 검사할 수 있습니다.
!pip install git+https://github.com/ssut/py-hanspell.git
from hanspell import spell_checker

spell_checker.check("외안돼")

checked = spelled_sent.checked 

 

  • 형태소 분석기를 사용한 문장 필터링

    konlpy의 Mecab을 사용해 명사(NN), 동사(V), 형용사(J)가 3개의 품사가 최소 하나씩 존재해야합니다.
!pip install konlpy
# Mecab 설치 / 가장 빠른 한국어 형태소 분석기

!bash <(curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh)
from konlpy.tag import Mecab


mecab = Mecab()

morphs = mecab.pos("아버지가방에들어가신다.", join=False) # pos = part of speech

>>> print(morphs)

[('아버지', 'NNG'), ('가', 'JKS'), ('방', 'NNG'), ('에', 'JKB'), ('들어가', 'VV'), ('신다', 'EP+EF'), ('.', 'SF')]

 

Tag들은 다음과 같이 분류할 수 있습니다. (명사 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"]

 

  • 불용어 필터링

    큰 의미가 없는 불용어를 필터링합니다.

 

  • 문장 최대, 최소 길이 필터링

    문장의 MAX LEN, MIN LEN을 기준으로 필터링합니다.

 

  • 다른 나라 언어 필터링

    음절 단위로 비교해 사전에 정의한 Unicode 범위 내에 존재하는 언어들을 필터링합니다.

    주의할 점은 같은 unicode 체계를 사용하는 언어가 있어 같이 삭제가 될 수 있습니다. (독일어를 삭제하려고 헀지만 같은 알파벳으로 영어가 삭제됨)
a = int(range_s, 16) # 16진수 -> 10진수 변환
 
b = int(range_e, 16)
 
if a<= ord(w) and ord(w) <= b:

 

2) 한국어 토크나이징

토크나이징의 목적은 크게 두 가지입니다.

  1. 의미를 지닌 단위로 자연어를 분절
  2. Model의 학습 시, 동일한 size로 입력

 

따라서 token의 개수가 부족할 때는 [padding] 처리를 해주고, 개수가 많을 때는 token을 잘라서 반환해주어야 합니다.

max_seq_length = 10

# padding

tokenized_text += ["[PAD]"] * (max_seq_length - len(tokenized_text))


>>> print(tokenized_text)

['이순신은', '조선', '중기의', '무신이다.', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']

 

 

  • 어절 단위 tokenizing

    모든 단어를 띄어쓰기 단위로 분리
text = "이순신은 조선 중기의 무신이다."

tokenized_text = text.split(" ") 


>>> print(tokenized_text)  

['이순신은', '조선', '중기의', '무신이다.']

 

  • 형태소 단위 tokenizing

    형태소 분석기를 사용해서 분리
!pip install konlpy
!bash <(curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh)
from konlpy.tag import Mecab

mecab = Mecab()

text = "이순신은 조선 중기의 무신이다."

tokenized_text = [lemma[0] for lemma in mecab.pos(text)]


>>> print(tokenized_text)

['이순신', '은', '조선', '중기', '의', '무신', '이', '다', '.']

 

  • 음절 단위 tokenizing

    자연어를 한 글자씩 분리
text = "이순신은 조선 중기의 무신이다."

tokenized_text = list(text)


>>> print(tokenized_text)  

['이', '순', '신', '은', ' ', '조', '선', ' ', '중', '기', '의', ' ', '무', '신', '이', '다', '.']

 

  • 자소 단위 tokenizing

    초성, 중성, 종성 총 3개의 자소로 분리
!pip install hgtk
import hgtk

text = "이순신은 조선 중기의 무신이다."

tokenized_text = list(hgtk.text.decompose(text))


>>> print(tokenized_text)  # ᴥ는 음절과 음절 단위를 구분해주는 구분자

['ㅇ', 'ㅣ', 'ᴥ', 'ㅅ', 'ㅜ', 'ㄴ', 'ᴥ', 'ㅅ', 'ㅣ', 'ㄴ', 'ᴥ', 'ㅇ', 'ㅡ', 'ㄴ', 'ᴥ', ' ', 'ㅈ', 'ㅗ', 'ᴥ', 'ㅅ', 'ㅓ', 'ㄴ', 'ᴥ', ' ', 'ㅈ', 'ㅜ', 'ㅇ', 'ᴥ', 'ㄱ', 'ㅣ', 'ᴥ', 'ㅇ', 'ㅢ', 'ᴥ', ' ', 'ㅁ', 'ㅜ', 'ᴥ', 'ㅅ', 'ㅣ', 'ㄴ', 'ᴥ', 'ㅇ', 'ㅣ', 'ᴥ', 'ㄷ', 'ㅏ', 'ᴥ', '.']

 

  • WordPiece tokenizing

    NLP 모델이 학습한 단어 조각으로 분리
!pip install transformers
!mkdir wordPieceTokenizer
from tokenizers import BertWordPieceTokenizer


# Initialize an empty tokenizer

wp_tokenizer = BertWordPieceTokenizer(

    clean_text=True,    # [이순신, ##은, ' ', 조선]에서 띄어쓰기 ' '를 지우고 싶다면 True // bert model은 모두 clean_text가 true
    
    handle_chinese_chars=True,  # 본문 내에 있는 한자가 음절 단위로 분리
    
    strip_accents=False,    # True: [YepHamza] -> [Yep, Hamza] // 대문자가 나오는 기준으로 분리
    
    lowercase=False,  # 모두 소문자로 변경 // False로 하는 것이 성능이 더 좋음
    
)

# And then train

wp_tokenizer.train(

    files="[FILE_PATH]",
    
    vocab_size=10000,   # 내가 만들고 싶은 vocab의 size이며, 크게 만들수록 음절 단위로 잘림
    
    min_frequency=2,    # 2개 이하로 등장하면 vocab으로 만들지 않음
    
    show_progress=True,
    
    special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"],
    
    limit_alphabet=1000,
    
    wordpieces_prefix="##"    # prefix // ##은 앞에 연결된 음절이 있다는 의미
    
)

# Save the files

wp_tokenizer.save_model("wordPieceTokenizer", "my_tokenizer")
text = "이순신은 조선 중기의 무신이다."

tokenized_text = wp_tokenizer.encode(text)


>>> print(tokenized_text)

Encoding(num_tokens=10, attributes=[ids, type_ids, tokens, offsets, attention_mask, special_tokens_mask, overflowing])


>>> print(tokenized_text.tokens)

['이', '##순', '##신은', '조선', '중', '##기의', '무', '##신이', '##다', '.']


>>> print(tokenized_text.ids)

[708, 1340, 7612, 1999, 756, 2601, 452, 8478, 1016, 16]

 

4️⃣ Sub Task

1) 개인정보 보호를 위한 regex 정리

개인정보 보호를 위해 "*"을 많이 사용하는데 re.sub를 많이 사용합니다.

근데 re.sub를 사용하면 match된 모든 값들을 repl 값들로 바꿔버리는 문제가 발생하였습니다.

 

  • 김철수 -> 김**
  • 김철수 -> ***

따라서 김철수 -> 김** 로 바꾸고 싶다면 (?<= ... ) 문구를 사용하면 됩니다.

만약, (?<= ABC) DEF 이런식으로 사용하게 되면 ABCDEF를 re.sub가 match시키지만, 변환은 DEF만 하게 됩니다.

 

이름 필터링은 다음과 같이 만들 수 있습니다.

text = "김철수"

text = re.sub("(?<=[가-힣])[가-힣]{1,3}", r'**' ,text)


>>> print(text)

"김**"

 

이메일 필터링은 다음과 같이 만들 수 있습니다.

text = "cheul_INFP@gmail.com"

text = re.sub(r"(?<=[a-zA-Z])[\w_-]+(?=@)", '*'*(len(text.split("@")[0])-1),text)


>> print(text)

"c*********@gmail.com"

 

 

2) 한글 발음으로 변환

'소리나는 대로' 표현하기 위해 일반적인 자연어 텍스트를 발음 형태로 변환할 수 있습니다.

!pip install g2pk
from g2pk import G2p

g2p = G2p()

>>> g2p(texts)

 

 

5️⃣ Evaluation

 

Data processing Model Training Time Accuracy
4/13 -
EDA
-
    1h -
4/13     -
new baseline
code
-
30m 59.3000%
           

 


1) 한국어 토크나이징

  • 위에서 배운 한국어 전처리와 토크나이징을 실제 프로젝트에 적용해보는 것이 목표입니다.

 

2) 차후 목표

  • AI 모델 사용하기

  • 프로젝트에 적용 가능한 최적의 Tokenizer 찾기 
Comments