관리 메뉴

솜씨좋은장씨

DACON 금융문자분석 공모전 - 도전 1일차 본문

DACON/KB 금융문자 분석 경진대회

DACON 금융문자분석 공모전 - 도전 1일차

사용자 솜씨좋은장씨 2019. 12. 14. 20:39

1. 도전하게 된 계기

 

[대회] 14회 금융문자 분석 경진대회

 

dacon.io

idEANS 팀원들과 함께했던 COMPAS 화성시 최적 시내버스 노선 제시 공모전을 잘 마무리하고

 

새로운 목표를 설정도 할겸 이번엔 멀티캠퍼스에서 자연어 처리를 들었던 내용을 살려 금융문자 분석 경진대회에 도전해보기로 했습니다.

 

[대회] 14회 금융문자 분석 경진대회 - [Dacon Baseline] 초급자용 코드

/*! * * Twitter Bootstrap * */ /*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ /*! normalize.css v3.0.3 | MIT License | github.com/necolas/

dacon.io

대회는 금융문자를 분석하여 이 문자가 스미싱일 확률이 얼마나 되는지를 분석하는 대회였습니다.

나중에 Private Score를 도출하는 상위 20팀의 점수들은 벌써 0.99를 넘어 새로 도전하는 사람에게 부담감을 주고 있었지만 그래도 도전해보고자 합니다.

 

2. 데이터 전처리

먼저 제공된 학습 데이터를 pandas의 read_csv를 활용하여 열어보았습니다.

 

(데이터 유출을 방지하기 위해 사진은 삭제하였습니다.)

 

총 295,945개의 문자 데이터를 확인할 수 있었습니다.

 

정상인 문자와 스미싱인 문자 데이터 비율이 어떻게 되는지 확인해보았습니다.

train_data['smishing'].value_counts().plot(kind='bar')
print(train_data.groupby('smishing').size().reset_index(name='count'))

 

 

정상인 문자가 277,242개 스미싱인 문자가 18,703개로 정상인 문자가 약 15배 정도 많은 것을 알 수 있었습니다.

 

앞서 영화 평점 예측하기, 기사 분류하기 프로젝트를 진행하면서 학습하는 데이터의 비율이 불균형하면 한쪽으로 오버피팅 되는 경향이 있는 것 같아 스미싱 문자와 정상문자의 개수를 18,703개로 동일하게 맞추면 어떨까 생각해보았습니다.

 

먼저 DataFrame에서 정상인 문자만 추출하였습니다.

positive_df = train_data[train_data['smishing'] == 0]
positive_df

 

(데이터 유출을 방지하기 위해 사진은 삭제하였습니다.)

 

277,242개의 데이터 중에서 18,703개의 데이터만 선정해서 학습시킨다고 했을때 가장 학습 효과가 좋으려면 어떤 데이터를 선정하면 좋을지 생각하다가 문자 데이터가 가장 긴 것부터 18,703개를 선정하면 좋을 것 같다는 생각이 들었습니다.

 

먼저 제공된 데이터를 문자 데이터 길이가 긴 순서대로 정렬하기 위해서 len이라는 새로운 column에 각 row의 문자 데이터 길이 정보를 넣었습니다.

for i in tqdm(range(len(positive_df['smishing']))):
  positive_df['len'].iloc[i] = len(positive_df['text'].iloc[i])

 

 

비효율적으로 for loop로 각 row를 탐색하며 len을 구하도록 하였더니 27만개의 데이터다보니 41분이라는 시간이 걸렸습니다.

더 효율적으로 할 수 있는 방법은 더 공부하면서 알아봐야겠습니다.

 

(데이터 유출을 방지하기 위해 사진은 삭제하였습니다.)

 

len이라는 column에 각 문자의 길이 정보가 담겨있는 것을 볼 수 있습니다.

positive_df_desen = positive_df.sort_values(by=['len'], axis=0, ascending=False)
positive_df_desen

len의 크기가 큰 순서대로 정렬해주었습니다.

 

(데이터 유출을 방지하기 위해 사진은 삭제하였습니다.)

 

정렬하고 보니 이름만 XXX로 치환한 줄 알았지만 일부 데이터에서는 불필요한 XXX 값이 너무 많은 것을  수 있었습니다.

for i in tqdm(range(277242)):
  positive_df_desen['clear_text'].iloc[i] = positive_df_desen['text'].iloc[i].replace('XXX','')

 

 

XXX값을 제거한 값을 clear_text라는 column을 만들고 넣어주었습니다.

 

이 과정은 글을 쓰다보니 그냥 text column의 데이터를 list로 받은 뒤에 XXX를 제거하고 clear_text column에 넣었으면 더 빠른 시간 안에 하지 않았을까 생각이 들었습니다.

from tqdm import tqdm

text_datas = list(positive_df_desen['text'])

clear_text_datas = []

for i in tqdm(range(len(text_datas))):
  clear_text_datas.append(text_datas[i].replace('XXX', ''))

글쓰면서 직접해보니 실제로..... 1시간 12분을 단 1초도 걸리지 않는 시간에........ 할 수 있음을 알 수 있었습니다.

 

다음에는 더 빠른 전처리가 가능할 것으로 보입니다.(눈...물)

 

 

for i in tqdm(range(len(positive_df_desen['smishing']))):
  positive_df_desen['len2'].iloc[i] = len(positive_df_desen['clear_text'].iloc[i])

이번에도 동일하게 len2라는 새로운 column을 만들고 정제한 문자 데이터의 길이를 넣어주었습니다.

 

(데이터 유출을 방지하기 위해 사진은 삭제하였습니다.)

 

정상 문자 다음으로 스미싱 문자도 동일하게 clear_text와 len2 column을 추가해주었습니다.

 

(데이터 유출을 방지하기 위해 사진은 삭제하였습니다.)

new_df = pd.concat([nega_df_desen, posi_df_densen])
new_df

concat 메소드를 활용하여 길이가 긴 순서대로 18,703개의 정상문자데이터와 스미싱문자데이터를 하나로 합쳤습니다.

 

new_df_desen = new_df.sort_values(by=['id'], axis=0, ascending=True)
new_df_desen
new_df_desen.to_csv("model_kb_dacon01_trainset.csv", index=False, encoding='utf-8')

이렇게 되면 정상 문자 데이터가 스미싱문자데이터 아래에 붙어 딱 반반으로 붙어 이를 섞어주기 위해서 id값으로 정렬하고

추후 파일을 불러와 사용하기 위해서 csv파일로 저장하였습니다.

 

테스트 데이터는 clear_text column을 만들어 정제된 문자 데이터까지만 전처리 해주었습니다.

 

(데이터 유출을 방지하기 위해 사진은 삭제하였습니다.)

 

3. 모델링

stopwords = ['의', '가', '이', '은', '들', '는', '좀', '잘', '걍', '과', '도', '를', '으로', '자', '에', '와', '한', '하다']
from tqdm import tqdm
X_train = []
for i in tqdm(range(len(new_df_desen['clear_text']))):
  temp_X = []
  temp_X = okt.morphs(new_df_desen['clear_text'].iloc[i], stem=True) # 토큰화
  temp_X = [word for word in temp_X if not word in stopwords] # 불용어 제거
  X_train.append(temp_X)

먼저 okt 형태분석기의 morphs를 활용하여 형태소 단위로 나눈 뒤 불용어를 제거한 단어들을 제거하여 train데이터와 text데이터를 만들어 주었습니다.

 

 

from keras.preprocessing.text import Tokenizer
max_words = 35000
tokenizer = Tokenizer(num_words = max_words)
tokenizer.fit_on_texts(X_train)
X_train = tokenizer.texts_to_sequences(X_train)
X_test = tokenizer.texts_to_sequences(X_test)

Keras의 preprocessing의 Tokenizer를 활용하여 정수인코딩을 실시합니다.

 

 

print("제목의 최대 길이 :" , max(len(l) for l in X_train))
print("제목의 평균 길이 : ", sum(map(len, X_train))/ len(X_train))
plt.hist([len(s) for s in X_train], bins=50)
plt.xlabel('length of Data')
plt.ylabel('number of Data')
plt.show()

 

 

from keras.utils import np_utils
import numpy as np

y_train = []

for i in range(len(new_df_desen['smishing'])):
  if new_df_desen['smishing'].iloc[i] == 1:
    y_train.append([0, 1])
  elif new_df_desen['smishing'].iloc[i] == 0:
    y_train.append([1, 0])

y_train = np.array(y_train)

smishing인지 아닌지 라벨링 되어있는 것도 one-hot인코딩 해주었습니다.

from keras.layers import Embedding, Dense, LSTM
from keras.models import Sequential
from keras.preprocessing.sequence import pad_sequences
max_len = 568 # 전체 데이터의 길이를 568로 맞춘다

X_train = pad_sequences(X_train, maxlen=max_len)
X_test = pad_sequences(X_test, maxlen=max_len)

pad_sequences를 활용하여 전체 길이를 568로 맞추었습니다.

 

먼저 전에 FRANEE프로젝트를 진행하면서 뉴스기사 긍정/부정/중립 분류했던 모델을 변형해서 사용해보기로 했습니다.

 

[Keras]기사 제목을 가지고 긍정 / 부정 / 중립으로 분류하는 모델 만들어보기

프로젝트를 진행하면서 네이버 기사 내용을 긍정/부정으로 분류해주는 기능을 넣자고 하여 구현해보았습니다. 모델을 만드는 것은 위키독스에서 제공하는 딥러닝을 이용한 자연어처리 입문에 나와있는 코드를 활..

somjang.tistory.com

기사분류때에는 긍정/부정/중립 이 3가지로 분류하여 activation을  softmax, loss를 categorical_crossentropy를 사용했지만

이번에는 스미싱인지 아닌지 2가지로 분류하기 때문에 sigmoid와 binary_crossentropy를 사용하였습니다.

 

첫번째 제출에 사용된 코드 (Google Colab -TPU)

model = Sequential()
model.add(Embedding(max_words, 100))
model.add(LSTM(128))
model.add(Dense(2, activation='sigmoid'))

model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=5, batch_size=32, validation_split=0.1)
epoch 5 batch_size 32
optimizer adam validation_split 0.1

첫번째 제출 결과

 

 

 

두번째 제출에 사용된 코드 (Google Colab -TPU)

model = Sequential()
model.add(Embedding(max_words, 100))
model.add(LSTM(128))
model.add(Dense(2, activation='sigmoid'))
 
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=5, batch_size=32)
epoch 5 batch_size 32
optimizer rmsprop validation_split X

 

 

두번째 제출 결과

 

 

세번째 제출에 사용된 코드 (Google Colab -TPU)

model = Sequential()
model.add(Embedding(max_words, 100))
model.add(LSTM(128))
model.add(Dense(2, activation='sigmoid'))
 
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=3, batch_size=32)
epoch 3 batch_size 32
optimizer adam validation_split X

 

 

세번째 제출 결과

 

 

제출용 csv만들기

mypredict = model2.predict(X_test)
submission_ids = list(test_data['id'])
my_result = []

for i in range(len(mypredict)):
  my_result.append(mypredict[i][1])

sub_dict = {"id":submission_ids, "smishing":my_result}
submission_df = pd.DataFrame(sub_dict)
submission_df.to_csv("kb_submission.csv", index=False, encoding='utf-8')

 

도전 첫날 결과

 

 

 

첫날 28위의 결과를 얻을 수 있었습니다.

 

 

첫번째 제출 결과가 가장 좋았어서 가장 마지막 제출에서는 첫번째 제출했던 모델에서 validation_split만 변경하고 모든 데이터를 학습 시켰는데 오히려 오버피팅을 우려하여 epoch 수를 3으로 줄여서 그런지 점수가 더 나오지 않는 경향이 있었습니다.

 

앞으로 bi-LSTM 과같은 다른 Layer들을 활용해서 계속 도전해보고자 합니다.

 

읽어주셔서 감사합니다.

4 Comments
  • 프로필사진 2020.02.24 13:57 비밀댓글입니다
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.02.24 14:33 신고 안녕하세요. 방문해주셔서 감사합니다.
    먼저 아쉽게도 이 해당 데이터는 제공이 불가함을 알려드립니다.
    금융문자분석 경진대회에서 사용했던 데이터는 KB금융지주와 KISA에서 제공을 해 준 데이터로 데이터 속에는 최대한 개인정보에 대해 비식별화를 진행하였지만 일부 데이터에서 아직 지우지 못한 개인정보도 존재한다고 합니다. 그러한 이유들로 인하여 이번 대회에 데이터를 제공하고 상금을 지원한 KB측에서도 데이터의 재배포를 금지하였고 대회를 진행하면서도 데이터를 다운로드 받기위해서재배포 금지에 대한 서약과 본인인증을 진행하도록 하고 각각의 파일도 재배포시 식별할 수 있도록 조치를 했다고 합니다. 도움을 드리지 못해 죄송합니다. 비슷한 대회가 영어 데이터이긴 하지만 Kaggle에 재난 문자트윗 진실/거짓 분류가 있습니다. 영어라 더 쉬울 줄 알았는데 아직 원하는 점수 내기가 힘들더군요. 이 대회 도전하면서 여러 모델도 사용해보고 여러 전처리 방법도 테스트 해보면서 공부해보시고 데이콘에서 비슷한 대회를 또 열 계획이 있다고 하니 그때 도전해보셔도 좋을 것 같습니다! 읽어주셔서 감사합니다.
  • 프로필사진 ysc 2020.02.24 15:18 답글 길게 써주셔서 너무 감사드립니다. 코드만 가지고 최대한 공부를 해나가야겠습니다.. ㅎㅎ 감사합니다.
    아직 1일차 밖에 보지를 못해서 나머지 도전하신 글도 다 읽어보려고 합니다. 좋은 글 감사합니다.
  • 프로필사진 BlogIcon 사용자 솜씨좋은장씨 2020.02.24 19:56 신고 감사합니다. 한가지 금융문자분석 경진대회 수상자들 발표를 하는 밋업에 가서 수상자 분들의 발표를 들어보니 저처럼 LSTM같은 딥러닝 모델을 사용한 분들 보다 LightGBM같은 머신러닝 앙상블 모델을 활용 한 것이 속도면에서도 정확도 면에서도 더 좋았던 것 같습니다. 자연어처리 공부 즐겁게 하시기 바랍니다!
댓글쓰기 폼