<jupyter_start><jupyter_text>유용한 특성 선택하기두 번째 모델의 특성 중요도를 보면 TF-IDF 벡터 특성은 가장 중요한 특성에서 빠져있습니다. 이 특성을 제외하고도 모델의 잘 수행되는지 확인하기 위해 이 특성들을 빼고 세 번째 모델을 훈련해 보겠습니다.<jupyter_code>import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, classification_report
import joblib
import sys
sys.path.append("..")
import warnings
warnings.filterwarnings('ignore')

from ml_editor.data_processing import (
    format_raw_df,
    get_split_by_author
)
from ml_editor.model_v2 import (
    add_char_count_features,
    get_word_stats,
    get_sentiment_score,
    POS_NAMES,
    get_question_score_from_input
)
from ml_editor.model_evaluation import (
    get_feature_importance,
    get_roc_plot,
    get_confusion_matrix_plot,
    get_calibration_plot
)
%load_ext autoreload
%autoreload 2
np.random.seed(35)

data_path = Path('../data/writers.csv')
df = pd.read_csv(data_path)
df = format_raw_df(df.copy())

df = df.loc[df["is_question"]].copy()
df["full_text"] = df["Title"].str.cat(df["body_text"], sep=" ", na_rep="")<jupyter_output><empty_output><jupyter_text>아래 함수는 특성을 추가합니다. 이 함수에 대한 자세한 내용은 `ml_editor` 소스 코드를 참고하세요.<jupyter_code>df = add_char_count_features(df.copy())
df = get_word_stats(df.copy())
df = get_sentiment_score(df.copy())
feature_arr = ["num_questions", 
               "num_periods",
               "num_commas",
               "num_exclam",
               "num_quotes",
               "num_colon",
               "num_stops",
               "num_semicolon",
               "num_words",
               "num_chars",
               "num_diff_words",
               "avg_word_len",
               "polarity"
              ]
feature_arr.extend(POS_NAMES.keys())<jupyter_output><empty_output><jupyter_text>모델새로운 특성을 추가했으니 새 모델을 훈련해 보겠습니다. 이전과 동일한 구조를 사용하지만 새로운 특성을 사용합니다. 아래 새로운 특성을 출력해 보겠습니다.<jupyter_code># 추가된 특성이 있으므로 데이터를 다시 분할합니다.
train_df, test_df = get_split_by_author(df, test_size=0.2, random_state=40)
df[feature_arr].head()*100
def get_feature_vector_and_label(df, feature_names):
    """
    벡터 특성과 특성 이름으로 입력과 출력 벡터를 생성합니다.
    :param df: 입력 DataFrame
    :param feature_names: (벡터가 아닌) 특성 열의 이름 
    :return: 특성 배열과 레이블 배열
    """
    features = df[feature_names].astype(float)
    labels = df["Score"] > df["Score"].median()
    return features, labels

X_train, y_train = get_feature_vector_and_label(train_df, feature_arr)
X_test, y_test = get_feature_vector_and_label(test_df, feature_arr)
y_train.value_counts()
X_test.shape<jupyter_output><empty_output><jupyter_text>`sklearn`을 사용해 모델을 훈련하고 이전에 설명한 방법으로 성능을 측정합니다.<jupyter_code>clf = RandomForestClassifier(n_estimators=100, class_weight='balanced', oob_score=True)
clf.fit(X_train, y_train)

y_predicted = clf.predict(X_test)
y_predicted_proba = clf.predict_proba(X_test)
def get_metrics(y_test, y_predicted):  
    # 진짜 양성 / (진짜 양성 + 가짜 양성)
    precision = precision_score(y_test, y_predicted, pos_label=True,
                                    average='binary')             
    # 진짜 양성 / (진짜 양성 + 가짜 음성)
    recall = recall_score(y_test, y_predicted, pos_label=True,
                              average='binary')
    
    # 정밀도와 재현율의 조화 평균
    f1 = f1_score(y_test, y_predicted, pos_label=True, average='binary')
    
    # 진짜 양성 + 진짜 음성 / 전체
    accuracy = accuracy_score(y_test, y_predicted)
    return accuracy, precision, recall, f1


# 훈련 정확도
# https://datascience.stackexchange.com/questions/13151/randomforestclassifier-oob-scoring-method 참조
y_train_pred = np.argmax(clf.oob_decision_function_,axis=1)

accuracy, precision, recall, f1 = get_metrics(y_train, y_train_pred)
print("훈련 정확도 = %.3f, 정밀도 = %.3f, 재현율 = %.3f, f1 = %.3f" % (accuracy, precision, recall, f1))
accuracy, precision, recall, f1 = get_metrics(y_test, y_predicted)
print("검증 정확도 = %.3f, 정밀도 = %.3f, 재현율 = %.3f, f1 = %.3f" % (accuracy, precision, recall, f1))<jupyter_output>검증 정확도 = 0.578, 정밀도 = 0.591, 재현율 = 0.449, f1 = 0.510<jupyter_text>새로운 모델과 벡터화 객체를 나중에 사용하기 위해 디스크에 저장합니다.<jupyter_code>model_path = Path("../models/model_3.pkl")
joblib.dump(clf, model_path)<jupyter_output><empty_output><jupyter_text>특성의 유용성 검증하기새로운 특성을 모델이 사용하는 확인하기 위해 특성 중요도를 살펴 보겠습니다.<jupyter_code>all_feature_names = np.array(feature_arr)
k = 20
print("상위 %s개 중요도:\n" % k)
print('\n'.join(["%s: %.2g" % (tup[0], tup[1]) for tup in get_feature_importance(clf, all_feature_names)[:k]]))

print("\n하위 %s개 중요도:\n" % k)
print('\n'.join(["%s: %.2g" % (tup[0], tup[1]) for tup in get_feature_importance(clf, all_feature_names)[-k:]]))<jupyter_output>상위 20개 중요도:

num_chars: 0.05
num_periods: 0.048
num_questions: 0.048
ADJ: 0.047
AUX: 0.046
num_diff_words: 0.045
ADV: 0.044
DET: 0.044
PUNCT: 0.043
ADP: 0.042
num_stops: 0.041
NOUN: 0.041
PART: 0.041
num_commas: 0.04
VERB: 0.04
PRON: 0.04
SCONJ: 0.04
num_words: 0.039
polarity: 0.038
avg_word_len: 0.038

하위 20개 중요도:

num_stops: 0.041
NOUN: 0.041
PART: 0.041
num_commas: 0.04
VERB: 0.04
PRON: 0.04
SCONJ: 0.04
num_words: 0.039
polarity: 0.038
avg_word_len: 0.038
PROPN: 0.031
NUM: 0.024
num_colon: 0.022
num_quotes: 0.018
SYM: 0.014
INTJ: 0.011
X: 0.011
num_semicolon: 0.0071
num_exclam: 0.0064
CONJ: 0<jupyter_text>예측과 데이터 비교하기이 섹션은 새로운 모델로 데이터와 예측 비교하기 [노트북](https://github.com/rickiepark/ml-powered-applications/blob/master/notebooks/comparing_data_to_predictions.ipynb)에서 설명한 평가 방법을 사용합니다.<jupyter_code>get_roc_plot(y_predicted_proba[:,1], y_test, figsize=(10,10))
get_confusion_matrix_plot(y_predicted, y_test, figsize=(9, 9))
get_calibration_plot(y_predicted_proba[:,1], y_test, figsize=(9,9))<jupyter_output>No handles with labels found to put in legend.<jupyter_text>세 번째 모델은 이해하기 쉬운 특성만 사용하고 이전 모델보다 더 보정이 잘 되어 있습니다. 이 애플리케이션에 매우 좋은 후보 모델입니다(자세한 비교 내용은 `Comparing Models`을 참고하세요).  추론 함수이전 두 개의 모델과 마찬가지로 임의의 질문을 받고 높은 점수를 받을 추정 확률을 출력하는 추론 함수를 정의합니다.<jupyter_code>pos_prob = get_question_score_from_input("""
When quoting a person's informal speech, how much liberty do you have to make changes to what they say?
""")

print("%s probability of the question receiving a high score according to our model" % (pos_prob))<jupyter_output>100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 106.22it/s]
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 928.56it/s]
{'fraction_non_alphanumeric': 0.1496294008647313, 'fraction_numerical': 0.04076590487955528, 'mean_word_length': 4.309016393442623, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 30, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '193862', 'license': 'MIT', 'n_tokens_mistral': 3442, 'n_tokens_neox': 3516, 'n_words': 575, 'path': 'notebooks/third_model.ipynb', 'repo_name': 'rickiepark/mlpa', 'size': '6476'}