Skip to main content
튜토리얼 시리즈 | Kubeflow 기반 트래픽 예측 모델

1. 데이터 탐색 및 모델 개발

📈 로그 데이터를 분석하고, 머신러닝 기반 예측 모델을 개발합니다.

기본 정보
  • 예상 소요 시간: 60분
  • 권장 운영 체제: MacOS, Ubuntu

시나리오 소개

Kubeflow의 주요 컴포넌트 중 하나인 Notebook을 활용하여, 로드 밸런서(Load Balancer)의 로그 데이터를 기반으로 시간대별 트래픽을 예측하는 머신러닝 모델을 개발합니다. 이 과정을 통해 데이터 전처리, 시각화, 피처 엔지니어링 및 여러 ML 모델 학습과 성능 평가를 단계적으로 알아볼 수 있습니다.

주요 내용은 다음과 같습니다.

  • 로그 데이터를 시간 단위로 정리하고 시각화
  • 주기성을 반영한 피처 엔지니어링 수행
  • Scikit-learn 기반 ML 모델 탐색 및 평가
  • 학습된 모델을 저장하여 서빙 단계에서 활용

시작하기 전에

1. Kubeflow 환경 준비

Kubeflow 환경이 사전에 구성되어 있어야 합니다. 사전 준비 사항을 참고하여 CPU 기반 노드 풀 환경 및 PVC 볼륨이 생성되어 있는지 확인하세요.

2. Notebook 인스턴스 생성

  1. Kubeflow 대시보드에 접속하여고 왼쪽의 Notebook 메뉴를 선택합니다.

  2. [+ New Notebook] 버튼을 클릭한 뒤 아래와 같이 생성합니다.

    항목설정값
    Namekc-lb-pred-handson
    NotebookJupyterLab
    Imagekc-kubeflow/jupyter-scipy:v1.8.0.py311.1a 추후 변경 가능 - 문서 배포 시점에 확인 필요
    CPU / RAM0.5 / 1
    Workspace Volume(사용자 설정)
    Data Volumes(하단 표 참고)
    Affinity / Tolerations(CPU 노드풀) / None
  3. 데이터 볼륨은 사전 작업에서 생성한 PVC를 사용하며, PVC 별 Mount Path는 아래와 같이 설정합니다.

    항목설정값 1설정값 2설정값 3
    TypeKubernetes VolumeKubernetes VolumeKubernetes Volume
    Namedataset-pvcmodel-pvcartifact-pvc
    Mount path/home/jovyan/dataset/home/jovyan/models/home/jovyan/artifacts
  4. Notebook 생성 후 [Connect] 버튼을 클릭하여 JupyterLab 환경에 접속합니다. JupyterLab

시작하기

Step 1. 로그 데이터 준비

이 실습에서는 로드 밸런서 로그 데이터를 활용하여  시간에 따른 로그 발생 수를 예측하는 모델을 개발합니다. 이를 위해 원본 로그 필드 대신, 시간 단위로 집계된 로그 수 데이터를 예측 대상으로 사용합니다. NLB 로그는 30분 주기로 수집되며, 각 코드 레코드는 JSON 형식으로 한 줄로 기록됩니다.

실제 데이터 대신 합성 데이터를 사용하며, 아래 링크에서 다운로드한 후 JupyterLab의 /home/jovyan 경로에 업로드합니다.

로드 데이터 필드 구조
필드설명
project_id프로젝트 id
time로그 생성 시간
lb_id로드 밸런서의 리소스 id
listener_id리스너 id
client_port클라이언트의 IP 주소 및 포트
destination_port대상의 IP 주소 및 포트
tls_cipherOpenSSL 형식 암호 그룹
tls_protocol_versionTLS 프로토콜 버전

JupyterLab

Step 2. NLB 데이터 전처리

  1. 로그 파일을 로드하고, 시간 단위 로그 수로 집계된 데이터프레임을 생성합니다.

    로그 로딩 및 집계
    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 객체 형식
  2. 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))
  3. 로그 집계 결과를 확인합니다.

    결과 미리보기
    df.head(5)

출력 예시

datetimecount
02024-04-01 00:00:0026
12024-04-01 00:30:0029
22024-04-01 01:00:0051
32024-04-01 01:30:0032
42024-04-01 02:00:0069

Step 3. NLB 데이터 분석

로그 수 데이터에 대한 패턴을 시각화하여 간략한 트래픽 분포 특성을 분석합니다.

  1. 필요한 모듈을 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
  2. 전체 기간에 따른 로그 수 변화를 그래프로 확인합니다.

    전체 로그 수 시계열 그래프
    plt.figure(figsize=(15, 5))
    plt.title('NLB Log Count')
    plt.xlabel('Date')
    plt.ylabel('Count')
    plt.plot(df['datetime'], df['count'])
    plt.show()

    graph1

    그래프상에서 전체 트래픽이 일정한 주기성을 갖는 것을 확인할 수 있습니다.

  3. 주차(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()

    graph2

    로그 수는 요일에 따라 반복적인 트래픽 패턴을 보이는 것을 확인할 수 있습니다. 

  4. 시간(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()

    graph3

    시각화를 통해 로그 발생이 특정 요일과 시간대에 집중되는 패턴을 확인할 수 있습니다.

Step 4. 피처 엔지니어링

로그 수는 시간과 요일에 따라 주기적인 패턴을 보입니다. 23시 이후에는 0시가 시작되고, 일요일 다음에는 다시 월요일이 이어집니다. 이러한 연속적인 특성과 주기적인 특성을 모델이 학습할 수 있도록, Cyclic encoding을 적용해 피처를 생성하고 머신러닝 모델을 학습합니다.

시간(hour)과 요일(day of week)은 순환성을 갖는 데이터이므로, 각 값을 사인(sin), 코사인(cos) 함수로 변환해 주기적 패턴을 반영합니다. 시간은 30분 단위로 24시간을 나눈 48개 구간으로 처리합니다.

cyclic encoding을 적용한 피처 생성
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)
생성된 학습 데이터셋 예시
datetimex1x2x3x4y
02024-04-01 00:00:000.0000001.0000000.01.026
12024-04-01 00:30:000.1305260.9914450.01.029
22024-04-01 01:00:000.2588190.9659260.01.051
32024-04-01 01:30:000.3826830.9238800.01.032
42024-04-01 02:00:000.5000000.8660250.01.069

위와 같이 총 4개의 피처를 갖는 데이터셋이 완성되었습니다.
• x1, x2: 시간(Hour + Minute)을 사인/코사인 변환한 피처
• x3, x4: 요일(Day of Week)을 사인/코사인 변환한 피처
• y: 해당 시점의 로그 발생 수

Step 5. ML 모델 학습, 평가 및 저장

  1. 모델을 개발하기에 앞서 성능 평가를 위해 데이터를 학습용검증용으로 분리합니다. 이번 실습에서는 주차(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]
  2. 성능 분석을 보다 효율적으로 수행하기 위해, 예측 결과의 점수 출력과 시각화를 하나의 함수로 정의합니다.

    모델 성능 출력 및 예측 결과 시각화 점수
    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()
  3. 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')

    graph4

    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')

    graph5

    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')

    graph6

    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')

    graph7

    tip

    실습에 사용된 모델에 대한 설명은 다음 링크를 통해 확인할 수 있습니다.

    • Linear Regression: link
    • Gaussian Naive Bayes: link
    • Random Forest: link
    • Gradient Boosting: link
    • 이 외에도 scikit-learn 공식문서를 참고하여, 다양한 모델과 하이퍼파라미터로 실험해 볼 수 있습니다.
  4. 학습된 모델은 joblib 라이브러리를 사용하여 파일로 저장합니다.

  5. 저장된 모델 파일은 이후 진행할 모델 배포 실습에서도 사용됨으로, 마운트된 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) # 모델 불러오기