AIVLE TIL ('23.04.07) 4차 미니프로젝트 5일차

cjkangme·2023년 4월 7일
0

에이블스쿨

목록 보기
51/81
post-custom-banner

Kaggle Competition

  • 2번째 Kaggle Competition이 돌아왔다.
  • 이번 주제는 지난 4일간 했던 Text Classification의 연장으로, 기존에 사용했던 데이터를 통째로 학습 데이터로 사용한다. 그리고 캐글에 주어진 데이터의 label을 예측하여 F1-macro score로 모델의 성능 경쟁을 진행하였다.
  • 지난 4일동안 데이터 전처리 및 분석을 하면서 정리한 결과는 다음과 같다.
    • 맞춤법 검사기를 이용해 텍스트 데이터를 한번 정제하면 성능이 상승한다.
    • 머신러닝 모델중에서는 SVM, Ridge Classifier, Logistic Regression의 성능이 뛰어났다.
    • (직접 모델링한) RNN 기반 딥러닝 모델은 성능이 좋지않다.
    • 한국어 데이터셋을 학습한 pretrained-model의 성능도 시도해볼 가치가 있다.
  • 결과적으로 이번 경진대회에서의 전략은 다음과 같았다.
    • 최종 학습 시에는 반드시 학습 데이터 전체에 대해 학습
      (기존에는 검증용 데이터를 쪼갠 상태로 학습한 경우가 많았다)
    • 머신러닝은 가장 성능이 좋았던 Ridge Classifier로 모델링
    • 기존 데이터에서 가장 성능이 좋았던 pretrained-model 하나를 정하여 모델링
    • 학습 결과를 분석하여 모델에서 어떤 클래스를 잘 예측하지 못했는지 확인하고, 적절히 보완
    • 조금이라도 도움이 될만한 코드들은 팀원과 적극 공유한다.

코드 리뷰 (총정리)

문법 교정

## 문법 교정 적용
for idx, row in test_df.iterrows():
    text = row['text']

    response = requests.post('http://164.125.7.61/speller/results', data={'text1': text})
    # 응답에서 필요한 내용 추출 (html 파싱)
    html = response.text.split('data = [', 1)[-1].rsplit('];', 1)[0]
    # json_text를 dictionary로 변환
    output_text = text
    try:
        json_dict = json.loads(html)
        # 교정된 문장 추출
        for info in json_dict['errInfo']:
            output_text = output_text.replace(info['orgStr'], info['candWord'].split('|')[0])
    except:
        pass
    
    test_df.loc[idx, 'cor_text'] = output_text
  • 다소 무식한 방법으로, 나라인포테크의 맞춤법 검사기에 직접 텍스트를 쏘고 응답을 받아오는 방식으로 하였다.
  • 데이터 수가 많지 않음에도 1시간 가량이 소요되는 방법이다.
  • 다만 나라인포테크의 맞춤법 검사가 정확도 높기로 유명하기 때문에, 가장 확실한 방법인 것 같다.
  • 처리 결과로는 띄어쓰기의 올바른 교정이 대부분이었고, 일부 업로드하다 -> 올리다와 같은 외래어 및 외국어나 간단한 오타 교정이 있었다.
  • 다른 조의 발표를 듣고 알게된 사실인데, 네이버가 제공하는 py-hanspell이라는 한국어 맞춤법 교정 라이브러리가 있었다. 다음부터는 이쪽을 사용하기로 하였다.

텍스트 전처리

정규식을 통한 특수문자 제거

# 정규식 라이브러리 불러오기
import re

# 숫자, 알파벳, 한글이 아닌 모든 문자를 띄어쓰기로 변환 + 코드에서 자주 사용되는 소괄호와 대괄호를 남김
def stopwords(text):
    return re.sub('[^0-9a-zA-Zㄱ-ㅣ가-힣()\[\]]', ' ' ,text)

x_train = x_train.apply(stopwords)
x_val = x_val.apply(stopwords)
  • 특수문자를 모두 지우는 정규식을 적용하였다.
  • 예외적으로 소괄호, 대괄호를 남겼는데, 코드에서 많이 사용되는 만큼 코드 구분에 유용하지 않을까라는 생각이었다.

TF-IDF matrix

# Countvectorizer 및 Kkma 토크나이저 불러오기
from sklearn.feature_extraction.text import TfidfVectorizer
from konlpy.tag import Kkma

# vectorizer 선언 (unigram)
tokenizer = Kkma()
vectorizer = TfidfVectorizer(tokenizer=tokenizer.morphs, ngram_range=(1, 1))
# 벡터화
x_train_uni = vectorizer.fit_transform(x_train)
x_val_uni = vectorizer.transform(x_val)
  • 띄어쓰기가 올바르게 처리되었다고 생각하여, konlpy가 제공하는 것중 가장 좋은 성능을 보인다고 알려진 꼬꼬마 형태소 분석기를 사용하였다.
  • 이것도 다른 조의 발표를 듣고 알게된 사실인데, 현재는 바른 형태소 분석기의 성능이 제일 우수하다고 한다. 별도로 설치하여 사용할 수 있다.

머신러닝 모델링

  • SVM과 Ridge Classifier의 하이퍼파라미터 튜닝을 하였고, 그 중 더 높은 성능을 보였던 Ridge Classifier를 최종 선정하였다.
  • 하이퍼파라미터 튜닝에는 베이지안 최적화를 이용하였다.
from skopt import BayesSearchCV
from skopt.space import Real, Integer, Categorical

# 베이지안 최적화
model_svm = SVC()

params = {
    'kernel' : Categorical(['sigmoid', 'linear']),
    'C' : Real(1, 50, prior='log-uniform', base=2),
    'gamma' : Real(0.1, 10, prior='log-uniform', base=2),
}

result_svm = BayesSearchCV(model_svm, params, scoring='f1_macro', n_jobs=-1, cv=5, verbose=1, n_iter=30)

# 학습
result_svm.fit(x_train_uni, y_train_reset)

# 결과 확인
print(result_svm.best_params_)
print(result_svm.best_score_)

# 테스트 결과 상위 순 정렬
columns =['param_C','param_gamma','param_kernel','mean_test_score','std_test_score','rank_test_score']

result_df = pd.DataFrame(result_svm.cv_results_)
result_df.sort_values('rank_test_score', ignore_index=True).loc[:10, columns]

  • 테스트 결과를 상위순으로 정렬하고, 범위를 좁혀 다시 베이지안 최적화를 진행하는 것을 반복하였다.
  • 범위가 어느정도 좁아진다면 GridSearch를 진행하는게 더 효율이 좋을 것 같다.
    베이지안 최적화는 범위에 상관없이 n_iter값에 따라 일정한 시간이 걸리기 때문이다.
F1-score 0.8691529724689302
              precision    recall  f1-score   support

           0       0.91      0.88      0.89       159
           1       0.94      0.86      0.90        73
           2       0.74      0.88      0.80        73
           3       0.93      0.89      0.91        56
           4       0.89      0.80      0.84        10

    accuracy                           0.88       371
   macro avg       0.88      0.86      0.87       371
weighted avg       0.88      0.88      0.88       371
  • 하이퍼파라미터 튜닝 결과 검증용 데이터에 대해 0.87의 높은 F1-macro score를 기록하여 전체 데이터로 학습한 뒤 제출하였다.

  • 조원분이 딥러닝을 통해 0.85678을 기록한 것이 최고기록이었고, Ridge Classifier는 근소한 차이로 두번째였다.
  • 이 결과를 구했을 때가 오전 10시경이었는데, 이게 사실상 최고점수가 될줄은 몰랐다.

Fine-Tuning

  • 미리 골라두었던
    KR-FinBert-SC
    를 가져와 PyTorch Trainer를 이용해 학습하였다.
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from transformers import Trainer, TrainingArguments

# 토크나이저 및 모델 불러오기
tokenizer = AutoTokenizer.from_pretrained("snunlp/KR-FinBert-SC")

model = AutoModelForSequenceClassification.from_pretrained("snunlp/KR-FinBert-SC", num_labels=5, ignore_mismatched_sizes=True)
                                                           
# Dataset 생성                          
def train_batch_preprocess(batch):
    return tokenizer(batch['cor_text'], padding='max_length', truncation=True, max_length=200)

tk_train_set = train_set.map(train_batch_preprocess, 
                    batched=True,
                    remove_columns=['cor_text'])
tk_test_set = test_set.map(train_batch_preprocess, 
                    batched=True,
                    remove_columns=['cor_text'])

# 학습 조건 지정
params = {
    'output_dir' : 'test_trainer',
    'num_train_epochs' : 9,
    'per_device_train_batch_size' : 8,
    'per_device_eval_batch_size' : 8,
    'gradient_accumulation_steps' : 8,
    'save_strategy' : 'epoch',
    'save_total_limit' : 1,
    'learning_rate' : 1e-5,
    'warmup_ratio' : 0.03,
    'lr_scheduler_type' : 'cosine',
    'logging_steps' : 1,
}

training_args = TrainingArguments(**params)

# 학습
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tk_train_set,
)

  • 성능은 실망스러웠고, 이후에도 다른 bert 모델을 불러오거나, 다른 머신러닝 모델 학습을 시도하였지만, 제출한 답과 비교해봤을 때 오답이 많을 것이라 생각이 들어 제출하지 못하였다.

보팅

  • 모델의 예측 분석을 위해 서로 다른 모델을 통해 예측한 정답을 비교해보았다.
# 서로 다른 데이터 확인
diff = []
for i, label in enumerate(my):
    if other[i] != label:
        diff.append((i, my[i], other[i]))

# 레이블 딕셔너리 설정
label_dict = {
    0 : '코드',
    1 : '웹',
    2 : '이론',
    3 : '시스템 운영',
    4 : '원격'
}

for i, pred, pred_cor in diff:
    print('-'*50)
    print('ID :', i)
    print('나의 예측 :', label_dict[pred])
    print('영재님 예측 :', label_dict[pred_cor])
    print('내용 :', test_df.loc[i, 'text'])

  • 머신러닝 모델은 대체로 코딩을 많이 예측하는 경향이 있었고, 딥러닝 모델은 대체로 시스템 운영, 이론을 많이 예측하는 경향이 있었다.
  • 두 예측값의 점수는 0.001도 차이나지 않는 수준이었는데 전체 900여개의 테스트 데이터중 무려 103개나서로 다르게 예측한 것으로 나와 두 모델에서 좋은 답안만 합치면 성능이 크게 오르지 않을까? 라는 생각을 하였다.
  • 그 결과 보팅이라는 아이디어가 떠올랐다.
  • 예측 2개로 보팅을 할순 없기에 예측결과가 0.8이상이 나온 머신러닝 기반 모델 3개와 딥러닝 기반 모델 3개의 예측값 중 더 많이 예측된 것을 최종 예측값으로 정하였다.
voted_df = pd.DataFrame({
    'voted': df_merged.apply(lambda x: x.value_counts().index[0], axis=1)
})
  • 원래는 홀수개로 하는 것이 맞다고 생각하였으나 6개로 보팅을 한 것이 가장 성능이 우수했다.
  • 가능하면 각 모델의 성능점수만큼 가중치를 부여하여 보팅을 하고 싶었지만, 제출 마감이 얼마 남지 않아서 시도하지 못하였다.

  • 0.85를 뚫지 못하다가 보팅을 통해 0.87의 성능을 달성했다.

결과 및 소감

결과

  • 최종 성적은 42팀 중 17등을 기록하였다. 15등까지가 A를 받을 수 있기에 다소 아쉬웠지만,
    지난 kaggle competition때는 29등을 기록한 것에 비해서는 꽤 발전했다고 할 수 있다.

새롭게 배운 것

  • 순수한 성능 경쟁이라면 트렌드와 SOAT 모델을 아는 것이 중요하다.
    • 상위권을 기록한 대부분의 조는 klue-bert라는 모델을 사용하였는데, KoBERT의 발전된 버전이지만 단순히 몰랐다는 이유로 사용하지 못했다.
  • 데이터의 특성을 파악하고 맞는 모델을 가져오는 것이 중요하다.
    • 이번에 주어진 데이터는 노이즈가 많고, 구어체의 정제되지 않은 데이터 형태였다.
    • 여기에 맞도록 네이버 뉴스의 댓글, 대댓글을 학습한 KrELECTRA 모델을 사용한 조가 있었고, 역시 상위권을 기록하였다.
    • 무턱대고 모델을 가져오는 것이 아니라 데이터 특징에 맞는 모델을 가져왔다는 점이 인상깊었고, 이를 잘 가져오기 위해서는 모델에 대한 지식이 필수라는 것을 깨달았다.
  • 항상 최신 모델이 만능은 아니다.
    • 당장 우리 조에서도 오직 Dense 레이어만을 이용한 DNN 모델이 가장 성능 점수가 높았다.
    • 또한 상위권 입상 조에는 간단한 1DCNN 모델을 직접 구현하여 상위권을 기록한 조도 있었다.
    • 최신 모델을 맹신하지 말고 문제에 맞는 모델을 탐색하는 것이 중요하다는 것을 깨달았다.
post-custom-banner

0개의 댓글