[Python] 머신러닝

0ㅑ채
|2024. 3. 7. 14:00

1. 인공지능

- 지능 : 문제를 해결할 수 있는 능력

  • 환자를 보고 병을 진단

- 인공지능: 지능 작업을 수행할 수 있는 기계의 능력

  • 환자에 대한 정보를 입력하면 컴퓨터가 병을 진단

- 구현 방법: 지식 공학(전문가 시스템): 문제 해결을 위한 알고리즘을 사람이 작성

  • 전문가의 도움을 받아서 개발자가 알고리즘을 작성해서 컴퓨터에 저장하고 이 알고리즘을 따라서 문제 해결
  • 컴퓨터의 역할은 결과를 만들어내는 것

- Machine Learning

  • Data와 Output을 주면 컴퓨터가 알고리즘을 만들어내는 방식

 

인공지능과 머신러닝의 관계

- 인공지능 > 기계학습(머신러닝) > 딥러닝, 강화학습
- 전문가 시스템 -> 머신 러닝 -> 딥러닝, 강화학습

  • 딥러닝은 이겼다, 졌다로 판정해주지만 강화학습은 순간순간 확률을 보여준다.=> 게임에 적합
  • 이미지 데이터가 존재하는 경우 머신 러닝은 이미지 데이터 1개를 데이터 1개로 바라보지만 딥러닝은 이를 작게 쪼개서 그 안에서 알고리즘을 찾을 수 있음

 
 
 

2. Machine Learning

- 데이터를 가지고 학습하도록 컴퓨터를 프로그래밍하는 과학
- 명시적으로 프로그램 되는 것이 아니라 훈련되며 작업과 관련있는 샘플을 제공하면 이 데이터에서 통계적 구조를 찾아 그 작업을 자동화하기 위한 규칙을 만들어내는 것
- 필요 요소

  • 입력 데이터 포인트: 최근에는 여러 입력 데이터 포인트로 얻어진 데이터를 한 곳에 모아서 처리하는 부분에 대해 중점
    • 데이터 발생지가 여러 곳인 경우 별도로 처리하는 것이 어렵기 때문에 한 곳에 잘 정리를 해서 모으는 것이 중요
  • 기대 출력: 어떤 결과를 원하는지
  • 알고리즘 성능 측정 방법: 평가 지표

 

1) 사용하는 이유

- 전통적인 방식으로는 너무 복잡하거나 알려진 알고리즘이 없는 문제

  • 음성인식은 직접 알고리즘을 만들기에는 너무 복잡해서 머신러닝을 이용

- 머신 러닝을 통해 학습을 할 수 있기 때문
대용량의 데이터를 분석하다보면 기존에 알고 있지 않은 또는 겉으로는 보이지 않는 패턴 발견 가능 - 데이터 마이닝
 
 

2) 역사

- 확률적 모델링

  • 나이브 베이즈
  • 로지스틱 회귀

- 신경망

  • 등장은 1950년대, 이때는 컴퓨터 성능이 좋지 못해서 효과적인 훈련 방법을 찾지 못했다.
  • 1989년 얀 르쿤에 의해 초창기 합성곱 신경망이 등장하면서 다시 각광

- 커널 방법

  • 분류에 사용되었는데 선형이 아닌 비선형으로 결정 경계 만들기 시작

- Decision Tree, Random Forest, Gradient Boosting: 앙상블 모형

  • 컴퓨터 성능이 좋아지면서 하나의 알고리즘을 부트스트랩을 이용하거나 여러 개의 알고리즘을 한꺼번에 학습하는 방법

- 신경망
- 생성형 AI
 
 

3) 분류

- 레이블(정답)의 존재 여부에 따른 분류

  • 지도학습: 레이블 존재 (회귀 or 분류)
  • 비지도학습: 레이블 없음 (주성분분석, 군집, 연관분석 등)
  • 준지도학습: 레이블을 만드는 작업 (레이블이 일부분밖에 없어서)
  • 강화학습: 보상이 주어지는 방식

- 실시간 점진적으로 학습을 할 수 있는지 여부

  • 온라인학습: 점진적 학습 가능
    • 실시간으로 데이터가 들어오는 경우도 학습이 가능하다.
  • 배치학습: 점진적 학습 불가능
    • 데이터가 확정이 되어있어야 한다.

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

  • 사례 기반 학습: 알고 있는 데이터와 새 데이터를 비교하는게 목적
  • 모델 기반 학습: 패턴을 발견해서 예측 모델을 만드는 방식

 
 

4) 지도학습 (Supervised Learning)

- 레이블이 존재하는 학습
- 입력-출력 쌍들을 매핑해주는 함수 학습
- 종류

  • 출력이 이산적일 때: 분류
  • 출력이 연속적일 때: 회귀
  • 출력이 확률인 경우: 추정 (Deep Learning)

- 단점

  • 사용할 수 있는 데이터에 한계 
  • 데이터를 생성하는 데 많은 비용

- 선형회귀
- 로지스틱 회귀
- k-최근접 이웃
- 서포트 벡터 머신
- 결정 트리
- 랜덤 포레스트
- 신경망
 
- 로지스틱 회귀를 제외하고는 분류와 회귀에 모두 사용 가능

  • 로지스틱 회귀는 분류만 가능

 
 

5) 비지도 학습(Unsupervised Learning)

- 레이블이 존재하지 않는 학습
- 데이터에 내재된 고유의 특징을 탐색하기 위해서 사용
- 지도 학습에 비해서 학습하기 어려움
 
- 군집

  • k means 
  • DBSCAN
  • 계층군집
  • 이상치 탐지와 특이치 탐지
  • 원클래스
  • 아이솔레이션 포레스트

- 시각화와 차원 축소

  • PCA (주성분 분석)
  • LLE (지역적 선형 임베딩)
  • t-SNE

- 연관 규칙 학습

  • Apriori
  • eclat

 

6) 준지도 학습

- 라벨링이 일부분만 되어있어서 그 데이터를 이용해서 라벨이 없는 데이터에 라벨을 붙이기 위해서 사용
 
 

7) 강화 학습

- 결과가 바로 주어지지 않고 시간이 지나서 주어지는 방식
- 최근에 로봇 같은 분야에서 많이 이용
 
 

8) 애플리케이션 사례

- Netflix의 영화 추천 시스템: 고객의 평점작성 내역과 구매 내역을 이용해서 추천
- 미국 국가 안보국의 SKYNET: 파키스탄의 테러리스트를 식별해서 사살하기 위한 프로그램

  • 휴대 전화 기록을 이용하여 휴대 전화를 자주 끄거나 USIM을 변경하는 사람을 테러리스트로 식별해서 사살했는데 잘못된 알고리즘으로 무고한 사람이 희생됨

 
 

3. scikit learn

- 파이썬 머신러닝 패키지 중 가장 많이 사용되는 라이브러리
- 가장 Python스러운 API
 - 패키지: sklearn

  • 아나콘다는 내장

 
 

4. 데이터 표현 방식

1) 테이블로서의 데이터

- 기본 테이블은 2차원 데이터 그리드 형태
- 행: 데이터 세트의 개별 요소, sample이라 부르고 행의 개수는 n_sample이라 표현
- 열:각 요소와 관련된 수량,  feature 또는 target이라고 부르는 경우가 많고 열의 개수를 n_features라고 표현

  • feature: 독립적인 데이터 
  • Target : feature로 인해서 만들어지는 데이터로 label이라고도 함

 

2) feature

- 보통은 X라는 변수에 저장
- 특징 행렬이라는 표현을 사용. [n_sampels, n_features]의 모양을 가진 2차원 행렬이라 가정하며 실제 자료형은 numpy의 ndarray나 pandas의 DataFrame으로 되어 있는 경우가 많은데 가끔 sklearn의 희소 행렬인 경우도 있음
- 정량적인 데이터여야 하기 때문에 대부분의 경우는 실수지만 이산적인 데이터아 부울도 가ㅡㄴㅇ
 
 

3) target

- 대상 행렬이라고 하는데 y로 표시
- numpy의 1차원 ndarray나 pandas의 Series인 경우가 많음
- 연속적인 수치가 이산 클래스를 가질 수 있음

import seaborn as sns
iris = sns.load_dataset('iris')
#특징 행렬과 타겟 분리
X_iris = iris.drop('species', axis=1)
Y_iris = iris['species']
#species를 분류하기 위한 데이터 (타겟)
#나머지 데이터는 특징행렬

 
 
 

5. Estimator API(머신러닝 모델 API)

1) 기본 원칙

- 일관성

  • 모든 객체는 일관된 문서를 갖춘 제한된 함수 집합에서 비롯된 공통 인터페이스 공유
  • 거의 모든 객체는 동일한 작업을 수행하는 메소드를 공통으로 소유
  • 템플릿 메소드 패턴: 공통으로 사용될 것 같은 메소드를 인터페이스에 등록하고 이를 클래스에서 구현해서 사용

- 검사 (inspection)

  • parameter: 함수나 기능을 수행하기 위해서 내부적으로 사용하는 데이터. argument, 인수, 인자, 매개변수라고 하기도 함
  • hyper parameter: 개발자가 직접 설정하는 파라미터. 
    • hyper parameter 튜닝은 값을 변경해서 더 좋은 모델을 만들거나 최적의 값을 찾아가는 작업이다.
  • 모든 hyper parameter를 public 속성으로 노출해서 확인이 가능하도록 함

- 제한된 객체 계층 구조

  • 알고리즘만 python 클래스에 의해 표현, 데이터세트는 표준 포맷으로 표현, 매개변수 이름은 문자열
    • 표준 포맷(numpy의 ndarray, pandas의 DataFrame, scipy의 Sparse Matrix)

- 구성

  • 대부분의 머신러닝 작업은 기본 알고리즘의 시퀀스로 나타낼 수 있음 (순서대로)

- 합리적인 기본값

  • 대다수의 하이퍼 파라미터는 라이브러리가 적절한 기본값을 가지도록 정의

 
 

2) 사용 방법

- 적절한 모델 클래스 mport
- 모델 클래스를 인스턴스화 할 때 적절한 하이퍼 파라미터를 설정
- 데이터를 특징 배열과 타겟 배열로 생성
- 모델 클래스의 인스턴스의 fit 메소드를 호출해서 모델을 데이터에 적합하도록 훈련
- 모델을 새 데이터에 적용

  • 지도 학습의 경우: predict 함수에 새로운 데이터 사용해서 예측
  • 비지도 학습의 경우: transform이나 predict를 이용해서 데이터의 속성을 변환하거나 예측

 
 

3) 선형 회귀 수행

 - sklearn.linear_model.LinearRegression

#샘플 데이터 생성
rng = np.random.RandomState(42)

#데이터 50개 생성
x = 10 * rng.rand(50)
#데이터를 이용해서 타겟 데이터 생성 - rng.randn(50)은 잡음
y = 2 * x -1 + rng.randn(50)

#x 데이터를 특징 행렬로 변환
print(x.shape) #1차원 배열 - 특성 배열은 2차원 배열, DataFrame, 희소 행렬
X = x.reshape(x.shape[0], -1) #x[:, np.newaxis]도 가능
print(X.shape) # X는 특성 배열이 되었다.
(50,)
(50, 1)
#추정기 인스턴스 생성
from sklearn.linear_model import LinearRegression
model = LinearRegression(fit_intercept=True)

#기존 데이터로 훈련
model.fit(X, y)

#훈련 결과 확인
print("회귀 계수:", model.coef_)
print("절편:", model.intercept_)
회귀 계수: [1.9776566]
절편: -0.9033107255311164
  • 훈련 결과를 확인 - 스스로 만든 파라미터나 결과에는 _가 붙음
  • 하이퍼 파라미터에는 _가 붙지 않음
#회귀는 지도학습: predict 함수로 예측
xfit = np.linspace(-1, 11)
Xfit = xfit[:, np.newaxis]
yfit = model.predict(Xfit)

#시각화
plt.scatter(x, y)
plt.plot(xfit, yfit)
  • 잡음을 섞어서 일정하게 나오지는 않는다.

 
 

4) 평가 지표를 이용한 성능 평가

- 앞의 방식은 모든 데이터를 가지고 훈련을 해서 모델을 만든 후 새로운 데이터에 적용한 방법

  • 여러 모델을 이용해서 만든 모델을 평가할 수 없다.

- 일반적으로 머신러닝에서는 기존 데이터를 분할해서 훈련에 사용하고 나머지 데이터를 이용해서 모델을 평가한다.

  • 여러 머신러닝 추정기를 적용하고 최적의 성능을 가진 모델을 선택하는 것이 일반적

 
 
 

6. Machine Learning 절차

1) 순서

- 작업 환경 설정: 파이썬 설치, 가상 환경 생성, 필요한 패키지 설치

  • 서비스를 만들고자 하는 경우는 가상 환경 생성 작업이 필수

- 문제 정의
- 데이터 수집 
- 데이터 탐색
- 데이터 전처리
- 모델을 선택하고 훈련
- 평가하고 전처리부터 다시 수행
- 최적의 모델을 선택하고 솔루션 제시
- 시스템을 런칭하고 모니터링 하면서 유지보수
 
 
2) 실행

- 환경 설정 
- 데이터 수집 : housing.csv

  • 캘리포니아 주택 데이터셋
  • 블록 별로 여러 컬럼들을 소유하고 있음

- 문제 정의: 중간 주택 가격 예측

  • 주택 가격이 이미 존재하므로 지도학습이다.
  • 주책 가격은 연속형 데이터이므로 회귀 분석

- 데이터를 불러와서 탐색

# 데이터 탐색
housing = pd.read_csv('./python_machine_learning-main/data/housing.csv')
housing
#각 열읱 특성 파악
housing.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 10 columns):
 #   Column              Non-Null Count  Dtype  
---  ------              --------------  -----  
 0   longitude           20640 non-null  float64
 1   latitude            20640 non-null  float64
 2   housing_median_age  20640 non-null  float64
 3   total_rooms         20640 non-null  float64
 4   total_bedrooms      20433 non-null  float64
 5   population          20640 non-null  float64
 6   households          20640 non-null  float64
 7   median_income       20640 non-null  float64
 8   median_house_value  20640 non-null  float64
 9   ocean_proximity     20640 non-null  object 
dtypes: float64(9), object(1)
memory usage: 1.6+ MB
  • total_berooms는 결측치 존재
  • ocean_proximity는 자료형이 객체 - 문자열
#문자열 컬럼은 범주형인지 확인
housing['ocean_proximity'].value_counts()
ocean_proximity
<1H OCEAN     9136
INLAND        6551
NEAR OCEAN    2658
NEAR BAY      2290
ISLAND           5
Name: count, dtype: int64
  • 5개 종류인 것으로 봐서 범주형이다.
#숫자데이터 범위 확인
housing.describe()
#숫자 데이터는 분포 확인 - 히스토그램
housing.hist(bins=50, figsize=(20, 15))
plt.show()
  • 데이터의 분포가 한쪽으로 몰린 경우에는 데이터의 분포가 조금 더 중앙에 많이 몰리도록 수정할 필요가 있다. (로그변환)
  • 좌우로 너무 넓게 펼쳐져있으면 극단치를 제거하는 것에 대해서 고려
  • 각 숫자 데이터의 범위를 비교해서 범위가 차이가 많이 나면 스케일링 고려
    • population이 너무 크다.
    • total_rooms의 max가 이상하다. 

 

- 데이터 분리

  • 훈련 데이터와 테스트 데이터 또는 검증용 데이터로 분리하는 것이 일반적
  • 데이터 전체를 파악하기 전에 분리하는 것이 좋다. 
  • 전체 데이터를 가지고 데이터 탐색을 수행하게 되면 과대적합 가능성이 발생한다.
  • 사람의 뇌가 패턴을 감지해버리기 때문이다.
  • 이러한 현상을 Data Snooping이라고 함

  • 데이터를 나눌 때 보통 7:3, 8:2를 많이 이용하지만 데이터 개수에 따라 다른 선택을 할 수 있다. 일반적으로 데이터가 많으면 훈련 데이터의 비율을 낮추고 데이터의 개수가 적으면 훈련 데이터의 비율을 높인다.
  • 데이터가 아주 많으면 검증용 데이터를 별도로 분할해도 된다.
  • 시계열 데이터는 데이터를 순차적으로 분할한다.

  • 훈련을 여러번 반복해서 수행하면 알고리즘의 비교 측면이나 모든 데이터를 샘플로 사용하게 되는 상황을 방지하기 위해서 일정한 데이터를 랜덤하게 추출하는 경우도 있다.

  • 분류 문제에서는 타겟의 비율이 다르다면 층화 추출도 고려
    • 100일 중 1번만 비가 온다면, 안온다고 할 떄 정확도가 99%가 된다.
      비가 오는 경우와 안오는 경우를 적절히 섞어야 한다. 

# sklearn 의 model_selection.train_test_split API를 이용한 데이터 분리

import sklearn
help(sklearn.model_selection.train_test_split)
  • arrys는 데이터 배열
  • test_size는 테스트 데이터 비율
  • train_size는 훈련 데이터의 비율로 test_size를 설정하면 설정하지 않음
  • 데이터가 아주 많으면 훈련하는 데 시간이 너무 많이 걸릴 수 있어서 설정하기도 함
  • random_state는 시드 번호로 설정하는 것을 권장
  • shuffle은 데이터를 섞을지 여부로 random_state를 설정하면 무의미
  • stratify는 측화추출을 하고자 할 때 데이터의 비율
  • 리턴되는 데이터는 훈련데이터와 테스트데이터의 튜플
#8:2로 분할 - 데이터를 하나의 데이터로 제공하면 2개로 리턴하고
#특성 배열과 타겟 배열 2개를 대입하면 4개로 리턴

train_set, test_set = sklearn.model_selection.train_test_split(housing, 
                                                              test_size=0.2, 
                                                              random_state=42)
#분할된 데이터의 차원 확인
print(train_set.shape)
print(test_set.shape)
(16512, 10)
(4128, 10)
X = housing.drop('median_house_value', axis=1)
y = housing['median_house_value']

result = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)
print(type(result))
<class 'list'>

 
 
- 층화 추출: 계층적 샘플링 (데이터를 일정한 비율로 샘플링)

  • 회귀나 분류를 할 때 타겟의 데이터 분포가 일정하지 않은 경우 왜곡된 결과를 만들 수 있음
    • 분류의 경우 1과 0의 비율을 10대 1 정도 되는 상황에서 훈련 데이터에 0으로 분류되는 데이터가 하나도 없다면 이 경우 모델은 테스트 데이터에 결과가 좋지 않을 것
    • 0으로 분류되는 모든 데이터가 훈련 데이터에 포함되어 버리면 잘못하면 테스트 데이터에 완전하게 맞는 결과가 나와버릴 수 있다.
  • 회귀의 경우 타겟이 연속형이라면 범주형으로 변환해서 수행해야 한다.
    • 이때 사용할 수 있는 함수는 pandas의 cut 함수
    • 데이터와 구간의 리스트, 레이블의 리스트 대입
  • API 함수는 StratifiedShuffleSplit
    • 이 함수의 리턴되는 데이터는 데이터가 아니고 데이터의 인덱스
    • 결과를 가지고 다시 데이터 추출을 해야 함
X = housing.drop('median_house_value', axis=1)
y = housing['median_house_value']

X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)
print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)
(16512, 9)
(4128, 9)
(16512,)
(4128,)

 
- median_income의 비율을 이용한 층화 추출 (계층적 샘플링)

# 연속형 데이터를 범주형으로 변환

#pd.cut(데이터, bins =[경계값 나열] ,labels =[ 레이블 나열 ])

housing['income_cat'] = pd.cut(housing['median_income'],
                               bins=[0, 1.5 ,3.0 ,4.5, 6, np.inf],
                               labels=[1,2,3,4,5])

housing['income_cat'].value_counts()
income_cat
3    7236
2    6581
4    3639
5    2362
1     822
Name: count, dtype: int64

 
# 층화 추출을 위한 객체 생성

#층화 추출을 위한 객체 생성
#n_splits은 조각의 개수
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)

result = split.split(housing, housing['income_cat'])

for train_index, test_index in result:
    start_train_set = housing.loc[train_index]
    start_test_set = housing.loc[test_index]
    
#비율확인
start_train_set['income_cat'].value_counts() / len(start_train_set)
  • k-ford validation: 데이터를 몇조각으로 등분해서, 5조각을 만들고 4개로 모델 만들고 1개로 테스트하고. 
  • 인덱스를 리턴해주어서 행단위 추출을 다시 해줘야 한다. (for문)
income_cat
3    0.350594
2    0.318859
4    0.176296
5    0.114462
1    0.039789
Name: count, dtype: float64

 
 
- 데이터 탐색을 완료하기 전에 데이터를 분리하는 것을 권장
 

- 데이터 탐색: 상관계수 출력

  • 데이터의 독립성을 살펴볼 때는 상관계수만 보면 안된다.
    여러개의 피쳐가 다른 피쳐를 설명할 수 있기 때문이다.
  • corr() 함수는 숫자가 아닌 컬럼을 제거하고 사용해야 한다. 
#숫자 이외의 컬럼 제거 - housing.info()
corr_matrix = housing.drop("ocean_proximity", axis=1).corr()
corr_matrix
  • 방 개수와 침대방 개수는 상관계수가 높다. 당연히 높겠지

# pandas로 해보기

from pandas.plotting import scatter_matrix

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

 
#출력된 이미지 저장

plt.savefig('산포도', format='png', dpi=300)

 
 

- 특성 조합을 이용한 탐색

  • 여러 특성을 조합해서 새로운 특성을 만들고, 그 특성과 다른 특성간의 관계 확인하는 것도 중요
  • 방의 개수보다 방의 개수 대비 침실의 개수와 같은 특성을 만들어서 확인해볼 수 있다.
housing['bedrooms_per_room'] = housing['total_bedrooms']/housing['total_rooms']
corr_matrix = housing.drop('ocean_proximity', axis=1).corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
median_house_value    1.000000
median_income         0.688075
income_cat            0.643892
total_rooms           0.134153
housing_median_age    0.105623
households            0.065843
total_bedrooms        0.049686
population           -0.024650
longitude            -0.045967
latitude             -0.144160
bedrooms_per_room    -0.255880
Name: median_house_value, dtype: float64
  • median_house_value 를 타겟으로 회귀하려 한다. 
  • 방의 개수와 침실의 개수는 상관계수가 낮다. 그런데 bedrooms_per_room은 -0.25니까 회귀를 하려면 차라리 이걸 쓰는게 낫다.
  • 그래서 도메인 지식이 중요하다.

 

- 피처와 타겟 분리

#훈련 데이터 복제 - 레이블을 제외한 데이터 복제
housing_feature = start_train_set.drop('median_house_value', axis=1)

#훈련 세트를 위해 레이블 삭제
#레이블에 변형을 적용하지 않기 위해서 레이블값도 복제
housing_leabels = start_train_set['median_house_value'].copy()

 
 

- 전처리 - 누락된 데이터 처리
 

#결측치 확인

#NaN 결측치 확인
sample_incomplete_rows = housing[housing.isnull().any(axis=1)].head()
sample_incomplete_rows

 
#누락 행 제거

#total_bedrooms가 누락된 행 제거
housing_feature.dropna(subset=["total_bedrooms"]).info()
 #   Column              Non-Null Count  Dtype   
---  ------              --------------  -----   
 0   longitude           16354 non-null  float64 
 1   latitude            16354 non-null  float64 
 2   housing_median_age  16354 non-null  float64 
 3   total_rooms         16354 non-null  float64 
 4   total_bedrooms      16354 non-null  float64 
 5   population          16354 non-null  float64 
 6   households          16354 non-null  float64 
 7   median_income       16354 non-null  float64 
 8   ocean_proximity     16354 non-null  object  
 9   income_cat          16354 non-null  category
  • 모두 16354!

# 누락 열 제거

#열 제거
sample_incomplete_rows.drop('total_bedrooms', axis=1)

 
# 누락 값 중간값으로 대체

from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")

#숫자로된 컬럼만 추출해서 NaN->중간값 대체
housing_num = housing.select_dtypes(include=[np.number])
imputer.fit(housing_num)

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

housing_tr.head()

 
 

- 범주형 데이터 처리

  • 머신러닝은 숫자만 다룸
  • 범주형이나 문자열 데이터가 있다면 숫자로 변환해야 함
  • 범주형의 경우 일련번호 형태로 변경하거나 원핫인코딩 필요
#범주형 데이터 추출
housing_cat = housing_features['ocean_proximity']
#일련번호로 만들기
housing_cat_encoded, housing_categoris = housing_cat.factorize()
print(housing_cat_encoded[:10])
print(housing_categoris)
[0 1 0 1 2 3 2 2 2 2]
Index(['INLAND', 'NEAR OCEAN', '<1H OCEAN', 'NEAR BAY', 'ISLAND'], dtype='object')
  • 원핫 인코딩 - 범주형의 개수만큼 열을 만들어서 해당하는 열에만 1 표시
#sklearn 의 변환기들은 2차원 배열을 요구합니다.
from sklearn.preprocessing import OrdinalEncoder
oridinalEncoder = OrdinalEncoder()
result = oridinalEncoder.fit_transform(housing_features['ocean_proximity'])
print(result[:10])
ValueError: Expected 2D array, got 1D array instead:
array=['INLAND' 'NEAR OCEAN' 'INLAND' ... '<1H OCEAN' '<1H OCEAN' 'INLAND'].
Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.
#sklearn 의 변환기들은 2차원 배열을 요구합니다.
from sklearn.preprocessing import OrdinalEncoder
oridinalEncoder = OrdinalEncoder()
result = oridinalEncoder.fit_transform(housing_features[['ocean_proximity']])
print(result[:10])
[[1.]
 [4.]
 [1.]
 [4.]
 [0.]
 [3.]
 [0.]
 [0.]
 [0.]
 [0.]]
#원 핫 인코딩 - 범주형의 개수 만큼 열을 만들어서 해당하는 열에만 1을 표시
from sklearn.preprocessing import OneHotEncoder
oneHotEncoder = OneHotEncoder()

#기본적으로 희소 행렬(sparse matrix)
result = oneHotEncoder.fit_transform(housing_features[['ocean_proximity']])
print(result[:10])
#밀집 행렬로 변환
print(result.toarray()[:10])
  (0, 1) 1.0
  (1, 4) 1.0
  (2, 1) 1.0
  (3, 4) 1.0
  (4, 0) 1.0
  (5, 3) 1.0
  (6, 0) 1.0
  (7, 0) 1.0
  (8, 0) 1.0
  (9, 0) 1.0

[[0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1.]
 [0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1.]
 [1. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0.]
 [1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0.]]

 
 

- 특성 스케일링

  • 입력 숫자 특성들의 스케일이 많이 다르면 정확한 예측을 하지 못하는 경우가 발생
  • 입력 숫자 특성들만 스케일링을 수행하는데 타겟의 경우는 범위가 너무 큰 경우 로그 스케일링을 한다.
  • 훈련 데이터와 테스트 데이터로 분할된 경우 일반적으로 훈련 데이터에만 fit 수행하고
    훈련 데이터와 테스트 데이터에 transform 수행
  • min-max scaler와 standard scaler 주로 이용

- 정규화 : MinMaxScaler

  • (데이터 - 최소값) / (최대값 - 최소값)
  • 최대값이 이상치인 경우 영향을 많이 받게 됨

- 표준화: StandardScaler

  • 먼저 평균을 뺀 후 (평균=0) 표준편차로 나눠서 분포의 분산이 1이 되도록 함
  • 범위의 상한과 하한이 없어서 어느정도 값이 만들어지질지 예측하기 어려움
    • 딥러닝에서는 0 ~ 1의 값만을 요구하는 경우가 있어서 사용이 불가
  • 이상치의 영향을 덜 받는다. 로버스트함

- 전처리 작업을 위한 Pipeline

  • 연속해서 작업 수행 - 여러 작업을 하나로 묶어서 수행하는 것
  • 결측치 대체를 하고 스케일링을 수행한다고 했을 때 따로 작업을 해도 되지만 묶어서 한번에 수행 가능
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

#수행하고자 하는 작업을 이름 과 추정기를 튜플로 묶어서 list로 설정해주면 됩니다.
num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy = 'median')),
    ('std_scaler', StandardScaler())
])

housing_num_tr = num_pipeline.fit_transform(housing_num)
print(housing_num_tr)
[[-0.94135046  1.34743822  0.02756357 ...  0.73260236  0.55628602    -0.8936472 ]
 [ 1.17178212 -1.19243966 -1.72201763 ...  0.53361152  0.72131799   1.292168  ]
 [ 0.26758118 -0.1259716   1.22045984 ... -0.67467519 -0.52440722   -0.52543365]
 ...
 [-1.5707942   1.31001828  1.53856552 ... -0.86201341 -0.86511838    -0.36547546]
 [-1.56080303  1.2492109  -1.1653327  ... -0.18974707  0.01061579    0.16826095]
 [-1.28105026  2.02567448 -0.13148926 ... -0.71232211 -0.79857323    -0.390569  ]]
  • 수행하고자 하는작업을이름과 추정기를 튜플로 묶어서 list로 설정

- ColumnTransformer

  • sklearn 0.20 버전에서 추가된 변환기로 여러 특성에 다른 변환 처리를 할 수 있도록 해주는 클래스
  • Pipeline과 유사하게 생성, 튜플을 만들 때 (변환기 이름, 변환기, 컬럼 이름리스트) 형태로 대입
from sklearn.compose import ColumnTransformer

#숫자 컬럼 이름 리스트
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
#print(num_attribs)

#열 별로 다른 변환기를 적용하기 위한 인스턴스 생성
full_pipeline = ColumnTransformer([
    ("num", num_pipeline, num_attribs),
    ("cat", OneHotEncoder(), cat_attribs)
])

#변환기 적용
hosing_prepared = full_pipeline.fit_transform(housing)
print(hosing_prepared)
[[-1.32783522  1.05254828  0.98214266 ...  0.          1.     0.        ]
 [-1.32284391  1.04318455 -0.60701891 ...  0.          1.     0.        ]
 [-1.33282653  1.03850269  1.85618152 ...  0.          1.     0.        ]
 ...
 [-0.8237132   1.77823747 -0.92485123 ...  0.          0.     0.        ]
 [-0.87362627  1.77823747 -0.84539315 ...  0.          0.     0.        ]
 [-0.83369581  1.75014627 -1.00430931 ...  0.          0.     0.        ]]

 
#피처 전처리

housing_prepared = full_pipeline.fit_transform(housing_features)

 
 

- 모델 적용 및 테스트

- 선형 회귀 모델

#선형 회귀 모델을 이용
from sklearn.linear_model import LinearRegression

#예측 모델 인스턴스 생성
lin_reg = LinearRegression()

#훈련
lin_reg.fit(housing_prepared, housing_labels)

#테스트
some_data = housing_features.iloc[:5]
some_labels = housing_labels.iloc[:5]

#샘플 피처 전처리
some_data_prepared = full_pipeline.transform(some_data)

print("예측한 값:", lin_reg.predict(some_data_prepared))
print("실제 값:", list(some_labels))
예측한 값: [ 88983.14806384 305351.35385026 153334.71183453 184302.55162102
 246840.18988841]
실제 값: [72100.0, 279600.0, 82700.0, 112500.0, 238300.0]

 
 

- 평가 지표

  • 모델을 생성하고 나면 이 모델을 다른 모델과 비교하기 위해서 평가지표 사용
  • 회귀의 평가지표로 사용 - RMSE, RMAE
    • RMSE: 예측한 값과 실제 값의 차이(residual-잔차)를 제곱해서 더한 후 제곱근을 해서 사용
    • RMAE: 잔차에 절대값을 적용한 후 합계를 구한 것
    • 이 값이 적은 쪽이 우수한 모델
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error

#모델을 만들 때 사용한 데이터를 가지고 예측
housing_predictions = lin_reg.predict(housing_prepared)

#잔차 제곱합
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)

#잔차의 절대값 합
lin_mae = mean_absolute_error(housing_labels, housing_predictions)

print("잔차 제곱합:", lin_mse)
print("잔차 제곱합의 제곱근:", lin_rmse)
print("잔차 절대값합:", lin_mae)
잔차 제곱합: 4767980139.451871
잔차 제곱합의 제곱근: 69050.56219504567
잔차 절대값합: 49905.329442715316
  • 제곱합은 단위가 너무 큼. 판정하기 어렵기 때문에 제곱근을 해서 스케일링한다.
  • 집값은 120000 ~ 260000 사이가 많은데, 실제로는 7만에서 5만 차이가 나니까 잔차가 많다.
  • 훈련에 사용한 데이터로 테스트 했는데 훈련에 사용한 데이터도 잘 맞지 않으면 과소 적합이라고 한다.
    • 데이터를 더 수집하기
    • 다른 모델(알고리즘)을 사용해보기
    • 하이퍼파라미터를 조정해보기
  • 훈련 데이터에는 잘 맞지만, 테스트 데이터나 새로운 데이터에 잘 맞지 않으면 과대 적합이라고 한다. 

 

- 새로운 모델 적용 

- Decision Tree

  • 트리 모델은 일반적인 형태로 검증하지 않고 데이터를 K개의 그룹으로 나눈 후 (k-1)개의 데이터 그룹을 사용해서 모델을 생성하고 나머지 그룹을 이용해서 테스트 수행하고 이 작업을 k번 반복해서 그 때의 평균을 리턴한다.
  • sklearn에서는 점수가 높은 쪽이 좋다고 생각하기 때문에 평가지표를 설정할 때 (-)를 곱해서 사용한다.
from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(housing_prepared, housing_labels)

housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
print(tree_rmse)
0.0
  • 얜 안되겠네

- K-fold cross-validation (K 겹 교차 검증)

 

from sklearn.model_selection import cross_val_score

#10개의 K를 설정
scores = cross_val_score(tree_reg, housing_prepared, housing_labels, 
                        scoring="neg_mean_squared_error", cv=10)
tree_rmse_scores = np.sqrt(-scores)
print(tree_rmse_scores)
[71177.6601991  69770.07865373 64770.5639395  68536.60203993
 67057.08155801 68847.12456973 70977.38255647 69208.86346929
 67187.87131535 73280.38732407]
  • 그닥 별로. 오히려 나쁜듯

 

- 그리드 탐색

  • 가장 좋은 하이퍼 파라미터를 찾는 것
    • 파라미터: 함수 또는 메소드가 수행할 때 결정하는 데이터
    • 하이퍼 파라미터: 사용자가 직접 설정하는 데이터
      대부분의 하이퍼파라미터는 가장 알맞은 기본값을 소유하고 있다.
  • GridSearchCV를 이용하면 여러종류의 하이퍼 파라미터를 설정해서 훈련한 후 가장 좋은 하이퍼 파라미터를 추천
  • 파라미터를 설정할 때는 list의 dict로 설정하게 되는데 dict에 하이퍼 파라미터 이름과 값의 list를 설정하는데 하나의 dict 내에 있는 파라미터는 모든 조합을 가지고 수행하고 각 dict는 별개로 수행
  • 시간이 오래 걸릴 가능성이 높음
from sklearn.ensemble import RandomForestRegressor

forest_reg = RandomForestRegressor(random_state=42)

#파라미터 조합을 생성: 12 + 6 : 18번 수행
param_grid = [{'n_estimators':[3, 10, 30], 'max_features': [2, 4, 6, 8]}, 
              {'bootstrap':[False], 'n_estimators':[3, 10], 'max_features':[2, 3, 4]}]

from sklearn.model_selection import GridSearchCV
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
                          scoring='neg_mean_squared_error', 
                          return_train_score=True, n_jobs=2)

grid_search.fit(housing_prepared, housing_labels)
print(grid_search.best_params_)
#내장 파라미터는 뒤에 _ 붙여야 함
{'max_features': 8, 'n_estimators': 30}

 
# 평가 점수 확인

cvres = grid_search.cv_results_
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
    print(np.sqrt(-mean_score), params)
63827.76261554265 {'max_features': 2, 'n_estimators': 3}
55056.82212305312 {'max_features': 2, 'n_estimators': 10}
52673.5498401615 {'max_features': 2, 'n_estimators': 30}
60299.48845134689 {'max_features': 4, 'n_estimators': 3}
53106.41271952157 {'max_features': 4, 'n_estimators': 10}
50370.55528306362 {'max_features': 4, 'n_estimators': 30}
58363.22748437211 {'max_features': 6, 'n_estimators': 3}
52446.057900340325 {'max_features': 6, 'n_estimators': 10}
50177.91173851986 {'max_features': 6, 'n_estimators': 30}
58058.12321723554 {'max_features': 8, 'n_estimators': 3}
51849.42681935635 {'max_features': 8, 'n_estimators': 10}
49941.11534754462 {'max_features': 8, 'n_estimators': 30}
62820.05402812565 {'bootstrap': False, 'max_features': 2, 'n_estimators': 3}
53846.18083156347 {'bootstrap': False, 'max_features': 2, 'n_estimators': 10}
59026.17902108823 {'bootstrap': False, 'max_features': 3, 'n_estimators': 3}
52996.55803561763 {'bootstrap': False, 'max_features': 3, 'n_estimators': 10}
58842.47703118809 {'bootstrap': False, 'max_features': 4, 'n_estimators': 3}
51891.75100173946 {'bootstrap': False, 'max_features': 4, 'n_estimators': 10}

 
#최적의 모델 가져오기

print(grid_search.best_estimator_)
RandomForestRegressor(max_features=8, n_estimators=30, random_state=42)

 

- 랜덤 서치

  • 그리드 서치는 파라미터 값을 직접 입력해서 선택하도록 하는데 파라미터가 적을 때는 유용하지만 많을 때는 어려움
  • RandomizedSearchCV는 파라미터의 상한과 하한을 
forest_reg = RandomForestRegressor(random_state=42)

from scipy.stats import randint
#파라미터 조합을 생성: 12 + 6 : 18번 수행
param_distribs = {'n_estimators':randint(low=1, high=200), 
                  'max_features': randint(low=1, high=8)}

from sklearn.model_selection import RandomizedSearchCV
random_search = RandomizedSearchCV(forest_reg, param_distributions = param_distribs, cv=5,
                          scoring='neg_mean_squared_error', n_jobs=-1,
                                  n_iter=10)

grid_search.fit(housing_prepared, housing_labels)

 
- 앙상블

  • 여러 개의 모델을 사용하는 방식
  • DecisionTree는 하나의 트리를 이용하지만 RandomForest는 여러개의 DecisionTree를 가지고 예측

 
- 테스트를 할 때는 모델을 만들 때 사용한 데이터가 아닌 테스트 데이터로 수행하기도 함

  • Overfitting(과대적합): 훈련 데이터에 잘 맞지만 테스트 데이터에 잘 맞지 않는 상황
  • Underfitting(과소적합): 훈련데이터에도 잘 맞지 않는 상황