728x90

*주요 요약

1. hour_bef_ozone의 결측치 예측

2. 결측치 다수 존재하는 변수들은 미 사용하기로 판단함

 

 

* 본 내용

결측치가 많은 ozone, pm10, pm2.5를 예측하여 값을 채우려 시도하고 있습니다. 기존에는 각 시간, 변수 별 평균값으로 결측치를 채웠습니다. 하지만 이러한 방법은 정확성을 떨어뜨릴수 있습니다 게다가 그러한 결측치가 워낙 많다 보니까 새로운 방법을 찾아야 했습니다. ozone, pm10, pm2.5를 제외한 feature을 활용했습니다.(id, count 제외)

 

먼저, 사용될 X feature들의 결측치 먼저 처리해야 합니다. 결측치 수가 많지 않으므로 이는 기존에 만들어 사용했던 시간,변수별 평균값 함수를 사용하여 채웠습니다. 아래 step 1을 통해 상세하게 설명드립니다.

 

Step 1. nan_mean_fill 함수를 사용하여 X feature 채우기 (단, precipitation은 분류 이므로 이 또한 제외)

def nan_mean_fill(dataset):
  #결측치가 있는 컬럼
  nan_columns = [col for col in dataset.iloc[:,2:-1].columns.to_list() if col!='hour_bef_precipitation' and col!='hour_bef_ozone' and col!='hour_bef_pm10' and col!='hour_bef_pm2.5']

  #for 문을 통해 결측치를 각 시간대별 평균값으로 대체
  for col in nan_columns:
    hour_mean_value = dataset.groupby('hour').mean()[col]
    hour_mean_value.fillna(hour_mean_value.mean(),inplace=True) #회귀를 통해서 값 채우기 시도

    for nan_hour in dataset[dataset[col].isna()]['hour'].unique():
      
      #nan index 구하기
      index= dataset[dataset[col].isna()].loc[dataset['hour']==nan_hour,col].index

      #채우기 
      for idx in index:
        dataset.loc[idx, col] = hour_mean_value[nan_hour]
        
  print(dataset.isna().sum())
  return dataset

train = nan_mean_fill(train)
test= nan_mean_fill(test)

 

id                          0
hour                        0
hour_bef_temperature        0
hour_bef_precipitation      2
hour_bef_windspeed          0
hour_bef_humidity           0
hour_bef_visibility         0
hour_bef_ozone             76
hour_bef_pm10              90
hour_bef_pm2.5            117
count                       0
dtype: int64
id                         0
hour                       0
hour_bef_temperature       0
hour_bef_precipitation     1
hour_bef_windspeed         0
hour_bef_humidity          0
hour_bef_visibility        0
hour_bef_ozone            35
hour_bef_pm10             37
hour_bef_pm2.5            36

precipitation과 target으로 사용될 ozone,pm10, pm2.5를 제외한 나머지 변수들의 값을 채운걸 확인해 볼수 있습니다.

 

다음은 Logistic을 통해 precipitation의 결측치를 채우겠습니다. 참고로 precipitation은 비가 온다 안온다에 대한 데이터이며 0과1로 구성되어 있습니다.

 

 

 

step 2. precipitation 결측값 채우기

from sklearn.linear_model import LogisticRegression
from sklearn import metrics

#x,y 분리
precipitation_x_del = pd.concat([train,test], axis=0).iloc[:,1:-4].dropna()
cls_x_train = precipitation_x_del[[col for col in precipitation_x_del.columns if col!='hour_bef_precipitation']]
cls_y_train =precipitation_x_del['hour_bef_precipitation']

#data split
cls_X_train, cls_X_val, cls_Y_train, cls_Y_test = train_test_split(cls_x_train, cls_y_train, test_size = 0.3, shuffle = True, random_state = 2021)

# model fit
cls_model = LogisticRegression()
cls_model.fit(cls_X_train, cls_Y_train)

#predict
y_pred = pd.Series(cls_model.predict(cls_X_val))

# model evaluation
print("Accuracy:", metrics.accuracy_score(cls_Y_test, y_pred))
Accuracy: 0.9662576687116564

 

# 불필요한 변수 제거
hour_bef_precipitation_train_df = train[train['hour_bef_precipitation'].isna()].iloc[:,1:-4]
del hour_bef_precipitation_train_df['hour_bef_precipitation']
display(hour_bef_precipitation_train_df)

hour_bef_precipitation_test_df = test[test['hour_bef_precipitation'].isna()].iloc[:,1:-3]
del hour_bef_precipitation_test_df['hour_bef_precipitation']
display(hour_bef_precipitation_test_df)

#predict -> 비가 오지 않음(0) 
print(cls_model.predict(hour_bef_precipitation_train_df))
print(cls_model.predict(hour_bef_precipitation_test_df))

# 결측값 0으로 채우기
train['hour_bef_precipitation'].fillna({934:0, 1035:0}, inplace=True)
print(train.isna().sum())

test['hour_bef_precipitation'].fillna({653:0}, inplace=True)
print(test.isna().sum())

#predict -> 비가 오지 않음(0) 
print('train의 hour_bef_precipitation 예측값:',cls_model.predict(hour_bef_precipitation_train_df))
print("test의 hour_bef_precipitation 예측값:",cls_model.predict(hour_bef_precipitation_test_df))

 

	hour	hour_bef_temperature	hour_bef_windspeed	hour_bef_humidity	hour_bef_visibility
934	0	14.788136	1.965517	58.169492	1434.220339
1035	18	20.926667	3.838333	40.450000	1581.850000
hour	hour_bef_temperature	hour_bef_windspeed	hour_bef_humidity	hour_bef_visibility
653	19	26.110345	3.541379	47.689655	1561.758621
[0. 0.]
[0.]
id                          0
hour                        0
hour_bef_temperature        0
hour_bef_precipitation      0
hour_bef_windspeed          0
hour_bef_humidity           0
hour_bef_visibility         0
hour_bef_ozone             76
hour_bef_pm10              90
hour_bef_pm2.5            117
count                       0
dtype: int64
id                         0
hour                       0
hour_bef_temperature       0
hour_bef_precipitation     0
hour_bef_windspeed         0
hour_bef_humidity          0
hour_bef_visibility        0
hour_bef_ozone            35
hour_bef_pm10             37
hour_bef_pm2.5            36
dtype: int64
train의 hour_bef_precipitation 예측값: [0. 0.]
test의 hour_bef_precipitation 예측값: [0.]

결측값을 채우기 위한 분류 결과, 모두 비가 오지 않았다는 예측을 얻었을 수가 있었습니다. 따라서 precipitation 결측값에 모두 0을 채웠습니다. 

 

X feature들의 결측값을 모두 채웠으므로 ozone, pm10, pm2.5 각각을 Y_target으로 설정하여 예측을 해보록 하겠습니다. 

 

step3. ozone, pm10, pm2.5 결측값 예측

def evaluate(y_true, y_pred):
  y_true, y_pred = np.array(y_true), np.array(y_pred)
  mape= np.mean(np.abs((y_true-y_pred)/y_true))*100
  mae= mean_absolute_error(y_true,y_pred)
  mse= mean_squared_error(y_true,y_pred)
  score = pd.DataFrame([mape, mae, mse], index=['mape','mae','mse'], columns=['score']).transpose()
  return score

 

def Nan_prediction(targets):
  # train 데이터 만들기
  for target in targets:
    if target =='hour_bef_pm2.5':
       use_columns = ['hour', 'hour_bef_temperature', 'hour_bef_precipitation','hour_bef_windspeed', 'hour_bef_humidity', 'hour_bef_visibility','hour_bef_pm10',target]
    else:
      use_columns = ['hour', 'hour_bef_temperature', 'hour_bef_precipitation','hour_bef_windspeed', 'hour_bef_humidity', 'hour_bef_visibility',target]

    #feature, target data split
    without_isna = pd.concat([train,test],axis=0).loc[:,use_columns].dropna()
    X_feature = without_isna.iloc[:,:-1]
    Y_train = without_isna[target]

    X_train, X_test, Y_train, Y_test = train_test_split(X_feature,Y_train, test_size=0.3, random_state=2021)

    # 원본 train data 안에 nan 값에 해당하는 Feature 추출 
    ozone_isna_train_df= train[train[target].isna()]

    # 원본 test data 안에 nan 값에 해당하는 Feature 추출
    ozone_isna_test_df= test[test[target].isna()]

    models = [
        ('ridge', lm.Ridge()),
        ('lasso', lm.Lasso()),
        ('elastic', lm.ElasticNet()),
        ('LassoLars', lm.LassoLars()),
        ('SGDRegressor', lm.SGDRegressor()),
        ('knn', KNeighborsRegressor(n_jobs = -1)),
    ]

    params = {
        'ridge': {
            'alpha': [0.01, 0.1, 1.0, 10, 100],
            'fit_intercept': [True, False],
            'normalize': [True, False],
        },
        'lasso': {
            'alpha': [0.1, 1.0, 10],
            'fit_intercept': [True, False],
            'normalize': [True, False],
        },
        'elastic': {
            'alpha': [0.1, 1.0, 10],
            'normalize': [True, False],
            'fit_intercept': [True, False],
        },
            'LassoLars': {
            'alpha': [0.1, 1.0, 10],
            'normalize': [True, False],
            'fit_intercept': [True, False],
        },
        'SGDRegressor': {
            'penalty': ['l1', 'l2'],
            'alpha': [0.001, 0.01, 0.1, 1.0, 10, 100],
            'fit_intercept': [True, False],
        },
        'knn': {
            "n_neighbors": range(2,7),
        }
    }
    
    #target 별 상관성이 상대적으로 낮은 변수들 제외하기 위한 x_colname 정의
    hour_bef_ozone_x_colname = [col for col in X_train_scaled.columns if col != 'hour_bef_precipitation' and col != 'hour_bef_ozone' and col != 'hour_bef_pm10' and col != 'hour_bef_pm2.5']
    hour_bef_pm10_x_colname =['hour_bef_humidity','hour_bef_visibility']
    hour_bef_pm25_x_colname = ['hour_bef_humidity','hour_bef_visibility','hour_bef_pm10'] 

    if target =="hour_bef_ozone":
      x_colname = hour_bef_ozone_x_colname
    elif target =="hour_bef_pm10":
      x_colname = hour_bef_pm10_x_colname
    else:
      x_colname = hour_bef_pm25_x_colname

    best_model, best_score = None, float('inf')
    for model_name, model in models:
        param_grid = params[model_name]
        grid = GridSearchCV(model, cv=5, n_jobs=-1, param_grid=param_grid)
        grid = grid.fit(X_train[x_colname], Y_train)

        model = grid.best_estimator_
        predictions = model.predict(X_test[x_colname])

        evaluation = evaluate(Y_test, predictions)
        score = evaluation['mse'][0]
        print(model_name, score)

        if score < best_score:
            best_score=score
            best_model = model

    print("최적의 모델:",best_model)
    print("최종 mse score:",best_score,"\n")
    best_model = grid.best_estimator_
    best_train_pred = best_model.predict(ozone_isna_train_df[x_colname])
    best_test_pred = best_model.predict(ozone_isna_test_df[x_colname])

    # 예측값을 train,test에 적용
    ozone_nan_train_index = train[train[target].isna()][target].index
    ozone_nan_test_index = test[test[target].isna()][target].index

    for pred,index in zip(best_train_pred,ozone_nan_train_index):
      train[target].fillna({index:pred}, inplace=True)
    for pred,index in zip(best_test_pred,ozone_nan_test_index):
      test[target].fillna({index:pred}, inplace=True)

targets =['hour_bef_ozone', 'hour_bef_pm10', 'hour_bef_pm2.5']
Nan_prediction(targets)

 

ridge 0.00022735981284121033
lasso 0.0003577431018933861
elastic 0.00032536467486597313
LassoLars 0.000357741115357743
SGDRegressor 0.0019727722132471728
knn 0.0002798427571351642
최적의 모델: Ridge(alpha=0.01, copy_X=True, fit_intercept=True, max_iter=None,
      normalize=True, random_state=None, solver='auto', tol=0.001)
최종 mse score: 0.00022735981284121033 

ridge 479.574083827637
lasso 480.9698920519781
elastic 480.99426051508937
LassoLars 480.97117933041903
SGDRegressor 3.519192783192444e+29
knn 514.6375284552845
최적의 모델: Ridge(alpha=0.01, copy_X=True, fit_intercept=True, max_iter=None,
      normalize=True, random_state=None, solver='auto', tol=0.001)
최종 mse score: 479.574083827637 

ridge 84.67710204218932
lasso 84.72469340584847
elastic 84.63747722153994
LassoLars 84.7245776238065
SGDRegressor 1.022157379699085e+30
knn 45.39878500823723
최적의 모델: KNeighborsRegressor(algorithm='auto', leaf_size=30, metric='minkowski',
                    metric_params=None, n_jobs=-1, n_neighbors=4, p=2,
                    weights='uniform')
최종 mse score: 45.39878500823723

각 target별 연관성이 있다고 판단한 변수들만 뽑아서 예측해 보았습니다. ozone변수를 보면 여러개의 X feature들과 연관성을 가지고 있습니다. 반면 pm10, pm 2.5는 영향력있는 변수가 적었기 때문에 예측성능이 좋지 못했습니다. 따라서 

ozone은 예측값을 사용하고 그외 pm10, pm 2.5은 예측값을 넣되 사용하지 하지않기로 결정했습니다. 왜냐하면 이 둘의 결측값의 수만 해도 100개 이상이며 최종 target인 count와 연관성이 적기 때문입니다. (아래 이미지를 참고해주세요)

 

 

 

* 다음 시간 예고

다음 시간부터는 count의 출퇴근 수요를 설명해줄 변수를 생성하려고 합니다. 

아래는 X feature들의 분포입니다.

colum=['hour_bef_temperature','hour_bef_windspeed','hour_bef_humidity','hour_bef_ozone','hour_bef_pm10','hour_bef_pm2.5']
for col in colum:
  train.groupby(by='hour').mean()[col].plot(label=col)
  plt.legend()
  plt.show()

 

 

*Count 분포

train.groupby(by='hour').mean()['count'].plot()

출퇴근 시간에 수요가 급등하는 모습을 보이고 있습니다. 이러한 특징적인 현상을 설명해줄 변수가 필요합니다. X feature들의 분포를 보면 몇몇 변수에서 퇴근시간 증가하는 모습을 보입니다. 그렇지만 증가하는 분포? 폭이 넓기 때문에 기대이상으로 퇴근 시간대의 수요를 설명해 주기란 어려울 것으로 생각했습니다. 특히 출근 시간대의 수요증가는 hour뿐 이기 때문에 새로운 변수의 필요성을 느끼게 됩니다. 아직은 이렇게 해서 만들어야 겠다! 라는 아이디어는 떠오르지 않아서 시간이 좀 걸리지 않을까 생각해 봅니다 

 

좀 더 개선된 내용을 가지고 다음 시간에 포스팅 하겠습니다. 감사합니다

 

728x90
728x90

*오늘 얻어낸 것들

1. target의 log화는 성능을 떨어뜨린다. 

2. 오늘 시도했던 잡다한 방법들 모두 성능에 영향을 주지 못하거나 떨어뜨린다 (자잘한게 많아서 뭘 했는지 조차 기억..)

3. 결측값을 평균값으로 일괄 적용X

*현재 시도 중인 것

본래 결측치 값을 평균으로 싹 다 처리했습니다. 그런데 생각해 보니까 날씨 데이터가 있는데 평균값으로 대체 하면 말이 안되더라구요. 예를들어 비가 오는데 기온이 높으면 이치에 어긋나기 때문에 이를 수정하기로 했습니다.

 

 

특히, 마지막 3개는 결측값이 엄청 납니다. 가뜩이나 데이터도 많진 않아서 삭제하기 부담스럽습니다. 그래서 평균으로 대체 하는 것보다 예측값을 넣는게 차라리 낫지 않을까 하는 생각에 뒤에 이 3개 변수를 각각 예측모델링 짜고 있습니다.

전처리 하고 이것저것 자잘하게 처리 하고 그리드 서치 한 결과 test 예측부분에서

릿지모델에서 가장 낮은 mse를 기록했습니다. 해당 모델 가지고 적용하려는 순간 

 

best_model = grid.best_estimator_
best_pred = best_model.predict(test_ozone_scaled[x_colname])
ValueError: Input contains NaN, infinity or a value too large for dtype('float64').

 

test_ozone_scaled.isna().sum()
hour                      0
hour_bef_temperature      2
hour_bef_precipitation    2
hour_bef_windspeed        3
hour_bef_humidity         2
hour_bef_visibility       2

 

결측값이 있었던 걸 잊고 있었습니다. 해당 결측값들은 train할때 정확한 정보를 주고자 삭제를 시켰었습니다. 하지만 원본 데이터에는 그대로 존재하기 때문에 이 결측값을 먼저 처리해야 합니다. 결측값이 각 변수 별로 얼마 되지 않기 때문에 기존에 만들어 놓은 함수로 평균값 채우기 할 생각입니다. 오늘이 여기까지 진도 나갔습니다. 더 개선된 내용으로 업로드 하겠습니다. 

728x90
728x90

저번 포스팅까지는 X 변수가 하나라는 가정하에 진행해 왔습니다. 이제 부터는 X가 둘 이상인 multi을 다룹니다.

위 그림처럼, X들이 여러개 나뉘어 졌다. X1: 공부시간, X2: 등하교 시간으로 예를 들수 있겠습니다. 그래서 각각의 변수 데이터를 가지고 해당되는 target 값인 y를 예측하게 됩니다.

 

 

X2가 추가가 되었음을 볼 수 있다. 각 theta, X에 대해서 벡터 형식으로 정리가 가능하다. X 벡터의 1인 dummy 변수를 넣어 줘야 dot product 했을때 계산이 가능합니다.

 

 

Loss function에 th가 3개로 확장 되었으므로 더이상 평면이 아닌 공간 상에서 표현이 될 것입니다.

 

 

전에 배웠던 내용과 다른 점이 있다면 th2가 추가 되었다는 점 뿐이다.

 

 

각 th별로 미분을 하면 빨간색과 같은 수식이 나온다. 다만 달라진 점은 X인데 해당 X에 따라서 데이터 어떤 영향을 끼치는지 계속 자각해야 한다.

 

 

gradient을 구한뒤 GDM의 수식을 위 처럼 정리 할수 있겠다. th2은 X2에 영향을 받고, th1은 X1에 영향을 받는다는 점이 핵심이다.

 

 

Cost도 같은 과정을 거치게 되면 최종적으로 위 그림처럼 정리할 수 있다.

 

 

 

이번엔 X가 m개일 경우를 살펴본다. 예를 들어 100*100 이미지가 있다면 10000개의 X가 존재하고 이를 벡터형식으로 바꾼 모습이 위와 같을 것이다.

 

 

 

각각의 th,X의 벡터을 dot product을 시키면 맨 아래 수식처럼 간단한 형식으로 표현이 가능하다. Loss도 마찬가지로 같은 원리를 통해 간단하게 표한가능하다.

 

 

 

weight M개, bias 1개를 포함한 M+1개를 각각 gradients 구하면 위처럼 정리 할수 있다. m+1 차원의 Gradients가 만들어 진다. weight에는 P라는 첨자가 붙고 bias에는 붙지 않는다는 것에 대한 의미를 까먹지 않도록 상기 시켜야 한다.

 

 

 

 

Affine function 부분에서 forward, backward propagation이 발생하 넘겨주고, 받는 역할을 하게 된다. 누구에게 넘겨주느냐? Activation Funtion에게 넘겨주게 된다. 이런 전체적인 과정이 딥러닝이 일어나는 과정이다.

왼쪽 그림을 보면 둘로 나눠져 있다. 즉,affine funtion 뒤에 Activation이 붙든, Cross entropy가 붙든, soft max가 붙든, Loss 구하든지, cost를 구하든지 일절 상관이 없다. Affine은 단지 Z값을 전달하고, 자기가 내줬던 값에 대한 편미분 값을 받아서 업데이트 하는 것 뿐이다.

 

 

 

지금까지 배웠던 것을 그림으로 표현하면 위와 같다. Z값을 받고 Loss 혹은 Cost를 구한뒤 편미분을 통해 업데이트가 일어난다. 뉴런 1개에 이와 같은 연산이 발생한다.

multi linear에서는 Loss function이 어떻게 변하는지 알아보자.

 

 

 

Loss funtion

-편의상 두개의 x값을 가지고 진행한다.

각각의 datset에 따른 prediction과 Loss는 위 처럼 구할수 있을 것이다. 이것을 다음에 적용해 보도록 한다.

 

 

 

해당 식과 data가 있을때, 변화를 면밀하게 살펴보기 위해서 세개의 값중 하나를 고정시켜 보도록 한다. 가장 왼쪽은 bias가 학습이 다 됐다는 가정 하에 th의 변화를, 두번째는 th1이 학습완료가 됐다는 가정하에 th2와 th0간에 변화를 나타내고 마지막도 같은 원리로 진행한다.

 

 

 

 

가장 왼쪽 사진에서 적용된 data는 (1,1)이다. 파란색 식을 보면 (1,1)일때 비율이 같기 때문에(학습량이 같다) a=-x방향의 Loss funtion을 볼수 있다.

두번째 사진에서 적용된 data는 x1의 1뿐이다. 그러면 th2와 th0 간의 비율도 역시 같기 때문에 대각선 방향이며 세번째 그림도 같은 원리로 대각선 방향이다.

 

 

 

첫번째 사진에서 적용된 data는 (2,1)이다. th2가 더 높은 비율을 가지므로 th1보다 빨리 학습 될것이다. 그림에서 보듯이 기울기가 y축 방향으로 붙는걸 확인할수있다. th2방향으로 먼저 projection이 된다.

두번째 그림에서 적용된 값은 x2의 2이다. 파란색 식을 보면 2*a*x2 VS 2*a 이므로 2배차이가 발생한다. 그래서 첫번째 그림처럼 y축에 붙게 된다.

세번째 그림은 th1과 th0은 서로 같은 비율이기 때문에 y = -x 방향의 대각선을 갖게된다.

 

 

 

파란색과 빨간색은 서로 같은 비율이기 때문에 겹쳐있다.

 

 

 

왼쪽그림에서 x2=3, x1=2 일때, 이 둘의 비율 차이가 1.5배이다. 1보다는 크고 2보다는 작은 모습으로 만들어진 것이다.

가운데 그림에서는 비율이 3배 차이가 나기 때문에 기울기가 더욱 기울어진 모습을 보인다.

오른쪽 그림은 비율의 차이가 2배이므로 1보다는 크고 3보다는 작은 모습으로 만들어진다.

 

 

 

왼쪽그림에서 x1=10, x2= 3 일때, x1이 더 크기 때문에 먼저 학습이 될 것이다. 전에 배웠던 X의 값이 1보다 작을때의 Loss function 형태와 같아진다.

두번째 그림에서는 x2와 bias의 비율 차이가 3배이기 때문에 이와 같이 만들어진다.

세번째 그림에서는 10배 만큼 비율의 차이가 있으므로 y축에 바짝 붙는 형태가 만들어진다.

 

 

 

 

표준정규분포를 따르는 데이터를 입력으로 넣었을 때는 th들이 균등하게 학습이 되는 특징을 가지고 있다. 위 모습이 가장 "이상적인 학습의 모습"이다.

 

 

x2=5로 늘리 다. 또한 Loss의 양을 보면 초반에 초반에만 급격하게 치솟아 올랐다. 이는 급격한 th2의 학습이 원인이다. x값이 크면 Loss의 양도 비례하게 된다. Loss가 크다는 건 학습해야 할 양이 많다는 건데 X값이 너무 커지면 lr로 더욱 작게 설정하여 발산하지 못하도록 해야한다.

 

 

 

 

x2가 1보다 작은 수 라면, th2만 혼자서 학습속도가 느려진 걸 확인 할수 있다. 하단의 그림을 보면 th1, th0이 먼저 학습할려고 위쪽으로 끌어 당기는 모습이 보인다. 그리고 th1,th0은 같은 학습속도 이므로 좌하단처럼 대각선 모양을 가진다.

 

 

 

극단적으로 0.1를 std로 설정하면 거의 0 근처 값들만 나오게 될 것이다. 그래서 하단 그림을 보면 거~의 학습이 되지 않는다. interation을 많이 주게 되면 결국은 목표값에 도달하겠지만 그만큼 시간과 자원이 소모되는 걸 감안 했을때는 비효율적이다. 또한 중간에 끊기게 되면 th1,th0은 학습이 된 상태될 것이고, th2은 학습이 미완성인 상태로 끝나게 되는 문제점도 있다.

이번엔 평균이 바뀔때 변하는 모습을 보도록 한다.

 

 

 

X2의 평균값 자체가 커지면서 th2의 지배적인 학습이 진행됨을 볼수있다. 가운데를 보면 th2가 자기쪽으로 당기기 때문에 th0에 대한 학습이 지그재그로 천천히 학습이 되는 문제점이 생긴다.

 

 

 

상하단 그림을 비교해 보자. mean값이 커질수록 먼저 학습이 되기 때문에 하단처럼 th2,1이 먼저 치고 올라 가는걸 볼수 있다. 반면 th0은 더욱 느려진다. 왜냐면 Loss가 th2,1에게 많이 뺐겼기 때문이다. 하단의 loss를 보면 평행을 이룰만큼 거의 존재하지 않는다.

 

 

 

 

하단을 보면 공통적으로 보이는 것은 th2,1 쪽으로 먼저 빠르게 학습이 된 후에야 th0가 지그재그로 학습 되고 있다.

 

 

 

x2 평균이 -5 음수이다. 이때 특이한 모양이 보이는데 바로 윗 그림을 보면 th0값이 뒤로 오히러 퇴보? 뒤로 당겨지는 모습을 보인다. 이유는 간단하다. -5의 Loss function 방향으로 projection이 되다 보니까 이런 현상이 발생한 것이다. 초기값이 만일 다른 곳에서 출발했다면 또 다른 모습을 보일 것이다.

728x90
728x90

오늘 진도 나간 부분은 아래 순으로 진행했습니다.

 

step 1) 피처중요도가 작았던 날씨 데이터를 활용한 새로운 파생변수 만들기

-> 처음에는 전체 데이터를 가지고 주말,평일을 나눠볼려고 시도했습니다. 그래서 군집분석을 해봤는데, 시도할수록 

2개로 군집 분석해도 이 의미가 과연 주말과 평일을 나누는 기준인가? 를 모르겠다는 겁니다 ㅎ;; 시도해 보고 나니 깨달았네요, 그대신 얻은 게 있다면 중요도가 낮은 변수들을 가지고 군집 분석을 통해 파생변수를 시도 했다는 것입니다. 제가 알수 없는 날씨 데이터간의 의미를 알아보는 시간이었습니다. 제 생각이지만 예를들어, 해당 군집 변수를 통해서 비가 내렸는지 , 눈이 내렸는지, 매우 더웠는지 등 알수 있지 않을까 생각해 봅니다(뇌피셜ㅎㅎ;;) 아래는 제가 책 보면서 실습해본 내용들입니다. 여러 실험을 해봤는데 .. 그닥 유의미한 변수를 뽑아 내지 못해서 사용하진 않을 것같습니다 ㅠ

 

*KMeans

#Train

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples,silhouette_score

recent_value = float('-inf')
for i in range(2,20):
  kmean = KMeans(n_clusters=i,random_state=2021)
  kmean.fit(X_features_log)

  #실루엣 평가
  score_samples = silhouette_samples(X_features_log, kmean.labels_)

  #실루엣 평균값
  # np.mean(train['silhouette_coeff'].values)
  print('n_clusters:',i,"평균값",np.mean(score_samples))
  

  if recent_value < np.mean(score_samples):
    recent_value = np.mean(score_samples)
    train['cluster'] = kmean.labels_

 

n_clusters: 2 평균값 0.30259347606298753
n_clusters: 3 평균값 0.22084565811856927
n_clusters: 4 평균값 0.216332733050493
n_clusters: 5 평균값 0.20117735495570135
n_clusters: 6 평균값 0.18407185642824536
n_clusters: 7 평균값 0.17967102086096723
n_clusters: 8 평균값 0.18385313419854404
n_clusters: 9 평균값 0.18900846443879946
n_clusters: 10 평균값 0.192206439445478
n_clusters: 11 평균값 0.19231478574253635
n_clusters: 12 평균값 0.19283175071206277
n_clusters: 13 평균값 0.18663087129390746
n_clusters: 14 평균값 0.18418271434686048
n_clusters: 15 평균값 0.1842834634207479
n_clusters: 16 평균값 0.19119926923768746
n_clusters: 17 평균값 0.1923920182110372
n_clusters: 18 평균값 0.19543748089010413
n_clusters: 19 평균값 0.19708363804370685

 

train['cluster']

 

0       1
1       1
2       0
3       1
4       0
       ..
1454    0
1455    0
1456    0
1457    0
1458    0
Name: cluster, Length: 1459, dtype: int32

 

* MeanShift

from sklearn.cluster import MeanShift
from sklearn.cluster import estimate_bandwidth

best_bandwidth = estimate_bandwidth(X_features_log)

meanshift = MeanShift(bandwidth=best_bandwidth)
cluster_labels= meanshift.fit_predict(X_features_log)
train['cluster'] = cluster_labels 
print('cluster labels 유형:',np.unique(cluster_labels))
cluster labels 유형: [0 1]

 

*GMM(Gaussian Mixture Model)

from sklearn.mixture import GaussianMixture

gmm = GaussianMixture(n_components=2, random_state=2021).fit(X_features_scaled)
gmm_cluster_labels = gmm.predict(X_features_scaled)

train['cluster'] = gmm_cluster_labels
print('gmm labels 유형:',np.unique(gmm_cluster_labels))

gmm_test = GaussianMixture(n_components=2, random_state=2021).fit(X_features_test_scaled)
gmm_cluster_test_labels = gmm.predict(X_features_test_scaled)

test['cluster'] = gmm_cluster_test_labels
gmm labels 유형: [0 1]

 

*DSCAN

from sklearn.cluster import DBSCAN

dbscan = DBSCAN(eps = 0.4 , min_samples=12, metric='euclidean')
dbscan_labels = dbscan.fit_predict(X_features_log)

train['cluster'] = dbscan_labels
print('gmm labels 유형:',np.unique(dbscan_labels))
dbscan labels 유형: [-1  0  1]

-1로 군집된 것들은 군집이 되지 못한 값들입니다. 따라서 0과 1로 군집이 되었음을 알수 있습니다. -1이 있다고 해서 잘못 군집 된 것이 아니라고 합니다.

 

 

 

 

step 2) rfecv 사용한 최적 변수 선택

from sklearn.feature_selection import RFECV

model = xgb.XGBRegressor(gamma=0.239148938489844, max_depth=2, min_child_weight=1, n_estimators=200,n_jobs=-1)
rfecv = RFECV(estimator=model, step=1, scoring='neg_mean_squared_error')
rfecv.fit(X_train[x_columns], y_train)
rfecv.transform(X_train[x_columns])
rfecv_colname =[colname for idx,colname in enumerate(X_train.columns) if rfecv.ranking_[idx]==1]
print(rfecv_colname)

 

['hour', 'hour_bef_temperature', 'hour_bef_precipitation', 'hour_bef_humidity', 'hour_bef_visibility', 'hour_bef_ozone', 'hour_bef_pm10', 'cluster']

rfecv를 간략하게 말하자면, 변수 하나씩 모두 투입하여 가장 최적의 변수를 선택하는 기법입니다.

grid를 통해 가장 성능이 좋게 나왔던 모델로 rfecv를 진행했습니다. 그 결과 위 컬럼들을 알수 있었고 해당 컬럼만을 가지고 다시 돌려본 결과 좀 더 나빠졌습니다. 물론 데이콘에 직접 제출하기 전까지는 단정짓긴 힘들지만 하루에 제출 3번이 한계라서 막 제출 할수 없어서 피드백이 좀 아쉽습니다. 하여튼 성능개선을 보이지 않아 이 방법도 일단 보류하게 되었습니다.

 

 

 

Last 3) 원 핫인코딩 및 target log화

# 데이터 분리
x_train = train[[column for column in train.columns if column != 'count']]
y_train = train['count']

# target log
y_target_log = np.log1p(y_train)

#원핫 인코딩
x_train = pd.get_dummies(x_train, columns=['hour'])
test = pd.get_dummies(test, columns=['hour'])

X_train, X_test, y_train, y_test = train_test_split(x_train, y_target_log, test_size=0.3, random_state=2021)

target 변수가 전에 봤을때 살짝 비대칭 이었지만 정규분포이라는 통계량을 받았었습니다. 하지만 해보지 않으면 모르는법. 할수 있는 것들을 모두 해보자는 생각에 일단 target에 log화 시켜서 정규분포에 가깝게 만들었습니다. 그리고 기존에 피처 중요도에서 hour가 가장 크게 나왔습니다. hour가 0~23까지 있는데, 숫자형 값으로 입력하게 되면 해당 값에 크게 영향을 받게 되어서 피처 중요도가 높게 찍히게 됩니다. 카테고리 형은 원-핫 인코딩을 적용하여 변환해 줘야 합니다. 사실 이 부분을 간과하고 있었습니다..;

 

그래서 현재 target log 했을때의 성능 vs 원핫 + log 했을때의 성능

이 둘을 비교 하고 싶은데, 앞서 제출 기회 3번을 이미 쓴지라 제출 자료만 미리 만들어 놨습니다 

 

그런데, 확실히 원핫인코딩 한 후에 성능이 꽤 좋아진 걸 확인 할수 있었습니다. 제출해 봐야 알겠지만 자세한 내용은 아래를 참고하겠습니다.

#그리드 서치
models = [
    ('ridge', lm.Ridge()),
    ('lasso', lm.Lasso()),
    ('elastic', lm.ElasticNet()),
    ('LassoLars', lm.LassoLars()),
    # ('LogisticRegression', lm.LogisticRegression()),
    # ('SGDRegressor', lm.SGDRegressor()),
    ('xgboost', xgb.XGBRegressor()),
    ('lgb', lgb.LGBMRegressor()),
    ('knn', KNeighborsRegressor(n_jobs = -1) ),
    # ('rf', RandomForestRegressor(n_jobs = -1)),
    ('dt',  DecisionTreeRegressor()),
    ('ada', AdaBoostRegressor())
]

params = {
    'ridge': {
        'alpha': [0.01, 0.1, 1.0, 10, 100],
        'fit_intercept': [True, False],
        'normalize': [True, False],
    },
    'lasso': {
        'alpha': [0.1, 1.0, 10],
        'fit_intercept': [True, False],
        'normalize': [True, False],
    },
    'elastic': {
        'alpha': [0.1, 1.0, 10],
        'normalize': [True, False],
        'fit_intercept': [True, False],
    },
    'LassoLars': {
        'alpha': [0.1, 1.0, 10],
        'normalize': [True, False],
        'fit_intercept': [True, False],
    },
    # 'LogisticRegression': {
    #     'penalty': ['l1', 'l2'],
    #     'C': [0.01,0.1, 1.0, 10,100],
    #     'fit_intercept': [True, False],
    # },
    # 'SGDRegressor': {
    #     'penalty': ['l1', 'l2'],
    #     'alpha': [0.001, 0.01, 0.1, 1.0, 10, 100],
    #     'fit_intercept': [True, False],
    # },
    'xgboost': {
        "gamma": uniform(0, 0.5).rvs(3),
        "max_depth": range(2, 7), 
        "n_estimators": [100,200,300] 
    },
    'lgb': {
        "gamma": uniform(0, 0.5).rvs(3),
        "max_depth": range(2, 7), 
        "n_estimators": [100,200,300,400,500] 
    },
    'knn': {
        "n_neighbors": range(2,7),
        },
    # 'rf': {
    #     "max_depth": range(2, 5),
    #     "min_samples_split": range(2, 5),
    #     "min_samples_leaf": range(2, 5), 
    #     "n_estimators": [100,200,300],
    #     },
    'dt': {
        "max_depth": range(2, 5),
        "min_samples_split": range(2, 5),
        "min_samples_leaf": range(2, 5), 
        },
    'ada': {
        "n_estimators": [40,50,60]
    }
}

best_model, best_score = None, float('inf')
# x_columns = ['hour','hour_bef_temperature','hour_bef_precipitation']
x_columns = X_train.columns
for model_name, model in models:
    param_grid = params[model_name]
    grid = GridSearchCV(model, cv=5, n_jobs=-1, param_grid=param_grid)
    grid = grid.fit(X_train[x_columns], y_train)

    model = grid.best_estimator_
    predictions = model.predict(X_test[x_columns])

    y_test_exp = np.expm1(y_test)
    predictions_exp = np.expm1(predictions)
    score = evaluate(y_test_exp, predictions_exp)['mse'][0]

    print(model_name, score)

    if score < best_score:
        best_score=score
        best_model = model

 

ridge 1605.0139030409086
lasso 4164.706326162681
elastic 4106.425576729229
LassoLars 4164.84687453161
[12:29:45] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror.
xgboost 1796.044492222476
lgb 1829.1214004774263
knn 5546.570322907046
dt 3549.1672544535268
ada 6018.963056798382

해당 결과 값은 MSE입니다. 줄곧 베스트 모델이 xgb 였는데, 원핫 인코딩 하고나서는 성능도 개선 되면서 ridge가 가장 좋은 모델로 선택되었습니다. 

best_model
Ridge(alpha=0.01, copy_X=True, fit_intercept=True, max_iter=None,
      normalize=True, random_state=None, solver='auto', tol=0.001)

 

 

여기까지가 오늘 진도나간 부분입니다. 다음 시간에 더 개선된 내용을 가지고 업로드 하겠습니다.

 

 

 

728x90
728x90

이번 포스팅에서는 주로 weight 변화에 따라서 bias에 어떠한 영향을 미치는지를 중점적으로 살펴보도록 한다.

weight, bias가 3,3인 것으로 부터 mu, sigma의 변화에 따른 영향을 살펴본다.

 

*표준편차가 1이하 일 때

(0, std)를 따를 때, std가 0에 가까울수록 뽑히는 x의 값이 0에 가까운 값이 뽑히게 될 것이다. 예를 들어 x의 값이 0.1, 0.5처럼 1보다 작은 값들이 뽑히게 된다는 것이다. 저번 포스팅에서 배웠던 것처럼 x의 값이 1보다 작게 되면 weight가 학습되는 양 자체가 적어진다. 그리고 std =1 일때는 th1,th0이 균등하게 대각선 방향으로 학습이 된다. 위 왼쪽 그림의 보락색이 이에 해당 된다.

 

th1의 loss grdient 양은 -2*x*lr(y-pred)이 된다. 이때 입력되는 x 값이 소수점이라면 gradient의 양 자체가 감소하게 된다. 그래서 th1의 학습속도가 느려지게 된다. 즉, step by step으로 학습이 매번 이뤄질때마다 학습 되는 양이 적다는 얘기다.

 

왼쪽 그림의 보락색은 초반에 띄엄 띄엄 학습되는 걸 볼수 있다. 반면 파란색은 매 학습 step마다 촘촘히 학습되고 있다. 이는 곧 "학습되는 양" 자체가 적어서 업데이트도 적게 이뤄진다는 걸 알수 있다.

오른쪽 th0의 학습 그래프를 보면, 학습 되는 속도가 std 변화에 따라서 크게 영향을 끼치지 않는다. std가 작아지든 커지든 th0의 학습에는 영향을 주지 않는 것인가? th0의 gradient를 보면 -2*lr*(y-pred) , x가 빠져 있다. 즉 x에 영향을 받지 않는 다는 것이다. 왼쪽 그림은 th1이 혼자 느려질때의 th1,th0의 그래프이고, th0만 따로 떼어 보면 오른쪽 처럼 균일한 학습속도를 보이게 되는 것이다.

 

 

*표준편차가 1이상 일때

반대로 std가 1보다 큰 값이 들어오게 된다면, th1의 gradient 값이 th0보다 많아 지기 때문에 왼쪽 그래프 처럼 th1에 지배적인 학습이 먼저 이뤄진다. gradient가 많아지게 되면 발생하는 대표적인 문제점이 하나 있다. 바로 발산이다. 그리고 발산하기 직전에는 지그재그로 왔다갔다 하게 된다. 왼쪽 그림에서 std가 가장 높은 점들을 보면 지그재그로 왔다갔다 학습이 이뤄지고 있다. 지그잭로 학습 되기 때문에 당연히 전반적인 학습 속도가 느리게 된다.

 

오른쪽 그림의 th0은 마찬가지로 gradient에서 x가 영향을 끼치지 않으므로 gradient의 양 변동이 크지 않다. 오직 th1의 gradient가 커졌다 작아졌다 할뿐이다.

가장 이성적인 학습속도는 어떤 것인가?? 바로 th1,th0이 "동시에" 학습이 완료되는 것이다. 마치 std=1일때 처럼 말이다.

 

정리를 하자면(std의 변화에 따른 결론이다)

1. std의 값이 1이하 일 때

-> th1의 gradient 양이 "작아서" 학습 속도가 느려진다.

2. std의 값이 1 이상 일 때

-> th1의 gradient 양이 커져서 "지그재그로 학습되어" 속도가 느려진다.

 

 

*mean 변화량에 따른 학습 영향

mean값이 커질수록 loss function은 수직으로 세워지게 된다.(앞서 학습 내용과 겹치므로 자세한 내용 생략) 따라서 mean이 높을수록 더욱 지그재그로 학습이 많이 이뤄진다. mean 0일 때와 5일 때를 비교해보자. th0의 학습 속도에 차이가 난다.

 

왜일까? mean =5이기 때문에 지그재그로 더 많이 학습이 되기 때문이다. 쉽게 말하자면 빨리 갈 수 있는 길을 내버려두고 돌고 돌아서 가기 때문에 목표지점에 도달하는 시간이 더 길어지는 것이다.

그런데, 오른쪽 그림을 보면 아까와는 다른 그래프 모양을 가진다. 왜일까? "지그재그"때문이다. 지그재그 할 때는 증감을 반복하기 때문이다. th0 입장에서 왼쪽 하늘색을 보면 증감의 반복을 볼수있다. 이런 현상이 왼쪽 그래프 반영된 것이다.

이러한 현상때문에 bias은 mean에 대해서 아주 큰 영향을 받게 된다. 따라서 input값들을 평균 0으로 맞춰 주는게 중요하다. 0으로 맞추면 std가 크든 작든 좌우 대칭으로 dataset이 뽑히기 때문에 위와 같은 악영향을 줄일수 있다.

 

이해를 돕기 위해서 위 학습 내용을 벡터로 표현해본다. 단, 그래프 표현을 위해서 벡터의 크기는 1로 통일한다 (gradient의 학습 양을 벡터로 그대로 표현하면 그래프로 보기가 힘들기 때문이다)

 

 

*벡터로 표현한 학습

오른쪽 벡터의 중심점이 본래 가지고 있던 점이다. 즉, 출발점이다. 벡터가 가리키는 것이 학습의 방향성이다. 왼쪽의 학습 방향을 벡터로 표현한 것이 오른쪽이다.

 

 

 

*std를 높힌 경우

std를 높힌다는 것은 x의 절대값이 커진다는 것이고, losss function contour plot이 y축으로 기울어 진다는 것이다.

오른쪽 그림를 보면 양쪽 좌우에 벡터가 뭉쳐 있다. 반면 상대적으로 가운데 벡터는 적다. 왜그럴까? std가 커진다는건 x의 절대값이 커진다는 것이다. 예를들어 -3,-5,3,4 이러한 x가 반영된다. 1보다 큰 x값들은 y축으로 기울진 즉, 수직에 가까운 loss function을 갖게 된다. 이러한 loss function에 projection되는 방향으로 학습이 되기 때문에 벡터 좌우에 몰리게 되고, 가운데 분포는 적게 된다. 좌우로 쏠린 벡터들이 서로 상쇄가 되면서 학습된다. 즉, 평균이 0이고, std만 달라져도 서로 대칭되는 값들이 뽑히기 때문에 서로 상쇄가 되어서 학습에 큰 영향을 주지 않게 된다. 진짜 문제는 평균이 0이 아닐때 발생하게 되는것이다.

 

참고로 가운데 벡터는 x축과 평행에 가까운 loss function에 projection되는 방향이다. 즉, x의 값이 1보다 작은 값들이 나올 때 이러한 벡터 방향이 나온다.

 

*mean를 높인 경우

mean을 키우게 되면, 전반적인 x의 값들을 키운 것과 같다. mean을 키운 순간 대각선 방향으로의 th1,th0 학습을 하지 못하게 된다. 어느 한쪽으로 쏠리게 되는데, 이 경우는 양수에 치우친 x값들이 생기게 된다. 따라서 왼쪽 하단에 분포가 많이 생기게 된다.

 

또한 (1,1)이므로 0에 가까운 loss function도 많이 만들어질 것이다. 그래서 오른쪽 상단 벡터에도 분포가 많이 생기게 된다. 즉, 왼쪽 오른쪽 분포가 많으므로 지그재그 형태를 띠게 된다.

mean더 키우게 된다는 것은 y축과 더욱 평행한 loss function이 만들어진다는 것이며, 사실상 음수 loss function이 만들어지지 않는다고도 볼 수 있다.

지금까지는 데이터 하나를 가지고 loss function의 변화를 봤다면, 지금부터는 여러 개의 데이터 샘플을 사용한 cost의 변화를 보도록 하자.

가장 이상적인 모습을 보인 건 전체적인 데이터 특성을 제일 잘 나타낸 배치 사이즈 32이다. 배치 사이즈가 커지면 아웃라이어의 영향을 줄일 수 있다. 그래서 배치 2를 보면 다른 것에 비해 불규칙적으로 움직인 걸 볼 수 있다.

 

 

*std 변화에 따른 영향

std =5 일 때의 그래프이다. 배치가 2인 파란색이 확실히 변동이 있는 반면, 배치가 32인 빨간색은 안정적으로 학습됨을 볼 수 있다.

 

 

*mean 변화에 따른 영향

mean 자체가 커지면서 지그재그 파동이 많아짐을 볼 수 있다. cost라도 mean이 커지면 지그재그 현상이 없어지지 않는다.

 

 

* std 변화에 따라서 벡터의 모양을 비교해 보록 하자

배치가 2일 땐 데이터 샘플 하나를 이용했을 때와 별 차이점이 없다. 그래서 좌우로 지그재그로 학습이 되기 때문에 벡터에도 좌우에 많은 분포를 가진다.

배치가 커질수록 벡터의 얖옆이 줄어들면서 가운데에 분포를 하게 된다. 다시 말해서 좌우로 가는 벡터가 적어짐에 따라 지그재그 현상이 감소하게 된다.

 

 

* mean변화에 따라서 벡터의 모양을 비교해 보록 하자

mean의 변화에 따른 벡터의 모양을 보면 비슷함을 알 수 있다. 특히 좌우 벡터의 양이 std 변화에 따른 벡터처럼 줄어들지가 않는다. 왜일까?

 

mean 자체를 증가시켰기 때문에 std=1이든 2이든 어찌 됐든 x가 큰 값이 input으로 들어가게 된다. mean이 커지면 더 이상 표준 정규분포가 아니게 된다. 위 그래프의 학습 cost function 모양이 대략  \일 것이다.

 

아무리 32개를 뽑고 cost를 계산해봐도 \과 비슷한 cost function을 만들게 된다. 또한 x의 값이 커지면 발산의 우려가 있다.  그 전초기 증상이 바로 지그재그이다. 이를 막기 위해서 lr의 조정이 필요한 것이다.

728x90
728x90

지난 포스팅에 이어서 오늘은 모델링을 만들어 봤습니다. 

#그리드 서치
models = [
    ('ridge', lm.Ridge()),
    ('lasso', lm.Lasso()),
    ('elastic', lm.ElasticNet()),
    ('LassoLars', lm.LassoLars()),
    ('LogisticRegression', lm.LogisticRegression()),
    ('SGDRegressor', lm.SGDRegressor()),
    ('Perceptron', lm.Perceptron(n_jobs=-1)),
    ('xgboost', xgb.XGBRegressor()),
    ('lgb', lgb.LGBMRegressor()),
    ('knn', KNeighborsRegressor(n_jobs = -1) ),
    ('rf', RandomForestRegressor(n_jobs = -1)),
    ('dt',  DecisionTreeRegressor()),
    ('ada', AdaBoostRegressor())
]

params = {
    'ridge': {
        'alpha': [0.01, 0.1, 1.0, 10, 100],
        'fit_intercept': [True, False],
        'normalize': [True, False],
    },
    'lasso': {
        'alpha': [0.1, 1.0, 10],
        'fit_intercept': [True, False],
        'normalize': [True, False],
    },
    'elastic': {
        'alpha': [0.1, 1.0, 10],
        'normalize': [True, False],
        'fit_intercept': [True, False],
    },
    'LassoLars': {
        'alpha': [0.1, 1.0, 10],
        'normalize': [True, False],
        'fit_intercept': [True, False],
    },
    'LogisticRegression': {
        'penalty': ['l1', 'l2'],
        'C': [0.01,0.1, 1.0, 10,100],
        'fit_intercept': [True, False],
    },
    'SGDRegressor': {
        'penalty': ['l1', 'l2'],
        'alpha': [0.001, 0.01, 0.1, 1.0, 10, 100],
        'fit_intercept': [True, False],
    },
    'Perceptron' :{
        'penalty': ['None', 'l1', 'l2'],
        'alpha': [0.001, 0.01, 0.1, 1.0, 10, 100],
        'fit_intercept': [True, False]
    },
    'xgboost': {
        "gamma": uniform(0, 0.5).rvs(3),
        "max_depth": range(2, 7), 
        "n_estimators": [100,200,300] 
    },
    'lgb': {
        "gamma": uniform(0, 0.5).rvs(3),
        "max_depth": range(2, 7), 
        "n_estimators": [100,200,300] 
    },
    'knn': {
        "n_neighbors": range(2,7),
        },
    'rf': {
        "max_depth": range(2, 5),
        "min_samples_split": range(2, 5),
        "min_samples_leaf": range(2, 5), 
        "n_estimators": [100,200,300],
        },
    'dt': {
        "max_depth": range(2, 5),
        "min_samples_split": range(2, 5),
        "min_samples_leaf": range(2, 5), 
        },
    'ada': {
        "n_estimators": [40,50,60]
    }
}

best_model, best_mae = None, float('inf')
x_columns = ['hour','hour_bef_temperature','hour_bef_precipitation']
for model_name, model in models:
    param_grid = params[model_name]
    grid = GridSearchCV(model, cv=5, n_jobs=-1, param_grid=param_grid)
    grid = grid.fit(X_train[x_columns], y_train)

    model = grid.best_estimator_
    predictions = model.predict(X_test[x_columns])
    mae = mean_absolute_error(y_test, predictions)

    print(model_name, mae)

    if mae < best_mae:
        best_mae=mae
        best_model = model
ridge 40.31602129541864
lasso 40.30855653538877
elastic 41.252566307860604
LassoLars 40.30853364226131
LogisticRegression 51.55936073059361
SGDRegressor 40.257925028487605
Perceptron 93.32876712328768
[10:07:54] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror.
xgboost 27.404816101675163
lgb 27.910991375973378
knn 27.974885844748858
rf 30.92272982406107
dt 32.59905879619483
ada 34.54503408876004

 

가장 좋은 성능을 보인 xgboost로 test 데이터 predict 제출한 결과, 

 

37.03 점수를 받았습니다. 참고로 리더보드 1등 점수는 29.31입니다.

53점을 받은 pred는 모든 변수를 넣은 결과 입니다. VIF와 xgb의 feature 중요도를 본 경과 변수 세 개만 이용해도 될것 같아서 변수의 수 만 조절했는데 53->37로 성능이 좋아졌습니다. 

 

위에 해당하는 모델 말고도 CV를 기반한 스태킹 코드를 미리 짜놨는데, 스태킹을 해도 성능이 획기적으로 줄어 들것같진 않더라구요. 그래서 스케일링도 해보고, 이것저것 시도 중입니다. 

 

변수가 한정적이라서 어떻게 뭘 더 해야할지 고민 중입니다. 타켓값의 분포를 보면 계절성은 있어 보였습니다. 

 

그래서 계절성 성분을 뽑아서 변수로 넣을 고민 중에 있습니다. 받은 데이터 시간순으로 되어 있지 않아서 이를 어떻게 적용할지 감이 확 오진 않더라구요.

 

target 분포가 정규분포가 아니면 log을 사용해볼려고 했는데, 그래프상 약간 왼쪽으로 치우친 것 같지만 통계량 결과 정규분포라고 나왔어요, log를 씌운 것과 비교 했을때 그닥 차이가 없어 보여서 제외 시켰습니다 .ㅠ

 

앞서 언급한 스케일링도, 하기 전/후의 성능이 고만고만했습니다. 종류별로 다 돌려 봤는데 거의 비슷했습니다. 

구글링하면서 다른 분들은 어떻게 했는지 찾아 보고 있는데, 저와는 데이터 자체가 달라서 인사이트를 얻기가 쉽진 않았어요 

 

많이 알수록 많은 걸 볼수 있는데 이럴때면 참 갈길이 멀구나 생각이 드네요 ㅎㅎ;

728x90
728x90

저번 포스팅까지는 하나의 데이터에 대한 학습과정을 공부했습니다. 이번 시간부터는 미니배치 즉, 여러개 샘플을 가지고 학습을 시키는 방법에 대해 공부한 내용을 포스팅하겠습니다. 2가지로 나눠서 진행할 예정입니다.

 

1. 두개의 sample에 대해서 cost function을 이용한 업데이트 방법

2. vector notation을 이용한 방법 (n개의 미니배치가 있을 경우의 업데이트 방법)

 

*실습링크

2021.09.04 - [실습 note] - 딥러닝 수학 실습과제 모음 9~12

 

딥러닝 수학 실습과제 모음 9~12

실습파일이 없는 줄 알았는데 있더라구요 그래서 쌓여 있는 실습을 진행했습니다. 각 구분선에 따라 이론내용과 실습을 분리했습니다. 순서는 맨 위부터 아래 순입니다. 2021.08.19 - [Data 일기] - 20

ghdrldud329.tistory.com

 

1. 두개의 sample에 대해서 cost function을 이용한 업데이트 방법

Loss 값들을 모두 평균내는 cost 부분을 미분하면 1/n이다. 만일 2개의 x data가 있다고 한다면 2개의 Loss값이 나오고 1/2씩 값을 가져와서 cost를 계산하게 된다. 이해를 돕기 위해 아래 그림을 참고해보자.

각 구해진 Loss 값에서 1/2씩 전달되어 cost가 만들어진다. backward 일때도 같은 원리로 과정을 거치게 된다.

데이터가 하나일때와 다른점은 1/2이 추가 된것 뿐이다. 미분은 내가 어느정도 영향을 끼쳤는지를 볼때 사용된다. forward 할때 1/2씩 각 loss값에서 cost로 전달이 되었고, 이를 다시 backward를 진행할 땐 내가 주었던 1/2을 "다시 받는 것이다." cost의 backward는 "공평하게 값을 나눈다."

다음으로 추가적인 핵심 부분은 아래 그림과 같다.

현재 th1,th0 두개가 있을 경우 backward의 진행을 살펴보고 있다. 초록색 수식을 보면 th1,th0 각각에 할당되는 값을 볼수있다. 두개 모두 "1" 이다. 즉, 앞전에 진행된 값을 "그대로" th1,th0에 전달하게 되는 것이다. 파란색 수식을 보면 값이 같다는 걸 알수 있다. 딱 여기까지가 bias term이 cost에 얼마나 영향을 주었는지를 알기 위해 편미분 값을 구한 것이다. 참고로 위 그림에서 검정색 글씨 th1이 두개가 있는데 왼쪽에 있는 것이 th1, 뒤에 있는 것이 th0이다.(강의 자료 오류)

정리를 하게 되면 아래와 같다.

각각 어떤 X data sample을 뽑느냐에 따라 수식은 같지만 값이 완전히 달라질수 있다. 이를 명심해야한다. cost는 각각 다른 이 두개를 섞어 주면 되는 것이다. 아래 수식을 통해 확인해 보자.

최종 수식의 의미를 본다면 "각각의 Loss을 더한 뒤 1/n만큼 나눈다" 이다. 이는 cost와 같은 의미이다.

th1, th0 각각의 평균값이 cost 값으로 이용이 된다. cost를 사용하게 되면 데이터들의 "전반적인 특성"을 반영하다 보니까 개별의 gradient 특성을 파악하는데 힘들수가 있게 된다. (배치 사이즈를 작게 하면 해결가능하다) 반면 얻는 장점은 튀는 값들을 surpress 해줌으로써 안정적인 학습을 가능케 한다.

 

 

2. vector notation을 이용한 방법 (n개의 미니배치가 있을 경우의 업데이트 방법)

x가 n개가 있을때 이를 jacobian metrices로 아래처럼 표현할수 있다.

n개에 대한 함수에 대해서 각각 th1의 편미분 값을 구할수 있다. 다른 편미분 값들도 보자면 아래처럼 표현할수 있겠다.

위는 벡터 function에 대한 벡터 독립변수의 편미분값을 구하는 과정이다. 행과열이 n개이므로 n*n 매트릭스가 된다. 위 내용 중 th1,th0으로 가는 편미분 값은 1이기 때문에 앞서 받았던 편미분을 그대로 전달 한다고 배웠다. 그때와 지금 다른 점은 매트릭스라는 형태이다. 항등행렬을 곱하므로써 자기 자신을 그대로 전달하는 역할을 하게 된다. th0도 마찬가지고 편미분 값이 모두 1로 나옴으로써 앞서 계산된 편미분 값을 그대로 받게된다. plus_node의 특징이다.

마이너스에 대해서 편미분을 구하게 되기 때문에 값이 모두 마이너스로 계산되는 특징을 가진다.

스칼라 fuuction에 대해서 벡터에 대한 미분을 해준다는게 특징이다.

앞으로 강의에서는 해당 수식들을 vec(편미분)으로 표현 해줌으로써 vector 표현을 변경하였다. 이를 활용하여 어떻게 backward가 진행되는지 자세한 과정은 생략하도록 한다.

정리를 하게 되면 아래와 같다.

Implementation

기존과 달라진 점은 vector로 연산이 된다는 점 이외에는 배웠던 내용과 같으므로 자세한 설명은 생략해도 될것같다.

8개씩 나뉜 평균값 cost를 사용하므로 위 그림처럼 부드러운 곡선을 나타내며 학습이 진행된다.

728x90
728x90

저번 포스팅에서 언급했던 결측치 문제를 오늘 해결했습니다. 결과만 보면 이렇게 간단한데 그 과정을 알기까지가 힘드네요. 아래가 해결한 코드입니다.

def nan_fill(dataset):
  #결측치가 있는 컬럼
  nan_columns = [col for col in dataset.iloc[:,2:-1].columns.to_list() if col!='hour_bef_precipitation']

  #for 문을 통해 결측치를 각 시간대별 평균값으로 대체
  for col in nan_columns:
    hour_mean_value = dataset.groupby('hour').mean()[col]
    hour_mean_value.fillna(hour_mean_value.mean(),inplace=True) #회귀를 통해서 값 채우기 시도

    for nan_hour in dataset[dataset[col].isna()]['hour'].unique():
      
      #nan index 구하기
      index= dataset[dataset[col].isna()].loc[dataset['hour']==nan_hour,col].index

      #채우기 
      for idx in index:
        dataset.loc[idx, col] = hour_mean_value[nan_hour]

  #hour_bef_precipitation 결측치 삭제
  dataset.dropna(inplace=True)
  print(dataset.isna().sum())
  return dataset
train =  nan_fill(train)
test= nan_fill(test)
id                        0
hour                      0
hour_bef_temperature      0
hour_bef_precipitation    0
hour_bef_windspeed        0
hour_bef_humidity         0
hour_bef_visibility       0
hour_bef_ozone            0
hour_bef_pm10             0
hour_bef_pm2.5            0
count                     0
dtype: int64
id                        0
hour                      0
hour_bef_temperature      0
hour_bef_precipitation    0
hour_bef_windspeed        0
hour_bef_humidity         0
hour_bef_visibility       0
hour_bef_ozone            0
hour_bef_pm10             0
hour_bef_pm2.5            0

 

위 함수를 통해 각 시간별 평균값을 대체값으로 활용할수 있었습니다. 

 

VIF를 통해 다중공선성을 확인해 봤습니다. 

한 시간전 온도가 다중공선성이 있는 걸로 계산 되었습니다. 온도가 타켓변수와 상관성이 높게 나왔기 때문에 무작정 제거 하기 보다는 상관성과 모델링 비교를 통해서 결정해야 할것같습니다.

 

다음시간에는 FE에 대해 좀더 색다른 방법은 없는지 조사해 보면서 진도 나갈것같습니다.

728x90

+ Recent posts