이번 7주차부터 매 주 정해진 각 주제를 가지고 이전에 학습했던 내용들을 복습할 겸, 미니 프로젝트를 진행한다.
첫 주차에 할당된 미니 프로젝트 주제는 종이 헬리콥터 회귀문제이다.
프로젝트를 진행한 순서는 아래와 같다.
1. 데이터 수집(직접 헬리콥터를 만들고, 4개의 독립 변수와 1개의 종속 변수로 데이터를 구성)
2. 모델 학습, 예측
3. Shapley Value로 변수 공헌도 확인
초 단위 체공시간(ex. 3초대, 2초대)은 비교적 잘 맞추는 편이었으나, 0.x초 단위에서 오차가 약간은 큰 편이었다.
Shapley Value로 모델 학습에 대한 변수 공헌도를 책정했을 때, 날개 길이 ➡️ 날개 폭 ➡️ 다리 길이 ➡️ 몸통 길이 순으로 공헌도가 낮아지는 것을 확인할 수 있었다.
직접 종이 헬리콥터를 만들고, 아래와 같이 데이터를 생성했다.
각 변수가 의미하는 것은 순서대로 날개 길이, 날개 폭, 몸통 길이, 다리 길이, 체공 시간을 의미하며 체공 시간의 경우 3번의 측정 후, 평균값을 구해 소수점 둘째 자리 미만의 숫자들은 버리고 사용했다.
모델의 경우 ANN과 Scikit-learn(SGDRegressor, SVR)을 사용하여 학습했다.
[ANN]
from keras.models import Model
from keras.layers import Dense, Input
from keras.optimizers import Adam
from keras.callbacks import ReduceLROnPlateau
input_layer = Input(shape = (4, ))
x = Dense(64, activation = "relu")(input_layer)
x = Dense(64, activation = 'relu')(x)
x = Dense(64, activation = 'relu')(x)
x = Dense(64, activation = 'relu')(x)
output_layer = Dense(1, activation = "sigmoid")(x)
optim = Adam(learning_rate = 0.001)
model = Model(input_layer, output_layer)
model.compile(optimizer = optim, loss = "mean_squared_error")
model.fit(X, y, epochs = 500, callbacks = [ReduceLROnPlateau(monitor = 'loss', mode = "min", factor = 0.3, patience = 5)])
데이터의 경우 MinMaxScaler
로 정규화를 진행하되, 몸통 길이가 1.0인 경우 0으로 변환되어 학습에 방해가 되는 상황을 방지하기 위해 feature_range
를 0.1 ~ 1로 지정하여 정규화를 진행했다.
이후 입력층과 은닉층, 출력층을 정의하고 훈련을 진행했다.
이렇게 생성된 모델을 기반으로 최적의 변수 조합을 찾기위해 아래와 같이 실행했다.
best_flight_times = []
best_conditions = []
for _ in range(5000):
random_array = np.random.rand(4).reshape(1, -1)
predicted_time1 = model.predict(random_array)
X = train_X_scaler.inverse_transform(random_array)
y_pred = train_y_scaler.inverse_transform(predicted_time1)[0][0]
if y_pred >= 2.9:
best_flight_times.append(y_pred)
best_conditions.append(X)
랜덤한 4개의 인수를 받아 (1, 4)의 형태로 reshape하고, 모델에 넣어 예측을 진행한 다음 입력값과 출력값을 inverse_transform으로 비정규화 후 체공 시간을 2.9초 이상으로 예측한 값들을 각 리스트에 담아 구해봤다.
print(max(best_flight_times))
print(best_conditions[np.argmax(best_flight_times)])
체공 시간 리스트 중 가장 큰 값을 구하고, 그 값의 인덱스를 가지고 독립변수 조합을 참조하여 아래와 같은 결과를 얻었다.
3.1027317 # 예상 체공 시간
[[16.46627483 4.63141677 3.22032149 8.06453777]] # 예상 최적 변수조합
모델이 반환한 결과값을 기반으로 헬리콥터를 만들어 체공 시간을 측정한 결과, 2.78초의 체공 시간을 얻을 수 있었다.
[Scikit learn (SGDRegressor, SVR)]
from sklearn.svm import SVR
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import SGDRegressor
lin = SVR()
lin2 = SGDRegressor(random_state = 22)
params = {
'epsilon' : np.logspace(-2, 2, 5),
'C' : np.logspace(-3, 0, 4),
'max_iter' : list(range(0, 3001, 500)),
'kernel' : ['linear', 'poly', 'rbf', 'sigmoid']
}
lin_grid = GridSearchCV(lin, param_grid = params, n_jobs = -1, cv = 10)
lin_grid.fit(X, y)
params2 = {
"alpha" : [0.001, 0.0001, 0.005, 0.0005, 0.01],
'epsilon' : [0.0001, 0.0005, 0.0003, 0.0007, 0.0009],
'l1_ratio' : [0.05, 0.1, 0.15, 0.2, 0.25]
}
lin2_grid = GridSearchCV(lin2, param_grid = params2, n_jobs = -1, cv = 5)
lin2_grid.fit(X, y)
이로 얻은 값들은 아래와 같다.
=========================SVR + GridSearch=========================
변수들 : [[5. 3.5 1. 5. ]]
실측값 : 1.633333333333333
예측값 : [0.55763502]
=========================SVR + GridSearch=========================
변수들 : [[5. 3.5 1. 2.5]]
실측값 : 1.6499999999999997
예측값 : [0.58991533]
=========================SGDRegressor + GridSearch=========================
변수들 : [[5. 3.5 1. 5. ]]
실측값 : 1.633333333333333
예측값 : [0.4389756]
=========================SGDRegressor + GridSearch=========================
변수들 : [[5. 3.5 1. 2.5]]
실측값 : 1.6499999999999997
예측값 : [0.38554578]
사용한 모델들의 특성은 아래와 같다.
자세한 내용은 https://ko.wikipedia.org/wiki/%EC%84%9C%ED%8F%AC%ED%8A%B8_%EB%B2%A1%ED%84%B0_%EB%A8%B8%EC%8B%A0 에서 참고하자.
자세한 내용은 https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDRegressor.html 를 참고하자.
해당 모델들에 GridSearchCV를 적용해 하이퍼 파라미터를 튜닝했고, 두 번 정도의 시도가 있었으나 오차값이 너무 큰 것을 확인하고 추가적인 튜닝은 하지 않았다.
게임이론
각 변수들이 의미하는 바는 아래와 같다.
python에서는 이 shap value를 손쉽게 사용할 수 있게 shap이라는 모듈을 제공한다.
!pip install shap
해당 프로젝트에는 아래와 같이 적용할 수 있었다.
import shap
shap.initjs()
explaner = shap.explainers.Deep(model, X)
shap_value = explaner.shap_values(X)
shap.summary_plot(shap_value, X, plot_type = 'bar', plot_size = (9, 6))
먼저 shap.initjs()
로 js기반 시각화 코드를 내 jupyter파일에 로드하고, explainer 객체를 생성하여 딥러닝 모델과 데이터를 값으로 넣어준다. 이후 shap_value를 추출하여 시각화한다. 결과는 아래와 같다.
날개 길이 ➡️ 날개 폭 ➡️ 다리 길이 ➡️ 몸통 길이 순으로 변수의 공헌도를 파악할 수 있었다.
사실 막 그렇게 잘 된 케이스는 아니라고 생각한다. 하지만 이전에는 shap value에 대한 개념도 없었을 뿐더러, 회귀 분석이라고 하면 scikit-learn으로만 해결할 생각을 했는데, 일반적인 ANN으로도 해결할 수 있다는 점을 알기도 했으며, 다른 수강생들과 함께 그나마 정형화된 프로젝트를 처음부터 끝까지 느낀 것 같아 즐거운 일주일이었다. 다음주는 CNN을 이용한 가위바위보 게임을 만든다고 하니, CNN에 대한 추가적인 테크닉이나 정보들을 미리 정리해 두어야 겠다.
프로젝트 전체 코드는 https://github.com/LeeHeonWoo1/Intel/blob/master/projects/paper%20Helicopter/paper_helicopter.ipynb 에서 확인할 수 있다.