728x90

* 앙상블 학습 : 더 좋은 예측 결과를 만들기 위해 여러 개의 모델을 훈련하는 러신머닝 알고리즘

   - 정형 데이터를 다루는 데 가장 뛰어난 성과를 내고 있다. 대부분 결정 트리 기반

* 랜덤 포레스트 : 대표적인 결정 트리 기반 앙상블. bootstrap sample을 사용하고 랜덤하게 일부 특성을 선택하여 트리를 만드는 것이 특징

* 엑스트라 트리 : bootstrap sample을 만들지 않고 훈련 데이터를 사용. 대신 랜덤하게 노드를 분할해 과대적합을 감소

* 그레이디언트 부스팅 : 결정트리를 연속적으로 추가하여 손실 함수를 최소화. 훈련 속도가 느리지만 더 나은 성능을 기대할 수 있다. 이의 속도를 개선한 것이 히스토그램 기반 그레이디언트 부스팅이며 안정적인 결과와 높은 성능으로 인기

 

정형데이터 : 일정한 구조로 되어 있는 데이터 -> 앙상블 학습

비정형데이터 : 사진, 음악, 문학 등 일정하게 처리하기 어려운 데이터 -> 신경망 알고리즘

 

Random Forest

결정 트리를 랜덤하게 만들어 결정트리의 숲을 만든다.

bootstrap sample : train data에서 random으로 추출하여 sample을 만든다. 이 때 size는 train data와 같고, 중복 추출을 허용한다. -> train data가 1000개면 중복 추출 허용 방식으로 1000개를 뽑아 bootstrap sample을 만든다.

사이킷런의 random forest는 기본적으로 100개의 결정 트리를 훈련한 다음 각 트리의 클래스별 확률을 평균하여 가장 높은 확률을 가진 클래스를 예측으로 삼는다. (회귀일 때는 단순히 각 트리의 예측을 평균한다고 한다)

Extra Trees

random forest와 매우 비슷하게 동작. 기본 100개의 결정 트리를 훈련. parameter도 동일.

bootstrap sample 대신 전체 훈련 세트를 사용. 대신 노드를 분할할 때 랜덤으로 분할.(splitter='random')

성능이 떨어질 수 있으나 과대적합을 막는데 효과가 있다.

Gradient boosting

깊이 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식.

사이킷런의 GradientBoostingClassifier는 기본 깊이 3인 결정 트리 100개 사용.

깊이가 얕기 때문에 과대적합에 강함. 높은 일반화 성능 기대.

4장에서 다루었던 하강법을 사용하여 트리를 앙상블에 추가. 분류에서는 로지스틱 손실 함수를 사용하고 회귀에서는 평균 제곱 오차 함수 사용.

Histogram-based Gradient Boosting

입력 특성을 256개의 구간으로 나눈다. -> 노드를 분할할 때 최적의 분할을 빠르게 찾음

256개의 구간 중 하나를 떼어 놓고 누락된 값을 위해 사용한다. -> 누락된 특성이 있어도 이를 전처리 할 필요 없음

HistGradientBoostingClassifier는 트리 개수를 정하는데 n_estimators대신 부스팅 반복 횟수를 지정하는 max_iter를 사용. max_iter를 조정하여 성능을 조율.

 

사이킷런 외의 library

* XGBoost

* LightGBM

 

랜덤포레스트

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

wine = pd.read_csv('https://bit.ly/wine_csv_data')

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

train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)

from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(rf, train_input, train_target, return_train_score=True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))

0.9973541965122431 0.8905151032797809

return_train_score의 기본값은 false : train socre를 같이 확인하면 과대적합을 파악하는데 용이.

훈련세트에 과대적합 되어 있다.

 

rf.fit(train_input, train_target)
print(rf.feature_importances_)

[0.23167441 0.50039841 0.26792718]

feature는 알코올 도수, 당도, pH순

5-1 결정트리에서는 아래와 같았다.

[0.12345626 0.86862934 0.0079144 ]
출처: https://bluelimn.tistory.com/entry/혼공머신-05-1-결정트리 [ANMIAN]

 

당도의 중요도가 내려가고 pH의 중요도가 높아졌다.

rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)

rf.fit(train_input, train_target)
print(rf.oob_score_)

0.8934000384837406

자체 알고리즘을 평가하는 기능이 있다. oob_score=True로 해주어야 한다.

엑스트라트리

from sklearn.ensemble import ExtraTreesClassifier

et = ExtraTreesClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(et, train_input, train_target, return_train_score=True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))

0.9974503966084433 0.8887848893166506

et.fit(train_input, train_target)
print(et.feature_importances_)

[0.20183568 0.52242907 0.27573525]

그레이디언트 부스팅

from sklearn.ensemble import GradientBoostingClassifier

gb = GradientBoostingClassifier(random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))

0.8881086892152563 0.8720430147331015

gb = GradientBoostingClassifier(n_estimators=500, learning_rate=0.2, random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))

0.9464595437171814 0.8780082549788999

결정트리의 개수(default:100)를 500으로 늘려도 과대적합에 비교적 안전함

gb.fit(train_input, train_target)
print(gb.feature_importances_)

[0.15872278 0.68010884 0.16116839]

히스토그램 기반 부스팅

# 사이킷런 1.0 버전 아래에서는 다음 라인의 주석을 해제하고 실행하세요.
# from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier

hgb = HistGradientBoostingClassifier(random_state=42)
scores = cross_validate(hgb, train_input, train_target, return_train_score=True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))

0.9321723946453317 0.8801241948619236

from sklearn.inspection import permutation_importance

hgb.fit(train_input, train_target)
result = permutation_importance(hgb, train_input, train_target, n_repeats=10,
                                random_state=42, n_jobs=-1)
print(result.importances_mean)

[0.08876275 0.23438522 0.08027708]

result = permutation_importance(hgb, test_input, test_target, n_repeats=10,
                                random_state=42, n_jobs=-1)
print(result.importances_mean)

[0.05969231 0.20238462 0.049 ]

hgb.score(test_input, test_target)

0.8723076923076923

XGBoost

from xgboost import XGBClassifier

xgb = XGBClassifier(tree_method='hist', random_state=42)
scores = cross_validate(xgb, train_input, train_target, return_train_score=True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))

0.8824322471423747 0.8726214185237284

LightGBM

from lightgbm import LGBMClassifier

lgb = LGBMClassifier(random_state=42)
scores = cross_validate(lgb, train_input, train_target, return_train_score=True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))

0.9338079582727165 0.8789710890649293

 

결정트리
[0.12345626 0.86862934 0.0079144 ]
Random Forest
[0.23167441 0.50039841 0.26792718]
Extra Trees
[0.20183568 0.52242907 0.27573525]
Gradient Boosting
[0.15872278 0.68010884 0.16116839]
Histogram-based Gradient Boosting
[0.08876275 0.23438522 0.08027708]

 

728x90
728x90

이번 장에서는 하이퍼파라미터를 머신 러닝을 이용하여 찾을 수 있다는 것이 핵심으로 보인다.

 

검증세트: 하이퍼파라미터 튜닝을 위한 모델 평가 시, test set을 이용하지 않기 위해 훈련세트에서 다시 떼어낸 data set

교차검증: 훈련세트를 여러개의 폴드로 나누고, 하나는 검증세트로 사용하고 나머지는 훈련세트로 사용한다. 교차검증은 이러한 방식으로 검증세트를 바꿔가며 모든 폴드에 대해 검증 점수를 얻어 평균하는 방법

그리드서치: 하이퍼파라미터 탐색을 자동으로 해주는 도구. 탐색할 parameter를 나열하면 교차 검증을 수행하여 가장 좋은 검증 점수의 매개변수 조합을 선택한다.

랜덤서치: 연속된 매개변수 값을 탐색할 때 유용. 탐색 값을 직접 나열하는 것이 아니고 탐색 값을 샘플링할 수 있는 확률 분포 객체를 전달 함. 지정된 횟수만큼 샘플링하여 교차 검증을 수행하기 때문에 시스템 자원이 허락하는 만큼 탐색량을 조절할 수 있다.

 

 

검증 세트

import pandas as pd

wine = pd.read_csv('https://bit.ly/wine_csv_data')
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)

sub_input, val_input, sub_target, val_target = train_test_split(
    train_input, train_target, test_size=0.2, random_state=42)

print(sub_input.shape, val_input.shape)

(4157, 3) (1040, 3)

from sklearn.tree import DecisionTreeClassifier

dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)

print(dt.score(sub_input, sub_target))
print(dt.score(val_input, val_target))

0.9971133028626413
0.864423076923077

교차 검증

from sklearn.model_selection import cross_validate

scores = cross_validate(dt, train_input, train_target)
print(scores)

{'fit_time': array([0.00867891, 0.00763416, 0.00746775, 0.00764298, 0.00717449]),
'score_time': array([0.00082707, 0.00078511, 0.00080347, 0.00081134, 0.00074959]),
'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}

import numpy as np

print(np.mean(scores['test_score']))

0.855300214703487

<<< 위와 아래는 같은 내용이다

from sklearn.model_selection import StratifiedKFold

scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores['test_score']))

0.855300214703487

splitter = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))

0.8574181117533719

하이퍼파라미터 튜닝

from sklearn.model_selection import GridSearchCV

params = {'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}

gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)

gs.fit(train_input, train_target)

GridSearchCV(estimator=DecisionTreeClassifier(random_state=42),
n_jobs=-1,
param_grid={'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]})

 

GridSearch는 상당히 많은 작업량 때문에 시간이 오래 걸린다. n_jobs은 작업에 사용할 CPU 개수이며 -1은 system 최대치
dt = gs.best_estimator_
print(dt.score(train_input, train_target))

0.9615162593804117

print(gs.best_params_)

{'min_impurity_decrease': 0.0001}

print(gs.cv_results_['mean_test_score'])

[0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]

best_index = np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])

{'min_impurity_decrease': 0.0001}

params = {'min_impurity_decrease': np.arange(0.0001, 0.001, 0.0001),
          'max_depth': range(5, 20, 1),
          'min_samples_split': range(2, 100, 10)
          }

gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)

GridSearchCV(estimator=DecisionTreeClassifier(random_state=42), n_jobs=-1, param_grid={'max_depth': range(5, 20), 'min_impurity_decrease': array([0.0001, 0.0002, 0.0003, 0.0004, 0.0005, 0.0006, 0.0007, 0.0008, 0.0009]), 'min_samples_split': range(2, 100, 10)})

 np.arange()는 parameter가 0.0001 ~ 0.001까지 0.0001씩 더한 배열 생성

range()도 동일하나 정수만 사용 가능

 

print(gs.best_params_)

{'max_depth': 14, 'min_impurity_decrease': 0.0004, 'min_samples_split': 12}

print(np.max(gs.cv_results_['mean_test_score']))

0.8683865773302731

랜덤 서치

from scipy.stats import uniform, randint
rgen = randint(0, 10)
rgen.rvs(10)
np.unique(rgen.rvs(1000), return_counts=True)

(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
array([102, 87, 91, 105, 122, 91, 109, 90, 87, 116]))

ugen = uniform(0, 1)
ugen.rvs(10)

array([0.09471503, 0.74711048, 0.81052059, 0.30578852, 0.37093519, 0.17989678, 0.96227617, 0.807383 , 0.69161244, 0.62003229])

params = {'min_impurity_decrease': uniform(0.0001, 0.001),
          'max_depth': randint(20, 50),
          'min_samples_split': randint(2, 25),
          'min_samples_leaf': randint(1, 25),
          }

from sklearn.model_selection import RandomizedSearchCV

gs = RandomizedSearchCV(DecisionTreeClassifier(random_state=42), params, 
                        n_iter=100, n_jobs=-1, random_state=42)
gs.fit(train_input, train_target)

RandomizedSearchCV(estimator=DecisionTreeClassifier(random_state=42), n_iter=100, n_jobs=-1, param_distributions={'max_depth': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7fdf61082d10>, 'min_impurity_decrease': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7fdf7000f5d0>, 'min_samples_leaf': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7fdf61082a10>, 'min_samples_split': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7fdf61082090>}, random_state=42)

print(gs.best_params_)

{'max_depth': 39, 'min_impurity_decrease': 0.00034102546602601173, 'min_samples_leaf': 7, 'min_samples_split': 13}

print(np.max(gs.cv_results_['mean_test_score']))

0.8695428296438884

dt = gs.best_estimator_

print(dt.score(test_input, test_target))

0.86

 

 

scikit-learn

* cross_validate() : 교차 검증 수행(default 5-폴드)

* GridSearchCV : 교차 검증으로 하이퍼파라미터 탐색을 수행

* RandomizedSearchCV : 교차 검증으로 랜덤한 하이퍼파라미터 탐색을 수행

728x90
728x90

결정트리: yes/no로 구분하여 트리로 답을 찾는 알고리즘, 이해하기 쉽고 성능도 뛰어남

불순도: 결정트리가 최적의 질문을 찾기 위한 기준. sklearn은 gini불순도와 엔트로피 불순도를 제공

정보이득: 부도 노드와 자식 노드의 불순도 차이. 결정 트리 알고리즘은 정보 이득이 최대화 되도록 학습

  - 결정트리는 제한 없이 성장하면 훈련세트에 과대적합되기 쉬움.

  - 가지치기는 결정트리의 성장을 제한하는 방법

특성 중요도: 결정트리에 사용된 특성이 불순도를 감소하는데 기여한 정도

 

결정 트리

로지스틱 회귀로 와인 분류하기

import pandas as pd

wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.head()

wine.info()

 

info() 함수는 누락된 내용이 있는지 파악하는데 용이함

총 6497 entries중 non-null이 모두 6497이므로 누락된 내용은 없음

누락된 값이 있다면 그 data를 버리거나 평균 값으로 채워 넣어야 한다.

 

wine.describe()

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))

0.7808350971714451

0.7776923076923077

훈련세트(0.78), 테스트세트(0.77)로 과소적합

 

print(lr.coef_, lr.intercept_)

[[ 0.51270274 1.6733911 -0.68767781]] [1.81777902]

 

결정 트리

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

로지스틱보다 나은 결과가 나왔지만 훈련세트가 0.99로 아주 높고 테스트세트가 0.85로 낮아 과대적합이다.

 

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree

plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()

depth가 너무 깊어 과대적합이 되었다. 이를 제한하여 어떻게 결정되었는지 알아보자

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

가지치기

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

max_depth를 5로 할 때 더 좋은 결과가 나왔는데 조건을 확인하기가 어려웠다

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

조건 식의 값들이 일반적으로 알기 어려운 숫자들이다. 이는 전처리를 거쳤기 때문으로 전처리 되지 않은 자료를 다시 넣고 결과를 확인해보자.

data에 대한 전처리가 필요하지 않다는 것이 결정 트리 알고리즘의 장점 중 하나

 

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

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

당도(sugar)가 4.325 이하, 1.625 이상이면서 alcohol이 11.025 이하면 red wine으로 인식하고 나머지는 white wine으로 인식

print(dt.feature_importances_)

[0.12345626 0.86862934 0.0079144 ]

 

728x90
728x90

확률적 경사 하강법, 손실 함수, 에포크

 

점진적 학습(온라인 학습)

 - 한번에 수많은 data를 모두 memory에 넣고 학습을 할 수 없으니 점진적으로 학습이 필요.

 - 또는 학습할 data가 시간이 지나면서 계속 추가되는 경우

 

확률적 경사 하강법 : Stochastic  Gradient Descent

 - 대표적인 점진적 학습 알고리즘

 - 훈련세트에서 하나씩 샘플을 꺼내 손실함수의 경사를 따라 최적의 모델을 찾는 알고리즘

미니배치 경사 하강법 : Minibatch Gradient Descent

 - 여러 개씩 꺼내 사용

배치 경사 하강법 : Batch Gradient Descent

 - 모두 꺼내 사용

점진적 학습 과정

손실함수: Loss Function, 비용 함수 : Cost Function

 - 손실 함수는 샘플 하나에 대한 손실을 정의, 비용 함수는 훈련 세트에 있는 모든 샘플에 대한 소실 함수의 합을 말하지만 엄격히 구분하지 않고 섞어 사용하는 경우가 많음

 - 확률적 경사 하강법이 최적화할 대상

 - 손실 함수는 오차가 얼마나 큰 지 측정하는 기준으로 함수의 값이 작을수록 좋다.

 - 손실 함수는 미분이 가능해야 한다

 

로지스틱 손실함수: Logistic Loss Function

이진 크로스엔트로비 손실 함수: Binary Cross-entropy Loss Function

크로스엔트로비 손실 함수: Cross-entropy Loss Function

 - 로지스틱 손실 함수를 사용하면 로지스틱 회귀 모델이 만들어진다.

 

회귀에서 사용하는 손실 함수:

평균 절댓값 오차

 - 타깃에서 예측을 뺀 절댓값을 모든 샘플에 평균한 값

평균 제곱 오차: Mean Squared Error

 - 타깃에서 예측을 뺀 값을 제곱한 다음 모든 샘플에 평균한 값

 

에포크

 - 확률적 경사 하강법에서 전체 샘플을 모두 사용하는 한 번 반복을 의미

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

 - 에포크가 너무 적으로면 과소, 많으면 과대 적합

 - 과대적합이 되기 전에 훈련을 멈추는 것이 조기종료(early stopping)

 

힌지 손실: hinge loss

 - SGDClassifier의 loss parameter의 기본 값, 서포트 백터 머신(Support vector machine) 알고리즘을 위한 손실 함수

 

SGDClassifier

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)

from sklearn.linear_model import SGDClassifier
sc = SGDClassifier(loss='log', max_iter=35, random_state=42)
sc.fit(train_scaled, train_target)

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

0.8403361344537815

0.8

sc.partial_fit(train_scaled, train_target)

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

0.907563025210084

0.925

에포크와 과대/과소적합

import numpy as np

sc = SGDClassifier(loss='log', random_state=42)

train_score = []
test_score = []

classes = np.unique(train_target)

# python에서 _ 변수는 다른 곳에서 사용하지는 않고 버리는 값을 저장하는데 쓴다
for _ in range(0, 300):
    sc.partial_fit(train_scaled, train_target, classes=classes)
    
    train_score.append(sc.score(train_scaled, train_target))
    test_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()

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

sc = SGDClassifier(loss='hinge', 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.9495798319327731

0.925

728x90

+ Recent posts