5 minute read

전처리(데이터 변환)를 자동화(함수 만들기)해야 하는 이유

  1. 새롭거나 바뀐 데이터셋이 들어왔을 때 데이터 변환을 쉽게 반복할 수 있어야 한다: 엄청 많이 할 거거든
  2. 자동화해놓은 변환 프로세스는 향후 프로젝트에서 사용할 수 있다. 즉, 나만의 변환 라이브러리를 점진적으로 구축하게 된다.
  3. 실제 시스템에서 알고리즘에 새 데이터를 주입하기 전에 변환해야 하는데, 그 때 이 함수를 사용할 수 있다
  4. 여러가지 데이터 변환을 쉽게 시도하고, 어떤 조합이 가장 좋은지 확인하는 데 편리하다

데이터셋 불러와 X, y 나누기

# 데이터셋을 불러온다
import pandas as pd
# 첫 번째 컬럼이 우리가 만든 index이므로 index_col=0을 지정해준다.
strat_train_set = pd.read_csv("../datasets/temp/strat_train_set.csv", index_col=0)

# drop() 함수는 해당 컬럼을 삭제하는데, 복사본을 만들어서 삭제하므로 원본은 그대로 유지된다.
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()

# housing 저장해두기
housing_labels.to_csv("./housing_labels.csv")
housing.to_csv("./housing.csv")
# na값 확인
housing['total_bedrooms'].isnull().sum()
158

데이터 정제1) 누락값을 어떻게 처리할 것인가?

total_bedrooms에 누락된 값이 있는데, 아래와 같은 방법 중 하나를 선택할 수 있다.

  1. 해당 구역 제거
  2. 전체 특성 삭제
  3. 0, 평균, 중간값 등 어떤 값으로 채운다.

주의, 3번을 선택했을 경우 어떤 값으로 채웠는지 저장할 것. 나중에 새 데이터가 들어와도 같은 그 값으로 채워야 한다

채우기1: fillna를 이용해 중간값으로 채우기

median = housing['total_bedrooms'].median()
housing_fillna_bedrooms = housing['total_bedrooms'].fillna(median)

# na값이 채워졌는지 확인
print(housing_fillna_bedrooms.isnull().sum()) # na가 잘 채워진 것을 확인했다
print(housing['total_bedrooms'].isnull().sum()) # fillna는 원본을 건드리지 않는다
0
158

채우기2: SimpleImputer 활용하기

  • SimpleImputer는 중간값 등을 활용해 누락된 값을 쉽게 채운다.
  • 계산된 중간값은 저장된다.
  • 수치형 특성에서만 적용되기 때문에 텍스트 특성 등을 제외하고 수치형으로만 복사본을 만들어 활용해야 한다.
from sklearn.impute import SimpleImputer

# 누락된 값을 중간값으로 대체하는 Imputer 객체를 생성한다.
imputer = SimpleImputer(strategy="median")
# 중간값이 수치형 특성에서만 계산될 수 있기 때문에 텍스트 특성인 ocean_proximity를 제외한 데이터 복사본을 생성한다.
housing_num = housing.drop("ocean_proximity", axis=1)
# imputer 객체의 fit() 메서드를 사용하면 imputer.statistics_에 각 특성의 중간값이 계산되어 저장된다
imputer.fit(housing_num)

# imputer 객체의 statistics_ 속성에 저장된 값과 수동으로 계산한 값이 같은지 확인해보자
print(imputer.statistics_)
print(housing_num.median().values)
[-118.51 34.26 29. 2119. 433. 1164.
408. 3.54155]
[-118.51 34.26 29. 2119. 433. 1164.
408. 3.54155]
# 학습된 imputer 객체를 사용해 훈련 세트에서 누락된 값을 학습한 중간값으로 바꾼다.
X = imputer.transform(housing_num)

# X는 numpy 배열이므로 아래와 같은 명령어가 먹히지 않는다
# print(X.head())

# 다시 pandas의 DataFrame으로 변환한다.
housing_tr = pd.DataFrame(X, columns=housing_num.columns, index=housing_num.index)
housing_tr.head()
longitude latitude housing_median_age total_rooms total_bedrooms \ 12655 -121.46 38.52 29.0 3873.0 797.0 15502 -117.23 33.09 7.0 5320.0 855.0 2908 -119.04 35.37 44.0 1618.0 310.0 14053 -117.13 32.75 24.0 1877.0 519.0 20496 -118.70 34.28 27.0 3536.0 646.0 population households median_income 12655 2237.0 706.0 2.1736 15502 2015.0 768.0 6.3373 2908 667.0 300.0 2.8750 14053 898.0 483.0 2.2264 20496 1837.0 580.0 4.4964

데이터 정제 2) 텍스트와 범주형 특성 숫자변환

housing_category = housing[['ocean_proximity']]
housing_category.head(10)

# 아래 결과에서 이 특성은 임의의 텍스트가 아니라, 제한된 특정 값들로 구분한 범주형임을 확인할 수 있다.
ocean_proximity 12655 INLAND 15502 NEAR OCEAN 2908 INLAND 14053 NEAR OCEAN 20496 <1H OCEAN 1481 NEAR BAY 18125 <1H OCEAN 5830 <1H OCEAN 17989 <1H OCEAN 4861 <1H OCEAN

방법1) OrdinalEncoder

from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
housing_category_encoded = ordinal_encoder.fit_transform(housing_category)
print(housing_category_encoded[:10])
print(ordinal_encoder.categories_)
[[1.]
[4.]
[1.]
[4.]
[0.]
[3.]
[0.]
[0.]
[0.]
[0.]]
[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]

cetegories_는 변환한 범주형 특성마다 카테고리의 1D 배열을 담은 리스트를 변환한다. 여기서 범주형 특성은 하나뿐이므로 배열 하나를 담은 리스트가 반환됐다

문제점

  • 머신러닝 알고리즘은 가까운 값을 떨어진 값보다 더 비슷하다고 생각한다.
  • 예를 들어 terrible, bad, average, good, excellent의 경우에는 문제가 없다.
  • excellent는 good과 가깝고, terrible은 bad와 가까우니까.
  • 하지만 ocean_proximity는 그런 경우에 해당하지 않는다.

One-Hot-Encoding

  • ocean_proximity의 범주 값은 5가지이다. 이 5가지를 하나씩의 특성으로 만들어, 이 특성에 해당합니까? 에 대한 값을 1 또는 0으로 가지게 만든다.
  • 예를 들어 INLAND값을 가진다면, INLAND 특성은 1, 나머지 특성들은 0을 가진다.
  • 새로 생긴 5개 특성을 dummy라고도 부른다.
from sklearn.preprocessing import OneHotEncoder
cat_encoder = OneHotEncoder()
housing_category_1hot = cat_encoder.fit_transform(housing_category)
housing_category_1hot
# 아래 출력되는 메시지를 확인해보면, 16512개 행에 5개의 카테고리가 있고, 0이 아닌 원소의 위치만 저장되어 있다.
<16512x5 sparse matrix of type '<class 'numpy.float64'>' with 16512 stored elements in Compressed Sparse Row format>

출력을 보면 Sparse Matrix다. 예를 들어 카테고리 값이 수천개가 되는 특성을 원 핫 인코딩 한다면 이 특성은 column이 수천개가 되어버린다.

각 row는 1이 하나뿐이고 그 외에는 모두 0으로 채워져 있을 것이다.

이것을 모두 메모리에 저장하는 것은 낭비이다.

1의 위치만 저장하는 것이 훨씬 효율적이며, 이것이 바로 희소 행렬(sparse matrix)다.

이것을 2차원 배열처럼 사용할 수도 있지만, 넘파이 배열로 바꾸고 싶다면 toarray()메서드를 호출하면 된다.

표현 학습

  • 원 핫 인코딩은 많은 수의 특성을 만들어낸다.
  • 이는 훈련을 느리게 하고 성능을 감소시킨다.
  • 범주 값을 “의미있는” 숫자형 특성으로 바꿀 수 있다.

  • 예를 들어 국가 코드라는 범주를 사용하는 대신 국가의 1인당 GDP와 인구라는 특성으로 바꿀 수 있다.
  • 이렇게 하면 국가끼리 구분을 유지하면서, 쓸데없는 152개 Column을 생성하는 대신 의미있는 2개의 컬럼을 생성한다.

  • ocen_proximity 특성은 구할 수 있다면 해안까지의 거리라는 특성으로 바꿀 수 있다.

나만의 변환기 만들기

  • 사이킷런은 DUCK TYPING을 지원한다.
  • 상속이나 인터페이스 구현이 아니라, 객체의 속성이나 메서드가 객체의 유형을 결정하는 방식을 말한다.
  • 즉, fit()(self를 반환), tranform(), fit_transform() 메서드가 구현되어 있는 클래스를 만들면, 그것을 다른 사이킷런 객체들과 연결해 pipeline을 만들 수 있다.
  • fit_tranform() 메서드는 따로 구현할 필요 없이, TranformerMixin을 상속하는 것 만으로 자동으로 생성된다.
  • BaseEstimator를 상속하면 하이퍼파라미터 튜닝에 꼭! 필요한 get_params(), set_params()를 얻을 수 있다.
  • 다만 위 두 메서드를 사용하면 *args나 **kargs는 사용할 수 없다
from sklearn.base import BaseEstimator, TransformerMixin

rooms_ix, bedrooms_ix, population_ix, households_ix = 3, 4, 5, 6

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
  def __init__(self, add_bedrooms_per_room=True):
    self.add_bedrooms_per_room = add_bedrooms_per_room
  def fit(self, X, y=None):
    return self
  def transform(self, X):
    rooms_per_household = X[:, rooms_ix] / X[:, households_ix]
    population_per_household = X[:, population_ix] / X[:, households_ix]
    if self.add_bedrooms_per_room:
      bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
      return np.c_[X, rooms_per_household, population_per_household, bedrooms_per_room]
    else:
      return np.c_[X, rooms_per_household, population_per_household]
  • 위의 변환기는 add_bedrooms_per_room 파라미터 하나를 가진다.
  • 객체를 생성할 때 이 값을 주지 않으면 기본값은 True다.
  • 이 특성을 추가하는 것이 머신러닝 알고리즘에 도움이 될 지 이 하이퍼파라미터로 쉽게 확인해볼 수 있다.

특성 스케일링(Feature Scaling)

  • 데이터에 적용할 가장 중요한 변환 중 하나다.
  • Tree 기반 알고리즘 등 몇 가지를 빼고는, 머신러닝 알고리즘은 숫자 특성들의 스케일이 많이 다르면 성능이 크게 떨어진다.
  • (타겟값에 대한 스케일링은 일반적으로 불필요하다)
  • 예를 들어 전체 방 개수의 범위는 6에서 39,320인데, 중간소득의 범위는 0에서 15까지다. 이를 비슷하게 맞춰줘야 한다.

min-max 스케일링(=정규화(normalization))

  • 데이터에서 최솟값을 빼고 최댓값과 최솟값의 차이로 나누어 모든 숫자를 최소0, 최대1로 맞추는 것.
  • 사이킷런에서는 MinMaxScaler 변환기로 사용.
  • 문제: 이상치의 영향을 표준화보다 더 받음. 예) 중간소득이 0~15 사이인데, 1개의 값만 100이라고 잘못 입력되어 있다면, 사실상 0 ~ 0.15 사이로 스케일링되어버릴것
  • 이상치를 확실하게 제거한 다음 사용해야 함.

표준화(standardization)

  • 평균을 빼고 표준편차로 나누어, 데이터의 평균을 0, 표준편차가 1이 되도록 하는 것.
  • 범위의 상한과 하한이 없어 특정 알고리즘에서 사용할 수 없다.
  • 예를 들어 신경망 알고리즘들은 종종 입력값 범위로 0~1 사이 값을 제공해야 한다.

스케일링시의 주의점!

  • 훈련세트에 대해서만 fit()한 다음
  • 훈련세트+테스트세트+새로운 데이터에 대해 transform() 한다.
  • 즉, label이 아닌 모든 데이터가 transform() 되어야 하지만, fit 과정에서 알고리즘은 훈련세트만 알아야 한다.

Leave a comment