SVC真实数据案例:预测明天是否会下雨

SVC在现实中的应用十分广泛,尤其实在图像和文字识别方面。然而,这些数据不仅非常难以获取还难以在课程 中完整呈现出来SVC真实应用的代码其实就是sklearn中的三行真正能够展现出SVM强大之处的反而很少是案例本身而是我们之前所作的各种探索

我们在学习算法的时候,会使用各种各样的数据集来进行演示但这些数据往往非常干净并且规整不需要做太多的数据预处理。在我们讲解第三章数据预处理与特征工程时用了自制的文字数据和kaggle上的高维数据来为大家讲解然而这些数据依然不能够和现实中采集到的数据的复杂程度相比因此大家学习了这门课程依然会对究竟怎样做预处理感到困惑

在实际工作中数据预处理往往比建模难得多耗时多得多因此合理的数据预处理是非常必要的考虑到大家渴望学习真实数据上的预处理的需求,以及SVM需要在比较规则的数据集上来表现的特性我为大家准备了这个Kaggle上下载的未经过预处理的澳大利亚天气数据集我们的目标是在这个数据集上来预测明天是否会下雨

这个案例的核心目的是通过巧妙的预处理和特征工程来向大家展示,在现实数据集上我们往往如何做数据预处或者我们都有哪些预处理的方式和思路预测天气是一个非常非常困难的主题因为影响天气的因素太 Kaggle的这份数据也丝毫不让我们失望是一份非常难的数据集难到我们目前学过的所有算法在这个数据集上都不会有太好的结果,尤其是召回率recall,异常地低。在这里我为大家抛砖引玉在这个15W行数据的数据集上,随机抽样5000个样本来为大家演示我的数据预处理和特征工程的过程为大家提供一些数据预处理和特征工程的思路

导入库数据,探索特征

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

weather = pd.read_csv("/Users/zhucan/Desktop/weatherAUS5000.csv",index_col=0)
weather.head()

#将特征矩阵和标签Y分开
X = weather.iloc[:,:-1]
Y = weather.iloc[:,-1]
#探索缺失值
X.isnull().mean()

 

Y.isnull().sum()

结果:

0
#探索标签的分类
np.unique(Y)

结果:

array(['No', 'Yes'], dtype=object)
#标签是2分类

粗略观察可以发现这个特征矩阵由一部分分类变量和一部分连续变量组成,其中云层遮蔽程度虽然是以数字表但是本质却是分类变量大多数特征都是采集的自然数据比如蒸发量日照时间湿度等等而少部分特征是人为构成的还有一些是单纯表示样本信息的变量比如采集信息的地点以及采集的时间

分集,优先处理标签

在现实中,我们会先分训练集和测试集再开始进行数据预处理这是由于测试集在现实中往往是不可获得的 或者被假设为是不可获得的我们不希望我们建模的任何过程受到测试集数据的影响否则的话就相当于提前告 诉了模型一部分预测的答案在之前的课中为了简便操作都给大家忽略了这个过程一律先进行预处理再分训练集和测试集这是一种不规范的做法在这里为了让案例尽量接近真实的样貌所以采取了现实中所使用的 这种方式先分训练集和测试集再一步步进行预处理这样导致的结果是我们对训练集执行的所有操作都必须对测试集执行一次,工作量是翻倍的

#分训练集和测试集
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,Y,test_size=0.3,random_state=420)

#恢复索引
for i in [Xtrain, Xtest, Ytrain, Ytest]:
    i.index = range(i.shape[0])
#是否有样本不平衡问题?
print(Ytrain.value_counts())
print(Ytest.value_counts())

结果:

0    2704
1     796
dtype: int64

0    1157
1     343
dtype: int64
#将标签编码
from sklearn.preprocessing import LabelEncoder
encorder = LabelEncoder().fit(Ytrain) #允许一维数据 #认得了 有两类 yes是1 no是0
Ytrain = pd.DataFrame(encorder.transform(Ytrain))
Ytest = pd.DataFrame(encorder.transform(Ytest))
Ytrain.to_csv("")

描述性统计与异常值

对于去kaggle上下载了数据的小伙伴们,以及坚持要使用完整版数据的(15W行)小伙伴们

如果你发现了异常值,首先你要观察,这个异常值出现的频率

如果异常值只出现了一次,多半是输入错误,直接把异常值删除

如果异常值出现了多次,去跟业务人员沟通,可能这是某种特殊表示,如果是人为造成的错误,异常值留着是没有用 的,只要数据量不是太大,都可以删除

如果异常值占到你总数据量的10%以上了,不能轻易删除。可以考虑把异常值替换成非异常但是非干扰的项,比如说用0 来进行替换,或者把异常当缺失值,用均值或者众数来进行替换

#描述性统计
Xtrain.describe([0.01,0.05,0.1,0.25,0.5,0.75,0.9,0.99]).T
Xtest.describe([0.01,0.05,0.1,0.25,0.5,0.75,0.9,0.99]).T
 
#先查看原始的数据结构
Xtrain.shape
Xtest.shape

#观察异常值是大量存在,还是少数存在
Xtrain.loc[Xtrain.loc[:,"Cloud9am"] == 9,"Cloud9am"]
Xtest.loc[Xtest.loc[:,"Cloud9am"] == 9,"Cloud9am"]
Xtest.loc[Xtest.loc[:,"Cloud3pm"] == 9,"Cloud3pm"]

#少数存在,于是采取删除的策略
#注意如果删除特征矩阵,则必须连对应的标签一起删除,特征矩阵的行和标签的行必须要一一对应
Xtrain = Xtrain.drop(index = 71737)
Ytrain = Ytrain.drop(index = 71737)

#删除完毕之后,观察原始的数据结构,确认删除正确
Xtrain.shape

Xtest = Xtest.drop(index = [19646,29632])
Ytest = Ytest.drop(index = [19646,29632])
Xtest.shape

#进行任何行删除之后,千万记得要恢复索引
for i in [Xtrain, Xtest, Ytrain, Ytest]:
    i.index = range(i.shape[0])

Xtrain.head()
Xtest.head()

处理困难特征日期

#我们须现在拥有的日期特征,是连续性特征,还是分类型特征
#日期是一年分了365类的分类型变量
#我们的日期特征中,日期是否有重复
Xtrain.iloc[:,0].value_counts()

结果:

2015-07-03    6
2014-05-16    6
2015-10-12    6
2011-07-19    5
2012-11-23    5
             ..
2015-07-24    1
2009-07-11    1
2014-04-26    1
2008-11-09    1
2017-03-31    1
Name: Date, Length: 2141, dtype: int64

我们的思考简单一些我们可以直接删除日期这个特征首先它不是一个直接影响我们标签的特征并且要处理日期其实是非常困难的如果大家认可这种思路那可以直接运行下面的代码来删除我们的日期

Xtrain = Xtrain.drop(["Date"],axis=1) 
Xtest = Xtest.drop(["Date"],axis=1)

但在这里很多人可能会持不同意见怎么能够随便删除一个特征(哪怕我们已经觉得它可能无关?如果我们要删除,我们可能需要一些统计过程来判断说这个特征确实是和标签无关的那我们可以先将日期这个特征编码后对它和标签做方差齐性检验(ANOVA),如果检验结果表示日期这个特征的确和我们的标签无关那我们就可以愉快地删除这个特征了。但要编码日期这个特征就又回到了它到底是否会被算法当成是分类变量的问题上

其实我们可以想到日期必然是和我们的结果有关的它会从两个角度来影响我们的标签

首先我们可以想到昨天的天气可能会影响今天的天气而今天的天气又可能会影响明天的天气也就是说着日期的逐渐改变样本是会受到上一个样本的影响的但是对于算法来说普通的算法是无法捕捉到样本与样之间的联系的我们的算法捕捉的是样本的每个特征与标签之间的联系(即列与列之间的联系),而无法捕捉样 与样本之间的联系(行与行的联系)。

要让算法理解上一个样本的标签可能会影响下一个样本的标签,我们必须使用时间序列分析。时间序列分析是指将一统计指标的数值按其发生的时间先后顺序排列而成的数列时间序列分析的主要目的是根据已有的历史数据对 未来进行预测然而(据我所知时间序列只能在单调的唯一的时间上运行即一次只能够对一个地点进行预不能够实现一次性预测多个地点除非进行循环而我们的时间数据本身不是单调的也不是唯一的经过抽样之后甚至连连续的都不是了我们的时间是每个混杂在多个地点中每个地点上的一小段时间如何使用时间序列来处理这个问题,就会变得复杂

那我们可以换一种思路既然算法处理的是列与列之间的关系我是否可以把今天的天气会影响明天的天气这个 指标转换成一个特征呢?我们就这样来操

我们观察到,我们的特征中有一列叫做Rainfall",这是表示当前日期当前地区下的降雨量换句话说也就是天的降雨量。凭常识我们认为今天是否下雨应该会影响明天是否下雨比如有的地方可能就有这样的气候一旦下雨就连着下很多天也有可能有的地方的气候就是一场暴雨来得快去的快因此我们可以将时间对气候的连续影响,转换今天是否下雨这个特征巧妙地将样本对应标签之间的联系转换成是特征与标签之间的联系

Xtrain.loc[Xtrain["Rainfall"] >= 1,"RainToday"] = "Yes"
Xtrain.loc[Xtrain["Rainfall"] < 1,"RainToday"] = "No"
Xtrain.loc[Xtrain["Rainfall"] == np.nan,"RainToday"] = np.nan

Xtest.loc[Xtest["Rainfall"] >= 1,"RainToday"] = "Yes"
Xtest.loc[Xtest["Rainfall"] < 1,"RainToday"] = "No"
Xtest.loc[Xtest["Rainfall"] == np.nan,"RainToday"] = np.nan

如此我们就创造了一个特征今天是否下雨RainToday

那现在我们是否就可以将日期删除了呢?对于我们而言日期本身并不影响天气但是日期所在的月份和季节其实是影响天气的如果任选梅雨季节的某一天那明天下雨的可能性必然比非梅雨季节的那一天要大虽然我们无法让机器学习体会不同月份是什么季节但是我们可以对不同月份进行分组算法可以通过训练感受到 这个月或者这个季节更容易下雨。因此,我们可以将月份或者季节提取出来作为一个特征使用而舍弃掉具体的日如此我们又可以创造第二个特征月份"Month"。

int(Xtrain.loc[0,"Date"].split("-")[1]) #提取出月份

Xtrain["Date"] = Xtrain["Date"].apply(lambda x:int(x.split("-")[1]))
#apply是对dataframe上的某一列进行处理的一个函数
#lambda x匿名函数,请在dataframe上这一列中的每一行帮我执行冒号后的命令

#替换完毕后,我们需要修改列的名称
#rename是比较少有的,可以用来修改单个列名的函数
#我们通常都直接使用  df.columns = 某个列表  这样的形式来一次修改所有的列名
#但rename允许我们只修改某个单独的列
Xtrain = Xtrain.rename(columns={"Date":"Month"})

Xtest["Date"] = Xtest["Date"].apply(lambda x:int(x.split("-")[1]))
Xtest = Xtest.rename(columns={"Date":"Month"})

处理困难特征地点

地点,又是一个非常tricky的特征。常识上来说,我们认为地点肯定是对明天是否会下雨存在影响的伦敦明天是否会下雨北京明天是否会下雨我一定会猜测伦敦会下雨而北京不会因为伦敦是常年下雨的城市而北京的气候非常干燥对澳大利亚这样面积巨大的国家来说然存在着不同的城市有着不同的下雨倾向的情况但尴尬的是和时间一样我们输入地点的名字对于算法来说就是一串字符, "London""Beijing"对算法来说,01没有区别同样我们的样本中含有49个不同地点如果做成分类型变量算法就无法辨别它究竟是否是分类变量也就是说我们需要让算法意识到不同的地点因为气候不同,所以对天是否会下雨有着不同的影响如果我们能够将地点转换为这个地方的气候的话我们就可以将 不同城市打包到同一个气候中而同一个气候下反应的降雨情况应该是相似的

Xtrain.loc[:,"Location"].value_counts().count()

结果:

49
#超过25个类别的分类型变量,都会被算法当成是连续型变量

这是由澳大利亚气象局和澳大利亚建筑规范委员会(ABCB制作统计的澳大利亚不同地区不同城市的所在的气候区域划分总共划分为八个区域非常适合我们用来做分类如果能够把49个地点转换成八种不同的气候这个 信息应该会对是否下雨的判断比较有基于气象局和ABCB的数据我为大家制作了澳大利亚主要城市所对应的气候类型数据并保存在csv文件city_climate.csv当中然后使用以下代码google上进行爬虫爬出了每 个城市所对应的经纬度,并保存在数cityll.csv当中大家可以自行导入来查看这个数据

有主要城市的气候,有主要城市的经纬度、气候站的经纬度,气候站离哪个主要城市的距离比较近,那么气候站就是该城市的气候

cityll = pd.read_csv("/Users/zhucan/Desktop/cityll.csv",index_col=0)
city_climate = pd.read_csv("/Users/zhucan/Desktop/Cityclimate.csv")

print(cityll.head())
city_climate.head()

#去掉度数符号
cityll["Latitudenum"] = cityll["Latitude"].apply(lambda x:float(x[:-1])) 
cityll["Longitudenum"] = cityll["Longitude"].apply(lambda x:float(x[:-1]))

#观察一下所有的经纬度方向都是一致的,全部是南纬,东经,因为澳大利亚在南半球,东半球
#所以经纬度的方向我们可以舍弃了
citylld = cityll.iloc[:,[0,5,6]]

#将city_climate中的气候添加到我们的citylld中
citylld["climate"] = city_climate .iloc[:,-1]
citylld.head()

samplecity = pd.read_csv("/Users/zhucan/Desktop/samplecity.csv" ,index_col=0)

#我们对samplecity也执行同样的处理:去掉经纬度中度数的符号,并且舍弃我们的经纬度的方向
samplecity["Latitudenum"] = samplecity["Latitude"].apply(lambda x:float(x[:-1]))  
samplecity["Longitudenum"] = samplecity["Longitude"].apply(lambda x:float(x[:-1]))
samplecityd = samplecity.iloc[:,[0,5,6]]
samplecityd.head()

 

#首先使用radians将角度转换成弧度
from math import radians, sin, cos, acos
citylld.loc[:,"slat"] = citylld.iloc[:,1].apply(lambda x : radians(x))       
citylld.loc[:,"slon"] = citylld.iloc[:,2].apply(lambda x : radians(x))        
samplecityd .loc[:,"elat"] = samplecityd .iloc[:,1].apply(lambda x : radians(x)) 
samplecityd .loc[:,"elon"] = samplecityd .iloc[:,2].apply(lambda x : radians(x))

import sys
for i in range(samplecityd .shape[0]):
    slat = citylld.loc[:,"slat"]
    slon = citylld.loc[:,"slon"]
    elat = samplecityd .loc[i,"elat"]
    elon = samplecityd .loc[i,"elon"]
    dist = 6371.01 * np.arccos(np.sin(slat)*np.sin(elat) + np.cos(slat)*np.cos(elat)*np.cos(slon.values - elon))
    city_index = np.argsort(dist)[0]
    #每次计算后,取距离最近的城市,然后将最近的城市和城市对应的气候都匹配到samplecityd中
    samplecityd.loc[i,"closest_city"] = citylld.loc[city_index,"City"] 
    samplecityd.loc[i,"climate"] = citylld.loc[city_index,"climate"]
    #查看最后的结果,需要检查城市匹配是否基本正确
samplecityd .head()

locafinal = samplecityd .iloc[:,[0,-1]]
locafinal.columns = ["Location","Climate"]
locafinal.head()

 

#在这里设定locafinal的索引为地点,是为了之后进行map的匹配
locafinal = locafinal.set_index(keys="Location")
locafinal.head()

 

有了每个样本城市所对应的气候我们接下来就使用气候来替掉原本的城市原本的气象站的名称在这我们可以使用map功能,map能够将特征中的值一一对应到我们设定的字典中,并且用字典中的值来替换样本中原本的我们在评分卡中曾经使用这个功能来用WOE替换我们原本的特征的值。 

#将location中的内容替换,并且确保匹配进入的气候字符串中不含有逗号,气候两边不含有空格 #我们使用re这个模块来消除逗号
#re.sub(希望替换的值,希望被替换成的值,要操作的字符串)
#x.strip()是去掉空格的函数
import re
#删除逗号和空格
Xtrain["Location"] = Xtrain["Location"].map(locafinal.iloc[:,0]).apply(lambda x:re.sub(",","",x.strip()))
Xtest["Location"] = Xtest["Location"].map(locafinal.iloc[:,0]).apply(lambda x:re.sub(",","",x.strip()))
#修改特征内容之后,我们使用新列名“Climate”来替换之前的列名“Location”
#注意这个命令一旦执行之后,就再没有列"Location"了,使用索引时要特别注意
Xtrain = Xtrain.rename(columns={"Location":"Climate"})
Xtest = Xtest.rename(columns={"Location":"Climate"})

到这里地点就处理完毕了其实我们还没有将这个特征转化为数字即还没有对它进行编码我们稍后和其他的分类型变量一起来编码

处理分类型变量缺失值

接下来我们总算可以开始处理我们的缺失值了首先我们要注意到由于我们的特征矩阵由两种类型的数据组分类型和连续型因此我们必须对两种数据采用不同的填补缺失值策略传统地如果是分类型特征我们则采用众数进行填补。如果是连续型特征,我们则采用均值来填补

此时由于我们已经分了训练集和测试集我们需要考虑一件事究竟使用哪一部分的数据进行众数填补呢?答案使用训练集上的众数对训练集和测试集都进行填补为什么会这样呢?按道理说就算用测试集上的众数对测试集进行填补也不会使测试集数据进入我们建好的模型不会给模型透露一些信息然而在现实中我们的测试集未必是很多条数据也许我们的测试集只有一条数据而某个特征上是空值此时此刻测试集本身的众数根本存在要如何利用测试集本身的众数去进行填补呢?因此为了避免这种尴尬的情况发生我们假设测试集和训练集数据分布和性质都是相似的因此我们统一使用训练集的众数和均值来对测试集进行填补

sklearn当中即便是我们的填补缺失值的类也需要由实例化  t和接口调用执行填补三个步骤来进行而这种分割其实一部分也是为了满足我们使用训练集的建模结果来填补测试集的需求我们只需要实例化后使用训练集进行t,然后在调用接口执行填补时用训练集t后的结果分别来填补测试集和训练集就可以了

#查看缺失值的缺失情况
Xtrain.isnull().mean()

#首先找出,分类型特征都有哪些
cate = Xtrain.columns[Xtrain.dtypes == "object"].tolist()
cate

#除了特征类型为"object"的特征们,还有虽然用数字表示,但是本质为分类型特征的云层遮蔽程度
cloud = ["Cloud9am","Cloud3pm"]
cate = cate + cloud
cate

 

#对于分类型特征,我们使用众数来进行填补
from sklearn.impute import SimpleImputer

si = SimpleImputer(missing_values=np.nan,strategy="most_frequent")
#注意,我们使用训练集数据来训练我们的填补器,本质是在生成训练集中的众数
si.fit(Xtrain.loc[:,cate])

#然后我们用训练集中的众数来同时填补训练集和测试集
Xtrain.loc[:,cate] = si.transform(Xtrain.loc[:,cate])
Xtest.loc[:,cate] = si.transform(Xtest.loc[:,cate])

#查看分类型特征是否依然存在缺失值
print(Xtrain.loc[:,cate].isnull().mean())
Xtest.loc[:,cate].isnull().mean()

 

处理分类型变量将分类型变量编码 

在编码中,和我们的填补缺失值一样,我们也是需要先用训练集t模型,本质是将训练集中已经存在的类别转换成是数字然后我们再使用接口transform分别在测试集和训练集上来编码我们的特征矩阵当我们使用接口在测试集上进行编码的时候如果测试集上出现了训练集中从未出现过的类别那代码就会报错表示说我没有见过这个类别我无法对这个类别进行编码此时此刻你就要思考你的测试集上或许存在异常值错误值或者的确有一个新的类别出现了,而你曾经的训练数据中并没有这个类别以此为基础你需要调整你的模型

#将所有的分类型变量编码为数字,一个类别是一个数字
from sklearn.preprocessing import OrdinalEncoder
oe = OrdinalEncoder()

#利用训练集进行fit
oe = oe.fit(Xtrain.loc[:,cate])

#用训练集的编码结果来编码训练和测试特征矩阵
#在这里如果测试特征矩阵报错,就说明测试集中出现了训练集中从未见过的类别
Xtrain.loc[:,cate] = oe.transform(Xtrain.loc[:,cate])
Xtest.loc[:,cate] = oe.transform(Xtest.loc[:,cate])

Xtrain.loc[:,cate].head()
Xtest.loc[:,cate].head()

哑变量和普通的编码进行比较,如果认为引入哑变量会有太多的特征的话,那就进行普通编码,或两者都试一试,看看模型最终的效果

处理连续型变量填补缺失值

连续型变量的缺失值由均值来进行填补连续型变量往往已经是数字无需进行编码转换与分类型变量中一样 我们也是使用训练集上的均值对测试集进行填补如果学过随机森林填补缺失值的小伙伴可能此时会问为什不使用算法来进行填补呢?使用算法进行填补也是没有问题的但在现实中其实我们非常少用到算法来进行填补,有以下几个理由:

1. 算法是黑箱,解释性不强。如果你是一个数据挖掘工程师你使用算法来填补缺失值后你不懂机器学习的 老板或者同事问你的缺失值是怎么来你可能需要从头到尾帮他/她把随机森林解释一遍这种效率过低的事情是不可能做的,而许多老板和上级不会接受他们无法理解的东西

2. 算法填补太过缓慢运行一次森林需要有至少100棵树才能够基本保证森林的稳定而填补一个列就需要很长的时间在我们并不知道森林的填补结果是好是坏的情况下填补一个很大的数据集风险非常有可能需要跑好几个小时但填补出来的结果却不怎么优秀这明显是一个低效的方法

因此在现实工作时,我们往往使用易于理解的均值或者中位数来进行填补。当然在算法比赛中我们可以穷尽一切我们能够想到的办法来填补缺失值以追求让模型的效果更好不过现实中除了模型效果之外我们还要追求可解释性

col = Xtrain.columns.tolist()

for i in cate:
    col.remove(i)

#实例化模型,填补策略为"mean"表示均值
impmean = SimpleImputer(missing_values=np.nan,strategy = "mean")
#用训练集来fit模型
impmean = impmean.fit(Xtrain.loc[:,col])
#分别在训练集和测试集上进行均值填补
Xtrain.loc[:,col] = impmean.transform(Xtrain.loc[:,col])
Xtest.loc[:,col] = impmean.transform(Xtest.loc[:,col])

理连续型变量无量纲化 

数据的无量纲化是SVM执行前的重要步骤,因此我们需要对数据进行无量纲化但注意这个操作我们不对分类型变量进行

col.remove("Month") #还需要除去月份这个分类变量

from sklearn.preprocessing import StandardScaler #不改变数据的分布,只是把数据换成均值为0,方差为1的数据

ss = StandardScaler()
ss = ss.fit(Xtrain.loc[:,col])
Xtrain.loc[:,col] = ss.transform(Xtrain.loc[:,col])
Xtest.loc[:,col] = ss.transform(Xtest.loc[:,col])

建模与模型评

from time import time
import datetime
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score
from sklearn.metrics import roc_auc_score , recall_score

Ytrain = Ytrain.iloc[:,0].ravel()
Ytest = Ytest.iloc[:,0].ravel()

#建模选择自然是我们的支持向量机SVC,首先用核函数的学习曲线来选择核函数 #我们希望同时观察,精确性,recall以及AUC分数
times = time() #因为SVM是计算量很大的模型,所以我们需要时刻监控我们的模型运行时间

for kernel in ["linear","poly","rbf","sigmoid"]:
    clf = SVC(kernel = kernel
    ,gamma="auto"
    ,degree = 1
    ,cache_size = 5000
    ).fit(Xtrain, Ytrain)
    result = clf.predict(Xtest)
    score = clf.score(Xtest,Ytest)
    recall = recall_score(Ytest, result)
    auc = roc_auc_score(Ytest,clf.decision_function(Xtest))
    print("%s 's testing accuracy %f, recall is %f', auc is %f"% (kernel,score,recall,auc))
    print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))

结果:

linear 's testing accuracy 0.844000, recall is 0.469388', auc is 0.869029
03:39:433290
poly 's testing accuracy 0.840667, recall is 0.457726', auc is 0.868157
03:39:905005
rbf 's testing accuracy 0.813333, recall is 0.306122', auc is 0.814873
03:41:492447
sigmoid 's testing accuracy 0.655333, recall is 0.154519', auc is 0.437308
03:42:331627

我们注意到,模型的准确度和auc面积还是勉勉强强,但是每个核函数下的recall都不太高相比之下其实线性模型的效果是最好的那现在我们可以开始考虑了在这种状况下我们要向着什么方向进行调参呢?我们最想要 是什么?

我们可以有不同的目标

一,我希望不计一切代价判断出少数类得到最高的recall

我们希望追求最高的预测准确率一切目的都是为了让accuracy更高我们不在意recall或者AUC

三,我们希望达到recall  ROCaccuracy之间的平衡,不追求任何一个也不牺牲任何一个

模型调参

(1)最求最高Recall

如果我们想要的是最高的recall可以牺牲我们准确度希望不计一切代价来捕获少数类那我们首先可以打开我 们的class_weight参数使用balanced模式来调节我们recall

times = time()
for kernel in ["linear","poly","rbf","sigmoid"]:
    clf = SVC(kernel = kernel
    ,gamma="auto"
    ,degree = 1
    ,cache_size = 5000
    ,class_weight = "balanced"
    ).fit(Xtrain, Ytrain)
    result = clf.predict(Xtest)
    score = clf.score(Xtest,Ytest)
    recall = recall_score(Ytest, result)
    auc = roc_auc_score(Ytest,clf.decision_function(Xtest))
    print("%s 's testing accuracy %f, recall is %f', auc is %f"%(kernel,score,recall,auc))
    print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))

结果:

linear 's testing accuracy 0.796000, recall is 0.775510', auc is 0.870057
00:01:005093
poly 's testing accuracy 0.793333, recall is 0.763848', auc is 0.871448
00:01:639463
rbf 's testing accuracy 0.803333, recall is 0.600583', auc is 0.819713
00:03:424353
sigmoid 's testing accuracy 0.562000, recall is 0.282799', auc is 0.437119
00:05:174632

在锁定了线性核函数之后,我甚至可以将class_weight调节得更加倾向于少数类来不计代价提升recall

times = time()
clf = SVC(kernel = "linear"
        ,gamma="auto"
        ,cache_size = 5000
        ,class_weight = {1:10}).fit(Xtrain, Ytrain) #注意,这里写的其实是,类别1:10,隐藏了类别0:1这个比例 )
result = clf.predict(Xtest)
score = clf.score(Xtest,Ytest)
recall = recall_score(Ytest, result)
auc = roc_auc_score(Ytest,clf.decision_function(Xtest))
print("testing accuracy %f, recall is %f', auc is %f" %(score,recall,auc)) 
print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))

结果:

testing accuracy 0.636667, recall is 0.912536', auc is 0.866360
00:01:642328

随着recall地无节制上升我们的精确度下降得十分厉害不过看起来AUC积却还好稳定保持在0.86左右果此时我们的目的就是追求一个比较高的AUC分数和比较好的recall那我们的模型此时就算是很不错了虽然现在,我们的精确度很低,但是我们的确精准地捕捉出了每一个雨天

(2)追求最高准确率

在我们现有的目标(判断明天是否会下雨追求最高准确率而不顾recall其实意义不大但出于练习的目的我们来看看我们能够有怎样的思路。此时此刻我们不在意我们的Recall了,那我们首先要观察一下我们的样本不 均衡状况如果我们的样本非常不均衡但是此时却有很多多数类被判错的话那我们可以让模型任性地把所有地样本都判断为0完全不顾少数类

#查看模型的特异度

from sklearn.metrics import confusion_matrix as CM
clf = SVC(kernel = "linear"
        ,gamma="auto"
        ,cache_size = 5000
        ).fit(Xtrain, Ytrain)
result = clf.predict(Xtest)

cm = CM(Ytest,result,labels=(1,0))
specificity = cm[1,1]/cm[1,:].sum()
specificity #几乎所有的0都被判断正确了,还有不少1也被判断正确了

结果:

0.9550561797752809

可以看到,特异度非常高此时此刻如果要求模型将所有的类都判断为0则已经被判断正确的少数类会被误伤整体的准确率一定会下降而如果我们希望通过让模型捕捉更多少数类来提升精确率的话却无法实现因为一旦我们让模型更加倾向于少数类,就会有更多的多数类被判错

可以试试看使用class_weight将模型向少数类的方向稍微调整已查看我们是否有更多的空间来提升我们的准确如果在轻微向少数类方向调整过程中出现了更高的准确率则说明模型还没有到极限

irange = np.linspace(0.01,0.05,10)

for i in irange:
        times = time()
        clf = SVC(kernel = "linear"
                ,gamma="auto"
                ,cache_size = 5000
                ,class_weight = {1:1+i}
                ).fit(Xtrain, Ytrain)
        result = clf.predict(Xtest)
        score = clf.score(Xtest,Ytest)
        recall = recall_score(Ytest, result)
        auc = roc_auc_score(Ytest,clf.decision_function(Xtest))
        print("under ratio 1:%f testing accuracy %f, recall is %f', auc is %f" % (1+i,score,recall,auc))
        print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))

结果:

under ratio 1:1.010000 testing accuracy 0.844667, recall is 0.475219', auc is 0.869157
00:00:790930
under ratio 1:1.014444 testing accuracy 0.844667, recall is 0.478134', auc is 0.869185
00:00:758325
under ratio 1:1.018889 testing accuracy 0.844667, recall is 0.478134', auc is 0.869198
00:00:741147
under ratio 1:1.023333 testing accuracy 0.845333, recall is 0.481050', auc is 0.869175
00:00:773846
under ratio 1:1.027778 testing accuracy 0.844000, recall is 0.481050', auc is 0.869394
00:00:756873
under ratio 1:1.032222 testing accuracy 0.844000, recall is 0.481050', auc is 0.869528
00:00:746989
under ratio 1:1.036667 testing accuracy 0.844000, recall is 0.481050', auc is 0.869659
00:00:779989
under ratio 1:1.041111 testing accuracy 0.844667, recall is 0.483965', auc is 0.869629
00:00:781562
under ratio 1:1.045556 testing accuracy 0.844667, recall is 0.483965', auc is 0.869712
00:00:776110
under ratio 1:1.050000 testing accuracy 0.845333, recall is 0.486880', auc is 0.869863
00:00:769783

惊喜出现了我们的最高准确度是84.53%超过了我们之前什么都不做的时候得到的84.40%可见模型还是有潜力的。我们可以继续细化我们的学习曲线来进行调整

irange_ = np.linspace(0.018889,0.027778,10)

for i in irange_:
        times = time()
        clf = SVC(kernel = "linear"
                ,gamma="auto"
                ,cache_size = 5000
                ,class_weight = {1:1+i}
                ).fit(Xtrain, Ytrain)
        result = clf.predict(Xtest)
        score = clf.score(Xtest,Ytest)
        recall = recall_score(Ytest, result)
        auc = roc_auc_score(Ytest,clf.decision_function(Xtest))
        print("under ratio 1:%f testing accuracy %f, recall is %f', auc is %f" % (1+i,score,recall,auc))
        print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))

结果:

under ratio 1:1.018889 testing accuracy 0.844667, recall is 0.478134', auc is 0.869225
00:00:790760
under ratio 1:1.019877 testing accuracy 0.844000, recall is 0.478134', auc is 0.869228
00:00:757072
under ratio 1:1.020864 testing accuracy 0.844000, recall is 0.478134', auc is 0.869218
00:00:767355
under ratio 1:1.021852 testing accuracy 0.844667, recall is 0.478134', auc is 0.869188
00:00:745883
under ratio 1:1.022840 testing accuracy 0.844667, recall is 0.478134', auc is 0.869220
00:00:765762
under ratio 1:1.023827 testing accuracy 0.844667, recall is 0.481050', auc is 0.869188
00:00:782703
under ratio 1:1.024815 testing accuracy 0.844667, recall is 0.481050', auc is 0.869231
00:00:777356
under ratio 1:1.025803 testing accuracy 0.844000, recall is 0.481050', auc is 0.869253
00:00:831290
under ratio 1:1.026790 testing accuracy 0.844000, recall is 0.481050', auc is 0.869233
00:00:807941
under ratio 1:1.027778 testing accuracy 0.844000, recall is 0.481050', auc is 0.869326
00:00:814191

模型的效果没有太好并没有再出现比我们的84.53%精确度更高的取值可见模型在不做样本平衡的情况下准确度其实已经非常接近极限了让模型向着少数类的方向调节不能够达到质变如果我们真的希望再提升准只能选择更换模型的方式调整参数已经不能够帮助我们了想想看什么模型在线性数据上表现最好呢?

from sklearn.linear_model import LogisticRegression as LR
logclf = LR(solver="liblinear").fit(Xtrain, Ytrain)
logclf.score(Xtest,Ytest)

C_range = np.linspace(3,5,10)

for C in C_range:
    logclf = LR(solver="liblinear" ,C=C).fit(Xtrain, Ytrain)
    print(C,logclf.score(Xtest,Ytest))

结果:

3.0 0.8493333333333334
3.2222222222222223 0.8493333333333334
3.4444444444444446 0.8493333333333334
3.6666666666666665 0.8493333333333334
3.888888888888889 0.8493333333333334
4.111111111111111 0.8493333333333334
4.333333333333333 0.8493333333333334
4.555555555555555 0.8493333333333334
4.777777777777778 0.8493333333333334
5.0 0.8493333333333334

尽管我们实现了非常小的提升但可以看出来模型的精确度还是没有能够实现质变也许要将模型的精确度提 升到90%以上我们需要集成算法比如梯度提升树

(3)追求平衡

我们前面经历了多种尝试,选定了线性核,并发现调节class_weight并不能够使我们模型有较大的改善现在我们 试试看调节线性核函数的C值能否有效果

import matplotlib.pyplot as plt
C_range = np.linspace(0.01,20,20)

recallall = []
aucall = []
scoreall = []
for C in C_range:
    times = time()
    clf = SVC(kernel = "linear",C=C,cache_size = 5000
    ,class_weight = "balanced"
    ).fit(Xtrain, Ytrain)
    result = clf.predict(Xtest)
    score = clf.score(Xtest,Ytest)
    recall = recall_score(Ytest, result)
    auc = roc_auc_score(Ytest,clf.decision_function(Xtest))
    recallall.append(recall)
    aucall.append(auc)
    scoreall.append(score)
    print("under C %f, testing accuracy is %f,recall is %f', auc is %f" % (C,score,recall,auc))
    print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))

print(max(aucall),C_range[aucall.index(max(aucall))])
plt.figure()
plt.plot(C_range,recallall,c="red",label="recall")
plt.plot(C_range,aucall,c="black",label="auc")
plt.plot(C_range,scoreall,c="orange",label="accuracy")
plt.legend()
plt.show()

结果:

under C 0.010000, testing accuracy is 0.800000,recall is 0.752187', auc is 0.870634
00:00:575043
under C 1.062105, testing accuracy is 0.796000,recall is 0.775510', auc is 0.870024
00:01:006639
under C 2.114211, testing accuracy is 0.794000,recall is 0.772595', auc is 0.870196
00:01:301387
under C 3.166316, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870165
00:01:694227
under C 4.218421, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870178
00:02:082972
under C 5.270526, testing accuracy is 0.796000,recall is 0.775510', auc is 0.870082
00:02:402437
under C 6.322632, testing accuracy is 0.796000,recall is 0.775510', auc is 0.870062
00:02:780317
under C 7.374737, testing accuracy is 0.796000,recall is 0.775510', auc is 0.870090
00:03:125332
under C 8.426842, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870060
00:03:387333
under C 9.478947, testing accuracy is 0.796000,recall is 0.775510', auc is 0.870118
00:04:029910
under C 10.531053, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870110
00:04:062661
under C 11.583158, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870065
00:04:417111
under C 12.635263, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870009
00:05:393049
under C 13.687368, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870042
00:05:000740
under C 14.739474, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870009
00:05:387656
under C 15.791579, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870047
00:06:273191
under C 16.843684, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870034
00:06:432247
under C 17.895789, testing accuracy is 0.795333,recall is 0.772595', auc is 0.870027
00:06:751890
under C 18.947895, testing accuracy is 0.795333,recall is 0.772595', auc is 0.869981
00:07:039094
under C 20.000000, testing accuracy is 0.794667,recall is 0.772595', auc is 0.870024
00:07:129722
0.8706340666900172 0.01

可以观察到几个现象

首先我们注意到随着C值逐渐增大模型的运行速度变得越来越慢对于SVM这个本来运行就不快的模型来说,巨大的C值会是一个比较危险的消耗所以正常来说我们应该设定一个较小的C值范围来进行调整

其次,C很小的时候,模型的各项指标都很低,但当C1以上之模型的表现开始逐渐稳定C逐渐变大之后,模型的效果并没有显著地提高。可以认为我们设定的C值范围太大了然而再继续增大或者缩小C值的范围AUC面积也只能够在0.86上下进行变化了调节C值不能够让模型的任何指标实现质变

我们把目前为止最佳的C值带入模型看看我们的准确率  Recall的具体值:

times = time()
clf = SVC(kernel = "linear",C=3.1663157894736838 ,cache_size = 5000
,class_weight = "balanced"
).fit(Xtrain, Ytrain)
result = clf.predict(Xtest)
score = clf.score(Xtest,Ytest)
recall = recall_score(Ytest, result)
auc = roc_auc_score(Ytest,clf.decision_function(Xtest))
print("testing accuracy %f,recall is %f', auc is %f" % (score,recall,auc)) 
print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))

结果:

testing accuracy 0.795333,recall is 0.772595', auc is 0.870165
00:01:742594

可以看到这种情况下模型的准确率  RecallAUC都没有太差,但是也没有太好,这也许就是模型平衡后的一种结果。现在,光是调整支持向量机本身的参数,已经不能够满足我们的需求了要想让AUC面积更进一步我们需要绘制ROC曲线,查看我们是否可以通过调整阈值来对这个模型进行改进 

from sklearn.metrics import roc_curve as ROC
import matplotlib.pyplot as plt

FPR, Recall, thresholds = ROC(Ytest,clf.decision_function(Xtest),pos_label=1) 
area = roc_auc_score(Ytest,clf.decision_function(Xtest))
plt.figure()
plt.plot(FPR, Recall, color='red',label='ROC curve (area = %0.2f)' % area)
plt.plot([0, 1], [0, 1], color='black', linestyle='--')
plt.ylabel('Recall')
plt.title('Receiver operating characteristic example') 
plt.legend(loc="lower right")

结果:

 以此模型作为基础,我们来求解最佳阈值

maxindex = (Recall - FPR).tolist().index(max(Recall - FPR)) 
thresholds[maxindex]

结果:

-0.08950517396460822

基于我们选出的最佳阈值,我们来认为确定y_predict并确定在这个阈值下的recall和准确度的值 

from sklearn.metrics import accuracy_score as AC

times = time()
clf = SVC(kernel = "linear",C=3.1663157894736838 ,cache_size = 5000
        ,class_weight = "balanced"
        ).fit(Xtrain, Ytrain)

prob = pd.DataFrame(clf.decision_function(Xtest))

prob.loc[prob.iloc[:,0] >= thresholds[maxindex],"y_pred"]=1
prob.loc[prob.iloc[:,0] < thresholds[maxindex],"y_pred"]=0
prob.loc[:,"y_pred"].isnull().sum()

#检查模型本身的准确度
score = AC(Ytest,prob.loc[:,"y_pred"].values)
recall = recall_score(Ytest, prob.loc[:,"y_pred"])
print("testing accuracy %f,recall is %f" % (score,recall))               
print(datetime.datetime.fromtimestamp(time()-times).strftime("%M:%S:%f"))

结果:

testing accuracy 0.789333,recall is 0.804665
00:01:574062

反而还不如我们不调整时的效果好可见如果我们追求平衡SVC本身的结果就已经非常接近最优结果了节阈值调节参数C和调节class_weight都不一定有效果但整体来看我们的模型不是一个糟糕的模型但这个结果如果提交到kaggle参加比赛是绝对不足够的如果大家感兴趣还可以更加深入地探索模型或者换别的方法来处理特征以达到AUC面积0.9以上或是准确度或recall都提升到90%以上

SVM总结&结语

在两周的学习中我们逐渐探索了SVCsklearn中的全貌我们学习了SVM原理包括决策边界损失函数拉格朗日函数拉格朗日对偶函数软间隔硬间隔核函数以及核函数的各种应用我们了解SVC类的各种重要参属性和接口其中参数包括软间隔的惩罚系数C核函数kernel核函数的相关参数gammacoef0degree,解决样本不均衡的参数class_weight解决多分类问题的参数decision_function_shape控制概率的参probability,控制计算内存的参数cache_size属性主要包括调用支持向量的属性support_vectors_和查看特征 重要性的属性coef_。接口中,我们学习了最核心的decision_function除此之外我们介绍了分类模型的模型评估指标:混淆矩阵和ROC曲线,还介绍了部分特征工程和数据预处理的思路

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值