Machine Learning (3) - 데이터 전처리
사이킷런으로 시작하는 머신러닝
데이터 전처리(Data Preprocessing)
제대로 된 데이터를 입력해야 결과도 제대로 나온다.(Garbage In, Garbage Out)
- NaN, Null 값 허용하지 않음
- 문자열 값을 입력값으로 허용하지 않음
데이터 인코딩
레이블 인코딩(Label encoding)
유일값을 뽑아 목록을 만든 뒤 일련번호를 붙인다.(리스트로 만들고 idx를 사용)
숫자값이 의미가 없고 구분용도로 쓰인다.
변환된 숫자값이 중요도로 인식되어 예측성능이 떨어질 수도 있다.
따라서 선형회귀와 같은 ML 알고리즘에는 적용하지 않아야 한다.
트리 계열의 ML 알고리즘은 숫자의 이러한 특성을 반영하지 않으므로 별문제가 없다.
from sklearn.preprocessing import LabelEncoder
items = ['TV', '냉장고', '전자레인지', '컴퓨터', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']
encoder = LabelEncoder()
# 데이터를 정리, 사전 작성
encoder.fit(items)
# 레이블 인코딩 수행
encoder.transform(items)
labels = encoder.transform(items)
labels
array([0, 1, 4, 5, 5, 3, 3, 2, 2])
encoder.transform(['냉장고'])
array([1])
# 사전 정보 확인
encoder.classes_
array(['TV', '냉장고', '믹서', '선풍기', '전자레인지', '컴퓨터'], dtype='<U5')
# 원상복구(인코딩된 값을 다시 디코딩)
encoder.inverse_transform([1])
array(['냉장고'], dtype='<U5')
encoder.inverse_transform(labels)
array(['TV', '냉장고', '전자레인지', '컴퓨터', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서'],
dtype='<U5')
One-Hot Encoding
크기에 따른 영향을 없애기 위해 사용한다.
2차원 ndarray로 reshape해야 한다.
변환한 값이 희소 행렬(Sparse Matrix) 형태이므로 밀집 행렬(Dense Matrix) 형태로 변환해야 한다.
from sklearn.preprocessing import OneHotEncoder
import numpy as np
items
['TV', '냉장고', '전자레인지', '컴퓨터', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']
# 2차원 ndarray로 변환(List로 만들어도 가능) - 한 행에 한건의 데이터
items_t = np.array(items).reshape(-1, 1)
items_t
items_l = [['TV'],
['냉장고'],
['전자레인지'],
['컴퓨터'],
['컴퓨터'],
['선풍기'],
['선풍기'],
['믹서'],
['믹서']]
# 리스트 형태로 넣어도 가능하다
oh_encoder = OneHotEncoder()
oh_encoder.fit(items_l)
result = oh_encoder.transform(items_l)
result.toarray()
array([[1., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0.],
[0., 0., 0., 0., 1., 0.],
[0., 0., 0., 0., 0., 1.],
[0., 0., 0., 0., 0., 1.],
[0., 0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0., 0.],
[0., 0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0., 0.]])
# ndarray형태
oh_encoder.fit(items_t)
oh_labels = oh_encoder.transform(items_t)
oh_labels.toarray()
array([[1., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0.],
[0., 0., 0., 0., 1., 0.],
[0., 0., 0., 0., 0., 1.],
[0., 0., 0., 0., 0., 1.],
[0., 0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0., 0.],
[0., 0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0., 0.]])
oh_encoder.categories_
[array(['TV', '냉장고', '믹서', '선풍기', '전자레인지', '컴퓨터'], dtype='<U5')]
oh_encoder.inverse_transform([[1., 0., 0., 0., 0., 0.]]) # 2차원으로 넣어줘야 함 # 기본 데이터 타입이 실수형이라 '.' 없이 넣어도 자동으로 변환
array([['TV']], dtype='<U5')
oh_encoder.transform([['냉장고']]).toarray()
array([[0., 1., 0., 0., 0., 0.]])
import pandas as pd
df = pd.DataFrame({'item':['TV', '냉장고', '전자레인지', '컴퓨터', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']})
# 숫자형 변환 없이도 바로 변환 가능
pd.get_dummies(df['item']) # 특정컬럼 지정하지 않으면 item_TV, item_냉장고 처럼 컬럼이 나옴
TV | 냉장고 | 믹서 | 선풍기 | 전자레인지 | 컴퓨터 | |
---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 1 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 1 |
4 | 0 | 0 | 0 | 0 | 0 | 1 |
5 | 0 | 0 | 0 | 1 | 0 | 0 |
6 | 0 | 0 | 0 | 1 | 0 | 0 |
7 | 0 | 0 | 1 | 0 | 0 | 0 |
8 | 0 | 0 | 1 | 0 | 0 | 0 |
피처 스케일링과 정규화
서로 다른 변수의 값 범위를 일정한 수준으로 맞추는 작업이다.
- 표준화(Standardization)
평균이 0이고 분산이 1인 가우시안 정규 분포를 가진 값으로 변환한다.
- 정규화(Normalization)
서로다른 피처의 크기를 통일하기 위해 크기를 변환해주는 개념이다.
값들의 범위를 0에서 1사이로 변환한다.
- 사이킷런에서의 정규화(벡터 정규화)
선형대수에서의 정규화 개념이 적용된다.
개별 벡터의 크기를 맞추기 위해 변환하는 것을 의미한다.
개별 벡터를 모든 피처 벡터의 크기로 나눠준다.
StandardScaler
표준화를 쉽게 지원하기 위한 클래스
가우시안 정규분포를 따르는 데이터에 적용
from sklearn.datasets import load_iris
iris = load_iris(as_frame=True)
# 평균확인
iris.data.mean()
sepal length (cm) 5.843333
sepal width (cm) 3.057333
petal length (cm) 3.758000
petal width (cm) 1.199333
dtype: float64
# 분산확인
iris.data.var()
sepal length (cm) 0.685694
sepal width (cm) 0.189979
petal length (cm) 3.116278
petal width (cm) 0.581006
dtype: float64
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(iris.data)
iris_scaled = scaler.transform(iris.data)
# transform시 numpy ndarray로 반환되므로 DataFrame으로 변환
iris_df = pd.DataFrame(iris_scaled)
# 스케일된 데이터의 평균확인
iris_df.mean().round(5)
0 -0.0
1 -0.0
2 -0.0
3 -0.0
dtype: float64
# 스케일된 데이터의 분산확인
iris_df.var()
0 1.006711
1 1.006711
2 1.006711
3 1.006711
dtype: float64
MinMaxScaler
데이터의 분포가 가우시안 분포가 아닐 경우에 적용해 볼 수 있다.
from sklearn.preprocessing import MinMaxScaler
# 최소값 확인
iris.data.min()
sepal length (cm) 4.3
sepal width (cm) 2.0
petal length (cm) 1.0
petal width (cm) 0.1
dtype: float64
# 최대값 확인
iris.data.max()
sepal length (cm) 7.9
sepal width (cm) 4.4
petal length (cm) 6.9
petal width (cm) 2.5
dtype: float64
scaler = MinMaxScaler()
scaler.fit(iris.data)
iris_scaled = scaler.transform(iris.data)
iris_df = pd.DataFrame(iris_scaled)
# 스케일된 데이터의 최솟값 확인
iris_df.min()
0 0.0
1 0.0
2 0.0
3 0.0
dtype: float64
# 스케일된 데이터의 최댓값 확인
iris_df.max()
0 1.0
1 1.0
2 1.0
3 1.0
dtype: float64
학습 데이터와 테스트 데이터의 스케일링 변환 시 유의점
학습 데이터 세트로 fit()과 transform()을 적용하면 테스트 데이터 세트로는 다시 fit()을 수행하지 않고 학습 데이터 세트로 fit()을 수행한 결과를 이용해 transform() 변환을 적용해야 한다
train_array = np.arange(0, 11).reshape(-1, 1)
train_array
array([[ 0],
[ 1],
[ 2],
[ 3],
[ 4],
[ 5],
[ 6],
[ 7],
[ 8],
[ 9],
[10]])
test_array = np.arange(0, 6).reshape(-1, 1)
test_array
array([[0],
[1],
[2],
[3],
[4],
[5]])
scaler = MinMaxScaler()
scaler.fit(train_array)
train_array = scaler.transform(train_array)
train_array
array([[0. ],
[0.1],
[0.2],
[0.3],
[0.4],
[0.5],
[0.6],
[0.7],
[0.8],
[0.9],
[1. ]])
# train으로 fit된 scaler에 test는 transform만 수행
test_array = scaler.transform(test_array)
test_array
array([[0. ],
[0.1],
[0.2],
[0.3],
[0.4],
[0.5]])
# 잘못된 사용
scaler.fit(test_array)
test_array = scaler.transform(test_array)
test_array
array([[0. ],
[0.2],
[0.4],
[0.6],
[0.8],
[1. ]])
- 가능하다면 전체 데이터의 스케일링 변환을 적용한 뒤 학습과 테스트 데이터로 분리
- 위가 여의치 않다면 테스트 데이터 변환시에는 fit()이나 fit_transform()을 적용하지 않고 학습 데이터로 이미 fit()된 scaler 객체를 이용해 transform()으로 변환
Reference
- 이 포스트는 SeSAC 인공지능 자연어처리, 컴퓨터비전 기술을 활용한 응용 SW 개발자 양성 과정 - 심선조 강사님의 강의를 정리한 내용입니다.
댓글남기기