
import pandas as pd
red_url = '.../winequality-red.csv'
white_url = '.../winequality-white.csv'
red_wine = pd.read_csv(red_url, sep=';')
white_wine = pd.read_csv(white_url, sep=';')
red_wine['color'] = 1
white_wine['color'] = 0
wine = pd.concat([red_wine, white_wine])
wine.head()

wine['taste'] = [1. if grade > 5 else 0. for grade in wine['quality']]
wine.head()

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
# quality는 taste와 강한 상관관계를 가질 가능성이 높은 변수. 이를 포함할 경우 모델이 단순히 quality를 사용해 taste를 예측
# 따라서 quality를 제거하여 모델이 다른 특성을 활용하도록 유도
X = wine.drop(['taste', 'quality'], axis=1)
y = wine['taste']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=4)
estimators = [
('scaler', StandardScaler()),
('clf', DecisionTreeClassifier())]
pipe = Pipeline(estimators)
pipe

pipe.set_params(clf__max_depth=2)
pipe.set_params(clf__random_state=4)
pipe.fit(X_train, y_train)
y_pred_test = pipe.predict(X_test)
accuracy_score(y_test, y_pred_test)
# 결과 :
0.9569230769230769
from sklearn.metrics import accuracy_score, recall_score, precision_score, f1_score
from sklearn.metrics import roc_auc_score, roc_curve
y_pred_tr = wine_tree.predict(X_train)
y_pred_test = wine_tree.predict(X_test)
print('accuracy_score: ', accuracy_score(y_test, y_pred_test))
print('recall_score: ', recall_score(y_test, y_pred_test))
print('precision_score: ', precision_score(y_test, y_pred_test))
print('f1_score: ', f1_score(y_test, y_pred_test))
print('roc_auc_score: ', roc_auc_score(y_test, y_pred_test))
# 결과 :
accuracy_score: 0.7346153846153847
recall_score: 0.8578371810449574
precision_score: 0.7558886509635975
f1_score: 0.8036425725668753
roc_auc_score: 0.6899248798306549
import matplotlib.pyplot as plt
# predict_proba 메서드는 각 클래스에 대한 예측 확률을 반환 (각 클래스에 속할 확률)
# 이진 분류 모델인 경우 각 샘플에 대해 두 가지 확률 값 [P(class=0), P(class=1)]을 반환
# [:, 1]은 이 확률 배열에서 두 번째 열(class=1에 대한 확률)만 선택
# ROC 곡선을 그릴 때는 양성 클래스에 대한 확률을 사용하는 것이 일반적
pred_proba = wine_tree.predict_proba(X_test)[:,1]
# fpr: 모델이 실제로는 음성 클래스(Negative)인 샘플을 양성 클래스(Positive)로 잘못 분류한 비율.
# tpr: 모델이 실제로 양성 클래스(Positive)인 샘플을 양성으로 올바르게 분류한 비율 (= recall)
# 범위: [0, 1] / TPR이 높을수록 모델이 양성을 정확히 분류하는 능력이 높음
fpr, tpr, _ = roc_curve(y_test, pred_proba)
plt.figure(figsize=(8,4))
plt.plot([0,1], [0,1], 'r')
plt.plot(fpr,tpr)
plt.grid()
plt.show()

import pandas as pd
wine_url = '.../dataset/wine.csv'
wine = pd.read_csv(wine_url, index_col=0)
wine['taste'] = [1 if grade >5 else 0 for grade in wine['quality']]
X = wine.drop(['taste', 'quality'], axis=1)
y = wine['taste']
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=4)
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
lr = LogisticRegression(solver='liblinear', random_state=4)
lr.fit(X_train, y_train)
y_pred_tr = lr.predict(X_train)
y_pred_test = lr.predict(X_test)
accuracy_score(y_train, y_pred_tr), accuracy_score(y_test, y_pred_test)
# 결과 :
(0.7463921493169136, 0.7123076923076923)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
estimators = [
('scaler', StandardScaler()),
('clf', LogisticRegression(solver='liblinear', random_state=4))
]
pipe = Pipeline(estimators)
pipe.fit(X_train, y_train)
y_pred_tr = pipe.predict(X_train)
y_pred_train = pipe.predict(X_test)
accuracy_score(y_train, y_pred_tr), accuracy_score(y_test, y_pred_train)
# 결과 :
(0.7488935924571868, 0.7169230769230769)
# scaler 진행 전과 큰 차이는 없음
models = {
'logistic regression' : lr,
'decision tree' : wine_tree
}
from sklearn.metrics import roc_curve
import matplotlib.pyplot as plt
plt.figure(figsize=(6,4))
plt.plot([0,1],[0,1], 'k--')
for model_name, model in models.items():
pred = model.predict_proba(X_test)[:,1]
fpr, tpr, _ = roc_curve(y_test, pred)
plt.plot(fpr, tpr, label=model_name)
plt.grid()
plt.legend()
plt.show()

리스트 (Pipeline 사용):
작업이 순차적으로 처리되어야 할 때 사용
전처리 및 모델 학습이 연결된 흐름(Flow)을 나타낼 때 적합
예: Pipeline 객체.
딕셔너리 (모델 비교/관리):
여러 객체를 명확한 이름으로 관리하고 접근해야 할 때 사용
모델의 성능 비교, 선택, 평가 등 비순차적인 작업에 적합
예: 여러 모델 간 성능 비교를 위한 코드.


PIMA_url = 'https://raw.githubusercontent.com/PinkWink/ML_tutorial/refs/heads/master/dataset/diabetes.csv'
PIMA = pd.read_csv(PIMA_url)
PIMA = PIMA.astype('float')
(PIMA==0).astype(int).sum()
zero_features = ['Glucose','BloodPressure','SkinThickness','BMI']
PIMA[zero_features] = PIMA[zero_features].replace(0, PIMA[zero_features].mean())
(PIMA[zero_features]==0).sum()

from sklearn.model_selection import train_test_split
X = PIMA.drop('Outcome',axis=1)
y = PIMA['Outcome']
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2, random_state=4, stratify=y)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
estimators = [
('scaler', StandardScaler()),
('clf', LogisticRegression(solver='liblinear', random_state=4))
]
pipe_lr = Pipeline(estimators)
pipe_lr.fit(X_train, y_train)
pred = pipe_lr.predict(X_test)
from sklearn.metrics import accuracy_score, recall_score, precision_score, f1_score, confusion_matrix
print('accuracy: {:.2f}'.format(accuracy_score(y_test, pred)))
print('recall: {:.2f}'.format(recall_score(y_test, pred)))
print('precision: {:.2f}'.format(precision_score(y_test, pred)))
print('f1 score: {:.2f}'.format(f1_score(y_test, pred)))
print(confusion_matrix(y_test,pred))
# 결과 :
accuracy: 0.72
recall: 0.59
precision: 0.60
f1 score: 0.60
[[79 21]
[22 32]]
coeff = list(pipe_lr['clf'].coef_[0])
labels = list(X_train.columns)
pipe_lr['clf'].coef_
# 결과 :
array([[ 0.40067199, 1.30063334, -0.13956054, -0.02672671, -0.27189878,
0.76428723, 0.35433026, 0.05208156]])
features = pd.DataFrame({'Features': labels, 'Importance': coeff})
features.sort_values(by=['Importance'], inplace=True)
features['Positive'] = features['Importance']>0
features.set_index('Features', inplace=True)
features['Importance'].plot(kind='barh', figsize=(8,5), color=features['Positive'].map({True: 'blue', False: 'red'}))


import pandas as pd
red_url = '.../data/winequality-red.csv'
white_url = '.../data/winequality-white.csv'
red_wine = pd.read_csv(red_url, sep=';')
white_wine = pd.read_csv(white_url, sep=';')
red_wine['color'] = 1
white_wine['color'] = 0
wine = pd.concat([red_wine, white_wine])
wine['taste'] = [1 if grade >5 else 0 for grade in wine['quality']]
X = wine.drop(['taste', 'quality'], axis=1)
y = wine['taste']
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state = 4)
wine_tree = DecisionTreeClassifier(max_depth=2, random_state=4)
wine_tree.fit(X_train, y_train)
y_pred_tr = wine_tree.predict(X_train)
y_pred_test = wine_tree.predict(X_test)
accuracy_score(y_train, y_pred_tr), accuracy_score(y_test, y_pred_test)
# 결과 :
(0.7396574947084856, 0.7184615384615385)
from sklearn.model_selection import StratifiedKFold, cross_val_score
# 결정 트리 모델 생성
wine_tree_cv = DecisionTreeClassifier(max_depth=2, random_state=4)
# StratifiedKFold 객체 생성
skfold = StratifiedKFold(n_splits=5)
# 교차 검증 수행 (매개변수 순서 변경X = 모델 > X > y > cv전략)
# cv: 교차 검증 분할 전략 (skfold 객체)
cross_val_score(wine_tree_cv, X, y, cv=skfold)
# 결과 :
array([0.55230769, 0.68846154, 0.71439569, 0.73210162, 0.75673595])
from sklearn.model_selection import StratifiedKFold, cross_validate
wine_tree_cv = DecisionTreeClassification(max_depth=2, random_state=4)
skfold = StratifiedKFold(n_splits=5)
cross_validate(wine_tree_cv, X, y, cv=skfold, return_train_score=True)
# 결과 :
{'fit_time': array([0.01651144, 0.01403475, 0.01599216, 0.01027298, 0.010813 ]),
'score_time': array([0.0045104 , 0.00299239, 0.00396204, 0.00199461, 0.00199318]),
'test_score': array([0.55230769, 0.68846154, 0.71439569, 0.73210162, 0.75673595]),
'train_score': array([0.74773908, 0.74696941, 0.74317045, 0.73509042, 0.73258946])}

from sklearn.model_selection import GridSearchCV
from sklearn.tree import DecisionTreeClassifier
params = {'max_depth':[2,4,7,10]}
wine_tree = DecisionTreeClassifier(random_state=4)
grid_search = GridSearchCV(wine_tree, param_grid=params, cv=5)
grid_search.fit(X_train, y_train)
grid_search.cv_results_
# 결과
{'mean_fit_time': array([0.0102963 , 0.01396451, 0.02435012, 0.02766619]),
'std_fit_time': array([0.00210397, 0.00139692, 0.00164525, 0.00162327]),
'mean_score_time': array([0.00280542, 0.00259871, 0.00239406, 0.00179524]),
'std_score_time': array([0.00038965, 0.0008024 , 0.00048873, 0.00039077]),
'param_max_depth': masked_array(data=[2, 4, 7, 10],
mask=[False, False, False, False],
fill_value='?',
dtype=object),
'params': [{'max_depth': 2},
{'max_depth': 4},
{'max_depth': 7},
{'max_depth': 10}],
'split0_test_score': array([0.72980769, 0.73076923, 0.73269231, 0.75480769]),
'split1_test_score': array([0.73557692, 0.74230769, 0.74230769, 0.74903846]),
'split2_test_score': array([0.73820982, 0.73243503, 0.73051011, 0.7545717 ]),
'split3_test_score': array([0.74687199, 0.7574591 , 0.76419634, 0.7574591 ]),
'split4_test_score': array([0.7333975 , 0.74013474, 0.75072185, 0.75938402]),
'mean_test_score': array([0.73677278, 0.74062116, 0.74408566, 0.7550522 ]),
'std_test_score': array([0.00575142, 0.0094939 , 0.01237533, 0.00349324]),
'rank_test_score': array([4, 3, 2, 1])}
grid_search.best_estimator_
# 결과 : DecisionTreeClassifier(max_depth=10, random_state=4)
grid_search.best_score_
# 결과 : 0.7550521951580661

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
estimators = [('scaler', StandardScaler()), ('clf', DecisionTreeClassifier(random_state=4))]
pipe = Pipeline(estimators)
# 하이퍼파라미터 그리드 설정
# <단계 이름>__<하이퍼파라미터 이름> 형식
param_grid = [{'clf__max_depth': [2,4,7,10]}]
# GridSearchCV 생성
grid_search = GridSearchCV(pipe, param_grid=param_grid, cv=5)
grid_search
# 교차 검증 및 모델 학습
grid_search.fit(X_train, y_train)
# 최적 점수 확인
# best_score_는 최적의 하이퍼파라미터 조합에서 모든 폴드의 점수의 평균값
grid_search.best_score_
# 결과 : 0.7562071518471903
import pandas as pd
wine_url = '.../dataset/wine.csv'
wine = pd.read_csv(wine_url, index_col=0)
wine['taste'] = [1 if grade>5 else 0 for grade in wine['quality']]
X = wine.drop(['taste', 'quality'], axis=1)
y = wine['taste']
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=4)
lr = LogisticRegression(solver='liblinear', random_state=4)
lr.fit(X_train, y_train)
y_pred_tr = lr.predict(X_train)
y_pred_test = lr.predict(X_test)
accuracy_score(y_train, y_pred_tr), accuracy_score(y_test, y_pred_test)
# 결과 :
(0.7463921493169136, 0.7123076923076923)
from sklearn.metrics import classification_report, confusion_matrix
print(classification_report(y_test, y_pred_test))
# 결과 :
precision recall f1-score support
0 0.64 0.52 0.57 487
1 0.74 0.83 0.78 813
accuracy 0.71 1300
macro avg 0.69 0.67 0.68 1300
weighted avg 0.71 0.71 0.70 1300
confusion_matrix(y_test, y_pred_test)
# 결과 :
array([[252, 235],
[139, 674]], dtype=int64)
1. 클래스 별 recall 값 차이의 의미
2. 클래스별 Recall 값 분석
[클래스 0 : Recall = 0.52]
[클래스 1 : Recall = 0.83]
3. Recall 차이가 발생하는 이유
(1) 클래스별 데이터 분포
(2) 모델의 특성
(3) 임계값(Threshold)
(4) 데이터의 특성 차이 :
4. Recall 차이를 해결하려면
(1) 클래스 불균형 해결
LogisticRegression(class_weight='balanced', solver='liblinear')
(2) 임계값 조정: 기본적으로 0.5인 분류 임계값을 변경하여 Recall 값을 조정할 수 있습니다:
from sklearn.metrics import recall_score
y_pred_new = (model.predict_proba(X_test)[:, 1] >= 0.4).astype(int)
print(recall_score(y_test, y_pred_new, pos_label=0)) # 클래스 0의 Recall 계산
print(recall_score(y_test, y_pred_new, pos_label=1)) # 클래스 1의 Recall 계산
(3) 모델 개선
import matplotlib.pyplot as plt
from sklearn.metrics import precision_recall_curve
plt.figure(figsize=(8,4))
# predict_proba는 각 샘플이 각 클래스에 속할 확률을 반환
# [:, 1]은 두 번째 열(클래스 1의 확률)만 추출
pred = lr.predict_proba(X_test)[:,1]
precision, recall, threshold = precision_recall_curve(y_test, pred)
# thresholds는 각 경계값에서의 precision과 recall을 계산하므로, 배열 길이를 맞추기 위해 precision[:-1]와 recall[:-1]를 사용
# 가장 낮은 확률값과 가장 높은 확률값의 외부 경계를 포함하지 않음
plt.plot(threshold, precision[:-1], label='precision')
plt.plot(threshold, recall[:-1], label='recall')
plt.grid()
plt.legend()
plt.show()
# 참고
pred_proba = lr.predict_proba(X_test)
pred_proba
# 결과 :
array([[0.08832224, 0.91167776],
[0.43973537, 0.56026463],
[0.11991766, 0.88008234],
...,
[0.23679924, 0.76320076],
[0.06032141, 0.93967859],
[0.05777677, 0.94222323]])

import numpy as np
np.concatenate([lr.predict_proba(X_test), y_pred_test.reshape(-1,1)], axis=1)
# 결과 :
array([[0.08832224, 0.91167776, 1. ],
[0.43973537, 0.56026463, 1. ],
[0.11991766, 0.88008234, 1. ],
...,
[0.23679924, 0.76320076, 1. ],
[0.06032141, 0.93967859, 1. ],
[0.05777677, 0.94222323, 1. ]])
np.concatenate([lr.predict_proba(X_test), y_test.values.reshape(-1,1)], axis=1)
# 결과 :
array([[0.08832224, 0.91167776, 1. ],
[0.43973537, 0.56026463, 0. ],
[0.11991766, 0.88008234, 0. ],
...,
[0.23679924, 0.76320076, 0. ],
[0.06032141, 0.93967859, 1. ],
[0.05777677, 0.94222323, 1. ]])
from sklearn.preprocessing import Binarizer
biz = Binarizer(threshold=0.6).fit(pred_proba)
pred_bin = biz.transform(pred_proba)[:,1]
pred_bin[:5]
# 결과 :
array([1., 0., 1., 1., 0.])
print(classification_report(y_test, pred_bin))
# 결과 :
precision recall f1-score support
0 0.61 0.68 0.64 487
1 0.79 0.74 0.76 813
accuracy 0.71 1300
macro avg 0.70 0.71 0.70 1300
weighted avg 0.72 0.71 0.72 1300
confusion_matrix(y_test, pred_bin)
# 결과 :
array([[331, 156],
[215, 598]], dtype=int64)