Share |

воскресенье, 20 августа 2017 г.

Задача классификации с помощью пакета mlr в r на кейсе по адаптации персонала



После семинара по R участники делают свои проекты - домашние задания. Я тоже решил сделать свое домашнее задание. Чтобы показать студентам, что я сам еще могу руками работать. И еще показать потенциальным участникам семинара "HR-аналитика в R", что их ждет на семинаре. Если Вы не аналитик, рекомендую Вам в самом низу почитать менеджерскую часть проекта - то, что делает менеджер, принимающий решения, на основе анализа.
В качестве задачи я решил взять задачу про прогноз успешности адаптации персонала (мы на семинаре разбирали такую задачу), но решить ее с помощью пакета mlr.
mlr - это фреймфорк, пакет обертка для алгоритмов машинного обучения, он значительно облегчает жизнь специалистам по машинному обучению. До последнего времени я пользовался пакетом caret, его же использую на семинаре. Но вот в качестве собственного развития покажу, как работает mlr

Итак, 

library(mlr)

Сразу обозначу, почему mlr мне показался симпатичен- параметры модели. И возьму один из самых популярных алгоритмов - случайный лес. Но количество алгоритмов больше, в том числе любимый всеми xgboost



getParamSet("classif.randomForest")
 Type  len   Def   Constr Req Tunable Trafo
ntree             integer    -   500 1 to Inf   -    TRUE     -
mtry              integer    -     - 1 to Inf   -    TRUE     -
replace           logical    -  TRUE        -   -    TRUE     -
classwt     numericvector      - 0 to Inf   -    TRUE     -
cutoff      numericvector      -   0 to 1   -    TRUE     -
strata            untyped    -     -        -   -   FALSE     -
sampsize    integervector      - 1 to Inf   -    TRUE     -
nodesize          integer    -     1 1 to Inf   -    TRUE     -
maxnodes          integer    -     - 1 to Inf   -    TRUE     -
importance        logical    - FALSE        -   -    TRUE     -
localImp          logical    - FALSE        -   -    TRUE     -
proximity         logical    - FALSE        -   -   FALSE     -
oob.prox          logical    -     -        -   Y   FALSE     -
norm.votes        logical    -  TRUE        -   -   FALSE     -
do.trace          logical    - FALSE        -   -   FALSE     -
keep.forest       logical    -  TRUE        -   -   FALSE     -
keep.inbag        logical    - FALSE        -   -   FALSE     -
Это параметры модели. Больше, чем в caret. Для дерева решений параметры такие
getParamSet("classif.rpart")
    Type len  Def   Constr Req Tunable Trafo
minsplit        integer   -   20 1 to Inf   -    TRUE     -
minbucket       integer   -    - 1 to Inf   -    TRUE     -
cp              numeric   - 0.01   0 to 1   -    TRUE     -
maxcompete      integer   -    4 0 to Inf   -    TRUE     -
maxsurrogate    integer   -    5 0 to Inf   -    TRUE     -
usesurrogate   discrete   -    2    0,1,2   -    TRUE     -
surrogatestyle discrete   -    0      0,1   -    TRUE     -
maxdepth         integer   -   30  1 to 30   -    TRUE     -
xval            integer   -   10 0 to Inf   -   FALSE     -
parms           untyped   -    -        -   -    TRUE     - 

Если помните, в caret мы настраиваем только параметр cp.
Вернемся к случайному лесу. Сначала зададим учителя - сам алгоритм
rf = makeLearner("classif.randomForest",  predict.type = "prob", par.vals = list(ntree = 200, mtry = 3,  importance = TRUE))
Обратите внимание, я сразу задаю тип прогноза вероятность, а не класс, чтобы можно было играться границами принятия решения. Если вы хотите предсказывать класс, указываете "response". И самое вкусное - задаем тюнинг гиперпараметров
 rf_param = makeParamSet(
  makeIntegerParam("ntree",lower = 50, upper = 500),
  makeIntegerParam("mtry", lower = 1, upper = 6),
  makeIntegerParam("nodesize", lower = 10, upper = 50)
)

rancontrol = makeTuneControlRandom(maxit = 50L)<- makeparamset="" p=""> <- maketunecontrolrandom="" maxit="50L)</p">
mtry у меня небольшой, потому что всего 6 переменных в уравнении.
Задаем кросс валидацию
set_cv = makeResampleDesc("RepCV", folds = 10L, reps = 5L)
Прежде, чем перейти к тренировке параметров, скажу, что в mlr развита функция препроцессинга, вы можете делать импутацию, нормализацию и feature selection. Я это не показываю здесь, потому что импутация и feature selection не нужна для моего датасета, тем более это сильно раздует пост, а вот скейлинг я привык делать с помощью
range01 = function(x){(x-min(x))/(max(x)-min(x))
И на вход мы подаем не dataframe, а класс Task - это по сути одна строка кода
trainTask = makeClassifTask( data = train,target = "ef")
testTask = makeClassifTask(data = test,target = "ef")
Где train и test - это наш разбитый на две подвыборки датасет, а ef - целевая переменная. В нашем случае это показатель проходимости срока адаптации: 1 - прошел срок адаптации, 0 - не прошел. Запускаем тюнинг
rf_tune = tuneParams(learner = rf, resampling = set_cv, task = trainTask, par.set = rf_param, control = rancontrol, measures = auc)
Количество параметров, итераций и фолдов кросс валидации с повторениями позволяет мне поставить чай, заварить пакетик и пофилософствовать. После посмотрим на best гиперпараметры
rf_tune$x
$ntree
[1] 366
$mtry
[1] 2
$nodesize
[1] 37

И полученное качество модели (мы указали measures = auc, auc - площадь под кривой, но можно указать любую другую меру оценки качества модели):
rf_tune$y
auc.test.mean
     0.615503  
Прямо скажем, не очень хороший показатель. Ну а что вы хотели? Эти данные реальные, не из учебника.
Поскольку выборка у нас разбалансирована: класс "1" в трейн сете 1080, а класс "0" - 514, применим технику работы с такими данными - over- and undersampling (обращаю внимание, я показываю simple over- and undersampling, есть более продвинутые техники, см. Imbalanced Classification Problems
task.over = oversample(trainTask, rate = 2)
task.under = undersample(trainTask, rate = 1/2)
И если наш датасет имел соотношение классов 514 / 1080, то для новых выборок получаем
table(getTaskTargets(task.over))
   0    1
1028 1080
table(getTaskTargets(task.under))
  0   1
514 540
Т.е. в первом случае мы увеличили число не прошедших испытательный срок, во втором - уменьшили количество прошедших. И запускаем тюнинг параметров для этих выборок. И получаем метрики качества для обеих выборок
rf_tune.over$y
auc.test.mean
    0.7951566
rf_tune.under$y
auc.test.mean
    0.6162755 
Чувствуется разница? Площадь под кривой 0, 795 для оверсемплинга.
Задаем полученные гиперпараметры для моделей
rf.tree = setHyperPars(rf, par.vals = rf_tune$x)
rf.tree.over =setHyperPars(rf, par.vals = rf_tune.over$x)
rf.tree.under = setHyperPars(rf, par.vals = rf_tune.under$x)
И обучаем трейн датасет с полученными параметрами
rforest = mlr::train(rf.tree, trainTask)
rforest.over = mlr::train(rf.tree.over, task.over)
rforest.under = mlr::train(rf.tree.under, task.under)
mlr:: в данном коде - последствие того, что я запустил с mlr одновременно caret - машина ругается и выдает ошибку.
Посмотрим на полученное качество моделей на тест сете (покажу код только на примере основного датасета, и в данном случае опять использую mlr:: , потому что в caret есть также команда performance)
performance(predict(rforest, newdata = test), measures = auc)
auc = 0.6105039
auc.over = 0.6209616
auc.under = 0.6045515
Вывод: в нашем случае техники работы с несбалансированными выборками не дали эффекта. Ну и сама по себе модель слабая, что ставит вопрос о ее пркатической применимости.

ROC curve

Задача классификации с помощью пакета mlr в r
Не густо

Важность факторов

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

И самое важное

А можем ли мы полученные результаты применять на практике? Здесь уже должен включаться менеджер, а не специалист по машинномуобучению

Мы с вами прогнозируем, пройдет ли кандидат адаптационный период, а не отбираем кандидатов в отряд космонавтов, поэтому наше решение на графике precision recall curve должно быть скорее справа, чем слева: нам надо набрать больше людей. В этой ситуации кажется наиболее комфортным решением с границей 0.8
      FALSE TRUE
  0      93       110
  1     132      348
Жесть, правда? Мы отсекаем почти половину не очень хороших кандидатов 93 / (93+110), но при этом отсекаем почти треть потенциально хороших кандидатов 132 / (132+348).
Сделаем меньше границу? 0, 7
      FALSE TRUE
  0      53       150
  1      77       403
При такой границе принятия решения мы отсекаем примерно каждого четвертого неуспешного кандидата и каждого шестого успешного. И если у нас в среднем проходило успешно испытательный срок 68 % кандидатов, то при внедрении алгоритма мы дадим бизнесу 73 % кандидатов, которые пройдут испытательный срок. Итого, мы выигрываем 5 %, это снижение затрат на обучение, зарплату и т.д... Но в минусе - работа спеца по аналитике (это не самое дорогое), а также увеличение затрат на подбор, потому что трафик кандидатов надо будет увеличить примерно на четверть - 25 %. Это может оказаться дороже.
Поэтому вопрос, внедрять данную систему отбора или нет, надо после ответа на два вопроса:
  1. Можем ли мы собрать и использовать в модели дополнительную информацию о кандидатах, которая могла бы улучшить качество модели?
  2. Будет ли выигрыш затрат на обучение / зарплату и т.п. вновь принятых выше, чем косты, связанные с увеличением затрат на подбор?

Понравился пост? 

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

Комментариев нет:

Отправить комментарий

Популярные сообщения

п