数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已;
好的特征不仅能够表示出数据的主要特点,还应该符合模型的假设;
特征越好、灵活性越强,构建的模型越简单、性能越出色。
特征工程是从原始数据提取特征的过程,这些特征可以很好地描述数据,并且利用特征建立的模型在未知数据上的性能表现可以达到最优。特征工程一般包括特征使用、特征获取、特征处理、特征选择和特征监控。
特征处理的方法主要有特征缩放、连续型变量离散化、One-Hot编码等方法。
特征缩放会改变特征的尺度。机器学习中的一些算法是输入的平滑函数,比如线性回归模型、逻辑回归模型等,这些模型会受到输入尺度的影响。所以需要进行特征缩放。
标准化是依照特征矩阵的列处理数据,即通过求标准分数的方法,将特征转换为标准正态分布,并和整体样本分布相关。每个样本点都能对标准化结果产生影响。其转换公式如下: x ′ = x − X ‾ S x^{'}=\frac{x-\overline X}{S} x′=Sx−X其中 X ‾ \overline X X为特征 x x x的均值, S S S为特征 x x x的标准差。
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
iris=load_iris()
X=iris.data
X_df=pd.DataFrame(X,columns=iris.feature_names)
X_new=pd.DataFrame(StandardScaler().fit_transform(X_df),
columns=iris.feature_names)
"""
StandardScaler()中的fit_transform()和fit()方法中的X只能接受二维数据,
所以在对单个特征进行处理的时候需要注意。
若是DataFrame型变量也是直接使用原始公式进行转换。
"""
X_new_2=pd.DataFrame(None)
X_new_3=pd.DataFrame(None)
for i,col in enumerate(X_df.columns):
X_new_2[col]=(X_df[col]-X_df[col].mean())/X_df[col].std()
tmp=StandardScaler().fit_transform(X[:,[i]])
X_new_3[col]=tmp.flatten()
若数据中存在异常值,可能会影响平均值和方差,影响标准化结果。在此种情况下,使用中位数和四分位数间距进行缩放会更有效。
from sklearn.preprocessing import RobustScaler
from sklearn.datasets import load_iris
X=load_iris().data
X_new=RobustScaler().fit_transform(X)
区间缩放法常见的是利用两个最值(最大值和最小值)进行缩放。其公式为: x ′ = x − x m i n x m a x − x m i n x^{'}=\frac{x-x_{min}}{x_{max}-x_{min}} x′=xmax−xminx−xmin其中 x m a x x_{max} xmax和 x m i n x_{min} xmin为特征 x x x的最大值和最小值。
from sklearn.datasets import load_iris
from sklearn.preprocessing import MinMaxScaler
iris=load_iris()
X=iris.data
X_df=pd.DataFrame(X,columns=iris.feature_names)
X_new=pd.DataFrame(MinMaxScaler().fit_transform(X),
columns=iris.feature_names)
#针对单个字段的变换
X_new_2=pd.DataFrame(None)
X_new_3=pd.DataFrame(None)
for i,col in enumerate(X_df.columns):
X_new_2[col]=(X_df[col]-X_df[col].min())/(X_df[col].max()-X_df[col].min())
tmp=MinMaxScaler().fit_transform(X[:,[i]])
X_new_3[col]=tmp.flatten()
归一化是将样本的特征值转换到统一量纲下,将数据映射到 [ 0 , 1 ] [0,1] [0,1]或着 [ a , b ] [a,b] [a,b]区间内。归一化会改变数据的原始距离、分布和信息。使用 L 2 L2 L2范数的归一化公式如下: x ′ = x x 1 2 + x 2 2 + ⋯ + x m 2 x^{'}=\frac{x}{\sqrt{x_{1}^{2}+x_{2}^{2}+\dots+x_{m}^{2}}} x′=x12+x22+⋯+xm2x
from sklearn.datasets import load_iris
from sklearn.preprocessing import Normalizer
iris=load_iris()
X=iris.data
X_new=Normalizer().fit_transform(X)
X_new_2=[]
for i in range(X.shape[0]):
tmp=X[i]/np.sqrt(np.sum(np.square(X[i])))
X_new_2.append(tmp)
X_new_2=np.array(X_new_2)
#X_new和X_new_2中每一行数据的平方和为1
tmp=np.sum(np.square(X_new),axis=1)
tmp_2=np.sum(np.square(X_new_2),axis=1)
tips: sklearn中的Normalizer()可以通过参数norm选择不同的归一化公式,具体有三种方式可选:‘l1’, ‘l2’(默认值), ‘max’。
归一化和标准化、区间缩放法的不同在于,标准化和区间缩放法是针对每一个特征做的,而归一化是针对数据的行做的。经过归一化之后,特征列的范数就是1(目前在机器学习算法中很少看到这一种方法的使用)。
区间缩放法是归一化的一种特例,所以在很多材料里也把区间缩放法称为归一化方法(以下所说的归一化方法是指区间缩放法)。归一化和标准化的应用场景如下:
对特征进行缩放可以带来几个优势:
实现
from sklearn.datasets import make_regression
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error,r2_score
from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split
X,y=make_regression(n_samples=1000,n_features=2,n_informative=2,
noise=10,n_targets=1,random_state=0)
#人为调整第二个变量的取值范围,作为归一化之前的数据 X为归一化之后的数据
X_new=X.copy()
X_new[:,1]=X_new[:,1]*5+100
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.25,
random_state=0)
X_train_new=X_train.copy()
X_train_new[:,1]=X_train_new[:,1]*5+100
X_test_new=X_test.copy()
X_test_new[:,1]=X_test_new[:,1]*5+100
#验证模型收敛速度(使用梯度下降法)
def L_theta(theta, X_x0, y,):
h = np.dot(X_x0, theta) # np.dot 表示矩阵乘法、
theta_without_t0 = theta[1:]
L_theta = 0.5 * mean_squared_error(h, y)
return L_theta
# 梯度下降
def GD(lamb,X_x0, theta, y, alpha):
m=X_x0.shape[0]
result=[]
for i in range(T):
h = np.dot(X_x0, theta)
theta_with_t0_0 = np.r_[np.zeros([1, 1]), theta[1:]]
theta -=(alpha * 1/m * np.dot(X_x0.T, h - y) + lamb*(theta_with_t0_0))
if i%5000==0:
result.append(L_theta(theta, X_x0, y))
return theta,result
T = 120000 # 迭代次数
theta = np.ones((3, 1)) # 参数的初始化,degree = 11,一个12个参数
alpha = 0.000005
lamb = 0.000001
X_x0=np.c_[np.ones((X.shape[0],1)),X]
y_y0=y.reshape(-1,1)
theta,result= GD(lamb=lamb,X_x0=X_x0, theta=theta, y=y_y0, alpha=alpha)
X_new_x0=np.c_[np.ones((X_new.shape[0],1)),X_new]
theta_new = np.ones((3, 1))
theta_new,result_new = GD(lamb=lamb,X_x0=X_new_x0, theta=theta_new, y=y_y0, alpha=alpha)
plt.plot(result,label='after normalizer')
plt.plot(result_new,label='before normalizer')
plt.legend()
plt.ylabel("MSE")
plt.show()
##分别对两组数据训练模型
lr=LinearRegression()
lr.fit(X_train,y_train)
y_test_pred=lr.predict(X_test)
score,mse=r2_score(y_test,y_test_pred),mean_squared_error(y_test,y_test_pred)
print("归一化之后模型的准确率: R2为{:.3f},MSE为{:.3f}".format(score,mse))
print("归一化之后模型的参数重要性依次为:{:.3f},{:.3f}".format(lr.coef_[0],lr.coef_[1]))
print("归一化之后模型的截距为:{:.3f}".format(lr.intercept_))
lr=LinearRegression()
lr.fit(X_train_new,y_train)
y_test_pred_new=lr.predict(X_test_new)
score,mse=r2_score(y_test,y_test_pred),mean_squared_error(y_test,y_test_pred)
print("归一化之前模型的准确率: R2为{:.3f},MSE为{:.3f}".format(score,mse))
print("归一化之前模型的参数重要性依次为:{:.3f},{:.3f}".format(lr.coef_[0],lr.coef_[1]))
print("归一化之前模型的截距为:{:.3f}".format(lr.intercept_))
(1) 在相同迭代次数下,归一化之后其收敛速度更快,具体如下图:
(2) 虽然模型效果并没有下降,但归一化之后特征的参数发生了变化。具体结果如下 :
归一化之后模型的准确率: R2为0.973,MSE为88.592
归一化之后模型的参数重要性依次为:40.455, 40.700
归一化之后模型的截距为:-0.410
归一化之前模型的准确率: R2为0.973,MSE为88.592
归一化之前模型的参数重要性依次为:40.455, 8.140
归一化之后模型的截距为:-814.418
两个或两个以上特征的乘积可以组成一对简单的交互式特征。sklearn中提供了专门的类来构造交互式特征,具体如下:
其中几个参数的作用如下:
类的属性:
from sklearn.preprocessing import PolynomialFeatures
from sklearn.datasets import load_iris
X,y=load_iris(return_X_y=True)
poly=PolynomialFeatures(degree=3,interaction_only=True,include_bias=False)
X_new=poly.fit_transform(X)
#自动生成新特征的名称
names=poly.get_feature_names(input_features=load_iris().feature_names)
当数据大量且迅速地生成时,很有可能包含一些极端值。这时候就需要对数据进行检查,确定是应该保留数据原始的数值形式,还是将其转换成二值数据,或者进行粗粒度的分箱操作。
二值化的核心在于设定一个阈值,大于阈值的设为1,否则为0。
from sklearn.preprocessing import Binarizer
from sklearn.datasets import load_iris
X,y=load_iris(return_X_y=True)
#threshold参数用于设定阈值
bn=Binarizer(threshold=3)
X_new=bn.fit_transform(X)
区间分箱可以将连续变量离散化。在对变量进行区间量化之前,需要确定分箱宽度。有两种确定分箱宽度的方法:固定宽度分箱和分位数分箱。
区间分箱可以用pandas中的cut()(固定宽度分箱)和qcut(分位数分箱)方法实现,不再赘述。
Box-Cox变换主要用于用于连续的响应变量不满足正态分布的情况,其转化公式如下: x ′ = { x λ − 1 λ λ ≠ 0 l n ( x ) λ = 0 x^{'}=\begin{cases} \frac{x^{\lambda}-1}{\lambda}&\lambda \neq0 \\ ln(x) &\lambda =0 \end{cases} x′={λxλ−1ln(x)λ=0λ=0当 λ = 0 \lambda=0 λ=0时为对数变换;当 λ = 0.5 \lambda =0.5 λ=0.5时为平方根变换。当 λ \lambda λ小于1时,可以对大数值的范围进行压缩;当 λ \lambda λ大于1时,作用相反。以对数函数 f x ) = l o g 10 ( x ) fx)=log_{10}(x) fx)=log10(x)为例,当 x ∈ [ 1 , 10 ] x\in[1,10] x∈[1,10]时, f ( x ) ∈ [ 0 , 1 ] f(x)\in[0,1] f(x)∈[0,1]。当 x ∈ [ 10 , 100 ] x\in[10,100] x∈[10,100]时, f ( x ) ∈ [ 1 , 2 ] f(x)\in[1,2] f(x)∈[1,2]。注意,在对变量进行Box-Cox变换之前,要先对起进行归一化。
Box-Cox变换的优点主要有以下几个方面:
import pandas as pd
from sklearn.linear_model import LinearRegression
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error,mean_absolute_error
from scipy import stats
from matplotlib import pyplot as plt
from sklearn.preprocessing import FunctionTransformer
data=pd.read_csv('onlinenewspopularity.csv',usecols=[3,60])
#数据变换-对数变换
data_new=data.copy()
log_f=FunctionTransformer(np.log1p,validate=False)
data_new.iloc[:,0]=log.fit_transform(data_new.iloc[:,[0]])
#数据变换-BoxCox变换
data_new_2=data.copy()
data_new_2.iloc[:,0]+=1
data_new_2.iloc[:,0],lamb=stats.boxcox(data_new_2.iloc[:,0])
#分布散点图
fig=plt.figure(figsize=(10,20),dpi=80)
fig.add_subplot(3,1,1)
plt.scatter(data.iloc[:,0],data.iloc[:,1],label='normal')
plt.xlabel('Number of Words in Articles')
plt.ylabel('Number of Shares')
plt.legend()
fig.add_subplot(3,1,2)
plt.scatter(data_new.iloc[:,0],data.iloc[:,1],label='log transform')
plt.xlabel('log the Number of Words in Articles')
plt.ylabel('Number of Shares')
plt.legend()
fig.add_subplot(3,1,3)
plt.scatter(data_new_2.iloc[:,0],data.iloc[:,1],label='box-cox transform')
plt.xlabel('Box-Cox Number of Words in Articles')
plt.ylabel('Number of Shares')
plt.legend()
plt.show()
#模型训练
X_train,X_test,y_train,y_test=train_test_split(data.iloc[:,[0]],data.iloc[:,[1]],
test_size=0.2,random_state=0)
X_train_new=data_new.iloc[X_train.index,[0]]
X_test_new=data_new.iloc[X_test.index,[0]]
X_train_new_2=data_new_2.iloc[X_train.index,[0]]
X_test_new_2=data_new_2.iloc[X_test.index,[0]]
lr=LinearRegression()
lr.fit(X_train,y_train)
y_pred=lr.predict(X_test)
mae,mse=mean_absolute_error(y_test,y_pred),mean_squared_error(y_test,y_pred)
print("变换之前模型的准确率:MAE为{:.3f},MSE为{:.3f}".format(mae,mse))
lr1=LinearRegression()
lr1.fit(X_train_new,y_train)
y_pred1=lr1.predict(X_test_new)
mae,mse=mean_absolute_error(y_test,y_pred1),mean_squared_error(y_test,y_pred1)
print("对数变换之后模型的准确率:MAE为{:.3f},MSE为{:.3f}".format(mae,mse))
lr2=LinearRegression()
lr2.fit(X_train_new_2,y_train)
y_pred2=lr2.predict(X_test_new_2)
mae,mse=mean_absolute_error(y_test,y_pred2),mean_squared_error(y_test,y_pred2)
print("Box-Cox变换之后模型的准确率:MAE为{:.3f},MSE为{:.3f}".format(mae,mse))
(1)散点图
(2) 模型效果(在这里模型效果没有明显提升或降低)
变换之前模型的准确率:MAE为3174.127,MSE为76106487.136
对数变换之后模型的准确率:MAE为3172.253,MSE为76119780.290
Box-Cox变换之后模型的准确率:MAE为3176.513,MSE为76109830.747
前面介绍的特征处理方法大多用于数值型变量。还有一些特征处理方法仅适用于类别性变量。
在以前的博文中已经介绍过这两种编码方法了,具体可以参考one-hot编码和哑变量编码,这里不再赘述。对类别型变量进行One-Hot编码或哑变量编码可以带来以下好处:
如果类别型变量不同取值之间存在大小关系,则可以使用序号编码将类别型变量转换为数值。
import pandas as pd
data=pd.DataFrame(['二线','三线','二线','一线','超一线'],
columns=['城市发展等级'])
dev_map={'三线':1,'二线':2,'一线':3,'超一线':4}
data['dev_map']=data['城市发展等级'].map(dev_map)
tips: sklearn.preprocessing中提供了LabelEncoder类可以直接对特征进行序号编码,但该函数无法指定类别型变量的大小顺序,使用时要注意。