↓ メインコンテンツへスキップ
  1. 第09講 機械学習(2/3; 教師あり学習)/

決定木(Decision Tree)

分類
#

線形回帰では、連続した値を予測する問題を扱いました。 しかし、世の中には連続した値ではなく、ある集合の中のいずれかの値を予測する問題もあります。 例えば、性別、病気の有無、天気などが挙げられます。 このような問題は、分類(classification)という問題になります。

なお、合格か不合格かや、病気の有無など、予測する値が2つの値の場合は、2値分類(binary classification)と呼び、 3つ以上の値の場合は、多値分類(multiclass classification)と呼びます。

分類にも以下のように多くの手法がありますが、ここではその中でも決定木(Decision Tree)による分類を学びます。

  • 決定木(Decision Tree)
  • ランダムフォレスト(Random Forest)
  • サポートベクターマシン(Support Vector Machine)
  • ニューラルネットワーク(Neural Network)

決定木(Decision Tree)
#

決定木の例として、例題09-1: 最終成績を予測する。を考えます。 例題09-1 では、最終成績を予測しましたが、合格したか否かを予測する問題に変更します。

predict_score.py をコピーし、predict_pass.py として保存します。 そして、read_csv 関数を以下のように変更します。

def read_csv(filename):
    x = []
    y = []
    with open(filename, "r") as f:
        f.readline()    # ヘッダ行を読み飛ばす。
        for line in f:
            cols = line.strip().split(",") # 改行を削除し、コンマで分割する。
            score = float(cols[1])
            if score >= 60:
                y.append("合格")   # 目的変数(最終成績)を追加する。
            else:
                y.append("不合格")
            # 説明変数を追加する。
            x.append([float(cols[2]), float(cols[3]), float(cols[4]),\
                float(cols[5]), float(cols[6])]) 
    return x, y

続いて、LinearRegression() を DecisionTreeClassifier(random_state=2) に変更します。 なお、LinearRegressionはsklearn.linear_modelモジュールにありますが、 DecisionTreeClassifierはsklearn.treeモジュールにあります。 決定木の構築にも乱数が使われるため、random_state を指定すると毎回同じ木が得られます。 加えて、回帰モデルの評価(MAE, MSE, RMSE, R2)は利用できませんので、削除してください。

実行すると、次の結果が得られます(train_test_split と DecisionTreeClassifier の random_state を外すと、実行のたびに結果が変わります)。

python3 predict_pass.py score.csv
学習データに対するモデルの精度: 1.0
テストデータに対するモデルの精度: 0.9074074074074074
目的変数がカテゴリ値のとき、線形回帰は用いるとどうなる?

なお、read_csv を上記のように変更し、目的変数をカテゴリ変数に変更したときに LinearRegression を利用すると、実行時に以下のようなエラーが発生します。

Traceback (most recent call last):
  File "predict_pass.py", line 31, in <module>
    model.fit(x_train, y_train)
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.13/site-packages/sklearn/base.py", line 1389, in wrapper
    return fit_method(estimator, *args, **kwargs)
  File "/opt/homebrew/lib/python3.13/site-packages/sklearn/linear_model/_base.py", line 622, in fit
    X, y, X_offset, y_offset, X_scale = _preprocess_data(
                                        ~~~~~~~~~~~~~~~~^
        X,
        ^^
    ...<3 lines>...
        sample_weight=sample_weight,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/opt/homebrew/lib/python3.13/site-packages/sklearn/linear_model/_base.py", line 169, in _preprocess_data
    y = check_array(y, dtype=X.dtype, copy=copy_y, ensure_2d=False)
  File "/opt/homebrew/lib/python3.13/site-packages/sklearn/utils/validation.py", line 1055, in check_array
    array = _asarray_with_order(array, order=order, dtype=dtype, xp=xp)
  File "/opt/homebrew/lib/python3.13/site-packages/sklearn/utils/_array_api.py", line 832, in _asarray_with_order
    array = numpy.asarray(array, order=order, dtype=dtype)
ValueError: could not convert string to float: np.str_('合格')

逆に、連続変数の目的変数を分類に用いてもエラーは発生しません。ただし、精度は著しく低くなります。

分類モデルの評価
#

分類においても、学習したモデルがどの程度の成果であるのかを評価するための指標があります。 正解率、適合率、再現率、F1値、混同行列などがあり、それぞれ次のように得られます。 これらの指標は、sklearn.metrics モジュールに用意されています。

print(f"正解率 {metrics.accuracy_score(y_test, y_pred)}")
print(f"適合率 {metrics.precision_score(y_test, y_pred, pos_label='合格')}")
print(f"再現率 {metrics.recall_score(y_test, y_pred, pos_label='合格')}")
print(f"F1値  {metrics.f1_score(y_test, y_pred, pos_label='合格')}")
print("混同行列")
print(metrics.confusion_matrix(y_test, y_pred, labels=["合格", "不合格"]))

実行結果は次の通りです(上と同じ実行で得られたものです)。

正解率 0.9074074074074074
適合率 0.9347826086956522
再現率 0.9555555555555556
F1値 0.945054945054945
混同行列
[[43  2]
 [ 3  6]]
  • 正解率(accuracy)
    • 正しく分類された割合です。上の例では、90.74% が正しく分類されています。
  • 適合率(precision)
    • 陽性と予測されたもののうち、実際に陽性である割合です。精度を表す指標です。上の例では、合格と予測されたもののうち、93.48% が実際に合格でした。
  • 再現率(recall)
    • 実際に陽性であるもののうち、陽性と予測された割合です。網羅性を表す指標です。上の例では、実際の合格者のうち、95.56% が合格と予測されました。
  • F1値(F1-score)
    • 適合率と再現率の調和平均です。
  • 混同行列(confusion matrix)
    • 予測結果と実際の結果を表にしたものです。

ここで、適合率・再現率・F1値の「陽性(positive)」とは、pos_label='合格' で指定した 合格 のことです。

混同行列(confusion matrix)
#

機械学習はどんなに学習しても、100%の精度を出せるわけではありません。 そのため、どのような誤りを犯しているのかを知ることが重要です。 混同行列は、予測結果と実際の結果を表にしたものです。

予測:合格予測:不合格
実際:合格TPFN
実際:不合格FPTN

metrics.confusion_matrix(y_test, y_pred, labels=["合格", "不合格"]) は、行が実際のクラス、列が予測したクラスを、labels で指定した順に並べた行列を返します。 上の実行結果 [[43 2] [3 6]] は、TP = 43、FN = 2、FP = 3、TN = 6 を表しています。

  • TP(True Positive; 真陽性)
    • 実際に合格で、合格と予測された数です。
  • TN(True Negative; 真陰性)
    • 実際に不合格で、不合格と予測された数です。
  • FP(False Positive; 偽陽性)
    • 実際は不合格で、合格と予測された数です。すなわち誤検出された数です。
  • FN(False Negative; 偽陰性)
    • 実際は合格で、不合格と予測された数です。つまり、検出漏れの数を表します。

上の精度、適合率、再現率、F1値は、これらの値をもとに計算されています。

  • 正解率(accuracy)
    • \(\mathrm{accuracy} = \frac{TP + TN}{TP + FP + FN + TN}\)
  • 適合率(precision)
    • \(\mathrm{precision} = \frac{TP}{TP + FP}\)
  • 再現率(recall)
    • \(\mathrm{recall} = \frac{TP}{TP + FN}\)
  • F1値(F1-score)
    • \(\mathrm{F1} = 2 \times \frac{\mathrm{precision} \times \mathrm{recall}}{\mathrm{precision} + \mathrm{recall}}\)

上の実行結果の値で確かめてみましょう。

  • 正解率 \(= \frac{43 + 6}{43 + 3 + 2 + 6} = \frac{49}{54} \approx 0.9074\)
  • 適合率 \(= \frac{43}{43 + 3} = \frac{43}{46} \approx 0.9348\)
  • 再現率 \(= \frac{43}{43 + 2} = \frac{43}{45} \approx 0.9556\)

なお、陽性(positive)・陰性(negative)は、2つのクラスのどちらに注目するかという クラスの呼び方 であり、予測の正誤とは別のものです。 上の例では合格を陽性、不合格を陰性としていますが、不合格を陽性(pos_label='不合格')にしても構いません。 正誤は True/False の方で表され、陽性を陽性と予測した TP も、陰性を陰性と予測した TN も、どちらも正解です。 どちらを陽性にするかによって適合率・再現率・F1値の値は変わりますが、正解率は変わりません。 混同行列を出力するとき、陽性、陰性を labels オプションで指定すると間違いが少なくなるでしょう。