본문 바로가기
AI/머신러닝

[머신러닝] 데이터 전처리 | 표준화, 정규화

by snow_white 2022. 4. 12.

1.1 표준화 (Standardization)

표준화는 데이터의 평균을 0 분산 및 표준편차를 1로 만들어 줍니다.

표준화를 하는 이유

  • 서로 다른 통계 데이터들을 비교하기 용이하기 때문입니다.
  • 표준화를 하면 평균은 0, 분산과 표준편차는 1로 만들어 데이터의 분포를 단순화 시키고, 비교를 용이하게 합니다.

표준화 공식

표준화 결과

  1. 평균이 0이 되고, 분산 정도는 1이 됩니다.(표준편차도 1이 됩니다)
  2. 데이터가 분포되는 생김새, 패턴이 변하진 않지만, 값의 range(scaling)만 변합니다.

 

샘플데이터

iris 붓꽃 샘플데이터를 가져옵니다.

**from** sklearn.datasets **import** load_iris
iris  **=** load_iris()
x **=** iris['data']*# data, input data, X*
y **=** iris['target'] *# target, labwl, 정답, y*

4개의 feature 데이터 중 1개의 feature만 임의로 선택합니다.

*# x 데이터 중에서 row는 다 받아옥, 컬럼은 첫 번째 것 한 개만 가져온다.*
X **=** x[:, 0]
X[:5]
# array([5.1, 4.9, 4.7, 4.6, 5. ])

표준화 코드 구현

표준화를 Python으로 직접 구현하면 다음과 같습니다. 공식을 그대로 코드로 옮기면 됩니다.

X_ **=** (X**-**X.mean())**/**X.std() *# 표준화 시킨 X 값*
X_
'''
array([-0.90068117, -1.14301691, -1.38535265, -1.50652052, -1.02184904,
       -0.53717756, -1.50652052, -1.02184904, -1.74885626, -1.14301691,
'''

시각화

시각화로 표준화의 전과 후를 비교합니다.

plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
sns.distplot(X, bins=5, color='b')
plt.title('Original', fontsize=16)

plt.subplot(1, 2, 2)
sns.distplot(X_, bins=5, color='r')
plt.title('Standardization', fontsize=16)
plt.show()

표준화 전과 후의 그래프를 구별해보면 가장 두드러지는 점이 바로 평균이 0으로 맞춰진다는 것과 scale이 줄어든다는 것입니다.

 

StandardScaler의 활용

sklearn.preprocesssing 에 StandardScaler로 표준화 (Standardization) 할 수 있습니다.

표준화를 진행할 땐 y는 라벨이기 때문에 X만 scaling 해야 합니다.

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler() # ((150, 4)
x.shape, x[:5]
'''
array([[5.1, 3.5, 1.4, 0.2],
        [4.9, 3. , 1.4, 0.2],
        [4.7, 3.2, 1.3, 0.2],
        [4.6, 3.1, 1.5, 0.2],
        [5. , 3.6, 1.4, 0.2]])
'''
x_scaled = scaler.fit_transform(x)
x_scaled[:5]
'''
array([[-0.90068117,  1.01900435, -1.34022653, -1.3154443 ],
       [-1.14301691, -0.13197948, -1.34022653, -1.3154443 ],
       [-1.38535265,  0.32841405, -1.39706395, -1.3154443 ],
       [-1.50652052,  0.09821729, -1.2833891 , -1.3154443 ],
       [-1.02184904,  1.24920112, -1.34022653, -1.3154443 ]])
'''
round(x_scaled

Scale 전, 후 비교 (시각화)

Sepal - 꽃받침

plt.figure(figsize=(12,6))

plt.subplot(1,2,1)
sns.scatterplot(x[:,0],x[:,1], hue=y, palette='muted')
plt.title('Sepal(0riginal)', fontsize=16)

plt.subplot(1,2,2)
sns.scatterplot(x_scaled[:,0],x_scaled[:,1], hue=y, palette='muted')
plt.title('Sepal(Scaled)', fontsize=16)

plt.show()

 

Scale 전, 후 비교 (시각화)

Petal - 꽃잎

plt.figure(figsize=(12,6))

plt.subplot(1,2,1)
sns.scatterplot(x[:,2],x[:,3], hue=y, palette='muted')
plt.title('Petal(0riginal)', fontsize=16)

plt.subplot(1,2,2)
sns.scatterplot(x_scaled[:,2],x_scaled[:,3], hue=y, palette='muted')
plt.title('Petal(Scaled)', fontsize=16)

plt.show()

 

1.2 정규화 (Normalization)

정규화 (Normalization)도 표준화와 마찬가지로 데이터의 스케일을 조정합니다.

정규화가 표준화와 다른 가장 큰 특징은 모든 데이터가 0 ~ 1 사이의 값을 가집니다.

즉, 최대값은 1, 최소값은 0으로 데이터의 범위를 조정합니다.

정규화 공식

scaling ⇒ 데이터 모양 변하지 않고 단지 x, y 값의 range만 변하는데 표준화 작업을 왜 해야 할까요?

예를 들어 영화 평점 예측 model을 Naver vs. 넷플릭스로 비교해봅시다.

네이버는 영화 평점 1~10점이고,

넷플릭스는 영화 평점 1~5점 일 때

두 데이터를 merge 해서 학습시켜야 할 때를 고려해봅시다.

네이버에서 4.5점과 넷플릭스 4점은 평점이 완전히 다른 경우이죠.

하지만 scaling이 맞춰졌을 때 동일한 선상에서 데이터 분석이 가능해질 수 있습니다.

 

정규화 코드 구현

X = x[:,0]
X_ = (X-X.min())/(X.max()-X.min()) # 정규화 적용
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
sns.distplot(**X**, bins=5, color='b')
plt.title('Original', fontsize=16)

plt.subplot(1, 2, 2)
sns.distplot(**X_**, bins=5, color='r')
plt.title('Normalization', fontsize=16)

plt.show()

 

 

MinMaxScaler의 활용

from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
x_scaled = scaler.fit_transform(x)
x[:5]
'''
array([[5.1, 3.5, 1.4, 0.2],
       [4.9, 3. , 1.4, 0.2],
       [4.7, 3.2, 1.3, 0.2],
       [4.6, 3.1, 1.5, 0.2],
       [5. , 3.6, 1.4, 0.2]])
'''
x_scaled[:5]
'''
array([[0.22222222, 0.625     , 0.06779661, 0.04166667],
       [0.16666667, 0.41666667, 0.06779661, 0.04166667],
       [0.11111111, 0.5       , 0.05084746, 0.04166667],
       [0.08333333, 0.45833333, 0.08474576, 0.04166667],
       [0.19444444, 0.66666667, 0.06779661, 0.04166667]])
'''
x_scaled.min(), x_scaled.maxx() # (0.0, 1.0)

Scale 전, 후 비교 (시각화)

plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
sns.scatterplot(x[:, 0], x[:, 1], hue=y, palette='muted')
plt.title('Sepal (Original)', fontsize=16)

plt.subplot(1, 2, 2)
sns.scatterplot(x_scaled[:, 0], x_scaled[:, 1], hue=y, palette='muted')
plt.title('Sepal (Scaled)', fontsize=16)
plt.show()

plt.figure(figsize=(12, 6))

plt.subplot(1, 2, 1)
sns.scatterplot(x[:, 2], x[:, 3], hue=y, palette='muted')
plt.title('Petal (Original)', fontsize=16)

plt.subplot(1, 2, 2)
sns.scatterplot(x_scaled[:, 2], x_scaled[:, 3], hue=y, palette='muted')
plt.title('Petal (Scaled)', fontsize=16)

plt.show()

 

2. Encoding ↔ Decoding

Label Encoder

머신러닝 알고리즘은 문자열 데이터를 입력으로 받지 못합니다.

따라서, 데이터가 가지고 있는 범주형(Categorical) 데이터는 반드시 숫자형(Numerical)으로 변환해주어야 합니다.

LabelEncoder는 범주형(Categorical) 데이터를 수치형으로 인코딩(encoding) 합니다.

여기서 인코딩(encoding) 이란, 문자형 -> 숫자형 데이터로 변환 해주는 것을 의미합니다.

샘플 데이터 (tips)

tips = sns.load_dataset('tips')
tips.head()

tips['day'].value_counts() # 값이 많은 순으로 출력
'''
Sat     87
Sun     76
Thur    62
Fri     19
Name: day, dtype: int64
'''
plt.figure(figsize=(10, 5))
sns.countplot(tips['day'])
plt.title('Data Counts', fontsize=16)
plt.show()

만약 tips 데이터의 day 컬럼에서의 값(value)인 [Thur(목), Fri(금), Sat(토), Sun(일)] 을 인코딩(encoding) 해주지 않고 그대로 머신러닝 모델에 학습데이터로 feed한다면 error가 발생 합니다.

 

apply를 활용한 인코딩(encoding)

아래와 같이 encoding이라는 함수를 정의한 후 변환하고자 하는 컬럼에 apply해줌으로써 인코딩을 진행합니다.

def convert(data):
    if data=='Thur':
        return 0
    elif data == 'Fri':
        return 1
    elif data == 'Sat':
        return 2
    elif data == 'Sun':
        return 3

converted = tips['day'].apply(convert)
converted.value_counts()
'''
2    87
3    76
0    62
1    19
Name: day, dtype: int64
'''

인코딩 한 값을 간단히 시각화 해 볼 수 있습니다.

plt.figure(figsize=(10, 5))
sns.countplot(converted)
plt.title('(Encoded) Data Counts', fontsize=16)
plt.show()

 

LabelEncoder 활용

위와 같이 apply로 변환하고자 하는 컬럼 별로 인코딩을 해줄 수 있지만, sklearn.preprocessing.LabelEncoder를 활용하여 쉽게 인코딩할 수 있습니다.

from sklearn.preprocessing import LabelEncoder

# LabelEncoder 적용
encoder = LabelEncoder()

# LabelEncoder 변환
encoded = encoder.fit_transform(tips['day']) # 날짜를 변환해야 함!

#결과값 확인
encoded
'''
array([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
       3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 3])
'''

LabelEncoder를 사용하면 원래 class 의 label도 확인할 수 있습니다.

encoder.classes_ # array(['Fri', 'Sat', 'Sun', 'Thur'], dtype=object)

Inverse Transform(역변환)

머신러닝 학습을 위하여 어쩔 수 없이 범주형(Categorical) 데이터를 숫자형으로 변환하였다면, 이제 결과 확인을 위하여 다시 역변환이 필요합니다.

이는 LabelEncoder의 inverse_transform을 사용하여 쉽게 역변환할 수 있습니다.

#역변환
inversed = encoder.inverse_transform(encoded)
inversed
'''
array(['Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun',
       'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun',
       'Sun', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat',
       'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat',
       'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sun', 'Sun', 'Sun', 'Sun',
       'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun',
       'Sun', 'Sun', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat',
       'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat',
       'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Thur', 'Thur', 'Thur', 'Thur',
       'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur',
       'Thur', 'Fri', 'Fri', 'Fri', 'Fri', 'Fri', 'Fri', 'Fri', 'Fri',
       'Fri', 'Fri', 'Fri', 'Fri', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat',
       'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sun', 'Sun', 'Sun', 'Sun',
       'Sun', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur',
       'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur',
       'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur',
       'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur',
       'Thur', 'Thur', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun',
       'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun',
       'Sun', 'Sun', 'Sat', 'Sat', 'Sat', 'Sat', 'Sun', 'Sun', 'Sun',
       'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun',
       'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Sun', 'Thur', 'Thur',
       'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Thur',
       'Thur', 'Thur', 'Thur', 'Thur', 'Thur', 'Sat', 'Sat', 'Sat', 'Sat',
       'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat',
       'Sat', 'Fri', 'Fri', 'Fri', 'Fri', 'Fri', 'Fri', 'Fri', 'Sat',
       'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat',
       'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Sat', 'Thur'], dtype=object)
'''
plt.figure(figsize=(10, 5))
sns.countplot(inversed)
plt.title('(Inversed) Data Counts', fontsize=16)
plt.show()

댓글