(в R) Почему результат ksvm, использующего пользовательское линейное ядро, отличается от результата ksvm, использующего vanilladot?
Я хотел использовать пользовательскую функцию ядра для Ksvm в R. Итак, я попытался создать ядро vanilladot и сравнить с "vanilladot", который встроен в "kernlab", на практике.
Я пишу свое ядро следующим образом.
#
###vanilla kernel with class "kernel"
#
kfunction.k <- function(){
k <- function (x,y){crossprod(x,y)}
class(k) <- "kernel"
k}
l<-0.1 ; C<-1/(2*l)
###use kfunction.k
tmp<-ksvm(x,factor(y),scaled=FALSE, type = "C-svc", kernel=kfunction.k(), C = C)
alpha(tmp)[[1]]
ind<-alphaindex(tmp)[[1]]
x.s<-x[ind,] ; y.s<-y[ind]
w.class.k<-t(alpha(tmp)[[1]]*y.s)%*%x.s
w.class.k
Я полагаю, что результат этой операции аналогичен последующему. Однако это не так.
#
###use "vanilladot"
#
l<-0.1 ; C<-1/(2*l)
tmp1<-ksvm(x,factor(y),scaled=FALSE, type = "C-svc", kernel="vanilladot", C = C)
alpha(tmp1)[[1]]
ind1<-alphaindex(tmp1)[[1]]
x.s<-x[ind1,] ; y.s<-y[ind1]
w.tmp1<-t(alpha(tmp1)[[1]]*y.s)%*%x.s
w.tmp1
Я думаю, возможно, эта проблема связана с классом ядра. Когда класс установлен на "ядро", эта проблема возникает. Однако когда для класса установлено значение "vanillakernel", результат использования ksvm с использованием определенного пользователем ядра будет равен результату ksvm с использованием "vanilladot", встроенного в Kernlab.
#
###vanilla kernel with class "vanillakernel"
#
kfunction.v.k <- function(){
k <- function (x,y){crossprod(x,y)}
class(k) <- "vanillakernel"
k}
# The only difference between kfunction.k and kfunction.v.k is "class(k)".
l<-0.1 ; C<-1/(2*l)
###use kfunction.v.k
tmp<-ksvm(x,factor(y),scaled=FALSE, type = "C-svc", kernel=kfunction.v.k(), C = C)
alpha(tmp)[[1]]
ind<-alphaindex(tmp)[[1]]
x.s<-x[ind,] ; y.s<-y[ind]
w.class.v.k<-t(alpha(tmp)[[1]]*y.s)%*%x.s
w.class.v.k
Я не понимаю, почему результат отличается от "vanilladot" при установке класса в "ядро".
Есть ли ошибка в моей работе?
1 ответ
Во-первых, это действительно хороший вопрос!
Теперь к делу. В источниках ksvm
мы можем найти, когда проходит граница между использованием определенного пользователем ядра и встроенными модулями:
if (type(ret) == "spoc-svc") {
if (!is.null(class.weights))
weightedC <- class.weights[weightlabels] * rep(C,
nclass(ret))
else weightedC <- rep(C, nclass(ret))
yd <- sort(y, method = "quick", index.return = TRUE)
xd <- matrix(x[yd$ix, ], nrow = dim(x)[1])
count <- 0
if (ktype == 4)
K <- kernelMatrix(kernel, x)
resv <- .Call("tron_optim", as.double(t(xd)), as.integer(nrow(xd)),
as.integer(ncol(xd)), as.double(rep(yd$x - 1,
2)), as.double(K), as.integer(if (sparse) xd@ia else 0),
as.integer(if (sparse) xd@ja else 0), as.integer(sparse),
as.integer(nclass(ret)), as.integer(count), as.integer(ktype),
as.integer(7), as.double(C), as.double(epsilon),
as.double(sigma), as.integer(degree), as.double(offset),
as.double(C), as.double(2), as.integer(0), as.double(0),
as.integer(0), as.double(weightedC), as.double(cache),
as.double(tol), as.integer(10), as.integer(shrinking),
PACKAGE = "kernlab")
reind <- sort(yd$ix, method = "quick", index.return = TRUE)$ix
alpha(ret) <- t(matrix(resv[-(nclass(ret) * nrow(xd) +
1)], nclass(ret)))[reind, , drop = FALSE]
coef(ret) <- lapply(1:nclass(ret), function(x) alpha(ret)[,
x][alpha(ret)[, x] != 0])
names(coef(ret)) <- lev(ret)
alphaindex(ret) <- lapply(sort(unique(y)), function(x)
which(alpha(ret)[,
x] != 0))
xmatrix(ret) <- x
obj(ret) <- resv[(nclass(ret) * nrow(xd) + 1)]
names(alphaindex(ret)) <- lev(ret)
svindex <- which(rowSums(alpha(ret) != 0) != 0)
b(ret) <- 0
param(ret)$C <- C
}
Важными частями являются две вещи, во-первых, если мы предоставим ksvm
с нашим собственным ядром, то ktype=4
(пока для vanillakernel
, ktype=0
) поэтому он делает два изменения:
- в случае пользовательского ядра матрица ядра вычисляется вместо фактического использования ядра
tron_optim
рутина запускается с информацией о ядре
Теперь в svm.cpp
мы можем найти tron
рутины, и в tron_run
(вызывается из tron_optim
), тот LINEAR
ядро имеет отдельную подпрограмму оптимизации
if (param->kernel_type == LINEAR)
{
/* lots of code here */
while (Cpj < Cp)
{
totaliter += s.Solve(l, prob->x, minus_ones, y, alpha, w,
Cpj, Cnj, param->eps, sii, param->shrinking,
param->qpsize);
/* lots of code here */
}
totaliter += s.Solve(l, prob->x, minus_ones, y, alpha, w, Cp, Cn,
param->eps, sii, param->shrinking, param->qpsize);
delete[] w;
}
else
{
Solver_B s;
s.Solve(l, BSVC_Q(*prob,*param,y), minus_ones, y, alpha, Cp, Cn,
param->eps, sii, param->shrinking, param->qpsize);
}
Как видите, линейный случай рассматривается более сложным, более подробным образом. Существует внутренний цикл оптимизации, вызывающий решатель много раз. Это потребовало бы действительно глубокого анализа фактической оптимизации, выполняемой здесь, но на этом этапе можно ответить на ваш вопрос следующим образом:
- Там нет ошибки в вашей работе
- У svm в Kernlab есть отдельная подпрограмма для обучения SVM с линейным ядром, которая основана на типе ядра, переданного в код, и изменение "kernel" на "vanillakernel" сделало
ksvm
думаю, что на самом деле он работает с vanillakernel, и поэтому выполнил эту отдельную процедуру оптимизации - На самом деле это не кажется ошибкой, поскольку линейный SVM на самом деле сильно отличается от версии с ядром с точки зрения эффективных методов оптимизации. Количество эвристических, а также числовых проблем, о которых нужно позаботиться, действительно велико. В результате требуются некоторые приближения, которые могут привести к различным результатам. В то время как для богатого пространства признаков (например, вызванного ядром RBF) это не должно иметь большого значения, для простых ядер линейные линейные - это упрощение может привести к значительным выходным изменениям.