[변수 생성] AutoEncoder로 파생변수 만들기

2019. 6. 2. 12:37분석 Python/Data Preprocessing

728x90

데이터 분석을 하다보면, 새로운 파생변수를 만들어야 할 때가 있다.

 

개인적으로 나도 그러한 부분에 관심이 있어서 여래개로 포스팅을 했는데, 한번 보시면 도움이 될 것 같다.

https://data-newbie.tistory.com/148?category=749566 https://data-newbie.tistory.com/93?category=749566  https://data-newbie.tistory.com/93?category=749566

 

그렇다면 결국 도메인이 중요한 것이고 즉 협엽이 잘 될 때 Feature Engineering도 빛을 발하게 되는데,

사실 현실은 그렇지 못하다.

만약 자기 데이터를 가지고 할 경우, 그러한 부분에 대해서 충분히 고려하고, 자주 데이터를 만질 수도 있지만,

만약 데이터를 끌어다 쓰는 경우에는 주어진 것 내에서 먼가 새로운 것을 만들어야 하기 때문에 참 어렵다.

 

그럴 때 어떤 특정 통계량값들(평균 , 편차 , 중앙값) 같은 것들을 사용할 것이다.

 

개인적으로 저런것을 넣을 때도 모델의 성능이 높아지는 경우가 있긴 하지만, 좀 더 현재 AI시대에 맞게

AutoFeature Engineering 방법을 좀 더 선호하게 된다.

 

그래서 위에 있는 글들 중 대부분은 어떤 방법으로 뽑을 수 있는지에 대한 글이다.

 

이번 글에서는 AutoEncoder로 데이터에 대한 새로운 파생변수를 뽑는 것에 대해서 이야기 하고자 한다.

개인적으로 거의 AutoEncoder가 만병통치약처럼 많이 쓰이고 있다.

데이터를 압축하고 싶을 때 쓰거나 먼가 노이즈를 제거하는 Latent를 배우던지, 아니면 생성 모델에 쓰던지

등등으로 귀에 걸면 귀걸이 코에 걸면 코걸이처럼, 내 개인적으로 생각으로는 어디든 갖다가 쓸 수 있는 참 좋은 알고리즘 방식인 것 같다.

 

이 알고리즘은 엄청 다양하게 쓰이는데 여기서는 다음과 같은 방법으로 쓰려고 한다.

 

  • 기존 인풋에서 Code로 Encoding을 시켜준다.
  • 그리고 그 Code를 사용해서 다시 복원을 시켜준다.
  • 즉 Encoder 가 잘 학습해서 좋은 Code를 만들고, 그 좋은 Code를 다시 Decoder로 태웠을 때, 복원이 된다면,
  • 이 Code는 Input에 대해서 중요한 정보를 낮은 차원에서 잘 함축시켰다고 볼 수 있다

 

사실 저 Code를 참 여러가지 AAE는 Denosie AE, VAE 같이 큰 차원에서 놀기 어려우니 낮은 차원으로 낮춰서

가지고 놀기가 참 좋고 유용하면서 중요한 것 같다.

 

그래서 나는 이러한 중요한 Code를 데이터를 잘 합축한 중요한 요소로 보고,

이것을 새로운 파생변수로 써보려고 한다.

이번 포스터에서는 만드는 과정까지만 설명드리고 다음 포스트에서는 그것을 넣었을 때 실제로 유용할지에 대해서 이야기 해보려고 한다

 

데이터는 이것을 GAIN을 사용해서 결측치를 대체 후 사용했다.

https://www.kaggle.com/jsphyg/weather-dataset-rattle-package

 

항상 주의하게 생각해야하는 것은

Test Data는 우리가 완전히 새로 본 것을 가정으로 해야한다는 것!

그러므로 항상 Train기준으로 한 것을 Test Data에 적용을 해야한다.

Scaling도 Onehot도 기준은 Train 기준

기대하는 바는 Train과 Test는 전체적인 분포 형태를 봤을 때 같은 분포에 속할 것이라는 가정

https://data-newbie.tistory.com/96?category=749566

 

Overfit? or dissimilar train and test? (medium 번역 및 생각)

https://towardsdatascience.com/how-dis-similar-are-my-train-and-test-data-56af3923de9b How (dis)similar are my train and test data? Understanding a scenario where your machine learning model can fai..

data-newbie.tistory.com

## Data Handling

## 시간 처리
from datetime import datetime
year = lambda x: datetime.strptime(x, "%Y-%m-%d" ).year
day_of_week = lambda x: datetime.strptime(x, "%Y-%m-%d" ).weekday()
month = lambda x: datetime.strptime(x, "%Y-%m-%d" ).month
week_number = lambda x: datetime.strptime(x, "%Y-%m-%d" ).strftime('%V')
data['year'] = data['Date'].map(year)
data["day_of_week"] = data["Date"].map(day_of_week)
data['month'] = data['Date'].map(month)
data["week_number"] = data["Date"].map(week_number)
data = data.drop(["RISK_MM"], axis = 1) # 'Location'  'Date' 


## 수가공적은 Feature Engineering 추가
def new_add(data) :
    newdata = data.groupby(['Location','month', "day_of_week"], group_keys=False).\
    agg({"Sunshine" : ["mean","std"],"Humidity3pm" :["mean","std"] , "Pressure3pm" :["mean","std"] }).reset_index()
    newdata.columns = ["Location", 'month', "day_of_week" , 
                       "Shunshine_mean","Shunshine_std",
                       "Humidity3pm_mean", "Humidity3pm_std",
                       "Pressure3pm_mean", "Pressure3pm_std"]
    data = pd.merge(data, newdata, how='left')
    return data

new_train = new_add(train)
new_test = new_add(test)

## 제거할 변수와 X , y 분리하기.
y = "RainTomorrow"
train_y = new_train[y]
new_train = new_train.drop([y], axis = 1)
test_y = new_test[y]
new_test = new_test.drop([y], axis = 1)
remove_var = ["Date"]
new_train = new_train.drop(remove_var , axis = 1)
new_test  = new_test.drop(remove_var  , axis = 1)
object_var = new_train.select_dtypes("object").columns.tolist()
numeric_var = list(set(new_train) - set(object_var)) 


## categorical 변수에 대해서 onehot 해주기
from sklearn.preprocessing import LabelBinarizer
trainxx = np.array([], ).reshape(len(new_train) ,0)
testxx = np.array([], ).reshape(len(new_test),0)
for e in object_var :
    lb = LabelBinarizer()
    xx = new_train[e].values.astype(str)
    lb.fit(xx)
    xx2 = lb.transform(xx)
    print(xx2.shape, trainxx.shape)
    trainxx = np.concatenate((trainxx , xx2), axis = 1)
    xx2 = lb.transform(new_test[e].values.astype(str))
    testxx = np.concatenate((testxx , xx2), axis = 1)
    
new_train = new_train.drop(object_var , axis = 1)
new_test  = new_test.drop(object_var  , axis = 1)
new_train = pd.concat([pd.DataFrame(trainxx), new_train],axis = 1)
new_test = pd.concat([pd.DataFrame(testxx), new_test],axis = 1)
### 여긴 꼭 주의!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

scaler = StandardScaler()
scaler.fit(new_train[numeric_var].values)
new_train[numeric_var] = scaler.transform(new_train[numeric_var].values)
new_test[numeric_var] = scaler.transform(new_test[numeric_var].values)
new_test.fillna(new_test.mean(axis=0), inplace=True) 

## AutoEncoder 모델링

 

## 아주 간단한 모델링 목적은 파생변수니까, 목숨걸고 할 이유가 없다고 생각

input_ = tf.placeholder(tf.float32 , [None , X.shape[1]])
E_layer = tf.layers.dense(input_ , units=int(X.shape[1]/2) , activation= tf.nn.sigmoid )
encoding = tf.layers.dense(E_layer , int(X.shape[1]/8) , activation= tf.nn.sigmoid )
D_layer = tf.layers.dense(encoding , int(X.shape[1]/2) , activation= tf.nn.sigmoid )
Decoding = tf.layers.dense(D_layer , X.shape[1] , activation= None)
cost = tf.reduce_mean(tf.pow( input_ - Decoding , 2))
optimizer = tf.train.RMSPropOptimizer(0.001).minimize(cost)


## modeling
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
batch_size = 100
output = pd.DataFrame({"iter" : [0] , "train_loss" : [0] , "test_loss" : [0] })
for epoch in range(5000) :
    total_cost = 0
    batch_idx = np.arange(X.shape[0])
    np.random.shuffle(batch_idx)
    for n in range( 0 , (X.shape[0]// batch_size) * batch_size , batch_size) :
        idx = batch_idx[n:n+batch_size]
        batch_inputs = X[idx,:]
        _ , mse_loss = sess.run([optimizer ,cost] ,
                                feed_dict = {input_ : batch_inputs})
        total_cost += mse_loss
    avg_loss = total_cost / (X.shape[0]// batch_size)
    test_mse = sess.run(cost , feed_dict = {input_ : test_X})
    if output.test_loss.min() > test_mse :
        print(output.test_loss.min() ,  test_mse)
        train_encoding = sess.run(encoding , feed_dict={input_ : X})
        test_encoding = sess.run(encoding , feed_dict={input_ : test_X})
        encoding_var = np.concatenate((np.array(train_encoding) , 
                                       np.array(test_encoding)), axis = 0)
        np.save("AutoEncoder_Latent.npy" , encoding_var )
    output1 = pd.DataFrame({"iter" : [epoch] , "train_loss" : [avg_loss] , "test_loss" : [test_mse] })
    output  = output.append(output1)
    clear_output(wait= True)
    fig , ax = plt.subplots(figsize = (26,13))
    fig.subplots_adjust(top = 0.95 , left = 0.03 , bottom = 0.04 , right = 0.99)
    output = output[output.iter>0]
    ax.plot(output.iter , output.train_loss , label ="avg_train_loss" , linestyle ="-" , marker ="." , linewidth = 4, markersize = 12)
    ax.plot(output.iter , output.test_loss , label ="test_loss" , linestyle ="-" , marker ="." , linewidth = 4, markersize = 12)
    chr_ = "Epoch : {} , Train AVG MSE Loss : {:.4f} , Test MSE : {:.4f}".format(epoch , avg_loss , test_mse )
    print(chr_)
    ax.set_title(chr_, fontsize= 30)
    box = ax.get_position()
    ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9])
    ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.05), fancybox=True, shadow=True, ncol=4 , fontsize= 20)    
    plt.savefig("autoencoder_loss.png")
    plt.show()

 

현재 계속 학습중이라서 아직 결과를 적용해보지는 않았지만, 잘 떨어지고 있으니, 먼가 유용한게 나왔을 것이라 기대함

 

-끝-

이제 모델링을 통해서 실제 저 변수가 유의미한지를 체크해보자.

https://data-newbie.tistory.com/165

 

AutoEncoder로 파생변수 만들기 -2 (모델링 파트) Catboost

https://data-newbie.tistory.com/163 AutoEncoder로 파생변수 만들기 데이터 분석을 하다보면, 새로운 파생변수를 만들어야 할 때가 있다. 개인적으로 나도 그러한 부분에 관심이 있어서 여래개로 포스팅을 했는..

data-newbie.tistory.com

 

 

https://www.youtube.com/watch?v=Vzoi-yNgnAY

 

728x90