[이어드림스쿨 1차 모의대회]119 신고 접수량 예측 후기
이어드림스쿨에서 모의 경진 대회를 진행했다. 이번에 진행하는 대회는 시계열예측으로 119 신고 접수량을 예측하는 대회다.
대회 목적 및 평가방식
대회 목적은 9~17시 신고 건수를 예측하는 것이 목적이다.
평가방식은 MAPE이다.
EDA 및 전처리
어떤 데이터를 이용해야 예측을 잘 할 수 있을지 파악하기 위해 EDA를 진행했다.
데이터 분석
일단, 대회에서 주어진 데이터다. 데이터를 보고 생긴 의문점과 추가하고 싶은 컬럼은 다음과 같다.
- 접수경로, 시군구, 접수분류, 긴급구조종류가 신고 건수에 영향을 줄까?
- 시계열 문제인데 9시~17시 데이터가 없다? 회귀 문제로 풀어야 하나?
- 휴일 데이터 추가
- 1일 평균
- 이동 평균
- 시간별 긴급주조 종류
휴일 데이터 추가
휴일에 신고건수가 많아진다는 말을 들었다.
그래서 휴일데이터를 추가하고 결과다.
휴일을 추가하고 점수가 좋아졌다.
1일 평균 추가
(먼저, 9시~17시를 예측해야 하는데 시간이 중간에 있어서 이를 +6으로 전처리 해서 15~23시를 예측하는 걸로 변경했다.)
처음에 1일(0~23시) 평균을 추가했는데 오히려 LB점수가 나빠졌다.
하지만, 0~14시 평균을 추가했는데 LB 점수가 개선되었다.
이동평균
실제값과 이동평균값의 그래프다.
하지만, CV, LB 모두 나빠졌다.
회귀문제로 풀기
시계열 문제인데 예측 할 때마다 중간에 데이터가 없다. 그래서 이 문제가 정말 시계열인가 의문이 들어서 한번 회귀문제로 풀어봤다. 간단하다. train spit을 할 때 shuffle를 추가해서 회귀 문제로 풀었다.
결과는 대만족!! 이후부터 회귀로 풀었다.
각 시간별 이동평균
이전에 진행한 이동평균은 1일 이동평균이다. 회귀문제로 변경하고, 각 시간별로 3일 이동평균을 구했다.
좋아 좋아! 결과가 더 좋아졌다.
이상치 제거
갑자기 신고가 급증하는 것은 외부데이터 없이 예측하는 것은 불가능하다고 생각해서 과감하게 이상치로 제거했다.
총 데이터 3467개에서 3417로 줄었다.
결과는 만족!
차트 분석 및 COS 추가
시간별 신고건수에 대한 차트다. 그래프가 COS 곡선으로 반복되는 되는 것을 볼 수 있다. 그래서 각 시간별로 cos을 곱해서 컬럼을 추가했다.
cos을 곱하고 추가한 컬럼의 차트다.
제출한 결과 점수가 개선되었다.
시간별 긴급구조 종류
생각해보니 대형사고인 경우 다수가 신고를 하는 경우가 있다. 그래서 시간별로 긴급구조 종류를 Count한 컬럼을 추가했다.
크~ 좋아좋아!
딥러닝
이후에 컬럼 개수가 100개가 넘어가서 머신러닝보다 딥러닝을 이용하는 것이 유리하다고 생각했다.
네트워크 수정
베이스라인 네트워크를 수정했다. 네트워크 층을 변경했고 Drop out을 추가했다.
class MLP(nn.Module):
def __init__(self, input_dim, output_dim, dropout_rate):
super().__init__()
self.drop = nn.Dropout(p = dropout_rate)
self.fc1 = nn.Linear(input_dim, 128)
self.bn1 = nn.BatchNorm1d(128)
self.fc2 = nn.Linear(128, 64)
self.bn2 = nn.BatchNorm1d(64)
self.fc3 = nn.Linear(64, output_dim)
def forward(self, inputs):
x = F.relu(self.bn1(self.fc1(inputs)))
x = self.drop(x)
x = F.relu(self.bn2(self.fc2(x)))
x = self.drop(x)
x = self.fc3(x)
return x
결과는 최악이다.
CV하고 LB를 봤을 떄는 과적합인데 네트워크가 작아서 예측을 못하는 것인지 그냥 과적합인지 확실하지 않다.
네트워크 수정2
class MLP(nn.Module):
def __init__(self, input_dim, output_dim, dropout_rate):
super().__init__()
self.drop = nn.Dropout(p = dropout_rate)
self.fc1 = nn.Linear(input_dim, 256)
self.bn1 = nn.BatchNorm1d(256)
self.fc2 = nn.Linear(256, 128)
self.bn2 = nn.BatchNorm1d(128)
self.fc3 = nn.Linear(128, 128)
self.bn3 = nn.BatchNorm1d(128)
self.fc4 = nn.Linear(128, 64)
self.bn4 = nn.BatchNorm1d(64)
self.fc5 = nn.Linear(64, output_dim)
def forward(self, inputs):
x = F.relu(self.bn1(self.fc1(inputs)))
x = self.drop(x)
x = F.relu(self.bn2(self.fc2(x)))
x = F.relu(self.bn3(self.fc3(x)))
x = F.relu(self.bn4(self.fc4(x)))
x = self.drop(x)
x = self.fc5(x)
return x
네트워크가 작아서 예측을 못한다고 가정했을 때 한번 네트워크 층수를 늘렸다. 그 결과

대만족!!
네트워크 수정3~5
이후 네트워크를 계속 수정하면서 테스트했다.
네트워크 3
네트워크 4
네트워크5
class MLP(nn.Module):
def __init__(self, input_dim, output_dim, dropout_rate):
super().__init__()
self.drop = nn.Dropout(p = dropout_rate)
self.fc1 = nn.Linear(input_dim, 256)
self.bn1 = nn.BatchNorm1d(256)
self.fc2 = nn.Linear(256, 256)
self.bn2 = nn.BatchNorm1d(256)
self.fc3 = nn.Linear(256, 128)
self.bn3 = nn.BatchNorm1d(128)
self.fc4 = nn.Linear(128, 64)
self.bn4 = nn.BatchNorm1d(64)
self.fc5 = nn.Linear(64, output_dim)
def forward(self, inputs):
x = F.relu(self.bn1(self.fc1(inputs)))
x = self.drop(x)
x = F.relu(self.bn2(self.fc2(x)))
x = F.relu(self.bn3(self.fc3(x)))
x = F.relu(self.bn4(self.fc4(x)))
x = self.drop(x)
x = self.fc5(x)
return x
대회 제출 시간 오버로 네트워크 5의 결과는 모른다.

최종결과 및 마무리
하하하... public이 23등인데 private가 40등으로 추락했다.

그래서 원인분석을 했다.
상위권에 있는 사람들은 어떤 모델을 사용했고 데이터는 어떻게 처리했는지 물어봤다.
대부분 XGB모델을 사용했고, 데이터는 시간데이터만 이용했다고 한다. 이 후 진행한 것은 Grid를 이용해서 파라미터 튜닝만했다고 한다.
주어진 데이터가 부족해서 데이터를 가공하는 것은 오히려 독이 된다는 것을 알게되었다.
데이터가 부족하면 외부에서 데이터를 추가하자.
데이터를 추가할 수 없으면, 최대한 심플한 모델, 심플한 컬럼을 사용하자.