支持向量机的一般原理
支持向量机(SVW,support vector machine)跟逻辑回归比较像。可以说,支持向量机是逻辑回归的一种优化或者扩展。因此,虽然说支持向量机既可以处理分类问题,也可以处理回归问题,但是它一般还是主要用于分类问题。而且其本质是处理二元分类问题。
简单说,支持向量机就是找到个(决策)边界(或者理解成分界线,或超平面),既能将两个分类进行完美的区分,又能使得该边界距离两个分类之间的间距达到最大。(因为只有间距最大,才更具有鲁棒性,分类器的效果才最好)。
1.最大边缘分类
边缘(margin)
数据集中任何一点到该超平面的最小正交距离
最大边缘超平面
寻找一条直线,可以分隔两个类别,尽可能远离观测数据
如何找到这个“超平面”?
在逻辑回归中,因为数据要分成两类,用0,1来进行的两类区分。而且为了最后使得
的值能落在0,1之间,引入了逻辑函数
此外,hθ(x)只和θTx有关,θTx>0,那么hθ(x)>0.5,而g(z)只是用来映射,真实的类别决定权还是在于θTx。
当θTx>>0时,hθ(x)=1,反之hθ(x)=0。如果只从θTx出发,希望模型达到的目标就是让训练数据中y=1的特征θTx>>0,而是y=0的特征θTx<<0。Logistic回归就是要学习得到θ,使得正例的特征远大于0,负例的特征远小于0,而且要在全部训练实例上达到这个目标。 在svm中,对上述逻辑回归稍微做一点点调整。首先,还是讲数据分为两类,但是用-1,1来进行区分。因此,引入一个新的g(z),能够使得映射后的值落在[-1,1] 之间。
注意,这个新的映射函数g(z) 是一个分段函数,而不是之前在逻辑回归中的连续函数。换言之,在进行分类的时候,遇到一个新的数据点x,将x代入f(x) 中,如果f(x)小于0则将x的类别赋为-1,如果f(x)大于0则将x的类别赋为1。
假设已经找到了这个超平面,用表示。一定会满足如下几点:
1)在分类为1的所有样本中,一定存在一个点,其距离超平面的距离最近。设为x0;
2)在分类为-1的所有样本中,一定存在一个点,其距离超平面的距离最近。设为x1;
3)将x0带入到上面的式子中,得到的f(x0),一定大于0。且其他所有分类为1的样本点,都有f(xn)≥f(x0);
4) 同理,将x1带入到上面的式子中,得到的f(x1),一定小于0。且其他所有分类为-1的样本点,都有f(xm)≤f(x1);
5)对于x0或者x1到超平面f(x) 的函数距离|f(x0)|,|f(x1)|,无论是多少,都不会影响超平面f(x)=0的存在。即,如果成比例的改变w和b,函数距离|f(x0)|,|f(x1)|也会成比例改变,但是,对于要求解的超平面是不会有影响的;
2.软边缘(soft margin)
允许某些观测数据可以违反距离分隔超平面至少要和边缘一样远的约束条件可以引入一个松弛变量εi,令εi≥0,即
增加软边缘后我们的优化目标变成了:
这里的C,就跟之前过拟合中的λ一样,是一个惩罚因子。如果C设的很大,ε就会很小,就相当于是硬边缘,要求完全线形可分。反之,如果C设得比较小,ε就会相对较大,此时将会允许有跟多的样本点不满足约束条件。
3.核函数
还有一些数据没法直接用一条直线分开,这类数据叫做非线性可分数据。比如下图,一维数据红点和蓝点位于同一条线上,找不到分界点将红点和蓝点分开。
对于这种问题,如果用函数f(x)=x2来映射这几个数据,增加一个维度,将一维数据转换为二维数据,红点就变成(-1, 1)和(1, 1),而蓝点变成(-3, 9)和(3, 9)。这样红点和蓝点就变成线性可分的,可以很容易地找到决策边界。
所以,可以将低维空间中的非线性可分离数据,映射到高维空间,就可能得到线性可分离数据。
这就如同在逻辑回归中提到的一样,可以使用高级数的多项式模型来解决无法用直线进行分隔的分类问题:
的形式。
但是这里有个问题:原来的特征是低维的,人为的构造了一些特征,让其变成高维的了。那到底要如何构造特征,才能确保到了高维后,一定线形可分呢?
如何构造新的特征?介绍一个新的构造特征的思路:即引入某种函数,这些函数用来计算当前训练样本与给定标记之间的相似度。如果给定了5个标记,那么就会构造出5个新的特征。这些变换过的特征,代表当前所有训练样本,与这5个标记之间的相似程度。这就是核函数的思想。
那么,如何选定标记呢? 一个最简单的做法,即是将训练集的所有样本作为标记。一条样本代表一个标记,有m个样本,就有m个标记。那么转换后的特征就有m个(实际是m+1个,这里不考虑截距项)。要做的事情,就是用交叉验证或者测试集或者直接再用训练集来计算出m个特征的参数θ。
核函数的作用就是接收低维空间的输入向量,能够计算出在高维空间里的向量点积。
4.预测化学品的生物降解
- 数据集:biodeg.csv
- 41个数值型变量,1055种化学品的分子组成
- V42包含输出,不可生物降解NRB,可生物降解RB
- 使用e1071包,svm函数
设置scale=TRUE,输入特征归一化
在R中进行支持向量机的模型预测比较简单,可以直接使用e1071包的svm函数即可实现。
#导入数据,预测化学品的生物降解
bdf <- read.table("biodeg.csv", sep=";", quote="\"")
head(bdf, n = 3) #查看数据,有41个特征。第42列包含输出,不可生物降解为NRB,可生物降解为RB
#将第42列因子化
bdf$V42 <- factor(bdf$V42)
#将第42列变为0,1
levels(bdf$V42)<-c(0,1)
#区分测试集和训练集
library(caret)
library(tidyverse)
set.seed(23419002)
bdf_sampling_vector <- createDataPartition(bdf$V42, p = 0.80, list = FALSE)
bdf_train <- bdf[bdf_sampling_vector,]
bdf_test <- bdf[-bdf_sampling_vector,]
#区分测试集和训练集
library(e1071)
#用支持向量机来进行模型构建。kernel核函数,此处为线性核函数;cost代表损失函数的值,即之前讲到的C
model_lin<-svm(V42~.,data=bdf_train,kernel="linear",cost=10)
model_lin$fitted #查看训练后的结果
#计算训练后的模型准确度。可以看到,在训练集上有87.9%的准确率。
mean(bdf_train[,42] == model_lin$fitted)
#查看混淆矩阵
table(actual = bdf_train[,42],predictions = model_lin$fitted)
#在测试集上进行预测
test_predictions <- predict(model_lin,bdf_test[,1:41])
#查看测试集的准确度
mean(bdf_test[,42] == test_predictions)
可以看到,测试集上的准确度达到了91.9%。
这里有个问题。cost到底取多少算合适呢?所以,面临一个参数调优的问题。参数调优,可以手工来进行,也可也自动来进行。
先看手工方式(线性核kernel="linear"):
#定义一函数,用于计算不同cost下的准确度
getAccuraciesOfLinearSVM<-function(cost) {
#线性核只需要控制cost,根据给定参数cost来拟合模型
model_lin<-svm(V42~.,data=bdf_train,kernel="linear",cost=cost)
#计算训练集的准确度。signif用于数据截取函数,digits表示保留3位小数
train_accuracy <- signif(mean(bdf_train[,42] == model_lin$fitted), digits = 3)
test_predictions <- predict(model_lin,bdf_test[,1:41]) #预测测试集
test_accuracy <- signif(mean(bdf_test[,42] == test_predictions), digits = 3) #计算测试集准确度
return(list(training=train_accuracy, test=test_accuracy))
}
#手工取c为0.01, 0.1, 1, 10, 100, 1000这几个数,观察效果
cost <- c(0.01, 0.1, 1, 10, 100, 1000)
linearPerformances <- sapply(cost, getAccuraciesOfLinearSVM)
colnames(linearPerformances) <- cost #给输出结果取个列名
linearPerformances
径向核Radial Kernel,需要指定gamma
#径向核指定gamma
model_radial<-svm(V42~., data=bdf_train, kernel="radial", cost=10, gamma=0.5)
mean(bdf_train[,42] == model_radial$fitted)
table(actual = bdf_train[,42],predictions = model_radial$fitted)
test_predictions <- predict(model_radial,bdf_test[,1:41])
mean(bdf_test[,42] == test_predictions)
定义一函数,用于计算不同cost/gamma下的准确度
getAccuraciesOfRadialSVM<-function(cost, gamma) {
model_radial<-svm(V42~.,data=bdf_train,kernel="radial",cost=cost, gamma=gamma)
train_accuracy <- signif(mean(bdf_train[,42] == model_radial$fitted), digits = 3)
test_predictions <- predict(model_radial,bdf_test[,1:41])
test_accuracy <- signif(mean(bdf_test[,42] == test_predictions), digits = 3)
return(list(training=train_accuracy, test=test_accuracy))
}
cost <- c(0.01, 0.1, 1, 10, 100)
gamma <- c(0.01, 0.05, 0.1, 0.5, 1)
grid_params <- expand.grid(cost, gamma)
radialPerformances_u <- mapply(getAccuraciesOfRadialSVM,
grid_params[,1], grid_params[,2])
radialPerformances <- t(data.frame(cost = grid_params[,1],
gamma = grid_params[,2],
t(radialPerformances_u)))
radialPerformances
library(caret)
# tune交叉检验
bdf_radial_tune <- tune(svm,V42~.,data=bdf_train, kernel="radial",
ranges = list(cost=c(0.01,0.1,1,10,100),
gamma=c(0.01,0.05,0.1,0.5,1)))
还可以使用caret包中的tune来自动完成调参工作。
预测信用评分
- 对银行来说,把一位高风险客户错误分类成为低风险客户的成本比把一位低风险客户错误分类成为高风险客户的成本要高5倍
如:银行发放了无法偿还的贷款
错过了能够产生利息的贷款 - svm()函数有一个class.weights参数
指定把一条观测数据错误分类到每个类别的成本
#读取数据(信贷数据)
german_raw <- read.table("german.data", quote="\"")
#重新命名列名
names(german_raw) <- c("checking", "duration", "creditHistory", "purpose", "credit", "savings", "employment", "installmentRate", "personal", "debtors", "presentResidence", "property", "age", "otherPlans", "housing", "existingBankCredits", "job", "dependents", "telephone", "foreign", "risk")
library(caret)
#将一些因子变量变为虚拟变量,同时将risk因子化,且变为0和1(之前是1和2)
dummies <- dummyVars(risk ~ ., data = german_raw)
german<- data.frame(predict(dummies, newdata = german_raw), risk=factor((german_raw$risk-1)))
#划分训练集和测试集
set.seed(977)
german_sampling_vector <- createDataPartition(german$risk, p = 0.80, list = FALSE)
german_train <- german[german_sampling_vector,]
german_test <- german[-german_sampling_vector,]
#这里增加了权重。在信贷问题上,如果把一个高风险客户识别为低风险客户,对银行有可能造成较大损失,反之则不然。因此这里要对分类结果增加权重。
#把分类为低风险客户设置为5,高风险客户设置为1,分到低风险客户的惩罚项是高风险客户的5倍
#结果会偏向于尽可能的被分为高风险客户
class_weights <- c(1,5)
names(class_weights) <- c("0","1")
class_weights
set.seed(2423)
#使用tune进行自动调参
german_radial_tune <- tune(svm,risk~.,data=german_train, kernel="radial", ranges = list(cost=c(0.01,0.1,1,10,100),
gamma=c(0.01,0.05,0.1,0.5,1)),
class.weights = class_weights)
#查看调参后的最优参数组合
german_radial_tune$best.parameters
#在最优参数组合下的性能
german_radial_tune$best.performance
#根据调参得到的最优模型在测试集上进行预测,可以看到,在测试集上有75%的准确率
german_model <- german_radial_tune$best.model
test_predictions <- predict(german_model,german_test[,1:61])
mean(test_predictions == german_test[,62])
table(predicted = test_predictions, actual = german_test[,62])
set.seed(2423)
#不设权重的结果
german_radial_tune_unbiased <- tune(svm,risk~.,data=german_train, kernel="radial", ranges = list(cost=c(0.01,0.1,1,10,100), gamma=c(0.01,0.05,0.1,0.5,1)))
german_radial_tune_unbiased$best.parameters
german_radial_tune_unbiased$best.performance