관리 메뉴

솜씨좋은장씨

[DACON] 소설 작가 분류 AI 경진대회 4일차! 본문

DACON/소설 작가 분류 AI 경진대회

[DACON] 소설 작가 분류 AI 경진대회 4일차!

솜씨좋은장씨 2020. 11. 2. 21:58
728x90
반응형

 

소설 작가 분류 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

소설 작가 분류 AI 경진대회 4일차!

 

오늘은 먼저 DACON 에서 제공해주는 베이스라인을 먼저 시도해보았습니다.

 

개발은 NIPA에서 지원받은 GPU 서버환경에서 진행하였습니다.

 

소설 작가 분류 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

import pandas as pd
import warnings 
warnings.filterwarnings(action='ignore')
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import Tokenizer
import re

먼저 필요한 라이브러리를 import 합니다.

#파일 불러오기
train = pd.read_csv('train.csv', encoding = 'utf-8')
test = pd.read_csv('test_x.csv', encoding = 'utf-8')
sample_submission = pd.read_csv('sample_submission.csv', encoding = 'utf-8')

데이터를 불러옵니다.

#부호를 제거해주는 함수
def alpha_num(text):
    return re.sub(r'[^A-Za-z0-9 ]', '', text)

정규식을 활용하여 영어 대문자, 소문자, 숫자만 남기고 나머지 데이터는 삭제합니다.

# 불용어 제거해주는 함수
def remove_stopwords(text):
    final_text = []
    for i in text.split():
        if i.strip().lower() not in stopwords:
            final_text.append(i.strip())
    return " ".join(final_text)

# 불용어
stopwords = [ "a", "about", "above", "after", "again", "against", "all", "am", "an", "and", "any", "are", "as", 
             "at", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", "could", 
             "did", "do", "does", "doing", "down", "during", "each", "few", "for", "from", "further", "had", "has", 
             "have", "having", "he", "he'd", "he'll", "he's", "her", "here", "here's", "hers", "herself", "him", "himself", 
             "his", "how", "how's", "i", "i'd", "i'll", "i'm", "i've", "if", "in", "into", "is", "it", "it's", "its", "itself", 
             "let's", "me", "more", "most", "my", "myself", "nor", "of", "on", "once", "only", "or", "other", "ought", "our", "ours", 
             "ourselves", "out", "over", "own", "same", "she", "she'd", "she'll", "she's", "should", "so", "some", "such", "than", "that", 
             "that's", "the", "their", "theirs", "them", "themselves", "then", "there", "there's", "these", "they", "they'd", "they'll", 
             "they're", "they've", "this", "those", "through", "to", "too", "under", "until", "up", "very", "was", "we", "we'd", "we'll", 
             "we're", "we've", "were", "what", "what's", "when", "when's", "where", "where's", "which", "while", "who", "who's", "whom", 
             "why", "why's", "with", "would", "you", "you'd", "you'll", "you're", "you've", "your", "yours", "yourself", "yourselves" ]

불용어를 정의하고 불용어 처리를 실시하는 함수를 만들어줍니다.

#전처리 적용
train['text'] = train['text'].str.lower()
test['text'] = test['text'].str.lower()
train['text'] = train['text'].apply(alpha_num).apply(remove_stopwords)
test['text'] = test['text'].apply(alpha_num).apply(remove_stopwords)

만든 함수들을 바탕으로 전처리를 진행합니다.

X_train = np.array([x for x in train['text']])
X_test = np.array([x for x in test['text']])
y_train = np.array([x for x in train['author']])

학습데이터와 테스트데이터, 각 데이터의 정답에 해당하는 값을 numpy의 array로 변환해줍니다.

 

#파라미터 설정
vocab_size = 20000
embedding_dim = 16
max_length = 500
padding_type='post'
#oov_tok = "<OOV>"

모델링 시 활용할 파라미터를 선언해줍니다.

여기서 embedding_dim에 16이 적힌 이유는 embedding layer 다음에 오는 GlobalAveragePooling1D 레이어의 입력이

16차원의 입력값을 받기 때문입니다.

#tokenizer에 fit
tokenizer = Tokenizer(num_words = vocab_size)#, oov_token=oov_tok)
tokenizer.fit_on_texts(X_train)
word_index = tokenizer.word_index
#데이터를 sequence로 변환해주고 padding 해줍니다.
train_sequences = tokenizer.texts_to_sequences(X_train)
train_padded = pad_sequences(train_sequences, padding=padding_type, maxlen=max_length)

test_sequences = tokenizer.texts_to_sequences(X_test)
test_padded = pad_sequences(test_sequences, padding=padding_type, maxlen=max_length)

데이터를 sequence로 변환하고 모든 길이를 500으로 동일하게 맞추어 줍니다.

#가벼운 NLP모델 생성
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
    tf.keras.layers.GlobalAveragePooling1D(),
    tf.keras.layers.Dense(24, activation='relu'),
    tf.keras.layers.Dense(5, activation='softmax')
])

Embedding레이어, GlobalAveragePooling1D 레이어, Dense 레이어를 활용하여 모델을 만들어줍니다.

# compile model
model.compile(loss='sparse_categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

# model summary
print(model.summary())

y_train 값을 원핫인코딩하지 않고 활용하므로 loss는 sparse_categorical_crossentropy를 활용합니다.

optimizer는 항상 가장 무난했던 adam을 활용합니다.

Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, 500, 16)           320000    
_________________________________________________________________
global_average_pooling1d_1 ( (None, 16)                0         
_________________________________________________________________
dense_2 (Dense)              (None, 24)                408       
_________________________________________________________________
dense_3 (Dense)              (None, 5)                 125       
=================================================================
Total params: 320,533
Trainable params: 320,533
Non-trainable params: 0
_________________________________________________________________
None
# fit model
num_epochs = 20
history = model.fit(train_padded, y_train, 
                    epochs=num_epochs, verbose=2, 
                    validation_split=0.2)

20번의 epoch 만큼 학습을 진행하고 validation data의 비율은 학습데이터의 20%로 설정합니다.

( 학습데이터가 100개일 경우 20개가 validation data )

Train on 43903 samples, validate on 10976 samples
Epoch 1/20
43903/43903 - 11s - loss: 1.5676 - accuracy: 0.2769 - val_loss: 1.5593 - val_accuracy: 0.2692
Epoch 2/20
43903/43903 - 8s - loss: 1.4574 - accuracy: 0.3836 - val_loss: 1.3442 - val_accuracy: 0.4677
...
Epoch 19/20
43903/43903 - 7s - loss: 0.5305 - accuracy: 0.8091 - val_loss: 0.7929 - val_accuracy: 0.7157
Epoch 20/20
43903/43903 - 7s - loss: 0.5109 - accuracy: 0.8170 - val_loss: 0.7935 - val_accuracy: 0.7158

그동안 보았던 학습양상과는 확실히 다른 모습을 보였습니다.

import matplotlib.pyplot as plt

def draw_history(history):
    fig, loss_ax = plt.subplots()
    acc_ax = loss_ax.twinx()

    loss_ax.plot(history.history['loss'], 'y', label='train loss')
    loss_ax.plot(history.history['val_loss'], 'r', label='val loss')
    loss_ax.set_xlabel('epoch')
    loss_ax.set_ylabel('loss')
    loss_ax.legend(loc='upper left')

    acc_ax.plot(history.history['accuracy'], 'b', label='train acc')
    acc_ax.plot(history.history['val_accuracy'], 'g', label='val acc')
    acc_ax.set_ylabel('accuracy')
    acc_ax.legend(loc='bottom left')

    plt.show()

 

결과 도출

# predict values
pred = model.predict_proba(test_padded)
sample_submission[['0','1','2','3','4']] = pred
sample_submission.to_csv('submission_10.csv', index = False, encoding = 'utf-8')

 

DACON 제출 결과

 

결과는 0.5730682471!

 

현재까지의 기록중에 최고의 기록이었습니다.

 

이 모델에 Dropout Layer를 추가하여 학습시키면 어떨까 한번 시도해보았습니다.

 

전처리 과정은 동일하고

# Dropout Layer 추가
model2 = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
    tf.keras.layers.GlobalAveragePooling1D(),
    tf.keras.layers.Dense(24, activation='relu'),
    tf.keras.layers.Dropout(0.1),
    tf.keras.layers.Dense(5, activation='softmax')
])
# compile model
model2.compile(loss='sparse_categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

# model summary
print(model2.summary())
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_2 (Embedding)      (None, 500, 16)           320000    
_________________________________________________________________
global_average_pooling1d_2 ( (None, 16)                0         
_________________________________________________________________
dense_4 (Dense)              (None, 24)                408       
_________________________________________________________________
dropout (Dropout)            (None, 24)                0         
_________________________________________________________________
dense_5 (Dense)              (None, 5)                 125       
=================================================================
Total params: 320,533
Trainable params: 320,533
Non-trainable params: 0
_________________________________________________________________
None
# fit model
num_epochs = 20
history2 = model2.fit(train_padded, y_train, 
                    epochs=num_epochs, 
                    validation_split=0.2)
Train on 43903 samples, validate on 10976 samples
Epoch 1/20
43903/43903 [==============================] - 7s 151us/sample - loss: 1.5697 - accuracy: 0.2712 - val_loss: 1.5618 - val_accuracy: 0.2680
Epoch 2/20
43903/43903 [==============================] - 6s 148us/sample - loss: 1.5112 - accuracy: 0.3136 - val_loss: 1.4021 - val_accuracy: 0.4055
...
Epoch 19/20
43903/43903 [==============================] - 7s 162us/sample - loss: 0.4773 - accuracy: 0.8299 - val_loss: 0.8171 - val_accuracy: 0.7103
Epoch 20/20
43903/43903 [==============================] - 7s 162us/sample - loss: 0.4605 - accuracy: 0.8341 - val_loss: 0.7706 - val_accuracy: 0.7271

 

결과 도출

# predict values
pred = model2.predict_proba(test_padded)
sample_submission[['0','1','2','3','4']] = pred
sample_submission.to_csv('submission_11.csv', index = False, encoding = 'utf-8')

 

DACON 제출 결과

 

이전 제출결과 보다 더 나은 0.5536510687 이 나왔습니다.

 

이번엔 여기서 batch size 만 32 에서 128으로 변경해보았습니다.

 

model7 = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
    tf.keras.layers.GlobalAveragePooling1D(),
    tf.keras.layers.Dense(24, activation='relu'),
    tf.keras.layers.Dropout(0.1),
    tf.keras.layers.Dense(5, activation='softmax')
])

# compile model
model7.compile(loss='sparse_categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

# model summary
print(model7.summary())
Model: "sequential_9"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_9 (Embedding)      (None, 500, 16)           320000    
_________________________________________________________________
global_average_pooling1d_9 ( (None, 16)                0         
_________________________________________________________________
dense_18 (Dense)             (None, 24)                408       
_________________________________________________________________
dropout_7 (Dropout)          (None, 24)                0         
_________________________________________________________________
dense_19 (Dense)             (None, 5)                 125       
=================================================================
Total params: 320,533
Trainable params: 320,533
Non-trainable params: 0
_________________________________________________________________
None
# fit model
num_epochs = 20
history7 = model7.fit(train_padded, y_train, 
                    epochs=num_epochs, batch_size=128,
                    validation_split=0.2)
Train on 43903 samples, validate on 10976 samples
Epoch 1/20
43903/43903 [==============================] - 4s 82us/sample - loss: 1.5723 - accuracy: 0.2749 - val_loss: 1.5672 - val_accuracy: 0.2680
Epoch 2/20
43903/43903 [==============================] - 3s 72us/sample - loss: 1.5602 - accuracy: 0.2766 - val_loss: 1.5492 - val_accuracy: 0.2680
...
Epoch 19/20
43903/43903 [==============================] - 3s 73us/sample - loss: 0.6662 - accuracy: 0.7593 - val_loss: 0.7939 - val_accuracy: 0.7077
Epoch 20/20
43903/43903 [==============================] - 3s 73us/sample - loss: 0.6419 - accuracy: 0.7672 - val_loss: 0.7781 - val_accuracy: 0.7111

 

결과 도출

# predict values
pred = model7.predict_proba(test_padded)
sample_submission[['0','1','2','3','4']] = pred
sample_submission.to_csv('submission_12.csv', index = False, encoding = 'utf-8')

 

DACON 제출 결과

batch_size를 늘려보니 기존 보다 나은 0.5275360364의 점수를 얻을 수 있었습니다.

 

오늘은 데이콘에서 제공해준 BaseLine 코드를 바탕으로 한번 시도해보았습니다.

 

모델을 바꾸고 전처리 방식을 바꾸니 점수가 많이 달라졌습니다.

 

내일부터는 또 여러 가지 시도를 해보면서 좋은 성능을 내기 위해 노력해보고자 합니다.

 

읽어주셔서 감사합니다.

Comments