'll Hacker

[ML 실습] 9일차 복습- 사이킷런 머신러닝 만들어보기(데이터 전처리) 본문

AI/머신러닝

[ML 실습] 9일차 복습- 사이킷런 머신러닝 만들어보기(데이터 전처리)

씨이오가 되자 2025. 3. 17. 00:27
Contents
728x90

LG Aimers를 하면서 데이터 전처리의 중요성을 알게 되었다. 전처리를 할 때마다 public score 달라지는 것을 볼 수 있었다. ML알고리즘이 데이터 기반하고 있기 때문에 어떤 데이터를 입력으로 가지느냐에 따라 결과도 크게 달라질 수 있다. 

 

중요!

  1. 결손값(NaN, Null값)은 허용되지 않는다.
  2. 문자열을 입력값으로 허용되지 않는다. ▶️인코딩 처리 

Label Encoding

카테고리 피처를 코드형 숫자 값으로 변환하는 것이다.

'TV', '냉장고', '세탁기'...이렇게 있다면, 'TV'를 1로, '냉장고'를 2로, '세탁기'로 3으로 숫자로 변환해준다.

# label encoding
from sklearn.preprocessing import LabelEncoder

items=['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','컴퓨터','선풍기','믹서','믹서']

# LabelEncoder를 객체로 생성한 후, fit()과 transform으로 레이블 인코딩 수행
encoder=LabelEncoder()
encoder.fit(items)
labels=encoder.transform(items)
labels=encoder.transform(items)
print('인코딩 변환값:',labels)

TV는 0, 냉장고는 1, 전자레인지는 4....등등 변환됨. 실제로는 많은 데이터를 다룰텐데, 그때 변환값을 확인하는 방법이 LabelEncoder객체의 classes_ 속성값으로 확인된다.

print('인코딩 클래스:',encoder.classes_)

inverse_transform()을 통해 인코딩된 값을 다시 디코딩할 수 있다. 

위에서 봤듯이, 레이블 인코딩은 간단하게 문자열 값을 숫자형 카테고리 값으로 변환한다. 하지만 레이블 인코딩이 일괄적인 숫자 값으로 변환이 되면서 몇몇 ML알고리즘에는 이를 적용할 경우 예측 성능이 떨어지는 경우가 발생할 수 있다.

예를들어, 냉장고가 1이고, 믹서가 2이라면, 믹서가 더 크므로 특정 ML 알고리즘에서 가중치가 더 부여되거나 더 중요하게 인식될 가능성이 높은 것이다. 그래서 레이블 인코딩은 선형 회귀와 같은 ML 알고리즘에서는 적용하지 말아야한다. 트리 계열의 ML알고리즘은 숫자의 이러한 특성을 반영하지 않으므로 별 문제는 없다.

더보기

선형 회귀 ML 알고리즘? (멀랑.. 자세한거슨 뒤에서 살펴볼것)

1. 단순 선형 회귀 (Simple Linear Regression)

2. 다중 선형 회귀 (Multiple Linear Regression)

3. 릿지 회귀 (Ridge Regression)

4. 라쏘 회귀 (Lasso Regression)

5. 엘라스틱넷 회귀 (Elastic Net Regression)

6. 일반화 선형 모델 (GLM, Generalized Linear Model)

7. 최소 절대 편차 회귀 (Least Absolute Deviation Regression, LAD)

8. 로버스트 회귀 (Robust Regression)

9. 베이지안 선형 회귀 (Bayesian Linear Regression)

10. 플리노미얼 회귀 (Polynomial Regression)

11. 스텝와이즈 회귀 (Stepwise Regression)

one-hot Encoding은 레이블 인코딩의 문제점을 해결하였다.

One-Hot Encoding

피처 값의 유형에 따라 새로운 피처를 추가해 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지 칼럼에는 0을 표시하는 방식이다. 즉, 행 형태로 되어있는 피처의 고유 값을 열 형태로 차원을 변환한 뒤, 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지 칼럼에는 0을 표시한다. 

왼쪽사진: 변환 후(교재나와있음), 오른쪽사진: 여기는 실제 LG Aimers할때 처음 코딩할 때 원핫 인코딩 해본 것이다.

One-hot Encoding은 사이킷런에서 OneHotEncoder 클래스로 변환이 가능하다. 입력값으로 2차원 데이터가 필요하고, OneHotEncoder를 이용해 변환한 값이 희소 행렬 형태이므로 이를 다시 toarray( ) 메서드를 이용해 밀집행렬로 변환해야된다...

희소행렬?
대부분의 원소가 0으로 채워진 행렬을 의미한다.
반대로 0이 아닌 값이 대부분을 차지하는 행렬을 밀집행렬이라고 한다. 
toarray() 메서드?
희소행렬을 밀집행렬로 변환하는 메서드이다.

 

# one-hot Encoding
from sklearn.preprocessing import OneHotEncoder
import numpy as np
items=['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서']

# 2차원 ndarray로 변환
items=np.array(items).reshape(-1,1)

# 원핫 인코딩 적용
oh_encoder = OneHotEncoder()
oh_encoder.fit(items)
oh_labels = oh_encoder.transform(items)

# OneHotEncoder로 변환한 결과는 희소행렬이므로 toarray()를 이용해 밀집 행렬로 변환.
print('원-핫 인코딩 데이터')
print(oh_labels.toarray())
print('원-핫 인코딩 데이터 차원')
print(oh_labels.shape)

출력결과

8개의 레코드와 1개의 칼럼을 가진 원본 데이터가 8개의 레코드와 6개의 칼럼을 가진 데이터로 변환된 것을 볼 수 있다. 

변환 과정

판다스에서 원-핫 인코딩을 더 쉽게 지원하는 API가 있다. 

get_dummies( ) 을 이용하면 된다. 사이킷런의 OneHotEncoder와 다르게 문자열 카테고리 값을 숫자형으로 변환할 필요 없이 바로 변환할 수 있다. 

import pandas as pd

df=pd.DataFrame({'item':['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서']})
pd.get_dummies(df)

피처 스케일링과 정규화

피처 스케일링은 서로 다른 변수의 값 범위를 일정한 수준으로 맞추는 작업이다. 대표적인 방법으로는 표준화(Standardization)정규화(Normalization)가 있다. 

 

표준화는 데이터의 피처 각각이 평균이 0이고 분산이 1인 가우시안 정규 분포를 가진 값으로 변환하는 것을 의미한다.

일반적으로 정규화는 서로 다른 피처의 크기를 통일하기 위해 크기를 변환해주는 개념이다. 예를 들면, A 칼럼은 거리를 나타내는 변수로서 값이 0~100KM로 주어지고, B 칼럼은 금액을 나타내는 속성으로 값이 0~100,000,000,000원으로 주어진다면 이 변수를 모두 동일한 크기 단위로 비교하기 위해 값을 모두 최소 0 ~ 최대1의 값으로 변환하는 것이다. 즉, 개별 데이터의 크기를 모두 똑같은 단위로 변경하는 것이다.

사이킷런의 Normalizer 모듈(고르게 분배?)과 일반적인 정규화(크기를 통일?)는 약간의 차이가 있다. 

사이킷런의 Normalizer 모듈은 선형대수에서의 정규화 개념이 적용됐으며, 개별 벡터의 크기를 맞추기 위해 변환하는 것을 의미한다.  공식은 다음과 같다:

StandardScaler

표준화 지원 클래스이다. 개별 피처를 평균이 0이고, 분산이 1인 값으로 변환해준다. 

사이킷런에서 SVM, 회귀 모델 등 데이터가 가우시안 분포를 가지고 있다고 가저하고 구현됐기 때문에 사전에 표준화를 적용하는 것은 예측 성능 향상에 중요 요소가 될 수 있다.

import sklearn
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())

StandardScaler 적용 후 ⏬

from sklearn.preprocessing import StandardScaler

# StandardScaler 객체 생성
scaler=StandardScaler()
# StandardScale로 데이터 세트 변환, fit()과 transform() 호출
scaler.fit(iris_df)
iris_scaled=scaler.transform(iris_df)

# transform()시 스케일 변환된 데이터 세트가 Numpy ndarray로 반환돼 이를 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())

모든 칼럼 값의 평균이 0에 아주 가까운 값으로, 그리고 분산은 1에 아주 가까운 값으로 변환됐음을 알 수 있다.

 

MinMaxScaler

이 클래스는 데이터값을 0과 1 사이의 범위 값으로 변환한다. (음수값이 있으면 -1에서 1값으로 변환한다)

데이터의 분포가 가우시안 분포가 아닐경우에 Min, Max Scale을 적용해보면 된다.

더보기

대체 가우시안 분포가 뭥미?

내가 아는 정규분포가 가우시안 분포.....였어...😯

from sklearn.preprocessing import MinMaxScaler

# MinMaxScaler 객체 생성
scaler=MinMaxScaler()
# MinMaxScaler로 데이터 세트 변환, fit()과 transform()호출.
scaler.fit(iris_df)
iris_scaled=scaler.transform(iris_df)

# transform() 시 스케일 변환된 데이터 세트가 Numpy ndarray로 반환돼 이를 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())

모든 피처에 0에서 1 사이의 값으로 변환되는 스케일링이 적용됐음을 알 수 있다.

학습 데이터와 테스트 데이터의 스케일링 변환 시 유의점 (이 부분 이해안됨...🥹 지피티씨가 도와줌)

Scaler 객체를 이용해 데이터의 스케일링 변환 시 fit(), transform(), fit_transform() 메서드를 이용한다.

  • fit( )은 데이터 변환을 위한 기준 정보 설정 적용.
  • transform( )은 이렇게 설정된 정보를 이용해 데이터를 변환.
  • fit_transform( )은 fit( )과 transform( )을 한 번에 적용하는 기능을 수행.

여기서 기억해야할 것은‼️

Scaler 객체를 이용해 학습 데이터 세트로 fit()과 transform()을 적용하면 테스트 데이터 세트로는 다시 fit()을 수행하지 않고 학습 데이터 세트로 fit()을 수행한 결과를 이용해 transform() 변환을 적용해야한다. 

즉, 학습 데이터로 fit()이 적용된 스케일링 기준 정보를 그대로 테스트 데이터에 적용해야 하며, 

그렇지 않고 테스트 데이터로 다시 새로운 스케일링 기준 정보를 만들게 되면 학습 데이터와 테스트 데이터의 스케일링 기준 정보가 서로 달라지기 때문에 올바른 예측 결과를 도출할 수 없다.

 

그니까,, 테스트 데이터는 훈련 데이터와 다르기 때문에 평균과 표준편차가 다르고 새롭게 계산되기 때문에 모델이 일관된 데이터 분포를 학습하지 못해서 성능이 저하될 가능성이 크다는 뜻...???

from sklearn.preprocessing import StandardScaler
import numpy as np

# 예제 데이터 생성 (훈련 데이터와 테스트 데이터)
X_train = np.array([[10], [20], [30], [40], [50]])  # 학습 데이터
X_test = np.array([[60], [70], [80]])  # 테스트 데이터

# StandardScaler 적용 (잘못된 방식)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 학습 데이터에 fit() + transform() 적용
X_test_scaled = scaler.fit_transform(X_test)  # ❌ 테스트 데이터에 fit() + transform() (잘못된 방식)
----------------------------
# 올바른 방법: 학습 데이터에서 fit() 한 후, 테스트 데이터에는 transform()만 적용
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # 학습 데이터에 fit() + transform()
X_test_scaled = scaler.transform(X_test)  # 🚀 테스트 데이터에는 transform()만!

이렇게 하면 학습 데이터에서 계산한 평균과 표준 편차를 유지한채, 테스트 데이터에도 동일한 변환을 적용하게 된다.

 

train data를 fit( ) + transfrom( )하면?⏬

from sklearn.preprocessing import MinMaxScaler
import numpy as np

# 학습데이터는 0부터 10까지, 테스트 데이터는 0부터 5까지 값을 가지는 데이터 세트로 생성
# Scaler 클래스의 fit(), transform()은 2차원 이상 데이터만 가능하므로 reshape(-1,1)로 차원 변경
train_array=np.arange(0,11).reshape(-1,1)
test_array=np.arange(0,6).reshape(-1,1)

# MinMaxScaler 객체에 별도의 feature_range 파라미터 값을 지정하지 않으면 0~1 값으로 변환
scaler=MinMaxScaler()

# fit()하게 되면 train_array 데이터의 최솟값이 0, 최댓값이 10으로 설정
scaler.fit(train_array)

# 1/10 scale로 train_array 데이터 변환함. 원본 10 -> 1로 변환됨
train_scaled=scaler.transform(train_array)

print('원본 train_array 데이터:',np.round(train_array.reshape(-1),2))
print('Scaled된 train_array 데이터:',np.round(train_scaled.reshape(-1),2))

 

test data를 fit( ) + transform( )하면?⏬

# MinMaxScaler에 test_array를 fit()하게 되면 원본 데이터의 최솟값이 0, 최댓값이 5로 설정됨
scaler.fit(test_array)

# 1/5 scale로 test_array 데이터 변환함. 원본 5->1로 변환.
test_scaled=scaler.transform(test_array)

# test_array의 scale 변환 출력.
print('원본 test_array 데이터:',np.round(test_array.reshape(-1),2))
print('Scale된 test_array 데이터:',np.round(test_scaled.reshape(-1),2))

출력결과를 확인하면 학습 데이터와 테스트 데이터의 스케일링이 맞지 않다.

학습 데이터는 최솟값이 0, 최댓값이 10이고 1/10으로, 테스트 데이터의 경우는 최솟값 0, 최댓값 5이므로 1/5으로 스케일링된다. 따라서 학습데이터는 스케일링 변환으로 원본값 2가 0.2로 변환됐고, 원본값 10이 1로 변환되었다. 반면에 테스트는 2가 0.4로 스케일링되었다.. 

ML 모델은 학습 데이터를 기반으로 학습되기 때문에 반드시 테스트 데이터는 학습 데이터의 스케일링 기준에 따라야한다.

 

다음은 올바르게 코드를 구현한 것임 ⏬

scaler=MinMaxScaler()
scaler.fit(train_array)
train_scaled=scaler.transform(train_array)
print('원본 train_array 데이터:',np.round(train_array.reshape(-1),2))
print('Scale된 train_array 데이터:',np.round(train_scaled.reshape(-1),2))

# test_array에 Scale 변환을 할 때는 반드시 fit()을 호출하지 않고 transform()만으로 변환해야함
test_scaled=scaler.transform(test_array)
print('\n원본 test_array 데이터:',np.round(test_array.reshape(-1),2))
print('Scale된 test_array 데이터:',np.round(test_scaled.reshape(-1),2))

맞게 스케일링된 것을 볼 수 있다..!!!

728x90