Ответ 1
Представленные вами данные не отражают соотношение двух целевых классов, поэтому я изменил данные, чтобы лучше отразить это (см. раздел "Данные" ):
> prop.table(table(train$Target))
0 1
0.96707581 0.03292419
> 700/27700
[1] 0.02527076
Отношение теперь относительно близко...
library(rpart)
tree <- rpart(Target ~ ., data=train, method="class")
printcp(tree)
Результаты в:
Classification tree:
rpart(formula = Target ~ ., data = train, method = "class")
Variables actually used in tree construction:
character(0)
Root node error: 912/27700 = 0.032924
n= 27700
CP nsplit rel error xerror xstd
1 0 0 1 0 0
Теперь причина, по которой вы видите только root node для вашей первой модели, вероятно, связана с тем, что у вас чрезвычайно несбалансированные целевые классы, и поэтому ваши независимые переменные не могут предоставить достаточную информацию для роста дерево. У моих выборочных данных 3,3%, но у вас всего около 2,5%!
Как вы уже упоминали, существует способ заставить rpart
вырастить дерево. То есть переопределить параметр сложности по умолчанию (cp
). Мера сложности - это комбинация размера дерева и того, насколько дерево разделяет целевые классы. Из ?rpart.control
"Любой раскол, который не уменьшает общее отсутствие соответствия коэффициентом cp, не предпринимается". Это означает, что ваша модель в данный момент не имеет раскола за пределами корня node, что снижает уровень сложности, достаточный для rpart
, чтобы принять во внимание. Мы можем уменьшить этот порог того, что считается "достаточным", либо установив низкий, либо отрицательный cp
(отрицательный cp
в основном заставляет дерево расти до его полного размера).
tree <- rpart(Target ~ ., data=train, method="class" ,parms = list(split = 'information'),
control =rpart.control(minsplit = 1,minbucket=2, cp=0.00002))
printcp(tree)
Результаты в:
Classification tree:
rpart(formula = Target ~ ., data = train, method = "class", parms = list(split = "information"),
control = rpart.control(minsplit = 1, minbucket = 2, cp = 2e-05))
Variables actually used in tree construction:
[1] ID V1 V2 V3 V5 V6
Root node error: 912/27700 = 0.032924
n= 27700
CP nsplit rel error xerror xstd
1 4.1118e-04 0 1.00000 1.0000 0.032564
2 3.6550e-04 30 0.98355 1.0285 0.033009
3 3.2489e-04 45 0.97807 1.0702 0.033647
4 3.1328e-04 106 0.95504 1.0877 0.033911
5 2.7412e-04 116 0.95175 1.1031 0.034141
6 2.5304e-04 132 0.94737 1.1217 0.034417
7 2.1930e-04 149 0.94298 1.1458 0.034771
8 1.9936e-04 159 0.94079 1.1502 0.034835
9 1.8275e-04 181 0.93640 1.1645 0.035041
10 1.6447e-04 193 0.93421 1.1864 0.035356
11 1.5664e-04 233 0.92654 1.1853 0.035341
12 1.3706e-04 320 0.91228 1.2083 0.035668
13 1.2183e-04 344 0.90899 1.2127 0.035730
14 9.9681e-05 353 0.90789 1.2237 0.035885
15 2.0000e-05 364 0.90680 1.2259 0.035915
Как вы можете видеть, дерево выросло до размера, что снижает уровень сложности минимум на cp
. Следует отметить две вещи:
- В нуле
nsplit
значениеcp
уже равно 0,0004, где по умолчаниюcp
вrpart
установлено значение 0,01. - Начиная с
nsplit == 0
, ошибка перекрестной проверки (xerror
) увеличивается по мере увеличения количества разделов.
Оба из них указывают на то, что ваша модель переопределяет данные в nsplit == 0
и выше, поскольку добавление в вашу модель независимых переменных не добавляет достаточной информации (недостаточное сокращение CP), чтобы уменьшить ошибку перекрестной проверки. С учетом сказанного, ваша корневая модель node является лучшей моделью в этом случае, что объясняет, почему ваша исходная модель имеет только корень node.
pruned.tree <- prune(tree, cp = tree$cptable[which.min(tree$cptable[,"xerror"]),"CP"])
printcp(pruned.tree)
Результаты в:
Classification tree:
rpart(formula = Target ~ ., data = train, method = "class", parms = list(split = "information"),
control = rpart.control(minsplit = 1, minbucket = 2, cp = 2e-05))
Variables actually used in tree construction:
character(0)
Root node error: 912/27700 = 0.032924
n= 27700
CP nsplit rel error xerror xstd
1 0.00041118 0 1 1 0.032564
Что касается части обрезки, теперь становится яснее, почему ваше обрезанное дерево является корневым деревом node, так как дерево, выходящее за рамки 0, имеет большую ошибку перекрестной проверки. Взятие дерева с минимальным значением xerror
оставило бы вас с корнем node, как и ожидалось.
Информационный выигрыш в основном говорит вам, сколько "информации" добавляется для каждого раскола. Так что технически, каждый раскол имеет некоторую степень получения информации, поскольку вы добавляете в свою модель больше переменных (коэффициент полезного действия всегда неотрицателен). То, о чем вы должны думать, заключается в том, уменьшает ли этот дополнительный выигрыш (или нет коэффициента усиления) ошибки, достаточные для того, чтобы вы могли использовать более сложную модель. Следовательно, компромисс между смещением и дисперсией.
В этом случае на самом деле нет смысла уменьшать cp
и позже обрезать полученное дерево. так как, установив низкий cp
, вы сообщаете rpart
делать разрывы, даже если они перерабатывают, в то время как обрезка "разрезает" все перегруженные узлы.
Данные:
Обратите внимание, что я перетасовываю строки для каждого столбца и образца вместо выборки индексов строк. Это связано с тем, что предоставленные вами данные, вероятно, не являются случайным образцом вашего исходного набора данных (вероятно, предвзятым), поэтому я в основном беспорядочно создаю новые наблюдения с комбинациями существующих строк, которые, надеюсь, уменьшат это смещение.
init_train = structure(list(ID = structure(c(16L, 24L, 29L, 30L, 31L, 1L,
2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L, 14L, 15L,
17L, 18L, 19L, 20L, 21L, 22L, 23L, 25L, 26L, 27L, 28L), .Label = c("SDataID10",
"SDataID11", "SDataID13", "SDataID14", "SDataID15", "SDataID16",
"SDataID17", "SDataID18", "SDataID19", "SDataID20", "SDataID21",
"SDataID24", "SDataID25", "SDataID28", "SDataID29", "SDataID3",
"SDataID31", "SDataID32", "SDataID34", "SDataID35", "SDataID37",
"SDataID38", "SDataID39", "SDataID4", "SDataID43", "SDataID44",
"SDataID45", "SDataID46", "SDataID5", "SDataID7", "SDataID8"), class = "factor"),
V1 = c(161L, 11L, 32L, 13L, 194L, 63L, 89L, 78L, 87L, 81L,
63L, 198L, 9L, 196L, 189L, 116L, 104L, 5L, 173L, 5L, 87L,
5L, 45L, 19L, 133L, 8L, 42L, 45L, 45L, 176L, 63L), V2 = structure(c(1L,
3L, 3L, 1L, 3L, 2L, 1L, 3L, 3L, 1L, 1L, 1L, 3L, 1L, 3L, 2L,
1L, 1L, 3L, 1L, 1L, 1L, 1L, 3L, 3L, 1L, 1L, 1L, 1L, 1L, 1L
), .Label = c("ONE", "THREE", "TWO"), class = "factor"),
V3 = c(1L, 2L, 2L, 1L, 2L, 3L, 1L, 2L, 2L, 1L, 3L, 3L, 3L,
2L, 2L, 3L, 1L, 2L, 3L, 3L, 3L, 2L, 1L, 2L, 2L, 1L, 1L, 1L,
1L, 1L, 1L), V5 = structure(c(1L, 3L, 1L, 3L, 1L, 1L, 1L,
1L, 3L, 3L, 1L, 3L, 3L, 3L, 2L, 4L, 1L, 2L, 1L, 2L, 1L, 3L,
1L, 3L, 1L, 3L, 3L, 3L, 1L, 1L, 3L), .Label = c("FOUR", "ONE",
"THREE", "TWO"), class = "factor"), V6 = c(0L, 2L, 2L, 2L,
0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, 2L, 1L, 0L, 0L, 3L, 0L,
3L, 3L, 1L, 0L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 3L), Target = c(0L,
1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L,
0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L
)), .Names = c("ID", "V1", "V2", "V3", "V5", "V6", "Target"
), class = "data.frame", row.names = c(NA, -31L))
set.seed(1000)
train = as.data.frame(lapply(init_train, function(x) sample(x, 27700, replace = TRUE)))