머신러닝 알고리즘을 위한 특성 스케일링

데이터에 적용할 가장 중요한 변환 중 하나가 특성 스케일링(Feature Scaling)입니다. 

머신러닝 알고리즘은 입력 숫자 특성들의 스케일이 많이 다르면 잘 작동하지 않습니다. 주택 가격 데이터도 이에 해당합니다. 즉, 전체 방 개수의 범위는 6에서 39,320인 반면 중간 소득의 범위는 0에서 15까지 입니다. 타깃 값에 대한 스케일링은 일반적으로 불필요합니다.

모든 특성의 범위를 같도록 만들어주는 방법으로 min-max 스케일링(정규화, Normalization)표준화(Standardization)가 널리 사용됩니다.

min-max 스케일링은 매우 간단합니다. 0~1 범위에 들도록 값을 이동하고 스케일을 조정하면 됩니다. 데이터에서 최소값을 뺀 후 최대값과 최소값의 차이로 나누면 이렇게 할 수 있습니다. 사이킷런에는 이에 해당하는 MinMaxScaler 변환기를 제공합니다. 0~1사이를 원하지 않는다면 feature_range 매개변수로 범위를 조정할 수 있습니다.

표준화는 많이 다릅니다. 먼저 평균을 뺀 후 표준편차로 나누어 결과 분포의 분산이 1이 되도록 합니다. 범위의 상한과 하한이 없어 어떤 알고리즘에서는 문제가 될 수 있습니다. 그러나 표준화는 이상치에 영향을 덜 받습니다. 사이킷런에는 표준화를 위한 StandardScaler 변환기가 있습니다.

*모든 변환기에서 스케일링은 (테스트 세트가 포함된) 전체 데이터가 아닌 훈련 데이터에 대해서만 fit() 함수를 적용해야 합니다. 이후 훈련 세트와 테스트 세트에 대해 transform() 함수를 사용합니다.



1. 변환 파이프라인

머신 러닝을 하기 위한 수많은 변환 단계는 정확한 순서대로 실행되어야 합니다. 사이킷런에는 연속된 변환을 순서대로 처리할 수 있도록 도와주는 Pipeline 클래스가 있습니다.

Code

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

num_pipeline = Pipeline([
('imputer', Imputer(strategy = "median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),
])

housing_num_tr = num_pipeline.fit_transform(housing_num)



Pipeline은 연속된 단계를 나타내는 이름/추정기 쌍의 목록을 입력으로 받습니다. 마지막 단계에는 변환기와 추정기를 모두 사용할 수 있고 그 외에는 모두 변환기여야 합니다. (즉, fit_transform() 함수를 가지고 있어야 합니다.)

파이프라인의 fit() 함수를 호출하면 모든 변환기의 fit_transform() 함수를 순서대로 호출하면서 한 단계의 출력을 다음 단계의 입력으로 전달합니다. 마지막 단계에서는 fit() 함수만 호출합니다.

파이프라인 객체는 마지막으로 추정기와 동일한 함수를 제공합니다. 이 예에서는 마지막 추정기가 변환기 StandardScaler이므로 파이프라인이 데이터에 대해 모든 변환을 순서대로 적용하는 transform() 함수를 가지고 있습니다. 

수치형 컬럼을 넘파이 배열로 추출하는 대신 판다스의 데이터프레임을 파이프라인에 직접 주입할 수 있다면 좋을 것 같습니다. 사이킷런이 판다스의 데이터프레임을 다룰 수는 없지만 이를 처리하는 변환기를 직접 만들 수 있습니다.

Code

from sklearn.base import BaseEstimator, TransformerMixin
class DataFrameSelector(BaseEstimator, TransformerMixin):
def __init__(self, attributes_names):
self.attributes_names = attributes_names

def fit(self, X, y = None):
return self

def transform(self, X):
return X[self.attributes_names].values

DataFrameSelector는 나머지는 버리고 필요한 특성을 선택하여 데이터프레임을 넘파이 배열로 바꾸는 식으로 데이터를 변환합니다. 이를 이용해 데이터프레임을 받아 수치형만 다루는 파이프라인을 손쉽게 만들 수 있습니다. 수치형 특성을 선택한 DataFrameSelector로 파이프라인을 시작해서 앞서 이야기한 다른 전처리 단계들을 나열합니다. 범주형 특성을 다루는 또 다른 파이프라인도 DataFrameSelector로 범주형 특성을 선택하고 CategoricalEncoder를 적용하면 간단합니다.

Code

from sklearn.pipeline import FeatureUnion
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]

num_pipeline = Pipeline([
('selector', DataFrameSelector(num_attribs)),
('imputer', Imputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),
])

cat_pipeline = Pipeline([
('selector', DataFrameSelector(cat_attribs)),
('cat_encoder', CategoricalEncoder(encoding="onehot-dense"))
])

full_pipeline = FeatureUnion(transformer_list=[
("num_pipeline", num_pipeline),
("cat_pipeline", cat_pipeline),
])

housing_prepared = full_pipeline.fit_transform(housing)
print(housing_prepared)

전체 파이프라인을 간단하게 실행할 수 있습니다.



References

  • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018


머신러닝 알고리즘을 위한 데이터 준비

이전 포스팅 '머신러닝을 위한 데이터 가져오기', '머신러닝을 위한 테스트 세트 만들기'에 이어 진행됩니다.

머신러닝 알고리즘을 위해 데이터를 준비할 차례입니다. 이러한 작업들은 아래와 같은 이유로 함수를 통해 자동화됩니다.

  • 어떤 데이터셋에 대해서도 데이터 변환을 쉽게 반복할 수 있습니다.
  • 향후 프로젝트에 사용할 수 있는 변환 라이브러리를 점진적으로 구축하게 됩니다.
  • 실제 시스템에서 알고리즘에 새 데이터를 주입하기 전에 변환시키는 데 이 함수를 이용합니다.
  • 여러 가지 데이터 변환을 쉽게 시도해볼 수 있고 어떤 조합이 가장 좋은지 확인하는 데 편리합니다.

먼저 원래 훈련 세트로 복원하고, 예측 변수와 타깃 값에 같은 변형을 적용하지 않기 위해 예측 변수와 레이블을 분리하도록 하겠습니다.

Code

housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()



1. 데이터 정제

대부분의 머신러닝 알고리즘은 누락된 특성을 다루지 못하므로 이를 처리할 수 있는 함수를 몇 개 만들도록 하겠습니다. 방법은 아래와 같이 세 가지입니다.

  • 해당 구역을 제거합니다.
  • 전체 특성을 삭제합니다.
  • 어떤 값으로 채웁니다. (0, 평균값, 중간값 등)


데이터 프레임의 dropna(), drop(), fillna() 함수를 이용하면 이런 작업들을 간단히 처리할 수 있습니다.

Code

housing.dropna(subset=["total_bedrooms"]) # Option 1
housing.drop("total_bedrooms", axis=1) # Option 2
median = housing["tottal_bedrooms"].median() # Option 3
housing["total_bedrooms"].fillna(median, inplace=True)



하지만, 사이킷런의 Imputer는 누락된 값을 손쉽게 다루도록 해줍니다. 먼저 누락된 값을 특성의 중간값으로 대체한다고 지정하여 Imputer 객체를 생성합니다. 중간값이 수치형 특성에게만 계산될 수 있기 때문에 텍스트 특성인 ocean_proximity를 제외한 데이터 복사본을 생성합니다. 이후 imputer 객체의 fit() 함수를 사용해 훈련 데이터에 적용합니다.

imputer는 각 특성의 중간값을 계산하여 그 결과를 객체의 statistics_ 속성에 저장합니다. total_bedrooms 특성에만 누락된 값이 있지만, 나중에 시스템이 서비스될 때 새로운 데이터에서 어떤 값이 누락될지 확신할 수 없으므로 모든 수치형 특성에 imputer를 적용하는 것이 바람직할 것입니다.

Code

from sklearn.preprocessing import Imputer

imputer = Imputer(strategy="median")
housing_num = housing.drop("ocean_proximity", axis=1)
imputer.fit(housing_num)

print(imputer.statistics_)
print(housing_num.median().values)

Output

imputer 객체에 중간값이 정상적으로 학습된 것을 확인할 수 있습니다.



이제 학습된 imputer 객체를 사용해 훈련 세트에서 누락된 값을 학습한 중간값으로 바꿀 수 있습니다.

Code

X = imputer.transform(housing_num)
housing_tr = pd.DataFrame(X, columns=housing_num.columns,
index=list(housing.index.values))



2. 텍스트와 범주형 특성 다루기

앞서 살펴본 범주형 특성 ocean_proximity가 텍스트라 중간값을 계산할 수 없었습니다.

대부분의 머신러닝 알고리즘은 숫자형을 다루므로 이 카테고리를 텍스트에서 숫자로 바꾸도록 하겠습니다.
이를 위해 각 카테고리를 다른 정숫값으로 매핑해주는 판다스의 factorize() 함수를 사용합니다.

Code

housing_cat = housing["ocean_proximity"]
housing_cat_encoded, housing_categories = housing_cat.factorize()
housing_cat_encoded[:10]

print(housing_cat_encoded)

Output



위와 같은 표현 방식의 문제는 머신러닝 알고리즘이 가까이 있는 두 값이 떨어져 있는 두 값보다 더 비슷하다고 생각한다는 점입니다. 
이 문제는 일반적으로 카테고리별 이진 특성을 만들어 해결합니다. 한 특성만 1이고 나머지는 0이므로 이를 원-핫 인코딩(One-Hot Encoding)이라고 부릅니다.

사이킷런은 숫자로 된 범주형 값을 원-핫 벡터로 바꿔주는 OneHotEncoder를 제공합니다. 카테고리들을 원-핫 벡터로 인코딩 해보겠습니다. fit_transform() 함수는 2차원 배열을 넣어줘야 하는데 housing_cat_encoded는 1차원 배열이므로 구조를 바꿔주어야 합니다. 또한 출력 형태가 사이파이(Scipy) 희소 행렬입니다. 이는 수천 개의 카테고리가 있는 범주형 특성일 경우 매우 효율적입니다. 이런 특성을 원-핫 인코딩하면 열이 수천 개인 행렬로 변하고 각 행은 1이 하나뿐이고 그 외에는 모두 0으로 채워져 있을 것입니다. 0을 모두 메모리에 저장하는 것은 낭비이므로 희소 행렬은 0이 아닌 원소의 위치만 저장합니다. 이 행렬을 거의 일반적인 2차원 배열처럼 사용할 수 있지만 넘파이 배열로 바꾸려면 toarray() 함수를 호출하면 됩니다.

Code

from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
print(housing_cat_1hot.toarray())

Output




References

  • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018



머신러닝을 위한 테스트 세트 만들기

이전에 포스팅 했던 '머신러닝을 위한 데이터 가져오기'에 이어 진행됩니다.

데이터를 더 깊게 들여다보기 이전에 테스트 세트를 따로 떼어 놓아야 합니다.
테스트 세트는 추후 훈련된 데이터를 평가하는데 사용되기 때문에 해당 데이터 세트는 훈련에 사용되어서는 안됩니다.


1. 테스트 세트 만들기

테스트 세트를 만드는 일은 이론적으로 매우 간단합니다. 무작위로 어떠한 샘플을 선택하여 데이터셋의 20% 정도를 떼어 놓으면 됩니다.
아래는 데스트 세트를 생성하는 코드입니다.

Code

import numpy as np

def split_train_test(data, test_ratio):
shuffled_indices = np.random.permutation(len(data))
test_set_size = int(len(data) * test_ratio)
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]

return data.iloc[train_indices], data.iloc[test_indices]


train_set, test_set = split_train_test(housing, 0.2)
print(len(train_set), "train +", len(test_set), "test")

Output

전체 20930개의 데이터가 16512개의 훈련 데이터, 4128개의 테스트 데이터로 나뉜 것을 확인할 수 있습니다.



하지만, 위의 코드는 완벽하지 않습니다. 프로그램을 다시 실행하면 다른 테스트 세트가 생성되기 때문입니다.
여러 번 계속하면 전체 데이터 셋을 보는 셈이므로 이러한 상황은 피하는 것이 좋습니다.

따라서, 일반적인 해결책으로 샘플의 식별자를 사용하여 테스트 세트로 보낼지 말지를 정하는 것입니다.
각 샘플마다 식별자의 해시값을 계산하여 해시의 마지막 바이트 값이 51보다 작거나(256의 20%정도) 같은 샘플만 테스트 세트로 보낼 수 있습니다.
새로운 테스트 세트는 새 샘플의 20%를 갖게 되지만, 이전에 훈련 세트에 있던 샘플은 포함시키지 않을 것입니다.

안타깝게도 주택 데이터셋에는 식별자 컬럼이 없습니다. 대신 행의 인덱스를 ID로 사용하면 간단히 해결됩니다.
행의 인덱스를 고유 식별자로 사용할 때 새 데이터는 데이터셋의 끝에 추가되어야 하고 어떤 행도 삭제되지 않아야 합니다.
이것이 불가능할 땐 고유 식별자를 만드는 데 안전한 특성을 사용해야 합니다. (예를 들어 위도와 경도)

다음은 이를 구현한 코드입니다.

Code

from zlib import crc32
def test_set_check(identifier, test_ratio):
return crc32(np.int64(identifier)) & 0xffffffff < test_ratio * 2**32


def split_train_test_by_id(data, test_ratio, id_column):
ids = data[id_column]
in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio))
return data.loc[~in_test_set], data.loc[in_test_set]


housing_with_id = housing.reset_index()
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")

housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")



위와 같은 복잡한 과정 없이 사이킷런에서는 데이터셋을 여러 서브셋으로 나누는 다양한 방법을 제공합니다. 
가장 간단한 함수는 train_test_split으로, 앞서 우리가 만든 split_train_test와 아주 비슷하지만 두 가지 특징이 있습니다.

첫째, 앞서 설명한 난수 초기값을 지정할 수 있는 random_state 매개변수가 있고,
둘째, 행의 개수가 같은 여러 개의 데이터셋을 넘겨 같은 인덱스를 기반으로 나눌 수 있습니다.

코드는 아래와 같습니다.

Code

from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
print(len(train_set), "train +", len(test_set), "test")

Output


split_train_test() 함수와 동일한 결과를 얻을 수 있는 것을 확인했습니다.


지금까지는 순수한 무작위 샘플링 방식을 살펴 보았습니다. 데이터셋이 충분히 크다면 일반적으로 괜찮지만, 그렇지 않다면 샘플링 편향이 생길 가능성이 큽니다.
따라서, 전체를 대표할 수 있는 샘플들을 선택하기 위해 노력해야 합니다. 미국 인구의 51.3%가 여성이고 48.7%가 남성이라면, 잘 구성된 설문조사는 샘플에서도 이 비율을 유지해야 합니다. 이를 계층적 샘플링(Stratified Sampling) 이라고 합니다.

전체 모수는 계층(Strata) 이라는 동질의 그룹으로 나뉘고, 테스트 세트가 전체 모수를 대표하도록 각 계층에서 올바른 수의 샘플을 추출합니다.
기본 무작위 샘플링을 사용하면 49%보다 적거나 54%보다 많은 여성이 테스트 세트에 들어갈 확률이 약 12%입니다. 어느 방법을 사용하든 설문조사 결과를 편향시키게 됩니다.

따라서, 계층별로 데이터셋에 충분한 샘플 수가 있어야 합니다. 그렇지 않으면 계층의 중요도를 추정하는 데 편향이 발생할 수 있습니다.
이 말은 너무 많은 계층으로 나누면 안 된다는 뜻이고 각 계층이 충분히 커야 합니다. 다음 코드는 중간 소득을 1.5로 나누고(소득의 카테고리 수 제한),
ceil 함수를 사용하여 반올림해 소득 카테고리 특성을 만들고(이산적인 카테고리를 만들기 위해), 5보다 큰 카테고리는 5로 합칩니다.

아래의 코드를 통해 확인할 수 있습니다.

Code

housing["income_cat"] = np.ceil(housing["median_income"] / 1.5)
housing["income_cat"].where(housing["income_cat"] < 5, 5.0, inplace=True)

housing["income_cat"].hist(bins=50, figsize=(20, 15))
plt.show()

Output

소득 카테고리가 5개로 나뉜 히스토그램을 확인할 수 있습니다.



이제 소득 카테고리를 기반으로 계층 샘플링을 할 준비가 되었습니다. 사이킷런의 Stratified ShuffleSplit을 사용할 수 있습니다.

Code

from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

for train_index, test_index in split.split(housing, housing["income_cat"]):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]

print(housing["income_cat"].value_counts() / len(housing))

Output

전체 주택 데이터셋에서 소득 카테고리의 비율을 확인할 수 있습니다. 



비슷한 코드로 테스트 세트에 있는 소득 카테고리의 비율을 측정합니다. 아래의 그림 Fig. 1은 전체 데이터셋과 계층 샘플링으로 만든 테스트 세트에서 소득 카테고리 비율을 비교했습니다. 그림에서 보듯 계층 샘플링을 사용해 만든 테스트 세트가 전체 데이터셋에 있는 소득 카테고리의 비율과 거의 같습니다. 반면 일반 무작위 샘플링으로 만든 테스트 세트는 비율이 많이 달라졌습니다.


< Fig. 1. 계층 샘플링과 순수한 무작위 샘플링의 편향 비교 >



References

  • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018


머신러닝을 위한 데이터 가져오기

머신러닝을 배울 때는 인공적으로 만들어진 데이터셋이 아닌 실제 데이터로 실험해보는 것이 가장 좋습니다.
아래의 목록은 여러 분야에 걸친 공개된 데이터셋을 얻을 수 있는 홈페이지들입니다.

  • UC Irvine 머신러닝 저장소 (http://archive.ics.uci.edu/ml/)
  • Kaggle Dataset (http://www.kaggle.com/datasets)
  • Amazon AWS Dataset (http://aws.amazon.com/ko/datasets
  • Wiki Dic. (https://goo.gl/SJHN2k)
  • Quora.com (http://goo.gl/zDR78y)
  • Dataset Subreddit(http://www.reddit.com/r/datasets)

데이터 실습으로 StatLib 저장소에 있는 캘리포니아 주택 가격 (California Housing Prices) 데이터셋을 사용할 예정입니다. 
해당 데이터셋은 1990년 캘리포니아 인구조사 데이터를 기반으로 합니다. 



1. 데이터 가져오기

아래의 코드는 데이터를 추출하는 코드입니다. 

fetch_housing_data()를 호출하면 작업공간에 datasets/housing 디렉토리를 만들고 housing.tgz파일을 내려받고 같은 디렉토리에 압축을 풀어 housing.csv파일을 만듭니다.
import os
import tarfile
from six.moves import urllib

DOWNLOAD_ROOT = "https://github.com/ageron/handson-ml/tree/master/"
HOUSING_PATH = "datasets/housing"
HOUSING_URL = DOWNLOAD_ROOT + HOUSING_PATH + "/housing.tgz"


def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH):
if not os.path.isdir(housing_path):
os.makedirs(housing_path)

tgz_path = os.path.join(housing_path, "housing.tgz")
urllib.request.urlretrieve(housing_url, tgz_path)
housing_tgz = tarfile.open(tgz_path)
housing_tgz.extractall(path=housing_path)
housing_tgz.close()

fetch_housing_data()



2. 데이터셋 확인

이제 판다스를 사용하여 데이터를 읽어 들이도록 하겠습니다. 데이터를 읽어들이는 간단한 함수를 사용하도록 하겠습니다.

load_housing_data() 함수는 모든 데이터를 담은 판다스의 데이터프레임 객체를 반환합니다. head() 함수를 활용하여 정상적으로 데이터가 적재되었는지 확인합니다.

Code

import pandas as pd

HOUSING_PATH = "datasets/housing"

def load_housing_data(housing_path=HOUSING_PATH):
csv_path = os.path.join(housing_path, "housing.csv")
return pd.read_csv(csv_path)

housing = load_housing_data()
print(housing.head())

Output

정상적으로 데이터가 적재되었음을 확인할 수 있습니다. 위의 표에서는 5개의 열만 보이지만 실제로는 10개의 열이 존재합니다.



info() 함수를 통해 데이터에 대한 간략한 설명과 전체 행 수, 각 특성의 데이터 타입과 널이 아닌 값의 개수를 확인합니다.

Code

housing = load_housing_data()
print(housing.info())

Output

데이터셋에 20,640개의 샘플이 있습니다. 머신러닝 프로젝트치고는 상당히 작은 편이지만, 처음 시작하기에 적당한 크기입니다.

ocean_proximity 필드를 제외하고는 모두 숫자형(float64)임을 확인할 수 있습니다. ocean_proximity 필드의 데이터 타입이 object이므로 어떤 파이썬 객체도 될 수 있지만,
데이터를 CSV 파일에서 읽어 들였기 때문에 텍스트 특성일 것이라고 추측할 수 있습니다. 해당 열의 값이 반복적으로 나타나는 것으로 보아 범주형 변수임을 확인할 수 있습니다. 



3. 데이터 필드 속성 확인

위에서 살펴보았던 ocean_proximity 필드를 집중적으로 살펴보도록 하겠습니다. 
ocean_proximity 필드에 어떤 카테고리가 있고 각 카테고리마다 얼마나 많은 구역이 있는지 확인해보도록 하겠습니다.

Code

housing = load_housing_data()
print(housing["ocean_proximity"].value_counts())

Output



4. 데이터 필드 시각화

데이터의 형태를 빠르게 검토하는 방법은 숫자형 특성을 히스토그램으로 보는 것입니다. 히스토그램은 주어진 값의 범위에 속한 샘플 수를 나타냅니다.
특성마다 따로 히스토그램을 그릴 수 있고 전체 데이터셋에 대해 hist() 메서드를 호출하면 모든 숫자형 특성에 대한 히스토그램을 출력합니다.

Code

housing.hist(bins=50, figsize=(20, 15))
plt.show()

Output

출력된 히스토그램을 통해 여러 가지 정보들을 얻을 수 있고, 이로 인해 우리가 다룰 데이터를 많이 이해하게 되었습니다.



References

  • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018

비선형 SVM의 분류 방법

선형 SVM 분류기가 효율적이고 많은 경우에 아주 잘 작동하지만, 선형적으로 분류할 수 없는 데이터셋이 더 많습니다. 

비선형 데이터셋을 다루는 한 가지 방법은 다항 특성과 같은 특성을 더 추가하는 것입니다. 이렇게 하면 선형적으로 구분되는 데이터셋이 만들어질 수 있습니다.


< Fig. 1. 특성을 추가하여 선형적으로 구분되는 데이터셋 만들기 예제 >


Fig. 1의 왼쪽 그래프는 하나의 특성 𝑥₁만을 가진 가장 간단한 데이터셋을 나타냅니다. 그림에서 볼 수 있듯이 이 데이터셋은 선형적인 구분이 불가능합니다.

하지만 두 번째 특성 𝑥₂ = (𝑥₁)²을 추가하여 만들어진 2차원 데이터셋은 완벽하게 선형적으로 구분할 수 있습니다.


사이킷런을 사용하여 이를 구현하려면 PolynomialFeatures 변환기와 StandardScaler, LinearSVC를 연결하여 Pipeline을 만들면 좋습니다.

아래의 예제는 moons 데이터셋에 위 3가지를 적용한 것입니다.

Code

from sklearn.datasets import make_moons
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC
X, y = make_moons(n_samples=100, noise=0.15, random_state=42)

polynomial_svm_clf = Pipeline([
("poly_features", PolynomialFeatures(degree=3)),
("scaler", StandardScaler()),
("svm_clf", LinearSVC(C=10, loss="hinge"))
])

polynomial_svm_clf.fit(X, y)



1. 다항식 커널 (Polynomial Kernel)

다항식 특성을 추가하는 것은 간단하고 모든 머신러닝 알고리즘에서 잘 작동합니다. 하지만, 낮은 차수의 다항식은 매우 복잡한 데이터셋을 잘 표현하지 못하고 높은 차수의 다항식은 굉장히 많은 특성을 추가하므로 모델을 느리게 만듭니다.

다행히도 SVM을 사용할 땐 커널 트릭(Kernel Trick)이라는 거의 기적에 가까운 수학적 기교를 적용할 수 있습니다. 실제로는 특성을 추가하지 않으면서 다항식 특성을 많이 추가한 것과 같은 결과를 얻을 수 있습니다. 사실 어떤 특성도 추가하지 않기 때문에 엄청난 수의 특성 조합이 생기지 않습니다. 이 기법은 SVC 파이썬 클래스에 구현되어 있습니다.

Code

from sklearn.svm import SVC
import matplotlib.pyplot as plt
import mglearn

X, y = make_moons(n_samples=100, noise=0.15, random_state=42)


poly_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=5))

])

poly_kernel_svm_clf.fit(X, y)


poly_kernel_svm_clf2 = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=10, coef0=100, C=5))

])

poly_kernel_svm_clf2.fit(X, y)


mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
mglearn.plots.plot_2d_separator(poly_kernel_svm_clf, X)
plt.show()


mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
mglearn.plots.plot_2d_separator(poly_kernel_svm_clf2, X)
plt.show()

Output


< d = 3, 𝛾 = 1, C = 5 >


< d = 10, 𝛾 = 100, C = 5 >



2. 유사도 특성 추가

비선형 특성을 다루는 또 다른 기법은 각 샘플이 각 특정 랜드마크와 얼마나 닮았는지 측정하는 유사도 함수(Similarity Function)로 계산한 특성을 추가하는 것입니다. 예를 들어 앞에서 본 1차원 데이터셋에 두 개의 랜드마크 𝑥₁ = -2와 𝑥₁ = 1을 추가합시다. 그리고 𝛾 = 0.3인 가우시안 방사 기저 함수(Radical Basis Function, RBF)를 유사도 함수도 정의하겠습니다.

< Exp. 1. 가우시안 RBF >


이 함수의 값은 0부터 1까지 변화하며 종 모양으로 나타납니다. 이제 새로운 특성을 만들 준비가 되었습니다. 예를 들어 𝑥₁ = -1 샘플을 살펴봅시다. 이 샘플은 첫 번째 랜드마크에서 1만큼 떨어져 있고 두 번째 랜드마크에서 2만큼 떨어져 있습니다. 그러므로 새로 만든 특성은 𝑥₂ = exp(-0.3 × 1²) ≈ 0.74와 𝑥₃ = exp(-0.3 × 2²) ≈ 0.30입니다. Fig. 1.의 오른쪽 그래프는 변환된 데이터셋을 보여줍니다.



< Fig. 1. 가우시안 RBF를 사용한 유사도 측정 >


위와 같이 변환하면 이제 선형적으로 구분이 가능합니다. 랜드마크는 어떻게 선택할까요? 간단한 방법은 데이터셋에 있는 모든 샘플 위치에 랜드마크를 설정하는 것입니다. 이렇게 하면 차원이 매우 커지고 따라서 변환된 훈련 세트가 선형적으로 구분될 가능성이 매우 높습니다. 단점은 훈련 세트에 있는 n개의 특성을 가진 m개의 샘플이 m개의 특성을 가진 m개의 샘플로 변환된다는 것입니다. 훈련 세트가 매우 클 경우 동일한 크기의 아주 많은 특성이 만들어집니다.



3. 가우시안 RBF 커널 (Gaussian RBF Kernel)

다항 특성 방식과 마찬가지로 유사도 특성 방식도 머신러닝 알고리즘에 유용하게 사용될 수 있습니다. 추가 특성을 모두 계산하려면 연산 비용이 많이 드는데 특히 훈련 세트가 클 경우 더 그렇습니다. 하지만 커널 트릭이 한 번 더 SVM의 마법을 만듭니다. 유사도 특성을 많이 추가하는 것과 같은 비슷한 결과를 실제로 특성을 추가하지 않고 얻을 수 있습니다. SVC 모델에 가우시안 RBF 커널을 적용해보도록 하겠습니다.

Code

gamma_lst = [0.1, 0.1, 5, 5]
C_lst = [0.001, 1000, 0.001, 1000]

for g, c in zip(gamma_lst, C_lst):
rbf_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="rbf", gamma=g, C=c))
])

rbf_kernel_svm_clf.fit(X, y)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
mglearn.plots.plot_2d_separator(rbf_kernel_svm_clf, X)
plt.show()

Output


< 𝛾 = 0.1, C = 0.001 >


< 𝛾 = 0.1, C = 1000 >


< 𝛾 = 5, C = 0.001 >


< 𝛾 = 5, C = 1000 >

각각의 그래프를 살펴보면 𝛾를 증가시키면 종 모양 그래프가 좁아져서 각 샘플의 영향 범위가 작아집니다. 결정 경계가 조금 더 불규칙해지고 각 샘플을 따라 구불구불하게 휘어집니다. 반대로 작은 𝛾의 값은 넓은 종 모양 그래프를 만들며 샘플이 넓은 범위에 걸쳐 영향을 주므로 결정 경계가 더 부드러워집니다. 결국 하이퍼파라미터 𝛾가 규제의 역할을 합니다. 모델이 과대적합일 경우엔 감소시켜야하고 과소적합인 경우엔 증가시켜야 합니다. 



References

  • https://tensorflow.blog/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D/2-3-7-%EC%BB%A4%EB%84%90-%EC%84%9C%ED%8F%AC%ED%8A%B8-%EB%B2%A1%ED%84%B0-%EB%A8%B8%EC%8B%A0/
  • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018



선형 SVM의 분류 방법


1. 라지 마진 분류 (Large Margin Classification)

< Fig. 1. Large Margin Classification >


Fig. 1은 붓꽃 데이터셋의 일부를 나타낸 그림입니다. 두 클래스가 직선으로 확실히 잘 나뉘어 진 것을 확인할 수 있습니다.

왼쪽 그래프에 세 개의 선형 분류기에서 만들어진 결정 경계를 확인할 수 있습니다. 점선으로 나타난 결계는 클래스를 적절히 분류하지 못하고 있습니다.

다른 두 모델은 훈련 세트에 완벽하게 동작합니다. 하지만, 결정 경계가 샘플에 너무 가까워 새로운 샘플에 대해서는 아마 잘 작동하지 못할 것입니다.


오른쪽 그래프에 있는 실선은 SVM 분류기의 결정 경계입니다. 이 직선은 두 개의 클래스를 나누고 있을 뿐만 아니라 제일 가까운 훈련 샘플로부터 가능한 한 멀리 떨어져 있습니다.

SVM 분류기를 클래스 사이에서 가장 폭이 넓은 도로를 찾는 것으로 생각할 수 있습니다. 이를 라지 마진 분류라고 합니다.


도로 바깥쪽에 훈련 샘플을 더 추가해도 결정 경계에는 전혀 영향을 미치지 않습니다. 도로 경계에 위치한 샘플에 의해 전적으로 결정됩니다.

이런 샘플을 서포트 백터라고 합니다. (오른쪽 그래프의 동그라미로 표시된 부분)


2. 소프트 마진 분류(Soft Margin Classification)

모든 샘플이 도로 바깥쪽에 올바르게 분류되어 있다면 이를 하드 마진 분류라고 합니다.

하드 마진 분류에는 두 가지 문제점이 있습니다. 데이터가 선형적으로 구분될 수 있어야 제대로 작동하며, 이상치에 민감합니다.


< Fig. 2 이상치에 민감한 하드 마진 >


Fig. 2를 살펴보면 왼쪽 그래프에 이상치가 하나 있습니다. 왼쪽 그래프에서는 하드 마진을 찾을 수 없습니다.

오른쪽 그래프의 결정 경계는 이상치가 없던 Fig. 1과 매우 다르고 일반화가 잘 되지 않습니다.


이러한 문제를 피하기 위해서는 조금 더 유연한 모델이 필요합니다. 도로의 폭을 가능한 넓게 유지하는 것과 마진 오류(Margin Violation) 사이에 적절한 균형을 잡하야 합니다.

이를 소프트 마진 분류라고 합니다. 


< Fig. 3. 좁은 마진과 넓은 마진 >


사이킷런의 SVM 모델에서는 C 하이퍼파라미터를 사용해 이 균형을 조절할 수 있습니다. C값을 줄이면 도로의 폭이 넓어지지만 마진 오류도 커집니다. 

Fig. 3은 선형적으로 구분되지 않는 데이터셋에 두 개의 소프트 마진 SVM 분류기로 만든 결정 경계와 마진을 보여줍니다. 왼쪽 그래프에서는 큰 C 값을 사용해 분류기가 마진 오류를 적게 냈지만 마진이 좁아졌고, 오른쪽 그래프에서는 작은 C 값을 사용하여 마진이 넓어졌지만 많은 샘플이 도로 안에 포함되었습니다. 두 개의 그래프에서는 오른쪽에 있는 분류기가 더 잘 일반화될 것 같아 보입니다. 사실 대부분의 마진 오류는 결정 경계를 기준으로 올바른 클래스로 분류되기 때문에 이 훈련 세트에서 예측 에러는 마진 오류보다 적습니다.


아래의 코드는 붓꽃 데이터를 적재하고, 특성 스케일을 변경하고, Iris - Virginia 품종을 감지하기 위해 선형 SVM 모델을 훈련시킵니다.

import numpy as np
from sklearn import datasets
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC

iris = datasets.load_iris()

X = iris["data"][:, (2, 3)]
y = (iris["target"] == 2).astype(np.float64)

svm_clf = Pipeline([
("scaler", StandardScaler()),
("linear_svc", LinearSVC(C=1, loss="hinge"))
])

svm_clf.fit(X, y)

print(svm_clf.predict([[5.5, 1.7]]))

> array([1.])


여기에서는 선형 SVM 분류기를 훈련시키기 위해 일반적인 확률적 경사 하강법을 적용합니다.

LinearSVC만큼 빠르게 수렴하지는 않지만 데이터셋이 아주 커서 메모리에 적재할 수 없거나, 

온라인 학습으로 분류 문제를 다룰 때는 유용합니다.



SVM (Support Vector Machine)

서포트 벡터 머신(SVM)은 매우 강력하고 선형, 비선형 분류, 회귀, 이상치 탐색에도 사용할 수 있는 다목적 머신러닝 모델입니다.
두 카테고리 중 어느 하나에 속한 데이터의 집합이 주어졌을 때, 주어진 데이터 집합을 바탕으로 새로운 데이터가 어느 카테고리에 속할지 판단하는 모델입니다.
SVM은 특히 복잡한 분류 문제에 잘 들어맞으며 작거나 중간 크기의 데이터셋에 적합합니다.

SVM의 분류 방법

< Fig. 1. SVM 분류 예시 1 >


Fig. 1을 보면 '빨간 원'과 '녹색 사각형'을 구분하는 3개의 직선 (Line 1 ~ 3)이 있는 것을 확인할 수 있습니다.

SVM에서는 어떠한 선을 사용하여 구분할까? 정답은 Line 2입니다.


< Fig. 2. SVM 분류 예시 2 >


Fig. 2를 보면 마찬가지로 '빨간 원'과 '녹색 사각형'을 구분하는 3개의 직선 (Line 1 ~ 3)이 있는 것을 확인할 수 있습니다.

SVM에서는 어떠한 선을 사용하여 구분할까요? 아마도 Line 2를 선택할 것이다. 왜냐하면, SVM에서는 Margin이 가장 큰 Line을 사용하기 때문이다. 여기서 Margin이란 Boundary(Line)에 가까이 있는 요소(Element)에서 Boundary까지 거리의 합을 의미합니다. (얼마나 가까이에 있는 원소까지 사용할 것인지는 Parameter 설정에 따릅니다.)

또한, 강건함(Robust) 관점에서 Line 1과 Line 3은 적절하지 않다. Line 1의 경우 왼쪽 위의 빨간 원의 위치가 조금 바뀌면 Boundary를 넘어 다른 Label을 침범하게 됩니다.


< Fig. 3. SVM 분류 예시 3 >


그렇다면 Fig. 3에서는 어떨까? '빨간 원' 원소 중 하나가 '초록 사각형' 원소들 가까이 위치하고 있습니다.

여기에서는 Line 1을 선택하게 됩니다. SVM은 먼저 Classification 한 다음 Margin이 가장 큰 선을 선택하기 때문입니다.


< Fig.  4. SVM의 Outlier 예시 >


Fig. 4에서는 SVM에서의 Outlier를 표현하는 그래프가 나타나있다. 빨간 원 중 하나가 초록 사각형 사이에 분포하고 있습니다.

여기에서는 어떠한 선을 선택하게 될까요? 정답은 Line 1입니다. 선형 SVM은 Outlier를 어느정도 무시하며 최선의 선택을 진행합니다.


References

  • http://hiuaa.tistory.com/78
  • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018


과대적합과 과소적합

머신러닝에서 모델(알고리즘)의 적합성은 중요한 의미를 내포하고 있습니다.
모델 정확성에 대한 근본적인 원인을 이해하는 것은 아주 중요한 부분을 차지합니다.



1. 과대적합(Overfitting)

머신러닝에서 과대적합은 학습데이터를 과하게 잘 학습한 것을 의미합니다. 너무 과하게 학습을 진행해 학습되지 않은 데이터가 들어오면 분류하지 못하게 됩니다.
따라서, 학습 데이터에 대해서는 오차가 감소하지만, 실제 데이터에 대해서는 오차가 증가하는 지점이 존재합니다. 
아래의 그림(Fig. 1)에서는 테스트 에러가 감소하다 갑자기 치솟는 부분에서 과대적합이 발생했다고 볼 수 있습니다.


< Fig. 1. 머신러닝에서의 과대적합 예시 >


과대적합의 해결 방법은 아래와 같습니다.

  • 파라미터 수가 적은 모델을 선택하거나, 모델에 제약을 가하여 단순화시킵니다.
  • 훈련 데이터를 더 많이 확보합니다.
  • 훈련 데이터의 잡음을 줄입니다. (Outlier, Error 제거)


모델을 단순하게 하고 과대적합의 위험을 감소시키기 위해 모델에 제약을 가하는 것을 규제(Regularization)라고 합니다. 예를 들어 앞서 만든 선형 모델은 두 개의 파라미터 𝜃₀와 𝜃₁을 가지고 있었습니다. 이는 데이터에 모델을 맞추기 위한 두 개의 자유도(Degree of Freedom)를 학습 알고리즘에 부여합니다. 모델은 직선의 절편(𝜃₀)과 기울기(𝜃₁)를 조절할 수 있습니다. 우리가 𝜃₁ = 0이 되도록 강제하면 알고리즘에 한 개의 자유도만 남게 되고, 데이터에 적절하게 맞춰지기 힘들어집니다. 즉, 할 수 있는 것이 훈련 데이터에 가능한 가깝게 되도록 직선을 올리거나 내리는 것이 전부이므로 결국 평균 근처가 됩니다. 알고리즘이 𝜃₁을 수정하도록 허락하되 작은 값을 갖도록 유지시키면 학습 알고리즘이 자유도 1과 2 사이의 적절한 어딘가에 위치할 것을 예상할 수 있습니다.

이러한 모델은 자유도가 2인 모델보다는 단순하고 자유도가 1인 모델보다는 복잡한 모델을 만들게 됩니다.
데이터에 완벽히 맞추는 것과 단순한 모델을 유지하는 것 사이의 올바른 균형을 찾는 것이 좋습니다.



2. 과소적합(Underfitting)

과소적합은 과대적합의 반대 개념이라고 할 수 있습니다. 이는 모델이 너무 단순해서 데이터의 내재된 구조를 학습하지 못할 때 발생합니다.
Fig. 1에서 

< Fig. 2. 과소적합, 과대적합의 예시 >


과소적합의 해결 방법은 아래와 같습니다.

  • 파라미터가 더 많은 강력한 모델을 선택합니다.

  • 학습 알고리즘에 더 좋은 특성을 제공합니다.

  • 모델의 제약을 줄입니다.



데이터를 올바르게 학습시키기 위해서는 과대적합과 과소적합의 중간점을 찾는 것이 좋습니다.
Fig. 3에서 보이는 바와 같이 너무 잘 분류해도, 분류하지 못해도 올바른 모델이라고 할 수 없습니다.

< Fig. 3. 좋은 알고리즘과 나쁜 알고리즘의 예시 >



References

  • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018
  • https://medium.com/greyatom/what-is-underfitting-and-overfitting-in-machine-learning-and-how-to-deal-with-it-6803a989c76




머신러닝에서의 좋은 데이터와 나쁜 데이터란?

우리가 목표하는 바는 알고리즘을 통해 어떤 데이터에 훈련시키는 것으므로 문제가 될 수 있는 것은 '데이터' 입니다.
데이터가 올바르지 못하면 원하는 결과를 얻을 수 없을 것입니다. 따라서, 머신러닝에서의 나쁜 데이터 예제 4가지를 살펴보도록 하겠습니다.


1. 충분하지 않은 양의 훈련 데이터

대부분의 머신러닝 알고리즘이 잘 작동하기 위해서는 데이터가 많아야 합니다. 
아주 간단한 문제에서조차도 수천 개의 데이터가 필요하고 이미지나 음성 인식 같은 복잡한 문제의 경우 수백만 개가 필요할 수 있습니다.


2. 대표성 없는 훈련 데이터

일반화가 잘되기 위해서는 우리가 일반화하기 원하는 새로운 사례를 훈련 데이터가 잘 대표하는 것이 중요합니다.
앞서 실습한 선형 모델을 훈련시키기 위해 사용한 나라의 집합에는 일부 나라가 빠져 있어 대표성을 가질 수 없습니다.
앞서 실습한 예제의 데이터(Fig. 1)에서는 38개의 나라만 사용되었음을 확인할 수 있습니다.
누락된 나라를 추가하면 모델이 크게 변경될 뿐만 아니라, 간단한 선형 모델은 적용되지 않습니다.

< Fig. 1. 대표성을 가지지 못하는 데이터 예시 >


대표성을 가지는 데이터를 사용하는 것이 가장 좋지만, 결코 쉬운일은 아닙니다.
샘플이 작으면 샘플링 잡음(Sampling Noise)[각주:1]이 생기고, 표본 추출 방법이 잘못되면 대표성을 띄지 못할 수 있습니다. 이를 샘플링 편향(Sampling Bias)이라고 합니다.


3. 낮은 품질의 데이터

훈련 데이터가 에러, 이상치, 잡음으로 가득하다면 머신러닝 시스템이 내재되어 있는 패턴을 찾기 어려울 수 있습니다.
따라서, 데이터 전처리에 신경을 곤두세울 필요가 있습니다.


4. 관련 없는 특성

훈련 데이터에 관련 없는 특성이 적고 관련 있는 특성이 충분해야 학습을 진행할 수 있습니다.
훈련에 사용할 좋은 특성들을 찾아야 하며, 이를 특성 공학(Feature Engineering)이라고 합니다.
특성 공학은 다음과 같은 작업을 포함합니다.

  • 특성 선택 (Feature Selection) : 가지고 있는 특성 중에서 훈련에 가장 유용한 특성을 선택합니다.
  • 특성 추출 (Feature Extraction) : 특성을 결합하여 더 유용한 특성을 만듭니다.
  • 새로운 데이터를 수집하여 새로운 특성을 만듭니다.



References

  • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018


  1. 우연에 의한 대표성 없는 데이터, Outlier [본문으로]

사례 기반 학습과 모델 기반 학습

머신러닝 시스템은 어떻게 일반화되는가에 따라 분류할 수 있습니다. 대부분의 머신러닝은 예측하는 것이 목표입니다.

다시 말하면, 주어진 훈련 데이터로 학습하지만 훈련 데이터에서는 본 적 없는 새로운 데이터로 일반화되어야 한다는 뜻입니다.

일반화를 위한 두 가지 접근법은 아래와 같습니다.


사례 기반 학습

가장 간단한 형태의 학습은 단순히 기억하는 것입니다. 아마도 가장 간단한 형태의 학습은 단순히 기억하는 것입니다. 
스팸 필터를 이러한 방식으로 만들면 사용자가 스팸이라고 지정한 메일과 동일한 모든 메일을 스팸으로 분류합니다. 최악의 방법은 아니지만 최선도 아닙니다.

스팸 메일과 동일한 메일을 스팸이라고 지정하는 대신 스팸 메일과 매우 유사한 메일을 구분하도록 스팸 필터를 프로그램할 수 있습니다. 

이렇게 하려면 두 메일 사이의 유사도(Similarity)를 측정해야 합니다. 두 메일 사이의 매우 간단한 유사도 측정 방법은 공통으로 포함한 단어의 수를 세는 것입니다. 스팸 메일과 공통으로 가지고 있는 단어가 많으면 스팸으로 분류합니다. 이를 사례 기반 학습(Instance-Based Learning)이라고 합니다. 시스템이 사례를 기억함으로써 학습합니다. 그리고 유사도 측정을 사용해 새로운 데이터에 일반화합니다.

< Fig. 1. 사례 기반 학습 예시 >


모델 기반 학습

샘플로부터 일반화시키는 다른 방법은 이 샘플들의 모델을 만들어 예측하는 것입니다.

이를 모델 기반 학습(Model-Based Learning)이라고 합니다.


< Fig. 2. 모델 기반 학습 예시 >


모델 기반 학습의 예시로 돈이 사람을 행복하게 만드는지 알아보도록 하겠습니다.

OECD 웹사이트에서 더 나은 삶의 지표(Better Life Index) 데이터와 IMF 웹사이트에서 1인당 GDP 통계를 내려받습니다.

두 데이터 테이블을 합치고 1인당 GDP로 정렬합니다.


< Fig. 3. 1인당 GDP 순으로 정렬한 데이터 >


위의 데이터를 보기 편하게 시각화 합니다.


< Fig. 4. 그래프로 표현한 1인당 GDP와 삶의 질 관계 >


Fig. 4를 살펴보면 어떠한 경향을 찾을 수 있습니다. 삶의 만족도는 국가의 1인당 GDP가 증가할수록 거의 선형으로 상승하는 것을 확인할 수 있었습니다.

그러므로, 1인당 GDP의 선형 함수로 삶의 만족도를 모델링 해보겠습니다. 이 단계를 모델 선택(Model Selection)이라고 합니다.

1인당 GDP라는 특성 하나를 가진 삶의 만족도에 대한 선형 모델(Linear Model)입니다.


삶의 만족도 = 𝜃₀ + 𝜃₁ × 1인당 GDP


이 모델은 두 개의 모델 파라미터 𝜃₀와 𝜃₁을 가집니다. 이 모델 파라미터를 조정하여 Fig. 5 처럼 어떤 선형 함수를 표현하는 모델을 얻을 수 있습니다.

모델을 사용하기 전에 𝜃₀와 𝜃₁을 정의해야 합니다. 모델이 최상의 성능을 내도록 하는 값을 어떻게 유추할 수 있을까요?

이 질문에 대답하기 위해서는 측정 지표를 정해두어야 합니다. 모델이 얼마나 좋은지, 나쁜지를 결정하는 효용 함수[각주:1](또는 적합도 함수[각주:2])를 정의하거나,

얼마나 이 모델이 나쁜지 측정하는 비용 함수[각주:3]를 정의할 수 있습니다. 선형 회귀에서는 보통 선형 모델의 예측과 훈련 데이터 사이의 거리를 재는 비용 함수를 주로 사용합니다.


여기에서 선형 회귀 알고리즘이 등장합니다. 알고리즘에 훈련 데이터를 공급하면 데이터에 가장 잘 맞는 선형 모델의 파라미터를 찾습니다.

이를 모델을 훈련[각주:4]시킨다고 말합니다. 실습을 통해 해당 모델을 훈련시켜보도록 하겠습니다.

아래의 파이썬 코드는 데이터를 로드하고 준비한 다음 산점도를 그려 시각화하고 선형 모델을 훈련하여 예측하는 과정을 보여줍니다.


사이킷런을 활용한 선형 모델의 훈련과 실행 코드

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import sklearn
import warnings
warnings.filterwarnings(action="ignore", module="scipy", message="^internal gelsd")


# Load the data
oecd_bli = pd.read_csv("BLI.csv", thousands=',')
gdp_per_capita = pd.read_csv("WEO.csv", thousands=',', delimiter='\t',
encoding='latin1', na_values="n/a")

# Pre-process the data
def prepare_country_stats(oecd_bli, gdp_per_capita):
oecd_bli = oecd_bli[oecd_bli["INEQUALITY"]=="TOT"]
oecd_bli = oecd_bli.pivot(index='Country', columns="Indicator", values="Value")
gdp_per_capita.rename(columns={"2015": "GDP per capita"}, inplace=True)
gdp_per_capita.set_index('Country', inplace=True)
full_country_stats = pd.merge(left=oecd_bli, right=gdp_per_capita,
left_index=True, right_index=True)
full_country_stats.sort_values(by="GDP per capita", inplace=True)
remove_indices = [0, 1, 6, 8, 33, 34, 35]
keep_indices = list(set(range(36)) - set(remove_indices))
return full_country_stats[["GDP per capita", 'Life satisfaction']].iloc[keep_indices]


# Prepare the data
country_stats = prepare_country_stats(oecd_bli, gdp_per_capita)
X = np.c_[country_stats["GDP per capita"]]
y = np.c_[country_stats["Life satisfaction"]]

# Visualize the data
country_stats.plot(kind='scatter', x="GDP per capita", y='Life satisfaction')
plt.show()

# Select a linear model
model = sklearn.linear_model.LinearRegression()

# Train the model
model.fit(X, y)

# Make a prediction for Cyprus
X_new = [[22587]] # Cyprus' GDP per capita
print(model.predict(X_new)) # outputs [[ 5.96242338]]


결과

[[ 5.96242338]]

지금까지의 작업을 간단히 요약하자면 다음과 같습니다.


  1. 데이터를 분석합니다.
  2. 모델을 선택합니다.
  3. 훈련 데이터로 모델을 훈련시킵니다.
  4. 마지막으로 새로운 데이터에 모델을 적용해 예측을 하고, 이 모델이 일반화되길 기대합니다.


이와 같은 과정이 머신러닝 프로젝트의 전형적인 형태라고 할 수 있습니다.



Reference

    • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018
    • https://github.com/ageron/handson-ml/blob/master/01_the_machine_learning_landscape.ipynb

  1. Utility Function [본문으로]
  2. Fitness Function [본문으로]
  3. Loss Function [본문으로]
  4. Training [본문으로]

+ Recent posts