1. 데이터 탐색 및 모델 개발
📈 로그 데이터를 분석하고, 머신러닝 기반 예측 모델을 개발합니다.
- 예상 소요 시간: 60분
- 권장 운영 체제: MacOS, Ubuntu
시나리오 소개
Kubeflow의 주요 컴포넌트 중 하나인 Notebook을 활용하여, 로드 밸런서(Load Balancer)의 로그 데이터를 기반으로 시간대별 트래픽을 예측하는 머신러닝 모델을 개발합니다. 이 과정을 통해 데이터 전처리, 시각화, 피처 엔지니어링 및 여러 ML 모델 학습과 성능 평가를 단계적으로 알아볼 수 있습니다.
주요 내용은 다음과 같습니다.
- 로그 데이터를 시간 단위로 정리하고 시각화
- 주기성을 반영한 피처 엔지니어링 수행
- Scikit-learn 기반 ML 모델 탐색 및 평가
- 학습된 모델을 저장하여 서빙 단계에서 활용
시작하기 전에
1. Kubeflow 환경 준비
Kubeflow 환경이 사전에 구성되어 있어야 합니다. 사전 준비 사항을 참고하여 CPU 기반 노드 풀 환경 및 PVC 볼륨이 생성되어 있는지 확인하세요.
2. Notebook 인스턴스 생성
-
Kubeflow 대시보드에 접속하여고 왼쪽의 Notebook 메뉴를 선택합니다.
-
[+ New Notebook] 버튼을 클릭한 뒤 아래와 같이 생성합니다.
항목 설정값 Name kc-lb-pred-handson Notebook JupyterLab Image kc-kubeflow/jupyter-scipy:v1.8.0.py311.1a 추후 변경 가능 - 문서 배포 시점에 확인 필요 CPU / RAM 0.5 / 1 Workspace Volume (사용자 설정) Data Volumes (하단 표 참고) Affinity / Tolerations (CPU 노드풀) / None -
데이터 볼륨은 사전 작업에서 생성한 PVC를 사용하며, PVC 별 Mount Path는 아래와 같이 설정합니다.
항목 설정값 1 설정값 2 설정값 3 Type Kubernetes Volume Kubernetes Volume Kubernetes Volume Name dataset-pvc model-pvc artifact-pvc Mount path /home/jovyan/dataset /home/jovyan/models /home/jovyan/artifacts -
Notebook 생성 후 [Connect] 버튼을 클릭하여 JupyterLab 환경에 접속합니다.
시작하기
Step 1. 로그 데이터 준비
이 실습에서는 로드 밸런서 로그 데이터를 활용하여 시간에 따른 로그 발생 수를 예측하는 모델을 개발합니다. 이를 위해 원본 로그 필드 대신, 시간 단위로 집계된 로그 수 데이터를 예측 대상으로 사용합니다. NLB 로그는 30분 주기로 수집되며, 각 코드 레코드는 JSON 형식으로 한 줄로 기록됩니다.
실제 데이터 대신 합성 데이터를 사용하며, 아래 링크에서 다운로드한 후 JupyterLab의 /home/jovyan
경로에 업로드합니다.
- 실습 데이터 다운로드: nlb-raw.txt
로드 데이터 필드 구조
필드 | 설명 |
---|---|
project_id | 프로젝트 id |
time | 로그 생성 시간 |
lb_id | 로드 밸런서의 리소스 id |
listener_id | 리스너 id |
client_port | 클라이언트의 IP 주소 및 포트 |
destination_port | 대상의 IP 주소 및 포트 |
tls_cipher | OpenSSL 형식 암호 그룹 |
tls_protocol_version | TLS 프로토콜 버전 |
Step 2. NLB 데이터 전처리
-
로그 파일을 로드하고, 시간 단위 로그 수로 집계된 데이터프레임을 생성합니다.
로그 로딩 및 집계import json
import pandas as pd # 데이터 전처리에 필요한 모듈을 import
file_path = '/home/jovyan/nlb-raw.txt' # NLB 파일 경로
raw_data = []
# load nlb dataset
with open(file_path) as f:
for line in f: # 각 line 에 있는 JSON형식 텍스트를 읽음
# load json data
data = json.loads(line) # JSON 형식 문자열을 python dict 타입으로 변환
# append to raw_data
raw_data.append(data)
raw_df = pd.json_normalize(raw_data) # key:value 형식의 tabular 데이터로 변환, pandas의 Dataframe 객체 형식 -
30분 단위로 로그 수를 집계하고, 시간 축 기반의 데이터프레임으로 구성합니다.
로드 집계# 시간을 30분 간격으로 변경
log_time_sr = pd.to_datetime(raw_df['time'],format='%Y/%m/%d %H:%M:%S:%f').dt.floor('30min')
# 30분 간격으로 로그의 수를 세고, 각 시간 별 로그 수를 dictionary 타입으로 저장
log_count_dict = log_time_sr.dt.floor('30min').value_counts(dropna=False).to_dict()
# 데이터셋 시간 범위를 생성 (30분 간격)
time_range = pd.date_range(start='2024-04-01', end='2024-05-01', freq='30min')
# 시간과 로그 수를 칼럼으로 갖는 데이터프레임 생성
df = pd.DataFrame({'datetime': time_range})
df['count'] = df['datetime'].apply(lambda x: log_count_dict.get(x, 0)) -
로그 집계 결과를 확인합니다.
결과 미리보기df.head(5)
출력 예시
datetime | count | |
---|---|---|
0 | 2024-04-01 00:00:00 | 26 |
1 | 2024-04-01 00:30:00 | 29 |
2 | 2024-04-01 01:00:00 | 51 |
3 | 2024-04-01 01:30:00 | 32 |
4 | 2024-04-01 02:00:00 | 69 |
Step 3. NLB 데이터 분석
로그 수 데이터에 대한 패턴을 시각화하여 간략한 트래픽 분포 특성을 분석합니다.
-
필요한 모듈을 import 하고, 데이터에 시간 관련 칼럼을 추가합니다.
시간 정보 컬럼 추가import matplotlib.pyplot as plt
df['week'] = df['datetime'].dt.isocalendar().week
df['dow'] = df['datetime'].dt.day_of_week
df['hour'] = df['datetime'].dt.hour -
전체 기간에 따른 로그 수 변화를 그래프로 확인합니다.
전체 로그 수 시계열 그래프plt.figure(figsize=(15, 5))
plt.title('NLB Log Count')
plt.xlabel('Date')
plt.ylabel('Count')
plt.plot(df['datetime'], df['count'])
plt.show()그래프상에서 전체 트래픽이 일정한 주기성을 갖는 것을 확인할 수 있습니다.
-
주차(week number)의 세부 트래픽을 비교하기 위한 그래프를 구성합니다. 4주간 로그 수를 주차 단위로 나누어 비교합니다.
주차별 로그 수 추이fig, axs = plt.subplots(2, 2, figsize=(20, 10))
fig.suptitle('Log Count by Week - 4 weeks')
for i in range(4):
axs[i//2][i%2].set_ylim(0, max(df['count']))
axs[i//2][i%2].plot(df[df['week'] == 14+i]['datetime'], df[df['week'] == 14+i]['count'])
axs[i//2][i%2].set_title(f'Week {1+i}')
plt.show()로그 수는 요일에 따라 반복적인 트래픽 패턴을 보이는 것을 확인할 수 있습니다.
-
시간(hour)과 요일(day of week)에 따른 로그 수 분포를 히트맵으로 시각화합니다.
시간-요일별 히드맵fig, axs = plt.subplots(2, 2, figsize=(15, 8))
fig.suptitle('Log Count Heatmap by Week')
for i in range(4):
week = i + 14
df_grouped = df[df['week'] == week].groupby(["hour", "dow"])["count"].sum().reset_index()
df_heatmap = df_grouped.pivot(index="dow", columns="hour", values="count")
axs[i//2][i%2].set_title(f'Week {i+1}')
axs[i//2][i%2].imshow(df_heatmap, cmap='hot', interpolation='nearest')
axs[i//2][i%2].set_xticks(range(24))
axs[i//2][i%2].set_xticklabels(range(24))
axs[i//2][i%2].set_yticks(range(7))
axs[i//2][i%2].set_yticklabels(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
axs[i//2][i%2].set_xlabel('Hour')
axs[i//2][i%2].set_ylabel('DoW')
plt.show()시각화를 통해 로그 발생이 특정 요일과 시간대에 집중되는 패턴을 확인할 수 있습니다.
Step 4. 피처 엔지니어링
로그 수는 시간과 요일에 따라 주기적인 패턴을 보입니다. 23시 이후에는 0시가 시작되고, 일요일 다음에는 다시 월요일이 이어집니다. 이러한 연속적인 특성과 주기적인 특성을 모델이 학습할 수 있도록, Cyclic encoding을 적용해 피처를 생성하고 머신러닝 모델을 학습합니다.
시간(hour)과 요일(day of week)은 순환성을 갖는 데이터이므로, 각 값을 사인(sin), 코사인(cos) 함수로 변환해 주기적 패턴을 반영합니다. 시간은 30분 단위로 24시간을 나눈 48개 구간으로 처리합니다.
import numpy as np
time_sr = df['datetime'].apply(lambda x: x.hour * 2 + x.minute // 30)
dow_sr = df['datetime'].dt.dayofweek
dataset = pd.DataFrame()
dataset['datetime'] = df['datetime'] # 이후 작업 편의를 위해 설정
# 시간 관련 feature x1, x2
dataset['x1'] = np.sin(2*np.pi*time_sr/48)
dataset['x2'] = np.cos(2*np.pi*time_sr/48)
# 요일 관련 feature x3, x4
dataset['x3'] = np.sin(2*np.pi*dow_sr/7)
dataset['x4'] = np.cos(2*np.pi*dow_sr/7)
# 예측하고자 하는 대상, label
dataset['y'] = df['count']
# 데이터셋 저장 (이후 실습에서 사용)
dataset.to_csv('/home/jovyan/dataset/nlb-sample.csv', index=False)
dataset.head(5)
생성된 학습 데이터셋 예시
datetime | x1 | x2 | x3 | x4 | y | |
---|---|---|---|---|---|---|
0 | 2024-04-01 00:00:00 | 0.000000 | 1.000000 | 0.0 | 1.0 | 26 |
1 | 2024-04-01 00:30:00 | 0.130526 | 0.991445 | 0.0 | 1.0 | 29 |
2 | 2024-04-01 01:00:00 | 0.258819 | 0.965926 | 0.0 | 1.0 | 51 |
3 | 2024-04-01 01:30:00 | 0.382683 | 0.923880 | 0.0 | 1.0 | 32 |
4 | 2024-04-01 02:00:00 | 0.500000 | 0.866025 | 0.0 | 1.0 | 69 |
위와 같이 총 4개의 피처를 갖는 데이터셋이 완성되었습니다.
• x1, x2: 시간(Hour + Minute)을 사인/코사인 변환한 피처
• x3, x4: 요일(Day of Week)을 사인/코사인 변환한 피처
• y: 해당 시점의 로그 발생 수
Step 5. ML 모델 학습, 평가 및 저장
-
모델을 개발하기에 앞서 성능 평가를 위해 데이터를 학습용과 검증용으로 분리합니다. 이번 실습에서는 주차(week number)를 기준으로 2024년 4월 21일 기준으로 16주차까지의 데이터는 학습용, 4월 22일 이후의 데이터는 검증용 데이터셋으로 사용합니다.
학습/검증용 데이터 분리train_df = dataset[dataset['datetime'].dt.isocalendar().week < 17]
test_df = dataset[dataset['datetime'].dt.isocalendar().week >= 17] -
성능 분석을 보다 효율적으로 수행하기 위해, 예측 결과의 점수 출력과 시각화를 하나의 함수로 정의합니다.
모델 성능 출력 및 예측 결과 시각화 점수from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
def print_metrics(y, y_pred):
mse = mean_squared_error(y, y_pred)
mae = mean_absolute_error(y, y_pred)
r2 = r2_score(y, y_pred)
print(f"MSE: {mse:.4f}\nMAE: {mae:.4f}\nR2: {r2:.4f}")
def draw_predictions(test_df, predictions, model_name=None):
plt.figure(figsize=(20, 5))
if model_name:
plt.title(model_name)
plt.xlabel('Date')
plt.ylabel('Count')
plt.plot(test_df['datetime'], test_df['y'], label='Actual')
plt.plot(test_df['datetime'], predictions, label='Prediction')
plt.legend()
plt.show() -
Scikit-learn의 다양한 머신러닝 모델을 적용해보고, 예측 정확도를 비교합니다. 모든 모델은 동일한 4개 입력 피처(x1, x2, x3, x4)를 기반으로 학습됩니다. 출력된 예측 그래프는 각 모델이 실제 트래픽과 얼마나 유사하게 예측했는지를 시각적으로 보여줍니다.
Linear Regression
from sklearn.linear_model import LinearRegression
params = {
# 모델 문서를 참고하여 파라미터 설정
}
model = LinearRegression(**params)
model.fit(train_df[['x1', 'x2', 'x3', 'x4']], train_df['y'])
predictions = model.predict(test_df[['x1', 'x2', 'x3', 'x4']])
print_metrics(test_df['y'], predictions)
draw_predictions(test_df=test_df,
predictions=predictions,
model_name='Linear Regression')Gaussian Naive Bayes
from sklearn.naive_bayes import GaussianNB
params = {
# 모델 문서를 참고하여 파라미터 설정
}
model = GaussianNB(**params)
model.fit(train_df[['x1', 'x2', 'x3', 'x4']], train_df['y'])
predictions = model.predict(test_df[['x1', 'x2', 'x3', 'x4']])
print_metrics(test_df['y'], predictions)
draw_predictions(test_df=test_df,
predictions=predictions,
model_name='Gaussian Naive Bayes')Random Forest Regressor
from sklearn.ensemble import RandomForestRegressor
params = {
# 모델 문서를 참고하여 파라미터 설정
}
model = RandomForestRegressor(**params)
model.fit(train_df[['x1', 'x2', 'x3', 'x4']], train_df['y'])
predictions = model.predict(test_df[['x1', 'x2', 'x3', 'x4']])
print_metrics(test_df['y'], predictions)
draw_predictions(test_df=test_df,
predictions=predictions,
model_name='Random Forest')Gradient Boosting Regressor
from sklearn.ensemble import GradientBoostingRegressor
params = {
# 모델 문서를 참고하여 파라미터 설정
}
model = GradientBoostingRegressor(**params)
model.fit(train_df[['x1', 'x2', 'x3', 'x4']], train_df['y'])
predictions = model.predict(test_df[['x1', 'x2', 'x3', 'x4']])
print_metrics(test_df['y'], predictions)
draw_predictions(test_df=test_df,
predictions=predictions,
model_name='Gradient Boosting')tip실습에 사용된 모델에 대한 설명은 다음 링크를 통해 확인할 수 있습니다.
- Linear Regression: link
- Gaussian Naive Bayes: link
- Random Forest: link
- Gradient Boosting: link
- 이 외에도 scikit-learn 공식문서를 참고하여, 다양한 모델과 하이퍼파라미터로 실험해 볼 수 있습니다.
-
학습된 모델은 joblib 라이브러리를 사용하여 파일로 저장합니다.
-
저장된 모델 파일은 이후 진행할 모델 배포 실습에서도 사용됨으로, 마운트된 model-pvc 볼륨의 '/home/jovyan/models' 경로에 저장합니다.
모델 저장import os
import joblib
model_dir = '/home/jovyan/models/lb-predictor'
os.makedirs(model_dir,exist_ok=True)
model_path = os.path.join(model_dir, 'model.joblib')
joblib.dump(model, model_path) # 모델 저장
# model = joblib.load(model_path) # 모델 불러오기