一. SVM模型的基本概念
1.1 从线性判别说起
如果需要构建一个分类器将上图中的黄点与蓝点分开,最简单的方法就是在平面中选择一条直线将二者分开,使得所有的黄点与蓝点分属直线的两侧。这样的直线有无穷多种选择,但是什么样的直线是最优的呢?
显而易见的是,中间的红色的分割线的效果会比蓝色虚线与绿色虚线的效果好。原因是,需要分类的样本点普遍距离红线比较远,因而健壮性更强。相反,蓝色虚线与绿色虚线分别距离几个样本点很近,从而在加入新的样本点后,很容易发生错误分类。
1.2 支持向量机(SVM)的基本概念
点到超平面的距离
在上述的分类任务中,为了获取稳健的线性分类器,一个很自然的想法是,找出一条分割线使得两侧样本与该分割线的平均距离足够的远。在欧式空间中,定义一个点𝒙到直线(或者高维空间中的超平面)的距离公式是:
在分类问题中,如果这样的分割线或者分割平面能够准确地将样本分开,对于样本 而言,若,则有,反之若,则有
支持向量与间隔
对于满足的样本,它们一定落在2个超平面上。这些样本被称为“支持向量(support vector)”,这2个超平面称为最大间隔边界。分属不同类别的样本距离分割平面的距离之和为
该距离之和被称为“间隔”
二. SVM的目标函数和对偶问题
2.1 支持向量机的优化问题
因此,对于完全线性可分的样本,分类模型的任务就是找出这样的超平面,满足
等价于求解带约束的最小化问题:
2.2 优化问题的对偶问题
一般来说,求解带等式或不等式约束的优化问题时,通常使用拉格朗日乘子法将原问题转换成对偶问题。在SVM的优化问题中,相应的对偶问题为:
对𝐿(𝑤,𝑏,𝛼)求关于𝑤,𝑏,𝛼的偏导数并且令为0,有:
最终的优化问题转化成
解出𝛼后,求出𝑤,𝑏即可得到模型。一般使用SMO算法求解。
2.3 支持向量与非支持向量
注意到,是不等式约束,因此需要满足(这是KKT条件中关于不等式约束的条件)。因此,满足这样的条件的样本,要么, 要么。因此对于SVM的训练样本来讲,
如果,则的计算中不会出现该样本
如果,则该样本处于最大间隔边界上
从这一点可以看出,大部分训练样本最后都不会对模型的求解有任何影响,仅支持向量影响到模型的求解。
三. 软间隔
3.1 线性不可分
在一般的业务场景中,线性可分是可遇而不可求的。更多是线性不可分的情形,即无法找出这样的超平面可以完全正确地将两类样本分开。
为了解决这个问题,一个方法是我们允许部分样本被错误的分类(但是不能太多!) 。带有错误分类的间隔,称之为“软间隔”。于是,目标函数仍然是带约束的最大化间隔,其中约束条件是,不满足的样本越少越好。
3.2 损失函数
基于这个思想,我们改写了优化函数
使其变为
可用的损失函数有:
3.3 松弛变量
当使用hinge loss的时候,损失函数变为
3.4 求解带松弛变量的软间隔SVM
令𝐿(𝑤,𝑏,𝛼,𝜂,𝜇)关于𝑤,𝑏, 𝜂的偏导等于0,则有:
3.5 支持向量与非支持向量
同样地,拉格朗日乘子也需满足和的条件。对于某样本,
当时,样本不会对模型有任何影响
当 此时有,该样本落在最大间隔边界上
当时,。此时若有,该样本落在最大间隔内部,属于正确分类的情况;若有,该样本落在最大间隔之间,属于错误分类的情况。
表明在hinge loss的情况下,带软间隔的SVM模型仍然只与支持向量有关。
四. 核函数
4.1 从低维到高维
在SVM的优化目标函数中,是参数,是类别,二者的形式是固定的。但是交互项是独立的,我们完全可以讲其进行拓展。注意到我们有两种拓展的办法:
拓展一:, 此时目标函数变为
拓展二:,此时目标函数变为
拓展一能够将𝑥映射到更高维的空间,从而将在低维不可分的情形变为在高维空间可分。这是除了软间隔之外,另一种解决线性不可分的办法。
线性不可分:
线性可分:
4.2 核函数
4.3 核函数的选择
一些先验经验
- 如果特征数远远大于样本数 ,使用线性核就可以了
- 如果特征数和样本数都很大,例如文档分类,一般使用线性核
- 如果特征数远小于样本数,这种情况一般使用RBF
或者使用交叉验证法选择最合适的核函数
4.4 SVM模型的优缺点
优点:
- 适合小样本的分类
- 泛化能力强
- 局部最优解一定是全局最优解
缺点:
- 计算量很大,大规模训练样本很难实施
- 给出的结果是硬分类而非基于概率的软分类。SVM也可输出概率,但是计算更加复杂
五. 案例
5.1 数据预处理
跟“随机森林”中的案例一样,我们选择前若干个变量来构建SVM模型,需要注意的是,SVM模型和RF模型对数据质量有不同的要求:
- SVM模型只能处理数值型变量,RF可以处理数值与非数值型变量
- SVM不能处理带有缺失值的变量,但是RF可以
- SVM对异常值敏感,RF对异常值不敏感
综上,在构建SVM模型前我们需要对变量进行如下工作:
- 缺失值填补
- 对非数值型变量进行数值编码,可以用哑变量或者独热编码
- 处理异常值,进行归一化
5.2 降维
从SMO算法中可以看出,当特征个数较多时,SVM的计算量很大。我们引入变量挑选的环节。做变量挑选是为了:
剔除不显著的变量,减少噪声干扰
降低计算开销
借助随机森林,我们对原始变量做特征重要性评估,结果如右图所示。我们选择importance高于0.05的变量带入到模型构建的工作中。
5.3 调参
在SVM中,最重要的参数是核函数与软间隔参数C。常用的核函数有线性核函数与高斯核函数。因此我们从“linear”&“rbf”中进行挑选。同时令C的初步选择空间是{1,11,21,..,191}。
第一步迭代后,最优的核函数是rbf,最优的C是171.
由于C的选择是步长为10的序列,因此我们缩小选择空间、降低步长,从{162,163,…,180}中选择最优的C,同时核函数依旧从“linear”&“rbf”中进行挑选。
第二部迭代后,最优的核函数是rbf,最优的C是173.
因此我们用kernel=‘rbf’,C=173来构建模型。
5.4 预测
得到最优的SVM模型后,我们在训练集上进行预测,预测结果与真实值的混淆矩阵如下所示:
所有违约样本中,被识别出来的样本的占比(即召回率)为3371/(3371+1891)=64.1%
同时,有28319/(28319+29774)=48.7%的非违约样本被误判成违约。
5.5 代码
代码:
import pandas as pd
import random
import numpy as np
import matplotlib.pyplot as plt
from hyperopt import hp
from sklearn import model_selection, metrics
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
def Outlier(x, k = 1.5):
[lower, upper] = list(np.percentile(x, [25, 75]))
d = upper - lower
ceiling = min(max(x), upper + k * d)
floor = max(min(x), lower - k * d)
return [floor, ceiling]
def Min_Max_Standardize(x, floor, ceiling):
d2 = ceiling - floor
x2 = x.apply(lambda y: max(0,min((ceiling - y)/d2,1)))
return x2
#读取数据,对数据进行筛选。由于原始数据中包含2种不同种类的贷款,我们只选取其中一个进行建模。
mydata = pd.read_csv('E:/file/application_train_small.csv')
cash_loan_data = mydata[mydata['NAME_CONTRACT_TYPE'] == 'Cash loans']
selected_features = ['CODE_GENDER','FLAG_OWN_CAR','LIVE_CITY_NOT_WORK_CITY', 'ORGANIZATION_TYPE',
'FLAG_OWN_REALTY','CNT_CHILDREN','AMT_INCOME_TOTAL','AMT_CREDIT','WEEKDAY_APPR_PROCESS_START',
'AMT_ANNUITY','AMT_GOODS_PRICE','NAME_TYPE_SUITE','NAME_INCOME_TYPE','OCCUPATION_TYPE',
'NAME_EDUCATION_TYPE','NAME_FAMILY_STATUS','NAME_HOUSING_TYPE','REGION_POPULATION_RELATIVE',
'DAYS_BIRTH','DAYS_EMPLOYED','DAYS_REGISTRATION','DAYS_ID_PUBLISH','OWN_CAR_AGE','FLAG_MOBIL',
'FLAG_EMP_PHONE','FLAG_WORK_PHONE','FLAG_CONT_MOBILE','FLAG_PHONE','FLAG_EMAIL',
'CNT_FAM_MEMBERS','REGION_RATING_CLIENT','REGION_RATING_CLIENT_W_CITY',
'HOUR_APPR_PROCESS_START','REG_REGION_NOT_LIVE_REGION','REG_REGION_NOT_WORK_REGION',
'LIVE_REGION_NOT_WORK_REGION','REG_CITY_NOT_LIVE_CITY','REG_CITY_NOT_WORK_CITY']
all_data = cash_loan_data[['TARGET']+selected_features]
train_data, test_data = model_selection.train_test_split(all_data, test_size=0.3)
#########################
#### 2,数据预处理 #####
#########################
#注意到,变量OWN_CAR_AGE和FLAG_OWN_CAR有对应关系:当FLAG_OWN_CAR='Y'时,OWN_CAR_AGE无缺失,否则OWN_CAR_AGE为有缺失
#这种缺失机制属于随机缺失。
#此外,对于非缺失的OWN_CAR_AGE,我们发现有异常值,例如0, 1,2等,无法判断该变量的含义,建议将其删除
selected_features.remove('OWN_CAR_AGE')
del train_data['OWN_CAR_AGE']
#变量OCCUPATION_TYPE和NAME_TYPE_SUITE属于类别型变量,可用哑变量进行编码
categorical_features = ['CODE_GENDER','FLAG_OWN_CAR','FLAG_OWN_REALTY','NAME_TYPE_SUITE','NAME_INCOME_TYPE','NAME_EDUCATION_TYPE',
'NAME_FAMILY_STATUS','NAME_HOUSING_TYPE','OCCUPATION_TYPE','WEEKDAY_APPR_PROCESS_START','ORGANIZATION_TYPE']
numerical_features =[ i for i in selected_features if i not in categorical_features]
train_data_2 = pd.get_dummies(data=train_data, columns=categorical_features)
#删除AMT_ANNUITY缺失的样本
train_data_2 = train_data_2[~train_data_2['AMT_ANNUITY'].isna()]
#######################
#### 3,特征衍生 #####
#######################
train_data_2['credit_to_income'] = train_data_2.apply(lambda x: x['AMT_CREDIT']/x['AMT_INCOME_TOTAL'],axis=1)
train_data_2['annuity_to_income'] = train_data_2.apply(lambda x: x['AMT_ANNUITY']/x['AMT_INCOME_TOTAL'],axis=1)
train_data_2['price_to_income'] = train_data_2.apply(lambda x: x['AMT_GOODS_PRICE']/x['AMT_INCOME_TOTAL'],axis=1)
numerical_features.append('credit_to_income')
numerical_features.append('annuity_to_income')
numerical_features.append('price_to_income')
#有四个与时长相关的变量DAYS_BIRTH,DAYS_EMPLOYED,DAYS_REGISTRATION ,DAYS_ID_PUBLISH中带有负号,不清楚具体的含义。
#我们在案例中仍然保留4个变量,但是建议在真实场景中获得字段的真实含义
#####################
#### 4,归一化 #####
#####################
#归一化工作中要考虑到极端值的影响。如果有极端值存在,则需要先排除极端值再做归一化
col_floor_ceiling={}
for col in numerical_features:
if min(train_data_2[col]) == 0 and max(train_data_2[col]) == 1:
continue
[floor, ceiling] = Outlier(train_data_2[col])
if ceiling == floor:
print('{} is a constant variable'.format(col))
continue
col_floor_ceiling[col] = [floor, ceiling]
train_data_2[col] = train_data_2.apply(lambda x: max(0,min((ceiling - x[col])/(ceiling-floor),1)), axis=1)
##########################
#### 5, 构建SVM模型 #####
#########################
all_features = list(train_data_2.columns)
all_features.remove('TARGET')
X0, y = train_data_2[all_features], train_data_2['TARGET']
RFC = RandomForestClassifier()
RFC.fit(X0,y)
feature_importance = pd.DataFrame({'feature':all_features,'importance':RFC.feature_importances_})
feature_importance = feature_importance.sort_values(by='importance', ascending=False)
plt.bar(x=range(feature_importance.shape[0]),height = feature_importance['importance'])
import_features = feature_importance[feature_importance['importance'] >= 0.05]
X = train_data_2[list(import_features['feature'])]
#使用GridSearch选择最优参数组合
###### 先从大范围内尝试参数 ######
parameters = {'kernel':('linear', 'rbf'), 'C':range(1,200,10)}
svc = SVC(class_weight = 'balanced')
clf = model_selection.GridSearchCV(svc, parameters, scoring='f1')
clf.fit(X, y)
sorted(clf.cv_results_.keys())
best_C, best_kernel = clf.best_params_['C'],clf.best_params_['kernel'] #best_C = 171
#best_C
parameters = {'kernel':('linear', 'rbf'), 'C':range(162,181)}
svc = SVC(class_weight = 'balanced')
clf = model_selection.GridSearchCV(svc, parameters, scoring='f1')
clf.fit(X, y)
best_C, best_kernel = clf.best_params_['C'],clf.best_params_['kernel'] #best_C =173,best_kernel=rbf
clf_best = SVC(C = best_C, kernel = best_kernel, class_weight = 'balanced')
clf_best.fit(X, y)
y_pred_train = np.mat(clf_best.predict(X))
f1_train = metrics.f1_score(y, y_pred_train.getA()[0])
conf_mat = metrics.confusion_matrix(y, y_pred_train.getA()[0])
tn, fp, fn, tp = conf_mat.ravel()
recall = tp/(tp+fn)
print(recall)
plt.show()
测试记录:
数据量不大,代码居然跑了2天左右
FLAG_MOBIL is a constant variable
REGION_RATING_CLIENT is a constant variable
REGION_RATING_CLIENT_W_CITY is a constant variable
0.6977093503567405