728x90
반응형

23년 8월 어느 날, KT AI 해커톤공고가 올라왔다.
개발에 목말라있던 나에겐 당연히 좋은 기회라고 생각했다.

하지만 우려되었던 부분도 있었다.
PM으로 리딩하고 있던 프로젝트가 10월까지 계획되어 있어서 업무부담이 가중되지 않을까란 우려와
Java 개발에 익숙한 내가 Python과 AI기술을 활용하여 개발을 할 수 있을까란 우려가 있었다.

그렇지만 10월까지는 야근과 주말근무도 감수하면 될꺼고... AI에도 관심이 있던 상태였다. 시원하게 저질렀다. AI 해커톤 참여하는 것으로...!

예선

AI 해커톤 지원 후, 얼마 지나지 않아 예선 과제와 일정이 주어졌다.
예선 과제는 KT고객의 고장문의와 고객단말 시험결과를 기반으로 하여 현장기사 출동/무출동을 판단하는 문제였다.
그리고 출동/무출동을 판단하여 정확도/정밀도 순으로 상위 20팀을 선발되어 본선에 참여할 수 있는 기회를 얻게된다.

그리고 기한도 함께 나왔다.
하필 내 해외여행 일정이 겹쳤다;;; 사전에 잡은 일정이라 취소도 어려웠다.

팀에 최대한 누가 되지 않도록 나는 일정을 앞당겨 먼저 예선에 몰두했다.
먼저 정확도/정밀도가 뭔지 개념을 파악하고, 기본적인 머신러닝 이진분류 알고리즘의 원리를 파악했다. (👉이진분류 알고리즘(Logistic Classification) 글 참고)

정확도/정밀도

정확도와 정밀도의 개념은 사진을 참고하면 된다.

쉽게말하면,
정확도는 전체 데이터중 모델이 예측한 True/False와 실제 값이 일치하는 경우의 비율이다.
(ex. 모델 예측 : 1=True, 2=True, 3=False, 실제 : 1=True, 2=False, 3=False, 전체 데이터 갯수 : 3개 (1, 2, 3), 일치하는 데이터 갯수 : 2개 (1, 3), 정확도 = 2 / 3 = 66.67%)

정밀도는 True/False로 예측한 데이터 중에서 실제 True/False로 일치하는 경우의 비율이다.
아래 예시는 모델이 True로 예측한 데이터 중에서 실제 True의 비율을 나타내는 예시이다.
(ex. 모델 예측 : 1=True, 2=True, 4=True, 실제 : 1=True, 2=True, 4=False, True 예측 데이터 갯수 : 3개 (1, 2, 4), 일치하는 데이터 갯수 : 2개 (1, 2), True정밀도 = 2 / 3 = 66.67%)

이번 예선에서는 정확도/정밀도 순으로 상위 20팀을 선발한다.
정확도 뿐만 아니라 정밀도도 평가에 반영되기 때문에, 모든 데이터의 기본 값을 출동으로 하여 무출동 값을 선별하는 것이 아닌, 적절한 출동/무출동 예측을 진행해야 한다.

출동/무출동 판단

평가기준이 정확도 70%, 정밀도 30%로 순위를 나열해서 본선진출팀을 선발되는 방식이기 때문에 어느정도 전략을 잘 짜야했다.
다행히 같이 참여하시는 과장님께서는 전년도에도 AI해커톤을 참여하신 경력이 있으시기에 전략짜기 한결 수월했다.

일단 과제해결 방식은 이렇다.
python코드를 활용하여 출동/무출동을 판단하는 AI모델을 구현한 후에 출동/무출동 라벨이 달려있는 학습용 데이터를 지도학습시키고,
라벨이 없는 테스트용 데이터를 출동/무출동 판단하여 데이터에 출동/무출동을 매핑한 후에 시스템에 제출하면 된다.
그러면 시스템은 정확도와 정밀도를 계산하여 자동으로 팀별 순위를 나열해주는 방식이다.

테스트용 데이터의 출동/무출동 비율을 알아보기 위해 우리는 모든 테스트용 데이터에 출동 라벨을 붙여 시스템에 제출하였다.
출동/무출동 둘중 어떤 데이터를 디폴트로 하여 어떤 데이터를 찾을지 결정하기 위함이다.
테스트 결과, 대략 출동 데이터 75%, 무출동 데이터 25% 정도로 구성되어 있는 것으로 확인되었다.
결론적으로 테스트용 데이터에 출동데이터를 디폴트로 하여 라벨링을 한 후에 어떤 데이터가 무출동인지만 찾아서 해당하는 데이터에 무출동을 라벨링하여 제출하면 된다. 단, 위에서도 언급한 것처럼 정밀도도 평가에 반영되기 때문에 적절한 무출동 값 예측도 필요하다.

먼저 과장님이 기반코드를 공유해주셨다.

import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score, precision_score, classification_report
import seaborn as sns
import numpy as np

train_base = pd.read_csv('../data/dfBase_Train.csv') # load 학습데이터
train_detail = pd.read_csv('../data/dfDetail_Train.csv')
test_base = pd.read_csv('../data/dfBase_Test.csv') # load 테스트데이터
test_detail = pd.read_csv('../data/dfDetail_Test.csv')

train_base.columns = ['고장접수번호', '현장출동구분', '신고내용'] # 컬럼명 설정
test_base.columns = ['고장접수번호', '신고내용']
train_detail.columns = ['고장접수번호', '시험종류', '시험수행일', '시험수행성공여부', '시험수행상세결과']
test_detail.columns = ['고장접수번호', '시험종류', '시험수행일', '시험수행성공여부', '시험수행상세결과']

def clacul(row):
    target_id = row.고장접수번호
    detail_df = train_detail[train_detail.고장접수번호 == target_id]
    detail_df_len = len(detail_df)
    return detail_df_len
res = train_base.apply(lambda x: calcul(x), axis=1) 
train_base['시험횟수'] = res # 문의별 단말시험횟수 값 계산 및 저장

train_detail['시험요약'] = train_detail['시험종류'] + '-' + train_detail['시험수행성공여부']
train_detail_pivot = train_detail[['고장접수번호', '시험요약', '시험수행성공여부']].pivot_table(index='고장접수번호', columns='시험요약', aggfunc='count') # 고장접수번호별 시험종류-시험수행성공여부 갯수를 기록한 피벗테이블 생성

train_detail_pivot = train_detail_pivot.fillna(0) # NaN -> 0.0

train_detail_pivot = train_detail_pivot.reset_index().droplevel(level=0, axis=1).rename(columns={'':'고장접수번호'})
train_merged = train_base.merge(train_detail_pivot, left_on='고장접수번호', right_on='고장접수번호') # pivot테이블을 기존테이블에 merge

train_merged['장애안내'] = train_merged.신고내용.map(lambda x:1 if(('장애안내' in x) | ('장애 안내' in x)) else 0) # '장애안내' 키워드 갯수 카운트 및 매핑

rf = RandomForestClassifier() # RandomForest 모델 선언
X_data = train_merged.drop(columns=['현장출동구분', '고장접수번호', '신고내용'])
y_data = train_merged['현장출동구분']

rf.fit(X_data, y_data) # 학습

위 코드를 간단히 설명하면,
고장문의마다 해당 고객의 단말시험을 진행하는데
문의별로 어떤 단말시험을 진행했는지? 결과는 어땠는지? 그리고 문의내용 중 특정 키워드가 있는지의 정보를 만들어서
RandomForest 모델에 학습시키는 코드이다.

위 코드로 모든 데이터를 출동으로 명시한 경우의 75%에서 76%까지 1%의 정확도를 올릴 수 있었다.

그리고 나는 무출동 데이터를 판별하는 핵심 키워드를 추출하기 위해 아래 코드를 추가하였다.

# 문자열 가공
def extract_word(text):
    text = text.lower()
    text = re.sub('[^a-z가-힣.%]', ' ', text)
    text = re.sub('\s+', ' ', text)
    return text
train_base['신고내용'] = train_base['신고내용'].apply(lambda x: extract_word(x))

# 형태소 분석
from konlpy.tag import Okt

okt = Okt()
words = " ".join(train_base['신고내용'].tolist())
words = okt.morphs(words, stem=True)

# 키워드 중복제거 및 count
from collections import Counter
frequent = Counter(words).most_common()

# 키워드 추출 ( 조건 : 2 * 무출동 >= 출동 )
for i in range(len(frequent)):
    check = train_base[(train_base.신고내용.str.contains(frequent[i][0]))].현장출동구분.value_counts()
    if "무출동" in check:
        ngo = check['무출동']
    else:
        ngo = 0

    if "출동" in check:
        go = check['출동']
    else:
        go = 0

    if (2 * ngo >= go):
        print(frequent[i][0])

위 코드를 간단히 설명하면,
모델에 키워드를 학습시켜 정확도를 향상시키기 위해 키워드를 추출하는 코드이다.
키워드 분석을 진행해보면,
일반적으로 특정 키워드가 신고내용에 포함되어 있는 경우 중 출동/무출동 라벨이 붙어있는 비율을 보면 대략 (출동 : 무출동 = 3 : 1) 의 비율이 형성되어 있다. 그래서 이보다 더 높은 무출동 비율의 (ex. 출동 : 무출동 = 2 : 1) 키워드를 찾아내서 학습시키면 정확도를 올릴 수 있을 것이라고 판단했고,
심사기준에 정밀도가 포함되어 있음을 고려하여 출동:무출동=2:1 비율 이상의 무출동 비율의 키워드를 추출할 수 있도록 조건을 설정하였다.

그리고 해당 코드와 핵심 키워드 몇개를 추출하여 해커톤팀에 공유하였다.
그 후에 나는 팀원들에게 뒤를 맡기고 예정된 해외여행 일정을 소화했다...

결과

팀원들이 남은기간 키워들을 잘 찾아내어 학습시켜주었기 때문에, 정확도를 77.6512까지 올릴 수 있었고
105팀중 최종 7위로 인간지능팀인 우리가 본선진출에 성공했다.

이제 본선을 대비해서 사전 개발작업을 했는데, 본선 후기는 👉다음글에서...

반응형

'개발 > AI' 카테고리의 다른 글

[AI] pandas를 활용한 데이터 핸들링 및 전처리  (2) 2024.04.10
KT AI 해커톤 회고 (2)  (1) 2023.11.02
[AI] Softmax Regression  (0) 2023.10.01
[NLP] Embedding  (0) 2023.09.23
[AI] Logistic Regression  (0) 2023.09.10

+ Recent posts