23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
내부적으로 AWS EMR에서 Spot Instance를 사용하여 배치 프로세스를 돌리는 시스템이 있습니다.
EMR은 클러스터 특성 상 1개 단일 Subnet(AZ)만을 사용하고 있으며,
메모리 자원을 많이 사용하기에 Spot Fleet에는 r type 인스턴스를 다수 설정하여 사용하고 있습니다.
Spot Instance의 요금은
특정 Zone이나 특정 Type의 수요가 몰리면 가격이 올라가고 반대로 수요가 떨어지면 가격이 내려가는 구조인데요.
동일한 성능을 냄에도 불구하고 비용 차이가 나서 저렴한 Zone과 Type을 설정하고 싶은데,
모든 Zone과 Type의 Spot 가격을 일일이 찾아가면서 조정하기에는 어려움이 있었습니다. ㅠㅠ
그러면 Spot Instance의 Zone/Type 별 가격을 학습 시켜서 해당 모델로 다음주 요금을 예측한다면,
저렴한 Subnet과 Fleet으로 미리 설정을 변경할 수 있지 않을까 생각하게 되었습니다.
그리하여 ML을 1도 모르는 저도 모델링을 할 수 있다는 AutoML 툴킷인 AutoGluon의 시계열 예측 모델로 Spot Instance 요금을 학습 시켜보기로 했습니다. ^^
AutoGluon은 AWS에서 구축한 오픈소스 AutoML 툴킷으로 머신 러닝을 모르는 비즈니스 분석가부터 머신 러닝 전문가까지
쉽게 고품질의 모델을 훈련하고 배포하는 다양한 레시피들과 모범 사례들을 제공하고 있습니다.
데이터 전처리, 피처 엔지니어링, 하이퍼파라미터 최적화, 앙상블 학습 등의 복잡한 작업을 자동으로 수행하며, 다양한 데이터 유형을 지원하는데요.
특히, 모델 성능을 최적화하기 위해 다양한 모델을 시도하고, 최적의 모델을 선택하거나 여러 모델을 결합하여 최상의 예측 성능을 제공합니다.
그럼 이제 AutoGluon을 통해 Spot Instance 요금 데이터를 학습 시키고 예측해보겠습니다.
대략적인 아키텍처 입니다. 총 5단계 과정에 대해서 자세히 설명 드리겠습니다.
아래와 같이 Spot Instance 요금 데이터를 추출하는 스크립트를 작성하였습니다.
Instance Type과 Region 지정
--> 지정하지 않으면 데이터가 너무 방대해집니다.
기간 설정
--> AWS에서는 Spot Instance 요금 데이터를 최근 90일 까지만 제공을 해줍니다.
때문에 보다 나은 모델 학습을 위해서는 미리미리 Spot Data를 확보해 두는 것이 좋습니다.
저는 약 6개월 정도의 데이터를 확보해 두었으며, 더 오랜 기간의 데이터가 있었다면 예측 결과가 더 좋지 않을까 하는 아쉬움은 있네요.
#!/bin/bash
instance_types=("r5.8xlarge" "r5.12xlarge" "r5.16xlarge" "r6g.8xlarge" "r6g.12xlarge" "r6g.16xlarge" "r6gd.8xlarge" "r6gd.12xlarge" "r6gd.16xlarge" "r6i.8xlarge" "r6i.12xlarge" "r6i.16xlarge")
for type in "${instance_types[@]}"
do
aws ec2 describe-spot-price-history \
--instance-types $type \
--product-descriptions "Linux/UNIX" \
--start-time 2025-08-01T00:00:00 \
--end-time 2025-09-30T23:59:59 \
--region ap-northeast-2 > spot_price/250930_${type}.json
done위 스크립트를 수행하면, 아래와 같이 json 형태의 Spot Data를 추출할 수 있습니다.
{
"SpotPriceHistory": [
{
"AvailabilityZone": "ap-northeast-2b",
"InstanceType": "r5.8xlarge",
"ProductDescription": "Linux/UNIX",
"SpotPrice": "0.999100",
"Timestamp": "2025-09-30T22:01:29+00:00"
},
{
"AvailabilityZone": "ap-northeast-2b",
"InstanceType": "r5.8xlarge",
"ProductDescription": "Linux/UNIX",
"SpotPrice": "0.996700",
"Timestamp": "2025-09-30T15:17:08+00:00"
},
...Timestamp는 일정하지 않고, Spot 요금이 변동 될 때에만 제공이 되는 것으로 보입니다.
(동일 Type/AZ 기준 약 7시간 빈도로 변동이 발생합니다.)
AutoGluon의 Timeseries(시계열) 예측 모델을 학습 시키기 위해서는 가지고 있는 Spot 데이터를 정해진 DataFrame에 맞추어 주어야 합니다.
Timeseries Dataframe은 일반적으로 아래와 같이 3개 필수 컬럼 구성이 필요합니다.
컬럼 | 의미 |
|---|---|
item_id | “무엇에 대한 시계열인가” ㅡ 각 개체의 ID (AZ, Instance Type) |
timestamp | “언제의 값인가” — 시간 축 (datetime 형식) |
target | “예측 대상 값” — 실제로 모델이 예측해야 하는 숫자 (Price) |
이러한 DataFrame 컬럼 기준에 맞추기 위해, 저는 확보한 Spot Data에서 아래와 같이 전처리 및 보간 작업을 수행하였습니다.
item_id, target
1) AZ와 Instance Type을 조합하여 item_id 생성 (ex. apne2a_r5.12xlarge)
2) Core당 가격 비교를 위해 target을 Core 가격으로 지정 (SpotPrice -> CorePrice)
timestamp
1) 일정한 간격의 데이터로 만들기 위해 1시간 단위로 치환 (분/초 날리기)
2) 1시간 간격의 Timeseries 데이터로 만들기 위해 사이 데이터 보간 (이전 시간의 가격으로)
이제 학습을 위한 Spot Instance 데이터 확보가 완료되었으니, AutoGluon을 이용하여 학습을 진행하겠습니다.
초반에 설명 드렸듯 AutoGluon은 AutoML 툴킷으로, ML 학습에 필요한 복잡한 작업을 자동으로 수행해줍니다.
따라서 설정도 매우 간단합니다.
아래와 같이 모델 학습 수준과 Target 정도만 지정한 뒤, predictor.fit()을 통해 예측 훈련을 수행해주면 됩니다.
(Target 외에 나머지 지표는 사실 설정하지 않아도 default로 학습이 됩니다.)
time_limit = 60 * 60 # 1시간 훈련
presets='best_quality'
prediction_length = 336 # 2주일 예측
target_column = "CorePrice"
eval_metric = "sMAPE"
predictor = TimeSeriesPredictor(
prediction_length=prediction_length,
target=target_column,
eval_metric=eval_metric,
)
# 학습
predictor.fit(train_data, time_limit=time_limit, presets=presets, hyperparameters=hyperparameters)훈련이 완료되면 아래와 같이 predictor.leaderboard()로 모델 별 순위표를 확인할 수 있습니다.
(보통 대부분의 훈련에서 WeightedEnsemble이 1위를 차지합니다.)
# 훈련 순위표
predictor.leaderboard()이제 훈련이 잘 되었는지 실제 데이터와 예측 데이터를 비교해 보겠습니다.
저는 train_data를 가지고 있는 test_data의 마지막 2주일을 제외해서 훈련시켰고,predictor.predict() 를 통해 train_data 이후의 예측을 수행합니다.
# prediction_length 기간 만큼의 값을 예측
predictions = predictor.predict(train_data)
predictions그리고 prediction.evaluate()로 예측 모델의 지표 별 성능 점수를 얻을 수 있습니다.
# 지표 별 성능 점수
metrics = ["MAE", "SMAPE"]
for metric in metrics:
score = predictor.evaluate(test_data, model='WeightedEnsemble', metrics=[metric])
print(metric, ":", -score[metric])*MAE(평균 절대 오차) : CorePrice 예측 값과 test_data(실제 값)의 평균 오차가 $0.00037 수준임
*SMAPE(대칭 평균 절대 비율 오차) : CorePrice 예측이 test_data(실제 값)의 가격 변동을 평균 1.219% 오차로 맞힘
이제 pyplot으로 Instance Type 별로 실제 데이터와 예측 값을 그려보겠습니다.
AZ와 Instance Type이 혼합되어 있던 item_id를 다시 분리하고, 각 AZ를 1개 Type Plot 안에 넣어 주겠습니다.
import matplotlib.pyplot as plt
# 기본 전처리
pred_df = predictions.reset_index()
true_df = test_data.reset_index()
for df in [pred_df, true_df]:
df[['AZ', 'InstanceType']] = df['item_id'].str.split('_', expand=True)
# InstanceType 기준으로 반복
for instance in true_df['InstanceType'].unique():
plt.figure(figsize=(12, 5))
# 예측/실제에서 해당 인스턴스만 필터링
true_inst = true_df[true_df['InstanceType'] == instance]
pred_inst = pred_df[pred_df['InstanceType'] == instance]
# AZ 별로 Plot
for az in sorted(true_inst['AZ'].unique()):
true_az = true_inst[true_inst['AZ'] == az].sort_values('timestamp')
pred_az = pred_inst[pred_inst['AZ'] == az].sort_values('timestamp')
plt.plot(true_az['timestamp'], true_az['CorePrice'], label=f'{az} Actual')
plt.plot(pred_az['timestamp'], pred_az['mean'], linestyle='--', label=f'{az} Predicted')
plt.fill_between(pred_az['timestamp'], pred_az['0.1'], pred_az['0.9'], alpha=0.2, label=f'{az} 10-90% CI')
# Plot 꾸미기
plt.title(f'Instance Type: {instance}')
plt.xlabel('Timestamp')
plt.ylabel('Core Price (USD)')
plt.legend(
loc='center left',
bbox_to_anchor=(1.02, 0.5),
frameon=True,
title='Zone / Type'
)
plt.grid(True)
plt.tight_layout()
plt.show()결과를 보면 예측을 아주 잘 했다 라고 보기엔 다소 모호한 부분이 있긴 합니다..
아무래도 Spot Instance 요금의 변화가 주기적인 패턴이 존재하는 양상은 아니다보니, 마지막 데이터의 전반적 추세에 의존하는 선형 형태로 예측을 하는 것으로 보입니다.
마지막으로 예측된 기간의 평균 값으로, 가장 가성비가 좋은 AZ와 Instance Type을 출력해 주도록 해보겠습니다.
# 기본 전처리
predict_df = predictions.reset_index()
# 2주일 동안의 평균 요금 계산
avg_df = predict_df.groupby('item_id', as_index=False)['mean'].mean()
# AZ 및 InstanceType 컬럼 추가
avg_df['AZ'] = avg_df['item_id'].str.extract(r'^(.*?)_')
avg_df['InstanceType'] = avg_df['item_id'].str.extract(r'_(.*)$')
# AZ별 평균 요금
az_mean = avg_df.groupby('AZ')['mean'].mean().sort_values().reset_index()
az_mean.columns = ['AZ', 'AvgPrice']
# InstanceType별 평균 요금
type_mean = avg_df.groupby('InstanceType')['mean'].mean().sort_values().reset_index()
type_mean.columns = ['InstanceType', 'AvgPrice']
print("Spot 요금 추천 AZ:")
print(az_mean)
print("\nSpot 요금 추천 Type:")
print(type_mean)위 출력 정보를 통해 미리 EMR Configuration에 다음주에 저렴할 AZ의 Subnet과 저렴한 Instance Type으로 설정한다면, 가성비 있는 Spot을 골라 쓸 수 있을 것으로 보입니다. ^^
(물론 Fleet은 넉넉하게 설정해두어서, 특정 Type이 모자람으로 인해 장애 상황이 생기지 않도록 해야겠죠?)
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.