pythonの混同行列(Confusion Matrix)を使いこなす

Confusion matrixの使い方

最近久しぶりにpythonで混同行列(sklearn.metrics.confusion_matrix)を利用しました。

個人的にlabels引数の指定は非常に重要だと思っていますが、labels引数の設定方法などをすっかり忘れてしまっていたので、勉強がてら使い方をメモしておきます。

ラベル数が2のとき、3以上のときで分けています。

実行環境

  • Windows10, WSL2, Ubuntu 20.04
  • scikit-learn: 0.24.1

混同行列(Confusion Matrix)とは

主に分類問題で予測モデルの分類精度を示すときに利用されます。下記の行列です。

予測ラベル
Positive Negative
正解ラベル Positive TP (True Positive) FN (False Negative)
Negative FP (False Positive) TN (True Negative)

TP, FP, FN, TNにはそれぞれ数値が入ります。それぞれの意味はここでは触れませんが、予測モデルの性能評価のときに利用する数値です。

この混同行列を作成するために、scikit-learnからconfusion_matrix関数が提供されているので、それを利用します。

正解ラベルが2つのとき

正解ラベルが2つ(2値分類問題)のときの基本的なconfusion_matrixの使い方をまとめます。

基本的な使い方

まず、単純に混同行列(numpy.ndarray)を求める方法です。

from sklearn.metrics import confusion_matrix

y_true = [0, 1, 1, 1, 1, 0, 0, 0, 0, 1] # 正解ラベル
y_pred = [0, 0, 0, 1, 1, 0, 1, 0, 0, 1] # 予測ラベル
cm = confusion_matrix(y_true, y_pred) # 混同行列(numpy.ndarray)の取得

# print(cm)
# [[4 1]
#  [2 3]]

非常に簡単ですね。正解ラベルリスト(y_true)と予測ラベルリスト(y_pred)を用意すれば混同行列を作成可能です。

一方で、上記のように取得できる混同行列がndarray型だと、どのラベルがどの行・列に対応しているのかがわからないですよね。

そこで、ラベルの順番を指定して出力時にラベル名を付与することで、結果をわかりやすくします。

行・列にラベル名を付与する

行・列にラベル名を付与して結果をわかりやすくしてみます。

from sklearn.metrics import confusion_matrix
import pandas as pd

y_true = [0, 1, 1, 1, 1, 0, 0, 0, 0, 1]  # 正解ラベル
y_pred = [0, 0, 0, 1, 1, 0, 1, 0, 0, 1]  # 予測ラベル
labels = [1, 0]  # ラベルの順序を指定
cm = confusion_matrix(y_true, y_pred, labels=labels)  # 混同行列の取得&ラベル順序指定

# 綺麗に出力
columns_labels = ["pred_" + str(l) for l in labels]
index_labels = ["act_" + str(l) for l in labels]
cm = pd.DataFrame(cm,
                  columns=columns_labels, index=index_labels)

# print(cm.to_markdown())
# |       |   pred_1 |   pred_0 |
# |:------|---------:|---------:|
# | act_1 |        3 |        2 |
# | act_0 |        1 |        4 |

各行・列とラベルの関係がわかるようになりました。ポイントはlabelsでラベルの順序を指定することと、dataframe型でindexとcolumnsを指定することです。

特に、ラベルの順序を指定することは非常に重要です。指定されていないと、どの数値が何を示しているかがわからなくなりますからね。

正解ラベルが3つ以上あるとき

正解ラベルが3つ以上(多値分類問題)のときのconfusion_matrixの使い方をまとめます。

基本的な使い方

labelsに3つ以上を指定するだけで、多値分類に対応可能です。非常に簡単ですね。

from sklearn.metrics import confusion_matrix
import pandas as pd

y_true = [2, 1, 1, 2, 1, 0, 2, 0, 0, 1, 0, 2]  # 正解ラベル
y_pred = [2, 0, 0, 1, 1, 0, 2, 2, 0, 1, 0, 1]  # 予測ラベル
labels = [0, 1, 2]  # ラベルの順序を指定
cm = confusion_matrix(y_true, y_pred, labels=labels) 

columns_labels = ["pred_" + str(l) for l in labels]
index_labels = ["act_" + str(l) for l in labels]
cm = pd.DataFrame(cm,
                  columns=columns_labels, index=index_labels)

# print(cm.to_markdown())
# |       |   pred_0 |   pred_1 |   pred_2 |
# |:------|---------:|---------:|---------:|
# | act_0 |        3 |        0 |        1 |
# | act_1 |        2 |        2 |        0 |
# | act_2 |        0 |        2 |        2 |

confusion_matrixのlabels引数を深堀してみる

confusion_matrix関数のlabelsの指定方法を変更してみて、理解を深めます。

今回はlabelsに指定するラベルを”2″と”0″のみにしてみます。

...
labels = [2, 0] # ラベルの順序を指定
...

print(cm.to_markdown())
# |       |   pred_2 |   pred_0 |
# |:------|---------:|---------:|
# | act_2 |        2 |        0 |
# | act_0 |        1 |        3 |

結果に、”2″と”0″ラベルのみが表示されており、指定した順序に列が並んでいることが確認できます。labelsに指定したラベルだけが混同行列の対象になるということです。

個人的なおすすめのlabels指定方法

labels引数は、どの行・列がどのラベルに対応しているかを明示的に指定できるので、必ず指定した方が良いです。

加えて、個人的に重要だと思うことは、y_trueとy_predに含まれているラベルはすべてlabelsに指定するべきということです。つまり、下記のコードのようにlabelsを取得するべきです。

labels = sorted(list(set(y_true) | set(y_pred)))
print(labels)
# [0, 1, 2]

こうすることで、混同行列の正解ラベル・予測ラベルに漏れがなくなり、予測モデルが変な挙動をしていたらすぐに気が付つくことができます。

まとめ

scikit-learnのconfusion_matrixの使い方についてまとめてみました。

重要だと思うポイントは下記です。

  • labels引数を必ず指定する
  • labels引数は正解ラベルリスト、予測ラベルリストから作成する

scikit-learnには、confusion_matrixのほかにも正答率の計算など便利な関数が複数あるので、いつかそれらもまとめようと思います。

おまけ

いきなり、to_markdown()関数を利用していましたが、これはtabulateパッケージをインストールすることで利用可能になります。

pip install tabulate

markdownとの連携も簡単ですし結果も見やすくなるので、コンソールでpythonのプログラムを実行するときにおすすめです。