Validation with Visualization (3)

  • 데이터 불균형으로 인해 train, validation, test dataset의 결과가 따로 놉니다.
  • 층화추출로 데이터를 최대한 균등하게 분할합시다.
  • hyperparameter도 라이브러리를 사용해 편리하게 피팅합시다.

6. Validation set 고정

  • 새로운 노트북을 만들어 데이터를 불러옵니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    %matplotlib inline

    import numpy as np
    import pandas as pd
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import seaborn as sns

    sns.set_context("talk")
    sns.set_style("white")
    sns.set_palette("Pastel1")

    # 데이터 불러오기
    df_vcv = pd.read_csv("df_vcv.csv")
    df_vcv.head()

6.1. 랜덤추출

sklearn.model_selection.train_test_split

  • 이번엔 index를 사용하지 않고 데이터를 분할합니다.

  • 편하게 sklearn의 train_test_split()을 사용합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from sklearn.model_selection import train_test_split

    # data split
    df_trainval, df_test = train_test_split(df_vcv, test_size=0.2, random_state=42)
    df_train, df_val = train_test_split(df_trainval, train_size=0.75, random_state=42)

    # index
    idx_train = df_train.index
    idx_val = df_val.index
    idx_test = df_test.index
  • 데이터가 잘 분할되었는지 확인합니다.

  • 지난번 글에 있는 코드를 고쳐서 train, validation, test set의 분포를 그립니다.

  • 코드가 길어서 접어두었습니다. 클릭하면 펴집니다.

    코드 보기/접기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    def plot_distribution(df, idx_train, idx_val, idx_test, filename):
    fig, axes = plt.subplots(ncols=3, nrows=2, figsize=(12, 8))
    axs = axes.ravel()

    for ax, col in zip(axs, df.columns):
    if col != "target":
    if df[col].dtype == "O": # categorical
    df_train = df.loc[idx_train, col].value_counts().reset_index()
    df_val = df.loc[idx_val, col].value_counts().reset_index()
    df_test = df.loc[idx_test, col].value_counts().reset_index()

    width_cat = 0.8/3
    ax.bar(df_train.index-width_cat, df_train[col]/len(idx_train), width=width_cat, ec="g", fc="#AAFFAADD")
    ax.bar(df_val.index, df_val[col]/len(idx_val), width=width_cat, ec="c", fc="#AAFFFFDD")
    ax.bar(df_test.index+width_cat, df_test[col]/len(idx_test), width=width_cat, ec="m", fc="#FFAAFFDD")
    ax.set_xticks(list(range(df_train.shape[0])))
    ax.set_xticklabels(df_train["index"].values)

    else: # numerical
    bins = np.linspace(df[col].min(), df[col].max(), 10)
    bins_center = (bins[:-1] + bins[1:])/2
    bins_delta = bins[1] - bins[0]
    counts_train, bins_train = np.histogram(df.loc[idx_train, col], bins=bins)
    counts_val, bins_val = np.histogram(df.loc[idx_val, col], bins=bins)
    counts_test, bins_test = np.histogram(df.loc[idx_test, col], bins=bins)

    width_num = bins_delta/3
    ax.bar(bins_center-width_num, counts_train/len(idx_train), width=width_num, ec="g", fc="#AAFFAADD")
    ax.bar(bins_center, counts_val/len(idx_val), width=width_num, ec="g", fc="#AAFFFFDD")
    ax.bar(bins_center+width_num, counts_test/len(idx_test), width=width_num, ec="m", fc="#FFAAFFDD")

    else:
    sns.kdeplot(df.loc[idx_train, col], color="g", fill=True, ax=ax, label="train")
    sns.kdeplot(df.loc[idx_val, col], color="c", fill=True, ax=ax, label="val.")
    sns.kdeplot(df.loc[idx_test, col], color="m", fill=True, ax=ax, label="test")
    ax.set_ylabel("")

    if col in ["A", "D"]:
    coltype = "categorical"
    else:
    coltype = "numerical"

    ax.set_title(f"{col} ({coltype})", pad=12)

    handles, labels = axs[5].get_legend_handles_labels()
    axs[2].legend(handles, labels, loc="upper left", bbox_to_anchor=(1,0.9))
    fig.tight_layout()
    fig.set_facecolor("w")
    fig.savefig(filename)
  • matplotlib은 bar plot을 나란히 놓기가 영 성가신 것이 사실입니다.
1
plot_distribution(df_vcv, idx_train, idx_val, idx_test, "68_vcv_1.png")


  • categorical은 그나마 좀 나은데 numerical들이 울퉁불퉁합니다.
  • 이 데이터들을 최대한 균등하게 나누어 봅시다.

6.2. 층화추출: stratified sampling

  • 층화추출은 train_test_split()stratify= 매개변수를 넣으면 됩니다.

    1
    2
    3
    # stratify split fail
    df_trainval, df_test = train_test_split(df_vcv, test_size=0.2, random_state=42,
    stratify=df_vcv[["B", "C", "E"]])


  • 그런데 에러가 납니다.

    • 층화추출은 데이터의 종류에 따라 경우의 수가 결정됩니다.
    • B x C x E = 5 x 5 x 4 = 100개는 되어야 하는데,
    • test dataset이 400 x 0.2 = 80개밖에 안됩니다.
    • 욕심을 줄이고 B와 C에만 층화추출을 적용합니다.
  • 1차로 train + val : test set을 8:2로 나누고
  • 2차로 train : val 을 3:1로 나눕니다.
  • 최종적으로 train : val : test = 3 : 1 : 1 로 나눕니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # stratify split success
    df_trainval, df_test = train_test_split(df_vcv, test_size=0.2, random_state=42,
    stratify=df_vcv[["B", "C"]])
    df_train, df_val = train_test_split(df_trainval, train_size=0.75, random_state=42,
    stratify=df_trainval[["B", "C"]])

    # index
    idx_train = df_train.index
    idx_val = df_val.index
    idx_test = df_test.index

    # visualization
    plot_distribution(df_vcv, idx_train, idx_val, idx_test, "68_vcv_2.png")

  • 분포가 한결 균일해집니다.

6.3. 더 층화추출

  • B와 C에 비해 E가 덜 균일한 것이 영 아쉽습니다.
  • B를 절반으로 나누어서라도 층화추출을 적용해 봅니다.
  • E의 범위를 측정해서 절반으로 자릅니다
  • np.quantile()을 사용합니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # quantizing E

    qbins = np.quantile(df_vcv["E"], np.linspace(0, 1, 3))
    qbins[0] = -np.inf
    qbins[-1] = np.inf
    print(qbins)

    qlabels = np.arange(2)
    print(qlabels)
    • 실행결과 :
      1
      2
      [-inf  2.5  inf]
      [0 1]
  • 2.5를 중심으로 나눈 기준을 이용해 0과 1로 인코딩을 합니다.
  • pd.cut()을 사용합니다.
    1
    2
    df_vcv["E_cat"] = pd.cut(df_vcv["E"], bins=qbins, labels=qlabels)
    df_vcv.head()

  • B, C에 E_cat까지 이용해 나눕니다.
  • 실제 사용할 데이터가 아니더라도 층화추출에 적용할 수 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # stratify split success
    df_trainval, df_test = train_test_split(df_vcv, test_size=0.2, random_state=42,
    stratify=df_vcv[["B", "C", "E_cat"]])
    df_train, df_val = train_test_split(df_trainval, train_size=0.75, random_state=42,
    stratify=df_trainval[["B", "C", "E_cat"]])

    # index
    idx_train = df_train.index
    idx_val = df_val.index
    idx_test = df_test.index

    # visualization
    plot_distribution(df_vcv, idx_train, idx_val, idx_test, "68_vcv_3.png")

  • 왠지 오히려 더 안좋습니다.
  • 아까 나누었던 B, C 층화추출을 채택하고, train, val, test set을 만듭니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # stratify split success
    df_trainval, df_test = train_test_split(df_vcv, test_size=0.2, random_state=42,
    stratify=df_vcv[["B", "C"]])
    df_train, df_val = train_test_split(df_trainval, train_size=0.75, random_state=42,
    stratify=df_trainval[["B", "C"]])

    # index
    idx_train = df_train.index
    idx_val = df_val.index
    idx_test = df_test.index

    # dataset
    X = df_vcv[["A", "B", "C", "D", "E"]]
    y = df_vcv["target"]

    X_train, X_val, X_test = X.loc[idx_train], X.loc[idx_val], X.loc[idx_test]
    y_train, y_val, y_test = y[idx_train], y[idx_val], y[idx_test]

7. 머신러닝

7.1. 사전 준비

  • 지난 글까지와 달리 이번 글에서는 고정된 validation set을 사용합니다.
  • 한편 hyperparameter는 이것저것 바꿔 넣을 것입니다.
  • 사용된 함수들을 일부 수정합니다. 자세한 설명은 생략합니다.

7.1.1. pipeline

  • 파이프라인을 구축합니다.
  • 학습과 예측에 필요한 데이터와 하이퍼파라미터만 넣으면 되도록 만듭니다.
    코드 보기/접기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    # encoder
    from sklearn.preprocessing import OneHotEncoder

    # machine learning models
    from sklearn.preprocessing import StandardScaler
    from sklearn.preprocessing import PolynomialFeatures

    # pipeline
    from sklearn.pipeline import Pipeline
    from sklearn.compose import ColumnTransformer

    # machine learning models
    from sklearn.svm import SVR
    from sklearn.kernel_ridge import KernelRidge
    from sklearn.neighbors import KNeighborsRegressor

    def get_model(method="svr", param=None):

    # categorical and numerical features
    cat_features = ["A", "D"]
    cat_transformer = OneHotEncoder(sparse=False)

    num_features = ["B", "C", "E"]
    num_transformer = Pipeline(steps=[("scaler", StandardScaler()),
    ("polynomial", PolynomialFeatures(include_bias=True))])

    # preprocessor
    preprocessor = ColumnTransformer(transformers=[("num", num_transformer, num_features),
    ("cat", cat_transformer, cat_features)
    ])

    # modeling
    if method == "svr":
    model = Pipeline(steps=[("preprocessor", preprocessor),
    ("svr", SVR())
    ])
    elif method == "kridge":
    model = Pipeline(steps=[("preprocessor", preprocessor),
    ("kridge", KernelRidge())
    ])
    elif method == "knr":
    model = Pipeline(steps=[("preprocessor", preprocessor),
    ("knr", KNeighborsRegressor())
    ])

    model.set_params(**param)
    return model
  • 예제 hyperparameter를 입력해서 모델이 잘 생성되는지 확인합니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # example: svr

    # hyperparameter
    param = {'preprocessor__num__polynomial__degree': 3,
    'svr__kernel': 'rbf', 'svr__C': 100, 'svr__gamma': 0.01}

    # model
    model = get_model(method="svr", param=param)

    # visualize
    from sklearn import set_config

    set_config(display='diagram')
    display(model)

7.1.2. evaluation

  • 모델 평가를 위한 함수를 마련합니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # evaluation metrics
    from sklearn.metrics import mean_absolute_error as mae_
    from sklearn.metrics import mean_squared_error as mse_
    from sklearn.metrics import r2_score as r2_

    def get_metrics(model, X, y):
    y_pred = model.predict(X)
    mae = mae_(y, y_pred)
    rmse = mse_(y, y_pred, squared=False)
    r2 = r2_(y, y_pred)

    return y_pred, mae, rmse, r2

    model.fit(X_train, y_train)
    y_pred_val, mae_val, rmse_val, r2_val = get_metrics(model, X_val, y_val)
    print(f"# mae_val : {mae_val:.3f}")
    print(f"# rmse_val : {rmse_val:.3f}")
    print(f"# r2_val : {r2_val:.3f}")
    • 실행 결과 :
      1
      2
      3
      #  mae_val : 0.296
      # rmse_val : 0.437
      # r2_val : 0.513

7.1.3. parity plots

  • 평가 결과를 scatter plot과 함께 시각화하는 함수를 만듭니다.
  • 지난 글에 있던 코드를 수정해서 확장성을 높였습니다.
  • tran & val만 볼 수도, train & val & test도 가능합니다.
    코드 보기/접기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    # single parity plot
    def plot_parity(true, predict, c="c", mae=None, rmse=None, r2=None,
    equal=True, title=None, xlabel="true", ylabel="predict", ax=None):
    # if axes not defined, create one.
    if not ax:
    fig, ax = plt.subplots(figsize=(4, 4))

    # scatter plot
    ax.scatter(true, predict, c=c, s=10, alpha=0.3)

    # x, y limits
    min_ = min(ax.get_xbound()[0], ax.get_ybound()[0])
    max_ = max(ax.get_xbound()[1], ax.get_ybound()[1])
    ax.set_xlim(min_, max_)
    ax.set_ylim(min_, max_)

    # x, y ticks
    lb, ub = ax.get_ybound()

    ticks =[x for x in ax.get_xticks() if x >= lb and x <= ub]
    ax.set_xticks(ticks)
    ax.set_xticklabels(ticks)
    ax.set_yticks(ticks)
    ax.set_yticklabels(ticks)

    # grids
    ax.set_aspect("equal")
    ax.grid(axis="both", c="lightgray")
    if equal:
    ax.plot([lb, ub], [lb, ub], c="k", alpha=0.3)

    # x, y labels and title
    ax.set_xlabel(xlabel, fontsize=16, labelpad=8)
    ax.set_ylabel(ylabel, fontsize=16, labelpad=8)
    ax.set_title(title, fontsize=16, pad=8)

    # metrics
    if mae:
    ax.text(0.95, 0.3, f" MAE ={mae:0.3f}",
    transform=ax.transAxes, fontsize=16, ha="right")
    if rmse:
    ax.text(0.95, 0.22, f"RMSE ={rmse:0.3f}",
    transform=ax.transAxes, fontsize=16, ha="right")
    if r2:
    ax.text(0.95, 0.14, f"R2 ={r2:0.3f}",
    transform=ax.transAxes, fontsize=16, ha="right")

    return ax

    # multiple parity plots
    def plot_parities(model, X_train, y_train, X_val=None, y_val=None, X_test=None, y_test=None, title=None, train=False):

    # validataion and test
    vis_val = False
    if (X_val is not None) and (y_val is not None):
    vis_val = True

    vis_test = False
    if (X_test is not None) and (y_test is not None):
    vis_test = True

    # train
    if train:
    model.fit(X_train, y_train)

    # prediction and get_metrics
    y_pred_train, mae_train, rmse_train, r2_train = get_metrics(model, X_train, y_train)

    if vis_val:
    y_pred_val, mae_val, rmse_val, r2_val = get_metrics(model, X_val, y_val)

    if vis_test:
    X_trainval = pd.concat([X_train, X_val], axis=0)
    y_trainval = pd.concat([y_train, y_val], axis=0)
    model.fit(X_trainval, y_trainval)
    y_pred_test, mae_test, rmse_test, r2_test = get_metrics(model, X_test, y_test)

    # figure preparation
    ncols = 1
    if vis_val:
    ncols += 1
    if vis_test:
    ncols += 1

    ax_width = 5
    if ncols > 1:
    ax_width = 4

    fig, axs = plt.subplots(ncols=ncols, figsize=(ax_width*ncols, 5), sharex=True, sharey=True)
    if not isinstance(axs, np.ndarray):
    axs = [axs]

    c_train, c_val, c_test = "g", "c", "m"

    axs[0] = plot_parity(y_train, y_pred_train, c=c_train, mae=mae_train, rmse=rmse_train, r2=r2_train,
    title="train", ax=axs[0])
    mins = [axs[0].get_xbound()[0]]
    maxs = [axs[0].get_xbound()[1]]

    if vis_val:
    axs[1] = plot_parity(y_val, y_pred_val, c=c_val, mae=mae_val, rmse=rmse_val, r2=r2_val,
    ylabel=None, title="validation", ax=axs[1])
    mins.append(axs[1].get_xbound()[0])
    maxs.append(axs[1].get_xbound()[1])

    if vis_test:
    axs[2] = plot_parity(y_test, y_pred_test, c=c_test, mae=mae_test, rmse=rmse_test, r2=r2_test,
    ylabel=None, title="test (train by full trainset)", ax=axs[2])
    mins.append(axs[2].get_xbound()[0])
    maxs.append(axs[2].get_xbound()[1])

    # suptitle
    if title:
    fig.suptitle(title, fontsize=20, ha="center")

    fig.tight_layout()
    fig.set_facecolor("w")

    if title:
    fig.savefig(f"{title.replace('(', '_').replace(')', '_').replace(':', '_')}.png")
  • train 후 validation을 출력하고,
  • train set과 validation set을 합쳐 다시 학습합니다.
  • 그 후에 test set으로 최종 성능을 평가합니다.
  • 층화추출이 적용되니 크게 고민하지 않은 값들을 넣었는데도 성능이 좋습니다.
    1
    2
    plot_parities(model, X_train, y_train, X_val, y_val, X_test, y_test, 
    title="test: SVR", train=True)
    ![](test_ SVR.png)

7.2. hyperparameter optimization

  • 이제 hyperparameter 최적화에 들어갑니다.
  • 모든 변수의 조합을 탐색하는 grid search
  • 앞선 결과를 바탕으로 최적 탐색을 하는 bayesian search를 해 봅니다.
  • 준비된 데이터셋을 모두 넣으면 grid search를 수행해서 결과를 출력하게 합니다.
  • 지난 글과 다른 형식으로 출력해 봅니다.
  • grid search를 수행해서 DataFrame 형식으로 출력합니다.
    코드 보기/접기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    from itertools import product

    def gs_model(X_train, y_train, X_val, y_val, method="svr", param_grid=None):

    # param_grid
    param_ks = param_grid.keys()
    param_vs = list(product(*param_grid.values()))

    df = pd.DataFrame(columns=list(param_ks)
    + ["mae_train", "mae_val", "rmse_train", "rmse_val", "r2_train", "r2_val"]
    + ["y_pred_train", "y_pred_val"])

    for i, vs in enumerate(param_vs):
    param = dict(zip(param_ks, vs))

    model = get_model(method=method, param=param)

    # train
    model.fit(X_train, y_train)

    # evaluation
    y_pred_train, mae_train, rmse_train, r2_train = get_metrics(model, X_train, y_train)
    y_pred_val, mae_val, rmse_val, r2_val = get_metrics(model, X_val, y_val)

    # output
    df.loc[i] = list(vs) + [mae_train, mae_val,
    rmse_train, rmse_val,
    r2_train, r2_val,
    y_pred_train, y_pred_val]

    return df
  • gs_model()를 이용해서 모든 결과를 출력하는 함수를 만듭니다.
  • gs_model() 결과물의 dataframe index에 따라 평가 결과를 출력합니다.
    코드 보기/접기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    def plot_gs(X_train, y_train, X_val, y_val, method="svr", param_grid=None, filename=None):
    df = gs_model(X_train, y_train, X_val, y_val, method=method, param_grid=param_grid)
    df_sort = df.sort_values("r2_val", ascending=False)

    # best params
    best_params = df_sort[list(param_grid.keys())].iloc[0].to_dict()
    best_params_idx = df_sort.index[0]
    print(best_params)

    # train vs validation plot
    ax = df_svr[["r2_train", "r2_val"]].plot(figsize=(12, 4))
    ax.axvline(best_params_idx, c="g", ls=":")
    fig = ax.figure
    fig.set_facecolor("w")

    if filename:
    fig.savefig(filename)

    return best_params
  • hyperparameter 범위를 설정하고 grid search를 수행합니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # hyperparameter space
    param_grid = {'preprocessor__num__polynomial__degree': [2],
    'svr__kernel': ['rbf'], #['linear', 'poly', 'rbf', 'sigmoid'],
    'svr__C': np.logspace(-2, 2, 11),
    'svr__gamma': np.linspace(0.01, 0.1, 11),
    'svr__coef0': [0]}

    # grid search & visualization 수행
    best_params = plot_gs(X_train, y_train, X_val, y_val, method="svr", param_grid=param_grid, filename="68_vcv_6.png")

  • 재미있는 현상이 나타났습니다.
    • validation set의 r2가 가장 큰 지점(점선)을 기준으로,
    • 왼쪽에선 train과 val 곡선이 비슷합니다
    • 오른쪽에선 두 곡선이 반대 경향입니다
    • validation r2가 증가하다 감소하는걸 볼 때 underfitting에서 overfitting으로 꺾이는 지점이 관측되었다고 볼 수 있습니다.
  • 최적조건으로 학습시켜 test set 평가까지 봅니다.
    1
    2
    3
    model = get_model(method="svr", param=best_params)
    plot_parities(model, X_train, y_train, X_val, y_val, X_test, y_test,
    title="SVR: rbf (deg=2)", train=True)
    ![](SVR_ rbf _deg=2_.png)
  • test set의 R2가 0.7을 넘었습니다!
    • train set과 val set의 R2도 비슷하네요.
    • 층화추출 덕에 안정적인 성능이 나오는 것 같습니다.

hyperopt
optuna
scikit-optimize
medium: hyperparameter tuning using bayesian optimisation code

  • 그런데 모든 변수를 다 탐색하는 과정이 번거롭습니다.
  • scikit-optimzie를 사용해서 베이지안 탐색을 시도합니다.
  • 다음 명령어로 설치할 수 있습니다.
    1
    !pip install scikit-optimize
  • 필요한 모듈을 불러옵니다.
    1
    2
    3
    4
    5
    # hyperparameter 탐색 범위 설정에 사용합니다.
    from skopt.space import Real, Integer, Categorical

    # objective function에 들어갈 parameter를 keyword arguments로 입력하게 해줍니다.
    from skopt.utils import use_named_args
  • 탐색 공간을 정의합니다.
    1
    2
    3
    4
    5
    6
    space = [Integer(1, 2, name="preprocessor__num__polynomial__degree"),
    Categorical(["rbf"], name="svr__kernel"),
    Real(0.01, 100, "log-uniform", name="svr__C"),
    Real(0.01, 0.1, "uniform", name="svr__gamma"),
    Integer(0, 1, name="svr__coef0")
    ]
  • 목표 함수(objective function)을 정의합니다.

  • @use_named_args를 사용해서 keyword argument로 입력합니다.

  • 제가 7.1.1.pipeline에서 만든 get_model()을 사용하도록 했습니다.

  • 출력은 r2_val 값으로 하고, 마이너스를 붙여 더 큰 값이 더 작게 출력되도록 했습니다.

    1
    2
    3
    4
    5
    6
    7
    @use_named_args(space)
    def objective(**params):
    model = get_model(method="svr", param=params)
    model.fit(X_train, y_train)
    y_pred_val, mae_val, rmse_val, r2_val = get_metrics(model, X_val, y_val)

    return -r2_val
  • gaussian process로 최적화합니다.

  • gp_minimize()명령을 사용합니다.

    1
    2
    3
    4
    5
    from skopt import gp_minimize

    model_gp = gp_minimize(objective, space, n_calls=50, random_state=0)

    print(f"best score = {model_gp.fun:.4f}")
    • 실행 결과:
      1
      best score = -0.6193
  • 최적 파라미터는 model_gp.x에 저장됩니다.
    1
    model_gp.x
    • 실행 결과:
      1
      [2, 'rbf', 0.7740303218260096, 0.1, 1]
  • 제법 괜찮은 결과를 얻은 것 같긴 한데, 수렴했는지 확인해봅니다.
  • plot_converge()를 사용합니다.
    1
    2
    3
    4
    5
    6
    7
    8
    from skopt.plots import plot_convergence

    ax = plot_convergence(model_gp)
    fig = ax.figure
    fig.set_facecolor("w")
    fig.set_figheight(5)
    fig.set_figwidth(12)
    fig.savefig("68_vcv_7.png")

  • 20회만에 최적 조건에 수렴했습니다.
  • 해당 조건이 테스트셋에선 어떤 결과를 내는지 확인해봅니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 최적 파라미터 key와 value 결합
    param_keys = [s.name for s in space]
    best_params = dict(zip(param_keys, model_gp.x))
    print(best_params)

    # 최적 파라미터로 모델 학습, 시각화
    model = get_model(method="svr", param=best_params)
    plot_parities(model, X_train, y_train, X_val, y_val, X_test, y_test,
    title="SVR: scikit-learn opt.", train=True)
    ![](SVR_ scikit-learn opt..png)
  • R2가 다시 0.7을 넘었습니다
  • 이번 업무의 목표가 0.7인데 SVR에서 0.7이 넘어서 상향 조정해도 될 것 같습니다.
  • 자주 사용할 수 있도록 앞에서 파편화된 함수들을 하나로 모읍니다.
  • local minima에 빠지지 않도록 seed를 흔들어 여러 번 시도합니다.
  • Andrew Ng 교수님이 Caviar approach라고 말씀하신 방법입니다.
  • 뭐 하나 잘 걸리라는 느낌입니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    def run_skopt(objective, space, method="svr", n_calls=30, iternum=10, random_state_base=0,
    filename_conv=None, filename_parity=None, figsize=(12, 5)):

    fig, ax = plt.subplots(figsize=figsize)
    fig.set_facecolor("w")

    best_score = np.inf

    # scikit optimization
    for i in range(iternum):
    model_gp = gp_minimize(objective, space, n_calls=n_calls, random_state=i*100+random_state_base)
    best_score_ = model_gp.fun
    print(f"best score #{i+1} = {best_score_:.4f}")

    # convergence plot
    ax = plot_convergence(model_gp, ax=ax)

    # best parameters
    if best_score_ < best_score:
    best_score = best_score_
    param_keys = [s.name for s in space]
    best_params = dict(zip(param_keys, model_gp.x))
    print(f"best parameters= {best_params}")

    fig.savefig(filename_conv)

    # train, val and test result
    best_model = get_model(method=method, param=best_params)
    plot_parities(best_model, X_train, y_train, X_val, y_val, X_test, y_test,
    title=filename_parity, train=True)

    with open(f"{filename_parity}.pkl", 'wb') as f:
    pickle.dump(best_model, f)

    return best_model, best_params
  • 사용할 때는 위의 space, objective와 함께 세트로 사용합니다.
  • @use_named_args(space) 데커레이터로 인해 space를 먼저 정의해야 합니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #space
    space = [Integer(1, 2, name="preprocessor__num__polynomial__degree"),
    Categorical(["rbf"], name="svr__kernel"),
    Real(0.001, 1000, "log-uniform", name="svr__C"),
    Real(1e-3, 10, "log-uniform", name="svr__gamma"),
    ]

    #objective
    @use_named_args(space)
    def objective(**params):
    model = get_model(method="svr", param=params)
    model.fit(X_train, y_train)
    y_pred_val, mae_val, rmse_val, r2_val = get_metrics(model, X_val, y_val)

    return -r2_val

    # optimize
    best_svr_rbf, best_params = run_skopt(objective, space, method="svr", n_calls=50,
    filename_conv="68_vcv_8.png",
    filename_parity="SVR_rbf_opt", figsize=(12, 5))


  • 이제 마음껏 실전에 투입합니다. :)


도움이 되셨나요? 카페인을 투입하시면 다음 포스팅으로 변환됩니다

Share