머신러닝 기초 | 데이터 전처리 | train_test_split 함수 사용법 | 원본 데이터 비율에 맞게 훈련/테스트 데이터 나누는 법 | 표준점수로 기준 맞추는 법 | kneighbors() 메서드(가까운 이웃 찾기) | 산점..

    728x90

     

    1. 넘파이로 데이터 준비하기

     

    ▶ 도미와 빙어 데이터 준비

    fish_length = [25.4, 26.3, 26.5, 29.0, 29.0, 29.7, 29.7, 30.0, 30.0, 30.7, 31.0, 31.0, 
                    31.5, 32.0, 32.0, 32.0, 33.0, 33.0, 33.5, 33.5, 34.0, 34.0, 34.5, 35.0, 
                    35.0, 35.0, 35.0, 36.0, 36.0, 37.0, 38.5, 38.5, 39.5, 41.0, 41.0, 9.8, 
                    10.5, 10.6, 11.0, 11.2, 11.3, 11.8, 11.8, 12.0, 12.2, 12.4, 13.0, 14.3, 15.0]
    fish_weight = [242.0, 290.0, 340.0, 363.0, 430.0, 450.0, 500.0, 390.0, 450.0, 500.0, 475.0, 500.0, 
                    500.0, 340.0, 600.0, 600.0, 700.0, 700.0, 610.0, 650.0, 575.0, 685.0, 620.0, 680.0, 
                    700.0, 725.0, 720.0, 714.0, 850.0, 1000.0, 920.0, 955.0, 925.0, 975.0, 950.0, 6.7, 
                    7.5, 7.0, 9.7, 9.8, 8.7, 10.0, 9.9, 9.8, 12.2, 13.4, 12.2, 19.7, 19.9]

     

    ▶ 넘파이 라이브러리 가져오기

    import numpy as np

     


    ▶ np.column_stack() 함수

    : 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결하는 함수
    : 2개의 리스트를 붙힘, 연결할 리스트는 튜플로 전달

    np.column_stack(([1,2,3], [4,5,6]))

     

    * fish_data 만들기

    : .column_stack() 함수로 fish_length와 fish_weight를 합침

    fish_data = np.column_stack((fish_length, fish_weight))
    print(fish_data[:5])


    ▶ np.ones() / np.zeros() 함수

    : 리스트[1], [0]을 곱해 타깃 데이터 만들기

    fish_target = np.concatenate((np.ones(35), np.zeros(14)))
    print(fish_target)

     


    2. 사이킷런으로 훈련 세트와 테스트 세트 나누기

     

    ▶ 사이킷런

    : 머신러닝 모델을 위한 알고리즘뿐만 아니라 유틸리티 도구도 제공
    : 대표적인 도구가 train_test_split() 함수

     

    ▶ train_test_split() 함수

    : 전달받은 리스트나 배열을 비율에 맞게 훈련 세트와 테스트 세트 나눠줌
    : 사이킷런의 model_selection 모듈 아래에 있음

     

    ▶ train_test_split() 함수의 매개변수 random_state

    : 매개변수 random_state에 섞을 데이터 수 적기 (np.random.seed()함수로도 동일한 실습 가능)

    : 기본적으로 25%를 테스트 세트로 생성

    from sklearn.model_selection import train_test_split
    train_input, test_input, train_target, test_target = train_test_split(
        fish_data, fish_target, random_state=42)

     

     

    * 훈련/테스트 세트의 데이터 shape 살펴보기

    print(train_input.shape, test_input.shape)

     

    * 타깃 데이터의 shape 살펴보기

    print(train_target.shape, test_target.shape)

     

    ▶ 테스트 데이터 출력해보기

    : 도미(1)와 빙어(0)가 잘 섞였는지 확인

    print(test_target)

    : 잘 섞인 것 같지만 빙어의 비율이 조금 모자름

    → 전체 데이터의 [도미 : 빙어] 비율은 [35: 14]로  [2.5 : 1] 정도인데 테스트 세트 데이터의 비율은 [3.3 : 1]임

    → 샘플링 편향이 발생

     

    ▶ train_test_split() 함수의 매개변수 stratify

    : 샘플링 편향이 발생했을 때의 해결 방법

    : 매개변수 stratify에 타깃 데이터를 전달하면 클래스 비율에 맞게 데이터를 나눔

    : 훈련 데이터가 작거나 특정 클래스의 샘플 개수가 적을 때 특히 유용함

    train_input, test_input, train_target, test_target = train_test_split(
        fish_data, fish_target, stratify=fish_target, random_state=42)
    print(test_target)

    : 빙어가 1개 추가되면서 테스트 세트의 비율이 [2.25 : 1] 로 됨

    : 데이터가 작아 전체 훈련 데이터의 비율과 동일하게 맞추긴 어렵지만 꽤 비슷한 비율

     


    3. 수상한 도미 한 마리

     

    ▶ k-최근접 이웃 훈련

    : 훈련 데이터로 모델을 훈련하고, 테스트 데이터로 모델을 평가

    from sklearn.neighbors import KNeighborsClassifier
    
    kn = KNeighborsClassifier()
    kn.fit(train_input, train_target)
    kn.score(test_input, test_target)

    : 정확도 100%, 테스트 세트의 도미와 빙어를 모두 올바르게 분류함

     


    ▶ [25, 150] 데이터는 뭘로 분류하는지 보기

    print(kn.predict([[25, 150]]))

    : 빙어로 분류함

     

    ▶ 산점도 그려보기

    :  [25, 150] 데이터는 삼각형으로 표현

    plt.scatter(train_input[:,0], train_input[:,1])
    plt.scatter(25, 150, marker='^')
    plt.xlabel('length')
    plt.ylabel('weight')
    plt.show()

     

    >> 삼각형([25, 150] 데이터)은 오른쪽 위로 뻗어 있는 도미 데이터에 더 가까움

    >> 그런데 이 삼각형 데이터를 빙어라고 판단한 이유는?

    >> k-최근접 이웃은 주변의 샘플 중에서 다수인 클래스를 예측으로 사용하기 때문에 왼쪽 아래에 낮게 깔린 빙어 데이터가 가깝다고 판단 내린 것

     

    ▶ kneighbors() 메서드

    distances, indexes = kn.kneighbors([[25, 150]])

    : KNeighborsClassifier 클래스는 주어진 샘플에서 가장 가까운 이웃을 찾아 주는 kneighbors() 메서드를 제공
    : 이 메서드는 이웃까지의 거리와 이웃 샘플의 인덱스를 반환
    : KNeighborsClassifier 클래스의 이웃 개수인 n_neighbors의 기본값은 5이므로 5개의 이웃이 반환

     

    ▶ 산점도 그려보기

    plt.scatter(train_input[:,0], train_input[:,1])
    plt.scatter(25, 150, marker='^')
    plt.scatter(train_input[indexes,0], train_input[indexes,1], marker='D') 
    plt.xlabel('length')
    plt.ylabel('weight')
    plt.show()

    : market = 'D'로 지정하면 산점도를 마름모로 변경

    >> 이웃 샘플 중 4개의 샘플은 모두 빙어

     

     

    ▶ 데이터 직접 확인해보기

     

    * train_input[indexes] 데이터

    print(train_input[indexes])

     

    * train_target[indexes] 데이터

    print(train_target[indexes])

    : [길이 25, 무게 150] 데이터와 가장 가까운 데이터는 빙어(0) 가 많음(5마리 중 도미는 1마리, 빙어는 4마리)

    : 산점도 상으로는 도미가 더 가까워보이지만 k-최근접 이웃 모델이 [25, 150] 데이터를 빙어로 예측하는 것은 타당해보임

     

    * distances 데이터

    : 이 문제의 실마리를 찾기 위해 kneighbors() 메서드에서 반환한 distances 배열을 출력
    : 이 배열에는 이웃 샘플까지의 거리가 담겨 있음

    print(distances)

     

     


    3. 기준 맞추기

     

    ▶ 기준을 맞추기 전의 상태

     

    >> 삼각형 샘플에서 가장 가까운 첫 번째 샘플까지의  거리는 92이고, 그 외 가장 가까운 샘플은 130, 138...
    >>  92와 130의 비율 차이가 너무 커 보임
    : 92의 거리보다 족히 몇 배는 되어 보이는데 거리가 겨우 130인 것이 이상함
    x축은 (10~40), y축은 (0~1000)이기 때문에 y축으로 조금만 멀어져도 거리가 아주 큰 값으로 계산
    → 그래서 오른쪽 위의 도미 샘플이 이웃으로 선택되지 못한 것

     

    ▶ 기준 맞추기

    : x축의 범위도 y축과 동일하게 0~1000으로 맞추기

    : 맷플롯립에서 x축 범위를 지정하려면 xlim() 함수를 사용(이와 마찬가지로 y축 범위 지정 시엔 ylim 함수 사용)

    plt.scatter(train_input[:,0], train_input[:,1])
    plt.scatter(25, 150, marker='^')
    plt.scatter(train_input[indexes,0], train_input[indexes,1], marker='D')
    plt.xlim((0, 1000))
    plt.xlabel('length')
    plt.ylabel('weight')
    plt.show()

    >> 두 특성(길이와 무게)의 값이 놓인 범위가 매우 다름

    >> 이를 두 특성의 스케일이 다르다고도 말함(매우 흔한 일)

    >> 이런 데이터라면 생선의 길이(x축)는 가장 가까운 이웃을 찾는 데 크게 영향을 미치지 못함, 오로지 생선의 무게(y축)만 고려 대상

     


    4. 데이터 전처리

     

    ▶ 데이터 전처리

    : 특성 값을 일정한 기준으로 맞추는 작업

    : 알고리즘들은 샘플 간 거리에 영향을 많이 받으므로 데이터의 기준이 다르면 알고리즘이 올바르게 예측할 수 없음
    : 알고리즘이 거리 기반일 때 특히 그러함(K-최근접 이웃도 포함)

     

    ▶ 데이터 전처리 방법

     

    * 표준점수

    : 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타냄
     : 이를 통해 실제 특성값의 크기와 상관없이 동일한 조건으로 비교 가능

    mean = np.mean(train_input, axis=0)
    std = np.std(train_input, axis=0)

    : 특성마다 값의 스케일이 다르므로 평균과 표준편차는 각 특성별로 계산해야 함
    : axis = 0으로 지정해 행을 따라 각 열의 통계 값을 계산하게 함
    : 계산된 평균과 표준편차를 출력

    print(mean, std)

     

    ▶ 표준 점수로 변환하기

    = (원본 데이터 - 평균) / 표준편차

    : 넘파이 기능을 브로드캐스팅(BROADCASTING)이라고 함

    train_scaled = (train_input - mean) / std

     

    ▶ [25, 150] 데이터도 표준점수로 변환하기

    new = ([25, 150] - mean) / std

     


    5. 전처리 데이터로 모델 훈련하기

     

    ▶ 표준점수로 변환한 데이터의 산점도 그려보기

    plt.scatter(train_scaled[:,0], train_scaled[:,1])
    plt.scatter(new[0], new[1], marker='^')
    plt.xlabel('length')
    plt.ylabel('weight')
    plt.show()

     

    ▶ fit() 함수로 표준점수로 변환한 훈련데이터 훈련

    kn.fit(train_scaled, train_target)


    ▶ 테스트데이터도 표준점수로 변환하기

    test_scaled = (test_input - mean) / std

     

    score() 함수로 테스트 데이터 정확도 확인

    kn.score(test_scaled, test_target)

    >> 정확도 100%

     

    ▶ predict() 함수로 new 데이터( [25, 150]을 표준점수화한 데이터) 예측하기

    print(kn.predict([new]))

    >> 드디어 도미(1)로 예측함

    >> 확실히 길이 25, 무게 150인 생선은 도미일 것임

     


    ▶ kneighbors() 메서드

    distances, indexes = kn.kneighbors([new])

    : new 데이터의 최근접 이웃 데이터 5개 뽑아짐 

     

    ▶ 산점도 그려보기

    plt.scatter(train_scaled[:,0], train_scaled[:,1])
    plt.scatter(new[0], new[1], marker='^')
    plt.scatter(train_scaled[indexes,0], train_scaled[indexes,1], marker='D')
    plt.xlabel('length')
    plt.ylabel('weight')
    plt.show()

    >> 그래프는 앞서 표준점수로 변환하기 전의 산점도와 거의 동일
    >> 크게 달라진 점은 x축, y축의 범위가 -1.5~1.5 사이로 바뀐 것

    728x90

    댓글