$$\phi(z) = \begin{cases} 1 & z \geq 0 \\ -1 & 그외 \end{cases}$$
- \(w_{0} = -\theta\)를 절편이라고 함
3. 퍼셉트론 학습 규칙
- 뇌의 뉴런 하나가 작동하는 방식을 흉내내는 환원주의 접근 방식 사용
가중치를 0 또는 랜덤한 작은 값으로 초기화
각 훈련 샘플 \(x^{(i)}\)에서 다음 작업 수행
출력 값 \(\hat{y}\) 계산 → 출력값은 계단 함수로 예측한 클래스 레이블
가중치 업데이트 →\(w_{j}:=w_{j}+\Delta w_{j}\) →가중치 업데이트 값인 \(\Delta w_{j}=\eta (y^{(i)}-\hat{y}^{(i)})x_{j}^{(i)}\) → \(\eta\)는 학습률로 0.0~0.1 사이의 값 → \(y^{(i)}\)와 \(\hat{y}^{(i)}\)는 각각 \(i\)번째 훈련 샘플의 정답 레이블, 예측 레이블
- 가중치 벡터의 모든 가중치는 동시에 업데이트
- 모든 가중치가 각자의 업데이트 값 \(\Delta w_{j}\)에 의해 업데이트되기 전에 예측 레이블 \(\hat{y}^{(i)}\)를 다시 계산하지 않음 → \(\Delta w_{0}=\eta(y^{(i)}-output^{(i)})\) → \(\Delta w_{1}=\eta(y^{(i)}-output^{(i)})x_{1}^{(i)}\) → \(\Delta w_{2}=\eta(y^{(i)}-output^{(i)})x_{2}^{(i)}\)
- 퍼셉트론이 클래스 레이블을 정확히 예측하면 가중치 유지(업데이트 값은 0) → \(y^{(i)}=-1, \hat{y}^{(i)}=-1, \Delta w_{j}=\eta(-1-(-1))x_{j}^{(i)}=0\) → \(y^{(i)}=1, \hat{y}^{(i)}=1,\Delta w_{j}=\eta(1-1)x_{j}^{(i)}=0\) - 퍼셉트론이 클래스 레이블을 잘못 예측하면 가중치를 양성 또는 음성 타겟 클래스 방향으로 이동시킴 → \(y^{(i)}=1, \hat{y}^{(i)}=-1, \Delta w_{j}=\eta(1-(-1))x_{j}^{(i)}=\eta(2)x_{j}^{(i)}\) → \(y^{(i)}=-1, \hat{y}^{(i)}=1, \Delta w_{j}=\eta(-1-1)x_{j}^{(i)}=\eta(-2)x_{j}^{(i)}\)
- 가중치 업데이트는 \(x_{j}^{(i)}\)값에 비례하여, 이 값을 조절해 결정경계를 더 크게 움직이거나 더 작게 움직일 수 있음
- 퍼셉트론은 두 클래스가 선형적으로 구분되고 학습률이 충분히 작을 때만 수렴이 보장됨
- 선형 결정 경계로 나눌 수 없다면 훈련 데이터셋을 반복할 최대 횟수(epoch)를 지정해 분류 허용 오차 지정, 그렇지 않으면 퍼셉트론은 가중치 업데이트를 멈추지 않음
4. 파이썬으로 퍼셉트론 학습 알고리즘 구현
- 퍼셉트론 클래스 정의
import numpy as np
class Perceptron(object):
def __init__(self, eta = 0.01, n_iter = 50, random_state = 1):
self.eta = eta
self.n_iter = n_iter
self.random_state = random_state
def fit(self, X, y):
rgen = np.random.RandomState(self.random_state) # 난수 생성기 객체 생성(격리된 시드 부여 가능, 이전과 동일한 랜덤 시드 재현 가능)
self.w_ = rgen.normal(loc = 0.0, scale = 0.01,
size = 1 + X.shape[1]) # 평균 0, 표준편차 0.01의 정규분포에서 훈련 데이터 개수(X.shape[1]) + 절편(1)만큼의 난수 생성
self.errors_ = [] # 훈련 중 error의 감소 확인을 위한 객체
for _ in range(self.n_iter):
errors = 0
for xi, target in zip(X, y):
update = self.eta * (target - self.predict(xi)) # 가중치 w_j의 변화량
self.w_[1:] += update * xi # 각 훈련 데이터에 가중치값 곱하기
self.w_[0] += update # 가중치값 업데이트
errors += int(update != 0.0) # 업데이트 된 값이 0이 아니면(실제값과 예측값이 다르면) errors에 1 더함
self.errors_.append(errors) # 전체 훈련 데이터에서 예측값이 실제값과 다르게 나온 횟수 추가
return self
# 최종입력인 Z 계산
def net_input(self, X):
return np.dot(X, self.w_[1:]) + self.w_[0]
# 결정함수를 임계값인 0과 비교, 크거나 같으면 1, 작으면 -1로 반환
def predict(self, X):
return np.where(self.net_input(X) >= 0, 1, -1)
- fit 메서드 내의 w_객체에 가중치를 초기화하여 선언 - 가중치가 0이 아니어야 학습률(eta)가 분류 결과에 영향을 줄 수 있어 0이 아닌 랜덤 값으로 초기화 - 가중치가 0이면 eta는 가중치 벡터의 방향이 아니라 크기에만 영향을 미침
- 붓꽃 데이터셋에서 퍼셉트론 훈련 - Perceptron은 이진분류이므로 setosa와 versicolor에 대해서만 분류 진행 - 또한, 특성은 첫번째 특성(꽃받침 길이)와 세번째 특성(꽃잎 길이)을 사용
import pandas as pd
import matplotlib.pyplot as plt
s = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
df = pd.read_csv(s, header = None, encoding = 'utf-8')
# 처음 100개의 데이터(setosa 50개와 versicolor 50개)의 다섯번째 열인 예측 대상을 y로 선언
y = df.iloc[0:100, 4].values
# setosa이면 -1, versicolor이면 1로 변경
y = np.where(y == 'Iris-setosa', -1, 1)
# 처음 100개의 데이터에서 예측에 사용하고자 하는 특성인 첫번재 열과 세번째 열만 추출
X = df.iloc[0:100, [0, 2]].values
# 산점도로 시각화
plt.scatter(X[:50, 0], X[:50, 1], color = 'red', marker = 'o', label = 'setose')
plt.scatter(X[50:100, 0], X[50:100, 1], color = 'blue', marker = 'x', label = 'versicolor')
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc = 'upper left')
plt.show()
# Perceptron 알고리즘 훈련
ppn = Perceptron(eta = 0.1, n_iter = 10)
ppn.fit(X, y)
# 훈련 과정에서 각 반복에서 나온 오차의 개수 시각화
plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker = 'o')
plt.xlabel('Epochs')
plt.ylabel('Number of updates')
plt.show()
from matplotlib.colors import ListedColormap
# 결정경계 시각화 함수
def plot_decision_regions(X, y, classifier, resolution = 0.02):
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])
# 두 특성의 최소값과 최대값에 각각 -1, +1을 한 값을 정의
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# 위에서 구한 최소값과 최대값을 시작으로 하는 meshgrid 생성(최소값과 최대값 사이에 0.02의 간격으로 데이터 생성)
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
np.arange(x2_min, x2_max, resolution))
# ravel 함수로 meshgrid를 1차원으로 평평하게 펴고, xx1, xx2를 각각 X, y로 Perceptron에 넣어 예측
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
# 예측된 값을 다시 meshgrid 형태로 변경
Z = Z.reshape(xx1.shape)
# contourf 함수로 등고선 그래프 그리기(등고선이 두 집단의 경계로 그려짐)
# 분류된 값이 -1인 것과 1인 것을 각각 빨간색과 파란색으로 구분
plt.contourf(xx1, xx2, Z, alpha = 0.3, cmap = cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
# 훈련 데이터를 빨간색 네모 모양와 파란색 x모양으로 구분
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x = X[y == cl, 0], y = X[y == cl, 1],
alpha = 0.8, c = colors[idx], marker = markers[idx], label = cl, edgecolor = 'black')
plot_decision_regions(X, y, classifier = ppn)
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc = 'upper left')
plt.show()
- OvR(One-versus-Resr)기법을 사용하면 이진 분류기로 다중 클래스 문제 해결 가능 →클래스마다 하나의 분류기를 훈련(각 클래스를 양성으로 분류하고 나머지는 음성으로 분류) →레이블이 없는 새로운 데이터 샘플 분류 시에는 클래스 레이블의 개수와 같은 n개의 분류기 사용 → 신뢰도가 가장 높은 클래스 레이블(최종 입력의 절댓값이 가장 큰 클래스)을 분류하려는 샘플의 레이블로 선택
- 퍼셉트론의 가장 큰 문제는 수렴 → 구 클래스가 선형적인 초평면으로 구분될 수 있을 때 수렴하지만, 선형 결정 경계로 완벽하게 구분되지 않으면 최대 epoch를 지정하지 않는한 가중치 업데이트가 멈추지 않음
● 예시로 '월간 데이콘 쇼츠 - 초전도체 임계온도 예측 AI 헤커톤'의 데이터 사용(train, test, submission 파일 3개 존재)
1. 모듈 임포트
import os
# Seed 고정
def seed_everything(seed):
random.seed(seed)
os.environ['PYTHONHASHSEED'] = str(seed)
np.random.seed(seed)
seed_everything(42)
# warning 무시
import warnings
warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.tree import DecisionTreeClassifier, DicisionTreeRegressor
from sklearn.linear_model import LogisticRegression, LinearRegression
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import accuracy_score, mean_squared_error, mean_absolute_error, r2_score
2. 데이터 불러오기
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
submission = pd.read_csv('sample_submission.csv')
3. 데이터 탐색
train.head() # 처음 5개의 데이터 (전체적인 데이터 구조 확인)
train.info() # 데이터에 대한 개략적인 정보 (행과 열 개수, non-null값 개수, 데이터 타입)
train.describe() # 숫자형 데이터 요약 (개수, 평균, 표준편차, 최소값, 25%, 50%, 75%, 최대값)
train.isna().sum() # 각 데이터의 결측값 개수
# 이외에도 이해가 가지 않는 행이나 열만 따로 뽑아서 탐색, 데이터 도메인 지식 등을 찾아보며 데이터에 대한 이해 필요
train = train.drop('ID', axis = 1) # train 데이터 탐색 결과 단순 식별자 변수인 ID는 필요 없는 것으로 판단, 삭제
feature = train.copy() # 밑에서 target변수를 제거하고 feature 변수만 남긴 데이터
target = feature.pop('critical_temp') # target 데이터 추출, train에는 feature 데이터만 남음
- train.head(): 처음 5개의 데이터 (전체적인 데이터 구조 확인)
- train.info(): 데이터에 대한 개략적인 정보 (행과 열 개수, non-null값 개수, 데이터 타입)
scaler = StandardScaler() # 박스플롯 출력 전 하나의 박스플롯에서 데이터를 한눈에 보기 위해 같은 범위로 임시 스케일링
plt.figure(figsize = (15, 10))
sns.boxplot(scaler.fit_transform(feature))
- 각 feature 데이터의 바이오린 플롯과 히스토그램 출력하여 데이터 분포 확인
for col in train.columns:
fig, axs = plt.subplots(nrows = 1, ncols = 2, figsize = (10, 4))
sns.violinplot(x = col, data = train, kde = True, ax = axs[0])
sns.histplot(x = col, data = train, kde = True, ax = axs[1])
# 방법 1. 결측값이 있는 행 제거
feature = feature.dropna(axis = 0, how = 'any') # 각 행에서 하나의 값이라도 NaN이면 해당 행 제거
feature = feature.dropna(axis = 0, how = 'all') # 각 행의 모든 값이 NaN이면 해당 행 제거
# 방법 2. 결측값 채우기(특정 값으로 채우기)
feature = feature.fillna(0) # 0으로 채우기
feature = feature.fillna(method = 'pad') # 바로 앞의 값으로 채우기
feature = feature.fillna(method = 'bfill') # 바로 뒤의 값으로 채우기
feature = feature.fillna(feature.mean()) # 해당 열의 평균값으로 채우기
# 방법 3. 결측값 채우기(보간법)
feature = feature.interpolate(method = 'time') # 인덱스가 날짜/시간일 때 날짜나 시간에 따라 보간
feature = feature.interpolate(method = 'nearest', limit_direction = 'forward') # 가장 가까운 값으로 보간
feature = feature.interpolate(method = 'linear', limit_direction = 'forward') # 양 끝값에 따라 선형식에 적용하여 보간
feature = feature.interpolate(method = 'polynomial', order = 2) # 양 끝값에 따라 다항식에 적용하여 보간 (order은 다항식 차수)
# 방법 4. 결측값이 있는 열을 해당 열의 결측값이 아닌 행과 다른 feature 변수들로 예측하여 채우기
feature_target = feature.pop('결측값 열') # 다른 행들로부터 결측값을 예측할 목표열 분리
target_index = feature_target[feature_target['결측값 열'] == np.nan].index # 목표열 중 결측값인 행의 인덱스 추출
feature_train = feature.iloc[~target_index] # 목표열이 아닌 다른 열에서 결측값이 있는 행의 인덱스가 아닌 행 추축(train 용)
feature_test = feature.iloc[target_index] # 목표열이 아닌 다른 열에서 결측값이 있는 행의 인덱스인 행 추출(test 용)
feature_target_train = feature_target[~target_index] # 목표열 중 결측값이 아닌 행 추출(train 용)
rf = RandomForestRegressor() # 랜덤포레스트로 예측
rf.fit(feature_train, feature_target_train) # 랜덤포레스트에 목표열 중 결측값이 아닌 행을 target 변수, 다른 열의 해당 행들을 feature 변수로 모델 피팅
pred = rf.predict(feature_test) # 피팅된 모델로 목표열에서 결측값인 행 예측
feature.iloc[target_index]['결측값 열'] = pred # 원래 데이터프레임에서 목표열의 결측값인 값을 위에서 예측한 값으로 대체
# 방법 1. 문자형 변수 라벨 인코딩
encoder = LabelEncoder()
feature[[feature.select_dtypes(object).columns]] = encoder.fit_transform(feature.select_dtypes(object))
test[[test.select_dtypes(object).columns]] = encoder.transform(test.select_dtypes(object))
# 방법 2. 문자형 변수 원핫 인코딩
encoder = OneHotEncoder()
feature[[feature.select_dtypes(object).columns]] = encoder.fit_transform(feature.select_dtypes(object).toarray())
test[[test.select_dtypes(object).columns]] = encoder.transform(test.select_dtypes(object).toarray())
- 라벨 인코딩은 문자형 변수 고윳값의 개수에 따라 0부터 n-1까지의 숫자로 변환되어 ML에서 왜곡된 결과를 불러올 수 있음
- 원핫 인코딩은 이런 문제를 해결하기 위해 고윳값의 개수만큼 새로운 feature 변수를 생성하여 각 행에서 고윳값에 해당하는 feature만 1, 나머지는 0으로 표시
9. 모델 피팅 및 예측
# feature 변수와 target 변수 분리
X = feature
y = target
# 학습용 데이터(X_train, y_train)와 검증용 데이터(X_test, y_test)로 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)
# 모델 선언(사용할 모델로 선정, 예시로는 RandomForestRegressor 선정)
model = RandomForestRegressor()
# 모델 학습
model.fit(X_train, y_train)
# 학습된 모델로 검증용 데이터 예측
y_pred = rf.predict(X_test)
# 실제 검증용 데이터와 예측한 검증용 데이터 비교하여 점수 출력
print(f'mse: {mean_squared_error(y_test, y_pred)}\nmae: {mean_absolute_error(y_test. y_pred)}\naccuracy: {accuracy_score(y_test, y_pred)}')
# 방법 2. cross_val_score
scores = cross_val_score(model, X, y, cv = 5)
for iter, accuracy in enumerate(scores):
print(f'교차 검증 {iter} 정확도: {accuracy:.4f}')
print(f'평균 정확도: {np.mean(scores):.4f}')
- KFold와 cross_val_score에서 각각 평균 정확도가 다른 이유는 cross_val_score가 StratifiedKFold를 이용해 폴드 세트를 분할하기 때문
# 방법 3. GridSearchCV
params = {'max_depth': [2, 3, 5, 10], 'min_samples_split': [2, 3, 5], 'min_samples_leaf': [1, 5, 8]} # 사용할 모델에 맞는 하이퍼 파라미터 중 탐색하고 싶은 범위 지정
grid_model = GridSearchCV(model, param_grid = params, scoring = 'accuracy', cv = 5, verbose = 3) # GridSearchCV에 model, 하이퍼 파라미터, 점수, 교차검증 횟수 등을 파라미터로 지정
grid_model.fit(X_train, y_train) # GridSearchCV 모델에 train 데이터 피팅
print(f'GridSearchCV 최적 하이퍼 파라미터: {grid_model.best_params_}') # 최적 하이퍼 파라미터
print(f'GridSearchCV 최고 정확도: {grid_model.best_score_:.4f}') # 최적 하이퍼 파라미터일 때 나온 최고 점수
best_model = grid_model.best_estimator_ # 최적 하이퍼 파라미터로 최고 점수가 나온 모델을 best_model로 저장
y_pred = best_model.predict(X_test) # best_model로 test 데이터 예측
accuracy = accuracy_score(y_test, y_pred) # 점수 계산
print(f'테스트 세트에서의 model 정확도: {accuracy:.4f}')
11. 기본 분석 이후
- pycaret, autogluon 등 auto ML을 사용해 최적의 모델 선택
- 데이터에 대한 깊은 이해를 통해 더 최적의 변수 선택 및 전처리, 차원 축소 및 군집화 등으로 파생변수 생성
# SVD 알고리즘을 통해 각각의 measure를 측정
model = SVD()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, verbose = True)
# 출력 결과
Evaluating RMSE, MAE of algorithm SVD on 5 split(s).
Fold 1 Fold 2 Fold 3 Fold 4 Fold 5 Mean Std
RMSE (testset) 0.9273 0.9320 0.9400 0.9451 0.9362 0.9361 0.0062
MAE (testset) 0.7333 0.7357 0.7387 0.7444 0.7372 0.7379 0.0037
Fit time 2.58 3.23 2.98 1.96 2.03 2.56 0.50
Test time 0.38 0.45 0.31 0.41 0.25 0.36 0.07
{'test_rmse': array([0.92730437, 0.93195753, 0.93997153, 0.94506308, 0.93619742]),
'test_mae': array([0.73328536, 0.73566774, 0.73869817, 0.74442193, 0.73723008]),
'fit_time': (2.5789830684661865,
3.2331647872924805,
2.975646495819092,
1.9638335704803467,
2.027461051940918),
'test_time': (0.3779747486114502,
0.4478921890258789,
0.3068206310272217,
0.40511560440063477,
0.24795103073120117)}
2. 컨텐츠 기반 필터링(Content-based Filtering)
컨텐츠 기반 필터링은 이전의 행동과 명시적 피드백을 통해 좋아하는 것과 유사한 항목을 추천
ex) 내가 지금까지 시청한 영화 목록과 다른 사용자의 시청 목록을 비교해 나와 비슷한 취향의 사용자가 시청한 영화를 추천
유사도를 기반으로 추천
컨텐츠 기반 필터링의 장단점
장점
많은 수의 사용자를 대상으로 쉽게 확장 가능
사용자가 관심을 갖지 않던 상품 추천 가능
단점
입력 틍성을 직접 설계해야 하기 때문에 많은 도메인 지식이 필요
사용자의 기존 관심사항을 기반으로만 추천 가능
import numpy as np
from surprise import Dataset
data = Dataset.load_builtin('ml-100k', prompt = False)
raw_data = np.array(data.raw_ratings, dtype = int)
# raw_data가 0부터 시작하도록 조절
raw_data[:, 0] -= 1
raw_data[:, 1] -= 1
n_users = np.max(raw_data[:, 0])
n_movies = np.max(raw_data[:, 1])
# 행: 총 유저수, 열: 총 영화수
shape = (n_users + 1, n_movies + 1)
shape
# 출력 결과
(943, 1682)
adj_matrix = np.ndarray(shape, dtype = int)
for user_id, movie_id, rating, time in raw_data:
# 1이 있는 위치가 현재 데이터가 있는 위치
adj_matrix[user_id][movie_id] = 1.
adj_matrix
# 출력 결과
array([[1, 1, 1, ..., 0, 0, 0],
[1, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[1, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 1, 0, ..., 0, 0, 0]])
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(adj_matrix):
# user_id와 my_id가 다르면 유사도(similarity) 계산
if my_id != user_id:
similarity = np.dot(my_vector, user_vector)
if similarity > best_match:
best_match = similarity
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# 출력 결과
Best Match: 183, Best Match ID: 275
recommend_list = []
for i, log in enumerate(zip(my_vector, best_match_vector)):
log1, log2 = log
# 내가 보지 않았지만(log1(my_vector)가 1보가 작음) 유사하게 나온 영화(log2(best_match_vector)가 0보다 큼)를 추천
if log1 < 1. and log2 > 0.:
recommend_list.append(i)
print(recommend_list)
# 출력 결과
[272, 273, 275, ..., 1480, 1481, 1482]
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = 9999, -1, []
for user_id, user_vector in enumerate(adj_matrix):
# user_id와 my_id가 다르면 유클리드 거리 계산
if my_id != user_id:
# 유클리드 거리 계산식
euclidian_dist = np.sqrt(np.sum(np.square(my_vector - user_vector)))
if euclidian_dist < best_match:
best_match = euclidian_dist
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# 출력 결과
Best Match: 14.832396974191326, Best Match ID: 737
recommend_list = []
for i, log in enumerate(zip(my_vector, best_match_vector)):
log1, log2 = log
recommend_list.append(i)
print(recommend_list)
# 출력 결과
[297, 312, 317, ..., 968, 1015, 1046]
# 코사인 유사도 계산식
def compute_cos_similarity(v1, v2):
norm1 = np.sqrt(np.sum(np.square(v1)))
norm2 = np.sqrt(np.sum(np.square(v2)))
dot = np.dot(v1, v2)
return dot / (norm1 * norm2)
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(adj_matrix):
# user_id와 my_id가 다르면 코사인 유사도 계산
if my_id != user_id:
cos_similarity = compute_cos_similarity(my_vector, user_vector)
if cos_similarity > best_match:
best_match = cos_similarity
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# 출력 결과
Best Match: 0.5278586163659506, Best Match ID: 915
recommend_list = []
for i, log in enumerate(zip(my_vector, best_match_vector)):
log1, log2 = log
# 내가 보지 않았지만(log1(my_vector)가 1보가 작음) 유사하게 나온 영화(log2(best_match_vector)가 0보다 큼)를 추천
if log1 < 1. and log2 > 0.:
recommend_list.append(i)
print(recommend_list)
# 출력 결과
[272, 275, 279, ..., 1427, 1596, 1681]
- 기존 방법에 명시적 피드백(사용자가 평가한 영화 점수)을 추가해 실험
adj_matrix = np.ndarray(shape, dtype = int)
for user_id, movie_id, rating, time in raw_data:
# 단순히 데이터가 존재하는지 안하는지에 따라 0, 1이 아니라 존재하는 곳에 rating값을 넣음
adj_matrix[user_id][movie_id] = rating
adj_matrix
# 출력 결과
array([[5, 3, 4, ..., 0, 0, 0],
[4, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[5, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 5, 0, ..., 0, 0, 0]])
# 유클리드 거리로 계산
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = 9999, -1, []
for user_id, user_vector in enumerate(adj_matrix):
# user_id와 my_id가 다르면 유클리드 거리 계산
if my_id != user_id:
# 유클리드 거리 계산식
euclidian_dist = np.sqrt(np.sum(np.square(my_vector - user_vector)))
if euclidian_dist < best_match:
best_match = euclidian_dist
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# 출력 결과
Best Match: 55.06359959174482, Best Match ID: 737
# 코사인 거리로 계산
my_id, my_vector = 0, adj_matrix[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(adj_matrix):
# user_id와 my_id가 다르면 코사인 유사도 계산
if my_id != user_id:
cos_similarity = compute_cos_similarity(my_vector, user_vector)
if cos_similarity > best_match:
best_match = cos_similarity
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# 출력 결과
Best Match: 0.569065731527988, Best Match ID: 915
3. 협업 필터링(Collaborative Filtering)
사용자와 항목의 유사성을 동시에 고려해 추천
기존에 내 관심사가 아닌 항목이라도 추천 가능
자동으로 임베딩 학습 가능
협업 필터링의 장단점
장점
자동으로 임베딩을 학습하기 때문에 도메인 지식이 필요없음
기존의 관심사가 아니더라도 추천 가능
단점
학습 과정에서 나오지 않은 항목은 임베딩을 만들 수 없음
추가 특성을 사용하기 어려움
from surprise import KNNBasic, SVD, SVDpp, NMF
from surprise import Dataset
from surprise.model_selection import cross_validate
data = Dataset.load_builtin('ml-100k', prompt = False)
- KNN을 사용한 협업 필터링
model = KNNBasic()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, n_jobs = 4, verbose = True)
# 출력 결과
Evaluating RMSE, MAE of algorithm KNNBasic on 5 split(s).
Fold 1 Fold 2 Fold 3 Fold 4 Fold 5 Mean Std
RMSE (testset) 0.9743 0.9842 0.9849 0.9823 0.9689 0.9789 0.0063
MAE (testset) 0.7689 0.7785 0.7786 0.7752 0.7643 0.7731 0.0056
Fit time 2.72 2.70 2.85 2.98 1.69 2.59 0.46
Test time 16.55 16.59 16.43 15.93 7.88 14.68 3.40
{'test_rmse': array([0.9742902 , 0.98423331, 0.98486835, 0.98230369, 0.96892197]),
'test_mae': array([0.76887025, 0.77850316, 0.77859337, 0.77524521, 0.76433142]),
'fit_time': (2.724426746368408,
2.6965529918670654,
2.8537588119506836,
2.977182388305664,
1.6942033767700195),
'test_time': (16.55093264579773,
16.586602687835693,
16.429330825805664,
15.92642879486084,
7.883346796035767)}
- SVD를 사용한 협업 필터링
model = SVD()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, n_jobs = 4, verbose = True)
# 출력 결과
Evaluating RMSE, MAE of algorithm SVD on 5 split(s).
Fold 1 Fold 2 Fold 3 Fold 4 Fold 5 Mean Std
RMSE (testset) 0.9364 0.9366 0.9345 0.9349 0.9355 0.9356 0.0008
MAE (testset) 0.7378 0.7368 0.7356 0.7365 0.7381 0.7370 0.0009
Fit time 3.69 3.54 4.03 4.02 4.02 3.86 0.20
Test time 1.18 1.36 1.51 1.55 0.45 1.21 0.40
{'test_rmse': array([0.93641775, 0.93660876, 0.934486 , 0.93493886, 0.93553188]),
'test_mae': array([0.73780807, 0.73676556, 0.73564193, 0.73649871, 0.73805738]),
'fit_time': (3.690873146057129,
3.541585922241211,
4.030731439590454,
4.015835523605347,
4.018853425979614),
'test_time': (1.1828830242156982,
1.3635668754577637,
1.5132882595062256,
1.5461750030517578,
0.4530982971191406)}
- NMF를 사용한 협업 필터링
model = NMF()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, n_jobs = 4, verbose = True)
# 출력 결과
Evaluating RMSE, MAE of algorithm NMF on 5 split(s).
Fold 1 Fold 2 Fold 3 Fold 4 Fold 5 Mean Std
RMSE (testset) 0.9603 0.9552 0.9631 0.9654 0.9632 0.9614 0.0035
MAE (testset) 0.7569 0.7511 0.7570 0.7578 0.7573 0.7560 0.0025
Fit time 4.99 5.05 4.98 4.89 2.28 4.44 1.08
Test time 0.62 0.64 0.53 0.46 0.22 0.49 0.15
{'test_rmse': array([0.96025745, 0.95521602, 0.96314659, 0.96535182, 0.96319395]),
'test_mae': array([0.75686291, 0.75106343, 0.75704307, 0.75776259, 0.75726996]),
'fit_time': (4.98656702041626,
5.046948671340942,
4.978093385696411,
4.887223243713379,
2.2761926651000977),
'test_time': (0.6195485591888428,
0.6439330577850342,
0.5302431583404541,
0.4606480598449707,
0.2169051170349121)}
- SVD++를 사용한 협업 필터링
model = SVDpp()
cross_validate(model, data, measures = ['rmse', 'mae'], cv = 5, n_jobs = 4, verbose = True)
# 출력 결과
Evaluating RMSE, MAE of algorithm SVDpp on 5 split(s).
Fold 1 Fold 2 Fold 3 Fold 4 Fold 5 Mean Std
RMSE (testset) 0.9180 0.9196 0.9126 0.9336 0.9101 0.9188 0.0082
MAE (testset) 0.7193 0.7196 0.7155 0.7324 0.7149 0.7204 0.0063
Fit time 64.45 65.24 65.04 65.49 25.32 57.11 15.90
Test time 9.41 9.28 8.90 9.10 4.87 8.31 1.73
{'test_rmse': array([0.91803553, 0.9196422 , 0.91259638, 0.9335687 , 0.91007489]),
'test_mae': array([0.71934988, 0.719645 , 0.71549358, 0.73242896, 0.71492586]),
'fit_time': (64.4547209739685,
65.24023914337158,
65.03504347801208,
65.49227690696716,
25.323737144470215),
'test_time': (9.414633989334106,
9.28382134437561,
8.901108026504517,
9.10077452659607,
4.8735032081604)}
4. 하이브리드(Hybrid)
컨텐츠 기반 필터링과 협업 필터링을 조합한 방식
많은 하이브리드 방식이 존재
실습에서는 협업 필터링으로 임베딩을 학습하고 컨텐츠 기반 필터링으로 유사도 기반 추천을 수행하는 추천 엔진 개발
U, S, V = randomized_svd(adj_matrix, n_components = 2)
S = np.diag(S)
print(U.shape)
print(S.shape)
print(V.shape)
# 출력 결과
(943, 2)
(2, 2)
(2, 1682)
# 분해 후 재조합
# (U * S) * V
np.matmul(np.matmul(U, S), V)
# 출력 결과
array([[ 2.75413997e+00, 1.40492906e+00, 7.42675835e-01, ...,
-8.18516364e-04, 1.56868753e-02, 1.47148277e-02],
[ 1.37986536e+00, 9.01276577e-02, 4.55027384e-01, ...,
1.09681871e-02, 4.52128919e-04, -1.05261346e-03],
[ 9.93032597e-01, 7.39687516e-03, 3.35229530e-01, ...,
8.95866035e-03, -3.68132502e-04, -1.54632039e-03],
...,
[ 6.28183642e-01, 6.62904209e-02, 2.03737923e-01, ...,
4.52498630e-03, 5.10681060e-04, -1.32467414e-04],
[ 9.59658161e-01, 4.03024146e-01, 2.70469585e-01, ...,
1.31860581e-03, 4.42188957e-03, 3.93973413e-03],
[ 1.80307089e+00, 1.01280345e+00, 4.73641731e-01, ...,
-2.26049256e-03, 1.13925590e-02, 1.09104420e-02]])
- 사용자 기반 추천
나와 비슷한 취향을 가진 다른 사용자의 행동을 추천
사용자 특징 벡터의 유사도 사용
my_id, my_vector = 0, U[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(U):
# user_id와 my_id가 다르면 코사인 유사도 계산
if my_id != user_id:
cos_similarity = compute_cos_similarity(my_vector, user_vector)
if cos_similarity > best_match:
best_match = cos_similarity
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# 출력 결과
# 무려 99% 유사
Best Match: 0.9999996213542565, Best Match ID: 639
recommend_list = []
for i, log in enumerate(zip(adj_matrix[my_id], adj_matrix[best_match_id])):
log1, log2 = log
# 내가 보지 않았지만(log1(my_vector)가 1보가 작음) 유사하게 나온 영화(log2(best_match_vector)가 0보다 큼)를 추천
if log1 < 1. and log2 > 0.:
recommend_list.append(i)
print(recommend_list)
# 출력 결과
[300, 301, 303, ..., 1227, 1243, 1257]
- 항목 기반 추천
내가 본 항목과 비슷한 항목을 추천
항목 특징 벡터의 유사도 사용
my_id, my_vector = 0, V.T[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(V.T):
# user_id와 my_id가 다르면 코사인 유사도 계산
if my_id != user_id:
cos_similarity = compute_cos_similarity(my_vector, user_vector)
if cos_similarity > best_match:
best_match = cos_similarity
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# 출력 결과
# 더 높은 유사도
Best Match: 0.9999999998965627, Best Match ID: 1425
recommend_list = []
for i, user_vector in enumerate(adj_matrix):
if adj_matrix[i][my_id] > 0.9:
recommend_list.append(i)
print(recommend_list)
# 출력 결과
[0, 1, 4, ..., 935, 937, 940]
my_id, my_vector = 0, U[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(U):
# user_id와 my_id가 다르면 코사인 유사도 계산
if my_id != user_id:
cos_similarity = compute_cos_similarity(my_vector, user_vector)
if cos_similarity > best_match:
best_match = cos_similarity
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# 출력 결과
Best Match: 0.9999996213542565, Best Match ID: 639
recommend_list = []
for i, log in enumerate(zip(adj_matrix[my_id], adj_matrix[best_match_id])):
log1, log2 = log
# 내가 보지 않았지만(log1(my_vector)가 1보가 작음) 유사하게 나온 영화(log2(best_match_vector)가 0보다 큼)를 추천
if log1 < 1. and log2 > 0.:
recommend_list.append(i)
print(recommend_list)
# 출력 결과
[300, 301, 303, ..., 1227, 1243, 1257]
항목 기반 추천
my_id, my_vector = 0, V.T[0]
best_match, best_match_id, best_match_vector = -1, -1, []
for user_id, user_vector in enumerate(U):
# user_id와 my_id가 다르면 코사인 유사도 계산
if my_id != user_id:
cos_similarity = compute_cos_similarity(my_vector, user_vector)
if cos_similarity > best_match:
best_match = cos_similarity
best_match_id = user_id
best_match_vector = user_vector
print('Best Match: {}, Best Match ID: {}'.format(best_match, best_match_id))
# 출력 결과
Best Match: 0.9999999998965627, Best Match ID: 1425
recommend_list = []
for i, user_vector in enumerate(adj_matrix):
if adj_matrix[i][my_id] > 0.9:
recommend_list.append(i)
print(recommend_list)
# 출력 결과
[0, 1, 4, ..., 935, 937, 940]
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris, fetch_olivetti_faces
from sklearn.decomposition import PCA, IncrementalPCA, KernelPCA, SparsePCA
from sklearn.decomposition import TruncatedSVD, DictionaryLearning, FactorAnalysis
from sklearn.decomposition import FastICA, NMF, LatentDirichletAllocation
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
iris, labels = load_iris(return_X_y = True)
faces, _ = fetch_olivetti_faces(return_X_y = True, shuffle = True)
iris 그래프 그리기 함수
def plot_iris(iris, labels):
plt.figure()
colors = ['navy', 'purple', 'red']
for xy, label in zip(iris, labels):
plt.scatter(xy[0], xy[1], color = colors[label])
faces 그리기 함수
def show_faces(faces):
plt.figure()
# 2*3의 배열로 표현
num_rows, num_cols = 2, 3
# 총 6개씩 뜨게 됨
for i in range(num_rows * num_cols):
plt.subplot(num_rows, num_cols, i+1)
plt.imshow(np.reshape(faces[i], (64, 64)), cmap = plt.cm.gray)
위의 함수 사용해서 그래프 그리기
plot_iris(iris[:, :2], labels)
show_faces(faces)
1. 주성분 분석(Principal Component Analysis, PCA)
PCA를 사용해 iris 데이터 변환
150×4 크기의 데이터를 150×2 크기의 행렬로 압축
# 기존 iris 데이터의 shape
iris.shape
# 출력 결과
(150, 4)
# PCA 변환 후 iris 데이터의 shape
model = PCA(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# PCA로 변환한 iris 데이터 시각화
plot_iris(transformed_iris, labels)
PCA를 통해 학습된 각 컴포넌트(6개)
각 컴포넌트는 얼굴의 주요 특징을 나타냄
# 기존 faces 데이터의 shape
faces.shape
# 출력 결과
(400, 4096)
# PCA 변환 후 faces 데이터의 shape
model = PCA(n_components = 6, random_state = 0)
model.fit(faces)
faces_components = model.components_
faces_components.shape
# 출력 결과
(6, 4096)
# PCA로 변환한 faces 데이터 시각화
show_faces(faces_components)
2. Incremental PCA
PCA는 SVD 알고리즘 실행을 위해 전체 학습용 데이터 셋을 메모리에 올려야 함
Incremental PCA는 학습 데이터를 미니 배치 단위로 나누어 사용
학습 데이터가 크거나 온라인으로 PCA 적용이 필요할 때 유용
model = IncrementalPCA(n_components = 2)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
model = IncrementalPCA(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape
# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)
3. Kernel PCA
비선형적인 형태가 Kernel로 표현될 수 있음
차원 축소를 위한 복잡한 비선형 투형
model = KernelPCA(n_components = 2, kernel = 'rbf', random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
model = KernelPCA(n_components = 6)
model.fit(faces)
# Kernel PCA는 components_를 출력할 수 없어 오류가 발생함
faces_components = model.components_
4. Sparse PCA
PCA의 주요 단점 중 하나는 주성분들이 보통 모든 입력 변수들의 선형 결합으로 나타난다는 점
희소 주성분 분석은 몇 개의 변수들만의 선형결합으로 주성분을 나타냄으로써 이러한 단점을 극복
model = SparsePCA(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
model = SparsePCA(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape
# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)
5. Truncated Singular Value Decomposition(Truncated SVD)
PCA는 정방 행렬에 대해서만 행렬 분해 가능
SVDs는 정방 행렬 뿐만 아니라 행과 열이 다른 행렬도 분해 가능
PCA는 밀집 행렬(Dense Matrix)에 대한 변환만 가능하지만, SVD는 희소 행렬(Sparse Matrix)에 대한 변환도 가능
전체 행렬 크기에 대해 Full SVD를 사용하는 경우는 적음
특이값이 0인 부분을 모두 제거하고 차원을 줄인 Truncated SVD를 주로 사용
model = TruncatedSVD(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
model = TruncatedSVD(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape
# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)
6. Dictionary Learning
Sparse code를 사용하여 데이터를 가장 잘 나타내는 사전 찾기
Sparse coding은 overcomplete 기저 벡터(basis vector)(기저보다 많은 수의 함수로 프레임 표현하는 )를 기반으로 데이터를 효율적으로 표현하기 위한 개발
기저 벡터는 벡터 공간에 속하는 벡터의 집합이 선형 독립이고, 다른 모든 벡터 공간의 벡터들이 그 벡터 집합의 선형 조합으로 나타남
이웃한 픽셀들의 가능한 모든 조합으로 이미지를 재정의
픽셀 정보를 넘어서 더 풍부한 표현력으로 이미지를 설명, 인식의 정확성 향상
model = DictionaryLearning(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
model = DictionaryLearning(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape
# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)
7. Factor Analysis
요인 분석은 변수들 간의 상관관계를 고려하여 저변에 내재된 개념인 요인들을 추출해내는 분석방법
요인 분석은 변수들 간의 상관관계를 고려하여 서로 유사한 변수들끼지 묶어주는 방법
PCA에선느 오차(error)를 고려하지 않고, 요인 분석에서는 오차(error)를 고려
model = FactorAnalysis(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
model = FactorAnalysis(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape
# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)
8. Independent Component Analysis(ICA)
독립 성분 분석은 다변량의 신호를 통계적으로 독립적인 하부 성분으로 분리하는 계산 방법
ICA는 주성분을 이용하는 점은 PCA와 유사하지만, 데이터를 가장 잘 설명하는 축을 찾는 PCA와 달리 가장 독립적인 축, 독립성이 최대가 되는 벡터를 찾음
model = FastICA(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
model = FastICA(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape
# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)
9. Non-negative Matrix Factorization
음수 미포함 행렬 분해는 음수를 포함하지 않은 행렬 V를 음수를 포함하지 않은 행렬 W와 H의 곱으로 분해하는 알고리즘
숫자 5를 분해하면 2+3, 1+4 등으로 분리 가능 -> 이것과 마찬가지로 행렬을 어떤 두 개의 행렬의 곱으로 분해
model = NMF(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
model = NMF(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape
# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)
10. Latent Dirichlet Allocation(LDA)
잠재 디리클레 할당은 이산 자료들에 대한 확률적 생성 모형
디리클레 분포에 따라 잠재적인 의미 구조를 파악
디리클레 분포(Dirichlet distribution)는연속 확률분포의 하나로, k차원의 실수 벡터 중 벡터의 요소가 양수이며 모든 요소를 더한 값이 1인 경우 (이를 k−1차원단체라고 한다)에 대해 확률값이 정의되는 분포이다.
디리클레 분포는베이즈 통계학에서다항 분포에 대한사전 켤레확률이다. 이 성질을 이용하기 위해, 디리클레 분포는 베이즈 통계학에서의사전 확률로 자주 사용된다.
model = LatentDirichletAllocation(n_components = 2, random_state = 0)
model.fit(iris)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
model = LatentDirichletAllocation(n_components = 6)
model.fit(faces)
faces_components = model.components_
faces_components.shape
# 출력 결과
(6, 4096)
# 시각화
show_faces(faces_components)
11. Linear Discriminant Analysis(LDA)
LDA는 PCA와 유사하게 입력 데이터 세트를 저차원 공간에 통해 차원을 축소
LDA는 지도 학습 분류에서 사용하기 쉽도록 개별 클래스를 분별할 수 있는 기준을 최대한 유지하면서 차원 축소
정답이 있는 지도학습에서 사용하기 때문에 faces 데이터에 대해서는 사용 불가능
model = LinearDiscriminantAnalysis(n_components = 2)
# 정답인 labels를 같이 모델에 넣어 학습시켜 줘야함
model.fit(iris, labels)
transformed_iris = model.transform(iris)
transformed_iris.shape
# 출력 결과
(150, 2)
# 시각화
plot_iris(transformed_iris, labels)
12. 압축된 표현을 사용한 학습
원래의 digits 데이터와 분해(decomposition)된 digits 데이터의 cross val score 비교
- digits 데이터 및 학습 모델 라이브러리 불러오기
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import cross_val_score
# min_max 스케일링 하는 함수
def min_max_scale(x):
min_value, max_value = np.min(x, 0), np.max(x, 0)
x = (x - min_value) / (max_value - min_value)
return x
# digits 데이터 그래프 그리는 함수
def plot_digits(digits, labels):
digits = min_max_scale(digits)
ax = plt.subplot(111, projection = '3d')
for i in range(digits.shape[0]):
ax.text(digits[i, 0], digits[i, 1], digits[i, 2],
str(labels[i]), color = plt.cm.Set1(labels[i] / 10.),
fontdict = {'weight': 'bold', 'size': 9})
ax.view_init(4, -72)
# digits 데이터 불러온 뒤 NMF로 분해
# 기존 데이터와 분해된 데이터 shape 비교
digits = load_digits()
nmf = NMF(n_components = 3)
nmf.fit(digits.data)
decomposed_digits = nmf.transform(digits.data)
print(digits.data.shape)
print(decomposed_digits.shape)
print(decomposed_digits)
# 출력 결과
(1797, 64)
(1797, 3)
[[0.48392621 0. 1.24523912]
[0.5829615 1.4676756 0.07150889]
[0.61515882 1.10963207 0.387782 ]
...
[0.55272665 1.26056519 0.72094739]
[0.7872562 0.2789873 1.04952028]
[0.78507412 0.67250884 0.92677982]]
# 분해 전
knn = KNeighborsClassifier()
score = cross_val_score(
estimator = knn,
X = digits.data, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.94722222 0.95555556 0.96657382 0.98050139 0.9637883 ]
mean cross val score: 0.9627282575054161 (+/- 0.011168537355954218)
# 분해 후
knn = KNeighborsClassifier()
score = cross_val_score(
estimator = knn,
X = decomposed_digits, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.54722222 0.58055556 0.64066852 0.59610028 0.56267409]
mean cross val score: 0.5854441349427422 (+/- 0.03214521445075084)
- SVC
# 분해 전
svm = SVC()
score = cross_val_score(
estimator = svm,
X = digits.data, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.96111111 0.94444444 0.98328691 0.98885794 0.93871866]
mean cross val score: 0.9632838130609718 (+/- 0.02008605863225686)
# 분해 후
svm = SVC()
score = cross_val_score(
estimator = svm,
X = decomposed_digits, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.61388889 0.62222222 0.66016713 0.60167131 0.59888579]
mean cross val score: 0.6193670690188796 (+/- 0.022070024720937543)
- Decision Tree
# 분해 전
decision_tree = DecisionTreeClassifier()
score = cross_val_score(
estimator = decision_tree,
X = digits.data, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.78333333 0.69722222 0.78830084 0.83286908 0.78830084]
mean cross val score: 0.7780052615289385 (+/- 0.04421837659784472)
# 분해 후
decision_tree = DecisionTreeClassifier()
score = cross_val_score(
estimator = decision_tree,
X = decomposed_digits, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.57222222 0.50833333 0.57938719 0.5821727 0.52924791]
mean cross val score: 0.5542726709996905 (+/- 0.0298931375955385)
- Random Forest
# 분해 전
random_forest = RandomForestClassifier()
score = cross_val_score(
estimator = random_forest,
X = digits.data, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.93888889 0.90555556 0.97214485 0.96657382 0.91086351]
mean cross val score: 0.9388053234292789 (+/- 0.02745509790638632)
# 분해 후
random_forest = RandomForestClassifier()
score = cross_val_score(
estimator = random_forest,
X = decomposed_digits, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.58888889 0.60277778 0.64066852 0.59052925 0.54874652]
mean cross val score: 0.594322191272052 (+/- 0.029463634023029848)
- 전반적으로 분해한 데이터에서 성능이 떨어진 모습
13. 복원된 표현을 사용한 학습
분해 후 복원된 행렬을 사용해 학습
- 데이터 행렬 복원
# 분해된 행렬에 곱하기 연산을 통해 원래의 행렬로 복원
components = nmf.components_
reconstructed_digits = decomposed_digits @ components
print(digits.data.shape)
print(decomposed_digits.shape)
print(reconstructed_digits.shape)
# 출력 결과
(1797, 64)
(1797, 3)
(1797, 64)
# reconstructed digits 시각화
plt.figure(figsize = (16, 8))
plt.suptitle('Re-Constructed digits')
for i in range(10):
plt.subplot(2, 5, i+1)
plt.xticks([])
plt.yticks([])
plt.imshow(reconstructed_digits[i].reshape(8, 8))
- KNN
knn = KNeighborsClassifier()
score = cross_val_score(
estimator = knn,
X = reconstructed_digits, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.54166667 0.59444444 0.66295265 0.57660167 0.57381616]
mean cross val score: 0.5898963169297431 (+/- 0.04029722337499952)
- SVM
svm = SVC()
score = cross_val_score(
estimator = svm,
X = reconstructed_digits, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.62777778 0.60555556 0.66016713 0.61002786 0.5821727 ]
mean cross val score: 0.6171402042711235 (+/- 0.025969174809053776)
- Decision Tree
decision_tree = DecisionTreeClassifier()
score = cross_val_score(
estimator = decision_tree,
X = reconstructed_digits, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.57777778 0.51666667 0.53481894 0.56824513 0.55153203]
mean cross val score: 0.5498081089445992 (+/- 0.0221279380012718)
- Random Forest
random_forest = RandomForestClassifier()
score = cross_val_score(
estimator = random_forest,
X = reconstructed_digits, y = digits.target,
cv = 5
)
print(score)
print('mean cross val score: {} (+/- {})'.format(score.mean(), score.std()))
# 출력 결과
[0.58055556 0.55833333 0.65181058 0.59610028 0.57660167]
mean cross val score: 0.592680284741566 (+/- 0.031916555892366645)
- 새로 복원한 데이터에서도 큰 성능 향상점은 없음
14. 이미지 복원
# faces 데이터를 train와 test 데이터로 분리
from sklearn.model_selection import train_test_split
train_faces, test_faces = train_test_split(faces, test_size = 0.1)
show_faces(train_faces)
show_faces(test_faces)
# 테스트 데이터에는 랜덤한 점에 0값을 주어 검은색의 점으로 노이즈를 생성
damaged_faces = []
for face in test_faces:
idx = np.random.choice(range(64 * 64), size = 1024)
damaged_face = face.copy()
damaged_face[idx] = 0.
damaged_faces.append(damaged_face)
show_faces(damaged_faces)
# 노이즈를 준 test 데이터를 NMF 모델에 넣어 분해하고 다시 복원하는 과정으로 노이즈 제거
# damaged_faces의 형식을 float32로 변환하는 과정 필요
damaged_faces = np.asarray(damaged_faces, dtype = np.float32)
matrix1 = nmf.transform(damaged_faces)
matrix2 = nmf.components_
show_faces(matrix1 @ matrix2)
# 분해 components를 조절하여 정교함 조절 가능, 더 높은 수로 분해할수록 더 정교하게 복원할 수 있음
nmf = NMF(n_components = 100)
nmf.fit(train_faces)
matrix1 = nmf.transform(damaged_faces)
matrix2 = nmf.components_
show_faces(matrix1 @ matrix2)
차원 축소 과정에서 중요하지 않은 정보는 버려지고 중요한 정보만 남기 대문에 데이터 정제에 활용 가능
데이터 생성 및 시각화 함수
# 필요 라이브러리
import numpy as np
import matplotlib.pyplot as plt
from sklearn import manifold
from sklearn import random_projection
from sklearn import datasets
# s_curve 데이터셋 생성
s_curve, color = datasets.make_s_curve(1000, random_state = 0)
# 손글씨 데이터셋 생성
digits, labels = datasets.load_digits(return_X_y = True)
# projection으로 만들기
rand_proj = random_projection.SparseRandomProjection(n_components = 3, random_state = 0)
projected_digits =rand_proj.fit_transform(digits)
# min_max_scale 하는 함수
def min_max_scale(x):
min_value, max_value = np.min(x, 0), np.max(x, 0)
x = (x - min_value) / (max_value - min_value)
return x
# s_curve 시각화 함수
def plot_s_curve(s_curve, color, position, projection):
s_curve = min_max_scale(s_curve)
if projection == '3d':
ax = plt.subplot(position, projection = projection)
ax.scatter(s_curve[:, 0], s_curve[:, 1], s_curve[:, 2], c = color, cmap = plt.cm.Spectral)
ax.view_init(4, -72)
elif projection == '2d':
ax = plt.subplot(position)
ax.scatter(s_curve[:, 0], s_curve[:, 1], c = color, cmap = plt.cm.Spectral)
# 손글씨 시각화 함수
def plot_digits(digits, labels, position, projection):
digits = min_max_scale(digits)
if projection == '3d':
ax = plt.subplot(position, projection = projection)
for i in range(digits.shape[0]):
ax.text(digits[i, 0], digits[i, 1], digits[i, 2], str(labels[i]),
color = plt.cm.Set1(labels[i] / 10.), fontdict = {'weight': 'bold', 'size': 9})
ax.view_init(4, -72)
elif projection == '2d':
ax = plt.subplot(position)
for i in range(digits.shape[0]):
ax.text(digits[i, 0], digits[i, 1], str(labels[i]),
color = plt.cm.Set1(labels[i] / 10.), fontdict = {'weight': 'bold', 'size': 9})
- 데이터 시각화
# 작성한 함수를 사용하여 s_curve와 손글씨 데이터를 각각 시각화(3차원으로)
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve, color, 121, '3d')
plot_digits(projected_digits, labels, 122, '3d')
# 작성한 함수를 사용하여 s_curve와 손글씨 데이터를 각각 시각화(2차원으로)
fig = plt.figure(figsize = (20, 10))
plot_s_curve(s_curve, color, 121, '2d')
plot_digits(projected_digits, labels, 122, '2d')
쿨백-라이블러 발산(Kullback–Leibler divergence,KLD)은 두 확률분포의 차이를 계산하는 데에 사용하는 함수로, 어떤 이상적인 분포에 대해, 그 분포를 근사하는 다른 분포를 사용해 샘플링을 한다면 발생할 수 있는정보 엔트로피차이를 계산, 상대 엔트로피(relative entropy), 정보 획득량(information gain), 인포메이션 다이버전스(information divergence)라고도 한다.
import numpy as np
import matplotlib.pyplot as plt
from sklearn import cluster
from sklearn import mixture
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
XGBoost는 GBM 기반이지만, GBM의 단점인 느린 수행 시간과 과적합 규제 부재 등의 문제를 해결
병렬 CPU 환경에서 빠르게 학습 가능
필요 라이브러리
from sklearn.datasets import load_iris, load_breast_cancer, load_wine, load_diabetes
from sklearn.model_selection import train_test_split, cross_validate
from sklearn.metrics import accuracy_score, precision_score, recall_score
import xgboost as xgb
from xgboost import XGBClassifier, XGBRegressor
from xgboost import plot_importance, plot_tree
import graphviz
import matplotlib.pyplot as plt
1. 파이썬 기반 XGBoost
- 유방암 데이터로 연습
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, test_size = 0.2, random_state = 42)
# XGBoost는 XGBoost만의 데이터 형식인 DMatrix 형식을 사용하므로 다음과 같이 train과 test 데이터를 변환
dtrain = xgb.DMatrix(data = X_train, label = y_train)
dtest = xgb.DMatrix(data = X_test, label = y_test)
# 파라미터
params = {
'max_depth': 3,
'eta': 0.1,
'objective': 'binary:logistic',
'eval_metric': 'logloss',
'early_stopping': 100
}
num_rounds = 400
# 모델 생성
evals = [(dtrain, 'train'), (dtest, 'eval')]
xgb_model = xgb.train(params = params, dtrain = dtrain, num_boost_round = num_rounds, early_stopping_rounds = 100, evals = evals)
- xgb_model 생성 결과, logloss가 더 이상 감소되지 않을 때까지 모델 최적화
- 계속 감소한다면 사전에 정해둔 round 횟수(num_rounds = 400)만큼만 진행한 뒤 모델 생성 종료
- 가장 마지막 모델로 생성
- train 데이터로 생성한 모델에 test 데이터를 넣어 예측값 출력(상위 10개만)
import numpy as np
predicts = xgb_model.predict(dtest)
print(np.round(predicts[:10], 3))
- 정확도, 정밀도, 재현율 출력
# 데이터 생성 결과를 이원화(0 또는 1로)하여 2*2 테이블 상에서 정확도, 정밀도, 재현율을 계산할 수 있도록 변경
preds = [1 if x > 0.5 else 0 for x in predicts]
print(preds[:10])
print("정확도: {}".format(accuracy_score(y_test, preds)))
print("정밀도: {}".format(precision_score(y_test, preds)))
print("재현율: {}".format(recall_score(y_test, preds)))
# 출력 결과
정확도: 0.9736842105263158
정밀도: 0.9722222222222222
재현율: 0.9859154929577465
일반화와 강건성(Robustness)을 향상시키기 위해 여러 모델의 예측 값을 결합하는 방법
앙상블의 종류
평균 방법
여러 추정값을 독립적으로 구한 뒤 평균을 취함
결합 추정값은 분산이 줄어들어 단일 추정값보다 좋은 성능
부스팅 방법
순차적으로 모델 생성
결합된 모델의 편향을 감소시키기 위해 노력
부스팅 방법의 목표는 여러 개의 약한 모델들을 결합해 하나의 강력한 앙상블 모델을 구축하는 것
1. Bagging meta-estimator
bagging은 bootstrap arregatng의 줄임말
원래 훈련 데이터셋의 일부를 사용해 여러 모델을 훈련
각각의 결과를 결합해 최종 결과를 생성
분산을 줄이고 과적합을 막음
강력하고 복잡한 모델에서 잘 동작
- 사용 라이브러리 및 연습용 데이터 불러오기
from sklearn.datasets import load_iris, load_wine, load_breast_cancer, load_diabetes
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import cross_validate
from sklearn.ensemble import BaggingClassifier, BaggingRegressor
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.svm import SVC, SVR
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
iris = load_iris()
wine = load_wine()
cancer = load_breast_cancer()
diabetes = load_diabetes()
- KNeighborsClassifier에서 일반 모델과 bagging 모델 비교
# 기본 모델 파이프라인 생성
base_model = make_pipeline(
StandardScaler(),
KNeighborsClassifier()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)
# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = base_model,
X = wine.data, y = wine.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.0015646457672119141 (+/- 0.00046651220506644384)
avg score time: 0.0024621009826660155 (+/- 0.0007703127175682573)
avg test score: 0.9493650793650794 (+/- 0.037910929811115976)
# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = bagging_model,
X = wine.data, y = wine.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.01585836410522461 (+/- 0.001614824165009782)
avg score time: 0.004801464080810547 (+/- 0.0011524532393675253)
avg test score: 0.9496825396825397 (+/- 0.02695890511274157)
- 일반 모델 파이프라인과 bagging 모델 분류기 간의 avg test score의 차이가 없음
- SVC에서 일반 모델과 bagging 모델 비교
# 기본 모델 파이프라인 생성
base_model = make_pipeline(
StandardScaler(),
SVC()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)
# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = base_model,
X = wine.data, y = wine.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.0019928932189941405 (+/- 0.0006299217280194511)
avg score time: 0.0007997512817382813 (+/- 0.0003998954564704184)
avg test score: 0.9833333333333334 (+/- 0.022222222222222233)
# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = bagging_model,
X = wine.data, y = wine.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.03082098960876465 (+/- 0.010328177445939042)
avg score time: 0.003788471221923828 (+/- 0.0007431931234207308)
avg test score: 0.9495238095238095 (+/- 0.03680897280161461)
- bagging 모델 분류기에서 avg test score가 일반 모델보다 더 감소함
- DecisionTreeClassifier에서 일반 모델과 bagging 모델 비교
# 기본 모델 파이프라인 생성
base_model = make_pipeline(
StandardScaler(),
DecisionTreeClassifier()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)
# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = base_model,
X = wine.data, y = wine.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.0016411781311035157 (+/- 0.0005283725076633593)
avg score time: 0.0005584716796875 (+/- 0.00046257629871031806)
avg test score: 0.8709523809523809 (+/- 0.04130828490281938)
# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = bagging_model,
X = wine.data, y = wine.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.025203371047973634 (+/- 0.006259195949988035)
avg score time: 0.0019852161407470704 (+/- 0.0006092217277319406)
avg test score: 0.9665079365079364 (+/- 0.032368500562618134)
- bagging 모델 분류기에서 avg test score가 일반 모델보다 더 증가함
- KNeighborsRegressor에서 일반 모델과 bagging 모델 비교
# 기본 모델 파이프라인 생성
base_model = make_pipeline(
StandardScaler(),
KNeighborsRegressor()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)
# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = base_model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))# 출력 결과
# 출력 결과
avg fit time: 0.0011986732482910157 (+/- 0.0003976469573187139)
avg score time: 0.0011948585510253907 (+/- 0.0004032221568088893)
avg test score: 0.3689720650295623 (+/- 0.044659049060165365)
# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = bagging_model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))# 출력 결과
# 출력 결과
avg fit time: 0.01416783332824707 (+/- 0.0021502472754025685)
avg score time: 0.005353689193725586 (+/- 0.0005163540544834088)
avg test score: 0.4116223973880059 (+/- 0.039771045284647706)
- bagging 모델 분류기에서 avg test score가 일반 모델보다 더 증가함
- SVR에서 일반 모델과 bagging 모델 비교
# 기본 모델 파이프라인 생성
base_model = make_pipeline(
StandardScaler(),
SVR()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)
# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = base_model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.005189418792724609 (+/- 0.00039828050210851215)
avg score time: 0.0029881954193115234 (+/- 4.301525777064362e-05)
avg test score: 0.14659868748701582 (+/- 0.021908831719954277)
# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = bagging_model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))# 출력 결과
# 출력 결과
avg fit time: 0.030098438262939453 (+/- 0.004244103777765697)
avg score time: 0.014351558685302735 (+/- 0.002644062031057098)
avg test score: 0.06636804266664734 (+/- 0.026375606251683278)
- bagging 모델 분류기에서 avg test score가 일반 모델보다 더 감소함
- DecisionTreeRegressor에서 일반 모델과 bagging 모델 비교
# 기본 모델 파이프라인 생성
base_model = make_pipeline(
StandardScaler(),
DecisionTreeRegressor()
)
# bagging 모델 분류기 생성
bagging_model = BaggingClassifier(base_model, n_estimators = 10, max_samples = 0.5, max_features = 0.5)
# 일반 모델 파이프라인을 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = base_model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.0030806541442871095 (+/- 0.0005084520543164849)
avg score time: 0.0005962371826171875 (+/- 0.0004868346584328474)
avg test score: -0.10402518188546787 (+/- 0.09614122612623877)
# bagging 모델 분류기를 사용하여 분류한 결과 정확도 출력
cross_val = cross_validate(
estimator = bagging_model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.028301239013671875 (+/- 0.009722365434744872)
avg score time: 0.001835775375366211 (+/- 0.0003816480339082081)
avg test score: 0.3206163477240517 (+/- 0.06240931175651393)
- bagging 모델 분류기에서 avg test score가 일반 모델보다 더 증가함
- 데이터에 따라 다르지만 bagging 모델이 일반 모델보다 좋은 성능을 보임
2. Forests of randomized trees
sklean.ensemble 모듈에는 무작위 결정 트리를 기반으로 하는 두 개의 평균화 알고리즘이 존재
Random Forest
Extra-Trees
모델 구성에 임의성을 추가해 다양한 모델 집합 생성
앙상블 모델의 예측은 각 모델의 평균
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier
- Random Forests 분류
# 모델 파이프라인 생성
model = make_pipeline(
StandardScaler(),
RandomForestClassifier()
)
# 붓꽃 데이터
cross_val = cross_validate(
estimator = model,
X = iris.data, y = iris.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.18105216026306153 (+/- 0.01685682915874488)
avg score time: 0.013797760009765625 (+/- 0.0025621248659456705)
avg test score: 0.96 (+/- 0.024944382578492935)
# 와인 데이터
cross_val = cross_validate(
estimator = model,
X = wine.data, y = wine.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.22976922988891602 (+/- 0.028256732778271565)
avg score time: 0.01587204933166504 (+/- 0.0023274044664123553)
avg test score: 0.9665079365079364 (+/- 0.032368500562618134)
# 유방암 데이터
cross_val = cross_validate(
estimator = model,
X = cancer.data, y = cancer.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.2917177200317383 (+/- 0.045315794078103835)
avg score time: 0.017597723007202148 (+/- 0.006803860063290489)
avg test score: 0.9578326346840551 (+/- 0.02028739541529243)
- Random Forests 회귀
# 모델 파이프라인 생성
model = make_pipeline(
StandardScaler(),
RandomForestRegressor()
)
# 당뇨병 데이터
cross_val = cross_validate(
estimator = model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.38582072257995603 (+/- 0.05492288277511406)
avg score time: 0.01399979591369629 (+/- 0.0017906758222056265)
avg test score: 0.41577582207684943 (+/- 0.04029500816412448)
- Extremely Randomized Trees 분류
# 모델 파이프라인 생성
model = make_pipeline(
StandardScaler(),
ExtraTreesClassifier()
)
# 붓꽃 데이터
cross_val = cross_validate(
estimator = model,
X = iris.data, y = iris.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.12993454933166504 (+/- 0.013365132836320116)
avg score time: 0.015802621841430664 (+/- 0.0026369354952103644)
avg test score: 0.9533333333333334 (+/- 0.03399346342395189)
# 와인 데이터
cross_val = cross_validate(
estimator = model,
X = cancer.data, y = cancer.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.14599318504333497 (+/- 0.027877641431797804)
avg score time: 0.013452291488647461 (+/- 0.0019263323264865461)
avg test score: 0.9776190476190475 (+/- 0.020831783767013237)
# 유방암 데이터
cross_val = cross_validate(
estimator = model,
X = cancer.data, y = cancer.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.15427541732788086 (+/- 0.01803722986834076)
avg score time: 0.014791202545166016 (+/- 0.0007367600639162756)
avg test score: 0.9683744760130415 (+/- 0.010503414750935476)
- Extremely Randomized Trees 회귀
# 모델 파이프라인 생성
model = make_pipeline(
StandardScaler(),
ExtraTreesRegressor()
)
# 당뇨병 데이터
cross_val = cross_validate(
estimator = model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.21371040344238282 (+/- 0.029887789885253244)
avg score time: 0.011214303970336913 (+/- 0.0022113466437151)
avg test score: 0.4334859913753323 (+/- 0.037205109491594564)
- Random Forest, Extra Tree 시각화
결정 트리, Random Forest, Extra Tree의 결정 경계와 회귀식 시각화
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.tree import DecisionTreeClassifier
# 기본 파라미터
n_classes = 3
n_estimators = 30
cmap = plt.cm.RdYlBu
plot_step = 0.02
plot_step_coarser = 0.5
RANDOM_SEED = 13
iris = load_iris()
plot_idx = 1
# 세 가지 모델을 for문에 넣어서 각 모델에 대한 예측 결과를 출력
models = [DecisionTreeClassifier(max_depth = None),
RandomForestClassifier(n_estimators = n_estimators),
ExtraTreesClassifier(n_estimators = n_estimators)]
plt.figure(figsize = (16, 8))
for pair in ([0, 1], [0, 2], [2, 3]):
# 위에서 지정한 세가지 모델에 대해 각각 예측
for model in models:
X = iris.data[:, pair]
y = iris.target
# 독립변수의 개수만큼 인덱스 배열 생성
idx = np.arange(X.shape[0])
# 랜덤시드 설정하고 랜덤으로 인덱스 셔플링(필요 x)
np.random.seed(RANDOM_SEED)
np.random.shuffle(idx)
X = X[idx]
y = y[idx]
# 평균과 표준편차를 계산하여 데이터 정규화
mean = X.mean(axis = 0)
std = X.std(axis = 0)
X = (X-mean) / std
# 위에서 정규화한 X와 y값을 모델에 피팅
model.fit(X, y)
# 모델 제목은 모델의 타입을 "."으로 분리하여
# ["<class 'sklearn", 'tree', '_classes', "DecisionTreeClassifier'>"]
# [-1]은 리스트의 가장 마지막 부분 추출("DecisionTreeClassifier'>")
# [:-2]는 위의 문자열의 끝에서 두번째 문자전까지만 추출("DecisionTreeClassifier")
# 마지막으로 해당 문자열에서 Classifier을 제외하기 위해 Classifier의 문자열 길이 전까지만 추출
model_title = str(type(model)).split(".")[-1][:-2][:-len("Classifier")]
# 3 * 3의 그래프 배열에서 plot_idx만큼(plot_idx는 모델 한 번 돌때마다 변경됨)
plt.subplot(3, 3, plot_idx)
if plot_idx <= len(models):
plt.title(model_title, fontsize = 9)
# meshgrid 생성
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, plot_step),
np.meshgrid(np.arange(y_min, y_max, plot_step)))
# DecisionTreeClassifier 모델일 때와 나머지 모델일 때 구분
if isinstance(model, DecisionTreeClassifier):
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
cs = plt.contourf(xx, yy, Z, cmap = cmap)
# 나머지 모델일 때는 alpha값을 주어 투명도 추가
else:
estimator_alpha = 1.0 / len(model.estimators_)
for tree in model.estimators_:
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
cs = plt.contourf(xx, yy, Z, alpha = estimator_alpha, cmap = cmap)
xx_coarser, yy_coarser = np.meshgrid(np.arange(x_min, x_max, plot_step_coarser),
np.arange(y_min, y_max, plot_step_coarser))
Z_points_coarser = model.predict(np.c_[xx_coarser.ravel(),
yy_coarser.ravel()]).reshape(xx_coarser.shape)
cs_points = plt.scatter(xx_coarser, yy_coarser, s = 15, c = Z_points_coarser, cmap = cmap, edgecolor = 'none')
plt.scatter(X[:, 0], X[:, 1], c = y, cmap = ListedColormap(['r','y','b']), edgecolor = 'k', s = 20)
plot_idx += 1
# 그래프 최상단 제목 설정
plt.suptitle("Classifiers", fontsize = 12)
plt.axis('tight')
plt.tight_layout(h_pad = 0.2, w_pad = 0.2, pad = 2.5)
plt.show()
plot_idx = 1
models = [DecisionTreeRegressor(max_depth = None),
RandomForestRegressor(n_estimators = n_estimators),
ExtraTreesRegressor(n_estimators = n_estimators)]
plt.figure(figsize = (16, 8))
for pair in (0, 1, 2):
for model in models:
X = diabetes.data[:, pair]
y = diabetes.target
idx = np.arange(X.shape[0])
np.random.seed(RANDOM_SEED)
np.random.shuffle(idx)
X = X[idx]
y = y[idx]
mean = X.mean(axis = 0)
std = X.std(axis = 0)
X = (X-mean) / std
model.fit(X.reshape(-1, 1), y)
model_title = str(type(model)).split(".")[-1][:-2][:-len('Regreffor')]
plt.subplot(3, 3, plot_idx)
if plot_idx <= len(models):
plt.title(model_title, fontsize = 9)
x_min, x_max = X.min() -1, X.max() + 1
y_min, y_max = y.min() -1, y.max() + 1
xx, yy = np.arange(x_min - 1, x_max + 1, plot_step), np.arange(y_min - 1, y_max + 1, plot_step)
if isinstance(model, DecisionTreeRegressor):
Z = model.predict(xx.reshape(-1, 1))
cs = plt.plot(xx, Z)
else:
estimator_alpha = 1.0 / len(model.estimators_)
for tree in model.estimators_:
Z = tree.predict(xx.reshape(-1, 1))
cs = plt.plot(xx, Z, alpha = estimator_alpha)
plt.scatter(X, y, edgecolors = 'k', s = 20)
plot_idx += 1
plt.suptitle("Regressor", fontsize = 12)
plt.axis('tight')
plt.tight_layout(h_pad = 0.2, w_pad = 0.2, pad = 2.5)
plt.show()
3. AdaBoost
대표적인 부스팅 알고리즘
일련의 약한 모델들을 학습
수정된 버전의 데이터를 (가중치가 적용된)반복 학습
가중치 투표(또는 합)을 통해 각 모델의 예측값을 결합
첫 단계에서는 원본 데이터를 학습하고 연속적인 반복마다 개별 샘플에 대한 가중치가 수정되고 다시 모델이 학습
잘못 예측된 샘플은 가중치가 증가, 올바르게 예측된 샘플은 가중치 감소
각각의 약한 모델들은 예측하기 어려운 샘플에 집중하게 됨
- AdaBoostClassifier
from sklearn.ensemble import AdaBoostClassifier, AdaBoostRegressor
# 기본 모델 파이프라인 생성
model = make_pipeline(
StandardScaler(),
AdaBoostClassifier()
)
# 붓꽃 데이터
cross_val = cross_validate(
estimator = model,
X = iris.data, y = iris.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.10685276985168457 (+/- 0.0251679732383264)
avg score time: 0.012082815170288086 (+/- 0.0029404438972186714)
avg test score: 0.9466666666666667 (+/- 0.03399346342395189)
# 와인 데이터
cross_val = cross_validate(
estimator = model,
X = wine.data, y = wine.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.12471566200256348 (+/- 0.022184160168356563)
avg score time: 0.010206842422485351 (+/- 0.0014569535430837709)
avg test score: 0.8085714285714285 (+/- 0.16822356718459935)
# 유방암 데이터
cross_val = cross_validate(
estimator = model,
X = cancer.data, y = cancer.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.21773457527160645 (+/- 0.036282505551639664)
avg score time: 0.01280508041381836 (+/- 0.006666702833190784)
avg test score: 0.9701133364384411 (+/- 0.019709915473893072)
- AdaBoostRegressor
from sklearn.ensemble import AdaBoostClassifier, AdaBoostRegressor
# 기본 모델 파이프라인 생성
model = make_pipeline(
StandardScaler(),
AdaBoostRegressor()
)
# 당뇨병 데이터
cross_val = cross_validate(
estimator = model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.0806793212890625 (+/- 0.019655713051304806)
avg score time: 0.004206609725952148 (+/- 0.0017171964676314028)
avg test score: 0.4325980577698525 (+/- 0.046753912177138576)
4. Gradient Tree Boosting
임의의 차별화 가능한 손실함수로 일반화한 부스팅 알고리즘
웹 검색, 분류 및 회귀 등 다양한 분야에서 모두 사용 가능
- GradientBoostingClassifier
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
# 기본 모델 파이프라인 생성
model = make_pipeline(
StandardScaler(),
GradientBoostingClassifier()
)
# 붓꽃 데이터
cross_val = cross_validate(
estimator = model,
X = iris.data, y = iris.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.28792128562927244 (+/- 0.08891755551147741)
avg score time: 0.0009984493255615235 (+/- 1.4898700510940572e-05)
avg test score: 0.9666666666666668 (+/- 0.02108185106778919)
# 와인 데이터
cross_val = cross_validate(
estimator = model,
X = wine.data, y = wine.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.557232141494751 (+/- 0.11776815518280624)
avg score time: 0.001400327682495117 (+/- 0.0004905852751874076)
avg test score: 0.9385714285714286 (+/- 0.032068206474093704)
# 유방암 데이터
avg fit time: 0.5082685947418213 (+/- 0.05271622631546759)
avg score time: 0.0009903907775878906 (+/- 9.857966772333804e-06)
avg test score: 0.9596180717279925 (+/- 0.02453263202329889)
- GradientBoostingRegressor
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
# 기본 모델 파이프라인 생성
model = make_pipeline(
StandardScaler(),
GradientBoostingRegressor()
)
# 당뇨병 데이터
cross_val = cross_validate(
estimator = model,
X = diabetes.data, y = diabetes.target,
cv = 5
)
print("avg fit time: {} (+/- {})".format(cross_val['fit_time'].mean(), cross_val['fit_time'].std()))
print("avg score time: {} (+/- {})".format(cross_val['score_time'].mean(), cross_val['score_time'].std()))
print("avg test score: {} (+/- {})".format(cross_val['test_score'].mean(), cross_val['test_score'].std()))
# 출력 결과
avg fit time: 0.12577242851257325 (+/- 0.01002747346482568)
avg score time: 0.0013751983642578125 (+/- 0.00048391264033975266)
avg test score: 0.40743256738384626 (+/- 0.06957393690515927)
5. 투표 기반 분류(Voting Classifier)
서로 다른 모델들의 결과를 투표를 통해 결합
두가지 방법으로 투표 가능
가장 많이 예측된 클래스를 정답으로 채택(hard voting)
예측된 확률의 가중치 평균(soft voting)
- hard 보팅
from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.model_selection import cross_val_score
model1 = SVC()
model2 = GaussianNB()
model3 = RandomForestClassifier()
vote_model = VotingClassifier(
estimators = [('svc', model1), ('naive', model2), ('forest', model3)],
voting = 'hard'
)
for model in (model1, model2, model3, vote_model):
model_name = str(type(model)).split(".")[-1][:-2]
scores = cross_val_score(model, iris.data, iris.target, cv = 5)
print('accurancy: %0.2f (+/- %0.2f) [%s]' % (scores.mean(), scores.std(), model_name))
# 출력 결과
accurancy: 0.97 (+/- 0.02) [SVC]
accurancy: 0.95 (+/- 0.03) [GaussianNB]
accurancy: 0.96 (+/- 0.02) [RandomForestClassifier]
accurancy: 0.96 (+/- 0.02) [VotingClassifier]