数据无量纲化
将不同规格的数据转换到同一规格,或不同分布的数据转换到某个特定分布的需求,这种需求统称为将数据“无量纲化”。
梯度和矩阵为核心的算法中,逻辑回归,支持向量机,神经网络,无量纲化可以加快求解速度;而在距离类模型,譬如K近邻,K-Means聚类中,无量纲化可以帮我们提升模型精度,避免某一个取值范围特别大的特征对距离计算造成影响。(决策树和树的集成算法不需要无量纲化,决策树可以把任意数据都处理得很好。)
数据的无量纲化可以是线性的,也可以是非线性的。包括中心化(Zero-centered或者Mean-subtraction)处理和缩放处理(Scale)。中心化的本质是让所有记录减去一个固定值,即让数据样本数据平移到某个位置。缩放的本质是通过除以一个固定值,将数据固定在某个范围之中,取对数也算是一种缩放处理
preprocessing.MinMaxScaler
归一化之后的数据服从正态分布,公式如下:
x ∗ = x − m i n ( x ) m a x ( x ) − m i n ( x ) x^*=\frac{x-min(x)}{max(x)-min(x)} x∗=max(x)−min(x)x−min(x)
preprocessing.MinMaxScaler API
步骤一:导入库和数据
from sklearn.preprocessing import MinMaxScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
import pandas as pd
pd.DataFrame(data)
scaler = MinMaxScaler()
scaler = scaler.fit(data)
result = scaler.transform(data)
result_ = scaler.fit_transform(data)
scaler.inverse_transform(result)
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = MinMaxScaler(feature_range=[5,10])
result = scaler.fit_transform(data)
import numpy as np
X = np.array([[-1, 2], [-0.5, 6], [0, 10], [1, 18]])
x_nor = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
x_inverse = x_nor * (X.max(axis=0) - X.min(axis=0)) + X.min(axis=0)
preprocessing.StandardScaer API
数据按均值(μ)中心化再按标准差(σ)缩放,数据就会服从为均值为0,方差为1的正态分布的过程,为数据标准化,公式如下:
x ∗ = x − u σ x^*=\frac{x-u}{σ} x∗=σx−u
from sklearn.preprocessing import StandardScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = StandardScaler()
scaler.fit(data)
scaler.mean_ #均值
scaler.var_ #方差
x_std = scaler.transform(data)
x_std #方差
x_std.mean() #均值
x_std.std() #方差
scaler.fit_transform(data)
scaler.inverse_transform(x_std)
StandardScaler和MinMaxScaler的说明
参数 | 含义&输入 |
---|---|
missing_values | 告诉SimpleImputer,数据中的缺失值长什么样,默认np.nan |
strategy | “mean”使用均值填补,默认;“median"用中值填补;"most_frequent”用众数填补;“constant"表示请参考参数“fill_value"中的值 |
fill_value | 参数startegy为”constant"的时候可用,可输入字符串或数字表示要填充的值,常用0 |
copy | 默认为True,将创建特征矩阵的副本,反之则会将缺失值填补到原本的特征矩阵中去 |
步骤一:库和数据导入
import pandas as pd
data = pd.read_csv("./data/Narrativedata.csv",index_col=0)
data.head()
步骤二:取出Age目标并实例化API进行填充
Age = data.loc[:,"Age"].values.reshape(-1,1)
from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer()
imp_median = SimpleImputer(strategy="median")
imp_0 = SimpleImputer(strategy="constant",fill_value=0)
imp_mean = imp_mean.fit_transform(Age)
imp_median = imp_median.fit_transform(Age)
imp_0 = imp_0.fit_transform(Age)
步骤三:使用中值进行填充,并同样对Embarked进行众数填充
data.loc[:,"Age"] = imp_median
data.info()
Embarked = data.loc[:,"Embarked"].values.reshape(-1,1)
imp_mode = SimpleImputer(strategy="most_frequent")
data.loc[:,"Embarked"] = imp_mode.fit_transform(Embarked)
data.info()
import pandas as pd
data = pd.read_csv('./data/Narrativedata.csv',index_col=0)
data.head()
data.loc[:,"Age"] = data.loc[:,"Age"].fillna(data.loc[:,"Age"].median())
data.dropna(axis=0,inplace=True) #inplace默认false
data.info()
为让数据适应算法和库,我们必须将数据进行编码,即将文字型数据转换为数值型
preprocessing.LabelEncoder:标签专用,能够将分类转换为分类数值
步骤一:导入库和数据
from sklearn.preprocessing import LabelEncoder
import pandas as pd
data = pd.read_csv('./data/Narrativedata.csv',index_col=0)
data.head()
LabelEncoder().fit(data.iloc[:,-1]).classes_
data.iloc[:,-1] = LabelEncoder().fit_transform(data.iloc[:,-1])
data.head()
preprocessing.OrdinalEncoder:特征专用,能够将分类特征转换为分类数值
步骤一:导入库和数据
from sklearn.preprocessing import OrdinalEncoder
data_ = data.copy() #用的上次处理Suivived的data
data_.head()
data_.loc[:,"Age"] = data.loc[:,"Age"].fillna(data.loc[:,"Age"].median())
data_.dropna(axis=0,inplace=True)
data_.info()
OrdinalEncoder().fit(data_.iloc[:,1:-1]).categories_
data_.iloc[:,1:-1] = OrdinalEncoder().fit_transform(data_.iloc[:,1:-1])
data_.head()
preprocessing.OneHotEncoder API:独热编码,创建哑变量
返回稀疏矩阵,让三个取值之间相互独立,即哑变量
实际操作:
步骤一:导入库和数据
from sklearn.preprocessing import LabelEncoder
import pandas as pd
data = pd.read_csv('./data/Narrativedata.csv',index_col=0)
data.head()
data.loc[:,"Age"] = data.loc[:,"Age"].fillna(data.loc[:,"Age"].median())
data.dropna(axis=0,inplace=True)
步骤三:取出目标特征列并实例化OneHotEncoder进行编码
from sklearn.preprocessing import OneHotEncoder
X = data.iloc[:,1:-1]
OneHotEncoder(categories='auto').fit_transform(X).toarray()
步骤四:拼接到表中,并删除之前的特征
newdata = pd.concat([data,pd.DataFrame(result)],axis=1)
newdata.head()
newdata.drop(['Sex','Embarked'],axis=1,inplace=True)
newdata.head()
newdata.columns =["Age","Survived","Female","Male","Embarked_C","Embarked_Q","Embarked_S"]
newdata.head()
sklearn.preprocessing.Binarizer API
设置阈值,大于阈值为1,其他为0。仅考虑某种现象的存在与否
使用实例:
data_2 = data.copy()
from sklearn.preprocessing import Binarizer
X = data_2.iloc[:,0].values.reshape(-1,1)
transformer = Binarizer(threshold=30).fit_transform(X)
transformer
preprocessing.KBinsDiscretizer API
将连续型变量划分为分类变量的类,能够将连续性变量排序后按顺序分箱后编码:参数说明如下
参数 | 含义&输入 |
---|---|
n_bins | 每个特征中分箱的个数,默认5,一次会被运用到所有导入的特征 |
encode | 编码方式,默认“onehot” “onehot”:做哑变量,之后返回一个稀疏矩阵,每一列是一个特征中的一个类别,含有该类别的样本表示为1,不含的表示为0 “ordinal”:每个特征的每个箱都被编码为一个整数,返回每一列是一个特征,每个特征下含有不同整数编码的箱的矩阵 “onehot-dense”:做哑变量,之后返回一个密集数组。 |
strategy | 用来定义箱宽的方式,默认"quantile" “uniform”:表示等宽分箱,即每个特征中的每个箱的最大值之间的差为(特征.max() - 特征.min())/(n_bins) “quantile”:表示等位分箱,即每个特征中的每个箱内的样本数量都相同 “kmeans”:表示按聚类分箱,每个箱中的值到最近的一维k均值聚类的簇心得距离都相同 |
使用实例:
from sklearn.preprocessing import KBinsDiscretizer
X = data.iloc[:,0].values.reshape(-1,1)
est = KBinsDiscretizer(n_bins=3, encode='ordinal',strategy='uniform')
est.fit_transform(X)
set(est.fit_transform(X).ravel())
查看分箱-哑变量
est = KBinsDiscretizer(n_bins=3,encode='onehot',strategy='uniform')
est.fit_transform(X).toarray()
这里用泰坦尼克的数据来当作例子
其中是否存活是我们的标签。很明显,以判断“是否存活”为目的,票号,登船的舱门,乘客编号明显是无关特征,可以直接删除。姓名,舱位等级,船舱编号,也基本可以判断是相关性比较低的特征。性别,年龄,船上的亲人数量,这些应该是相关性比较高的特征
6.1.1方差过滤:消除方差为0的特征
VarianceThreshold API
本次数据量很大,尤其是它的特征数量很多,如果直接使用支持向量机或者神经网络来处理会极其消耗资源,所以需要我们进行特征处理,下图是处理的基本步骤
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold()
X_var0 = selector.fit_transform(x)
删除差异值为0的特征后任然剩下708个特征,所以还需要进一步的选择。可以直接提高VarianceThreshold中threshold参数提高阈值
6.1.2使用特征方差的中位数作为参数
import numpy as np
np.median(x.var().values)
x_fsvar = VarianceThreshold(np.median(x.var().values)).fit_transform(x)
x_fsvar.shape
6.1.3使用伯努利随机变量删除某种分类占比80%以上的特征
#二分类问题,所以可以这样算
x_bar = VarianceThreshold(.8 * (1- .8)).fit_transform(x)
x_bar.shape
这里比较KNN和随机森林分别在方差过滤前和方差过滤后运行的效果和运行时间
步骤一:导入模块并准备数据
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.model_selection import cross_val_score
import numpy as np
X = data.iloc[:,1:]
y = data.iloc[:,0]
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)
步骤二:KNN方差-过滤前
#======【TIME WARNING:35mins +】======#
cross_val_score(KNN(),X,y,cv=5).mean()
#python中的魔法命令,可以直接使用%%timeit来计算运行这个cell中的代码所需的时间
#为了计算所需的时间,需要将这个cell中的代码运行很多次(通常是7次)后求平均值,因此运行%%timeit的时间会
# 远远超过cell中的代码单独运行的时间
#======【TIME WARNING:4 hours】======#
%%timeit
cross_val_score(KNN(),X,y,cv=5).mean()
#======【TIME WARNING:20 mins+】======#
cross_val_score(KNN(),X_fsvar,y,cv=5).mean()
#======【TIME WARNING:2 hours】======#
%%timeit
cross_val_score(KNN(),X,y,cv=5).mean()
KNN过滤后的效果十分明显:准确率稍有提升,平均运行时间减少了10分钟,特征选择过后算法的效率上升了1/3
步骤四:随机森林方差-过滤前与过滤后
cross_val_score(RFC(n_estimators=10,random_state=0),X,y,cv=5).mean()
cross_val_score(RFC(n_estimators=10,random_state=0),X_fsvar,y,cv=5).mean()
方差过滤影响总结:
阈值很小被 过滤掉得特征比较少 |
阈值比较大 被过滤掉的特征有很多 |
|
---|---|---|
模型表现 | 不会有太大影响 | 可能变好,代表被滤掉的特征大部分是噪音 也可能变糟糕,代表被滤掉的特征中很多都是有效特征 |
运行时间 | 可能降低模型的运行时间 基于方差很小的特征有多少 当方差很小的特征不多时对模型没有太大影响 |
降低模型的运行时间 算法在遍历特征时的计算越复杂,运行时间下降得越多 |
针对离散型标签(分类问题)的相关性过滤
基本流程可以概括为:先通过feature_selection.chi2计算每个非负特征和标签之间的卡方统计量,并依照统计量大小的高低通过feature_selection.SelectKBest来进行选择前K个分数最高的类,从而除去最可能独立于标签的特征。
实际操作:先方差过滤(如果模型表现增加)再卡方过滤
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
y = data.iloc[:,0]
X_fchi = SelectKBest(chi2, k=300).fit_transform(x_fsvar,y)
cross_val_score(RFC(n_estimators=10,random_state=0),X_fchi,y,cv=5).mean()
结果模型效果降低,说明K=300时删除了与模型相关的特征,需要提升K值
超参数K值的学习曲线
#======【TIME WARNING: 5 mins】======#
%matplotlib inline
import matplotlib.pyplot as plt
score = []
for i in range(390,200,-10):
X_fschi = SelectKBest(chi2, k=i).fit_transform(X_fsvar, y)
once = cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()
score.append(once)
plt.plot(range(390,200,-10),score)
plt.show()
可以观察K值增大,模型的表现也上升,即数据中所有的特征都与标签相关。为了节约时间,我们一般根据P值来选择K,具体规则如下表:
P值 | <=0.05或0.01 | >0.05或0.01 |
---|---|---|
数据差异 | 差异不是自然形成 | 差异是自然的样本误差 |
相关性 | 差异不是自然形成的 | 两组数据相互独立 |
原假设 | 拒绝原假设,接收备择假设 | 接受原假设 |
特征工程中,一般选择卡方值很大,p值小于0.05的特征,即和标签相关联的特征。而调用SelectKBest之前,可以直接从chi实例化后的模型中获得各个特征所对应的卡方值和P值
根据P值来选择K
chivalue, pvalues_chi = chi2(x_fsvar,y)
k = chivalue.shape[0] - (pvalues_chi > 0.05).sum()
该结果说明经过方差过滤后,数据集本身已经不含与标签无关的特征,可以全部用与模型
捕捉每个特征与标签之间的线性关系的过滤方法,包括feature_selection.f_classif(F检验分类)与feature_selection.f_regression(F检验回归)
from sklearn.feature_selection import f_classif
F, pvalues_f = f_classif(X_fsvar,y)
k = F.shape[0] - (pvalues_f > 0.05).sum()
得到的结论与之前相同,即所有标签都相关
F检验在数据服从正态分布时效果非常稳定,所以一般先将数据转换成正态分布再进行F过滤
用来捕捉每个特征与标签之间的任意关系的过滤方法。
包括feature_selection.mutual_info_classif(互信息分类)和
feature_selection.mutual_info_regression(互信息回归),可以找出任意关系,而F检验只能找出线性关系。返回“每个特征与目标之间的互信息量的估计”,为0表示两者独立
from sklearn.feature_selection import mutual_info_classif as MIC
result = MIC(x_fsvar,y)
k = result.shape[0] - sum(result <= 0)
显然所有值都大于0,与之前的结论相同
常用过滤法总结:
类 | 说明 | 超参数的选择 |
---|---|---|
VarianceThreshold | 方差过滤,可输入方差阈值,返回方差大于阈值的新特征矩阵 | 看具体数据究竟是含有更多噪声还是更多有效特征 一般使用0或1来筛选也可以画学习曲线或取中位数跑模型来帮助确认 |
SelectKBest | 用来选取K个统计量结果最佳的特征,生成符合统计量要求的新特征矩阵 | 看配合使用的统计量 |
chi2 | 卡方检验,专用于分类算法,捕捉相关性 | 追求p小于显著性水平的特征 |
f_classif | F检验分类,只能捕捉线性相关性要求数据服从正态分布 | 追求p小于显著性水平的特征 |
f_regression | F检验回归,只能捕捉线性相关性要求数据服从正态分布 | 追求p小于显著性水平的特征 |
mutual_info_classif | 互信息分类,可以捕捉任何相关性不能用于稀疏矩阵 | 追求互信息估计大于0的特征 |
mutual_info_regression | 互信息回归,可以捕捉任何相关性不能用于稀疏矩阵 | 追求互信息估计大于0的特征 |
嵌入法引入了算法来挑选特征,因此其计算速度也会和应用的算法有很大的关系。如果采用计算量很大,计算缓慢的算法,嵌入法本身也会非常耗时耗力。并且,在选择完毕之后,我们还是需要自己来评估模型
feature_selection.SelectFromModel API
参数 | 说明 |
---|---|
estimator | 只要是带feature_importances_或者coef_属性,或带有l1和l2惩罚项的模型都可以使用 |
threshold | 重要性低于这个阈值的特征都将被删除 |
prefit | 默认False,判断是否将实例化后的模型直接传递给构造函数。为True,则必须直接调用fit和transform,不能使用fit_transform,并且SelectFromModel不能与cross_val_score,GridSearchCV和克隆估计器的类似实用程序一起使用。 |
norm_order | k可输入非零整数,正无穷,负无穷,默认值为1 在评估器的coef_属性高于一维的情况下,用于过滤低于阈值的系数的向量的范数的阶数 |
max_features | 在阈值设定下,要选择的最大特征数。要禁用阈值并仅根据max_features选择,请设置threshold = -np.inf |
随机森林学习曲线找到最佳特征值
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
RFC_ = RFC(n_estimators =10,random_state=0)
X_embedded = SelectFromModel(RFC_,threshold=0.005).fit_transform(X,y)
#0.005这个阈值对于有780个特征的数据来说,是非常高的阈值,因为平均每个特征只能够分到大约0.001feature_importances_
X_embedded.shape
#模型的维度明显被降低了
#同样的,我们也可以画学习曲线来找最佳阈值
#======【TIME WARNING:10 mins】======#
import numpy as np
import matplotlib.pyplot as plt
RFC_.fit(X,y).feature_importances_
threshold = np.linspace(0,(RFC_.fit(X,y).feature_importances_).max(),20)
score = []
for i in threshold:
X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(X,y)
once = cross_val_score(RFC_,X_embedded,y,cv=5).mean()
score.append(once)
plt.plot(threshold,score)
plt.show()
在0.00134之前寻找最加特征值
#======【TIME WARNING:10 mins】======#
score2 = []
for i in np.linspace(0,0.00134,20):
X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(X,y)
once = cross_val_score(RFC_,X_embedded,y,cv=5).mean()
score2.append(once)
plt.figure(figsize=[20,5])
plt.plot(np.linspace(0,0.00134,20),score2)
plt.xticks(np.linspace(0,0.00134,20))
plt.show()
使用找到的0.000564跑模型:
X_embedded = SelectFromModel(RFC_,threshold=0.000564).fit_transform(X,y)
X_embedded.shape
cross_val_score(RFC_,X_embedded,y,cv=5).mean()
#=====【TIME WARNING:2 min】=====#
#我们可能已经找到了现有模型下的最佳结果,如果我们调整一下随机森林的参数呢?
cross_val_score(RFC(n_estimators=100,random_state=0),X_embedded,y,cv=5).mean()
结果已经接近需要计算2个小时的KNN,如果继续调参可以再提高,所以对于需要思考很多统计量的过滤法来说,嵌入法是更加有效的方法
from sklearn.feature_selection import RFE
RFC_ = RFC(n_estimators =10,random_state=0)
selector = RFE(RFC_, n_features_to_select=340, step=50).fit(X, y)
selector.support_.sum()#340
selector.ranking_
X_wrapper = selector.transform(X)
cross_val_score(RFC_,X_wrapper,y,cv=5).mean()
#======【TIME WARNING: 15 mins】======#
score = []
for i in range(1,751,50):
X_wrapper = RFE(RFC_,n_features_to_select=i, step=50).fit_transform(X,y)
once = cross_val_score(RFC_,X_wrapper,y,cv=5).mean()
score.append(once)
plt.figure(figsize=[20,5])
plt.plot(range(1,751,50),score)
plt.xticks(range(1,751,50))
plt.show()
应用50个特征时,模型的表现就已经达到了90%以上,比嵌入法和过滤法都高效很多
多读多看多试多想!
多读多看多试多想!!
多读多看多试多想!!!