Предвзятые данные и как с ними бороться

История недостаточной выборки

Мой студент только что закончил диплом с отличием в области науки о данных, связанный с классификацией - или, по крайней мере, он думал, что закончил. Он получил смесь из нескольких тысяч твитов, опубликованных ~ 2000 студентов и ~ 700 предпринимателями из района Бостона. Ему пришлось обучить модель машинного обучения (RandomForestClassifier), которая предсказывала, был ли твит опубликован студентом или предпринимателем. Звучит как легкая задача. Он произвольно выбрал 70% твитов для обучения, а остальные для тестирования. Модель вроде бы работала и имела точность 83% - совсем неплохо для курсового проекта.

К сожалению, модель очень хорошо распознавала твиты студентов (точность 95%), но довольно плохо распознавала твиты предпринимателей (точность 55%). Почему? Потому что в наборе обучающих данных было ~ 1400 студентов и только ~ 500 предпринимателей. Другими словами, он был предвзято относился к студентам и научился лучше распознавать их твиты.

Один из способов исправить набор обучающих данных - предоставить равное или несколько равное количество выборок для каждого результата. Если у нас есть только ~ 500 предпринимателей в данных по обучению, тогда мы должны также включить только ~ 500 студентов, если наша цель не состоит в том, чтобы исказить учебный процесс. Scikit-Learn, самый популярный пакет анализа данных в Python, не имеет функции разделения на тестовые и обучающие программы без смещения. Но мы можем построить его сами.

Пусть X будет фреймом данных Pandas, который содержит функции (предикторы) и результаты для нашего полного набора данных. Характеристики находятся в столбцах x1..xN, а результаты - в столбце y. Начните с вычисления количества образцов, подтверждающих каждый из результатов:

from numpy.random import binomial
np.random.seed(0)
# Generate synthetic dataset with three outcomes
samples = 100
X = pd.DataFrame({"x1": binomial(1, 0.3, samples),
                  "x2": binomial(1, 0.6, samples),
                   "y": binomial(2, 0.5, samples)})
grouper = X.groupby("y")
sizes = grouper.size()
#0 26 <- the smallest
#1 46
#2 28

Предположим, мы все еще хотим выбрать 70% из наименьшей группы выборок для обучения и соответствующее количество выборок из более крупных групп. Мы можем рассчитать процент образцов из более крупных групп, чтобы соответствовать 70% наименьшей группы. Эти проценты, кстати, являются вероятностями выбора образца:

fracs = (sizes.min() * 0.7) / sizes
#0 0.700000 <- the smallest
#1 0.395652
#2 0.650000

Теперь, когда мы знаем вероятности, мы можем использовать биномиальное распределение (np.random.binomial) и его логическое дополнение, чтобы сделать случайный выбор обучающей и тестовой выборок соответственно. Мы будем накапливать образцы в двух изначально пустых DataFrames по мере прохождения групп идентичных результатов:

test  = pd.DataFrame()
train = pd.DataFrame()
for grp, data in grouper: # reuse the grouper!
    mask = binomial(1, fracs.loc[grp], data.shape[0]).astype(bool)
    train = train.append(data[mask])
    test  =  test.append(data[~mask])

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

train.groupby("y").size()
#y
#0    21
#1    20
#2    23

Модель, обученная на этом наборе данных, увидит примерно одинаковое количество выборок, принадлежащих трем классам, и не будет смещена.

Должны ли мы также уравнять набор данных тестирования, который не сбалансирован? Скорее всего, нет: вы используете его только для тестирования, и его предвзятость не должна вызывать беспокойства.