【机器学习(五)】支持向量机

支持向量机的一般原理

【机器学习】支持向量机 SVM(非常详细)

支持向量机(SVW,support vector machine)跟逻辑回归比较像。可以说,支持向量机是逻辑回归的一种优化或者扩展。因此,虽然说支持向量机既可以处理分类问题,也可以处理回归问题,但是它一般还是主要用于分类问题。而且其本质是处理二元分类问题

简单说,支持向量机就是找到个(决策)边界(或者理解成分界线,或超平面),既能将两个分类进行完美的区分,又能使得该边界距离两个分类之间的间距达到最大。(因为只有间距最大,才更具有鲁棒性,分类器的效果才最好)。

1.最大边缘分类

边缘(margin)
数据集中任何一点到该超平面的最小正交距离
最大边缘超平面
寻找一条直线,可以分隔两个类别,尽可能远离观测数据

如何找到这个“超平面”?

逻辑回归中,因为数据要分成两类,用0,1来进行的两类区分。而且为了最后使得

的值能落在0,1之间,引入了逻辑函数

,并且给g(z)赋予了一个意义,即代表了分类结果为1时的概率。函数值越靠近1,代表分类结果为1的可能性越大。函数值越靠近0,代表分类结果为0的可能性越大。
此外,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)|也会成比例改变,但是,对于要求解的超平面是不会有影响的;

6)根据点到直线的距离公式,可以知道x0到超平面f(x) 的几何距离为:|f(x0)|/||w||。(x1是一样的),其中

所以,SVM的目标函数为:

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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容