Предыдущий ‹‹ Матрица путаницы и дисбаланс данных (1/3)

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

Label     Hiker     Animal     Tree     Rock
Count      400       200        800      800

Обратите внимание, что большая часть данных представляет собой деревья или камни. У сбалансированного набора данных этой проблемы нет. Например, если бы мы пытались предсказать, является ли объект путешественником, животным, деревом или камнем , в идеале нам нужно равное количество всех категорий, например:

Label     Hiker     Animal     Tree     Rock
Count      550       550        550      550

Если бы мы просто пытались предсказать, является ли объект путешественником, в идеале нам нужно равное количество объектов путешественник и не-путешественник. :

Label     Hiker     Non-Hiker
Count     1100        1100

Почему дисбаланс данных имеет значение?

Дисбаланс данных имеет значение, поскольку модели могут научиться имитировать этот дисбаланс, когда это нежелательно. Например, представьте, что мы обучили модель логистической регрессии идентифицировать объекты как путешественники или не-путешественникиr. Если бы в обучающих данных значительно преобладали метки «путешественник», то обучение привело бы к смещению модели так, что она почти всегда будет возвращать метки «путешественник». Однако в реальном мире мы можем обнаружить, что большинство объектов, с которыми сталкиваются дроны, — это деревья. Предвзятая модель, вероятно, назвала бы многие из этих деревьев путешественниками.

Это явление происходит потому, что функции стоимости по умолчанию определяют, был ли дан правильный ответ. Это означает, что для предвзятого набора данных самый простой способ достичь оптимальной производительности модели — практически игнорировать предоставленные функции и всегда или почти всегда возвращать один и тот же ответ. Это может иметь разрушительные последствия. Например, представьте, что наша модель путешественник/не-путешественник обучена на данных, где только один из 1000 образцов содержит путешественника. Модель, которая научилась каждый раз возвращать сообщение «не-путешественник», имеет точность 99,9 %! Эта статистика кажется выдающейся, но модель бесполезна, потому что она никогда не скажет нам, находится ли кто-то на горе, и мы не будем знать, как его спасти, если обрушится лавина.

Предвзятость в матрице путаницы

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

Мы можем сказать, что входные данные несмещены, поскольку суммы строк одинаковы (по 500 в каждой), что указывает на то, что половина меток имеет значение «true», а половина — «false». Точно так же мы видим, что модель дает объективные ответы, поскольку в половине случаев она возвращает true, а в другой половине времени false.

Напротив, предвзятые данные в основном содержат один вид меток, например:

Аналогичным образом, предвзятая модель в основном создает один вид ярлыков, например:

Смещение модели — это не точность

Помните, что предвзятость — это не точность. Например, некоторые из предыдущих примеров являются предвзятыми, а другие нет, но все они демонстрируют модель, которая дает правильный ответ в 50% случаев. В качестве более радикального примера приведенная ниже матрица показывает непредвзятую модель, которая является неточной:

Обратите внимание, что количество строк и столбцов в сумме составляет 500, что указывает на то, что оба данных сбалансированы и модель не является предвзятой. Однако эта модель почти все ответы дает неправильные!

Конечно, наша цель — сделать модели точными и беспристрастными, например:

Но нам необходимо убедиться, что наши точные модели не являются предвзятыми просто потому, что данные:

Обратите внимание, что в этом примере фактические метки в основном false (левый столбец, показывающий дисбаланс данных), а модель также часто возвращает false (верхний ряд, показывающий смещение модели). . Эта модель не очень хороша для правильного предоставления правдивых ответов.

Как избежать последствий несбалансированных данных

Вот несколько простых способов избежать последствий несбалансированных данных:

  • Избегайте этого за счет лучшего отбора данных.
  • Измените данные, чтобы они содержали дубликаты класса меток меньшинства.
  • Внесите изменения в функцию стоимости, чтобы она отдавала приоритет менее распространенным меткам. Например, если на tree дан неправильный ответ, функция стоимости может вернуть 1; а если путешественнику дан неправильный ответ, он может вернуть 10.

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

Визуализация данных

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

import pandas
!wget https://raw.githubusercontent.com/MicrosoftDocs/mslearn-introduction-to-machine-learning/main/graphing.py
!wget https://raw.githubusercontent.com/MicrosoftDocs/mslearn-introduction-to-machine-learning/main/Data/snow_objects.csv
!wget https://raw.githubusercontent.com/MicrosoftDocs/mslearn-introduction-to-machine-learning/main/Data/snow_objects_balanced.csv

#Import the data from the .csv file
dataset = pandas.read_csv('snow_objects.csv', delimiter="\t")

# Let's have a look at the data
dataset
           size     roughness     color     motion     label
0       50.959361    1.318226     green    0.054290    tree
1       60.008521    0.554291     brown    0.000000    tree
2       20.530772    1.097752     white    1.380464    tree
3       28.092138    0.966482     grey     0.650528    tree
4       48.344211    0.799093     grey     0.000000    tree
 ...       ...          ...        ...        ...       ...
2195    1.918175     1.182234     white    0.000000    animal
2196    1.000694     1.332152     black    4.041097    animal
2197    2.331485     0.734561     brown    0.961486    animal
2198    1.786560     0.707935     black    0.000000    animal
2199    1.518813     1.447957     brown    0.000000    animal

2200 rows × 5 columns

Напомним, что у нас есть несбалансированный набор данных. Некоторые занятия проводятся гораздо чаще, чем другие:

import graphing # custom graphing code. See our GitHub repo for details

# Plot a histogram with counts for each label
graphing.multiple_histogram(dataset, label_x="label", label_group="label", title="Label distribution")

Используйте двоичную классификацию

Здесь мы построим модель бинарной классификации. Мы хотим предсказать, являются ли объекты на снегу «путешественниками» или «непутешественниками». Для этого нам сначала нужно добавить еще один столбец в наш набор данных и установить для него значение true, где исходная метка — hiker, а falseчто-нибудь еще:

# Add a new label with true/false values to our dataset
dataset["is_hiker"] = dataset.label == "hiker"

# Plot frequency for new label
graphing.multiple_histogram(dataset, label_x="is_hiker", label_group="is_hiker", title="Distribution for new binary label 'is_hiker'")

Теперь в нашем наборе данных есть только два класса меток, но мы сделали его еще более несбалансированным.

Давайте обучим модель случайного леса, используя is_hiker в качестве целевой переменной, а затем измерим ее точность как при обучении, так и при тестировании. наборы:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
# import matplotlib.pyplot as plt
# from sklearn.metrics import plot_confusion_matrix
from sklearn.metrics import accuracy_score

# Custom function that measures accuracy on different models and datasets
# We will use this in different parts of the exercise
def assess_accuracy(model, dataset, label):
    """
    Asesses model accuracy on different sets
    """ 
    actual = dataset[label]        
    predictions = model.predict(dataset[features])
    acc = accuracy_score(actual, predictions)
    return acc

# Split the dataset in an 70/30 train/test ratio. 
train, test = train_test_split(dataset, test_size=0.3, random_state=1, shuffle=True)

# define a random forest model
model = RandomForestClassifier(n_estimators=1, random_state=1, verbose=False)

# Define which features are to be used (leave color out for now)
features = ["size", "roughness", "motion"]

# Train the model using the binary label
model.fit(train[features], train.is_hiker)

print("Train accuracy:", assess_accuracy(model,train, "is_hiker"))
print("Test accuracy:", assess_accuracy(model,test, "is_hiker"))
Train accuracy: 0.9532467532467532
Test accuracy: 0.906060606060606

Точность выглядит хорошо как для наборов train, так и для test, но помните, что этот показатель не является абсолютным показателем успеха. Нам нужно построить матрицу ошибок, чтобы увидеть, как на самом деле работает модель:

# sklearn has a very convenient utility to build confusion matrices
from sklearn.metrics import confusion_matrix
import plotly.figure_factory as ff

# Calculate the model's accuracy on the TEST set
actual = test.is_hiker
predictions = model.predict(test[features])

# Build and print our confusion matrix, using the actual values and predictions 
# from the test set, calculated in previous cells
cm = confusion_matrix(actual, predictions, normalize=None)

# Create the list of unique labels in the test set, to use in our plot
# I.e., ['True', 'False',]
unique_targets = sorted(list(test["is_hiker"].unique()))

# Convert values to lower case so the plot code can count the outcomes
x = y = [str(s).lower() for s in unique_targets]

# Plot the matrix above as a heatmap with annotations (values) in its cells
fig = ff.create_annotated_heatmap(cm, x, y)

# Set titles and ordering
fig.update_layout(  title_text="<b>Confusion matrix</b>", 
                    yaxis = dict(categoryorder = "category descending")
                    )

fig.add_annotation(dict(font=dict(color="black",size=14),
                        x=0.5,
                        y=-0.15,
                        showarrow=False,
                        text="Predicted label",
                        xref="paper",
                        yref="paper"))

fig.add_annotation(dict(font=dict(color="black",size=14),
                        x=-0.15,
                        y=0.5,
                        showarrow=False,
                        text="Actual label",
                        textangle=-90,
                        xref="paper",
                        yref="paper"))

# We need margins so the titles fit
fig.update_layout(margin=dict(t=80, r=20, l=120, b=50))
fig['data'][0]['showscale'] = True
fig.show()

Матрица путаницы показывает нам, что, несмотря на заявленные показатели, модель не невероятно точна. Из 660 образцов, представленных в тестовом наборе (30 % от общего числа образцов), он предсказал 29 ложноотрицательных результатов и 33 ложноположительных результатов.

Что еще более важно, посмотрите на нижнюю строку, которая показывает, что произошло, когда модели показали информацию о путешественнике: она давала неправильный ответ почти в 40 % случаев. Это означает, что он не сможет правильно идентифицировать почти 40% людей на горе! Что произойдет, если мы будем использовать эту модель для прогнозирования сбалансированных множеств?

Давайте загрузим набор данных с равным количеством результатов для «путешественников» и «не путешественников», а затем используем эти данные для составления прогнозов:

# Load and print umbiased set
#Import the data from the .csv file
balanced_dataset = pandas.read_csv('snow_objects_balanced.csv', delimiter="\t")

#Let's have a look at the data
graphing.multiple_histogram(balanced_dataset, label_x="label", label_group="label", title="Label distribution")

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

# Add a new label with true/false values to our dataset
balanced_dataset["is_hiker"] = balanced_dataset.label == "hiker"

hikers_dataset = balanced_dataset[balanced_dataset["is_hiker"] == 1] 
nonhikers_dataset = balanced_dataset[balanced_dataset["is_hiker"] == False] 
# take a random sampling of non-hikers the same size as the hikers subset
nonhikers_dataset = nonhikers_dataset.sample(n=len(hikers_dataset.index), random_state=1)
balanced_dataset = pandas.concat([hikers_dataset, nonhikers_dataset])

# Plot frequency for "is_hiker" labels
graphing.multiple_histogram(balanced_dataset, label_x="is_hiker", label_group="is_hiker", title="Label distribution in balanced dataset")

Обратите внимание, что метка is_hiker имеет одинаковое количество значений true и false для обоих классов. Теперь мы используем набор данных, сбалансированный по классам. Давайте выполним прогнозы для этого набора, используя ранее обученную модель:

# Test the model using a balanced dataset
actual = balanced_dataset.is_hiker
predictions = model.predict(balanced_dataset[features])

# Build and print our confusion matrix, using the actual values and predictions 
# from the test set, calculated in previous cells
cm = confusion_matrix(actual, predictions, normalize=None)

# Print accuracy using this set
print("Balanced set accuracy:", assess_accuracy(model,balanced_dataset, "is_hiker"))
Balanced set accuracy: 0.754

Как и ожидалось, при использовании другого набора мы получаем заметное падение точности. Опять же, давайте визуально проанализируем его производительность:

# plot new confusion matrix
# Create the list of unique labels in the test set to use in our plot
unique_targets = sorted(list(balanced_dataset["is_hiker"].unique()))

# Convert values to lower case so the plot code can count the outcomes
x = y = [str(s).lower() for s in unique_targets]

# Plot the matrix above as a heatmap with annotations (values) in its cells
fig = ff.create_annotated_heatmap(cm, x, y)

# Set titles and ordering
fig.update_layout(  title_text="<b>Confusion matrix</b>", 
                    yaxis = dict(categoryorder = "category descending")
                    )

fig.add_annotation(dict(font=dict(color="black",size=14),
                        x=0.5,
                        y=-0.15,
                        showarrow=False,
                        text="Predicted label",
                        xref="paper",
                        yref="paper"))

fig.add_annotation(dict(font=dict(color="black",size=14),
                        x=-0.15,
                        y=0.5,
                        showarrow=False,
                        text="Actual label",
                        textangle=-90,
                        xref="paper",
                        yref="paper"))

# We need margins so the titles fit
fig.update_layout(margin=dict(t=80, r=20, l=120, b=50))
fig['data'][0]['showscale'] = True
fig.show()

Матрица путаницы подтверждает низкую точность использования этого набора данных, но почему это происходит, когда у нас были такие отличные показатели в более ранних наборах train и test?

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

Несбалансированные наборы можно решить несколькими способами:
– Улучшение отбора данных
– Повторная выборка набора данных
– Использование взвешенных классов – › мы сосредоточим внимание на этом в упражнении.

Используйте веса классов для балансировки набора данных

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

Затем он будет наказывать за ошибки, когда класс меньшинства будет неправильно классифицирован, по сути, «заставляя» модель лучше изучать их функции и шаблоны.
Чтобы использовать взвешенные классы, нам нужно переобучить нашу модель. используя исходный набор train, но на этот раз указывая алгоритму использовать веса при вычислении ошибок:

# Import function used in calculating weights
from sklearn.utils import class_weight

# Retrain model using class weights
# Using class_weight="balanced" tells the algorithm to automatically calculate weights for us
weighted_model = RandomForestClassifier(n_estimators=1, random_state=1, verbose=False, class_weight="balanced")
# Train the weighted_model using binary label
weighted_model.fit(train[features], train.is_hiker)

print("Train accuracy:", assess_accuracy(weighted_model,train, "is_hiker"))
print("Test accuracy:", assess_accuracy(weighted_model, test, "is_hiker"))
Train accuracy: 0.962987012987013
Test accuracy: 0.9090909090909091

После использования взвешенных классов точность обучения осталась почти такой же, в то время как точность теста немного улучшилась (примерно 1%). Давайте посмотрим, улучшатся ли вообще результаты, снова используя сбалансированный набор для прогнозов:

print("Balanced set accuracy:", assess_accuracy(weighted_model, balanced_dataset, "is_hiker"))

# Test the weighted_model using a balanced dataset
actual = balanced_dataset.is_hiker
predictions = weighted_model.predict(balanced_dataset[features])

# Build and print our confusion matrix, using the actual values and predictions 
# from the test set, calculated in previous cells
cm = confusion_matrix(actual, predictions, normalize=None)
Balanced set accuracy: 0.761

Точность сбалансированного набора увеличилась примерно на 4%, но нам все равно следует попытаться визуализировать и понять новые результаты.

Окончательная матрица путаницы

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

# Plot the matrix above as a heatmap with annotations (values) in its cells
fig = ff.create_annotated_heatmap(cm, x, y)

# Set titles and ordering
fig.update_layout(  title_text="<b>Confusion matrix</b>", 
                    yaxis = dict(categoryorder = "category descending")
                    )

fig.add_annotation(dict(font=dict(color="black",size=14),
                        x=0.5,
                        y=-0.15,
                        showarrow=False,
                        text="Predicted label",
                        xref="paper",
                        yref="paper"))

fig.add_annotation(dict(font=dict(color="black",size=14),
                        x=-0.15,
                        y=0.5,
                        showarrow=False,
                        text="Actual label",
                        textangle=-90,
                        xref="paper",
                        yref="paper"))

# We need margins so the titles fit
fig.update_layout(margin=dict(t=80, r=20, l=120, b=50))
fig['data'][0]['showscale'] = True
fig.show()

Хотя результаты могут показаться немного разочаровывающими, теперь у нас 21% неверных прогнозов (FN + FP) по сравнению с 25% в предыдущем эксперименте.
Доля правильных прогнозов (TP + TN) выросла с 74,7% до 78,7%. Является ли общее улучшение на 4% значительным или нет?

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

Чтобы добиться лучших результатов, нам пришлось изменить всего одну строку кода, так что, похоже, усилия того стоят!

Резюме
В этом упражнении мы говорили о следующих понятиях:

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

Приятного обучения!
Далее ›› Матрица путаницы и дисбаланс данных (3/3)