일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- programmers
- 프로그래머스
- 더현대서울 맛집
- 편스토랑
- 데이콘
- AI 경진대회
- ubuntu
- 프로그래머스 파이썬
- 편스토랑 우승상품
- Docker
- 금융문자분석경진대회
- Real or Not? NLP with Disaster Tweets
- SW Expert Academy
- hackerrank
- 우분투
- PYTHON
- 캐치카페
- 파이썬
- 코로나19
- gs25
- ChatGPT
- dacon
- Kaggle
- Git
- Baekjoon
- 자연어처리
- 맥북
- leetcode
- github
- 백준
- Today
- Total
솜씨좋은장씨
[DACON] 청와대 청원 : 청원의 주제가 무엇일까? - 1일차 본문
그동안 Elasticsearch를 활용하여 검색 시스템을 개발하면서 자연어처리에 대해서 공부를 좀 소홀히 한 느낌이있어
다시 기존에 공부했던 내용을 리마인드 시킬 겸!
데이콘에서 교육용으로 열려있는 청와대 청원 분류 문제를 풀어보기로 했습니다.
이 문제는 청와대 청원이 0 : 인권 / 성평등 | 1 : 문화 / 예술 / 체육 / 언론 | 2 : 육아 / 교육 이 세가지 중 어떤 카테고리에 속하는지
분류를 하면되는 문제입니다.
모든 과정은 Google Colab의 GPU 환경에서 진행하였습니다.
먼저 pandas의 read_csv로 데이터를 불러와 각 카테고리마다 데이터가 몇개씩 존재하는지 확인해 보았습니다.
import pandas as pd
train_data = pd.read_csv("./data/train.csv")
test_data = pd.read_csv("./data/test.csv")
train_data['category'].value_counts().plot(kind='bar')
print(train_data.groupby('category').size().reset_index(name='count'))
category count
0 0 13301
1 1 13337
2 2 13362
각각 약 133,300개 씩으로 매우 균등한 분포를 보였습니다.
별도의 개수 조정은 없이 그대로 진행해보기로 했습니다.
먼저 정규식과 replace를 활용하여 각 데이터에서 특수문자와 개행문자를 제거하고
그 데이터를 clear_text라는 새로운 column을 만들어 저장하였습니다.
from tqdm import tqdm
import re
train_text = list(train_data['data'])
clear_text_list = []
for i in tqdm(range(len(train_text))):
plain_text = str(train_text[i])
clear_text = plain_text.replace("\\","").replace("n"," ")
clear_text = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]', '', clear_text)
clear_text_list.append(clear_text)
train_data['clear_text'] = clear_text_list
train_data
형태소 분석을 위한 Mecab을 설치하기 위해 위의 방법대로 Colab에 Mecab을 설치하고 계속 진행하였습니다.
from konlpy.tag import Mecab
mecab = Mecab()
X_train = []
train_clear_text = list(train_data['clear_text'])
for i in tqdm(range(len(train_clear_text))):
token_data = mecab.morphs(train_clear_text[i])
token_data = [word for word in token_data if len(word) > 1]
X_train.append(token_data)
X_train[:1]
mecab의 morphs를 활용하여 각 청원 문장을 토큰화하고 그 중 길이가 2 이상인 단어들만 남깁니다.
[['신혼', '부부', '위한', '주택', '정책', '보다', '보육', '시설', '늘려', '세요', '국민', '세금',
.... 이하 생략
from keras.preprocessing.text import Tokenizer
max_words = 35000
tokenizer = Tokenizer(num_words = max_words)
tokenizer.fit_on_texts(X_train)
X_train_vec = tokenizer.texts_to_sequences(X_train)
X_test_vec = tokenizer.texts_to_sequences(X_test)
토큰화한 단어들 중 35,000개만 사용하여 정수 인코딩을 수행합니다.
import numpy as np
from keras.utils import np_utils
y_train_data = list(train_data['category'])
y_train_vec = np_utils.to_categorical(y_train_data, num_classes=3)
y_train_vec
라벨 데이터는 keras.utils의 np_utils에 있는 to_categorical을 활용하여 Ont-Hot 인코딩을 실시합니다.
array([[0., 0., 1.],
[1., 0., 0.],
[0., 1., 0.],
...,
[0., 0., 1.],
[0., 0., 1.],
[1., 0., 0.]], dtype=float32)
import matplotlib.pyplot as plt
print("청원의 최대 길이 :" , max(len(l) for l in X_train_vec))
print("청원의 평균 길이 : ", sum(map(len, X_train_vec))/ len(X_train_vec))
plt.hist([len(s) for s in X_train_vec], bins=50)
plt.xlabel('length of Data')
plt.ylabel('number of Data')
plt.show()
모든 벡터의 길이를 같은 길이로 맞추기 위해서 청원 데이터의 평균 길이를 구합니다.
from keras_preprocessing.sequence import pad_sequences
max_len = 120
X_train_vec = pad_sequences(X_train_vec, maxlen=max_len)
X_test_vec = pad_sequences(X_test_vec, maxlen=max_len)
위에서 구한 평균 길이 값을 참고하여 pad_sequences를 통해 모든 데이터의 길이를 동일하게 120으로 맞추어줍니다.
Embedding, LSTM / Bi-LSTM 레이어로 구성된 모델을 활용하여 분류를 실행합니다.
참고 : layer 이름 E - Embedding, L - LSTM, Bi-L - Bi-LSTM
layers | optimizer | batch_size | epochs | validation_split | 형태소분석 | max_words | max_len | accuracy | |
1 | E(100) + L(128) | adam | 32 | 5 | 0.1 | mecab (morphs) | 35,000 | 120 | 0.8436 |
2 | E(100) + L(128) | adam | 32 | 3 | 0.1 | mecab (morphs) | 35,000 | 120 | 0.8256 |
3 | E(100) + L(128) | adam | 32 | 2 | 0.1 | mecab (morphs) | 35,000 | 120 | 0.857 |
4 | E(100) + L(128) | adam | 32 | 1 | 0.1 | mecab (morphs) | 35,000 | 120 | 0.8614 |
5 | E(100) + Bi-L(128) | adam | 32 | 2 | 0.1 | mecab (morphs) | 35,000 | 120 | 0.859 |
6 | E(100) + Bi-L(128) | adam | 32 | 1 | 0.1 | mecab (morphs) | 35,000 | 120 | 0.8652 |
결과는 위와 같았습니다.
이번에는 mecab의 nouns를 활용하여 명사만 추출한 후에 위에서 가장 좋았던 4번, 6번과 같은 조건으로 테스트를 해보았습니다.
layers | optimizer | batch_size | epochs | validation_split | 형태소분석 | max_words | max_len | accuracy | |
7 | E(100) + L(128) | adam | 32 | 1 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.8628 |
8 | E(100) + Bi-L(128) | adam | 32 | 1 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.862 |
결과는 위와 같았습니다.
7번, 8번에서 optimizer만 adam에서 rmsprop으로 바꾸어 학습한 후 제출해보았습니다.
layers | optimizer | batch_size | epochs | validation_split | 형태소분석 | max_words | max_len | accuracy | |
9 | E(100) + L(128) | rmsprop | 32 | 1 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.8684 |
10 | E(100) + Bi-L(128) | rmsprop | 32 | 1 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.8608 |
조금 더 좋은 결과가 나온 것을 확인했습니다.
이번엔 프로그래머스 Dev-Matching : 자연어처리 과제 도전때 가장 좋은 결과를 내었던 CNN-LSTM 모델을 사용해보았습니다.
참고 : layer 이름 E - Embedding, L - LSTM, Bi-L - Bi-LSTM, Cv1D - Conv1D, MP1D - MaxPooling1D, D - Dropout
layers | optimizer | batch_size | epochs | validation_split | 형태소분석 | max_words | max_len | accuracy | |
11 | E(100) + D(0.2) + Cv1D(256) + MP1D(4) + L(128) | adam | 32 | 5 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.8498 |
12 | E(100) + D(0.2) + Cv1D(256) + MP1D(4) + L(128) | adam | 32 | 3 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.8608 |
13 | E(100) + D(0.2) + Cv1D(256) + MP1D(4) + L(128) | adam | 32 | 1 | 0.1 | mecab (nouns) |
35,000 | 72 | 0.8672 |
앗 그런데 categorical_crossentropy를 사용했어야했는데 binary_crossentropy를 사용하여 결과를 냈습니다.
그런데 결과가 잘 나온게 신기합니다.
다시 categorical_crossentropy로 변경하여 결과를 도출해보았습니다.
layers | optimizer | batch_size | epochs | validation_split | 형태소분석 | max_words | max_len | accuracy | |
16 | E(100) + D(0.2) + Cv1D(256) + MP1D(4) + L(128) | adam | 32 | 5 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.8454 |
17 | E(100) + D(0.2) + Cv1D(256) + MP1D(4) + L(128) | adam | 32 | 3 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.8588 |
18 | E(100) + D(0.2) + Cv1D(256) + MP1D(4) + L(128) | adam | 32 | 1 | 0.1 | mecab (nouns) |
35,000 | 72 | 0.8688 |
위와 같은 결과를 얻을 수 있었습니다.
LSTM 레이어를 활용한 모델에서 128 -> 64로 변경하여 시도해보았습니다.
layers | optimizer | batch_size | epochs | validation_split | 형태소분석 | max_words | max_len | accuracy | |
14 | E(100) + L(64) |
adam | 32 | 1 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.8644 |
15 | E(100) + Bi-L(64) |
adam | 32 | 1 | 0.1 | mecab (nouns) | 35,000 | 72 | 0.8688 |
이번에는 padding의 길이를 72에서 120으로 변경해보았습니다.
layers | optimizer | batch_size | epochs | validation_split | 형태소분석 | max_words | max_len | accuracy | |
19 | E(100) + D(0.2) + Cv1D(256) + MP1D(4) + L(128) | adam | 32 | 1 | 0.1 | mecab (nouns) | 35,000 | 120 | 0.8728 |
20 | E(100) + Bi-L(64) |
adam | 32 | 1 | 0.1 | mecab (nouns) | 35,000 | 120 | 0.8648 |
21 | E(100) + L(64) |
adam | 32 | 1 | 0.1 | mecab (nouns) |
35,000 | 120 | 0.8644 |
위와 같은 결과를 얻을 수 있었습니다.
오늘은 여기까지!
앞으로 여러 방법들을 동원하여 성능을 더 높여볼 생각입니다!
부족하지만 읽어주셔서 감사합니다.
언제든지 이상한 점이나 피드백 있으면 가감없이 댓글 남겨주세요!