Как настроить модель в CARET для выполнения двухэтапной модели классификации PLS-[Classifer]?

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

Веренс Р. Хемометрика с многомерным анализом данных R в естественных науках и науках о жизни. 1-е издание. Гейдельберг; Нью-Йорк: Спрингер. 2011. (стр. 250).

Пример был взят из этой книги и ее пакета ChemometricsWithR. Он выявил некоторые подводные камни при моделировании с использованием методов перекрестной проверки.

Цель:
методология с перекрестной проверкой, использующая тот же набор повторяющихся CV для реализации известной стратегии PLS, за которой обычно следует LDA или двоюродные братья, такие как логистическая регрессия, SVM, C5.0, CART, с духом пакета caret. Таким образом, PLS потребуется каждый раз перед вызовом ожидающего классификатора, чтобы классифицировать пространство PLS score вместо самих наблюдений. Ближайший подход в пакете Caret — выполнение PCA в качестве шага предварительной обработки перед моделированием с помощью любого классификатора. Ниже представлена ​​процедура PLS-LDA только с одной перекрестной проверкой для проверки производительности классификатора, без 10-кратного CV или каких-либо повторений. Код ниже был взят из упомянутой книги, но с некоторыми исправлениями иначе выдает ошибку:

library(ChemometricsWithR)
data(prostate)
prostate.clmat <- classvec2classmat(prostate.type) # convert Y to a dummy var

odd <- seq(1, length(prostate.type), by = 2) # training
even <- seq(2, length(prostate.type), by = 2) # holdout test

prostate.pls <- plsr(prostate.clmat ~ prostate, ncomp = 16, validation = "CV", subset=odd)

Xtst <- scale(prostate[even,], center = colMeans(prostate[odd,]), scale = apply(prostate[odd,],2,sd))

tst.scores <- Xtst %*% prostate.pls$projection # scores for the waiting trained LDA to test

prostate.ldapls <- lda(scores(prostate.pls)[,1:16],prostate.type[odd]) # LDA for scores
table(predict(prostate.ldapls, new = tst.scores[,1:16])$class, prostate.type[even])

predictionTest <- predict(prostate.ldapls, new = tst.scores[,1:16])$class)

library(caret)    
confusionMatrix(data = predictionTest, reference= prostate.type[even]) # from caret

Выход:

Confusion Matrix and Statistics

          Reference
Prediction bph control pca
   bph       4       1   9
   control   1      35   7
   pca      34       4  68

Overall Statistics

               Accuracy : 0.6564          
                 95% CI : (0.5781, 0.7289)
    No Information Rate : 0.5153          
    P-Value [Acc > NIR] : 0.0001874       

                  Kappa : 0.4072          
 Mcnemar's Test P-Value : 0.0015385       

Statistics by Class:

                     Class: bph Class: control Class: pca
Sensitivity             0.10256         0.8750     0.8095
Specificity             0.91935         0.9350     0.5190
Pos Pred Value          0.28571         0.8140     0.6415
Neg Pred Value          0.76510         0.9583     0.7193
Prevalence              0.23926         0.2454     0.5153
Detection Rate          0.02454         0.2147     0.4172
Detection Prevalence    0.08589         0.2638     0.6503
Balanced Accuracy       0.51096         0.9050     0.6643

Однако матрица путаницы не совпадала с той, что была в книге, в любом случае код в книге сломался, но вот этот сработал со мной!

Примечания.
Хотя это был только один CV, но намерение состоит в том, чтобы сначала согласовать эту методологию, sd и mean набора поездов были применены к тестовому набору, ПЛЮС преобразован в баллы PLS на основе определенное количество ПК ncomp. Я хочу, чтобы это происходило каждый раунд CV в каретке. Если методология в виде кода здесь верна, то она может послужить, может быть, хорошим стартом для примера минимальной работы при модификации кода пакета каретки.

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

Опасности многократного центрирования/масштабирования
Приведенный ниже код просто показывает, как многократное центрирование/масштабирование может изменить данные. Здесь показано только центрирование, но та же проблема возникает и с масштабированием.

set.seed(1)
x <- rnorm(200, 2, 1)
xCentered1 <- scale(x, center=TRUE, scale=FALSE)
xCentered2 <- scale(xCentered1, center=TRUE, scale=FALSE)
xCentered3 <- scale(xCentered2, center=TRUE, scale=FALSE)
sapply (list(xNotCentered= x, xCentered1 = xCentered1, xCentered2 = xCentered2, xCentered3 = xCentered3), mean)

Выход:

xNotCentered    xCentered1    xCentered2    xCentered3 
 2.035540e+00  1.897798e-16 -5.603699e-18 -5.332377e-18

Пожалуйста, оставьте комментарий, если я что-то упустил в этом курсе. Спасибо.


person doctorate    schedule 11.01.2014    source источник
comment
Я не думаю, что Caret еще поддерживает клиентские методы для preProcess. Однако вы можете создать собственный поток моделей, включающий предварительную обработку: каретку. r-forge.r-project.org/custom_models.html   -  person Zach    schedule 12.01.2014
comment
Благодарю. Я изучил пользовательскую модель, сложность, которую я не нашел рядом, заключается в том, где в коде нужно указать train, что необходимо сложить баллы обучающего CV. Как правило, я бы сначала попробовал PLS-LDA, если он работает, то сделал бы то же самое для других классификаторов. Это как прототип модели. Итак, можете ли вы сначала предоставить код, как настроить PLS-LDA?   -  person doctorate    schedule 13.01.2014
comment
Я проголосовал за то, чтобы переместить это в stackoverflow, где я считаю это более подходящим.   -  person cbeleites unhappy with SX    schedule 13.01.2014
comment
@doctorate Вы бы определили пользовательскую модель, которая с учетом набора данных соответствовала бы модели pls-lda. Затем вы должны написать функцию прогнозирования для своей модели, которая, учитывая соответствие модели и тестовый набор, будет делать прогнозы. Затем вы передаете эти функции каретке в качестве пользовательского метода, и каретка будет обрабатывать передачу правильных данных каждому из них для каждой повторной выборки вашего набора данных.   -  person Zach    schedule 13.01.2014
comment
@Zach: пакет, о котором я упоминал в старой ветке, имеет модель, подходящую как plslda, так и predict.plslda (и еще несколько функций, таких как coef, и для некоторой постобработки). Однако пока нет поддержки caret.   -  person cbeleites unhappy with SX    schedule 13.01.2014
comment
@ Зак, не могли бы вы взглянуть на пользовательскую модель PLS-LDA ниже, где она терпит неудачу? может быть, у вас есть лучший способ сравнить результаты с другим методом, который вам нравится (я предложил набор данных радужной оболочки, но у меня нет независимого кода для модели PLS-LDA). это было бы чудесно.   -  person doctorate    schedule 14.01.2014


Ответы (4)


Если вы хотите, чтобы эти типы моделей соответствовали caret, вам нужно будет использовать последнюю версию на CRAN. Последнее обновление было создано для того, чтобы люди могли использовать нестандартные модели по своему усмотрению.

Мой подход, описанный ниже, заключается в том, чтобы объединить PLS и другую модель (я использовал случайный лес в приведенном ниже примере) и настроить их одновременно. Таким образом, для каждой складки используется 2D-сетка из ncomp и mtry.

«Хитрость» заключается в том, чтобы привязать загрузки PLS к объекту случайного леса, чтобы их можно было использовать во время прогнозирования. Вот код, определяющий модель (только классификация):

 modelInfo <- list(label = "PLS-RF",
              library = c("pls", "randomForest"),
              type = "Classification",
              parameters = data.frame(parameter = c('ncomp', 'mtry'),
                                      class = c("numeric", 'numeric'),
                                      label = c('#Components', 
                                                '#Randomly Selected Predictors')),
              grid = function(x, y, len = NULL) {
                grid <- expand.grid(ncomp = seq(1, min(ncol(x) - 1, len), by = 1),
                            mtry = 1:len)
                grid <- subset(grid, mtry <= ncomp)
                },
              loop = NULL,
              fit = function(x, y, wts, param, lev, last, classProbs, ...) { 
                     ## First fit the pls model, generate the training set scores,
                     ## then attach what is needed to the random forest object to 
                     ## be used later
                     pre <- plsda(x, y, ncomp = param$ncomp)
                     scores <- pls:::predict.mvr(pre, x, type = "scores")
                     mod <- randomForest(scores, y, mtry = param$mtry, ...)
                     mod$projection <- pre$projection
                     mod
                   },
                   predict = function(modelFit, newdata, submodels = NULL) {       
                     scores <- as.matrix(newdata)  %*% modelFit$projection
                     predict(modelFit, scores)
                   },
                   prob = NULL,
                   varImp = NULL,
                   predictors = function(x, ...) rownames(x$projection),
                   levels = function(x) x$obsLevels,
                   sort = function(x) x[order(x[,1]),])

и вот вызов train:

 library(ChemometricsWithR)
 data(prostate)

 set.seed(1)
 inTrain <- createDataPartition(prostate.type, p = .90)
 trainX <-prostate[inTrain[[1]], ]
 trainY <- prostate.type[inTrain[[1]]]
 testX <-prostate[-inTrain[[1]], ]
 testY <- prostate.type[-inTrain[[1]]]

 ## These will take a while for these data
 set.seed(2)
 plsrf <- train(trainX, trainY, method = modelInfo,
                preProc = c("center", "scale"),
                tuneLength = 10,
                trControl = trainControl(method = "repeatedcv",
                                         repeats = 5))

 ## How does random forest do on its own?
 set.seed(2)
 rfOnly <- train(trainX, trainY, method = "rf",
                tuneLength = 10,
                trControl = trainControl(method = "repeatedcv",
                                         repeats = 5))

Ради интереса я получил:

 > getTrainPerf(plsrf)
   TrainAccuracy TrainKappa method
 1     0.7940423    0.65879 custom
 > getTrainPerf(rfOnly)
   TrainAccuracy TrainKappa method
 1     0.7794082  0.6205322     rf

а также

 > postResample(predict(plsrf, testX), testY)
  Accuracy     Kappa 
 0.7741935 0.6226087 
 > postResample(predict(rfOnly, testX), testY)
  Accuracy     Kappa 
 0.9032258 0.8353982 

Максимум

person topepo    schedule 14.01.2014
comment
большое спасибо, во-первых, я сравнил вышеприведенный код с кодом в подкаталоге caret models для method="rf", я нашел прогноз с типом = prob, поэтому приведенный выше код должен быть: predict(modelFit, scores, type="prob")?. - person doctorate; 14.01.2014
comment
не могли бы вы проверить приведенный ниже код для PLS-LDA? Мое мнение нет, почему? потому что я попробовал данные сонара в виньетке каретки, один раз с методом = pls, встроенным, а второй раз с пользовательским PLS-LDA ниже, и результаты были точно такими же, даже до последней цифры, что не может быть , поэтому код по какой-то причине не дает желаемого эффекта, так что, пожалуйста, пересмотрите его. - person doctorate; 14.01.2014
comment
Ваш код выглядит нормально. Я предполагаю, что результаты одинаковы (в этом случае), потому что моя функция plsda может производить вероятности классов, используя правило Байеса (используя функцию NaiveBayes в пакете klaR). С двумя классами возможно, что LDA и наивная байесовская модель дадут одинаковые результаты (если вы предполагаете нормальность при вычислении наивной байесовской модели). - person topepo; 14.01.2014
comment
скорее всего, вы были правы с проблемой двух классов, я выложил данные по радужной оболочке, и такой проблемы не было. Спасибо за вашу поддержку. - person doctorate; 14.01.2014
comment
@topepo: можно ли это также использовать для сборки моделей? например случайный лес и СВР? Спасибо! - person jpcgandre; 05.09.2014

Основываясь на ценных комментариях Макса, я почувствовал необходимость иметь рефери IRIS, который известен своей классификацией, и, что более важно, результат Species имеет более двух классов, что было бы хорошим набором данных для проверки Пользовательская модель PLS-LDA в каретке:

data(iris)
names(iris)
head(iris)
dim(iris) # 150x5
set.seed(1)
inTrain <- createDataPartition(y = iris$Species,
                               ## the outcome data are needed
                               p = .75,
                               ## The percentage of data in the
                               ## training set
                               list = FALSE)
## The format of the results
## The output is a set of integers for the rows of Iris
## that belong in the training set.
training <- iris[ inTrain,] # 114
testing <- iris[-inTrain,] # 36

ctrl <- trainControl(method = "repeatedcv",
                     repeats = 5,
                     classProbs = TRUE)
set.seed(2)
plsFitIris <- train(Species ~ .,
                   data = training,
                   method = "pls",
                   tuneLength = 4,
                   trControl = ctrl,
                   preProc = c("center", "scale"))
plsFitIris
plot(plsFitIris)


set.seed(2)
plsldaFitIris <- train(Species ~ .,
                      data = training,
                      method = modelInfo,
                      tuneLength = 4,
                      trControl = ctrl,
                      preProc = c("center", "scale"))

plsldaFitIris
plot(plsldaFitIris)  

Теперь сравниваем две модели:

getTrainPerf(plsFitIris)
  TrainAccuracy TrainKappa method
1     0.8574242  0.7852462    pls
getTrainPerf(plsldaFitIris)
  TrainAccuracy TrainKappa method
1      0.975303  0.9628179 custom
postResample(predict(plsFitIris, testing), testing$Species)
Accuracy    Kappa 
   0.750    0.625 
postResample(predict(plsldaFitIris, testing), testing$Species)
 Accuracy     Kappa 
0.9444444 0.9166667  

Итак, наконец, появилась ОЖИДАЕМАЯ разница и улучшение показателей. Таким образом, это поддержало бы идею Макса о том, что проблемы с двумя классами из-за вероятностного подхода Байеса к функции plsda приводят к одним и тем же результатам.

person doctorate    schedule 14.01.2014
comment
Привет, Как я могу реализовать MASS::polr упорядоченную логистическую регрессию в методе каретки? Большое спасибо - person Rafik Margaryan; 18.06.2014

  • Вам нужно обернуть резюме как PLS, так и LDA.
  • Да, и plsr, и lda центрируют данные по-своему
  • Я внимательно посмотрел на caret::preProcess (): в том виде, в каком он определен сейчас, вы не сможете использовать PLS в качестве метода предварительной обработки, потому что он контролируется, но caret::preProcess () использует только неконтролируемые методы (нет возможности передать зависимую переменную). Это, вероятно, затруднит исправление.

  • Итак, внутри каркаса каретки вам нужно будет выбрать пользовательскую модель.

person cbeleites unhappy with SX    schedule 13.01.2014
comment
+1, за примечание, что настраиваемая модель - это способ сделать это. Если lda pls будет выполнять центрирование, а Caret снова будет выполнять центрирование/масштабирование, то у нас проблемы (см. опасности многократного центрирования/масштабирования), интересно, знает ли пакет Caret об этой тонкой проблеме? Буду очень признателен, если предоставите код прототипа модели PLS-LDA. - person doctorate; 13.01.2014
comment
@doctorate: пожалуйста, пришлите мне электронное письмо по адресу [email protected], указав также, нужен ли вам .zip для Win или .tar.gz (или оба), поскольку вы, вероятно, не хотите проверять весь репозиторий HyperSpec svn от r- forge (> 4 ГБ), чтобы получить несколько килобайт кода... - person cbeleites unhappy with SX; 13.01.2014
comment
@doctorate Caret не центрирует и не масштабирует ваши данные автоматически, если вы не установите preProcess=c('center', 'scale'). - person Zach; 13.01.2014
comment
@Zach, так что же обычно нужно делать, чтобы избежать нескольких предварительных процессов, основанных на cbeleites, если lda будет выполнять центрирование (собственное центрирование), а я наивно передаю preProc = c("center","scale"),, то у меня проблемы, верно? иначе что бы вы обычно делали, чтобы избежать этой проблемы многократного центрирования и масштабирования? - person doctorate; 13.01.2014
comment
@cbeleites, большое спасибо за щедрое предложение, уже отправлено. - person doctorate; 13.01.2014
comment
@doctorate: Что я делаю в пакете, так это то, что я удостоверяюсь, что центр, который используется до PLS, соответствует центру, который MASS::lda использует на 2-м шаге. (К сожалению, и MASS::lda, и pls::plsr имеют слегка отличающиеся жестко закодированные стратегии центрирования, поэтому я использую исправленную функцию plsr.) - person cbeleites unhappy with SX; 13.01.2014
comment
@cbeleites, не могли бы вы опубликовать набор данных с перекрестной проверкой радужной оболочки с помощью метода pls-lda, используя ваш код? используя тот же номер set.seed(xxx) и то же повторение 10kfold cv, скажем, 3 (просто чтобы уменьшить нагрузку на вычисления), таким образом можно сравнить производительность приведенного ниже пользовательского кода из caret, см. испытание ниже. заранее спасибо. - person doctorate; 14.01.2014

Если бы сценарий заключался в настройке модели типа PLS-LDA по коду, любезно предоставленному Максом (мейнтейнером CARET), то что-то в этом коде не так, но я не разобрался, потому что использовал Sonar данные одинаковые в caret виньетке и попытались воспроизвести результат один раз, используя method="pls", а другой раз, используя приведенную ниже пользовательскую модель для PLS-LDA, результаты были точно идентичными даже до последней цифры, которая была бессмысленный. Для бенчмаркинга нужен известный набор данных (я думаю, что набор данных с перекрестной проверкой PLS-LDA для радужной оболочки подойдет здесь, поскольку он известен этим типом анализа, и где-то должна быть его перекрестная проверка), все должны быть одинаковыми (set.seed(xxx) и номер повторения K-CV), за исключением рассматриваемого кода, чтобы правильно сравнивать и оценивать приведенный ниже код:

modelInfo <- list(label = "PLS-LDA",
                  library = c("pls", "MASS"),
                  type = "Classification",
                  parameters = data.frame(parameter = c("ncomp"),
                                          class = c("numeric"),
                                          label = c("#Components")),
                  grid = function(x, y, len = NULL) {
                    grid <- expand.grid(ncomp = seq(1, min(ncol(x) - 1, len), by = 1))
                  },
                  loop = NULL,
                  fit = function(x, y, wts, param, lev, last, classProbs, ...) { 
                    ## First fit the pls model, generate the training set scores,
                    ## then attach what is needed to the lda object to 
                    ## be used later
                    pre <- plsda(x, y, ncomp = param$ncomp)
                    scores <- pls:::predict.mvr(pre, x, type = "scores")
                    mod <- lda(scores, y, ...)
                    mod$projection <- pre$projection
                    mod
                  },
                  predict = function(modelFit, newdata, submodels = NULL) {       
                    scores <- as.matrix(newdata)  %*% modelFit$projection
                    predict(modelFit, scores)$class
                  },
                  prob = function(modelFit, newdata, submodels = NULL) {       
                    scores <- as.matrix(newdata)  %*% modelFit$projection
                    predict(modelFit, scores)$posterior
                  },
                  varImp = NULL,
                  predictors = function(x, ...) rownames(x$projection),
                  levels = function(x) x$obsLevels,
                  sort = function(x) x[order(x[,1]),])  

По запросу Зака ​​приведенный ниже код предназначен для method="pls" в знаке вставки, точно такой же конкретный пример в виде символа вставки на КРАН:

library(mlbench) # data set from here
data(Sonar)
dim(Sonar) # 208x60
set.seed(107)
inTrain <- createDataPartition(y = Sonar$Class,
                               ## the outcome data are needed
                               p = .75,
                               ## The percentage of data in the
                               ## training set
                               list = FALSE)
## The format of the results
## The output is a set of integers for the rows of Sonar
## that belong in the training set.
training <- Sonar[ inTrain,] #157
testing <- Sonar[-inTrain,] # 51

ctrl <- trainControl(method = "repeatedcv",
                     repeats = 3,
                     classProbs = TRUE,
                     summaryFunction = twoClassSummary)
set.seed(108)
plsFitSon <- train(Class ~ .,
                data = training,
                method = "pls",
                tuneLength = 15,
                trControl = ctrl,
                metric = "ROC",
                preProc = c("center", "scale"))
plsFitSon
plot(plsFitSon) # might be slightly difference than what in the vignette due to radnomness  

Теперь приведенный ниже код представляет собой пилотный запуск для классификации данных сонара с использованием пользовательской модели PLS-LDA, которая находится под вопросом. Ожидается, что будут получены любые числа, кроме идентичных тем, которые используют только PLS:

set.seed(108)
plsldaFitSon <- train(Class ~ .,
                   data = training,
                   method = modelInfo,
                   tuneLength = 15,
                   trControl = ctrl,
                   metric = "ROC",
                   preProc = c("center", "scale"))  

Теперь сравним результаты между двумя моделями:

getTrainPerf(plsFitSon)
   TrainROC TrainSens TrainSpec method
1 0.8741154 0.7638889 0.8452381    pls
getTrainPerf(plsldaFitSon)
   TrainROC TrainSens TrainSpec method
1 0.8741154 0.7638889 0.8452381 custom

postResample(predict(plsFitSon, testing), testing$Class)
Accuracy    Kappa 
0.745098 0.491954 
postResample(predict(plsldaFitSon, testing), testing$Class)
Accuracy    Kappa 
0.745098 0.491954 

Итак, результаты точно такие же, которых быть не может. Как бы не добавилась модель lda?

person doctorate    schedule 14.01.2014
comment
Если я скопирую и вставлю ваш modelInfo и код поезда/теста Макса, я получу разные результаты для method=modelInfo и method='lda'. Пожалуйста, опубликуйте свой код (вместе со случайными семенами), где вы получите разные результаты. - person Zach; 14.01.2014
comment
@ Зак, я добавил пример Sonar. Но если вы попробовали method="pls", а затем modelInfo, который представляет собой пользовательский PLS-LDA, я думаю, вы получите такие же результаты. - person doctorate; 14.01.2014
comment
Я смог воспроизвести то же самое в данных сонара. См. мой комментарий выше о том, почему (или, по крайней мере, мое предположение, почему). - person topepo; 14.01.2014
comment
Привет, как я могу реализовать MASS::polr упорядоченную логистическую регрессию в методе каретки? Большое спасибо - person Rafik Margaryan; 18.06.2014
comment
Для обучения кажется, что вы изменили форму поезда (X, Y), но вы предоставили форму поезда (класс ~ ., ...). Попробуйте извлечь X, Y из обучения и используйте формат train (X, Y). - person ; 24.02.2015