俗话说:数据与特征工程决定了模型的上限,改进算法只不过是逼近这个上限而已。
importpandasaspdimportnumpyasnpimportseabornassnsdf_titanic=sns.load_dataset('titanic')数据字段描述如下:
1.1区分结构化数据与非结构化数据
如一些以表格形式进行存储的数据,都是结构化数据;而非结构化数据就是一堆数据,类似于文本、报文、日志之类的。
1.2区分定量和定性数据
目标是提高数据质量,降低算法错误建模的风险。
现实的业务建模过程中,数据常常存在各种问题,数据存在不完全的、有噪声的、不一致的等各种情况。而这些带有错误信息的数据会对模型造成不利的影响。
数据清洗过程包括数据对齐、缺失值处理、异常值处理、数据转化等数据处理。
2.1数据对齐
2)字段:
3)量纲:
主要包括少量缺失的情况下,考虑不处理或删除缺失数据或者采用均值、中位数、众数、同类均值填充。
当缺失值对模型影响比较大,存在比较多的不缺失数据的情况下,可以采用模型预测或者插值的方式。当缺失值过多时,可以对缺失值进行编码操作。
对每个字段都计算其缺失值比例,然后按照缺失比例和字段重要性,分别制定策略,可用下图表示:
空值汇总分布
df_titanic.isnull().sum()survived0pclass0sex0age177sibsp0parch0fare0embarked2class0who0adult_male0deck688embark_town2alive0alone01)删除元组
将存在遗漏信息属性值的对象(元组,记录)删除,从而得到一个完备的信息表。
优点:
简单易行,在对象有多个属性缺失值、被删除的含缺失值的对象与初始数据集的数据量相比非常小的情况下有效;
不足:
当缺失数据所占比例较大,特别当遗漏数据非随机分布时,这种方法可能导致数据发生偏离,从而引出错误的结论。
代码实现
embark_town字段有2个空值,可以考虑删除缺失处理下
df_titanic[df_titanic["embark_town"].isnull()]df_titanic.dropna(axis=0,how='any',subset=['embark_town'],inplace=True)
2)数据填充
用一定的值去填充空值,从而使信息表完备化。通常基于统计学原理,根据初始数据集中其余对象取值的分布情况来对一个缺失值进行填充。
(a)人工填充(fillingmanually)
根据业务知识来进行人工填充。
(b)特殊值填充(TreatingMissingAttributevaluesasSpecialvalues)
将空值作为一种特殊的属性值来处理,它不同于其他的任何属性值。如所有的空值都用“unknown”填充。一般作为临时填充或中间过程。
df_titanic['embark_town'].fillna('unknown',inplace=True)(c)统计量填充
若缺失率较低(小于95%)且重要性较低,则根据数据分布的情况进行填充。
常用填充统计量:
平均值:对于数据符合均匀分布,用该变量的均值填补缺失值。
中位数:对于数据存在倾斜分布的情况,采用中位数填补缺失值。
众数:离散特征可使用众数进行填充缺失值。
fare:缺失值较多,使用中位数填充。
df_titanic['fare'].fillna(df_titanic['fare'].median(),inplace=True)embarked:只有两个缺失值,使用众数填充
df_titanic['embarked'].isnull().sum()执行结果:2df_titanic['embarked'].fillna(df_titanic['embarked'].mode(),inplace=True)df_titanic['embarked'].value_counts()执行结果:S64imputer类提供了缺失数值处理的基本策略,比如使用缺失数值所在行或列的均值、中位数、众数来替代缺失值。该类也兼容不同的缺失值编码。
填补缺失值:sklearn.preprocessing.Imputer(missing_values=’NaN’,strategy=’mean’,axis=0,verbose=0,copy=True)
主要参数说明:
missing_values:缺失值,可以为整数或NaN(缺失值numpy.nan用字符串‘NaN’表示),默认为NaNstrategy:替换策略,字符串,默认用均值‘mean’替换①若为mean时,用特征列的均值替换②若为median时,用特征列的中位数替换③若为most_frequent时,用特征列的众数替换axis:指定轴数,默认axis=0代表列,axis=1代表行copy:设置为True代表不在原数据集上修改,设置为False时,就地修改,存在如下情况时,即使设置为False时,也不会就地修改①X不是浮点值数组②X是稀疏且missing_values=0③axis=0且X为CRS矩阵④axis=1且X为CSC矩阵statistics_属性:axis设置为0时,每个特征的填充值数组,axis=1时,报没有该属性错误
age:根据sex、pclass和who分组,如果落在相同的组别里,就用这个组别的均值或中位数填充。
(d)模型预测填充
使用待填充字段作为Label,没有缺失的数据作为训练数据,建立分类/回归模型,对待填充的缺失字段进行预测并进行填充。
最近距离邻法(KNN)
回归(Regression)
age:缺失量较大,用sex、pclass、who、fare、parch、sibsp六个特征构建随机森林模型,填充年龄缺失值。
df_titanic_age=df_titanic[['age','pclass','sex','who','fare','parch','sibsp']]df_titanic_age=pd.get_dummies(df_titanic_age)df_titanic_age.head()执行结果为age pclass fare parch sibsp sex_female sex_male who_child who_man who_woman0 22.0 3 7.2500 0 1 0 1 0 1 01 38.0 1 71.2833 0 1 1 0 0 0 12 26.0 3 7.9250 0 0 1 0 0 0 13 35.0 1 53.1000 0 1 1 0 0 0 14 35.0 3 8.0500 0 0 0 1 0 1 0#乘客分成已知年龄和未知年龄两部分known_age=df_titanic_age[df_titanic_age.age.notnull()]unknown_age=df_titanic_age[df_titanic_age.age.isnull()]#y即目标年龄y_for_age=known_age['age']#X即特征属性值X_train_for_age=known_age.drop(['age'],axis=1)X_test_for_age=unknown_age.drop(['age'],axis=1)fromsklearn.ensembleimportRandomForestRegressorrfr=RandomForestRegressor(random_state=0,n_estimators=2000,n_jobs=-1)rfr.fit(X_train_for_age,y_for_age)#用得到的模型进行未知年龄结果预测y_pred_age=rfr.predict(X_test_for_age)#用得到的预测结果填补原缺失数据df_titanic.loc[df_titanic.age.isnull(),'age']=y_pred_agesns.distplot(df_titanic.age)
(e)插值法填充
包括随机插值,多重插补法,热平台插补,拉格朗日插值,牛顿插值等。
使用插值法可以计算缺失值的估计值,所谓的插值法就是通过两点(x0,y0),(x1,y1)估计中间点的值,假设y=f(x)是一条直线,通过已知的两点来计算函数f(x),然后只要知道x就能求出y,以此方法来估计缺失值。
.interpolate(method='linear',axis)方法将通过linear插值使用沿着给定axis的值替换NaN值,这个差值也就是前后或者上下的中间值
df_titanic['fare'].interpolate(method='linear',axis=0)同时,也可用行值插入
df_titanic['fare'].interpolate(method='linear',axis=1)代码实现
多重插补方法分为三个步骤:
Step1:为每个空值产生一套可能的插补值,这些值反映了无响应模型的不确定性;
每个值都可以被用来插补数据集中的缺失值,产生若干个完整数据集合;
Step2:每个插补数据集合都用针对完整数据集的统计方法进行统计分析;
Step3:对来自各个插补数据集的结果,根据评分函数进行选择,产生最终的插补值。
(f)哑变量填充
若变量是离散型,且不同值较少,可转换成哑变量,例如性别SEX变量,存在male,fameal,NA三个不同的值,可将该列转换成IS_SEX_MALE、IS_SEX_FEMALE、IS_SEX_NA。若某个变量存在十几个不同的值,可根据每个值的频数,将频数较小的值归为一类’other’,降低维度。此做法可最大化保留变量的信息。
sex_list=['MALE','FEMALE',np.NaN,'FEMALE','FEMALE',np.NaN,'MALE']df=pd.DataFrame({'SEX':sex_list})display(df)df.fillna('NA',inplace=True)df=pd.get_dummies(df['SEX'],prefix='IS_SEX')display(df)#原始数据SEX0MALE1FEMALE2NaN3FEMALE4FEMALE5NaN6MALE#填充后IS_SEX_FEMALEIS_SEX_MALEIS_SEX_NA001011002001310041005001601(g)当特征值缺失超过80%以上,建议删除【或变成是否变量】,容易影响模型效果
df_titanic.drop(["deck"],axis=1)2.3异常处理:
1)异常值识别
sns.catplot(y="fare",x="survived",kind="box",data=df_titanic,palette="Set2");
sns.distplot(df_titanic.age)
(a)基于统计分析
通常用户用某个统计分布对数据点进行建模,再以假定的模型,根据点的分布来确定是否异常。
如通过分析统计数据的散度情况,即数据变异指标,对数据的分布情况有所了解,进而通过数据变异指标来发现数据中的异常点数据。
常用的数据变异指标有极差、四分位数间距、均差、标准差、变异系数等等,如变异指标的值大表示变异大、散布广;值小表示离差小,较密集。
譬如最大最小值可以用来判断这个变量的取值是否超过了合理的范围,如客户的年龄为-20岁或200岁,为异常值。
(b)3σ原则
(c)箱线图分析
箱线图提供了识别异常值的一个标准:如果一个值小于Q1-1.5IQR或大于Q3+1.5IQR的值,则被称为异常值。
Q1为下四分位数,表示全部观察值中有四分之一的数据取值比它小;
Q4为上四分位数,表示全部观察值中有四分之一的数据取值比它大;
IQR为四分位数间距,是上四分位数Q1与下四分位数Q3的差值,包含了全部观察值的一半。
箱型图判断异常值的方法以四分位数和四分位距为基础,四分位数具有鲁棒性:25%的数据可以变得任意远并且不会干扰四分位数,所以异常值不能对这个标准施加影响。因此箱型图识别异常值比较客观,在识别异常值时有一定的优越性。
(d)基于模型检测
首先建立一个数据模型,异常是那些同模型不能完美拟合的对象;如果模型是簇的集合,则异常是不显著属于任何簇的对象;在使用回归模型时,异常是相对远离预测值的对象。
有坚实的统计学理论基础,当存在充分的数据和所用的检验类型的知识时,这些检验可能非常有效。
缺点:
对于多元数据,可用的选择少一些,并且对于高维数据,这些检测可能性很差。
(e)基于距离
基于距离的方法是基于下面这个假设:即若一个数据对象和大多数点距离都很远,那这个对象就是异常。通过定义对象之间的临近性度量,根据距离判断异常对象是否远离其他对象,主要使用的距离度量方法有绝对距离(曼哈顿距离)、欧氏距离和马氏距离等方法。
基于距离的方法比基于统计类方法要简单得多;
因为为一个数据集合定义一个距离的度量要比确定数据集合的分布容易的多。
该方法对参数的选择也是敏感的;
不能处理具有不同密度区域的数据集,因为它使用全局阈值,不能考虑这种密度的变化。
(f)基于密度
考察当前点周围密度,可以发现局部异常点,离群点的局部密度显著低于大部分近邻点,适用于非均匀的数据集。
给出了对象是离群点的定量度量,并且即使数据具有不同的区域也能够很好的处理。
对于低维数据使用特定的数据结构可以达到O(mlogm);
参数选择困难。
虽然算法通过观察不同的k值,取得最大离群点得分来处理该问题,但是,仍然需要选择这些值的上下界。
(g)基于聚类
对象是否被认为是异常点可能依赖于簇的个数(如k很大时的噪声簇)。该问题也没有简单的答案。一种策略是对于不同的簇个数重复该分析。另一种方法是找出大量小簇,其想法是:
较小的簇倾向于更加凝聚;
如果存在大量小簇时一个对象是异常点,则它多半是一个真正的异常点。
不利的一面是一组异常点可能形成小簇而逃避检测。
基于线性和接近线性复杂度(k均值)的聚类技术来发现离群点可能是高度有效的;
簇的定义通常是离群点的补,因此可能同时发现簇和离群点。
缺点:
产生的离群点集和它们的得分可能非常依赖所用的簇的个数和数据中离群点的存在性;
聚类算法产生的簇的质量对该算法产生的离群点的质量影响非常大。
(h)基于邻近度的异常点检测
一个对象是异常的,如果它远离大部分点。这种方法比统计学方法更一般、更容易使用,因为确定数据集的有意义的邻近性度量比确定它的统计分布更容易。一个对象的异常点得分由到它的k-最近邻的距离给定。异常点得分对k的取值高度敏感。如果k太小(例如1),则少量的邻近异常异常点可能导致较异常低的异常点得分;如果K太大,则点数少于k的簇中所有的对象可能都成了异常异常点。为了使该方案对于k的选取更具有鲁棒性,可以使用k个最近邻的平均距离。
简单
总结:
在数据处理阶段将离群点作为影响数据质量的异常点考虑,而不是作为通常所说的异常检测目标点,一般采用较为简单直观的方法,结合箱线图和MAD的统计方法判断变量的离群点。
sns.scatterplot(x="fare",y="age",hue="survived",data=df_titanic,palette="Set1")
2)处理方法
对异常值处理,需要具体情况具体分析,异常值处理的方法常用有四种:
目标是增强数据表达,添加先验知识。
如果我们对变量进行处理之后,效果仍不是非常理想,就需要进行特征构建了,也就是衍生新变量。
指根据业务经验或者常识等自行设定划分的区间,然后将原始数据归类到各个区间中。
(b)等距分箱
按照相同宽度将数据分成几等份。
从最小值到最大值之间,均分为N等份,这样,如果A,B为最小最大值,则每个区间的长度为W=(BA)/N,则区间边界值为A+W,A+2W,….A+(N1)W。这里只考虑边界,每个等份里面的实例数量可能不等。
缺点是受到异常值的影响比较大
(c)等频分箱
将数据分成几等份,每等份数据里面的个数是一样的。
区间的边界值要经过选择,使得每个区间包含大致相等的实例数量。比如说N=10,每个区间应该包含大约10%的实例。
2.计算出KS最大的那个值,即为切点,记为D。然后把数据切分成两部分。
3.重复步骤2,进行递归,D左右的数据进一步切割。直到KS的箱体数达到我们的预设阈值即可。
5.分箱过程中,决定分箱后的KS值是某一个切点,而不是多个切点的共同作用。这个切点的位置是原始KS值最大的位置。
注:代码实现请从网上查阅
3)卡方分桶
自底向上的(即基于合并的)数据离散化方法。它依赖于卡方检验:具有最小卡方值的相邻区间合并在一起,直到满足确定的停止准则。
基本思想
对于精确的离散化,相对类频率在一个区间内应当完全一致。因此,如果两个相邻的区间具有非常类似的类分布,则这两个区间可以合并;否则,它们应当保持分开。而低卡方值表明它们具有相似的类分布。
实现步骤
Step1:预先定义一个卡方的阈值;
Step2:初始化;根据要离散的属性对实例进行排序,每个实例属于一个区间;
Step3:合并区间;
计算每一对相邻区间的卡方值;
将卡方值最小的一对区间合并;
Aij:第i区间第j类的实例的数量;Eij:Aij的期望频率(=(Ni*Cj)/N),N是总样本数,Ni是第i组的样本数,Cj是第j类样本在全体中的比例;
阈值的意义
类别和属性独立时,有90%的可能性,计算得到的卡方值会小于4.6。大于阈值4.6的卡方值就说明属性和类不是相互独立的,不能合并。如果阈值选的大,区间合并就会进行很多次,离散后的区间数量少、区间大。
注意
ChiMerge算法推荐使用0.90、0.95、0.99置信度,最大区间数取10到15之间;也可以不考虑卡方阈值,此时可以考虑最小区间数或者最大区间数。指定区间数量的上限和下限,最多几个区间,最少几个区间;对于类别型变量,需要分箱时需要按照某种方式进行排序。
需要使总熵值达到最小,也就是使分箱能够最大限度地区分因变量的各类别。
熵是信息论中数据无序程度的度量标准,提出信息熵的基本目的是找出某种符号系统的信息量和冗余度之间的关系,以便能用最小的成本和消耗来实现最高效率的数据存储、管理和传递。
数据集的熵越低,说明数据之间的差异越小,最小熵划分就是为了使每箱中的数据具有最好的相似性。给定箱的个数,如果考虑所有可能的分箱情况,最小熵方法得到的箱应该是具有最小熵的分箱。
注:有限考虑强特征维度
1)离散+离散:笛卡尔积
2)离散+连续:连续特征分桶后进行笛卡尔积或基于类别特征groupby,类似于聚类特征构造
3)连续+连续:加减乘除,二阶差分等
df_titanic_numerical=df_titanic[['age','sibsp','parch','fare','family_size']]df_titanic_numerical.head()执行结果:age sibsp parch fare family_size0 22.0 1 0 7.2500 21 38.0 1 0 71.2833 22 26.0 0 0 7.9250 13 35.0 1 0 53.1000 24 35.0 0 0 8.0500 1#扩展数值特征fromsklearn.preprocessingimportPolynomialFeaturespoly=PolynomialFeatures(degree=2,include_bias=False,interaction_only=False)df_titanic_numerical_poly=poly.fit_transform(df_titanic_numerical)pd.DataFrame(df_titanic_numerical_poly,columns=poly.get_feature_names()).hea
sns.heatmap(pd.DataFrame(df_titanic_numerical_poly,columns=poly.get_feature_names()).corr())
目标是降低噪声,平滑预测能力和计算复杂度,增强模型预测性能
当数据预处理完成后,我们需要选择有意义的特征输入机器学习的算法和模型进行训练。
通常来说,从两个方面考虑来选择特征:
根据特征选择的形式又可以将特征选择方法分为3种:
1)方差过滤
这是通过特征本身的方差来筛选特征的类。比如一个特征本身的方差很小,就表示样本在这个特征上基本没有差异,可能特征中的大多数值都一样,甚至整个特征的取值都相同,那这个特征对于样本区分没有什么作用。所以无论接下来的特征工程要做什么,都要优先消除方差为0的特征。VarianceThreshold有重要参数threshold,表示方差的阈值,表示舍弃所有方差小于threshold的特征,不填默认为0,即删除所有的记录都相同的特征。
比如说,我们希望留下一半的特征,那可以设定一个让特征总数减半的方差阈值,只要找到特征方差的中位数,再将这个中位数作为参数threshold的值输入就好了:
df_titanic_numerical_fsvar=VarianceThreshold(np.median(df_titanic_numerical.var().values)).fit_transform(df_titanic_numerical)当特征是二分类时,特征的取值就是伯努利随机变,假设p=0.8,即二分类特征中某种分类占到80%以上的时候删除特征
X_bvar=VarianceThreshold(.8*(1-.8)).fit_transform(df_titanic_numerical)X_bvar.shape执行结果:(891,5)2)卡方过滤
卡方检验类feature_selection.chi2计算每个非负特征和标签之间的卡方统计量,并依照卡方统计量由高到低为特征排名
df_titanic_categorical=df_titanic[['sex','class','embarked','who','age_bin','adult_male','alone','fare_bin']]df_titanic_numerical=df_titanic[['age','sibsp','parch','fare','family_size','pclass']]df_titanic_categorical_one_hot=pd.get_dummies(df_titanic_categorical,columns=['sex','class','embarked','who','age_bin','adult_male','alone','fare_bin'],drop_first=True)df_titanic_combined=pd.concat([df_titanic_numerical,df_titanic_categorical_one_hot],axis=1)y=df_titanic['survived']X=df_titanic_combined.iloc[:,1:]fromsklearn.feature_selectionimportchi2fromsklearn.feature_selectionimportSelectKBestchi_value,p_value=chi2(X,y)#根据p值,得出k值k=chi_value.shape[0]-(p_value>0.05).sum()#要保留的特征的数量14#根据卡方值,选择前几特征,筛选后特征X_chi=SelectKBest(chi2,k=14).fit_transform(X,y)X_chi.shape(893)F检验
F检验,又称ANOVA,方差齐性检验,是用来捕捉每个特征与标签之间的线性关系的过滤方法。它即可以做回归也可以做分类,因此包含feature_selection.f_classif(F检验分类)和feature_selection.f_regression(F检验回归)两个类。其中F检验分类用于标签是离散型变量的数据,而F检验回归用于标签是连续型变量的数据。
F检验的本质是寻找两组数据之间的线性关系,其原假设是”数据不存在显著的线性关系“。
fromsklearn.feature_selectionimportf_classiff_value,p_value=f_classif(X,y)#根据p值,得出k值k=f_value.shape[0]-(p_value>0.05).sum()#筛选后特征X_classif=SelectKBest(f_classif,k=14).fit_transform(X,y)4)互信息法
互信息法是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法。和F检验相似,它既可以做回归也可以做分类,并且包含两个类:
feature_selection.mutual_info_classif(互信息分类)feature_selection.mutual_info_regression(互信息回归)
递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。使用feature_selection库的RFE类来选择特征的代码如下:
fromsklearn.feature_selectionimportRFEfromsklearn.linear_modelimportLogisticRegression#递归特征消除法,返回特征选择后的数据#参数estimator为基模型#参数n_features_to_select为选择的特征个数X_ref=RFE(estimator=LogisticRegression(),n_features_to_select=10).fit_transform(X,y)2)重要性评估
fromsklearn.ensembleimportExtraTreesClassifier#featureextractionmodel=ExtraTreesClassifier()model.fit(X,y)print(model.feature_importances_)feature=list(zip(X.columns,model.feature_importances_))feature=pd.DataFrame(feature,columns=['feature','importances'])feature.sort_values(by='importances',ascending=False).head(20)feature importances2 fare 0.22765915 adult_male_True 0.13000010 who_man 0.1089395 sex_male 0.07806511 who_woman 0.0590907 class_Third 0.0557554 pclass 0.0487333 family_size 0.0383470 sibsp 0.0354899 embarked_S 0.0295121 parch 0.02377820 fare_bin_(39.688,512.329] 0.02298514 age_bin_young 0.02140412 age_bin_midlife 0.0193796 class_Second 0.01930117 fare_bin_(7.854,10.5] 0.01644819 fare_bin_(21.679,39.688] 0.01600618 fare_bin_(10.5,21.679] 0.01487116 alone_True 0.01309313 age_bin_old 0.01123)排列重要性评估
**优点:**快速计算;易于使用和理解;特征重要性度量的属性;追求特征稳定性
**原理:**在训练机器学习模型之后计算置换重要性。这种方法在向模型提出假设,如果在保留目标和所有其他列的同时随机打乱一列验证集特征数据,对预测机器学习模型的准确性的影响程度。对于一个具有高度重要性的特征,random-reshuffle会对机器学习模型预测的准确性造成更大的损害。
**结果解读:**每一行的第一个数字表示模型性能(例子中用的是准确率)衰减了多少,±后面的数字表示多次打乱的标准差。
importnumpyasnpimportpandasaspdfromsklearn.model_selectionimporttrain_test_splitfromsklearn.ensembleimportRandomForestClassifierimporteli5fromeli5.sklearnimportPermutationImportancemy_model=RandomForestClassifier(random_state=0).fit(train_X,train_y)perm=PermutationImportance(my_model,random_state=1).fit(val_X,val_y)eli5.show_weights(perm,feature_names=val_X.columns.tolist())
1)基于惩罚项的特征选择法
使用带惩罚项的基模型,除了筛选出特征外,同时也进行了降维。
使用feature_selection库的SelectFromModel类结合带L1惩罚项的逻辑回归模型,来选择特征的代码如下:
fromsklearn.feature_selectionimportSelectFromModelfromsklearn.linear_modelimportLogisticRegression#带L1和L2惩罚项的逻辑回归作为基模型的特征选择,这个设置带L1惩罚项的逻辑回归作为基模型的特征选择lr=LogisticRegression(solver='liblinear',penalty="l1",C=0.1)X_sfm=SelectFromModel(lr).fit_transform(X,y)X_sfm.shape(891,7使用feature_selection库的SelectFromModel类结合SVM模型,来选择特征的代码如下:
fromsklearn.feature_selectionimportSelectFromModelfromsklearn.svmimportLinearSVClsvc=LinearSVC(C=0.01,penalty='l1',dual=False).fit(X,y)model=SelectFromModel(lsvc,prefit=True)X_sfm_svm=model.transform(X)X_sfm_svm.shape(891,72)基于树模型
树模型中GBDT也可用来作为基模型进行特征选择,使用feature_selection库的SelectFromModel类结合GBDT模型,来选择特征的代码如下:
fromsklearn.feature_selectionimportSelectFromModelfromsklearn.ensembleimportGradientBoostingClassifier#GBDT作为基模型的特征选择gbdt=GradientBoostingClassifier()X_sfm_gbdt=SelectFromModel(gbdt).fit_transform(X,y)X_sfm_gbdt.shape(891,5)总结一下,有几点做特征选择的方法经验:
(1)如果特征是分类变量,那么可以从SelectKBest开始,用卡方或者基于树的选择器来选择变量;
(3)如果是二分类问题,可以考虑使用SelectFromModel和SVC;
(4)在进行特征选择前,还是需要做一下EDA。
转换为Z-score,使数值特征列的算数平均为0,方差(以及标准差)为1。不免疫outlier。
注:如果数值特征列中存在数值极大或极小的outlier(通过EDA发现),应该使用更稳健(robust)的统计数据:用中位数而不是算术平均数,用分位数(quantile)而不是方差。这种标准化方法有一个重要的参数:(分位数下限,分位数上限),最好通过EDA的数据可视化确定。免疫outlier。
fromsklearn.preprocessingimportStandardScale#标准化模型训练Stan_scaler=StandardScaler()Stan_scaler.fit(x)x_zscore=Stan_scaler.transform(x)x_test_zscore=Stan_scaler.transform(x_test)joblib.dump(Stan_scaler,'zscore.m')#写入文件2)归一化(Normalization)把每一行数据归一化,使之有unitnorm,norm的种类可以选l1、l2或max。不免疫outlier。
,其中\iota表示norm函数。
将一列的数值,除以这一列的最大绝对值。
MinMaxScaler:线性映射到[0,1],不免疫outlier。
MaxAbsScaler:线性映射到[-1,1],不免疫outlier。
fromsklearnimportpreprocessingmin_max_scaler=preprocessing.MinMaxScaler()min_max_scaler.fit_transform(x)x_minmax=min_max_scaler.transform(x)x_test_minmax=min_max_scaler.transform(x_test)joblib.dump(min_max_scaler,'min_max_scaler.m')#写入文件注:如果数值特征列中存在数值极大或极小的outlier(通过EDA发现),应该使用更稳健(robust)的统计数据:用中位数而不是算术平均数,用分位数(quantile)而不是方差。这种标准化方法有一个重要的参数:(分位数下限,分位数上限),最好通过EDA的数据可视化确定。免疫outlier。
归一化与标准化区别
(a)目的不同,归一化是为了消除纲量压缩到[0,1]区间;标准化只是调整特征整体的分布。(b)归一化与最大,最小值有关;标准化与均值,标准差有关。(c)归一化输出在[0,1]之间;标准化无限制。
归一化与标准化应用场景
(a)在分类、聚类算法中,需要使用距离来度量相似性的时候(如SVM、KNN)或者使用PCA技术进行降维的时候,标准化(Z-scorestandardization)表现更好。(b)在不涉及距离度量、协方差计算、数据不符合正太分布的时候,可以使用第一种方法或其他归一化方法。比如图像处理中,将RGB图像转换为灰度图像后将其值限定在[0255]的范围。(c)基于树的方法不需要进行特征的归一化。例如随机森林,bagging与boosting等方法。如果是基于参数的模型或者基于距离的模型,因为需要对参数或者距离进行计算,都需要进行归一化。
利用统计或数学变换来减轻数据分布倾斜的影响。使原本密集的区间的值尽可能的分散,原本分散的区间的值尽量的聚合。
这些变换函数都属于幂变换函数簇,通常用来创建单调的数据变换。它们的主要作用在于它能帮助稳定方差,始终保持分布接近于正态分布并使得数据与分布的平均值无关。
log变换通常用来创建单调的数据变换。它的主要作用在于帮助稳定方差,始终保持分布接近于正态分布并使得数据与分布的平均值无关。因为log变换倾向于拉伸那些落在较低的幅度范围内自变量值的范围,倾向于压缩或减少更高幅度范围内的自变量值的范围。从而使得倾斜分布尽可能的接近正态分布。所以针对一些数值连续特征的方差不稳定,特征值重尾分布我们需要采用log化来调整整个数据分布的方差,属于方差稳定型数据转换。
log变换属于幂变换函数簇。该函数用数学表达式表示为
自然对数使用b=e,e=2.71828,通常叫作欧拉常数。你可以使用通常在十进制系统中使用的b=10作为底数。
sns.distplot(df_titanic.fare,kde=False)
df_titanic['fare_log']=np.log((1+df_titanic['fare']))sns.distplot(df_titanic.fare_log,kde=False)
box-cox变换是另一个流行的幂变换函数簇中的一个函数。该函数有一个前提条件,即数值型值必须先变换为正数(与log变换所要求的一样)。万一出现数值是负的,使用一个常数对数值进行偏移是有帮助的。
box-cox变换函数:
生成的变换后的输出y是输入x和变换参数的函数;当λ=0时,该变换就是自然对数log变换,前面我们已经提到过了。λ的最佳取值通常由最大似然或最大对数似然确定。
LabelEncoder是对不连续的数字或者文本进行编号,编码值介于0和n_classes-1之间的标签。
例如:比如有[dog,cat,dog,mouse,cat],我们把其转换为[1,2,1,3,2]。这里就产生了一个奇怪的现象:dog和mouse的平均值是cat。
优点:相对于OneHot编码,LabelEncoder编码占用内存空间小,并且支持文本特征编码。
缺点:它隐含了一个假设:不同的类别之间,存在一种顺序关系。在具体的代码实现里,LabelEncoder会对定性特征列中的所有独特数据进行一次排序,从而得出从原始输入到整数的映射。所以目前还没有发现标签编码的广泛使用,一般在树模型中可以使用。
fromsklearn.preprocessingimportLabelEncoderle=LabelEncoder()le.fit(["超一线","一线","二线","三线"])print('特征:{}'.format(list(le.classes_)))#输出特征:['一线','三线','二线','超一线']print('转换标签值:{}'.format(le.transform(["超一线","一线","二线"])))#输出转换标签值:array([302]...)print('特征标签值反转:{}'.format(list(le.inverse_transform([2,2,1]))))#输出特征标签值反转:['二线','二线','三线2)独热编码(onehotencoder)OneHotEncoder用于将表示分类的数据扩维。最简单的理解用N位状态寄存器编码N个状态,每个状态都有独立的寄存器位,且这些寄存器位中只有一位有效,只能有一个状态。
为什么要使用独热编码?
独热编码是因为大部分算法是基于向量空间中的度量来进行计算的,为了使非偏序关系的变量取值不具有偏序性,并且到圆点是等距的。使用one-hot编码,将离散特征的取值扩展到了欧式空间,离散特征的某个取值就对应欧式空间的某个点。将离散型特征使用one-hot编码,会让特征之间的距离计算更加合理。
为什么特征向量要映射到欧式空间?
将离散特征通过one-hot编码映射到欧式空间,是因为在回归、分类、聚类等机器学习算法中,特征之间距离或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算。
举个例子-假如有三种颜色特征:红、黄、蓝。
所以这时标签编码是不够的,需要进一步转换。因为有三种颜色状态,所以就有3个比特。即红色:100,黄色:010,蓝色:001。如此一来每两个向量之间的距离都是根号2,在向量空间距离都相等,所以这样不会出现偏序性,基本不会影响基于向量空间度量算法的效果。
优点:独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有0和1,不同的类型存储在垂直的空间。
缺点:只能对数值型变量二值化,无法直接对字符串型的类别变量编码。当类别的数量很多时,特征空间会变得非常大。在这种情况下,一般可以用PCA来减少维度。而且onehotencoding+PCA这种组合在实际中也非常有用。
sex_list=['MALE','FEMALE',np.NaN,'FEMALE','FEMALE',np.NaN,'MALE']df=pd.DataFrame({'SEX':sex_list})display(df)df.fillna('NA',inplace=True)df=pd.get_dummies(df['SEX'],prefix='IS_SEX')display(df)#原始数据SEX0MALE1FEMALE2NaN3FEMALE4FEMALE5NaN6MALE#填充后IS_SEX_FEMALEIS_SEX_MALEIS_SEX_NA001011002001310041005001pd.get_dummies(df_titanic,columns=['sex','class','pclass','embarked','who','family_size','age_bin'],drop_first=True)
注:当特征是字符串类型时,需要先用LabelEncoder()转换成连续的数值型变量,再用OneHotEncoder()二值化sklearn.preprocessing中的OneHotEncoder将shape=(None,1)的列向量中每个分量表示的下标(index)编码成onehot行向量。importnumpyasnpfromsklearn.preprocessingimportOneHotEncoder行向量转列向量:#非负整数表示的标签列表labels=[0,1,0,2]#行向量转列向量labels=np.array(labels).reshape(len(labels),-1)onehot编码:enc=OneHotEncoder()enc.fit(labels)targets=enc.transform(labels).toarray()#如果不加toarray()的话,输出的是稀疏的存储格式,即索引加值的形式,也可以通过参数指定sparse=False来达到同样的效果编码结果:array([[1.,0.,0.],[0.,1.,0.],[1.,0.,0.],[0.,0.,1.]])3)标签二值化(LabelBinarizer)功能与OneHotEncoder一样,但是OneHotEncoder只能对数值型变量二值化,无法直接对字符串型的类别变量编码,而LabelBinarizer可以直接对字符型变量二值化。
fromsklearnimportdatasetsiris_data=datasets.load_iris()X=iris_data.datay=iris_data.targetdefdraw_result(X,y):"""X:降维后的数据iris:原数据"""plt.figure()#提取Iris-setosasetosa=X[y==0]#绘制点:参数1x向量,y向量plt.scatter(setosa[:,0],setosa[:,1],color="red",label="Iris-setosa")#Iris-versicolorversicolor=X[y==1]plt.scatter(versicolor[:,0],versicolor[:,1],color="orange",label="Iris-versicolor")#Iris-virginicavirginica=X[y==2]plt.scatter(virginica[:,0],virginica[:,1],color="blue",label="Iris-virginica")plt.legend()plt.show()draw_result(X,y
作用:降维、压缩
步骤:
(a)手动实现PCA
classPCA:def__init__(self,dimension,train_x):#降维后的维度self.dimension=dimension#原始数据集self.train_x=train_x@propertydefresult(self):'返回降维后的矩阵'#1.数据中心化data_centering=self.train_x-np.mean(self.train_x,axis=0)#2.计算协方差矩阵cov_matrix=np.cov(data_centering,rowvar=False)#3.特征值分解eigen_val,eigen_vec=np.linalg.eig(cov_matrix)#4.生成降维后的数据p=eigen_vec[:,0:self.dimension]#取特征向量矩阵的前k维returnnp.dot(data_centering,p)调用方法:pca=PCA(2,X)iris_2d=pca.resultdraw_result(iris_2d,y
(b)sklearn的PCA
importnumpyasnpfromsklearn.decompositionimportPCApca=PCA(n_components=2)newX=pca.fit_transform(X)draw_result(newX,y)
作用:特征分解、降维
(a)手动实现SVD
classSVD:def__init__(self,dimension,train_x):self.dimension=dimensionself.train_x=train_x@propertydefresult(self):'返回降维后的矩阵'data_centering=self.train_x-np.mean(self.train_x,axis=0)#SVDU,Sigma,VT=np.linalg.svd(data_centering)returnnp.dot(data_centering,np.transpose(VT)[:,:self.dimension])调用方法:svd=SVD(2,X)iris_svd=svd.resultdraw_result(iris_svd,y)
(b)sklearn的SVD
TruncatedSVD,截断奇异值分解(当数据量非常大,svd跑不出来时使用此方法)。
fromsklearn.decompositionimportTruncatedSVDiris_2d=TruncatedSVD(2).fit_transform(X)draw_result(iris_2d,y)
是有监督的降维,通过最小化类内离散度与最大化类间离散度来获得最优特征子集。
LD1通过线性判定,可以很好的将呈正态分布的两个类分开。LD2的线性判定保持了数据集的较大方差,但LD2无法提供关于类别的信息,因此LD2不是一个好的线性判定。
fromsklearn.discriminant_analysisimportLinearDiscriminantAnalysisasLDAlda=LDA(n_components=2)iris_2d=lda.fit_transform(X,y)draw_result(iris_2d,y)
LDA与PCA相似:
PCA试图寻找到方差最大的正交的主成分分量轴LDA发现可以最优化分类的特征子空间LDA和PCA都是可用于降低数据集维度的线性转换技巧PCA是无监督算法LDA是监督算法LDA是一种更优越的用于分类的特征提取技术
fromsklearn.manifoldimportTSNEtsne=TSNE(n_components=2)iris_2d=tsne.fit_transform(X)draw_result(iris_2d,y)
切忌:一开始就把所有的特征一股脑地扔进模型,容易被一些没用的特征误导。
1)EDA
plot,plot,plot,重要的事情说三遍
2)特征预处理
连续特征离散化(决策树类型的模型没意义):一种挺有趣的变种,就是限制浮点数特征的精度,异常数据有很强的鲁棒性,模型也会更稳定。
clipping:可以用pandasdataframe的.clip(low,upper)方法,把特征值的取值限制在一定范围内
3)数据清洗
要合情合理,不可盲目填充缺失值、删除异常值,要建立在统计科学基础上。
4)特征变换
除非万不得已,不要用PCA或者LDA降维,建议直接减原始特征。
任何针对单独特征列的单调变换(如对数):不适用于决策树类算法。
对于决策树而言,X、X^3、X^5之间没有差异,|X|、X^2、X^4之间没有差异,除非发生了舍入误差。
用N1和N2表示数值特征,用C1和C2表示类别特征,利用pandas的groupby操作,可以创造出以下几种有意义的新特征:(其中,C2还可以是离散化了的N1)
median(N1)_by(C1)\\中位数mean(N1)_by(C1)\\算术平均数mode(N1)_by(C1)\\众数min(N1)_by(C1)\\最小值max(N1)_by(C1)\\最大值std(N1)_by(C1)\\标准差var(N1)_by(C1)\\方差freq(C2)_by(C1)\\频数仅仅将已有的类别和数值特征进行以上的有效组合,就能够大量增加优秀的可用特征。
将这种方法和线性组合等基础特征工程方法结合(仅用于决策树),可以得到更多有意义的特征,如:
N1-median(N1)_by(C1)N1-mean(N1)_by(C1)基于geneticprogramming的symbolicregression【python环境下首推基因编程库为gplearn】。
基因编程的两大用法:
spearman多用于决策树(免疫单特征单调变换),pearson多用于线性回归等其他算法。
回归(regression):原理同上,只不过直接用于回归而已。
在决策树系列的算法中(单棵决策树、gbdt、随机森林),每一个样本都会被映射到决策树的一片叶子上。因此,我们可以把样本经过每一棵决策树映射后的index(自然数)或one-hot-vector(哑编码得到的稀疏矢量)作为一项新的特征,加入到模型中。
具体实现:apply()以及decision_path()方法,在scikit-learn和xgboost里都可以用。
5)模型
对特征数值幅度不敏感,可以不进行无量纲化和统计变换处理;由于数模型依赖于样本距离来进行学习,可以不进行类别特征编码(但字符型特征不能直接作为输入,所以需要至少要进行标签编码)。LightGBM和XGBoost都能将NaN作为数据的一部分进行学习,所以不需要处理缺失值。其他情况下,我们需要使用。
对于数值型特征需要进行无量纲化处理;对于一些长尾分布的数据特征,可以做统计变换,使得模型能更好优化;对于线性模型,特征分箱可以提升模型表达能力;
光喻:【持续更新】机器学习特征工程实用技巧大全zhuanlan.zhihu.com