사진출처: https://thecleverprogrammer.com/2020/07/26/precision-and-recall/
모델이 판별할 때 모델의 예측값이
예측할 때 positive 인지 negative 인지를 판별하는 값을 threshhold 라고 한다.
(threshold 값을 높이면 recall은 낮아지고 precision은 높아진다.)
모델에 따라
경우에 따라서 어떤 것이 더 좋은가 생각해볼 수 있다.
예를 들어
🔥 경우에 따라 precision 혹은 recall 이 더 높은 모델을 사용해야 한다.
Precision/Recall 조절 방법에 대해서 알아보자.
errors[errors == True]
# 48 True
# 132 True
# 138 True
# 173 True
# 244 True
# ...
# 59719 True
# 59726 True
# 59731 True
# 59747 True
# 59766 True
# Name: class, Length: 1567, dtype: bool
y_train_pred[48], y_train_5[48]
# (True, False)
👉 49번째 데이터는 5라고 예측을 했지만 실제로는 5가 아닌 경우의 데이터이다.
우선 49번째 데이터에 대해서 확인
some_digit = X_train.loc[48]
y_scores = log_clf.decision_function([some_digit])
y_scores
# array([0.22419047])
📌 decision_function()
: 예측값이 결정 경계(decision boundary)로부터 얼마나 떨어져있는지 반환
some_digit_image = some_digit.values.reshape(28, 28)
plt.imshow(some_digit_image, cmap=mpl.cm.binary)
plt.axis("off")
save_fig("some_digit_plot")
plt.show()
threshold = 0
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
# array([ True])
threshold = 0.5
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
# array([False])
👉 threshold 값이 0 인경우에는 True
이지만, 0.5 로 조정했을 경우 False
로 바뀜
이제 전체 데이터에 대해서 확인
y_scores = cross_val_predict(log_clf, X_train, y_train_5, cv=3,
method="decision_function")
y_scores.shape
# (60000,)
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)
precisions.shape
# (59897,)
recalls.shape
# (59897,)
thresholds.shape
# (59896,)
def plot_precision_vs_recall(precisions, recalls):
plt.plot(recalls, precisions, "b-", linewidth=2)
plt.xlabel("Recall", fontsize=16)
plt.ylabel("Precision", fontsize=16)
plt.axis([0, 1, 0, 1])
plt.grid(True)
plt.figure(figsize=(8, 6))
plot_precision_vs_recall(precisions, recalls)
save_fig("precision_vs_recall_plot")
plt.show()
👉
🔥 precision 과 recall이 가져야할 최소값을 만족하는 threshold를 설정한다!
from sklearn.linear_model import LogisticRegression
softmax_reg = LogisticRegression(multi_class="multinomial",solver="lbfgs", C=10)
softmax_reg.fit(X_train, y_train)
# LogisticRegression(C=10, multi_class='multinomial')
softmax_reg.predict(X_train)[:10]
# array([5, 0, 4, 1, 9, 2, 1, 3, 1, 4], dtype=uint8)
😄 각각의 클래스가 비슷한 분포를 가지고 있기 때문에 Accuracy를 구하는 것이 나쁘지 않다!
from sklearn.metrics import accuracy_score
y_pred = softmax_reg.predict(X_test)
accuracy_score(y_test, y_pred)
# 0.9243
📌 shift image 함수 정의
이미지를 이동시킴
from scipy.ndimage.interpolation import shift
def shift_image(image, dx, dy):
image = image.reshape((28, 28))
shifted_image = shift(image, [dy, dx], cval=0, mode="constant")
return shifted_image.reshape([-1])
image = X_train.loc[1000].values
shifted_image_down = shift_image(image, 0, 5)
shifted_image_left = shift_image(image, -5, 0)
plt.figure(figsize=(12,3))
plt.subplot(131)
plt.title("Original", fontsize=14)
plt.imshow(image.reshape(28, 28), interpolation="nearest", cmap="Greys")
plt.subplot(132)
plt.title("Shifted down", fontsize=14)
plt.imshow(shifted_image_down.reshape(28, 28), interpolation="nearest", cmap="Greys")
plt.subplot(133)
plt.title("Shifted left", fontsize=14)
plt.imshow(shifted_image_left.reshape(28, 28), interpolation="nearest", cmap="Greys")
plt.show()
학습 데이터의 각 이미지를 변환
X_train_augmented = [image for image in X_train.values]
y_train_augmented = [label for label in y_train.values]
for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
for image, label in zip(X_train.values, y_train.values):
X_train_augmented.append(shift_image(image, dx, dy))
y_train_augmented.append(label)
X_train_augmented = np.array(X_train_augmented)
y_train_augmented = np.array(y_train_augmented)
X_train_augmented.shape
# (300000, 784)
shuffle_idx = np.random.permutation(len(X_train_augmented))
X_train_augmented = X_train_augmented[shuffle_idx]
y_train_augmented = y_train_augmented[shuffle_idx]
X_train_augmented.shape, X_train.shape
# ((300000, 784), (60000, 784))
softmax_reg_augmented = LogisticRegression(multi_class="multinomial",solver="lbfgs", C=10)
softmax_reg_augmented.fit(X_train_augmented, y_train_augmented)
y_pred = softmax_reg_augmented.predict(X_test)
accuracy_score(y_test, y_pred)
# 0.9279
👉 정확도는 0.9243 이었는데 Data Augmentation 이후 0.9279로 조금 올랐다.
(간단한 방식이기 때문에 영향이 적다)
데이터 준비
import numpy as np
import pandas as pd
train_data = pd.read_csv("train.csv")
train_data.head()
👉 Survived 속성을 목표값으로 설정
train_data.info()
👉
train_data.describe()
👉 대략 40% 미만의 생존자가 존재한다.
train_data["Survived"].value_counts()
# 0 549
# 1 342
# Name: Survived, dtype: int64
train_data["Pclass"].value_counts()
# 3 491
# 1 216
# 2 184
# Name: Pclass, dtype: int64
train_data["Sex"].value_counts()
# male 577
# female 314
# Name: Sex, dtype: int64
train_data["Embarked"].value_counts()
# S 644
# C 168
# Q 77
# Name: Embarked, dtype: int64
📌 DataFrameSelector 함수 정의
몇가지 사용할 속성만 처리하기 위해 Numerical 속성을 처리
from sklearn.base import BaseEstimator, TransformerMixin
class DataFrameSelector(BaseEstimator, TransformerMixin):
def __init__(self, attribute_names):
self.attribute_names = attribute_names
def fit(self, X, y=None):
return self
def transform(self, X):
return X[self.attribute_names]
Numerical 속성을 처리하는 pipeline을 만든다.
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
num_pipeline = Pipeline([
("select_numeric", DataFrameSelector(["Age", "SibSp", "Parch", "Fare"])),
("imputer", SimpleImputer(strategy="median")),
])
num_pipeline.fit_transform(train_data)
# array([[22. , 1. , 0. , 7.25 ],
# [38. , 1. , 0. , 71.2833],
# [26. , 0. , 0. , 7.925 ],
# ...,
# [28. , 1. , 2. , 23.45 ],
# [26. , 0. , 0. , 30. ],
# [32. , 0. , 0. , 7.75 ]])
👉 Numerical 속성들의 missing value는 중앙값으로 채워줌
📌 MostFrequentImputer 함수 정의
가장 많이 나타나는 값으로 missing value를 채워줌
class MostFrequentImputer(BaseEstimator, TransformerMixin):
def fit(self, X, y=None):
self.most_frequent_ = pd.Series([X[c].value_counts().index[0] for c in X],
index=X.columns)
return self
def transform(self, X, y=None):
return X.fillna(self.most_frequent_)
from sklearn.preprocessing import OneHotEncoder
cat_pipeline = Pipeline([
("select_cat", DataFrameSelector(["Pclass", "Sex", "Embarked"])),
("imputer", MostFrequentImputer()),
("cat_encoder", OneHotEncoder(sparse=False)),
])
cat_pipeline.fit_transform(train_data)
# array([[0., 0., 1., ..., 0., 0., 1.],
# [1., 0., 0., ..., 1., 0., 0.],
# [0., 0., 1., ..., 0., 0., 1.],
# ...,
# [0., 0., 1., ..., 0., 0., 1.],
# [1., 0., 0., ..., 1., 0., 0.],
# [0., 0., 1., ..., 0., 1., 0.]])
cat_pipeline.fit_transform(train_data)[0]
# array([0., 0., 1., 0., 1., 0., 0., 1.])
👉 Categorical 속성들의 missing value는 가장 많이 나타나는 값으로 채워주고 one-hot-encoding 진행
마지막으로 Categorical, Numerical 속성들을 통합
from sklearn.pipeline import FeatureUnion
preprocess_pipeline = FeatureUnion(transformer_list=[
("num_pipeline", num_pipeline),
("cat_pipeline", cat_pipeline),
])
X_train = preprocess_pipeline.fit_transform(train_data)
X_train
# array([[22., 1., 0., ..., 0., 0., 1.],
# [38., 1., 0., ..., 1., 0., 0.],
# [26., 0., 0., ..., 0., 0., 1.],
# ...,
# [28., 1., 2., ..., 0., 0., 1.],
# [26., 0., 0., ..., 1., 0., 0.],
# [32., 0., 0., ..., 0., 1., 0.]])
X_train.shape
# (891, 12)
목표값 벡터 확인
y_train = train_data["Survived"]
y_train
# 0 0
# 1 1
# 2 1
# 3 1
# 4 0
# ..
# 886 0
# 887 1
# 888 0
# 889 1
# 890 0
# Name: Survived, Length: 891, dtype: int64
logistic regression 을 이용하여 학습 진행
log_clf = LogisticRegression(random_state=0).fit(X_train, y_train)
모델 내에서 살펴보기
a = np.c_[log_clf.decision_function(X_train), y_train, X_train]
df = pd.DataFrame(data=a, columns=["Score", "Survived", "Age", "SibSp", "Parch", "Fare", "Pclass_1", "Pclass_2", "Pclass_3", "Female", "Male", "Embarked_C", "Embarked_Q", "Embarked_S"])
df
점수가 높은 값부터 정렬
df.sort_values(by=['Score'], ascending=False)[:20]
👉 생존한 사람들의 대부분이 Score가 높다.
점수가 낮은 값부터 정렬
df.sort_values(by=['Score'])[:20]
👉 생존하지 못한 사람들의 대부분이 Score가 낮다.