💬 한국어 텍스트 데이터 전처리
- 텍스트 데이터는 보통 그 자체로 사용하기보다, 의미의 단위로 나눠서 활용 여부나 사이 연관 관계를 찾는다.
- 저번 장에서 확인한 대로, 데이터셋의 텍스트 데이터는 한국어 문장으로 구성되어 있다.
- 학습할 때마다 한국어 형태소 분리를 실행해도 되지만, 시간이 너무 많이 걸려서, 텍스트 데이터를 전처리 해놓기로 한다.
한국어 텍스트 데이터 전처리 방법
- 텍스트 데이터의 전처리 단계는 다음과 같다.
1. 텍스트 데이터를 형태소 단위로 분리한다. 2. 분리된 데이터 중, 불용어를 제거한다. (Optional) 3. 동의어를 mapping 할 수 있다면, 동의어를 mapping한다. (Optional) 4. word dictionary를 생성 5. word를 정수 인코딩한다. |
1. 한국어 형태소 분리 (Tokenization)
- 한국어의 경우에는 영어와 달리, 의미의 단위가 띄어쓰기와 정확히 일치하지 않는다.
- 과거부터 한국어 형태소 분리가 연구되어 여러 방법들이 존재한다.
- 파이썬은 "konlpy" 라이브러리에서 다양한 형태소 분석기를 제공한다. (자세한 내용은 https://konlpy.org/ko/latest/index.html 참조)
- konlpy에서는 다양한 형태소 분석기를 제공해 주지만, 그중 "Okt"(Twitter에서 개발한 오픈소스 한국어 처리기)를 사용하여 한국어 형태소 분리 처리를 하기로 한다. (선택한 이유는 속도 때문이다.)
처리 전 | 처리 후 |
"아버지가 방에 들어가신다." | ["아버지","가","방","에","들어가신다","."] |
2. 불용어 처리
- 불용어(Stopword)는 분석에 필요하지 않거나, 분석 결과에 영향을 미치는 단어를 말한다. (예시: 은,는,이,가)
- 자연어 처리에서는 불용어 제거가 매우 중요하다. 불용어는 실제 분석에는 이용되지 않지만, 일반적으로 문장에서 등장하는 빈도수가 높아져서, 해당 단어들이 중요한 단어로 인식될 수가 있다.
- 또한, 불용어 제거는 전처리 과정에서 처리할 텍스트 데이터의 양이 줄어서 처리 속도 향상을 위해 꼭 필요하다.
- 다만, 불용어는 분석의 목적에 따라 달라지는 상대적인 개념이기 때문에, 분석에 영향을 미치지 않는 단어만 넣도록 한다. 예를 들어, 일반적으로 '?'는 Elastic Search 등의 텍스트 검색을 위한 처리에서 불용어 처리가 될 수 있지만, 우리가 다루고자 하는 비윤리적 텍스트 검출에서는 상대를 비꼬는 문장을 찾을 수 있는 중요한 단서가 되기도 한다.
- 일반적으로 특수문자는 문장에서 제거한 후에, 한국어 형태소 분리하지만, 해당 Task에서는 특수문자가 분류에 많이 사용될 것으로 생각되어서, 실제 사용되지 않을 것으로 보이는 특수문자만 불용어로 처리하였다.
불용어 예시 : stopwords = ['의','가','에','들','는','잘','걍','과','도','를','으로','한','하다','!','?','<','>','(',')','[',']','|','#','.']
|
3. 동의어 처리
- 동의어 처리는 일반적으로 텍스트 검색 기능에서 유사한 단어를 찾기 위해 자주 사용된다.
- 비윤리적 텍스트 문장 검출 데이터셋에서는 일반적인 단어를 특수문자나 축약어로 표현해 놓은 데이터가 많다. (예시: ㅇㅋ, Ok, 오키, 옿키)
- 좋은 성능의 검출 모델을 만들기 위해서 동의어 처리도 매우 중요할 것으로 생각되지만, 현실적으로 공수가 너무 많이 소요되어, 별도의 동의어 처리는 하지 않았다.
4. Word Dictionary 생성
- 학습 데이터셋을 형태소 분리하고, 불용어와 동의어 처리까지 처리한 후, 학습 데이터를 기반으로 워드 딕셔너리를 생성한다.
- 실사용 시, Word Dictionary에 포함되지 않는 단어가 등장했을 경우에는 모델은 해당 단어에 대한 정보를 사용할 수 없다. (자연어 처리 시, 다양하고 풍부한 학습 데이터가 필요한 이유이기도 하다.)
- 각 단어에 대한 고유의 번호를 지정한다.
- 모든 단어를 사용하는 것이 다양성 측면에서는 좋겠지만, 속도나 성능을 고려하면, word dictionary의 크기를 무작정 늘리는 것은 좋지 않다. (일반적으로 최대 단어 개수를 지정한다. )
- 정수 인코딩과 모델 테스트 시 빠른 사용을 위해, 인덱싱(key: 숫자, value: word)과 역인덱싱(key: word, value: 숫자)을 모두 진행해 놓는 것이 좋다.
문장 | 형태소 분리 | Word Dictionary | 인덱싱 |
"아버지가 방에 들어가신다." | ["아버지","가","방","에","들어가신다","."] | {<unk>:0,<pad>:1,"아버지":2, "방":3, "들어가신다":4} | {'0':<unk>,'1':<pad>,'2': "아버지", '3':"방", '4':"들어가신다"} |
5. 텍스트의 Word를 정수 인코딩
- 모델의 학습을 위해, 텍스트를 정수로 인코딩해주는 작업이 필요하다.
- Word Dictionary를 기반으로 학습 데이터의 각 단어들을 정수로 변환한다.
- Word Dictionary에 포함되지 않은 불용어 등은 "<unk>"라는 wildcard로 치환한다. 이를 통해, 모델이 입력받는 word 데이터의 종류는 Word Dictionary에 존재하는 단어 개수로 제한된다.
- 정수 인코딩을 진행하면, 문장마다의 인코딩 벡터의 길이는 전부 다르다. 학습을 위해 데이터를 정형화하는 편이 좋기 때문에, 인코딩 벡터의 끝부분에 "<pad>"값을 넣어, 벡터의 길이를 모두 같게 만들어준다.
처리 전 | 처리 후 |
"아버지가 방에 들어가신다." | 2,0,3,0,4,0,1,1,1,1 |
데이터 전처리 코드 구현
- 전처리 과정을 사전에 진행해 놓기 위한, 코드를 구현하였다. 다만, 학습과 테스트 시 코드 통일성을 위해, 정수 인코딩 부분은 모델 데이터셋 정의 과정에 넣었다. (테스트 데이터도 정수 인코딩은 진행해야 하기 때문에, Dataset 구성 파트에서 설명 예정)
- 자연어 Dataset 처리를 용이하게 하기 위해, "torchtext"라는 pytorch에서 제공해 주는 자연어 처리용 패키지를 사용하였다. (하지만, pytorch는 데이터셋의 종류에 따른 전처리를 모두 통일하기 위해 현재는 torch를 사용하도록 권장하고 있다. )
- Okt가 빠르긴 하지만, 처리 데이터가 많기 때문에 전처리에 시간이 많이 소요된다.
- 데이터 전처리 단계를 대략적으로 구성하였지만, 실사용에서는 불용어 지정이나 어느 종류의 형태소 분석기를 사용할 것인지, word dictionary를 어떻게 구성할 것인지 등이 성능을 결정하는 매우 중요한 단계이다.
from konlpy.tag import *
from torchtext import data
import json
import pandas as pd
tokenizer = Okt()
stopwords = ['의','가','에','들','는','잘','걍','과','도','를','으로','한','하다','!','?','<','>','(',')','[',']','|','#','.']
# 텍스트 전처리 함수
def norm_morphs(x):
x = tokenizer.normalize(x) # 텍스트 Normalization
x = tokenizer.morphs(x) # 형태소 분리
x = [word for word in x if not word in stopwords] #불용어 처리
return x
if __name__ =='__main__':
# 데이터셋 위치 지정
data_dir = r"..\korean_language\data"
# ID: 문서의 번호, TEXT: 문장 데이터(전처리 함수를 지정할 수 있음), LABEL: 윤리성 유무를 나타내는 LABEL
ID = data.Field(sequential=False, use_vocab=False)
TEXT = data.Field(sequential=True, use_vocab=True, tokenize = norm_morphs, batch_first=True, tokenizer_language='ko')
LABEL = data.Field(sequential=False, use_vocab=False, is_target=True)
# Torch Text의 splits를 이용해서, 데이터를 한번에 불러올 수 있다.
train_data, test_data = data.TabularDataset.splits(path=data_dir, train='train', test='test', format='tsv', fields=[('id',ID), ('label',LABEL),('temp1',None),('temp2',None),('temp3',None),('text',TEXT)], skip_header=True)
# word dictionary를 만듬 (최대 크기와, 최소 빈도수를 지정)
TEXT.build_vocab(train_data, min_freq=2, max_size=100000)
# word dictionary를 저장
with open('./dictionary.json','w') as f:
json.dump(TEXT.vocab.stoi, f, ensure_ascii=False, indent=4)
# Index dictionary를 저장
index_dict = {v: k for k, v in TEXT.vocab.stoi.items()}
with open('./index_dictionary.json','w') as f:
json.dump(index_dict, f, ensure_ascii=False, indent=4)
id_list = []
text_list = []
label_list = []
df = pd.DataFrame()
for id, data_dict in enumerate(train_data):
id_list.append(id)
text_list.append('|'.join(data_dict.text))
label_list.append(data_dict.label)
df['id'] = id_list
df['text'] = text_list
df['label'] = label_list
df.to_csv('train.csv', index=False,sep = '#')
id_list = []
text_list = []
label_list = []
df = pd.DataFrame()
for id, data_dict in enumerate(test_data):
id_list.append(id)
text_list.append('|'.join(data_dict.text))
label_list.append(data_dict.label)
df['id'] = id_list
df['text'] = text_list
df['label'] = label_list
df.to_csv('test.csv', index=False,sep = '#')
print("Train Data :",len(train_data))
print("Test Data :",len(test_data))
전처리를 진행하였으니, Dataset을 정의하고, 모델을 만들어볼 차례이다!
'NLP' 카테고리의 다른 글
자연어처리 모델 만들기-(1).데이터셋 분석-2 (2) | 2023.03.04 |
---|---|
자연어처리 모델 만들기-(1).데이터셋 분석-1 (1) | 2023.03.04 |
자연어처리 모델 만들기-(0).환경 세팅 (1) | 2023.02.27 |