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


    一. SVM模型的基本概念

    1.1 从线性判别说起

    image.png

    image.png

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

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

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

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

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

    image.png

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

    2.1 支持向量机的优化问题

    因此,对于完全线性可分的样本,分类模型的任务就是找出这样的超平面,满足
    在这里插入图片描述

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

    2.2 优化问题的对偶问题

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

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

    最终的优化问题转化成
    image.png

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

    2.3 支持向量与非支持向量

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

    三. 软间隔

    3.1 线性不可分

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

    同样地,拉格朗日乘子也需满足Font metrics not found for font: .Font metrics not found for font: .的条件。对于某样本 𝑥 𝑖 , 𝑦 𝑖 {𝑥_𝑖,𝑦_𝑖} xi,yi,
    Font metrics not found for font: .时,样本不会对模型有任何影响
    Font metrics not found for font: . 此时有Font metrics not found for font: .,该样本落在最大间隔边界上
    Font metrics not found for font: .时,Font metrics not found for font: .。此时若有Font metrics not found for font: .,该样本落在最大间隔内部,属于正确分类的情况;若有Font metrics not found for font: .,该样本落在最大间隔之间,属于错误分类的情况。
    表明在hinge loss的情况下,带软间隔的SVM模型仍然只与支持向量有关。

    四. 核函数

    4.1 从低维到高维

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

    线性不可分:
    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模型后,我们在训练集上进行预测,预测结果与真实值的混淆矩阵如下所示:
    在这里插入图片描述

    所有违约样本中,被识别出来的样本的占比(即召回率)为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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136

    测试记录:
    数据量不大,代码居然跑了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
    • 2
    • 3
    • 4

    参考:

    1. http://www.dataguru.cn/mycourse.php?mod=intro&lessonid=1701
    2. http://www.dataguru.cn/article-4063-1.html
  • 相关阅读:
    HashMap JDK1.7与1.8的区别
    halcon分割粘连字符
    2022年测试岗最新自动化测试面试题整理,干货满满
    Lost in the Middle: How Language Models Use Long Contexts
    Databend 数据集成方案 | Data Infra 第 15 期
    笔记:GO1.19 带来的优化(重新编译juicefs)
    cocos鼠标选装
    vue-qr插件使用 报错You may need an appropriate loader to handle this file type
    基于STM32G431嵌入式学习笔记——六、串口中断实例(基于第12届蓝桥杯串口部分题目)
    cookie加密8
  • 原文地址:https://blog.csdn.net/u010520724/article/details/126361185