Как написать путаницу в Python? - PullRequest
44 голосов
/ 27 января 2010

Я написал код вычисления матрицы путаницы в Python:

def conf_mat(prob_arr, input_arr):
        # confusion matrix
        conf_arr = [[0, 0], [0, 0]]

        for i in range(len(prob_arr)):
                if int(input_arr[i]) == 1:
                        if float(prob_arr[i]) < 0.5:
                                conf_arr[0][1] = conf_arr[0][1] + 1
                        else:
                                conf_arr[0][0] = conf_arr[0][0] + 1
                elif int(input_arr[i]) == 2:
                        if float(prob_arr[i]) >= 0.5:
                                conf_arr[1][0] = conf_arr[1][0] +1
                        else:
                                conf_arr[1][1] = conf_arr[1][1] +1

        accuracy = float(conf_arr[0][0] + conf_arr[1][1])/(len(input_arr))

prob_arr - это массив, который вернул мой классификационный код, и образец массива выглядит так:

 [1.0, 1.0, 1.0, 0.41592955657342651, 1.0, 0.0053405015805891975, 4.5321494433440449e-299, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.70943426182688163, 1.0, 1.0, 1.0, 1.0]

input_arr isисходные метки классов для набора данных, и это выглядит так:

[2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1]

Что мой код пытается сделать, это: я получаю prob_arr и input_arr и для каждого класса (1 и 2) я проверяю, если онинеправильно классифицировано или нет.

Но мой код работает только для двух классов.Если я запускаю этот код для нескольких классифицированных данных, он не работает.Как я могу сделать это для нескольких классов?

Например, для набора данных с тремя классами он должен вернуть: [[21,7,3],[3,38,6],[5,4,19]]

Ответы [ 14 ]

124 голосов
/ 26 апреля 2015

Scikit-Learn предоставляет confusion_matrix функцию

from sklearn.metrics import confusion_matrix
y_actu = [2, 0, 2, 2, 0, 1, 1, 2, 2, 0, 1, 2]
y_pred = [0, 0, 2, 1, 0, 2, 1, 0, 2, 0, 2, 2]
confusion_matrix(y_actu, y_pred)

которые выводят массив Numpy

array([[3, 0, 0],
       [0, 1, 2],
       [2, 1, 3]])

Но вы также можете создать путаницу с помощью панд:

import pandas as pd
y_actu = pd.Series([2, 0, 2, 2, 0, 1, 1, 2, 2, 0, 1, 2], name='Actual')
y_pred = pd.Series([0, 0, 2, 1, 0, 2, 1, 0, 2, 0, 2, 2], name='Predicted')
df_confusion = pd.crosstab(y_actu, y_pred)

Вы получите (красиво помеченный) Pandas DataFrame:

Predicted  0  1  2
Actual
0          3  0  0
1          0  1  2
2          2  1  3

Если вы добавите margins=True как

df_confusion = pd.crosstab(y_actu, y_pred, rownames=['Actual'], colnames=['Predicted'], margins=True)

вы также получите сумму для каждой строки и столбца:

Predicted  0  1  2  All
Actual
0          3  0  0    3
1          0  1  2    3
2          2  1  3    6
All        5  2  5   12

Вы также можете получить нормализованную матрицу путаницы, используя:

df_conf_norm = df_confusion / df_confusion.sum(axis=1)

Predicted         0         1         2
Actual
0          1.000000  0.000000  0.000000
1          0.000000  0.333333  0.333333
2          0.666667  0.333333  0.500000

Вы можете построить этот confusion_matrix, используя

import matplotlib.pyplot as plt
def plot_confusion_matrix(df_confusion, title='Confusion matrix', cmap=plt.cm.gray_r):
    plt.matshow(df_confusion, cmap=cmap) # imshow
    #plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(df_confusion.columns))
    plt.xticks(tick_marks, df_confusion.columns, rotation=45)
    plt.yticks(tick_marks, df_confusion.index)
    #plt.tight_layout()
    plt.ylabel(df_confusion.index.name)
    plt.xlabel(df_confusion.columns.name)

plot_confusion_matrix(df_confusion)

plot confusion matrix

Или построить нормализованную матрицу путаницы, используя:

plot_confusion_matrix(df_conf_norm)  

plot confusion matrix normalized

Вам также может быть интересен этот проект https://github.com/pandas-ml/pandas-ml и его пакет Pip https://pypi.python.org/pypi/pandas_ml

С этим пакетом запутанная матрица может быть довольно распечатана, сюжетно. Вы можете преобразовать путаницу в матрицу, получить статистику класса, такую ​​как TP, TN, FP, FN, ACC, TPR, FPR, FNR, TNR (SPC), LR +, LR-, DOR, PPV, FDR, FOR, NPV и некоторые общие статистика

In [1]: from pandas_ml import ConfusionMatrix
In [2]: y_actu = [2, 0, 2, 2, 0, 1, 1, 2, 2, 0, 1, 2]
In [3]: y_pred = [0, 0, 2, 1, 0, 2, 1, 0, 2, 0, 2, 2]
In [4]: cm = ConfusionMatrix(y_actu, y_pred)
In [5]: cm.print_stats()
Confusion Matrix:

Predicted  0  1  2  __all__
Actual
0          3  0  0        3
1          0  1  2        3
2          2  1  3        6
__all__    5  2  5       12


Overall Statistics:

Accuracy: 0.583333333333
95% CI: (0.27666968568210581, 0.84834777019156982)
No Information Rate: ToDo
P-Value [Acc > NIR]: 0.189264302376
Kappa: 0.354838709677
Mcnemar's Test P-Value: ToDo


Class Statistics:

Classes                                        0          1          2
Population                                    12         12         12
P: Condition positive                          3          3          6
N: Condition negative                          9          9          6
Test outcome positive                          5          2          5
Test outcome negative                          7         10          7
TP: True Positive                              3          1          3
TN: True Negative                              7          8          4
FP: False Positive                             2          1          2
FN: False Negative                             0          2          3
TPR: (Sensitivity, hit rate, recall)           1  0.3333333        0.5
TNR=SPC: (Specificity)                 0.7777778  0.8888889  0.6666667
PPV: Pos Pred Value (Precision)              0.6        0.5        0.6
NPV: Neg Pred Value                            1        0.8  0.5714286
FPR: False-out                         0.2222222  0.1111111  0.3333333
FDR: False Discovery Rate                    0.4        0.5        0.4
FNR: Miss Rate                                 0  0.6666667        0.5
ACC: Accuracy                          0.8333333       0.75  0.5833333
F1 score                                    0.75        0.4  0.5454545
MCC: Matthews correlation coefficient  0.6831301  0.2581989  0.1690309
Informedness                           0.7777778  0.2222222  0.1666667
Markedness                                   0.6        0.3  0.1714286
Prevalence                                  0.25       0.25        0.5
LR+: Positive likelihood ratio               4.5          3        1.5
LR-: Negative likelihood ratio                 0       0.75       0.75
DOR: Diagnostic odds ratio                   inf          4          2
FOR: False omission rate                       0        0.2  0.4285714

Я заметил, что новая библиотека Python для Confusion Matrix с именем PyCM отсутствует: возможно, вы можете посмотреть.

13 голосов
/ 11 января 2013

Scikit-learn (который я рекомендую использовать в любом случае) включен в модуль metrics:

>>> from sklearn.metrics import confusion_matrix
>>> y_true = [0, 1, 2, 0, 1, 2, 0, 1, 2]
>>> y_pred = [0, 0, 0, 0, 1, 1, 0, 2, 2]
>>> confusion_matrix(y_true, y_pred)
array([[3, 0, 0],
       [1, 1, 1],
       [1, 1, 1]])
10 голосов
/ 22 мая 2015

Если вы не хотите научиться делать это за вас ...

    import numpy
    actual = numpy.array(actual)
    predicted = numpy.array(predicted)

    # calculate the confusion matrix; labels is numpy array of classification labels
    cm = numpy.zeros((len(labels), len(labels)))
    for a, p in zip(actual, predicted):
        cm[a][p] += 1

    # also get the accuracy easily with numpy
    accuracy = (actual == predicted).sum() / float(len(actual))

Или взгляните на более полную реализацию здесь, в NLTK .

9 голосов
/ 04 января 2018

Прошло почти десятилетие, но решения (без sklearn) на этот пост замысловаты и излишне длинны. Вычисление матрицы путаницы может быть сделано в Python аккуратно в несколько строк. Например:

import numpy as np

def compute_confusion_matrix(true, pred):
  '''Computes a confusion matrix using numpy for two np.arrays
  true and pred.

  Results are identical (and similar in computation time) to: 
    "from sklearn.metrics import confusion_matrix"

  However, this function avoids the dependency on sklearn.'''

  K = len(np.unique(true)) # Number of classes 
  result = np.zeros((K, K))

  for i in range(len(true)):
    result[true[i]][pred[i]] += 1

  return result
3 голосов
/ 27 января 2010

Эта функция создает матрицы путаницы для любого количества классов.

def create_conf_matrix(expected, predicted, n_classes):
    m = [[0] * n_classes for i in range(n_classes)]
    for pred, exp in zip(predicted, expected):
        m[pred][exp] += 1
    return m

def calc_accuracy(conf_matrix):
    t = sum(sum(l) for l in conf_matrix)
    return sum(conf_matrix[i][i] for i in range(len(conf_matrix))) / t

В отличие от вашей функции, описанной выше, вы должны извлечь прогнозируемые классы перед вызовом функции на основе результатов вашей классификации, т.е. как

[1 if p < .5 else 2 for p in classifications]
2 голосов
/ 28 января 2010

Вот класс матрицы путаницы, который поддерживает симпатичную печать, и т. Д .:

http://nltk.googlecode.com/svn/trunk/doc/api/nltk.metrics.confusionmatrix-pysrc.html

1 голос
/ 30 декабря 2017

Обновление

С момента написания этого поста я обновил свою библиотечную реализацию, добавив в нее несколько других приятных функций. Как и в приведенном ниже коде, никаких сторонних зависимостей не требуется. Класс также может выводить хорошую таблицу табулирования, похожую на многие часто используемые статистические пакеты. Смотрите это Суть .


Простая мультиклассовая реализация

Мультиклассовая запутанная матрица может быть невероятно просто вычислена с помощью ванильного Python примерно за O (N) время. Все, что нам нужно сделать, это объединить уникальные классы, найденные в векторе actual, в двумерный список. Оттуда мы просто перебираем сжатые векторы actual и predicted и заполняем счетчики.

# A Simple Confusion Matrix Implementation
def confusionmatrix(actual, predicted, normalize = False):
    """
    Generate a confusion matrix for multiple classification
    @params:
        actual      - a list of integers or strings for known classes
        predicted   - a list of integers or strings for predicted classes
        normalize   - optional boolean for matrix normalization
    @return:
        matrix      - a 2-dimensional list of pairwise counts
    """
    unique = sorted(set(actual))
    matrix = [[0 for _ in unique] for _ in unique]
    imap   = {key: i for i, key in enumerate(unique)}
    # Generate Confusion Matrix
    for p, a in zip(predicted, actual):
        matrix[imap[p]][imap[a]] += 1
    # Matrix Normalization
    if normalize:
        sigma = sum([sum(matrix[imap[i]]) for i in unique])
        matrix = [row for row in map(lambda i: list(map(lambda j: j / sigma, i)), matrix)]
    return matrix

Использование

# Input Below Should Return: [[2, 1, 0], [0, 2, 1], [1, 2, 1]]
cm = confusionmatrix(
    [1, 1, 2, 0, 1, 1, 2, 0, 0, 1], # actual
    [0, 1, 1, 0, 2, 1, 2, 2, 0, 2]  # predicted
)

# And The Output
print(cm)
[[2, 1, 0], [0, 2, 1], [1, 2, 1]]

Примечание: классы actual расположены вдоль столбцов, а классы predicted - вдоль строк.

# Actual
# 0  1  2
  #  #  #   
[[2, 1, 0], # 0
 [0, 2, 1], # 1  Predicted
 [1, 2, 1]] # 2

Имена классов могут быть строками или целыми числами

# Input Below Should Return: [[2, 1, 0], [0, 2, 1], [1, 2, 1]]
cm = confusionmatrix(
    ["B", "B", "C", "A", "B", "B", "C", "A", "A", "B"], # actual
    ["A", "B", "B", "A", "C", "B", "C", "C", "A", "C"]  # predicted
)

# And The Output
print(cm)
[[2, 1, 0], [0, 2, 1], [1, 2, 1]]

Вы также можете вернуть матрицу с пропорциями (нормализация)

# Input Below Should Return: [[0.2, 0.1, 0.0], [0.0, 0.2, 0.1], [0.1, 0.2, 0.1]]
cm = confusionmatrix(
    ["B", "B", "C", "A", "B", "B", "C", "A", "A", "B"], # actual
    ["A", "B", "B", "A", "C", "B", "C", "C", "A", "C"], # predicted
    normalize = True
)

# And The Output
print(cm)
[[0.2, 0.1, 0.0], [0.0, 0.2, 0.1], [0.1, 0.2, 0.1]]

Извлечение статистики из матрицы путаницы множественной классификации

Когда у вас есть матрица, вы можете вычислить кучу статистических данных для оценки вашего классификатора. Тем не менее, извлечение значений из настройки матрицы путаницы для множественной классификации может быть немного головной болью. Вот функция, которая возвращает и матрицу путаницы, и статистику по классам:

# Not Required, But Nice For Legibility
from collections import OrderedDict

# A Simple Confusion Matrix Implementation
def confusionmatrix(actual, predicted, normalize = False):
    """
    Generate a confusion matrix for multiple classification
    @params:
        actual      - a list of integers or strings for known classes
        predicted   - a list of integers or strings for predicted classes
    @return:
        matrix      - a 2-dimensional list of pairwise counts
        statistics  - a dictionary of statistics for each class
    """
    unique = sorted(set(actual))
    matrix = [[0 for _ in unique] for _ in unique]
    imap   = {key: i for i, key in enumerate(unique)}
    # Generate Confusion Matrix
    for p, a in zip(predicted, actual):
        matrix[imap[p]][imap[a]] += 1
    # Get Confusion Matrix Sum
    sigma = sum([sum(matrix[imap[i]]) for i in unique])
    # Scaffold Statistics Data Structure
    statistics = OrderedDict(((i, {"counts" : OrderedDict(), "stats" : OrderedDict()}) for i in unique))
    # Iterate Through Classes & Compute Statistics
    for i in unique:
        loc = matrix[imap[i]][imap[i]]
        row = sum(matrix[imap[i]][:])
        col = sum([row[imap[i]] for row in matrix])
        # Get TP/TN/FP/FN
        tp  = loc
        fp  = row - loc
        fn  = col - loc
        tn  = sigma - row - col + loc
        # Populate Counts Dictionary
        statistics[i]["counts"]["tp"]   = tp
        statistics[i]["counts"]["fp"]   = fp
        statistics[i]["counts"]["tn"]   = tn
        statistics[i]["counts"]["fn"]   = fn
        statistics[i]["counts"]["pos"]  = tp + fn
        statistics[i]["counts"]["neg"]  = tn + fp
        statistics[i]["counts"]["n"]    = tp + tn + fp + fn
        # Populate Statistics Dictionary
        statistics[i]["stats"]["sensitivity"]   = tp / (tp + fn) if tp > 0 else 0.0
        statistics[i]["stats"]["specificity"]   = tn / (tn + fp) if tn > 0 else 0.0
        statistics[i]["stats"]["precision"]     = tp / (tp + fp) if tp > 0 else 0.0
        statistics[i]["stats"]["recall"]        = tp / (tp + fn) if tp > 0 else 0.0
        statistics[i]["stats"]["tpr"]           = tp / (tp + fn) if tp > 0 else 0.0
        statistics[i]["stats"]["tnr"]           = tn / (tn + fp) if tn > 0 else 0.0
        statistics[i]["stats"]["fpr"]           = fp / (fp + tn) if fp > 0 else 0.0
        statistics[i]["stats"]["fnr"]           = fn / (fn + tp) if fn > 0 else 0.0
        statistics[i]["stats"]["accuracy"]      = (tp + tn) / (tp + tn + fp + fn) if (tp + tn) > 0 else 0.0
        statistics[i]["stats"]["f1score"]       = (2 * tp) / ((2 * tp) + (fp + fn)) if tp > 0 else 0.0
        statistics[i]["stats"]["fdr"]           = fp / (fp + tp) if fp > 0 else 0.0
        statistics[i]["stats"]["for"]           = fn / (fn + tn) if fn > 0 else 0.0
        statistics[i]["stats"]["ppv"]           = tp / (tp + fp) if tp > 0 else 0.0
        statistics[i]["stats"]["npv"]           = tn / (tn + fn) if tn > 0 else 0.0
    # Matrix Normalization
    if normalize:
        matrix = [row for row in map(lambda i: list(map(lambda j: j / sigma, i)), matrix)]
    return matrix, statistics

Компьютерная статистика

Выше матрица смешения используется для табулирования статистики для каждого класса, которая возвращается в OrderedDict со следующей структурой:

OrderedDict(
    [
        ('A', {
            'stats' : OrderedDict([
                ('sensitivity', 0.6666666666666666), 
                ('specificity', 0.8571428571428571), 
                ('precision', 0.6666666666666666), 
                ('recall', 0.6666666666666666), 
                ('tpr', 0.6666666666666666), 
                ('tnr', 0.8571428571428571), 
                ('fpr', 0.14285714285714285), 
                ('fnr', 0.3333333333333333), 
                ('accuracy', 0.8), 
                ('f1score', 0.6666666666666666), 
                ('fdr', 0.3333333333333333), 
                ('for', 0.14285714285714285), 
                ('ppv', 0.6666666666666666), 
                ('npv', 0.8571428571428571)
            ]), 
            'counts': OrderedDict([
                ('tp', 2), 
                ('fp', 1), 
                ('tn', 6), 
                ('fn', 1), 
                ('pos', 3), 
                ('neg', 7), 
                ('n', 10)
            ])
        }), 
        ('B', {
            'stats': OrderedDict([
                ('sensitivity', 0.4), 
                ('specificity', 0.8), 
                ('precision', 0.6666666666666666), 
                ('recall', 0.4), 
                ('tpr', 0.4), 
                ('tnr', 0.8), 
                ('fpr', 0.2), 
                ('fnr', 0.6), 
                ('accuracy', 0.6), 
                ('f1score', 0.5), 
                ('fdr', 0.3333333333333333), 
                ('for', 0.42857142857142855), 
                ('ppv', 0.6666666666666666), 
                ('npv', 0.5714285714285714)
            ]), 
            'counts': OrderedDict([
                ('tp', 2), 
                ('fp', 1), 
                ('tn', 4), 
                ('fn', 3), 
                ('pos', 5), 
                ('neg', 5), 
                ('n', 10)
            ])
        }), 
        ('C', {
            'stats': OrderedDict([
                ('sensitivity', 0.5), 
                ('specificity', 0.625), 
                ('precision', 0.25), 
                ('recall', 0.5), 
                ('tpr', 0.5), 
                ('tnr', 0.625), (
                'fpr', 0.375), (
                'fnr', 0.5), 
                ('accuracy', 0.6), 
                ('f1score', 0.3333333333333333), 
                ('fdr', 0.75), 
                ('for', 0.16666666666666666), 
                ('ppv', 0.25), 
                ('npv', 0.8333333333333334)
            ]), 
            'counts': OrderedDict([
                ('tp', 1), 
                ('fp', 3), 
                ('tn', 5), 
                ('fn', 1), 
                ('pos', 2), 
                ('neg', 8), 
                ('n', 10)
            ])
        })
    ]
)
1 голос
/ 27 января 2010

Вы можете сделать свой код более лаконичным и (иногда) работать быстрее, используя numpy. Например, в случае двух классов ваша функция может быть переписана как (см. mply.acc()):

def accuracy(actual, predicted):
    """accuracy = (tp + tn) / ts

    , where:    

        ts - Total Samples
        tp - True Positives
        tn - True Negatives
    """
    return (actual == predicted).sum() / float(len(actual))

, где:

actual    = (numpy.array(input_arr) == 2)
predicted = (numpy.array(prob_arr) < 0.5)
0 голосов
/ 20 ноября 2018

Вот простая реализация, которая обрабатывает неодинаковое количество классов в прогнозируемых и реальных метках (см. Примеры 3 и 4). Надеюсь, это поможет!

Для тех, кто только изучает это, вот краткий обзор. Метки для столбцов указывают на прогнозируемый класс, а метки для строк указывают на правильный класс. В примере 1 мы имеем [3 1] в верхней строке. Снова, строки показывают истину, так что это означает, что правильная метка - «0», и есть 4 примера с основной меткой истины «0». Столбцы указывают на прогнозы, поэтому 3/4 выборок правильно помечены как «0», а 1/4 неправильно помечены как «1».

def confusion_matrix(actual, predicted):
    classes       = np.unique(np.concatenate((actual,predicted)))
    confusion_mtx = np.empty((len(classes),len(classes)),dtype=np.int)
    for i,a in enumerate(classes):
        for j,p in enumerate(classes):
            confusion_mtx[i,j] = np.where((actual==a)*(predicted==p))[0].shape[0]
    return confusion_mtx

Пример 1:

actual    = np.array([1,1,1,1,0,0,0,0])
predicted = np.array([1,1,1,1,0,0,0,1])
confusion_matrix(actual,predicted)

   0  1
0  3  1
1  0  4

Пример 2:

actual    = np.array(["a","a","a","a","b","b","b","b"])
predicted = np.array(["a","a","a","a","b","b","b","a"])
confusion_matrix(actual,predicted)

   0  1
0  4  0
1  1  3

Пример 3:

actual    = np.array(["a","a","a","a","b","b","b","b"])
predicted = np.array(["a","a","a","a","b","b","b","z"]) # <-- notice the 3rd class, "z"
confusion_matrix(actual,predicted)

   0  1  2
0  4  0  0
1  0  3  1
2  0  0  0

Пример 4:

actual    = np.array(["a","a","a","x","x","b","b","b"]) # <-- notice the 4th class, "x"
predicted = np.array(["a","a","a","a","b","b","b","z"])
confusion_matrix(actual,predicted)

   0  1  2  3
0  3  0  0  0
1  0  2  0  1
2  1  1  0  0
3  0  0  0  0
0 голосов
/ 22 октября 2018

Единственное решение для любого количества классов, которое не требует циклического выполнения:

import numpy as np

classes = 3
true = np.random.randint(0, classes, 50)
pred = np.random.randint(0, classes, 50)

np.bincount(true * classes + pred).reshape((classes, classes))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...