2장. 머신러닝 처음부터 끝까지 - 데이터분석과 테스트세트 분리

기운찬곰·2020년 10월 18일
1

Machine Learning

목록 보기
4/6

🤚 전체적인 진행과정과 실습, 일부 내용은 핸즈온 머신러닝(2판), 한빛미디어 를 참고했다. 이 장에서의 목표는 캘리포니아의 인구조사 데이터를 사용해 주택가격 모델을 만드는 것이다.

colab 실습 주소 : https://colab.research.google.com/github/rickiepark/handson-ml2/blob/master/02_end_to_end_machine_learning_project.ipynb


데이터 분석하기

pandas(판다스)

판다스(Pandas)는 파이썬 데이터 처리를 위한 라이브러리이다. 파이썬을 이용한 데이터 분석과 같은 작업에서 필수 라이브러리로 알려져있다. 아나콘다를 설치하면 자동으로 설치되어진다. 파이썬을 설치한 경우라면 따로 설치를 진행하면 된다.

사용법

pandas는 크게 세가지의 자료구조를 지원하고 있는데, 1차원 자료구조인 Series, 2차원 자료구조인 DataFrame, 그리고 3차원 자료구조인 Panel을 지원한다. 이 중 데이터프레임을 가장 많이 사용한다.

또한, Pandas는 CSV, 텍스트, Excel, SQL, HTML, JSON 등 다양한 데이터 파일을 읽고 데이터 프레임을 생성할 수 있다. 저번시간에 실행했던 코드는 그 중에서 Excel 파일을 읽어서 데이터 프레임을 생성한 경우이다.

데이터 프레임 조회

  • df.head(n) - 앞 부분을 n개만 보기. n이 없으면 5개만 조회
  • df.tail(n) - 뒷 부분을 n개만 보기. n이 없으면 5개만 조회
  • df['열이름'] - 해당되는 열을 확인
  • info() - 데이터에 대한 간략한 설명과 특히 전체 행수, 각 특성의 데이터 타입과 널이 아닌 값의 개수를 확인하는데 유용

위의 명령어는 데이터프레임에서 원하는 구간만 확인하기 위한 명령어로서 유용하게 사용된다. 아래 이미지는 info() 를 실행한 결과이다.

  • 데이터 셋은 20,640개이며, 특이하게도 total_bedrooms만 20433개이다. 이는 나머지 207개는 null 값을 의미한다.
  • ocean_proximity 필드를 제외하고는 모두 숫자형(float64)임을 확인할 수 있다.
  • ocean_proximity 필드의 데이터 타입이 object이므로 어떤 파이썬 객체도 될 수 있지만, CSV 파일을 보면 텍스트 특성일 것이라고 추측할 수 있다. 해당 열의 값이 반복적으로 나타나는 것으로 보아 범주형(category) 변수임을 확인할 수 있다.

어떤 카테고리가 있고 각 카테고리마다 얼마나 많은 구역이 있는지 value_counts() 메소드로 확인가능하다.

housing["ocean_proximity"].value_counts()

마지막으로 describe() 메소드를 사용하면 모든 필드에 대해서 count, mean, std, min, 백분위수를 알 수 있다.


Matplotlib(맷플롯립)

Pandas를 통해서 분석이 가능하지만 이왕이면 그림으로 보여주는게 더 효과적이겠지? 그런 라이브러리 또한 파이썬은 가지고 있다. 바로 맷플롯립(Matplotlib)를 사용하면 된다. Matplotlib은 데이터를 차트(chart)나 플롯(plot)으로 시각화(visulaization)하는 패키지이다.

사용법

저희는 그중에서 히스토그램을 통한 결과를 나타내볼것입니다. 히스토그램은 수평축은 값의 범위를, 수직축은 그 범위에 속한 샘플 수를 나타내는 그래프입니다.

%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(15, 8))
plt.show()
  • bins는 값의 범위를 나누는 갯수 (막대의 개수)
  • figsize는 창의 크기를 나타낸다. 저는 가로 15, 세로 8이 적당한거 같습니다.
  • show는 결과를 출력하도록 하는 메소드이다.

이렇게 보니까 훨씬 직관적이지? 근데 조금 특이한 그래프 모양이 housing_median_age와 median_house_value인듯 하다. 끝에 값이 크게 올라가 있다. 이는 범위를 넘어가는 값을 하나로 모아놨기 때문이 아닐까 싶다. 이럴 경우 문제가 있을 수 있기 때문에 나중에 처리를 해줘야 할 것이다.


테스트 세트 만들기

마지막으로 하나만 더 하고 마치도록 하겠다. 바로 데이터를 훈련용 데이터와 테스트용 데이터로 나누는 방법이다. 나누는 이유는 나중에 테스트 데이트 셋으로 평가를 할때 공정하게 하도록 하기 위해서이다.

나누는 방법은 이론적으로는 간단하다. 무작위로 어떠한 샘플을 선택하여 데이터셋의 20% 정도를 떼어 놓으면 된다.

방법 1. 무작위 분리

import numpy as np

def split_train_test(data, test_ratio):
    # 해당 숫자만큼의 개수를 갖는 배열(0부터 len(data))을 만들고 그 순서는 무작위로 지정한다
    # 예를 들어 [11885  6149  6293 ... 19507  1650 11009]
    shuffled_indices = np.random.permutation(len(data)) 
    
    test_set_size = int(len(data) * test_ratio) # 여기서는 20% 만 지정
    test_indices = shuffled_indices[:test_set_size]
    train_indices = shuffled_indices[test_set_size:]
	
    # iloc는 위치 정수를 기반으로 인덱싱하는 함수이다.
    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")

numpy 라는 라이브러리를 import 했다. numpy 역시 아주아주 중요한 라이브러리 중 하나이다.

Numpy는 수치 데이터를 다루는 파이썬 패키지이다. Numpy의 핵심이라고 불리는 다차원 행렬 자료구조인 ndarray를 통해 벡터 및 행렬을 사용하는 선형 대수 계산에서 주로 사용된다. Numpy는 편의성뿐만 아니라, 속도면에서도 순수 파이썬에 비해 압도적으로 빠르다는 장점이 있다.

결과는 잘 나왔다. 하지만 여기에는 문제가 있다 🤔. 프로그램을 다시 실행하면 다른 테스트 세트가 생성되기 때문이다. 여러 번 계속하면 전체 데이터 셋을 보는 셈이므로 이러한 상황은 피하는 것이 좋다.

해결방법은... 🙋‍♂️

  1. 처음 실행에서 테스트 세트를 저장하고 다음번 실행에서 이를 불러들인다.
  2. 항상 같은 난수 인덱스가 생성되도록 난수발생기의 초기값을 지정한다. ex) np.random.seed(42)
  3. 해시값을 계산해서 분할한다.

방법2. Scikit-learn(사이킷런) 이용

Scikit-learn(사이킷런) 은 사용하기 쉬운 머신러닝 라이브러리이다. 데이터 전처리, 세부조정, 모델 평가, 분류 알고리즘 등을, 간편한 API로 제공하고 있다.

여러 가지 기능 중에서 데이터셋을 여러 서브셋으로 나누는 다양한 방법 또한 제공하고 있다. 따라서 보통은 이 방식을 사용하면 된다.

from sklearn.model_selection import train_test_split

(...생략...)

# test_size를 통해 20%는 테스트 셋으로 나누도록 설정
# random_state를 통해 seed를 설정해서 다음에도 같은 데이터 테스트가 나올 수 있도록 설정
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42) 
print(len(train_set), "train +", len(test_set), "test")


방법3. 계층적 샘플링

방법1과 2의 근본적인 문제점은 순수한 무작위 샘플링 방식으로 샘플링 편향이 생길 가능성이 크다. 🤔 (참고)

따라서, 전체를 대표할 수 있는 샘플들을 선택하기 위해 노력해야 한다. 예를 들어 미국 인구의 51.3%가 여성이고 48.7%가 남성이라면, 잘 구성된 설문조사는 샘플에서도 이 비율을 유지해야 한다. 이를 계층적 샘플링(Stratified Sampling) 이라고 한다.

여기서는 주택가격과 가장 큰 상관관계가 있는 중간 소득을 계층별로 분류하여 샘플링하도록 하겠다.

1단계 - 계층 설정

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"].hist()
plt.show()

중간 소득 대부분은 1.5 ~ 6($15,000~$60,000) 사이에 모여 있다. 계층별로 분류하기전에 각 계층이 충분히 크도록 너무 많은 계층으로 나누지 않고 적당히 대표값을 설정해야 한다.

pd.cut() 함수를 사용해 카테고리 1은 0~1.5, 카테고리 2는 1.5~3... 으로 해서 카테고리 5까지 특성을 만들었다. 그리고 그래프로 출력해봤다. 가로는 소득, 세로는 그 범위에 해당하는 수를 나타낸다.

print(housing["income_cat"])

median_income이 카테고리 범위에 따라 1~5까지 나눠진것을 볼 수 있다.

2단계 - 계층별로 나누기

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

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(strat_test_set["income_cat"].value_counts() / len(strat_test_set))
print(housing["income_cat"].value_counts() / len(housing))

이렇게 나누고 나서 훈련용과 테스트 셋에 있는 income_cat의 비율을 보면 동일한게 나눠진 것을 볼 수 있을 것이다.

3단계

income_cat은 이제 더이상 쓰이지 않기 때문에 이 특성을 삭제해서 원래상태로 되돌려준다.

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

print(strat_train_set) # income_cat이 사라졌는지 확인

마침

References

profile
배움을 좋아합니다. 새로운 것을 좋아합니다.

0개의 댓글