[D&A 운영진 ML 스터디] 1주차 2차시

권유진·2022년 7월 22일
0

D&A 운영진 스터디

목록 보기
13/17

파이프라인

  • 데이터 처리 컴포넌트들이 연속되어 있는 것
  • 머신러닝은 데이터를 조작, 변환할 일이 많아 파이프라인 사용
  • 보통 컴포넌트들은 비동기적으로 작동
  • 각 컴포넌트는 많은 데이터를 추출해 처리하고 결과를 다른 데이터 저장소로 보냄
    • 그 후 다른 컴포넌트가 그 데이터를 추출해 자신의 출력을 내보냄
    • 각 컴포넌트는 독립적!!!
      • 컴포넌트 사이의 인터페이스는 데이터 저장소 뿐
      • 시스템을 이해하기 쉽게 만들고 각자의 컴포넌트에 집중 가능
        • 컴포넌트 하나가 다운돼도 한동안은 평상시와 같이 동작 가능

1. 문제 정의

  • 비즈니스 목적이 무엇인지 파악
    • 모델을 어떻게 사용해 이익을 얻으려고 하는지
    • 이에 따라 문제 구성, 알고리즘 선택, 성능 지표, 모델 튜닝 노력 정도가 결정됨
  • 현재 솔루션은 어떻게 구성되어 있나요?
    • 현재 상황은 문제 해결 방법에 대한 정보는 물론, 참고 성능으로도 사용 가능
  • 위 두 가지 사항을 파악했다면 문제 정의를 해야 함
    • 지도학습/비지도학습/강화학습
    • 분류/회귀
    • 배치 학습/온라인 학습

2. 성능 지표 선택

  • 회귀
    • RMSE(Root Mean Square Error)
      • 오차가 커질수록 값이 더욱 커짐
      • 예측에 얼마나 많은 오류가 있는지 가늠해줌
      • RMSE(X,h)=1mΣi=1m(h(x(i))y(i))2RMSE(X,h) = \sqrt{\cfrac{1}{m} \Sigma^m_{i=1}(h(x^{(i)})-y^{(i)})^2}
        • mm: 데이터 개수, x(i)x^{(i)}: ii번째 샘플의 feature 벡터, y(i)y^{(i)}: ii번째 샘플의 label
        • hh: 예측 함수, 가설
      • L2L2 노름, 유클리디안 노름
        • 노름의 지수가 클수록 큰 값의 원소에 치우치며 작은 값은 무시
        • RMSE가 MAE보다 이상치에 민감한 이유
          • 이상치가 매우 드물면 널리 사용
    • MAE(Mean Absolute Error)
      • MAE(X,h)=1mΣi=1mh(x(i))y(i)MAE(X, h) = \cfrac{1}{m} \Sigma^m_{i=1} |h(x^{(i)})-y^{(i)}|
      • L1L1 노름, 맨해튼 노름
        • 이상치가 많다고 여겨지면 사용
    • RMSLE(Mean Squared Log Error)
      • RMSELE=1NΣi=0N(log(yi+1)log(y^i+1))2RMSELE = \sqrt{\cfrac{1}{N} \Sigma^N_{i=0}(\log(y_i+1)-\log(\hat y_i + 1))^2}
      • RMSE에 로그를 적용한 지표
      • y=0y=0일 때, log(y)=\log(y) = \infin인 점을 고려해 +1+1
      • 이상치에 강건
        • \because 로그의 특징
      • 상대적 error 측정
        • log(xi+1)log(yi+1)=logcfracxi+1yi+1\because \log(x_i+1) - \log(y_i+1) = \log{cfrac{x_i+1}{y_i+1}}
      • 더 작게 계산한 값에 더 큰 패널티 부여
        • \because 로그의 특징
    • R^2(R Square)
      • R2=y^yR^2 = \cfrac{\hat y}{y}
      • 분산 기반으로 예측 성능 평가

3. 가정 검사

  • 지금까지 만든 가정을 검사
    • 심각한 문제를 조기 발견 가능

실습

1. 작업환경 만들기

# 디렉토리 생성
export ML_PATH="$HOME/ml"
mkdir -p $ML_PATH

# pip 버젼 확인
pip --version

# pip 버젼 업그레이드
python -m pip install --user -U virtualenv

# 독립적 개발 환경 생성
cd $ML_PATH
virtualenv env

# 가상환경 활성화
cd $ML_PATH
source env/bin/activate

# 패키지 설치
pip install jupyter scikit-learn

# 주피터에 커널 등록
python -m ipykernel install --user --name=python3

# 주피터 실행
jupyter notebook

2. 데이터 다운로드

import os
import tarfile
import urllib
import pandas as pd

DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/"
HOUSING_PATH = os.path.join("datasets", "housing")
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/housing.tgz"

def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH):
    os.makedirs(housing_path, exist_ok=True)
    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()

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

3. 데이터 구조 훑어보기

housing = load_housing_data()

# 데이터프레임 상위 5개 값 확인
housing.head()

# 데이터 타입, 개수, 결측치 등 확인
housing.info()

# column별 통계값 확인
housing.describe()

# 범주형 데이터 카테고리 수 확인
housing["ocean_proximity"].value_counts()

# 데이터 분포 확인
housing.hist(bins=50, figsize=(20,15))
plt.show()

4. 테스트 세트 만들기

  • 데이터를 더욱 깊게 들여다보기 전에 test set을 미리 분리해둬야 함
    • 오버피팅 방지
    • test set으로 일반화 오차 측정
from sklearn.model_selection import train_test_split

train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
  • 계층적 샘플링: test set이 전체 데이터셋을 잘 대표하도록 샘플링
    • 전체 인구의 51.3%가 여성, 48.7%가 남성이라면 1000명 샘플링 시 513명이 여성, 487명이 남성이어야 함
    • 연속형 변수가 target일 경우에는 계층별로 충분한 샘플 수가 존재하도록 카테고리로 분류하여
from sklearn.model_selection import StratifiedShuffleSplit

housing["income_cat"] = pd.cut(housing["median_income"], 
                                bins=[0, 1.5, 3, 4.5, 6, np.inf],
                                labels=[1,2,3,4,5])
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, ramdom_state=42)
for train_idx, test_idx in split.split(housing, housing["income_cat"]):
    train_set = housing.loc[train_idx]
    test_set = housing.loc[test_idx]

for set_ in (train_set, test_set):
    set_.drop("income_cat", axis=1, inplace=True)

5. 데이터 이해를 위한 EDA

  • 여러 가지 시각화 기법 수행
housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.4,
            s=housing['population']/100, label='population', figsize=(10,7),
            c='median_house_value', cmap=plt.get_cmap('jet'), colorbar=True,
            sharex=False)
plt.legend()
plt.show()

6. 상관관계 조사

  • 표준 상관계수(피어슨 rr)를 통해 상관관계 검사
    • 1r1-1 \le r \le 1
      • rr이 0이 가까울수록 상관관계가 없는 것
      • rr이 -1에 가까울수록 음의 상관관계, 1에 가까울수록 양의 상관관계
    • 상관계수는 선형적인 상관관계만 측정
      • 비선형적 상관관계 측정 불가
    • 상관관계는 기울기를 고려하지 못함
      • 기울기가 0이어도 상관관계 1 또는 -1
corr_matrix = housing.corr()
  • 산점도를 활용한 상관관계 검사
from pandas.plotting import scatter_matrix

attributes = ["median_house_value", "median_income", "total_rooms",
                "housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12,8))
plt.show()

7. feature 조합으로 실험

  • 여러 feature를 조합해 feature 생성
    • 특정 구역의 방 개수만으로는 그다지 유용하지 않음
    • 가구 수를 고려하여 가구당 방 개수를 계산하면 더욱 유용

8. 알고리즘을 위한 데이터 준비

  • 함수를 만들어 자동화하면 좋음
    • 어떤 데이터셋에 대해서도 데이터 변환을 손쉽게 할 수 있음
  • 데이터 정제
    • 결측값 처리
      • 해당 데이터 제거(행, row)
      • 해당 feature 제거(열, column)
      • 대체(0, 평균, 중간값 등)
    • 범주형 데이터 변환
      • 대부분의 머신러닝은 숫자 계산을 통해 이루어지므로 숫자형으로 변환해야 함
      • Label Encoding
        • 카테고리를 숫자 붙임
          • 학생: 1, 주부: 2, 교사: 3 ...
        • 실제로 범주 간의 대소 관계가 존재하지 않아도 알고리즘이 대소관계가 있다고 이해하는 문제점 보유
          • [bad, average, good, excellent] 같은 경우는 괜찮다.
      • One-Hot Encoding
        • 모두 값이 0인 범주 개수 만큼의 벡터 생성 후, 해당하는 범주의 index 차원의 값만 1로 표현
      • Target Encoding(Mean Encoding)
    • 스케일링
      • feature의 스케일이 많이 다르면 머신러닝 알고리즘이 잘 작동하지 않음.
      • 무조건 학습 데이터에 대해서만 fit() 해야 함
      • 종류
        • min-max 스케일링(정규화, normalization)
          • xminmax\cfrac{x-\min}{\max}
          • 값이 0~1 사이에 들도록 조정
            • feature_range 파라미터로 범위 변경 가능
          • 이상치에 민감
        • 표준화(standardization)
          • xμσ\cfrac{x-\mu}{\sigma}
          • 평균이 0, 표준편차가 1인 표준정규분포를 따르도록 변환
          • 상한, 하한이 없어 어떤 알고리즘에서는 문제가 될 수 있다.
            • 이상치의 영향 많이 받음
        • max-abs scaler
          • xmax(x)\cfrac{x}{\max(|x|)}
          • 절대값이 0~1 사이에 매핑되도록 한다.
            • 즉 -1~1 사이로 재조정
            • 양수 데이터셋에서는 min-max scaler와 유사하게 동작
          • 이상치에 민감
        • robust scaler
          • xixmedianx.75x.25\cfrac{x_i - x_{median}}{x_{.75} - x_{.25}}
          • 이상치의 영향을 최소화한 기법
            • 중위수가 이상치에 강하기 때문
          • standardization보다 동일한 값을 더 넓게 분포시킴
            • 즉, 이상치에 덜 민감하다는 의미
        • 로그 변환(Log Transformation)
          • 로그를 씌워 분포를 정규 분포로 변환
          • 거듭 제곱 변환을 통해 정규분포로 만드는 것: PowerTransformer
# 결측값 처리
housing.dropna(subset=['total_bedrooms']) # 행(data) 제거
housing.drop('total_bedrooms', axis=1) # 열(feature) 제거
housing['total_bedrooms'].fillna(median, inplace=True) # 채우기

from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy='median') # 누락값 중간값으로 대체
imputer.fit(housing_num) # imputer 학습
imputer.statistics_ # imputer가 학습한 Feature별 통계값
X = imputer.transform(housing_num) # imputer 적용

# 라벨 인코딩
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
ordinal_encoder.categories # 범주형 데이터 카테고리 리스트 반환

# 원 핫 인코딩
from sklearn.preprocessing import OneHotEncoder
cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot.toarray() # scipy의 csr로 생성하기 때문에 numpy array로 변환
cat_encoder.categories_
  • 나만의 변환기
    • scikit-learn은 상속이 아닌 duck typing을 지원
    • fit(), transform(), fit_transform() 메소드를 구현한 파이썬 클래스를 만들면 됨
      • fit_transform()TransformerMixin을 상속하면 자동으로 생성됨
      • BaseEstimator 상속 시 하이퍼파라미터 튜닝에 필요한 get_params()set_params() 얻음
    • 아래 변환기는 add_bedrooms_per_room 변수를 통해 해당 feature 추가 여부를 결정
      • 해당 feature가 알고리즘에 도움이 되는지 여부를 하이퍼 파라미터를 통해 쉽게 확인 가능
      • 이와 같이 자동화할 경우 더 많은 조합을 자동으로 시도 가능
        • 최상의 조합을 찾을 가능성이 높아짐
        • 시간 절약
from sklearn.base import BaseEstimator, TransformerMixin

rooms_ix, bedrooms_ix, population_ix, households_ix = 3, 4, 5, 6

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    def __init__(self, add_bedrooms_per_room=True):
        self.add_bedrooms_per_room = add_bedrooms_per_room
    def fit(self, X, y=None):
        return self
    def transform(self, x):
        rooms_per_household = X[:, rooms_ix] / X[:, households_ix]
        population_per_household = X[:, population_ix] / X[:, households_ix]
        if self.add_bed_rooms_per_room:
            bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
            return np.c_[X, rooms_per_household, population_per_household]
        else:
            return np.c_[X, rooms_per_household, population_per_household]

attr_adder = CombinedAttributesAdder(add_bed_rooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)
  • 변환 파이프라인
    • 변환 단계가 많고 정확한 순서대로 실행되어야 함
    • 연속된 변환을 순서대로 처리할 수 있도록 도와주는 Pipeline 클래스 존재
    • 연속된 단계를 나타내는 이름/추정기 쌍의 목록을 입력으로 받음
      • (이름, 추정기)
        • 이름은 하이퍼 파라미터 튜닝에서 사용될 예정
    • 마지막 단계에서는 fit_transform() 메소드를 보유한 변환기/추정기여야 함
    • 파이프라인의 fit() 메소드 호출 시 모든 변환기의 fit_transform() 메소드를 순서대로 호출하며 다음 단계의 입력으로 출력을 보냄
      • 마지막 단계에서는 fit()만 수행
    • 파이프라인 객체는 마지막 추정기와 동일한 메소드 제공
  • Feature 별로 파이프라인 적용
    • 변환기를 사용하는 대신 삭제하고 싶은 열이 있다면 "drop" 문자열로 지정
    • 변환을 적용하지 않을 열이 있다면 "passthrough"로 지정
    • 나열되지 않은 열은 자동으로 삭제됨
    • 밀집 행렬과 희소 행렬이 같이 주어졌을 경우, 최종 행렬의 밀집 정도를 기준으로 행렬 반환(default: 0.3)
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

# 일반 파이프라인
num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('attribs_adder', CombinedAttributesAdder()),
    ('std_scaler', StandardScaler())
])

housing_num_tr = num_pipeline.fit_transform(housing_num)

# 특정 열에만 적용할 수 있는 파이프라인
num_attribs = list(house_num)
cat_attribs = ["ocean_proximity"]

full_pipeline = ColumnTransformer([
    ("num", num_pipeline, num_attribs),
    ("cat", oneHotEncoder(), cat_attribs)
])

housing_prepared = full_pipeline.fit_transform(housing)

9. 모델 선택과 훈련

  • 훈련 세트에서 훈련하고 평가
from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)

X = full_pipeline.transform(data)
lin_reg.predict(X)

from sklearn.metrics import mean_squared_error
preds = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
  • 교차 검증을 사용한 평가
    • 모델을 여러번 훈련해야하므로 비용이 비쌈
    • k-겹 교차 검증(k-fold cross validation): fold라고 불리는 subset으로 무작위 분할하여 매번 다른 폴드로 모델을 k번 훈련하고 평가
      • k-1개의 폴드를 학습에 사용후 1개의 폴드를 평가 데이터로 사용
      • 매번 평가 폴드는 바뀜
from sklearn.model_selection import cross_val_score
scores = cross_val_score(lin_reg, housing_prepared, housing_labels,
                        scoring="neg_mean_squared_error", cv=10)
rmse_scores = np.sqrt(-scores)
  • 하이퍼파라미터 조정에 너무 많은 시간을 들이지 않으면서 다양한 모델을 시도해보면 좋음
    • 가능성 있는 2~5개 모델 선정이 목적
    • 실험한 모델 저장 시 쉽게 복원 가능
    • pickle이나 joblib 사용
import joblib

joblib.dump(my_model, "my_model.pkl") # 모델 저장
my_model_loaded = joblib.load("my_model.pkl") # 모델 불러오기

10. 모델 세부 튜닝

  • 모델 튜닝 시, 학습 데이터에 과적합 될 수 있음!!
    • 학습 데이터에 알맞는 하이퍼 파라미터로 학습이 진행되기 때문
    • 그래서 적당히 해야함!!
  • 그리드 탐색(Grid Search)
    • 하이퍼파라미터 범위를 지정하면, 가능한 모든 하이퍼파라미터 조합에 대해 평가
    • param_grid의 첫번째 딕셔너리를 먼저 실험한 후, 그 이후 딕셔너리를 차례대로 실험
    • 데이터 준비 단계에서도 하이퍼 파라미터에 따라서 실험 가능
from sklearn.model_selection import GridSearchCV

param_grid = [
    {'n_estimators':[3,10,30], 'max_features':[2,4,6,8]},
    {'bootstrap':[False], 'n_estimators':[3,10], 'max_features':[2,3,4]}
]

forest_reg = RandomForestRegressor()

grid_search = GridSearchCV(forest_reg, param_grid, cv=5, 
                scoring='neg_mean_squared_error', return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)

grid_search.best_params_ # 최상의 파라미터
grid_search.best_estimators_ # 최적 모델
grid_search.cv_results_ # 실험 결과
  • 랜덤 탐색(Randomized Search)
    • 그리드 탐색 방법은 적은 수의 조합을 탐구할 때 좋음
    • 하이퍼파라미터 탐색 공간이 커지면 그리드 탐색 방법의 비용이 커지므로 랜덤 탐색 방법 사용이 적절함
    • 각 반복마다 하이퍼파라미터에 임의의 수를 대입해 지정한 횟수만큼 평가
  • 베이지안 최적화(Bayesian Optimization)
    • 기존에 추출되어 평가된 결과를 바탕으로 앞으로 탐색할 범위를 좁혀서 효율적으로 탐색하는 것
    • Exploration + Exploitation

11. 앙상블

  • 모델을 세밀하게 튜닝하는 방법은 최상의 모델을 연결해보는 것

12. 최상의 모델과 오차 분석

  • 최상의 모델을 분석함으로써 문제에 대한 통찰
    • ex. RandomForest의 feature importance 분석
  • 시스템이 특정 오차를 만들었다면 이유 분석 후 문제 해결을 위해 노력해야 함
    • ex. feature 추가, 불필요 feature 제거, 이상치 제거 등

13. 테스트 세트로 시스템 평가

  • 테스트 세트로 최종 모델 평가
  • 테스트 과정에서는 fit_transform() 대신 transform() 사용해야 함!!
  • 일반화 오차의 신뢰구간 분석을 통해 해당 모델 사용 여부 결정
from scipy import stats
confidence = .95
squared_errors = (final_predictions - y_test) ** 2
np.sqrt(stats.t.interval(confidence, len(squared_errors)-1,
                        loc=squared_errors.mean(),
                        scale=stats.sem(squared_errors))

14. 론칭, 모니터링, 시스템 유지 보수

  • 전처리 파이프라인과 예측 파이프라인이 포함된 모델 저장
  • 상용 환경에서 모델 로드하고 predict() 메소드를 통해 예측 수행
    • 클라우드
      • 구글 클라우드 AI 플랫폼
  • 일정 간격으로 실시간 성능 체크 및 성능 감소 시 알람을 통지하는 모니터링 코드 작성해야 됨
    • 시간이 지나면서 모델이 낙후되는 경향 있음
  • 모델 백업

참고
Hands-on Machine Learning with Scikit-Learn, Keras & Tensorflow 2 - 오렐리앙 제롱

profile
데이터사이언스를 공부하는 권유진입니다.

0개의 댓글