見出し画像

初心者向け:データ分析③



1. はじめに


こんにちは、大学院生のYukiです。

今回はこちらからの引き続きの記事になります。

今までと同様に基本的に図の読み方などは示しますが、ターミナル上に表示される値はこちらに書きませんので、具体的な数値はSpyderやJupyter Notebookなどで自分の手を動かしながら確認をしてみてください。


2. データの前処理をしよう


データ解析にあたりまずはじめに下準備をしましょう。

import numpy as np
import pandas as pd
from ucimlrepo import fetch_ucirepo 
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OrdinalEncoder, LabelEncoder
from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt

import warnings
warnings.filterwarnings("ignore")

本日使用予定のライブラリです。

続いてデータセットですが、今回はUCIの乳がん再発に関するデータを使うのでダウンロードは不要です。(もしUCIがうまく使えない人は最初にこちらからbrest-cancers.dataをダウンロードしてきて過去やったように読み込んでみてください)

注意で書いてある通りなのですが、このデータセットには欠損値が含まれています。

raw_dataset = fetch_ucirepo(id=14)
dataset = pd.concat([raw_dataset.data.features, raw_dataset.data.targets], axis=1)

print('Missing value count: \n', dataset.isnull().sum())

こちらの方法で、どのカラムに欠損値がどれだけ含まれているか確認ができるはずです。

このままデータ解析を始めてしまうとエラーが発生するので、SimpleImputerを使用して最頻値で補完することにします。

imputer = SimpleImputer(strategy='most_frequent')
cancer_data = imputer.fit_transform(dataset)
print(pd.DataFrame(data=cancer_data, columns=dataset.columns).head())

最頻値で補完するという技法は頻繁に使用されるため、できれば覚えておきましょう。

補完し終わったデータがcancer_dataです。最後の行のコードですが、imputer.fit_transform() の結果は NumPy 配列です。この行でパンダスのDataFrameに戻し、最初の五行を表示しています。

次にすべてのカテゴリカルデータを数値に変換します。

enc_X = OrdinalEncoder()

これにより、文字列や他のカテゴリカルデータが機械学習アルゴリズムで使用可能な数値形式に変換されます。

更にcancer_dataのClass列をエンコードします。
LabelEncoderオブジェクトを作成します。

enc_y = LabelEncoder()

これは、カテゴリカルデータを数値に変換するためのエンコーダーです。

上記を使ってXとyを作成します。

target_index=9
X = enc_X.fit_transform(cancer_data[:, :target_index])
y = enc_y.fit_transform(cancer_data[:, target_index])

Xには特徴量(癌に関連する様々な属性)が含まれます。yにはターゲット変数(再発ありかなしかの診断結果)が含まれています。

yについてもっと詳しく解説すればcancer_dataの最初の列(target_index=9)である"Class"列の["no-recurrence-events", "recurrence-events"] を it_transformメソッドを使用して[0, 1] に変換しています。


3. 決定木モデルの訓練をしよう


それでは、モデルの訓練を行いましょう。

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
print("X_train shape: {}".format(X_train.shape))
print("y_train shape: {}".format(y_train.shape))
print("X_test shape: {}".format(X_test.shape))
print("y_test shape: {}".format(y_test.shape))

上記のようにデータセットを分けます。デフォルトでは75%が訓練セット、25%がテストセットになります。(ランダムシード: random_state=42 を設定することで、結果の再現性を確保しています。こちらもよく使われる値です。)

clf_default = DecisionTreeClassifier(random_state=42) 
clf_default.fit(X_train, y_train)

このコードは決定木分類器DecisionTreeClassifierをデフォルト設定で初期化し、訓練させています。決定木は、特徴量に基づいて分岐を繰り返し、最終的な分類を行う機械学習モデルです。

以下でこの決定木を可視化してみましょう

fig, ax = plt.subplots(figsize=(20, 20))
tree.plot_tree(clf_default, max_depth=4, filled=True, fontsize=10, feature_names=dataset.columns[1:], class_names=enc_y.classes_)
plt.show()


こちらが決定木を可視化したものです。上から条件を確認し、オレンジ色は再発ありの患者が多い場合、青は再発なしの患者が多い場合です。図よりリンパ節転移(node-caps)と浸潤リンパ節(inv-nodes)が再発確認において最も重要な役割を示していることがわかります。

ではこのモデルの正確さを確認してみましょう。

y_pred = clf_default.predict(X_test)
accuracy = accuracy_score(y_pred, y_test)
print('The testing accuracy is: %.4f\n' % accuracy)

labels = clf_default.classes_
cm = confusion_matrix(y_pred, y_test)
print(cm)

y_predではテストデータに対する予測を行っていおり、その後予測の正確さを計算しています。

もしターミナルが見えていたら、0.63という中程度の予測性能がでていることが確認できます。
更にconfusion_matrix(y_pred, y_test)では予測と実際の値を比較した混同行列を生成しています。私の方では[[40 17], [9 6]]と結果が出ましたが、この内訳を解説すると40を正しく「再発なし」と予測し、17を「再発なし」を「再発あり」と誤って予測し、9を「再発あり」を「再発なし」と誤って予測し、6を正しく「再発あり」と予測したという結果になります。


fig = plt.figure()
ax = fig.add_subplot()
cax = ax.matshow(cm)
fig.colorbar(cax)
ax.set_xticklabels(['', enc_y.inverse_transform([int(clf_default.classes_[0])])[0], enc_y.inverse_transform([int(clf_default.classes_[1])])[0]])
ax.set_yticklabels(['', enc_y.inverse_transform([int(clf_default.classes_[0])])[0], enc_y.inverse_transform([int(clf_default.classes_[1])])[0]])
plt.title('Confusion matrix on testing data\n')
plt.xlabel('Predicted')
plt.ylabel('True')
plt.show()

モデルは「再発なし」の予測に70%ほどの比較的良い性能を示していますが、「再発あり」の予測性能は40%ほどと低いです。

print("Feature importances:\n{}".format(clf_default.feature_importances_))

上記のコードでは、どの要素が予測に対して良い影響を与えているか可視化することができます。
詳しい値の言及はしませんが、腫瘍の大きさ(tumor-size)と浸潤リンパ節(inv-nodes)が0.2前後で正確さに貢献していることが判明します。
逆に閉経状態(menopause)と悪性度(deg-malig)は”モデルの正確さにおいて”、貢献は少ないようでした。


4. 分割基準のパラメータ調整


それでは以下のコードを記載し、実行をしてみてください。

cv_scores = []
cv_scores_std = []
splitting_criteria = ['gini', 'entropy']
for i in splitting_criteria:
    clf_dtc = DecisionTreeClassifier(criterion=i, random_state=42)
    scores = cross_val_score(clf_dtc, X, y, scoring='accuracy', cv=KFold(n_splits=10, shuffle=True, random_state=42))
    cv_scores.append(scores.mean())
    cv_scores_std.append(scores.std())

plt.bar(splitting_criteria, cv_scores, yerr=cv_scores_std, label='Accuracy')
plt.xlabel('Criterion')
plt.ylim([0, 1])
plt.ylabel('Accuracy')
plt.legend(loc='best')
plt.show()

こちらは決定木アルゴリズムで使用される不純度(impurity)の測定方法であるginiとentropyを比較しています。
一般的に大きく変わることはありませんが、ほんのわずかにですがentropyの方がAccuracyが高そうです。また黒いバー(エラーバー。長いほど、結果のばらつきが大きいことを意味する)もわずかにentropyの方が短いようです。

ほぼ誤差の範囲ですが、決定木の分割基準はentropyを使った方が正確だということがわかります。ただginiの方が処理が高速なので、どちらを使うかはデータセット次第になるかもしれません。



5. 過学習の防止について


モデルを作成している最中は、決定木は全ての葉が正しい答えになるまで成長を続けます。その結果先ほど決定木のプロットで示した通り、トレーニングデータに過剰適合した非常に複雑な決定木が生まれてしまいます。

これを、オーバーフィッティングと呼びます。

この状態を防ぐためには、事前プルーニング(「ツリーの階層構成を複雑になりすぎる前に終了しよう」)という方法とプルーニング(「ツリーを構成してから、情報の少ないノードを折りたたもう」)という方法が存在します。

今回は事前プルーニングを行ってみましょう。こちらでは、ツリーの最
⼤深度を制限したり、最⼤リーフ数を制限したり、ノード内のポイント数を最小にするなど様々なやり方が存在します。

cv_scores = []
cv_scores_std = []
depths = range(1, 21)
for i in depths:
    clf_dtc = DecisionTreeClassifier(max_depth=i, random_state=42)
    scores = cross_val_score(clf_dtc, X, y, scoring='accuracy', cv=KFold(n_splits=10, shuffle=True, random_state=42))
    cv_scores.append(scores.mean())
    cv_scores_std.append(scores.std())
plt.errorbar(depths, cv_scores, yerr=cv_scores_std, marker='x', label='Accuracy')
plt.xlabel('Tree depth')
plt.xticks(np.arange(0, 21, step=2))
plt.ylim(0.4, 0.9)
plt.ylabel('Accuracy')
plt.legend(loc='best')
plt.show()

こちらでは決定木の階層を1から20に設定し、10分割の交差検証を行い、精度スコアを計算しています。x軸が木の深さ、y軸が精度、縦棒がエラーバーを示しています。

この調査の結果、2〜3層までは精度が上がっているものの、4層目からはオーバーフィッティングが発生し精度が下がり続けているようです。

つまりmax_depthパラメータを2〜3に設定することで、75%前後の正確さの決定木モデルを作成することができるため、そのようにパラメーターを調整することが推奨されます。

このような視覚化は決定木の重要なハイパーパラメータである最大深さの最適値を発見したり、複雑さと性能のトレードオフを視覚的に理解するのに非常に役に立ちます。


6. 最後に


今回は決定木を使用したモデル構築の解説を行いました。

次回はデータ分析界のHello World、Iris(あやめ)を用いたデータセットを使用してKNN 分類器の解説を行いたいと思います。

最後まで読んでいただき、ありがとうございました。

Yuki


この記事が気に入ったらサポートをしてみませんか?