金融业信贷风控算法8-支持向量机

一. SVM模型的基本概念

1.1 从线性判别说起

image.png
image.png

如果需要构建一个分类器将上图中的黄点与蓝点分开,最简单的方法就是在平面中选择一条直线将二者分开,使得所有的黄点与蓝点分属直线的两侧。这样的直线有无穷多种选择,但是什么样的直线是最优的呢?

显而易见的是,中间的红色的分割线的效果会比蓝色虚线与绿色虚线的效果好。原因是,需要分类的样本点普遍距离红线比较远,因而健壮性更强。相反,蓝色虚线与绿色虚线分别距离几个样本点很近,从而在加入新的样本点后,很容易发生错误分类。

1.2 支持向量机(SVM)的基本概念

点到超平面的距离
在上述的分类任务中,为了获取稳健的线性分类器,一个很自然的想法是,找出一条分割线使得两侧样本与该分割线的平均距离足够的远。在欧式空间中,定义一个点𝒙到直线(或者高维空间中的超平面)𝒘^𝑇 𝒙+𝑏=0的距离公式是:
𝑟(𝑥)= (|𝒘^𝑇 𝒙+𝑏|)/(||𝒘||)
在分类问题中,如果这样的分割线或者分割平面能够准确地将样本分开,对于样本{𝒙_𝑖,𝑦_𝑖}∈𝐷, 𝑦_𝑖=±1 而言,若𝑦_𝑖=1,则有𝒘^𝑇 𝒙_𝒊+𝑏≥1,反之若𝑦_𝑖=-1,则有𝒘^𝑇 𝒙_𝒊+𝑏≤−1.

支持向量与间隔
对于满足𝒘^𝑇 𝒙_𝒊+𝑏=±1的样本,它们一定落在2个超平面上。这些样本被称为“支持向量(support vector)”,这2个超平面称为最大间隔边界。分属不同类别的样本距离分割平面的距离之和为
𝛾=2/(|\|𝑤||)
该距离之和被称为“间隔”

image.png

二. SVM的目标函数和对偶问题

2.1 支持向量机的优化问题

因此,对于完全线性可分的样本,分类模型的任务就是找出这样的超平面,满足


image.png

等价于求解带约束的最小化问题:


image.png

2.2 优化问题的对偶问题

一般来说,求解带等式或不等式约束的优化问题时,通常使用拉格朗日乘子法将原问题转换成对偶问题。在SVM的优化问题中,相应的对偶问题为:


image.png

对𝐿(𝑤,𝑏,𝛼)求关于𝑤,𝑏,𝛼的偏导数并且令为0,有:


image.png

最终的优化问题转化成


image.png

解出𝛼后,求出𝑤,𝑏即可得到模型。一般使用SMO算法求解。

2.3 支持向量与非支持向量

注意到,𝑦_𝑖 (𝒘^𝑇 𝒙_𝒊+𝑏)≥ 1是不等式约束,因此𝛼_𝑖需要满足𝛼_𝑖 (𝑦_𝑖 (𝒘^𝑇 𝒙_𝒊+𝑏)−1)=0(这是KKT条件中关于不等式约束的条件)。因此,满足这样的条件的样本{𝒙_𝒊,y_i},要么𝛼_𝑖=0, 要么𝑦_𝑖 (𝒘^𝑇 𝒙_𝒊+𝑏)−1。因此对于SVM的训练样本来讲,
如果𝛼_𝑖=0,则∑〖𝛼_𝑖−1/2 ∑∑𝛼_𝑖 𝛼_𝑗 𝑦_𝑖 𝑦_𝑗 𝒙_𝑖^𝑇 𝒙_𝒋的计算中不会出现该样本
如果𝑦_𝑖 (𝒘^𝑇 𝒙_𝒊+𝑏)−1,则该样本处于最大间隔边界上
从这一点可以看出,大部分训练样本最后都不会对模型的求解有任何影响,仅支持向量影响到模型的求解。

三. 软间隔

3.1 线性不可分

在一般的业务场景中,线性可分是可遇而不可求的。更多是线性不可分的情形,即无法找出这样的超平面可以完全正确地将两类样本分开。
为了解决这个问题,一个方法是我们允许部分样本被错误的分类(但是不能太多!) 。带有错误分类的间隔,称之为“软间隔”。于是,目标函数仍然是带约束的最大化间隔,其中约束条件是,不满足𝑦_𝑖 (𝒘^𝑇 𝒙_𝒊+𝑏)≥ 1的样本越少越好。

image.png

3.2 损失函数

基于这个思想,我们改写了优化函数


image.png

使其变为


image.png

可用的损失函数有:


image.png

3.3 松弛变量

当使用hinge loss的时候,损失函数变为


image.png

3.4 求解带松弛变量的软间隔SVM

令𝐿(𝑤,𝑏,𝛼,𝜂,𝜇)关于𝑤,𝑏, 𝜂的偏导等于0,则有:


image.png

3.5 支持向量与非支持向量

同样地,拉格朗日乘子也需满足𝛼_𝑖 (1−𝑦_𝑖 (𝒘^𝑇 𝒙_𝒊+𝑏)−𝜉_𝑖 )=0𝜇_𝑖 𝜉_𝑖=0的条件。对于某样本{𝑥_𝑖,𝑦_𝑖},
𝛼_𝑖=0时,样本不会对模型有任何影响
〖0<𝛼〗_𝑖<𝐶时,𝜇_𝑖>0, 此时有𝜉_𝑖=0,该样本落在最大间隔边界上
𝛼_𝑖=𝐶时,𝜇_𝑖=0。此时若有𝜉_𝑖≤1,该样本落在最大间隔内部,属于正确分类的情况;若有𝜉_𝑖>1,该样本落在最大间隔之间,属于错误分类的情况。
表明在hinge loss的情况下,带软间隔的SVM模型仍然只与支持向量有关。

四. 核函数

4.1 从低维到高维

在SVM的优化目标函数∑𝛼_𝑖−1/2 ∑∑𝛼_𝑖 𝛼_𝑗 𝑦_𝑖 𝑦_𝑗 𝒙_𝑖^𝑇 𝒙_𝒋中,{𝛼_𝑖}是参数,{𝑦_𝑖}是类别,二者的形式是固定的。但是交互项𝒙_𝑖^𝑇 𝒙_𝒋是独立的,我们完全可以讲其进行拓展。注意到我们有两种拓展的办法:
拓展一:𝒙_𝒋→𝝍(𝒙_𝒋), 此时目标函数变为∑𝛼_𝑖−1/2 ∑∑𝛼_𝑖 𝛼_𝑗 𝑦_𝑖 𝑦_𝑗 𝝍(𝑥_𝑖 )^𝑇 𝝍(𝒙_𝒋)
拓展二:𝒙_𝑖^𝑇 𝒙_𝒋→𝝍(𝒙_𝒊,𝒙_𝒋),此时目标函数变为∑𝛼_𝑖−1/2 ∑∑𝛼_𝑖 𝛼_𝑗 𝑦_𝑖 𝑦_𝑗 𝝍(𝒙_𝒊,𝒙_𝒋)
拓展一能够将𝑥映射到更高维的空间,从而将在低维不可分的情形变为在高维空间可分。这是除了软间隔之外,另一种解决线性不可分的办法。

线性不可分:


image.png

线性可分:


image.png
image.png

4.2 核函数

image.png

4.3 核函数的选择

image.png

一些先验经验

  1. 如果特征数远远大于样本数 ,使用线性核就可以了
  2. 如果特征数和样本数都很大,例如文档分类,一般使用线性核
  3. 如果特征数远小于样本数,这种情况一般使用RBF
    或者使用交叉验证法选择最合适的核函数

4.4 SVM模型的优缺点

优点:

  1. 适合小样本的分类
  2. 泛化能力强
  3. 局部最优解一定是全局最优解

缺点:

  1. 计算量很大,大规模训练样本很难实施
  2. 给出的结果是硬分类而非基于概率的软分类。SVM也可输出概率,但是计算更加复杂

五. 案例

5.1 数据预处理

跟“随机森林”中的案例一样,我们选择前若干个变量来构建SVM模型,需要注意的是,SVM模型和RF模型对数据质量有不同的要求:

  1. SVM模型只能处理数值型变量,RF可以处理数值与非数值型变量
  2. SVM不能处理带有缺失值的变量,但是RF可以
  3. SVM对异常值敏感,RF对异常值不敏感

综上,在构建SVM模型前我们需要对变量进行如下工作:

  1. 缺失值填补
  2. 对非数值型变量进行数值编码,可以用哑变量或者独热编码
  3. 处理异常值,进行归一化

5.2 降维

从SMO算法中可以看出,当特征个数较多时,SVM的计算量很大。我们引入变量挑选的环节。做变量挑选是为了:
剔除不显著的变量,减少噪声干扰
降低计算开销
借助随机森林,我们对原始变量做特征重要性评估,结果如右图所示。我们选择importance高于0.05的变量带入到模型构建的工作中。

image.png

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模型后,我们在训练集上进行预测,预测结果与真实值的混淆矩阵如下所示:


image.png

所有违约样本中,被识别出来的样本的占比(即召回率)为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

参考:

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

推荐阅读更多精彩内容