위키북스의 파이썬 머신러닝 완벽 가이드 책을 토대로 공부한 내용입니다.
ML 알고리즘은 데이터에 기반하고 있어서, 어떤 데이터를 입력으로 가지느냐에 따라 결과가 크게 달라지기 때문에 데이터 전처리(Data Preprocessing)는 ML 알고리즘만큼 중요하다.
NaN, Null등의 결손값은 혀용되지 않는다. faeture 중 Null값이 얼마 되지 않는다면 feature의 평균으로 값을 대체할 수는 있지만, Null 값이 대부분이라면 해당 feature는 drop하는 것이 좋다. 가장 결정하기 힘든 부분은 정확하게 어느 수준부터 drop해야 할지에 대한 기준이다. 또, 해당 feature가 높은 중요도를 가진다면 단순히 평군값으로 대체하면 예측 왜곡이 심할 수 있어 더 정밀한 대체 값이 필요해진다.
사이킷런의 머신러닝 알고리즘은 문자열 값을 입력으로 허용하지 않는다. 그래서 모든 문자열은 숫자형으로 인코딩된다. 문자열 feature는 일반적으로 카테고리형 feature와 텍스트형 feature를 의미하고, 카테고리형 feature는 코드 값으로 표현하는 것이 더 이해하기 쉽고, 텍스트형 feature는 feature vectorization 등의 기법으로 벡터화하거나 불필요한 feature라고 판단되면 삭제하는 것이 좋다.
ML의 대표적인 encoding 방식은 Label encoding과 One-hot encoding이다. Label encoding은 카테고리 feature를 코드형 숫자 값으로 반환하는 것을 말한다.
사이킷런의 label encoding은 LabelEncoder 클래스로 구현한다. LabelEncoder를 객체로 생성한 후 fit()과 transform()을 호출해 label encoding을 수행한다.
from sklearn.preprocessing import LabelEncoder items=['TV','냉장고','전자렌지','컴퓨터','선풍기','선풍기','믹서','믹서'] # LabelEncoder를 객체로 생성한 후 , fit( ) 과 transform( ) 으로 label 인코딩 수행. encoder = LabelEncoder() encoder.fit(items) labels = encoder.transform(items) print('인코딩 변환값:',labels) print('인코딩 클래스:',encoder.classes_) print('디코딩 원본 값:',encoder.inverse_transform([0, 1, 2, 3, 4, 5]]))
[output] label encoding은 간단하게 문자열 값을 숫자형 카테고리 값으로 변환할 수 있다. 하지만 label encoding이 일괄적인 숫자 값으로 변환이 되면 숫자 값이 클수록 특정 ML 알고리즘에서 가중치가 더 부여되거나 더 중요하게 인식될 가능성이 발생한다. 하지만 이 숫자 값들은 단순 코드일 뿐이라 순서나 중요도로 인식되어선 안된다. 이 특성 때문에 label encoding은 선형 회귀와 같은 ML 알고리즘에는 적용되지 않아야 하고, 트리 계열의 ML 알고리즘은 숫자의 이러한 특성을 반영하지 않아 label encoding을 사용해도 별문제가 되지 않는다.
One-Hot Encoding은 feature 값의 유형에 따라 새로운 feature를 추가해 고유 값에 해당하는 cloumn에만 1을 표시하고 나머지 cloumn에는 0을 표시하는 방식이다. 사이킷런에서는 OneHotEncoder 클래스로 쉽게 변환이 가능하다. 단, LabelEncoder와 다르게 주의할 점이 2가지가 있는데, 첫 번째는 OneHotEncoder로 변환하기 전에 모든 문자열 값이 숫자형으로 변환돼야 하며, 두 번째는 input 값으로 2차원 data가 필요하다는 점이다.
from sklearn.preprocessing import OneHotEncoder items=['TV','냉장고','전자렌지','컴퓨터','선풍기','선풍기','믹서','믹서'] # 먼저 숫자값으로 변환을 위해 LabelEncoder로 변환합니다. encoder = LabelEncoder() encoder.fit(items) labels = encoder.transform(items) # 2차원 데이터로 변환합니다. labels = labels.reshape(-1,1) # 원-핫 인코딩을 적용합니다. oh_encoder = OneHotEncoder() oh_encoder.fit(labels) oh_labels = oh_encoder.transform(labels) print('원-핫 인코딩 데이터') print(oh_labels.toarray()) print('원-핫 인코딩 데이터 차원') print(oh_labels.shape)
[output] 사이킷런에서 one-hot encoding을 하기 위해서는 먼저 LabelEncoding으로 문자열 카테고리 값을 숫자형으로 변환한 후 변환된 숫자형 값을 one-hot encoding 해준다. 판다스에는 one-hot encoding을 더 쉽게 지원하는 API가 있다. get_dummies()를 이용하면 된다. 사이킷런의 OneHotEncoder와 다르게 문자열 카테고리 값을 숫자 형으로 변환할 필요없이 바로 변환이 가능하다.
import pandas as pd df = pd.DataFrame({'item':['TV','냉장고','전자렌지','컴퓨터','선풍기','선풍기','믹서','믹서'] }) labels = pd.get_dummies(df) print('원-핫 인코딩 데이터') print(np.array(labels))
[output]
feature scaling이란 서로 다른 변수의 값 범위를 일정한 수준으로 맞추는 작업을 말하고, 대표적인 방법으로는 표준화(Standardization)와 정규화(Normalization)가 있다.
표준화는 data의 feature 각각이 평균이 0이고, 분산이 1인 Gaussian 정규 분포를 가진 값으로 변환하는 것을 말한다. 는 feature 의 번째 데이터가 표준화를 통해 변환된 새로운 feature를 말한다.
일반적으로 정규화는 서로 다른 feature의 크기를 통일하기 위해 크기를 변환해주는 개념이다. 즉, 개별 데이터의 크기를 모두 똑같은 단위로 변경하는 것을 말한다.
그런데 사이킷런의 전처리에서 제공하는 Normalizer 모듈과 일반적인 정규화는 조금 차이가 있다. 사이킷런의 Normalizer 모듈은 선형대수에서의 정규화 개념이 적용됬으며, 개별 벡터의 크기를 맞추기 위해 변환하는 것을 의미한다. 즉, 개별 벡터를 모든 feature 벡터의 크기로 나눠준다.
혼선을 방지하기 위해 이후 설명부터는 일반적인 의미의 표준화와 정규화를 feature scaling으로, 선형대수 개념의 정규화를 vector normalization로 지칭한다. 사이킷런에서 제공하는 대표적인 feature scaling 클래스는 StandardScaler와 MinMaxScaler가 있다.
Gaussian 정규 분포를 가지도록 데이터를 변환하는 것은 몇몇 알고리즘에서 중요하다. 특히, 사이킷런에서 구현한 RBF 커널을 이용하는 Support Vector Machine이나 Linear Regression, Logistic Regression은 데이터가 Gaussian 분포를 가지고 있다고 가정하고 구현됬기 때문에 사전에 표준화를 적용하는 것은 예측 성능 향상에 중요한 요소가 될 수 있다.
import pandas as pd from sklearn.datasets import load_iris # 붓꽃 데이터 셋을 로딩하고 DataFrame으로 변환합니다. iris = load_iris() iris_data = iris.data iris_df = pd.DataFrame(data=iris_data, columns=iris.feature_names) print('feature 들의 평균 값') print(iris_df.mean()) print('\nfeature 들의 분산 값') print(iris_df.var())
[output]
from sklearn.preprocessing import StandardScaler # StandardScaler객체 생성 scaler = StandardScaler() # StandardScaler 로 데이터 셋 변환. fit( ) 과 transform( ) 호출. scaler.fit(iris_df) iris_scaled = scaler.transform(iris_df) #transform( )시 scale 변환된 데이터 셋이 numpy ndarry로 반환되어 이를 DataFrame으로 변환 iris_df_scaled = pd.DataFrame(data=iris_scaled, columns=iris.feature_names) print('feature 들의 평균 값') print(iris_df_scaled.mean()) print('\nfeature 들의 분산 값') print(iris_df_scaled.var())
[output]
MinMaxScaler는 데이터 값을 0과 1 사이의 범위 값으로 변환한다. 만약 데이터에 음수 값이 있을 땐 -1과 1 사이의 범위 값으로 반환한다. 그리고 데이터의 분포가 Gaussian 분포가 아닐 경우 적용해볼 수 있다.
from sklearn.preprocessing import MinMaxScaler # MinMaxScaler객체 생성 scaler = MinMaxScaler() # MinMaxScaler 로 데이터 셋 변환. fit() 과 transform() 호출. scaler.fit(iris_df) iris_scaled = scaler.transform(iris_df) # transform()시 scale 변환된 데이터 셋이 numpy ndarry로 반환되어 이를 DataFrame으로 변환 iris_df_scaled = pd.DataFrame(data=iris_scaled, columns=iris.feature_names) print('feature들의 최소 값') print(iris_df_scaled.min()) print('\nfeature들의 최대 값') print(iris_df_scaled.max())
[output]
StandardScaler나 MinMaxScaler와 같은 Scaler 객체를 이용해 데이터의 스케일링 변환 시 fit(), transform(), fit_transform() method를 이용한다. 그런데 학습 dataset과 테스트 dataset에 fit(), transform() method를 사용할 경우 유의할 점이 있다. Scaler 객체를 이용해 학습 dataset에 fit()과 transform()을 적용하면 테스트 dataset으로 다시 fit()을 적용시키지 않고 학습 dataset으로 fit()을 수행한 결과를 이용해 trasform() 변환을 적용해야 한다. 그렇지 않고 테스트 데이터로 다시 새로운 스케일링 기준 정보를 만들게 되면 학습 데이터와 테스트 데이터의 스케일링 기준 정보가 서로 달라지기 때문에 올바르게 예측 결과를 도출할 수 없다. 따라서 학습/테스트 데이터에 Scaler를 적용할 경우 이러한 문제가 생기지 않도록 dataset을 학습/테스트 dataset으로 분리하기 전에 스케일링을 적용하는 것이 바람직하다.
- Summary
1. 가능하다면 전체 데이터의 스케일링 변환을 적용한 뒤 학습과 테스트 데이터로 분리
2. 1번이 여의치 않다면 테스트 데이터 변환 시에는 fit()이나 fit_transform()을 적용하지 않고 학습 데이터로 이미 fit()된 Scaler 객체를 이용해 transform()으로 변환