로지스틱 회귀 모델을 적용하여 이진 분류를 해보자.

import pandas as pd
wine = pd.read_csv("https://bit,ly/wine_csv_data")

 

wine.head()

 

데이터를 보면 첫 번째 열부터 차례대로 도수, 당도, pH를 나타내며, 마지막 열인 class는 타깃값이 0이면 레드와인, 1이면 화이트 와인이라고 한다.

wine.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6497 entries, 0 to 6496
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   alcohol  6497 non-null   float64
 1   sugar    6497 non-null   float64
 2   pH       6497 non-null   float64
 3   class    6497 non-null   float64
dtypes: float64(4)
memory usage: 203.2 KB

 

describe() 메소드를 이용하여 간략한 통계를 출력해보자.

wine.describe()


alcohol	sugar	pH	class
count	6497.000000	6497.000000	6497.000000	6497.000000
mean	10.491801	5.443235	3.218501	0.753886
std	1.192712	4.757804	0.160787	0.430779
min	8.000000	0.600000	2.720000	0.000000
25%	9.500000	1.800000	3.110000	1.000000
50%	10.300000	3.000000	3.210000	1.000000
75%	11.300000	8.100000	3.320000	1.000000
max	14.900000	65.800000	4.010000	1.000000

describe()를 통해 평균 / 표준편차 / 최솟값/ 쿼터값 / 최대값을 볼 수 있습니다.

 

이제 데이터 전처리 ( 표준화 ) 를 해보자.

 

data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(data,target,test_size=0.2, random_state = 42)

훈련 세트와 테스트 세트의 크기를 확인해 보자.

print(train_input.shape, test_input.shape)

(5197, 3) (1300, 3)

 이제 표준화를 해보자.

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

이제 훈련을 해보자.

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))

0.7808350971714451
0.7776923076923077

낮은 score() + 학습원리 설명하기 어려움

--결정 트리 이용

 

결정 트리 모델

 

from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state = 42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))

0.996921300750433
0.8592307692307692

점수가 높아졌다. !

 

이 트리를 시각화해보자. by using plot tree()

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10,7))
plt_tree(dt)
plt.show()

노드의 길이가 너무 길어서 제한해보자 by using max_depth = 1 하면 하나의 노드를 더 그림

plt.figure(figsize=(10,7))
plot_tree(dt, max_depth = 1 , filled = True, feature_names = ['alcohol','sugar','pH'])
plt.show()

 

그림이 담고 있는 정보는 조건 (sugar), 불순도(gini), 샘플 수, 클래스 별 샘플 수로 이루어져 있다.

루트 노드는 당도가 -0.239이하인지...yes <---   /   No --->

불순도

  • 해당 범주 안에 서로 다른 데이터가 얼마나 섞여 있는지를 뜻함.

gini는 지니 불순도를 의미하는데,

<<<<<클래스개수에 따른 케이스들의 불순한 정도를 나타내는 척도라고 생각하면 될 것 같다.

지니불순도가 필요한 이유는 의사결정을 하는데 있어서 최적의 분류를 위한 결정을 계속해서 맞이하는데 이 결정에 사용되기 때문이다. 이 변수로 인해서 분류를 거쳤을 때 지니불순도가 얼마나 되는가? 를 생각할 수 있게된다. 또다른 불순도지표로는 엔트로피가 있다.>>>>>>

DecisionTreeClassifier 클래스의 criterion 매개변수의 기본값이 gini이다.

 

지니 불순도(gini) = 1 - ( 음성 클래스 비율^2 + 양성 클래스 비율^2 )

결정 트리 모델은 부모와 자식 노드의 불순도 차이가 커지도록 트리를 성장시킨다.

 

(자식 노드의 불순도를 샘플 개수에 비례하여 모두 더하고 그 이후 부모 노드의 불순도에서 뺀다.)

 

=부모 gini - (왼쪽 샘플 수 / 부모 샘플 수) x 왼쪽 gini - (오른쪽 샘플 수 / 부모 샘플 수) x 오른쪽 gini

 

= 0.367 - [(2922/5197) x 0.481] - [(2275/5197) x 0.069] = 0.066

 

부모와 자식 노드간의 불순도 차이를 정보 이득이라고 한다.

따라서 정보 이득이 최대가 되도록 데이터를 나누고, 그 기준으로 불순도 (gini)를 사용한다.

 

한편, 사이킷런에서는 또 다른 불순도 기준이 존재한다. ( 엔트로피 불순도)

 

DeicisionTreeClassifier 클래스에서 criterion='entropy' 

엔트로피 불순도 기준은 아래에 log 2를 붙여서 사용한다.

 

가지치기

끝도 없이 자라나는 트리가 생길 수 있기 때문에 가지치기는 필수적이다.

가장 간단한 방법은 아까 했던 것처럼  max_depth = 으로 지정할 수 있다.

dt = DecisionTreeClassifier( max_depth = 3,  random_state=42 )
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))

0.8454877814123533
0.8415384615384616

 

plt.figure(figsize=(20,15))
plot_tree(dt, filled= True, feature_names=['alcohol', 'sugar','pH'])
plt.show()

위 그림을 보면 깊이 0~3 중 깊이 1은 당도를 기준, 2는 당도,도수,pH 순으로 기준을 사용했다.

최종 노드를 보면 왼쪽에서 세번째 노드만 음성클래스가 더 많은것을 볼 수 있다. 

따라서 이 노드 (주황)에 도착해야만 레드와인으로 분류하는것 같다.

즉, 당도는 -0.802~-0.239 사이, 도수는 0.454보다 작아야 레드와인으로 분류한다.

 

그런데 당도가 음수라는것을 어떻게 해석해야할까.( 문제는 전처리)

 

결정 트리는 표준화 전처리 과정을 하지 않아도 된다!!!

 

dt = DecisionTreeClassifier( max_depth = 3, random_state = 42)
dt.fit(train_input, train_target)
print(dt.score(train_input, train_target))
print(dt.score(test_input, test_target))

0.8454877814123533
0.8415384615384616

트리를 그려보자 (no 전처리)

plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol','sugar','pH'])
plt.show()

이제 가시적으로 해석하기 더 쉽다. 

마지막으로, 결정 트리에는 어떤 특성이 가장 유용한지 나타내는 특성 중요도를 계산해 줍니다. 이 트리의 루트 노드와 깊이1에서 당도를 사용했기 때문에 당도가 가장 유용한 특성일 것 같다.

메서드는 DecisionTreeClassifier.feature_importances_

print(dt.feature_importances_)

[0.12345626 0.86862934 0.0079144 ]

 

앞의 데이터와는 달리 만약 훈련 데이터들이 준비되어 있지 않고,

시간에 걸쳐 조금씩 추가가된다면 어떻게 학습을 시켜야할까?

 

기존에 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 훈련시킬 수는 없을까?

점진적 학습을 통해 이를 해결할 수 있다.

대표적인 점진적 학습법은 확률적 경사 하강법이라고 한다. (stochastic Gradient Descent)

 

확률적 경사 하강법에서 확률적이다 라는 말은 '무작위하게' 혹은 '랜덤하게'의 기술적인 표현이다.

경사 = 기울기

하강법 = 내려가는 방법

 

경사하강법은 기본적으로 가장 빠르게 내려가는 방법을 고수한다. (산에서 내려오는것을 생각)

하지만 만약 발걸음이 너무 크다면 경사를 따라 내려가는게 아니라 오히려 올라갈 수 도있다.

 경사하강법은 훈련 세트를 사용해 훈련할 때, 전체 샘플을 사용하지 않고, 딱 하나의 샘플을 훈련 세트에서 랜덤하게 골라 가장 가파른 길을 찾는다. 이처럼 훈련 세트에서 랜덤하게 하나의 샘플을 고르는것이 바로 률적 경사 하강법이라고 한다.

 < 훈련세트에서 랜덤하게 하나의 샘플을 택해 조금 내려간 후 다음 샘플 선택해서 내려가고, 다음,..다음...이런방식 >

 이렇게 모든 샘플을 사용할때 까지 반복한다.

 

하지만 만약 그래도 산을 다 내려오지 못한다면? 

다시 처음부터! 훈련세트에 모든 샘플을 다시 채워넣은 후 다시 랜덤하게 하나의 샘플을 선택해 이어서 경사를 내려간다.

이렇게 만족할 때 까지 계속 내려가면 된다.

확률적 경사 하강법에서 훈련 세트를 한 번 모두 사용하는 과정을 에포크라고 부른다.

일반적으로 경사하강법은 수십,수 백번의 에포크를 수행한다고 한다..

 

경사하강법은 무작위 샘플을 선택해서 산에서 내려가는건데 너무 무책임한거 아니야??라고 생각할 수 있다.

원래는 걱정하는것과 달리 이 알고리즘은 매우 정상적으로 작동하지만, 그래도 걱정이 된다면

1개씩 말고 몇 개씩 샘플을 선택해서 따라 내려갈 수 있다. 이를

미니배치 경사 하강법이라고 부른다. 이 방법또한 실전에서 많이 쓰인다.

 

그 밖에도 배치 경사 하강법이라는 방법도 있는데, 이는 한 번 경사를 따라 이동하는데에 모든 샘플을 사용한다.

이 방법이 가장 안전,정확하지만 그만큼 전체데이터를 사용하기 때문에 시간,자원이 많이 소모된다.

 

이 확률적 경사 하강법을 통해 매일매일 데이터가 업데이트 되어도 학습을 계속 이어나갈 수 있을것이다!

 

그런데 문제가 있다. 어디서 내려가야하는걸까? + 산은 도대체 무엇인걸까?? 이 산은 손실함수라고 부른다.

 

손실 함수 ( loss function )

손실함수란 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준이다.

그렇다면 손실함수의 값이 작을수록 좋다. 다행히도 우리가 다루는 많은 문제에 필요한 손실함수는 이미 정의가 되어있다.

 

이제 더 자세히 알아보도록 하자.

분류에서 손실은 확실하다. 못맞추는것 !

 

그럼 이를 어떻게 표현할 수 있지 않을까?

만약 4번의 예측 중에 두 번만 맞췄다면 정확도는 1/2 ( 0.5 ) 이다.

정확도를 손실함수로 사용한다면? 음수를 취해 -1이 가장 낮고, -0이 가장 높다. 이렇게 정확도를 손실함수로 사용할 수 있다.

하지만 정확도에는 치명적 단점이 있는데, 앞의 4개의 샘플만 있다면 가능한 정확도는 0, 0.25, 0.5 , 0.75, 1 다섯가지 뿐이다. 이는 경사하강법에서 연속적이지 않고 듬성듬성하게 내려오면 부적절할 수 있다.

 

그렇다면 손실함수를 연속적으로 만들려면 앞의 로지스틱과 연관지어보면된다.

 

로지스틱 손실 함수

우선 예시로 샘플 4개의 예측 확률을 각각 0.9, 0.3, 0.2, 0.8 이라고 가정해보자.

그리고 첫번째 두번째의 타겟은 1,   세 번째, 네 번째의 타겟은 0 이다. (불리안)

첫 번째 샘플의 예측은 0.9이므로 양성 클래스의 타깃인 1과 곱한다음 음수로 바꿀 수 있다. 이 경우 예측이 1에 가까울 수록 좋은 모델이라고 할 수 있다. 반대로 예측이 1에 가까울수록 음수는 점점 작아진다. 이 값을 손실함수로 사용해보자.

 

두 번째 샘플의 예측은 0.3이다. 거리가 멀다. 첫 번째 샘플처럼  - (예측 x 타겟)을 해보자. -0.3이다. 첫 번째보다 높은 손실.

 

세 번째 샘플에서 타겟 0을 곱하면 무조건 0이되기 때문에 문제가 생긴다.

★★★따라서 타겟을 양성클래스와 같이 1로 바꿔준다. 대신에 예측값도 양성클래스에 대한 예측으로 바꿔준다!!! ★★★

그렇다면 1-0.2 = - (0.8 x 1) = -0.8이다. 꽤 낮은 손실이다.

 

네 번째 샘플또한 세번째처럼 양성처럼 바꿔준다. -0.2이다. 높은 손실이다.

1,3번째 샘플 = 낮은 손실, 2,4번째 샘플  = 높은 손실

 

 

예측 확률을 사용해 이런 방식으로 계산한다면 연속적인 손실 함수를 얻을 수 있을 것 같다. 여기에서 예측 확률에 로그함수를 적용한다면 더욱 보기 좋을 수 있다.

로그함수의 특성 ( 0에 가까울 수록 큰 음수가 되어 손실을 크게 만들어줆.)

위와 같은  손실 함수를 로지스틱 손실 함수 또는 <이진or다중>크로스엔트로피 손실 함수라고 부른다.

 

이제 손실함수까지 알아봤으니, 경사 하강법을 이용한 분류 모델을 보자.

import pandas as pd
fish = pd.read_csv("https://bit.ly/fish_csv_data")

fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()

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

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

 

SGD Classifier

★SGD Classifier클래스는 사이킷런에서 확률적 경사 하강법을 제공하는 대표적인 분류용 클래스이다. !!!!★

from sklern.linear_model import SGDClassifier

SGDClassifier는 두개의 매개변수가 필요하다. loss= 와 max_iter= 이다.

loss=는 손실함수의 종류, max_iter = 은 에포크 횟수

여기서는 loss='log', max_iter = 10으로 해보자.

sc = SGDClassifier(loss='log', max_iter=10 , random_state=42)
sc.fit(train_scaled,train_target)

sc.score(train_scaled, train_target)
sc.score(test_scaled, test_target)

0.773109243697479
0.775

0.7로 정확도가 그리 높지 않은것을 볼 수 있다.

아마도 에포크가 부족한 것일 수도 있다.

 

SGDClassifier의 장점?특성에 따라 다시 만들 필요없이 추가 훈련을 해보자.

모델 추가 훈련할 때에는 partial_fit() 메소드를 사용하면 된다.

 

이 메소드는 fit()과 같지만 호출할 때마다 에포크를 1씩 이어서 훈련할 수 있다는 특징이 있다.

partial_fit() 메소드를 호출하고 다시 훈련세트와 테스트세트의 점수를 확인해보자.

 

sc.partial_fit(train_scaled, train_target)
sc.score(train_scaled, train_target)
sc.score(test_scaled, test_target)

0.8151260504201681
0.85

오 더 올라감. 그런데 얼마나 올려야할까????? 기준이 필요하다.

 

에포크의 과대/과소 적합

확률적 경사 하강법을 사용한 모델은 에포크 횟수에 따라 과소적합/과대적합이 될 수 있다.

에포크 횟수가 적으면 모델이 훈련 세트를 덜 학습한다. 마치 산을 다 내려오지 못하고 훈련이 끝난 상황.

에포크 횟수가 많으면 훈련 세트를 완전히 학습할 것이다.

바꿔 말하면 적은 에포크 횟수 동안 훈련한 모델은 훈련/테스트 세트에 잘 맞지 않는 과소적합된 모델일 것이고,  많은 에포크 횟수 동안 훈련한 모델은 훈련세트에 잘맞아 테스트세트에는 점수가 나쁜 과대적합된 모델일 가능성이 높다.

이를 그림으로 표현하면

 

이 그래프는 에포크가 진행됨에 따라 모델의 정확도를 나타낸 것이다. 훈련 세트 점수는 에포크가 진행될수록 꾸준히 증가하지만 테스트 세트 점수는 어느 순간 감소하기 시작한다. 이 지점이 바로 모델이 과대적합되기 시작하는 지점이다.

과대적합이 시작하기전에 훈련을 멈추는것을 조기종료라고 한다.

 

우리가 가진 데이터으로 위와 같은 그래프를 만들어보자.

이제 fit()이 아닌 partial_fit()만 사용할건데, 이를 위해서는 훈련세트에 있는 전체 클래스의 레이블을 partial_fit()메소드에 전달해줘야 한다. 이를 위해 np.unique()함수로 train_target 에 있는 7개 생선 목록을 만들고, 에포크마다 훈련세트와 테스트 세트의 점수를 보기 위해 2개의 리스트를 준비해보자.

 

import numpy as np
sc = SGDClassifier(loss='log', random_state= 42)
train_score = []
test_score = []
classes = np.unique(train_target)		# unique() = 오름차순으로 고유값 정렬시킴(중복제거)

300번의 에포크를 해보자.

for _ in range(0,300):
	sc.partial_fit(train_scaled, train_target, classes=classes)
    train_score.append(sc.score(train_scaled,train_target))
    train_score.append(sc.score(test_scaled,test_target))

이제 그래프를 그려보자!

import matplotlib.pyplot as plt
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

약 백 번째 에포크 이후에는 훈련 세트의 테스트 세트가 점점 벌어진다!!

 

이제 반복 횟수를 100에 맞춰볼까?

sc = SGDClassifier(loss='log', max_iter = 100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled,train_target))
print(sc.score(test_scaled,test_target))

0.957983193277311
0.925

점수가 매우 좋다!!!

SGDClassifier는 일정 에포크 동안 성능이 향상되지 않으면 더 훈련하지 않고 자동으로 멈춘다.

tol 매개변수에서 향상될 최솟값을 지정한다. 앞의 코드에서는 tol 매개변수를 None으로 지정하여 자동으로 멈추지 않고 max_iter = 100만큼 무조건 반복하도록 하였다.

 

판다스를 이용하여 csv파일을 읽어보자!

import pandas as pd
fish = pd.read_csv("https://bit.ly/fish_csv_data")
fish.head()

species에는 어떠한 종류의 생선들이 있는지 확인하기 위해 unique() 함수를 사용해보자.

pd.unique(fish["Species"])

도미,빙어를 비롯해 총 7가지 종류의 생선이 있다.

이제 Species 열을 타겟으로 만들고 나머지 5개 열을 입력 데이터로 사용해보자.

fish_input = fish[["Weight","Length","Diagonal","Height","Width"]].to_numpy()

잘 입력되었는지 처음 5개 행을 출력해보자!

print(fish_input[:5])

array([[242.    ,  25.4   ,  30.    ,  11.52  ,   4.02  ],
       [290.    ,  26.3   ,  31.2   ,  12.48  ,   4.3056],
       [340.    ,  26.5   ,  31.1   ,  12.3778,   4.6961],
       [363.    ,  29.    ,  33.5   ,  12.73  ,   4.4555],
       [430.    ,  29.    ,  34.    ,  12.444 ,   5.134 ]])

이제 타겟 데이터를 준비하자. ( 종류 ) 

fish_target = fish["Species"].to_numpy()

이제 훈련,테스트 세트로 나눠보자.

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

 

이제 데이터 전처리 (표준화)를 하고 훈련시켜보자 !

from sklearn.preprocessing import StandardScaler
ss = StandardSclaer()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

K-최근접 이웃 분류기로 확률 예측

from sklearn.neighbors import KNeighborsClassifier
Kn = KNeighborsClassifier()
Kn.fit(train_scaled,train_target)
print(Kn.score(train_scaled,train_target))
print(kn.score(test_scaled,test_target))

0.8907563025210085
0.85

이제 타겟값을 어떻게 예측하는지 알아보자.

print(kn.classes_)

array(['Bream', 'Parkki', 'Perch', 'Pike', 'Roach', 'Smelt', 'Whitefish'],
      dtype=object)

'Bream'이 첫 번째 클라스, 'Parkki'가 두 번째 클라스.. 방식 predict()메소드를 이용해서 타깃값으로 예측을 출력해보자.

Kn.predict(test_scaled[:5])

array(['Perch', 'Smelt', 'Pike', 'Perch', 'Perch'], dtype=object)

predit_proba()메소드클래스별 확률값을 반환한다.

import numpy as np
proba = Kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4)     #np.round() = 반올림 , decimals = n >>소수 n번째 자리까지 표시

해석 => 첫 번째 열 : 'Bream'일 확률, 두 번째 열 : 'Parkki'일 확률 ,,,,,,,이다.

확률을 봤으니 직접 샘플의 최근접 이웃 (3개)가 무엇인지 살펴보자.

distances, indexes = Kn.kneighbors(test_scaled[3:4])
print(train_scaled[indexes])

[['Roach' 'Perch' 'Perch']]

1/3 = 0.33333333      2/3 = 0.66666667 

기준 샘플 수가 3개이다 보니 확률이 0, 0.3, 0.6, 1 네 개의 숫자 밖에 나오지 않는다...  더 좋은 방법은 없을까?

 

로지스틱 회귀

로지스틱 회귀 (logistic regression)는 이름은 회귀이지만 분류 모델이다.

또한 로지스틱 회귀는 선형 회귀와 마찬가지로 선형 방정식을 학습한다.

 

변수 a,b,c,d,e는 가중치/계수.

z의 값은 0~1로 나오면 확률로 표현할 수 있다. 이를 위해 시그모이드 함수를 사용하면 된다!

import numpy as np
import matplot.pyplot as plt
z = np.arange( -5, 5 , 0.1)
phi = 1 / ( 1 + np.exp(-z))
plt.plot(z , phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()

정말 0 ~ 1로 출력된다!

 

# 로지스틱 회귀로 이진 분류 수행해보자

이진 분류이므로 불리언 인덱싱을 통해 해결 ( T / F)

char_arr = np.array(['A','B','C','D','E'])
print(char_arr[[True,False,True,False,False]])

['A' 'C']

위와 같은 방식을 통해 훈련 세트에서 도미,빙어의 행만 골라 내자

bream_smelt_indexes = (train_target == "Bream" ) | (train_target == "Smelt")
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_scaled[bream_smelt_indexes]

# | = or 과 같음

위 코딩 => 도미,빙어에는 True값이 들어가있고 나머지는 모두 False가 들어가 있다.

따라서 이 배열을 사용해 train_scaled와 train_target 배열에 불리언 인덱싱을 적용하면

손쉽게 도미와 빙어 데이터만 골라낼 수 있을것 같다.

 

이제 준비된 데이터로 로지스틱 회귀 모델을 훈련해보자.

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)

print(lr.predict(train_bream_smelt[:5]))

['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']

두 번째 샘플을 빼고 모두 도미로 예측했다.! 

KNeighborsClassifier()과 마찬가지로 예측확률은 preidct_proba() 메소드에서 제공한다.

lr.predict_proba(train_bream_smelt[:5])

array([[0.99759855, 0.00240145],
       [0.02735183, 0.97264817],
       [0.99486072, 0.00513928],
       [0.98584202, 0.01415798],
       [0.99767269, 0.00232731]])

샘플마다 두개의 확률이 출력되었다. !! 첫 번째 열 = 음성 클래스(0), 두 번째 열 = 양성 클래스(1)

무엇이 0, 1인지는 classes_ 를 통해 알아보자.

lr.classes_

array(['Bream', 'Smelt'], dtype=object)

빙어가 양성이다 ! 

 

이제 계수까지 확인해보자.

print( lr.coef_, lr.intercept_ )

[[-0.4037798  -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]

이 계수를 통해 보면 방정식은 다음과 같다.

위 방정식에서 z값을 구해보고싶다!!

= decision_function()메소드로 출력할 수 있다.

lr.decision_function(train_bream_smelt[:5])

array([-6.02927744,  3.57123907, -5.26568906, -4.24321775, -6.0607117 ])

이 z값을 시그모이드 함수에 통과시키면 확률을 얻을 수 있다!!

파이썬의 사이파이 라이브러리를 통해 시그모이드함수(expit()) == np.exp()함수를 이용할 수 있다.

 

from scipy.special import expit
expit(decision)

[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]

predict_proba()메소드를 사용했을 때의 두 번째 열과 동일하다. 

즉, decision_function() 메소드는 양성 클래스(빙어)에 대한 z값을 반환함을 알 수 있다.

 

이제 다중 분류 문제로 넘어가보자.

 

로지스틱 회귀로 다중 분류 수행하기

 

lr = LogisticRegression(C=20, max_iter = 1000)
lr.fit(train_scaled, train_target)

lr.score(train_scaled, train_target)
lr.score(test_scaled, test_target)

0.9327731092436975
0.925

처음 5개 샘플에 대한 예측을 출력해보자.

lr.predict(test_scaled[:5])

array(['Perch', 'Smelt', 'Pike', 'Roach', 'Perch'], dtype=object)

이번에는 테스트 세트의 처음 5개 샘플에 대한 예측확률을 출력해보자!

proba = lr.predict_proba(test_scaled[:5])
np.round(proba, decimals = 3)

array([[0.   , 0.014, 0.841, 0.   , 0.136, 0.007, 0.003],
       [0.   , 0.003, 0.044, 0.   , 0.007, 0.946, 0.   ],
       [0.   , 0.   , 0.034, 0.935, 0.015, 0.016, 0.   ],
       [0.011, 0.034, 0.306, 0.007, 0.567, 0.   , 0.076],
       [0.   , 0.   , 0.904, 0.002, 0.089, 0.002, 0.001]])

5개 샘플에 대한 예측이므로 5개의 행이 출력, 7개의 생선에 대한 확률을 계산했으므로 7개의 열이 출력되었다.

 

첫 번째 샘플을 보면  세 번째 열이 가장 높다. (Perch:농어)

lr.classes_

array(['Bream', 'Parkki', 'Perch', 'Pike', 'Roach', 'Smelt', 'Whitefish'],
      dtype=object)

3번째 열이 농어임을 확인!!!

 

 

이제 다중 분류일 경우의 선형방정식을 살펴보자.

소프트맥스함수

시그모이드함수는 하나의 선형방정식의 출력값을 0~1로 반환하는 반면,

소프트맥스함수는 여러개의 방정식의 출력값을 0~1로 압축하고 전체합이 1이 되게한다.

소프트맥스함수를 정규화된 지수함수라고 부르기도 한다.

 

 

먼저 decision_function()메소드를 통해 z1 ~ z7까지의 값을 구하고,

소프트맥스함수를 이용해 확률로 바꿔보자.

decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))

[[ -6.5    1.03   5.16  -2.73   3.34   0.33  -0.63]
 [-10.86   1.93   4.77  -2.4    2.98   7.84  -4.26]
 [ -4.34  -6.23   3.17   6.49   2.36   2.42  -3.87]
 [ -0.68   0.45   2.65  -1.19   3.26  -5.75   1.26]
 [ -6.4   -1.99   5.82  -0.11   3.5   -0.11  -0.71]]

 

from scipy.special import softmax
proba = softmax(decision, axis = 1)
print(np.round(proba, decimals = 3))

[[0.    0.014 0.841 0.    0.136 0.007 0.003]
 [0.    0.003 0.044 0.    0.007 0.946 0.   ]
 [0.    0.    0.034 0.935 0.015 0.016 0.   ]
 [0.011 0.034 0.306 0.007 0.567 0.    0.076]
 [0.    0.    0.904 0.002 0.089 0.002 0.001]]

앞서 구한 decision()배열을 softmax함수에 전달했다.

axis= 의 매개변수는 소프트맥스를 계산할 축을 지정한다.

여기서는 각 행(샘플)에 대해 소프트맥스를 계산한다. !

회귀에 의해 무게를 예측하는 알고리즘을 사용해, 50cm의 농어를 이용해 무게를 예측해보자.

 

데이터를 준비

perch_length = np.array([8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0, 21.0,
       21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5, 22.5, 22.7,
       23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5, 27.3, 27.5, 27.5,
       27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0, 36.5, 36.0, 37.0, 37.0,
       39.0, 39.0, 39.0, 40.0, 40.0, 40.0, 40.0, 42.0, 43.0, 43.0, 43.5,
       44.0])
perch_weight = np.array([5.9, 32.0, 40.0, 51.5, 70.0, 100.0, 78.0, 80.0, 85.0, 85.0, 110.0,
       115.0, 125.0, 130.0, 120.0, 120.0, 130.0, 135.0, 110.0, 130.0,
       150.0, 145.0, 150.0, 170.0, 225.0, 145.0, 188.0, 180.0, 197.0,
       218.0, 300.0, 260.0, 265.0, 250.0, 250.0, 300.0, 320.0, 514.0,
       556.0, 840.0, 685.0, 700.0, 700.0, 690.0, 900.0, 650.0, 820.0,
       850.0, 900.0, 1015.0, 820.0, 1100.0, 1000.0, 1100.0, 1000.0,
       1000.0])

훈련세트, 테스트세트로 나누고 2차원배열로 바꾸기

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

train_input = train_input.reshape(-1,1)
test_input = test_input.reshape(-1,1)

k-최근접 이웃 알고리즘 사용 ( 기준 샘플수 = 3 )

from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor(n_neighbors = 3)
knr.fit(train_input,train_target)

예측 실행

knr.predict([[50]])

1010.

길이가 압도적으로 길지만 무게는 크지않다. 

그림으로 그려보자...

distances, indexes = knr.kneighbors([[50]])


plt.scatter(train_input,train_target)
plt.scatter(50,1010,marker='^')
plt.scatter(train_input[indexes],train_target[indexes], marker='D')
plt.show()

k-최근접 이웃 모델의 한계점 (평균)

이렇게 되면 농어가 100cm , 150cm, 1km여도 여전히 1033.333으로 수렴한다.

이 방법을 해결하기 위해선 예측할 데이터의 범위가 주어진 데이터 내의 값 사이에 있어야 한다.

 

다른 방법, 다른 알고리즘은 없을까? 

 

선형 회귀

선형 회귀 ( linear regression )는 널리 사용되는 대표적인 회귀 알고리즘이다. 간단하고 성능이 뛰어남.

이름과 같이 특성이 하나인 경우 어떤 직선을 학습하는 알고리즘이다.!

 

 

from sklearn.linear_model import LinearRegression
lr = LinearRegression()

lr.fit(train_input, train_target)
print(lr.predict([[50]]))

[1241.83860323]

확실히 k-최근접 이웃 알고리즘보다 비교적 정확한 값을 도출해낸다.

 

보다 깊게 들여다보자.

 

선형 회귀는 y = ax + b 처럼  쓸 수 있다.

 x = 길이

 y = 무게

a(기울기) 와 b(y절편)은 lr 객체의 coef_ 와 intercept_속성에 저장되어 있다.

print( lr.coef_)
print( lr.intercept_ )

[39.01714496]
-709.0186449535477

농어의 길이 15에서 50까지 직선으로 그려보자 (기울기와 절편을 이용하여)

# hint( 15,y1 ) 과 (50,y2)의 값을 직선으로 긋기

plt.scatter(train_input,train_train_target) 	                                   # 데이터
plt.plot([15,50], [15 * lr.coef_ + lr.intercept_ , 50 * lr.coef_ + lr.intercept_]) # 직선
plt.scatter( 50 , 1241.8,marker='^')                                                  # 예측데이터

R^2 또한 확인해보자!

lr.score(train_input,train_target)
lr.score(test_input,test_target)

0.939846333997604
0.8247503123313558

과대적합vs과소적합?  훈련 > 테스트이므로 과대적합되었다고 볼 수 있다.

+ 하나의 문제점 

위의 그래프 상 x가 양수(작은 수이라 할 지라도) 임에도 무게가 음수가 나올 수 있다.

직선이라서 문제가 되는것 같은데 곡선으로 만들어야 할 것 같다.

 

다항 회귀

곡선 = 2차 방정식 => 길이를 제곱한 항이 훈련 세트에 추가되어야 한다. (넘파이를 이용)

다음처럼 농어의 길이를 제곱해서 원래 데이터 앞에 붙여보자.

 

# hint: column_stack() 함수 이용

train_poly = np.column_stack((train_input ** 2 , train_input))
test_poly = np.column_stack((test_input**2 ,test_input))

데이터셋 확인해보자. (브로드캐스팅 되었나 확인)

print(train_poly.shape, test_poly.shape)
(42, 2) (14, 2)

원래 특성인 길이를 제곱하여 왼쪽 열에 추가했기 때문에 훈련 세트와 테스트 세트 모두 열이 2개로 늘어났습니다.

 

이제 train_poly를 사용해 선형 회귀 모델을 다시 훈련해보자!

이제 무언가를 고르는게 아닌 무게를 예측해야 한다면 어떻게 해야할까?

'회귀' (Regression)를 통해 해결 할 수 있다.

 

지도학습 알고리즘은 크게 분류와 회귀로 나뉜다. 2장에서 한것이 분류이다.

회귀는 분류와 반대로 임의의 숫자를 예측하는 역할을 한다.

 

 

# k-최근접 이웃 회귀

k-최근접 이웃 회귀란 

앞에서 배운 k 최근접 이웃과 같이 기준 샘플과 비교한 후 샘플들의 평균값을 출력하는 알고리즘이다.

 

앞의 도미,빙어 데이터를 통해서 우선 산점도를 그려보자.

perch_length = np.array([8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0, 21.0,
       21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5, 22.5, 22.7,
       23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5, 27.3, 27.5, 27.5,
       27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0, 36.5, 36.0, 37.0, 37.0,
       39.0, 39.0, 39.0, 40.0, 40.0, 40.0, 40.0, 42.0, 43.0, 43.0, 43.5,
       44.0])
perch_weight = np.array([5.9, 32.0, 40.0, 51.5, 70.0, 100.0, 78.0, 80.0, 85.0, 85.0, 110.0,
       115.0, 125.0, 130.0, 120.0, 120.0, 130.0, 135.0, 110.0, 130.0,
       150.0, 145.0, 150.0, 170.0, 225.0, 145.0, 188.0, 180.0, 197.0,
       218.0, 300.0, 260.0, 265.0, 250.0, 250.0, 300.0, 320.0, 514.0,
       556.0, 840.0, 685.0, 700.0, 700.0, 690.0, 900.0, 650.0, 820.0,
       850.0, 900.0, 1015.0, 820.0, 1100.0, 1000.0, 1100.0, 1000.0,
       1000.0])

길이와 무게가 비례하는 모습 !

from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = 
train_test_split(perch_length, perch_weight, random_state=42) --> 훈련 세트 42개 뽑기

앞에서 보았던 train_test_split() 메소드를 사용해 랜덤으로 훈련/테스트세트의  입력/정답 데이터를 나누어 준다. (총 4개)

 

          # 위에서 perch_length와 perch_weight는 1차원 np배열이므로 2차원으로 바꿔줘야한다! 

 

train_input = train_input.reshape(-1,1)			# reshape(-1 = 나머지 원소 개수로 모두 채우라는 뜻)
test_input = test_input.reshape(-1,1)
print(train_input.shape, test_input.shape)
(42,1) (14,1)

# reshape()의 '-1'이 의미하는 바는, 변경된 배열의 '-1' 위치의 차원은 "원래 배열의 길이와 남은 차원으로 부터 추정"

나머지 원소 개수로 모두 채우라는 의미 ! (총 원소 개수 모르거나 쓰기 귀찮을 때 사용하면 유용)

 

 # 결정계수(R^2)

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor()
knr.fit(train_input, train_target)
print(knr.score(test_input, test_target))

0.99289

결정계수 공식 

예측이 타겟에 가까워지면 1에 가까워지고 / 타깃의 평균 정도를 예측하는 수준이면 0에 가까워진다.

 

 

# 과대적합 VS 과소적합

훈련 세트에서 점수가 좋았는데 테스트 세트에서는 점수가 나쁘다면 모델이 훈련 세트에 과대적합 되었다고 말한다.

반대로 훈련 세트보다 테스트 세트의 점수가 높거나 두 점수가 모두 너무 낮은 경우는 과소적합 되었다고 말한다.

 

if > 과소적합일 경우,

모델을 더 복잡하게 만들어야 한다.

k-최근접 이웃 알고리즘의 경우, 기준 샘플 수를 줄이는 방법이 있다. 

(샘플 많아질수록 일반적인 패턴을 가지기 때문)

'머신러닝 & 딥러닝 기초 > 3장 - 선형 회귀' 카테고리의 다른 글

3 - 2 선형회귀  (0) 2023.01.02

2 - 2 데이터 전처리


앞의 모델에서, height = 25 , weight = 150의 데이터를 입력하면 도미가 아닌 빙어라고 예측한다.

이 문제를 해결하기 전, 먼저 데이터를 더 학습해보자.

 

데이터 다시 준비

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.columns_stack(([1,2,3],[4,5,6]))

array([[1,4],
       [2,5],
       [3,6]])

np.column_stack() = 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결해준다. [columns=열]

쉽게 말해, 리스트 안의 index끼리 차례대로 묶어 2개씩 리스트를 생성함. (이게 쉽게 말한거야?)

 

이 방법을 통해 fish data를 새로 만들어보자.

import numpy as np
fish_Data = np.columns_stack((fish_length,fish_weight))

# 잘 되었는지 확인하기 위해 5개의 표본만 살펴보자.
fish_Data[:5]

[[25.4 , 242], [26.3 , 290], [26.5 , 340], [29 , 340], [29 , 430]

ㅇㅋ 잘 되었다.

 

학교때 배운 numpy의 다양한 메서드들을 이용해 타겟도 만들어보자.

np.concatenate() = 배열 합치기

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

print(fish_target)
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,,0,0,0,0,0,0,0,0,0,0,0]

 

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

앞의 인덱스 섞는 방법(random.shuffle) 보다 쉽게 쓸 수 있는 방법이 있다.

사이킷런은 머신러닝을 포함한 다양한 유틸리티 도구를 제공한다.

 

from sklearn.model_selection import train_test_split

tarin_set , test_set, train_target, test_target = train_test_split(fish_Data, fish_target, random_state=42)

train_test_split() : 매개변수의 리스트나 배열을 비율에 맞게 훈련세트와 테스트 세트로 나누어 준다 !

위에서는 2개의 배열을 전달했으므로 총 4개의 배열이 반환된다.

첫 번째 배열의 훈련,테스트 세트,

두 번째 배열의 훈련,테스트 세트

 

train_test_split(input, target, 랜덤시드)

 

# 복습 : [ 데이터 = 입력 = input  ] + [ 정답 = target ] = 훈련 데이터

 

이제 맨 위에서 도미 분류 오류를 살펴보자! 

from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_set, train_target)
kn.score(test_set, test_target)
1.0
print(kn.predict([[25, 150]])

0.0  # 도미= 1, 빙어 = 0

코드만 보고 문제는 해결되지 않으니 그림이라도 그려보자.

import matplotlib.pyplot as plt
plt.scatter(train_set[:,0],train_set[:,1])
plt.scatter(test_set[:,0], test_set[:,1])
plt.xlable("length")
plt.ylabel("weight")
plt.show()

직관적으로 그림만 보면 도미데이터와 가까워 보인다. 하지만 컴퓨터는 빙어로 인식한다. 왜일까? (x,y축 비율때문이다.)

우선 그 전에 Kneighborsclassifier()의 5가지 기준샘플들을 보기좋게 표시해보자.

distances , indexes = kn.kneighbors([[25,150]]) # 25,150의 k최근접이웃 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.show()

5개의 기준샘플 중 다수(3개)는 빙어였다 ! 

직접 거리를 출력해보자.

print(distances)

array([[ 92.00086956, 130.48375378, 138.32150953, 140.00603558,
        140.62090883]])

92,130,138,140 이상하다. 기준을 맞춰보자! 

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.show()

x축과 y축의 비율을 같게 했더니 일직선으로 나온다!! 이렇게 보면 직관적으로 봐도 k최근접 알고리즘과 유사하게 도출할 수 있다.

 

★★★이렇게 데이터를 일정한 기준으로 맞춰주는 것을 데이터 전처리라고 한다.★★★

가장 널리 사용하는 전처리 방법 중 하나느 표준점수이다.

표준점수 = 각 특성값이 평균에서 표준편차의 몇 배 만큼 떨어져 있는지를 나타낸다.

표준점수 = 각 값에서 평균을 빼고 표준편차로 나누어 주면 된다. [ 넘파이를 통해 도출 ]

mean = np.mean(train_input, axis = 0)   # axis = 0, axis = 1 차이점?
std = np.std(train_input, axis = 1 )

axis = 0 : 행을 따라 각 열의 값들을 계산     ( 이게 맞다 !)             

axis = 1 : 열을 따라 각 행들의 값들을 계산 ( 행마다 값이 나옴 !)             (책 그림 p.100참조)

print(mean,std)

[ 26.175      418.08888889] [ 10.21073441 321.67847023]

표준점수 구하기 !!!! 값-평균/표편

train_scaled = (train_input - mean) / std   # 자동으로 해줌 = 브로드캐스팅

다시 산점도를 그려보자!

new = ([25,150]-mean/std)
plt.scatter(train_scaled[:,0],train_scaled[:,1])
plt.scatter(new[0],new[1],marker='^')
plt.show()

x,y축의 값이 -1.5~1.5로 바뀜!

 

다시 훈련시켜보자.

from sklearn.kneighbors import KNeighborsClassifier()
kn = KNeighborsClassifier()
kn.fit(train_scaled,train_target)

테스트 세트 또한 훈련 세트와 마찬가지로 변환해야함!!

test_scaled = (test_input-mean) / std
kn.score(test_scaled,test_target)

1.0

 

다시 문제의 그 데이터를 변환시켜 예측시켜보자.

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

1. (도미 = 1)

정답 !

그림으로 그려볼까?

distances , indexes = kn.kneighbors([new])
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.show()

5개의 기준 데이터 모두 도미로 성공!

 

2-1 훈련세트와 테스트세트


머신러닝 알고리즘은 크게 지도 학습비지도 학습으로 나뉜다.

 

일반적으로 훈련데이터를 입력 / 정답데이터을 타겟 이라고 한다.

지도학습 :훈련데이터와 정답이 필요 (도미/빙어)비지도 학습 : 타겟 없이 입력데이터만 사용

 

테스트 세트 = 평가에 사용하는 데이터
훈련 세트 = 훈련에 사용하는 데이터

 

1장의 도미,빙어 데이터를 2차원으로 변환

bream_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]
bream_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]
smelt_length = [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]
smelt_weight = [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]

fish_length = bream_length + smelt_length
fish_weight = bream_weight + smelt_weight

fish_data = [[l,w] for l,w in zip(fish_length,fish_weight)]
fish_target = [1] * 35 + [0] * 14

리스트 인덱싱, 슬라이싱을 통해서 훈련세트 / 테스트 세트 만들기

 

처음 35개를 훈련세트, 14개를 테스트 세트로 만들어보자.

train_set = fish_data[:35]
train_target = fish_target[:35]

test_set = fish_data[35:]
tset_target = fish_target[35:]

kn = kn.fit(train_data,train_target)
kn.score(test_data, test_target)
0.0

정확도가 0이 나온 이유는 샘플링 편향 때문이다.

 

위의 데이터는 섞이지 않고 차례대로 35개의 도미, 14개의 빙어 데이터로 이루어져 있으므로,

train_data에는 빙어 데이터가 하나도 들어있지 않기 때문에 좋은 모델이라고 볼 수 없다.

 

골고루 섞기 위해 numpy, random 라이브러리 사용해야한다.

import numpy as np
input_arr = np.array(fish_data)
target_arr = np.array(fish_target)

np.random.seed(42)
index = np.arrange(49)
np.random.shuffle(index)​

위의 코드에서 index를 print하게 되면

[ 13, 23, 9, 31, 19 , 5,,,,,,20 ] 처럼 1~49의 랜덤한 숫자 리스트가 출력된다. 

 

np.random.seed()는 난수를 고정시키는 용도로 이해 (일정한 결과를 얻기 위해 사용)

 

이렇게 순서를 섞은 뒤 다시 실행시키면

train_arr = np.array(fish_data)
train_target = np.array(fish_target)

tarin_set = train_arr[index[:35]]
train_target = train_target[index[:35]]

test_set = train_arr[index[35:]]
test_target = train_target[index[35:]]

준비가 다 되었으니 그래프를 그려봅시다!

import matplotlib.pyplot as plt
plt.scatter(train_set[:,0],train_set[:,1])
plt.scatter(test_set[:,0], test_set[:,1])
plt.xlabel("길이")
plt.ylabel("무게")
plt.show()

blue = 도미                                                              < 잘 섞인 모습이다 ! >

oran = 빙어

 

다시 훈련시켜보자 !

from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_set, train_target)     #훈련세트  = fit()
kn.score(test_set, test_target)		#테스트세트 = score()
1.0

< 성공적이다! >

 

print(kn.predict(test_set))  # kn.predict() ==> 새로운 데이터의 정답을 예측하는 메서드

array([0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1,0])

print(test_target)

array([0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1,0])

< 일치한다! >

위 코드는 테스트 세트의 예측 결과와 실제 정답을 확인하는 코드

 

 

 

 

'머신러닝 & 딥러닝 기초 > 2장 - 데이터다루기' 카테고리의 다른 글

2-2 데이터 전처리  (0) 2022.12.30

도미 / 빙어 데이터를 이용해 어떠한 생선인지 분류할 수 있는 프로그램 만들기

bream_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]
bream_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]​

위 데이터를 넣고 맷플롯을 이용해 그래프를 그려보면 다음과 같이 선형그래프로 나온다.

import matplotlib.pyplot as plt
plt.scatter(bream_length, bream_weight)
plt.show()

빙어 데이터를 추가하고 그래프를 그려보자.

smelt_length = [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]
smelt_weight = [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]

plt.scatter(bream_length, bream_weight)
plt.scatter(smelt_length,smelt_weight)
plt.show()

 # K-최근접 이웃 알고리즘 

= 어떤 데이터에 대한 답을 구할 때 주위의 다른 데이터를 보고 다수를 차지하는 것을 정답으로 사용하는 알고리즘.

사이킷런을 이용하므로 데이터를 2차원 리스트로 만들어야 한다.

 

==> zip() 함수 이용! + 리스트함축

length = bream_length + smelt_length
weight = bream_weight + smelt_weight

fish_data = [[l,w] for l,w, in zip(length,weight)]

 

도미 = 1, 빙어 = 0 으로 표현한다면 정답리스트는 1이 35개, 그 뒤로 0이 14개가 나온다.

fish_target = [ 1 ] * 35 + [ 0 ] * 14

사이킷런 패키지에서 KNeighborsClassifier 임포트

from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
혹은
import sklearn
kn = sklearn.neighbors.KNeighborsClassifier()

kn.fit(fish_data, fish_target)
kn.score(fish_data , fish_target)

KNeighborsClassifier.fit() ==> 알고리즘 훈련하는 메서드

KNeighborsClassifier.score() ==> 사이킷런에서 모델을 평가하는 메서드

fit, score 모두 매개변수는 ( 데이터, 정답) 이다.

 

 

 # 참고 데이터 개수

KNeighborsClassifier 클래스의 기본값은 5개의 데이터를 참고로한다.

n_neighbors = n 를 통해 참고할 데이터 개수 설정 가능

 

만약 위의 데이터 모두를 참고한다면 ? (도미 35개, 빙어 14개)

kn = KNeighborsClassifier(n_neighbors=49)  #참고 데이터를 49로 한 모델

 

==> 다수의 데이터가 도미이므로 어떤데이터를 넣어도 무조건 도미로 예측할 것이다.

 

==> 정말 그런지 살펴보자.

kn49= KNeighborsClassifier(n_neighbors=49)
kn49.fit(fish_data, fish_score)
kn.49.score(fish_data, fish_score)

0.714285714285143
print(35/49)

0.7142857142857143

전체49 中 도미가 35개이므로 기대값과 일치한다.

 

 

+ Recent posts