LoL(리그 오브 레전드) 프로게이머 대전 승패 예측하기

elice에서 진행한 데이터사이언티스트 에듀챌린지에서 참여했던 프로젝트입니다. 경진대회 이후 학습을 목적으로 다시 한번 정리해 보았습니다.


목적

  • LoL 프로게이머 대전 데이터를 이용하여 모델을 학습시킨 후, 주어진 팀 구성원을 보고 어떤 팀이 이길지 판별해야 함

  • 팀은 5:5 로 이루어져 있으며, 각 팀의 플레이어 리스트, 각 플레이어의 챔피언(캐릭터) 선택이 주어질 때 정보를 가지고 승패를 예측


데이터셋

  • 데이터는 상위 0.06%~0.02%의 프로급 선수들로 이루어진 Master I 랭크에 소속 게이머 대전 정보
  • 데이터는 총 8,188 명의 프로급 게이머에 의해 치러진 48,679 개의 대전으로 이루어짐
    • 학습(Training) 데이터 : 38,679 개의 대전 데이터
    • 테스트(Test) 데이터 : 2,000 개의 공개된 input 및 output 데이터
    • 채점(Grading) 데이터 : 8,000 개의 공개된 input 및 비공개된 output 데이터
  • 주의 사항
    • Test(테스트) 및 Grading(채점) 데이터에는 학습 데이터의 정보 중 일부가 제거되어 있음
    • 학습 데이터에 존재하지 않는 게이머가 테스트 및 그레이딩 데이터에 존재할 수 있는 점
  • 데이터 출처 : Riot API (https://developer.riotgames.com/)

데이터 포맷

  • Json 데이터 형식
  • summonerId 는 익명화되어 있음
  • Items 및 championId는 아이템과 챔피언의 고유 ID로 구성됨

아이템 및 챔피언 데이터 설명

  • “matchDuration”: 1716, // 초 단위, 얼마나 게임이 오래 지속되었는가를 의미
  • “teams”: // 2개의 Dictionary
    • “firstDragon”: true, // 이 팀이 용(몬스터)를 먼저 죽였는가
    • “dragonKills”: 2, // 이 팀이 용을 몇번 죽였는가
    • “winner”: false, // 이 팀이 대전에서 승리했는가
    • “firstBaron”: false, // 이 팀이 바론(몬스터)를 먼저 죽였는가
    • “baronKills”: 0, // 이 팀이 바론을 몇번 죽였는가
    • “firstBlood”: false, // 첫 킬이 이 팀에서 나왔는가
    • “teamId”: 100 // 팀 ID
  • “participants”: // 10개의 Dictionary
    • “championId”: 412, // 챔피언 (캐릭터) ID
    • “summonerId”: 21983, // 플레이하는 게이머의 ID
    • “teamId”: 100, // 팀 ID (teams의 teamId와 매치됨)
    • “stats”: // 이번 대전에서의 통계를 의미
      • “kills”: 2, // 이 게이머가 다른 게이머를 몇번 죽였는가
      • “deaths”: 8, // 이 게이머가 몇번 죽었는가
      • “assists”: 11, // 다른 게이머를 죽이는데 몇번 도움을 주었는가
      • “goldEarned”: 7314, // 총 획득한 골드 (게임 내 화폐)
      • “totalDamageDealt”: 27629, // 몬스터 + 게이머에게 가한 총 데미지
      • “totalDamageDealtToChampions”: 9507, // 게이머에게만 가한 총 데미지
      • “totalDamageTaken”: 20419, // 받은 총 피해
      • “minionsKilled”: 37, // 죽인 미니언(몬스터) 수
      • “totalHeal”: 1014, // 총 치유량
      • “totalTimeCrowdControlDealt”: 241, // 다른 플레이어에게 Crowd Control (CC) 를 사용한 총 시간
      • “wardsPlaced”: 5 // Ward(시야를 넓혀주는 아이템) 을 맵에 장착한 횟수
      • “items”: // 아이템 정보 [3401, 2049, 1031, 3270, 0, 2043, 3364]

과정

import json
import requests
import urllib
import numpy as np
import pandas as pd
import time
from tqdm import tqdm
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import MinMaxScaler
from sklearn.feature_selection import RFE
from sklearn import decomposition, datasets

1. Json 데이터 처리

먼저 Json 형식으로 되어 있는 데이터를 분석하기 쉽게 하기 위해 데이터 프레임 형태로 저장

Json 데이터 살펴보기

  • train_data.json파일로 부터 데이터를 로드
with open("train_data.json") as fp:
    training_data = json.load(fp)
  • Json 데이터 확인
type(training_data) # list 데이터
training_data[0]

결과

{'matchDuration': 1784,
 'participants': [{'championId': 238,
   'stats': {'assists': 1,
    'deaths': 10,
    'goldEarned': 8849,
    'items': [3035, 3153, 3134, 0, 3117, 0, 3341],
    'kills': 2,
    'minionsKilled': 175,
    'totalDamageDealt': 112312,
    'totalDamageDealtToChampions': 18107,
    'totalDamageTaken': 21151,
    'totalHeal': 713,
    'totalTimeCrowdControlDealt': 142,
    'wardsPlaced': 4},
   'summonerId': 179,
   'teamId': 100},
  {'championId': 412,
   'stats': {'assists': 12,
    'deaths': 6,
    'goldEarned': 7739,
    'items': [3401, 3222, 2043, 2049, 1028, 3117, 3341],
    'kills': 2,
    'minionsKilled': 26,
    'totalDamageDealt': 20902,
    'totalDamageDealtToChampions': 9042,
    'totalDamageTaken': 21791,
    'totalHeal': 1422,
    'totalTimeCrowdControlDealt': 131,
    'wardsPlaced': 20},
   'summonerId': 178,
   'teamId': 100},
  {'championId': 236,
   'stats': {'assists': 6,
    'deaths': 5,
    'goldEarned': 11497,
    'items': [1055, 1055, 3031, 3254, 3046, 1036, 3342],
    'kills': 8,
    'minionsKilled': 185,
    'totalDamageDealt': 139159,
    'totalDamageDealtToChampions': 19974,
    'totalDamageTaken': 21835,
    'totalHeal': 2119,
    'totalTimeCrowdControlDealt': 169,
    'wardsPlaced': 5},
   'summonerId': 724,
   'teamId': 100},
  {'championId': 117,
   'stats': {'assists': 5,
    'deaths': 8,
    'goldEarned': 8124,
    'items': [3255, 3165, 3191, 1056, 1052, 1026, 3340],
    'kills': 3,
    'minionsKilled': 133,
    'totalDamageDealt': 71476,
    'totalDamageDealtToChampions': 12117,
    'totalDamageTaken': 17357,
    'totalHeal': 1083,
    'totalTimeCrowdControlDealt': 440,
    'wardsPlaced': 5},
   'summonerId': 553,
   'teamId': 100},
  {'championId': 20,
   'stats': {'assists': 7,
    'deaths': 6,
    'goldEarned': 9043,
    'items': [3725, 2049, 3102, 3024, 3111, 1029, 3364],
    'kills': 3,
    'minionsKilled': 32,
    'totalDamageDealt': 112805,
    'totalDamageDealtToChampions': 11765,
    'totalDamageTaken': 36657,
    'totalHeal': 10975,
    'totalTimeCrowdControlDealt': 564,
    'wardsPlaced': 10},
   'summonerId': 1577,
   'teamId': 100},
  {'championId': 421,
   'stats': {'assists': 12,
    'deaths': 2,
    'goldEarned': 12124,
    'items': [3717, 3111, 3075, 3022, 3082, 1011, 3361],
    'kills': 5,
    'minionsKilled': 41,
    'totalDamageDealt': 163418,
    'totalDamageDealtToChampions': 19171,
    'totalDamageTaken': 38184,
    'totalHeal': 13896,
    'totalTimeCrowdControlDealt': 912,
    'wardsPlaced': 7},
   'summonerId': 1406,
   'teamId': 200},
  {'championId': 22,
   'stats': {'assists': 10,
    'deaths': 6,
    'goldEarned': 11287,
    'items': [3140, 3031, 3046, 3006, 1055, 1055, 3340],
    'kills': 8,
    'minionsKilled': 142,
    'totalDamageDealt': 102425,
    'totalDamageDealtToChampions': 21119,
    'totalDamageTaken': 17776,
    'totalHeal': 2121,
    'totalTimeCrowdControlDealt': 1299,
    'wardsPlaced': 4},
   'summonerId': 1535,
   'teamId': 200},
  {'championId': 103,
   'stats': {'assists': 6,
    'deaths': 3,
    'goldEarned': 14350,
    'items': [1056, 3157, 3165, 3255, 1052, 3285, 3340],
    'kills': 13,
    'minionsKilled': 227,
    'totalDamageDealt': 180719,
    'totalDamageDealtToChampions': 28064,
    'totalDamageTaken': 17191,
    'totalHeal': 5123,
    'totalTimeCrowdControlDealt': 73,
    'wardsPlaced': 10},
   'summonerId': 2448,
   'teamId': 200},
  {'championId': 63,
   'stats': {'assists': 13,
    'deaths': 4,
    'goldEarned': 10539,
    'items': [2049, 3285, 3165, 3158, 3098, 1058, 3341],
    'kills': 4,
    'minionsKilled': 56,
    'totalDamageDealt': 62570,
    'totalDamageDealtToChampions': 18610,
    'totalDamageTaken': 11771,
    'totalHeal': 741,
    'totalTimeCrowdControlDealt': 28,
    'wardsPlaced': 12},
   'summonerId': 1420,
   'teamId': 200},
  {'championId': 150,
   'stats': {'assists': 6,
    'deaths': 3,
    'goldEarned': 11058,
    'items': [3153, 3143, 2003, 3265, 1033, 1028, 3361],
    'kills': 5,
    'minionsKilled': 189,
    'totalDamageDealt': 129340,
    'totalDamageDealtToChampions': 13789,
    'totalDamageTaken': 19372,
    'totalHeal': 3527,
    'totalTimeCrowdControlDealt': 583,
    'wardsPlaced': 14},
   'summonerId': 3440,
   'teamId': 200}],
 'teams': [{'baronKills': 0,
   'dragonKills': 1,
   'firstBaron': False,
   'firstBlood': False,
   'firstDragon': True,
   'teamId': 100,
   'winner': False},
  {'baronKills': 1,
   'dragonKills': 3,
   'firstBaron': True,
   'firstBlood': True,
   'firstDragon': False,
   'teamId': 200,
   'winner': True}]}
  • list 안에 dict 형태로 데이터가 저장되어 있음
training_data[0].keys()
dict_keys(['teams', 'matchDuration', 'participants'])
  • 데이터 내부를 보면 teams 데이터와 matchDuration, participants로 구성됨

  • team데이터는 팀 정보에 대한 내용으로 구성되어 있으며, 비교적 간단합니다. 다만, participants는 플레이어를 구별하는 summonerId와 플레이어가 선택한 championId, 그리고 플레이어가 챔피언을 가지고 어떤 플레이를 했는지 플레이 결과 데이터가 담겨져 있음

  • 테스트와 채점으로 주어진 데이터를 보면 championsummoner로 되어 있습니다. 따라서, 주어진 정보에는 어떤 플레이를 했는지에 대한 데이터가 없으므로 학습에는 불필요 할 것으로 판단됨

  • 결론적으로, 각각의 대전에 대한 team 정보에 대한 데이터프레임, championsummoner 정보가 담겨있는 participants 데이터프레임을 만들어 학습 데이터로 활용

Team 데이터로 데이터프레임 만들기

  • 컬럼명으로 사용할 컬럼 리스트 생성(팀을 1과 2로 구분)
    • 실제 데이터는 100과 200으로 구분되어 있음
list_team1_columns = [key+'_1' for key, value in training_data[0]['teams'][0].items()]
list_team2_columns = [key+'_2' for key, value in training_data[0]['teams'][1].items()]
  • 각 팀에 대한 데이터를 각각의 데이터 프레임으로 만들기
size = len(training_data)

df_team1 = pd.DataFrame()
start_time = time.time()
for i in range(size):
    s1 = pd.Series(list(training_data[i]['teams'][0].values()), index=list_team1_columns)
    df_team1 = df_team1.append(s1, ignore_index=True)
end_time = time.time()
print("{}s".format(end_time-start_time))

df_team2 = pd.DataFrame()
start_time = time.time()
for i in range(size):
    s2 = pd.Series(list(training_data[i]['teams'][1].values()), index=list_team2_columns)
    df_team2 = df_team2.append(s2, ignore_index=True)
end_time = time.time()
print("{}s".format(end_time-start_time))
  • 별도로 있는 matchDuration 데이터 프레임으로 만들기
df_matchDuration = pd.DataFrame({
    'matchDuration': [training_data[i]['matchDuration'] for i in range(size)]})
  • df_team1, df_team2, df_matchDuration 합치기
df_team = pd.concat([df_team1, df_team2, df_matchDuration], axis=1)

Participants 데이터로 데이터 프레임 만들기

  • 컬럼명 만들기(0부터 9까지 번호를 붙여서 구분함)
list_part_champ_columns = [ 'championId_'+ str(i) for i in range(10)]
list_part_summoner_columns = [ 'summonerId_'+ str(i) for i in range(10)]
  • championsummoner 데이터 프레임 만들기
# Champion DataFrame
df_part_champ = pd.DataFrame()
start_time = time.time()
for j in range(size):
    s3 = pd.Series([training_data[j]['participants'][i]['championId'] for i in range(10)], index=list_part_champ_columns)
    df_part_champ = df_part_champ.append(s3, ignore_index=True)
end_time = time.time()
print("{}s".format(end_time-start_time))

# Summoner DataFrame
df_part_summoner = pd.DataFrame()
start_time = time.time()
for j in range(size):
    s4 = pd.Series([training_data[j]['participants'][i]['summonerId'] for i in range(10)], index=list_part_summoner_columns)
    df_part_summoner = df_part_summoner.append(s4, ignore_index=True)
end_time = time.time()
print("{}s".format(end_time-start_time))    

  • championsummoner 데이터 프레임 합치기
df_part = pd.concat([df_part_champ, df_part_summoner], axis=1)

champion 캐릭터 정보를 데이터 프레임으로 만들기

  • 테스트 및 채점 데이터를 통해서 어떤 champion을 선택했는지를 알 수 있으므로, 주어진 championId를 통해 champion 고유 정보를 추가하여 학습이 가능함

  • riot API에서 제공하는 champion 각각의 고유 정보를 불러와 데이터 프레임으로 저장

with urllib.request.urlopen('http://ddragon.leagueoflegends.com/cdn/6.24.1/data/en_US/champion.json') as response:
    champion_info = json.loads(response.read().decode('utf8'))
  • champion데이터를 살펴보면 각 champion의 이름 안에 stats, info, key로 구성되어 있다.
    • info는 기본 능력치 정보이며, stats은 세부 캐릭터 정보임
    • key는 캐릭터 고유 ID임(주어진 championId와 같은 데이터)
  • 총 134개의 캐릭터에 대한 정보를 데이터 프레임으로 생성
# 컬럼 생성하기
list_stats_columns = list(data_champion_info['Ryze']['stats'].keys())
list_info_columns = list(data_champion_info['Ryze']['info'].keys())

# key 데이터 프레임 만들기
df_champion_key = pd.DataFrame({
    "key":[float(value['key']) for key, value in data_champion_info.items()]
})

# info 데이터 프레임 만들기
df_champion_info = pd.DataFrame()
start_time = time.time()
for j in range(len(data_champion_info)):
    s7 = pd.Series([list(value['info'].values()) for key, value in data_champion_info.items()][j], index=list_info_columns)
    df_champion_info = df_champion_info.append(s7, ignore_index=True)

end_time = time.time()
print("{}s".format(end_time-start_time))

# stats 데이터 프레임 만들기
df_champion_stats = pd.DataFrame()
start_time = time.time()
for j in range(len(data_champion_info)):
    s6 = pd.Series([list(value['stats'].values()) for key, value in data_champion_info.items()][j], index=list_stats_columns)
    df_champion_stats = df_champion_stats.append(s6, ignore_index=True)

end_time = time.time()
print("{}s".format(end_time-start_time))

# 각각의 데이터 프레임을 합쳐서 챔피언 정보를 가지고 있는 데이터 프레임 생성
df_champions = pd.concat([df_champion_key, df_champion_info, df_champion_stats], axis=1)

  • 생성된 데이터 프레임 저장하기
df_team.to_pickle("./df_lol_team.pkl")
df_part.to_pickle("./df_lol_part.pkl")
df_champions.to_pickle('./df_champions.pkl')

테스트 및 채점 데이터 데이터 프레임 만들기

  • 위의 학습 데이터와 같은 방법으로 데이터 프레임을 만들어 저장

2. 데이터 학습 및 평가(Participants 데이터 활용)

  • 데이터 불러오기
df_part = pd.read_pickle("./df_lol_part.pkl")
df_team = pd.read_pickle("./df_lol_team.pkl")
  • 학습 및 평가 데이터 만들기
 # 학습 입력 데이터
X_train = df_part

 # 학습 클래스 데이터
dict_winner = { 0: '200', 1: '100'}
y_train  = df_team['winner_1'].map(dict_winner)

 # 테스트 입력 데이터
df_test = pd.read_pickle('./df_lol_test.pkl')

 # 테스트 클래스(평가) 데이터
with open("./test.output", 'r') as f_y:
    y = f_y.read().splitlines()

Random Forest

clf_rf = RandomForestClassifier()
clf_rf.fit(X_train, y_train)
y_pred = clf_rf.predict(df_test)
print("테스트 정확도: {}".format(accuracy_score(y, y_pred)) )

결과

테스트 정확도: 0.846

GBC(Gradient Boosting Classifier)

clf_gbc = GradientBoostingClassifier()
clf_gbc.fit(X_train, y_train)
y_pred = clf_gbc.predict(df_test)
print("테스트 정확도: {}".format(accuracy_score(y, y_pred)) )

결과

테스트 정확도: 0.6005

MLPClassifier

clf_mlp = MLPClassifier(hidden_layer_sizes=300, learning_rate_init=0.001)
clf_mlp.fit(X_train, y_train)
y_pred = clf_mlp.predict(df_test)
print("테스트 정확도: {}".format(accuracy_score(y, y_pred)) )

결과

테스트 정확도: 0.525

Champion Data with Random Forest

  • champion 학습 및 테스트 데이터 만들기
X_train_champ = df_part.iloc[:, 0:10]
df_test_champ = df_test.iloc[:, 0:10]
  • 학습 및 평가
clf_rf = RandomForestClassifier()
clf_rf.fit(X_train_champ, y_train)
y_pred = clf_rf.predict(df_test_champ)
print("테스트 정확도: {}".format(accuracy_score(y, y_pred)) )

결과

테스트 정확도: 0.8345

Summoner Data with Random Forest

  • summoner 학습 및 테스트 데이터 만들기
X_train_champ = df_part.iloc[:, 10:]
df_test_champ = df_test.iloc[:, 10:]
  • 학습 및 평가
clf_rf = RandomForestClassifier()
clf_rf.fit(X_train_summon, y_train)
y_pred = clf_rf.predict(df_test_summon)
print("테스트 정확도: {}".format(accuracy_score(y, y_pred)) )

결과

테스트 정확도: 0.8475

3. 데이터 학습 및 평가(Champion 데이터 활용)

Champion 데이터 추가

  • 각 대전 별로 참여한 두 팀의 Champion에 대한 고유 데이터를 추가한다.
    • 고유 데이터는 캐릭터의 기본 및 상세 정보를 의미
  • champion정보를 추가하는 함수를 생성
from tqdm import tqdm

def add_champion_info(num_champ, df_part, df_champ):
    start_time = time.time()
    df_added_champ = pd.DataFrame()
    for j in tqdm(range(len(df_part))):
        df_row = pd.DataFrame()
        for i in range(0, num_champ):
            index_columns = [df_part.columns[i]] + list(df_part.columns[i] +"_"+df_champ.columns[1:])
            df_selected = df_champ.loc[df_champ['key']==df_part.iloc[j][i]]
            df_selected.columns = [index_columns]
            df_row = pd.concat([df_row, df_selected.reset_index(drop=True)], axis=1)
        df_added_champ = df_added_champ.append(df_row, ignore_index=True)
    end_time = time.time()
    print("{}s".format(end_time-start_time))
    return df_added_champ
  • 함수를 실행하여 champion 데이터 추가
    • championIdnominal 데이터이므로 제거
    • feature의 갯수가 240로 늘어남
df_added_champ = add_champion_info(10, df_part, df_champ)

df_test_champ_info = add_champion_info(10, df_test, df_champ)

df_added_champ = df_added_champ.drop([
    'championId_0',
    'championId_1',
    'championId_2',
    'championId_3',
    'championId_4',
    'championId_5',
    'championId_6',
    'championId_7',
    'championId_8',
    'championId_9'], axis=1)

df_test_champ_info = df_test_champ_info.drop([
    'championId_0',
    'championId_1',
    'championId_2',
    'championId_3',
    'championId_4',
    'championId_5',
    'championId_6',
    'championId_7',
    'championId_8',
    'championId_9'], axis=1)

df_prep_champ.shape,  df_test_champ_info.shape

결과

(38679, 240), (2000, 240)

Data Normalization

  • 특징 별로 값의 차이가 크므로 0과 1사이의 값으로 스케일을 조정
mm = MinMaxScaler()
added_champ_prep = mm.fit_transform(df_added_champ)
df_prep_champ = pd.DataFrame(added_champ_prep, columns=df_added_champ.columns)

df_test_prep = mm.fit_transform(df_test_champ_info)
df_test_prep = pd.DataFrame(df_test_prep, columns=df_prep_champ.columns)

학습 및 평가하기(w/ Feature Selection)

  • RFE(Recursive Feature Elimination)사용하여 30개로 줄임
rfe = RFE(clf_lr, 30)
rfe.fit(df_prep_champ, y_train)
selected_X_train = df_prep_champ.loc[:, rfe.support_]
selected_y_test = df_test_prep.loc[:, rfe.support_]
  • 학습 및 평가하기
clf_lr.fit(selected_X_train, y_train)
y_pred = clf_lr.predict(selected_y_test)
print("테스트 정확도: {}".format(accuracy_score(y, y_pred)) )

결과

테스트 정확도: 0.527
  • PCA(Principle Components Analysis) 사용하여 2개로 줄임
pca = decomposition.PCA(n_components=2)
X_train = pca.fit_transform(df_prep_champ)
y_test = pca.fit_transform(df_test_prep)
  • 학습 및 평가하기
clf_lr.fit(X_train, y_train)
y_pred = clf_lr.predict(y_test)
print("테스트 정확도: {}".format(accuracy_score(y, y_pred)) )

결과

테스트 정확도: 0.5145

결과

  • LoL 승패 예측에 있어서 가장 중요한 feature는 어떤 캐릭터를 선택하고 누가 플레이를 하였는지임
  • summonerchampion 데이터를 따로 사용한 것과 합친 것의 승패 예측 결과를 비교하였을 때 큰 차이가 없었음
  • 이 결과로 봐서는 각 팀에서 플레이하는 챔피언과 프로게이머가 비슷할 것으로 예상함
  • 학습 데이터로 주어진 정보가 실제 테스트 데이터에는 빠졌기 때문에 이를 보완하기 위해 캐릭터(챔피언)의 고유 정보를 사용하였으나 성능이 좋지 않았음
  • 이는 실제 각 캐릭터 능력치로 차이로 인해 승패가 결정되지졍되지 않는 것으로 판단됨
  • 하지만, 캐릭터 선택에 따른 조합만으로 예측하였을 때는 약 85점으로 높은 예측 결과를 나타냈으므로 캐릭터의 특성이 승패 예측에 반영되는 것을 알 수 있음
  • 정리하면 각각의 캐릭터 고유의 능력치보다는 캐릭터의 조합과 상성과 같은 특성 등이 승패의 영향을 미치는 것으로 판단됨

고찰

  • 원래 캐릭터 능력치 정보와 함께 팀에서 선택한 캐릭터와 프로게이머 조합을 함께 데이터로 사용하면 더 좋은 결과가 나올 것으로 예상하여 두 개의 데이터를 합쳐 예측을 시도하였다.
  • 하지만, 캐릭터와 프로게이머 정보는 숫자 정보가 아닌 고유의 이름이므로 이를 함께 모델로 분석하기 위해 one-hot Encoding을 통해 0과 1의 값으로 바꾸어 feature를 증가시켰다.
  • 결국, 기존의 champion의 능력치 정보의 feature 개수 240개에 participants정보를 2개 팀에서 134개의 캐릭터 중에서 5명씩 10명을 선택하는 가지 수로 각각 0과 1의 값을 넣었으므로 264개의 feature를 추가하여야 했다.
  • 하지만, 240개에 264개 feature를 합치는 데이터 프레임이 동작하지 않아 최종적으로 모델 결과를 확인해보지는 못했다.
  • 만약 ensemble 모델 처럼 캐릭터와 플레이어 예측 모델과 결과와 캐릭터의 능력치 정보만으로 예측하는 모델을 조합하는 방법에 대해 고민을 하게 되었다.
  • 이번 LoL 승패 예측 분석을 통해 얻은 교훈은 크게 두 가지이다.

1. Feature Engineering의 중요성

  • 모델 성능을 높이기 위해서는 데이터 특성에 맞추어 필요한 데이터를 뽑아내는 과정이 정말 중요하다는 것을 다시 깨달았다.
  • 지금까지 기존에 짧게 배운 것을 바탕으로 프로세스 일부로만 생각했던 Feature를 가공하는 부분에 대해 더욱 깊이 있는 공부가 필요하다.

2. Domain Knowledge의 중요성

  • 사실 리그 오브 레전드를 해보지 않은 사람으로서, 게임 데이터를 분석하기가 쉽지 않았다. 특히, 앞서 Feature에 대해 가공을 할 때 기존의 raw 데이터로부터 새로운 데이터를 뽑아내는 과정에서 데이터 분야의 지식과 전문성이 필요하다.
  • 단적인 예로, LoL에서는 캐릭터를 중복해서 선택할 수 없다. (프로게이머 대전의 경우) 이러한 간단한 사실도 데이터를 분석하는 데 큰 도움이 된다.
  • LoL에 대한 이해를 넓히기 위해 프로게이머 경기 영상을 봤는데, 실제로 어떤 챔피언을 선택하는지가 엄청나게 중요하기 때문에 엄청난 심리전과 작전을 펼치는 것을 실제로 보면서 데이터 분석 결과를 다시 한번 생각해 볼 수 있었다.

향후 과제

  • nominal data와 numerical 데이터를 포함하는 분석
  • hyper Parameter tuning
  • Feature Engineering
  • Ensemble 모델 또는 여러개의 신경망을 통해 특성별로 데이터셋을 분리하여 학습한 후 결과를 도출하는 방법