728x90
반응형
SMALL
728x90
반응형
LIST
728x90
반응형
SMALL

지난 포스팅에서 정규화의 중요성을 다뤘다면, 이번에는 연속형 특성과 범주형 특성에 대하여 알아보도록 하겠습니다.

연속형 특성과 범주형 특성이란?

지금까지 우리가 살펴보고, 사용했던 데이터는 2차원 실수형 배열로 각 열이 데이터 포인트를 설명하는 연속형 특성 - continuous feature을 살펴보았습니다.

하지만 우리가 수집하는 모든 데이터들이 연속형 특성을 띄고 있다고는 장담할 수 없습니다. 일반적인 특성의 전형적인 형태는 범주형 특성 - categorical feature입니다. 또는 이산형 특성 - discrete feature라고도 합니다.

 

이러한 범주형 특성, 이산형 특성들은 보통 숫자 값이 아닙니다. 연속적 특성의 예로 들 수 있는 것은 픽셀 밝기나 붓꽃의 측정값 등을 생각해 볼 수 있고, 범주형 특성은 옷의 브랜드, 색상, 상품 분류 등등이 있습니다. 이러한 특성들은 어떠한 상품을 묘사할 수 있는 특성이긴 하지만, 연속된 값으로 나타낼 수는 없습니다. (어디에 속하는 범주의 의미한다는 이야기)

범주형 특성의 특징은 뭔가 비교를 할 수 없다는 것입니다. 예를 들어 책은 옷보다 크거나 작지 않고, 청바지는 책과 옷 사이에 있지 않죠.

특성 종류 특성 형태
연속형 특성 (양적 - Quantitative): 몸무게, 매출액, 주가 등 5.11121
범주형 특성 (질적 - Qualitative): 성별, 지역, 만족도 등 남/여, 상/중/하

 

 

특성 공학을 이용한 데이터 표현의 중요성

데이터가 어떤 형태의 특성으로 구성되어 있는가 보다 (연속형인지, 범주형인지) 데이터를 어떻게 표현하는지가 머신러닝 모델의 성능에 영향을 더 많이 줍니다. 일전에 했었던 데이터 스케일랑 작업 같은 것들을 의미하는데요, 예를 들어 측정치가 센티미터인지, 인치로 측정을 했는지에 따라서 머신러닝 모델이 인식하는 데에 차이가 생기기 시작합니다.

또는 확장된 보스턴 데이터셋처럼 각 특성의 상호작용( 특성 간의 곱 )이나 일반적인 다항식을 추가 특성으로 넣는 것이 도움이 될 때도 있습니다.

이처럼 특성 애플리케이션에 가장 적합한 데이터 표현을 찾는 것을 특성 공학 - feature engineering이라고 합니다. 데이터 분석을 할 때 데이터 과학자와 머신러닝 기술자가 실제 문제를 풀기 위해 당면하는 주요 작업 중 하나입니다.

올바른 데이터 표현은 지도 학습 모델에서 적절한 매개변수를 선택하는 것보다 성능에 더 큰 영향을 미칠 때가 많습니다.

 

 

범주형 변수

범주형 변수를 알아보기 위해 예제 데이터셋을 판다스로 불러와서 사용해 보도록 하겠습니다. 1994년 인구조사 데이터베이스에서 추출한 미국 성인의 소득 데이터셋의 일부입니다. adult 데이터셋을 사용해 어떤 근로자의 수입이 50,000 달러를 초과하는지, 이하일지 예측하는 모델을 만드려고 합니다.

import pandas as pd

data = pd.read_csv('./data/adult.csv', encoding='utf-8')
display(data)

Result of data_set

위 데이터셋은 소득(income)이 <=50와 > 50K라는 두 클래스를 가진 분류 문제로 생각해 볼 수 있습니다. 정확한 소득을 예측해 볼 수도 있겠지만, 그것은 회귀 문제가 됩니다.

어찌 됐든 이 데이터셋에 있는 age와 hours-per-week는 우리가 다뤄봤었던 연속형 특성입니다. 하지만 workclass, education, gender, occupation은 범주형 특성입니다. 따라서 이런 특성들은 어떤 범위가 아닌 고정된 목록 중 하나를 값으로 가지며, 정량적이 아니고 정성적인 속성입니다.

 

맨 먼저 이 데이터에 로지스틱 회귀 분류기를 학습하면 지도 학습에서 배운 공식이 그대로 사용될 것입니다.

𝑦̂ =𝑤[0]∗𝑥[0]+𝑤[1]∗𝑥[1]+...+𝑤[𝑝]∗𝑥[𝑝]+𝑏

위 공식에 따라 𝑥[i]는 반드시 숫자여야 합니다. 즉 𝑥[1]은 State-gov나 Self-emp-not-inc 같은 문자열 형태의 데이터가 올 수 없다는 이야기입니다. 따라서 로지스틱 회귀를 사용하려면 위 데이터를 다른 방식으로 표현해야 할 것 같습니다. 이제부터 이 문제들을 해결하기 위한 방법에 대해 이야기해 보겠습니다.

 

 

범주형 데이터 문제열 확인하기

데이터셋을 읽고 나서 먼저 어떤 열에 어떤 의미 있는 범주형 데이터가 있는지 확인해 보는 것이 좋습니다. 입력받은 데이터를 다룰 때는 정해진 범주 밖의 값이 있을 수도 있고, 철자나 대소문자가 틀려서 데이터를 전처리 해야 할 수도 있을 것입니다. 예를 들어 사람에 따라 남성을 "male"이나 "man"처럼 다르게 표현할 수 있을 수 있기 때문에 이들을 같은 범주의 데이터로 인식시켜 보아야 합니다.

가장 좋은 방법은 pandas에서 value_counts() 메소드를 이용해 각 Series에 유일한 값이 몇 개씩 있는지를 먼저 출력해 보는 것입니다.

import os
import mglearn
#mglearn에서 adult 데이터셋 불러오기
data = pd.read_csv(
    os.path.join(mglearn.datasets.DATA_PATH, 'adult.data'),
    header=None, index_col=False,
    names=['age','workclass','fnlwgt','education','education-num','marital-status','occupation', 'relationship',
           'race','gender','capital-gain', 'capital-loss','hours-per-week', 'native-country', 'income'])
data = data[['age','workclass','education','gender','hours-per-week', 'occupation', 'income']]
print(data.gender.value_counts())

Gender data_set

다행스럽게도 위 데이터셋에는 정확하게 Male과 Female을 가지고 있어서 원-핫-인코딩으로 나타내기 굉장히 좋은 형태입니다. 실제 애플리케이션에서는 모든 열을 살펴보고 그 값들을 확인해야 합니다.

pandas에서는 get_dummies 함수를 사용해 데이터를 매우 쉽게 인코딩할 수 있습니다. get_dummies 함수는 객체 타입(object 또는 문자열 타입 같은 범주형을 가진 열을 자동으로 반환해 줍니다.

print("원본 특성:\n", list(data.columns), "\n")
data_dummies = pd.get_dummies(data)
print("get_dummies 후의 특성:\n", list(data_dummies.columns))

get dummies() method

연속형 특성인 age와 hours-per-week는 그대로이지만 범주형 특성은 값마다 새로운 특성으로 확장되었습니다. 즉 새로운 열이 추가되겠네요.

data_dummies.head()

data_dummies data_set

data_dummies의 values 속성을 이용해 DataFrame을 NumPy 배열로 바꿀 수 있으며, 이를 이용해 머신러닝 모델을 학습시킵니다. 모델을 학습시키기 전에 이 데이터로부터 우리가 예측해야 할 타깃값인 income으로 시작되는 열을 분리해야 합니다. 출력값이나 출력값으로부터 유도된 변수를 특성 표현에 포함하는 것은 지도학습 모델을 만들 때 특히 저지르기 쉬운 실수입니다.

features = data_dummies.loc[:, 'age':'occupation_ Transport-moving']
# Numpy 배열 추출하기
X = features.values
y = data_dummies['income_ >50K'].values
print('X.shape: {} y.shape: {}'.format(X.shape, y.shape))

set X, y for test

이제 이 데이터는 scikit-learn에서 사용할 수 있는 형태가 되었으므로, 이전과 같은 방식을 사용하여 예측을 해볼 수 있습니다.

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

X_train, X_test, y_train,y_test = train_test_split(X, y, random_state=0)
logreg = LogisticRegression()
logreg.fit(X_train, y_train)
print("테스트 점수: {:.2f}".format(logreg.score(X_test, y_test)))

 

숫자로 표현된 범주형 특성

데이터 취합 방식에 따라서 범주형 데이터가 숫자로 취합된 경우도 생깁니다. 범주형 변수가 숫자라고 해서 연속적으로 다뤄도 된다는 의미는 아닙니다. 여러분들이 머신러닝에 사용할 데이터셋을 봤을 때, 순서를 나타낸 숫자가 아닌 단순히 범주를 나타내기 위한 숫자라는 사실을 확인하였으면, 이 값은 이산적이기 때문에 연속형 변수로 다루면 안 된다고 생각해야 합니다.

 

pandas의 get_dummis 함수는 숫자 특성은 모두 연속형이라고 생각해서 가변수를 만들지 않습니다. 대신 어떤 열이 연속형인지 범주형인지를 지정할 수 있는 scikit-learn의 OneHotEncoder를 사용해 DataFrame에 있는 숫자로 된 열을 문자열로 바꿀 수도 있습니다. 간단한 예를 보겠습니다.

# 숫자 특성과 범주형 문자열 특성을 가진 DataFrame 만들기
demo_df = pd.DataFrame({'숫자 특성' : [0, 1, 2, 1],
                        '범주형 특성' : ['양말','여우','양말','상자']})
demo_df

단순하게 get_dummies만 사용하면 문자열 특성만 인코딩 되며 숫자 특성은 바뀌지 않습니다.

pd.get_dummies(demo_df)

숫자 특성도 가변수로 만들고 싶다면 columns 매개변수에 인코딩 하고 싶은 열을 명시해야 합니다.

# 숫자 특성을 문자열로 변환
demo_df['숫자 특성'] = demo_df['숫자 특성'].astype(str)
pd.get_dummies(demo_df, columns=['숫자 특성', '범주형 특성'])

지금까지 원 핫 인코딩에 대하여 알아보았습니다.

다음 포스팅에서는 구간분할과 이산화 그리고 상호작용과 다항식에 대하여 알아보도록 하겠습니다!

728x90
반응형
LIST
728x90
반응형
SMALL

이번 포스팅은 데이터 정규화(Normalisation)에 대해서 알아보도록 하겠습니다.

 

정규화(Normalisation)가 중요한 이유?

머신러닝 알고리즘은 데이터가 가진 feature(특성)들을 비교하여 데이터의 패턴을 찾습니다.

그런데 여기서 주의해야 할 점은 데이터가 가진 feature의 스케일이 심하게 차이가 나는 경우 문제가 되기 때문이다.

데이터의 차이는 모델의 성능을 좌우하기 때문에 신경망 같은 알고리즘들은 데이터의 스케일에 굉장히 민감합니다.

그래서 알고리즘들에 맞게 데이터의 특성 값들을 조절하게 되는데요, 보통 특성마다 스케일을 조정하여 데이터를 변경시킵니다.

예시를 한번 보겠습니다.

우선, 기본적으로 필요한 라이브러리를 임포트 해줍니다.

 

왼편의 그림은 데이터를 전처리 하기 전인 원본 데이터, 오른쪽은 각종 전처리 방법을 사용했을 때의 4가지 모습을 보여주고 있습니다.

본격적으로 전처리 방법에 대해 간단히 이야기해보겠습니다.

 

StandardScaler

scikit-learn의 StandardScaler는 각 특성의 평균을 0, 분산을 1로 변경하여 모든 특성이 같은 크기를 가지게 합니다. 하지만 최솟값과 최댓값 크기를 제한하지는 않습니다. 공식은 다음과 같습니다.

σ는 표준편차를 의미하고, x는 특성 값, 평균을 의미합니다. 이 결과를 표준 점수 또는 z-점수(z-score)라고도 합니다.

RobustScaler

RobustScaler는 일단 특성들이 같은 스케일을 갖게 된다는 것은 StandaradScaler와 비슷하나, 평균과 분산 대신 중간값 mediummedium과 사분위 값 quartilequartile을 사용합니다.

여기서 중간값이란 x보다 작은 수가 절반이고, x보다 큰 수가 절반인 x를 의미합니다. 단순히 평균이라고도 생각할 수 있는데요, 좌표 평면을 활용하기 때문에 각 사분위 별 개수가 중요합니다.

1 사분위 값은 x보다 작은 수가 전체 개수의 1/4인 x이고, 3 사분위는 x보다 큰 수가 전체 개수의 1/4인 x를 의미합니다. 공식은 다음과 같습니다.

q2는 중간값, q1은 1 사분위 값, q3은 3 사분위 값입니다.

이러한 방식 때문에 RobustScaler는 전체 데이터와 아주 동떨어진 데이터 포인트에 영향을 받지 않습니다. 이런 이상한 (멀리 떨어진 데이터)를 이상치(outlier) 라고 합니다. 이는 다른 스케일 조정 기법에서는 문제가 될 수도 있습니다.

MinMaxScaler

MinMaxScaler는 모든 특성이 정확하게 0과 1 사이에 위치하도록 데이터를 변경합니다. 2차원 데이터셋일 경우에는 모든 데이터가 x축의 0 ~ 1, y축은 0 ~ 1 사이의 사각 영역에 담기게 됩니다. 공식은 다음과 같습니다.

Normalizer

Normalizer는 매우 다른 스케일 조정 기법입니다. 이 방식은 특성 벡터의 유클리디안 길이가 1이 되도록 데이터 포인트를 조정합니다. 다른 정규 기법인 StandardScaler, RobustScaler, MinMaxScaler 기법은 특성들의 열 (특성)의 통계치를 사용하지만, Normalizer는 행(데이터 포인트)마다 각기 정규화됩니다.

다른 말로 하면 지름이 1인 원( 3차원 일 땐 구 )에 데이터 포인트를 투영하게 됩니다. 이 말은 각 데이터 포인트가 다른 비율로 (길이에 반비례하여) 스케일이 조정된다는 뜻이 됩니다. 이러한 정규화(normalizationnormalization)는 특성 벡터의 길이는 상관없고 데이터의 방향(또는 각도) 만이 중요할 때 많이 사용합니다.

데이터 변환 적용하기

여러 종류의 변환을 알아보았습니다. cancer 데이터셋에 커널 SVM(SVC)를 적용시키고 MinMaxScaler를 적용시켜 보겠습니다.

sklearn.preprocessing 모듈은 사이킷런에서 각종 전처리 작업을 위한 파이썬 클래스를 내장하고 있습니다.

MinMaxScaler 내부에는 fit 메서드가 존재합니다.

이 메서드는 모델을 훈련시키기 위해 존재하는 메서드가 아닌 각 특성마다의 최소, 최댓값을 계산해 줍니다.

따라서 특성을 의미하는 X_train만 넘겨줍니다.(y_train)은 사용하지 않습니다.

실제 fit 메서드를 통한 변환을 적용시키려면 transform() 메서드를 사용합니다.

transform은 새로운 데이터 표현(representation)을 만들 때 사용하는 메서드입니다.

변환된 배열 크기(shape)는 원본 배열과 동일합니다.

스케일 조정 후를 살펴보면 모든 특성의 최솟값과 최댓값이 각각 0과 1로 바뀐 것이 확인됩니다.

이 데이터에 SVM 모델을 적용하려면 테스트 데이터셋도 스케일 조정이 되어야 합니다. 이번엔 X_test를 조정하겠습니다.

이상하게도 똑같이 스케일을 조정했지만 0 또는 1이 나오지 않습니다. 그리고 0과 1의 범위를 넘어가 버리는 값도 존재하는 것이 확인됩니다.

모든 스케일 모델들은 항상 훈련 세트와 테스트 세트에 같은 변환을 적용시켜야 합니다. transform 메서드는 테스트 세트의 최솟값과 범위를 사용하지 않고, 항상 훈련 세트의 최솟값을 빼고 훈련 세트의 범위로 나눕니다. 간단하게 공식으로 살펴보면 다음과 같습니다.

훈련 데이터와 테스트 데이터의 스케일을 같은 방법으로 조정하기

지도 학습 모델에서 테스트 세트를 사용하려면 훈련 세트와 테스트 세트에 같은 변환을 적용해야 한다는 점이 중요합니다.

일단 이번 예제에서는 위와 반대로, 테스트 세트의 최솟값과 범위를 사용했을 때 어떤 일들이 일어나는지 보겠습니다.

from sklearn.datasets import make_blobs
# 인위적인 데이터 생성하기
X, _ = make_blobs(n_samples=50, centers=5, random_state=4, cluster_std=2)
# 훈련 세트와 테스트 세트로 나누기
X_train, X_test = train_test_split(X, random_state=5, test_size=.1)

# 훈련 세트와 테스트 세트의 산점도 그리기
fig, axes = plt.subplots(1,3,figsize=(13, 4))
axes[0].scatter(X_train[:, 0], X_train[:, 1], c=mglearn.cm2(0), label="훈련 세트", s=60)
axes[0].scatter(X_test[:, 0], X_test[:, 1],c=mglearn.cm2(1), label="테스트 세트", s=60, marker='^')
axes[0].legend(loc='upper left')
axes[0].set_title("원본 데이터")

# MinMaxScaler를 사용해 스케일 조정하기
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled  = scaler.transform(X_test)

# 스케일이 조정된 데이터의 산점도 그리기
axes[1].scatter(X_train_scaled[:, 0], X_train_scaled[:, 1], c=mglearn.cm2(0), label="훈련 세트", s=60)
axes[1].scatter(X_test_scaled[:, 0], X_test_scaled[:, 1], c=mglearn.cm2(1), label="테스트 세트", s=60, marker='^')
axes[1].set_title("스케일 조정된 데이터")


# 테스트 세트의 스케일을 따로 조정하기
# 테스트 세트의 최솟값은 0, 최댓값이 1이 됨
# 절대로 이렇게 사용하지 말것!

test_scaler = MinMaxScaler()
test_scaler.fit(X_test)
X_test_scaled_badly = test_scaler.transform(X_test)

# 잘못 조정된 데이터의 산점도 그리기
axes[2].scatter(X_train_scaled[:, 0], X_train_scaled[:, 1], c=mglearn.cm2(0), label="훈련 세트", s=60)
axes[2].scatter(X_test_scaled_badly[:, 0], X_test_scaled_badly[:, 1], 
                                    marker='^', c=mglearn.cm2(1), label="테스트 세트", s=60)
axes[2].set_title("잘못 조정된 데이터")

for ax in axes:
    ax.set_xlabel("특성 0")
    ax.set_ylabel("특성 1")

첫 번째 그림은 단순히 2차원 원본 데이터셋이며, 훈련 세트는 파란 동그라미, 테스트 세트는 빨간 세모로 표시합니다.

두 번째 그래프는 같은 데이터를 MinMaxScaler를 이용해 스케일을 조절하였는데, 각 그래프의 눈금 빼고는 모양이 변한 것이 없습니다.

하지만, 테스트 데이터(세모)의 최솟값과 최댓값이 0과 1은 아닙니다.

문제는 세 번째 데이터입니다. 훈련 세트와 테스트 세트에 대해 각각 스케일을 조정한 경우, 각 데이터셋이 서로 다르게 스케일 되었기 때문에 삼각형의 배치가 뒤죽박죽 되었습니다.

지도 학습에서 데이터 전처리 효과

다시 cancer 데이터셋으로 돌아와서, SVC를 학습시킬 때 MinMaxScaler의 효과를 확인해 보도록 하겠습니다.

from sklearn.svm import SVC

X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0)

svm = SVC(C=100)
svm.fit(X_train, y_train)
print("테스트 세트 정확도: {:.2f}".format(svm.score(X_test, y_test)))

스케일이 조정되지 않은 원본 데이터의 정확도는 63%의 정확도 밖에 표현하지 못합니다. 다음은 스케일 조정 후의 학습 결과입니다.

# MinMaxScaler 사용
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled  = scaler.transform(X_test)

# 조정된 데이터로 SVM 학습
svm.fit(X_train_scaled, y_train)

# 스케일 조정된 테스트 세트의 정확도
print("스케일 조정된 테스트 세트의 정확도: {:.2f}".format(svm.score(X_test_scaled, y_test)))

단순히 스케일만 조정했을 뿐인데 그 효과는 매우 큰 것이 확인됩니다.

스케일을 조정하는 모든 파이썬 클래스들은 fit과 transform을 제공하므로 손쉽게 전처리 알고리즘을 바꿔낼 수 있습니다.

# 평균 0, 분선 1을 갖도록 스케일 조정하기
from sklearn.preprocessing import StandardScaler

# StandaradScaler 사용
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled  = scaler.transform(X_test)

# 조정된 데이터로 SVM 학습
svm.fit(X_train_scaled, y_train)

# 스케일 조정된 테스트 세트의 정확도
print("스케일 조정된 테스트 세트의 정확도: {:.2f}".format(svm.score(X_test_scaled, y_test)))

지금까지 데이터 학습 훈련을 위한 스케일링에 대하여 알아보았습니다.

다음 포스팅에서는 One-Hot-Encoding에 대하여 알아보도록 하겠습니다.

728x90
반응형
LIST

+ Recent posts