관리 메뉴

솜씨좋은장씨

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

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

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

솜씨좋은장씨 2020. 11. 15. 20:20
728x90
반응형

 

소설 작가 분류 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

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

오늘은 DACON 코드공유 페이지에서 LA오빠님께서 공유해주신 코드에서 StarifiedKFold 를 참고하여 시도해보았습니다.

 

소설 작가 분류 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

그리고 전처리 과정에서 아직 남아있던 프랑스어를 조금 더 찾아서 영어로 번역하여 전처리를 진행하였습니다.

 

sorted_keys = sorted(keys_with_length, key=lambda x : -x[1])
sorted_keys = [ key[0] for key in sorted_keys]
sorted_keys
["L'Evangile... voyez-vous, désormais nous prêcherons ensemble",
 "quelque chose de bête et d'Allemand dans la physionomie.",
 "C'est le meilleur et le plus irascible homme du monde.",
 "_C'est un ange; c'était plus qu'un ange pour moi.",
 "Oui, j'ai beaucoup à vous dire, chère amie.",
 "Oui, j'ai pris un mot pour un autre.",
 "c'est rassurant au plus haut degré.",
 "j'ai en tout quarante roubles mais",
 "c'est un pauvre sire, tout de même",
 'Elle me soupçonnera toute sa vie',
 "Oh, hier il avait tant d'esprit",
 'parce que nous avons à parler.',
 "Je n'ai rien contre l'Evangile",
 "Pardon, j'ai oublié son nom.",
 "C'est un pense-creux d'ici",
 "c'est une si pauvre tête!",
 "_un doigt d'eau de vie_.",
 'et à cette chère ingrate',
 "Il n'est pas du pays",
 'cette pauvre_ auntie',
 "C'est encore mieux",
 "Mais c'est égal.",
 'chère innocente',
 "jawing--v'yages",
 "c'est un ange",
 "d'eau de vie_",
 'Grace à Dieu',
 "c'est admis",
 "c'est très",
 "c'est égal",
 "a'terwards",
 'et puis',
 "m'clour",
 "Cap'n",
 "ma'am"]

몇 가지 더 추가한 리스트를 활용하여 각각의 문장에서 해당 프랑스어를 영어로 바꾸었습니다.

cnt = 0

text_list = list(df_questions['text'])

for i in tqdm(range(len(sorted_keys))):
    for j in range(len(text_list)):
        if sorted_keys[i] in text_list[j]:
            text_list[j] = text_list[j].replace(sorted_keys[i], translate_user_dict[sorted_keys[i]])
            cnt = cnt + 1
print("{}번 수정되었습니다.".format(cnt))
156번 수정되었습니다.
cnt = 0

text_list2 = list(df_test['text'])

for i in tqdm(range(len(sorted_keys))):
    for j in range(len(text_list2)):
        if sorted_keys[i] in text_list2[j]:
            text_list2[j] = text_list2[j].replace(sorted_keys[i], translate_user_dict[sorted_keys[i]])
            cnt = cnt + 1
print("{}번 수정되었습니다.".format(cnt))
42번 수정되었습니다.

학습데이터에서는 156번, 테스트 데이터에서는 42번 수정되었습니다.

from nltk.corpus import stopwords
stopwords_list = [ "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", "will",
             "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" ]
stopwords_list = stopwords_list + stopwords.words("english")
stopwords_list = list(set(stopwords_list))
for stopword in stopwords_list:
    print(nltk.word_tokenize(stopword))
nltk_fit_stopwords = []

for stopword in stopwords_list:
    tokens = nltk.word_tokenize(stopword)
    
    for token in tokens:
        nltk_fit_stopwords.append(token)
nltk_fit_stopwords = [token.lower() for token in nltk_fit_stopwords]

nltk toknize에 맞는 stopwords를 만들어줍니다.

import re

def alpha_num(text):
    text = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\“\”\'\"…》]', '', text)
    return re.sub(r"[^A-Za-z0-9]", ' ', text)

def get_clean_tokens(text_list, stopwords_list):
    clean_tokens = []
    
    for i in tqdm(range(len(text_list))):
        text = text_list[i].lower()
        word_tokens = nltk.word_tokenize(text)
        word_tokens = [ alpha_num(token) for token in word_tokens if token not in nltk_fit_stopwords]
        word_tokens = [word for word in word_tokens if len(word) > 1]
        
        clean_tokens.append(word_tokens)
        
    return clean_tokens
train_tokens = get_clean_tokens(train_text, stopwords_list)
text_tokens = get_clean_tokens(test_text, stopwords_list)
word_list = []

for i in tqdm(range(len(train_tokens))):
    for j in range(len(train_tokens[i])):
        word_list.append(train_tokens[i][j])
vocab_size = len(list(set(word_list)))
print(vocab_size)
41151

문장을 이루는 모든 유니크한 단어의 개수는 41,151개 입니다.

 

from keras_preprocessing.text import Tokenizer
#tokenizer에 fit
tokenizer = Tokenizer()#, oov_token=oov_tok)
tokenizer.fit_on_texts(train_text)
word_index = tokenizer.word_index
threshold = 2
total_cnt = len(tokenizer.word_index) # 단어의 수
rare_cnt = 0 # 등장 빈도수가 threshold보다 작은 단어의 개수를 카운트
total_freq = 0 # 훈련 데이터의 전체 단어 빈도수 총 합
rare_freq = 0 # 등장 빈도수가 threshold보다 작은 단어의 등장 빈도수의 총 합

# 단어와 빈도수의 쌍(pair)을 key와 value로 받는다.
for key, value in tokenizer.word_counts.items():
    total_freq = total_freq + value

    # 단어의 등장 빈도수가 threshold보다 작으면
    if(value < threshold):
        rare_cnt = rare_cnt + 1
        rare_freq = rare_freq + value

print('단어 집합(vocabulary)의 크기 :',total_cnt)
print('등장 빈도가 %s번 이하인 희귀 단어의 수: %s'%(threshold - 1, rare_cnt))
print("단어 집합에서 희귀 단어의 비율:", (rare_cnt / total_cnt)*100)
print("전체 등장 빈도에서 희귀 단어 등장 빈도 비율:", (rare_freq / total_freq)*100)
단어 집합(vocabulary)의 크기 : 42305
등장 빈도가 1번 이하인 희귀 단어의 수: 16336
단어 집합에서 희귀 단어의 비율: 38.61482094315093
전체 등장 빈도에서 희귀 단어 등장 빈도 비율: 0.6862357557561931
# 전체 단어 개수 중 빈도수 2이하인 단어 개수는 제거.
# 0번 패딩 토큰과 1번 OOV 토큰을 고려하여 +2
vocab_size = total_cnt - rare_cnt + 2
print('단어 집합의 크기 :',vocab_size)
단어 집합의 크기 : 25971

전체 단어 개수 중에서 빈도수가 2이하인 단어의 개수를 제거하면 25,971개가 됩니다.

tokenizer = Tokenizer(vocab_size, oov_token = 'OOV') 
tokenizer.fit_on_texts(train_text)
X_train = tokenizer.texts_to_sequences(train_text)
X_test = tokenizer.texts_to_sequences(test_text)
def below_threshold_len(max_len, nested_list):
    cnt = 0
    for s in nested_list:
        if len(s) <= max_len:
            cnt = cnt + 1
    print('전체 샘플 중 길이가 %s 이하인 샘플의 비율: %s'%(max_len, (cnt / len(nested_list))*100))
max_len = 390
below_threshold_len(max_len, X_train)
전체 샘플 중 길이가 390 이하인 샘플의 비율: 99.99271123744965

가장 긴 데이터의 길이는 473이지만 코드를 통해 길이가 390 이하인 데이터가 전체 데이터의 99.9927%임을 알수있습니다.

from tensorflow.keras.preprocessing.sequence import pad_sequences

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

import numpy as np
y_train = np.array([x for x in df_questions['author']])

print(vocab_size, max_len)
25971 390

모든 데이터를 390으로 맞추어줍니다.

 

from sklearn.metrics import accuracy_score, log_loss
from sklearn.model_selection import StratifiedKFold

n_fold = 5
n_class = 5
seed = 42

cv = StratifiedKFold(n_splits=n_fold, shuffle=True, random_state=seed)

DACON 코드공유에서 참고한 StartifiedKFold를 활용하여 진행하였습니다.

def get_model2():
    import tensorflow as tf

    model9 = tf.keras.Sequential([
        tf.keras.layers.Embedding(vocab_size, 100, input_length=max_len),
        tf.keras.layers.GlobalAveragePooling1D(),
        tf.keras.layers.Dense(24, activation='relu'),
        tf.keras.layers.Dense(5, activation='softmax')
    ])

    model9.compile(loss='categorical_crossentropy',
                  optimizer='adam',
                  metrics=['accuracy'])

    return model9
MODEL_SAVE_FOLDER_PATH = './model16_3/'
if not os.path.exists(MODEL_SAVE_FOLDER_PATH):
      os.mkdir(MODEL_SAVE_FOLDER_PATH)

model_path = MODEL_SAVE_FOLDER_PATH + '{epoch:02d}-{val_loss:.4f}.hdf5'

cb_checkpoint = ModelCheckpoint(filepath=model_path, monitor='val_loss',
                                verbose=1, save_best_only=True)
n_class = 5
embedding_dim = 100
max_length = max_len

p_val = np.zeros((X_train.shape[0], n_class))
p_tst = np.zeros((X_test.shape[0], n_class))
for i, (i_trn, i_val) in enumerate(cv.split(X_train, y_train), 1):
    print(f'training model for CV #{i}')
    es = EarlyStopping(monitor='val_loss', min_delta=0.001, patience=3,
                       verbose=1, mode='min', baseline=None, restore_best_weights=True)

    clf = get_model2()    
    clf.fit(X_train[i_trn], 
            to_categorical(y_train[i_trn]),
            validation_data=(X_train[i_val], to_categorical(y_train[i_val])),
            epochs=20,
            batch_size=256,
            callbacks=[es, cb_checkpoint])
    p_val[i_val, :] = clf.predict(X_train[i_val])
    p_tst += clf.predict(X_test) / n_fold

여기서 나머지 하이퍼파라미터는 그대로 고정하고 epoch만 20 -> 25 -> 30으로 변경하면서

학습 후 결과를 각각 도출하고 제출해보았습니다.

 

결과 도출

sample_file = "./sample_submission.csv"
sub = pd.read_csv(sample_file)
print(sub.shape)

sub[['0', '1', '2', '3', '4']] = p_tst
sub.to_csv("./submission_45.csv", index=False)
sub.to_csv("./submission_46.csv", index=False)
sub.to_csv("./submission_47.csv", index=False)

 

DACON 제출 결과

 

오늘은 0.3878192117로 다시 최고기록을 경신할 수 있었습니다.

 

오늘 LA오빠님을 통해서 이전 KB금융문자분석 경진대회 1등팀이 사용해서 사용하는 방법이 궁금했던

StratifiedKFold 를 활용할 수 있었습니다.

 

내일부터는 조금 더 많은 EDA와 여러가지 다른 모델링, 전처리방식을 통해 도전해보고자합니다.

 

읽어주셔서 감사합니다.

Comments