데이터과학 삼학년

Ch.5 Categorical Variables: Counting Eggs in theAge of Robotic Chickens 본문

Feature Engineering

Ch.5 Categorical Variables: Counting Eggs in theAge of Robotic Chickens

Dan-k 2020. 4. 14. 17:28
반응형

범주형 변수 (Categorical Variables)

  • 카테고리나 label을 나타내기 위해 사용되는 변수

  • Ex. 도시 이름, 성별, 요일 등

  • Nonordinal

범주형 변수 인코딩(Encoding Categorical Variables)

  • 범주형 변수들의 범주(categories)들은 일반적으로 숫자가 아님

  • 숫자가 아닌 범주들을 숫자로 치환시키는 인코딩 기법이 필요

  • 가장 단순한 아이디어 : 1~k까지의 숫자로 치환

    • Orderable해져서 안됨

One-Hot Encoding

  • 비트의 그룹을 사용

  • 변수가 동시에 여러 categories에 속할 수 없을 때 사용

  • e_1+e_2+e_3+...+e_k = 1

Dummy Coding

  • 원 핫 인코딩에서 비트 하나를 골라 제거하여 자유도를 하나 줄인 것

Effect Coding

  • 더미 코딩에서 기준 범주를 (예제에서 NYC) 0대신 -1로 표현

  • 더미 코딩의 Intercept와 Coef를 원 핫 인코딩의 Intercept와 Coef로 바꿔주기 위함

각 범주형 변수 인코딩의 장단점

  • 어차피 셋 다 비슷함

  • 범주의 수가 많아지면 성능이 떨어짐

  • 원 핫 인코딩 : 불필요한 자유도로 인해 동일 문제에 대해 여러 개의 모델 형성

  • 더미 코딩 : 각 범주의 효과를 기준 범주를 기준으로 인코딩해 해석이 귀찮음

  • 이펙트 코딩 : 모든 값이 -1인 벡터가 dense vector라서 저장과 계산에 비용이 많이 듬

대규모 범주형 변수 처리

  • 메모리 효율적이고 학습이 빠르면서 정확한 모델을 만들 수 있는 피처를 찾아야 한다

  • 과적합 문제가 생길 수 있음

Feature Hashing

  • Hash function

    • Z -> [1,m]으로 매핑시켜주는 함수

    • 입력 크기 > 출력 크기 이기 때문에, 여러 값이 동일한 출력에 매핑됨 : Collision

    • 여러 피처들을 하나의 출력값으로 뭉쳐놓기 때문에, 해시처리 된 피처를 해석할 수 없다는 단점이 있음

    • Scikit-learn에 있는 FeatureHasher를 사용해서 구현

Bin counting

  • 범주형 변수의 값을 피쳐로 사용하는 대신, 목표 변수에 대한 통계량을 피쳐로 사용한다.

  • ex) 광고 클릭 확률을 계산하기 위해 범주형 변수 ‘사용자 : Alice’ 대신 ‘Alice가 클릭한 확률’을 피쳐로 사용한다는 것.

  • 당연한 이야기지만, 기존에 누적된 데이터를 통해 미리 정해진 통계량이 계산되어 있어야한다.

  • 사용되는 통계량 : 조건부 확률, 오즈비(odds-ratio), 로그 오즈비 등

  • N+ : 조건부 확률, N- : 1- 조건부 확률, log_N+ : 오즈비

  • 용량이 7300031 -> 525697로 크게 줄어들었음.

 

요약

  1. 원 핫 인코딩 (n : 데이터 포인트 수, k : 범주 수)

  • 공간 요구 : O(n)

  • 계산 요구 : O(nk)

  • 구현이 쉽고, 정확한 편

  • 선형 모델 외에는 비적합

  • 범주 수가 크면 비적합

  1. 피처 해싱 (n : 데이터 포인트 수, m : 해시 저장소 수)

  • 공간 요구 : O(n)

  • 계산 요구 : O(nm)

  • 구현이 쉬움, 새로운 범주, 희귀 범주 처리가 쉬움

  • 해시처리된 피처를 해석할 수 없음

  • 선형, 커널 모델외에는 비적합

  • 정확도에 대해서 아직 검증되지 않음

  1. 빈 카운팅 (n : 데이터 포인트 수, k : 범주 수, a : 범주에 대해 유지해야하는 통계량들)

  • 공간 요구 : O(n+k+a)

  • 계산 요구 : 선형 모델에서 O(n)

  • 계산 부담이 가장 적음

  • 트리 기반 모델에서도 사용 가능 (모든 피처를 반복적으로 검색)

  • 새 범주 처리가 쉬움

  • 누적된 과거 데이터를 필요로 함

  • 업데이트에 지연이 있음

  • 누출 가능성에 대한 고려가 필요함

 

Chapter 5. Categorical Variables: Counting Eggs in the Age of Robotic Chickens

In [1]:
import pandas as pd
from sklearn import linear_model
 

Example 5-1. Linear regression on a categorical variable using one-hot and dummy codes

In [2]:
# 연습용 데이터셋 정의
df = pd.DataFrame({'City': ['SF', 'SF', 'SF', 'NYC', 'NYC', 'NYC', 'Seattle', 'Seattle', 'Seattle'],
                   'Rent': [3999, 4000, 4001, 3499, 3500, 3501, 2499, 2500, 2501]})
df
Out[2]:
  City Rent
0 SF 3999
1 SF 4000
2 SF 4001
3 NYC 3499
4 NYC 3500
5 NYC 3501
6 Seattle 2499
7 Seattle 2500
8 Seattle 2501
In [3]:
one_hot_df = pd.get_dummies(df, prefix=['city'])
one_hot_df
Out[3]:
  Rent city_NYC city_SF city_Seattle
0 3999 0 1 0
1 4000 0 1 0
2 4001 0 1 0
3 3499 1 0 0
4 3500 1 0 0
5 3501 1 0 0
6 2499 0 0 1
7 2500 0 0 1
8 2501 0 0 1
In [4]:
lin_reg = linear_model.LinearRegression()
In [5]:
# 원-핫 인코딩 데이터에 대한 선형회귀모델 학습
lin_reg.fit(one_hot_df[['city_NYC', 'city_SF', 'city_Seattle']], one_hot_df['Rent'])
lin_reg.coef_
Out[5]:
array([ 166.66666667,  666.66666667, -833.33333333])
In [6]:
lin_reg.intercept_
Out[6]:
3333.3333333333335
In [7]:
df['Rent'].mean()
Out[7]:
3333.3333333333335
In [8]:
# One-hot encoding weights + intercept
w1 = lin_reg.coef_
b1 = lin_reg.intercept_
In [9]:
# 더미 코딩
dummy_df = pd.get_dummies(df, prefix=['city'], drop_first=True)
dummy_df
Out[9]:
  Rent city_SF city_Seattle
0 3999 1 0
1 4000 1 0
2 4001 1 0
3 3499 0 0
4 3500 0 0
5 3501 0 0
6 2499 0 1
7 2500 0 1
8 2501 0 1
In [10]:
lin_reg.fit(dummy_df[['city_SF', 'city_Seattle']], dummy_df['Rent'])
lin_reg.coef_
Out[10]:
array([  500., -1000.])
In [11]:
lin_reg.intercept_
Out[11]:
3500.0
In [12]:
# Dummy coding weights + intercept
w2 = lin_reg.coef_
b2 = lin_reg.intercept_
 

Example 5-2. Linear regression with effect coding

In [13]:
effect_df = dummy_df.copy()
In [14]:
effect_df.loc[3:5, ['city_SF', 'city_Seattle']] = -1.0
effect_df
Out[14]:
  Rent city_SF city_Seattle
0 3999 1.0 0.0
1 4000 1.0 0.0
2 4001 1.0 0.0
3 3499 -1.0 -1.0
4 3500 -1.0 -1.0
5 3501 -1.0 -1.0
6 2499 0.0 1.0
7 2500 0.0 1.0
8 2501 0.0 1.0
In [15]:
lin_reg.fit(effect_df[['city_SF', 'city_Seattle']], effect_df['Rent'])
lin_reg.coef_
Out[15]:
array([ 666.66666667, -833.33333333])
In [16]:
lin_reg.intercept_
Out[16]:
3333.3333333333335
In [17]:
# Effect coding weights + intercept
w3 = lin_reg.coef_
b3 = lin_reg.intercept_
In [18]:
# 각 도시별 임대료 비교 플롯
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="whitegrid", font_scale=1.4, color_codes=True)
In [19]:
sns.swarmplot(x="City", y="Rent", data=df);
 
In [20]:
print('One-hot encoding weights: ' ,w1, '\t and intercept: ', b1)
print('Dummy   coding   weights: ', w2, '\t\t\t\t and intercept: ', b2)
print('Effect  coding   weights: ' ,w3, '\t\t and intercept: ', b3)
 
One-hot encoding weights:  [ 166.66666667  666.66666667 -833.33333333] 	 and intercept:  3333.3333333333335
Dummy   coding   weights:  [  500. -1000.] 				 and intercept:  3500.0
Effect  coding   weights:  [ 666.66666667 -833.33333333] 		 and intercept:  3333.3333333333335
In [21]:
# geometry of one-hot vs. dummy encoding

# Create a list of values in the best fit line for one-hot encoding
one_hot_y = [((w1[0] * one_hot_df.city_NYC[i]) + 
              (w1[1] * one_hot_df.city_SF[i]) +
              (w1[2] * one_hot_df.city_Seattle[i]) + b1) 
             for i in range(0,one_hot_df.shape[0])]

# Create a list of values in the best fit line for dummy coding
dummy_y = [((w2[0] * dummy_df.city_SF[i]) +
            (w2[1] * dummy_df.city_Seattle[i]) + b2)
           for i in range(0,dummy_df.shape[0])]

print(one_hot_y)
print(dummy_y)
 
[4000.0000000000005, 4000.0000000000005, 4000.0000000000005, 3500.0, 3500.0, 3500.0, 2500.0, 2500.0, 2500.0]
[3999.9999999999995, 3999.9999999999995, 3999.9999999999995, 3500.0, 3500.0, 3500.0, 2500.0, 2500.0, 2500.0]
 

Example 5-5. Feature hashing

In [22]:
import json
from sklearn.feature_extraction import FeatureHasher
from sys import getsizeof
In [23]:
# 처음 10,000개의 리뷰 로드
f = open('data/yelp/yelp_academic_dataset_review.json', encoding='utf-8')
js = []
for i in range(10000):
    js.append(json.loads(f.readline()))
f.close()
review_df = pd.DataFrame(js)
review_df.shape
Out[23]:
(10000, 9)
In [24]:
# 고유한 business_id 개수
m = len(review_df.business_id.unique())
m
Out[24]:
8577
In [25]:
h = FeatureHasher(n_features=m, input_type='string')
f = h.transform(review_df['business_id'])
In [26]:
f.shape
Out[26]:
(10000, 8577)
In [27]:
review_df['business_id'].unique().tolist()[0:5]
Out[27]:
['iCQpiavjjPzJ5_3gPD5Ebg',
 'pomGBqfbxcqPv14c3XH-ZQ',
 'jtQARsP6P-LbkyjbO1qNGg',
 'elqbBhBfElMNSrjFqW3now',
 'Ums3gaP2qM3W1XcA5r6SsQ']
In [28]:
f.toarray()
Out[28]:
array([[0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]])
In [29]:
print('Our pandas Series, in bytes: ', getsizeof(review_df['business_id']))
print('Our hashed numpy array, in bytes: ', getsizeof(f))
 
Our pandas Series, in bytes:  790104
Our hashed numpy array, in bytes:  56
 

Example 5-6. Bin-counting example

Click-Through Rate Prediction by Avazu

https://www.kaggle.com/c/avazu-ctr-prediction

  • train_subset은 전체 6 GB 데이터 중에서 처음 100K 행만 추출한 데이터셋.
In [30]:
df = pd.read_csv('data/ctr/train.csv')
df.shape
Out[30]:
(40428967, 24)
In [31]:
df = df.loc[:99999,:]
df.to_csv('data/ctr/train_subset.csv', index=None)
In [32]:
df = pd.read_csv('data/ctr/train_subset.csv')
df.shape
Out[32]:
(100000, 24)
In [33]:
len(df['device_id'].unique())
Out[33]:
7202
In [34]:
df.head()
Out[34]:
  id click hour C1 banner_pos site_id site_domain site_category app_id app_domain ... device_type device_conn_type C14 C15 C16 C17 C18 C19 C20 C21
0 1.000009e+18 0 14102100 1005 0 1fbe01fe f3845767 28905ebd ecad2386 7801e8d9 ... 1 2 15706 320 50 1722 0 35 -1 79
1 1.000017e+19 0 14102100 1005 0 1fbe01fe f3845767 28905ebd ecad2386 7801e8d9 ... 1 0 15704 320 50 1722 0 35 100084 79
2 1.000037e+19 0 14102100 1005 0 1fbe01fe f3845767 28905ebd ecad2386 7801e8d9 ... 1 0 15704 320 50 1722 0 35 100084 79
3 1.000064e+19 0 14102100 1005 0 1fbe01fe f3845767 28905ebd ecad2386 7801e8d9 ... 1 0 15706 320 50 1722 0 35 100084 79
4 1.000068e+19 0 14102100 1005 1 fe8cc448 9166c161 0569f928 ecad2386 7801e8d9 ... 1 0 18993 320 50 2161 0 35 -1 157

5 rows × 24 columns

 

Features are $\theta$ = [$N^+$, $N^-$, $log(N^+)-log(N^-)$, isRest]

$N^+$ = $p(+)$ = $n^+/(n^+ + n^-)$

$N^-$ = $p(-)$ = $n^-/(n^+ + n^-)$

$log(N^+)-log(N^-)$ = $\frac{p(+)}{p(-)}$

isRest = back-off bin (not shown here)

In [35]:
def click_counting(x, bin_column):
    clicks = pd.Series(x[x['click'] == 1][bin_column].value_counts(), name='clicks')
    no_clicks = pd.Series(x[x['click'] == 0][bin_column].value_counts(), name='no_clicks')
    
    counts = pd.DataFrame([clicks, no_clicks]).T.fillna('0')
    counts['total'] = counts['clicks'].astype('int64') + counts['no_clicks'].astype('int64')
    
    return counts

def bin_counting(counts):
    counts['N+'] = counts['clicks'].astype('int64').divide(counts['total'].astype('int64'))
    counts['N-'] = counts['no_clicks'].astype('int64').divide(counts['total'].astype('int64'))
    counts['log_N+'] = counts['N+'].divide(counts['N-'])

    # 빈 카운팅 특성만 반환하길 원하면 여기서 필터링 한다.
    bin_counts = counts.filter(items= ['N+', 'N-', 'log_N+'])
    return counts, bin_counts
In [36]:
# device_id에 대한 빈 카운팅 예제
bin_column = 'device_id'
device_clicks = click_counting(df.filter(items= [bin_column, 'click']), bin_column)
device_all, device_bin_counts = bin_counting(device_clicks)
In [37]:
# 모든 device_id가 포함되었는지 확인
len(device_bin_counts)
Out[37]:
7202
In [38]:
device_all.sort_values(by = 'total', ascending=False).head(4)
Out[38]:
  clicks no_clicks total N+ N- log_N+
a99f214a 15729 71206 86935 0.180928 0.819072 0.220894
c357dbff 33 134 167 0.197605 0.802395 0.246269
31da1bd0 0 62 62 0.000000 1.000000 0.000000
936e92fb 5 54 59 0.084746 0.915254 0.092593
In [39]:
# 피쳐 크기 비교
print('Our pandas Series, in bytes: ', getsizeof(df.filter(items= ['device_id', 'click'])))
print('Our bin-counting feature, in bytes: ', getsizeof(device_bin_counts))
 
Our pandas Series, in bytes:  7300104
Our bin-counting feature, in bytes:  641002
728x90
반응형
LIST
Comments