机器学习是一门人工智能的分支学科,通过算法和模型让计算机从数据中学习,进行模型训练和优化,做出预测、分类和决策支持。Python成为机器学习的首选语言,依赖于强大的开源库如Scikit-learn、TensorFlow和PyTorch。本专栏介绍机器学习的相关算法以及基于Python的算法实现。
【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/Python_machine_learning。
上篇文章介绍了机器学习数据预处理的数据准备这一部分的内容,本文介绍数据预处理的特征工程部分。
通常情况下,使用原始数据直接建模的效果往往不好,为了使建立的模型简单精确,需要对原始数据进行特征变换,把原始的特征转化为更为有效的特征。常用的特征变换方法有特征缩放、独热编码和特征离散化等。
不同特征之间往往具有不同的量纲,由此所造成的数值间的分布差异可能会很大,在涉及空间距离计算或梯度下降法等情况时,不对量纲差异进行处理会影响数据分析结果的准确性。为了消除特征之间量纲和取值范围造成的影响,需要对数据进行标准化处理。常用数据标准化方法有离差标准化、标准差标准化、小数定标标准化和函数转换等。
1. 离差标准化(归一化)
离差标准化是对原始数据的一种线性变换,结果是将原始数据的数值映射到
区间内,转换公式如下。
其中,
为样本数据的最大值,
为样本数据的最小值,
为极差。离差标准化保留了原始数据值之间的联系,是消除量纲和数据取值范围影响最简单的方法,但受离群点影响较大,适用于分布较为均匀的数据。
2. 标准差标准化
标准差标准化也叫零均值标准化或z分数标准化,是当前使用最广泛的数据标准化方法。经过该方法处理的数据均值为0,标准差为1,转化公式如下。
其中,
为原始数据的均值,
为原始数据的标准差。标准差标准化适用于数据的最大值和最小值未知的情况,或数据中包含超出取值范围的离群点的情况。
3. 小数定标标准化
通过移动数据的小数位数,将数据映射到区间
区间,移动的小数位数取决于数据绝对值的最大值。转化公式如下,在下方公式中,
表示数据整数位个数。
4. 函数转换
函数变换是使用数学函数对原始数据进行转换,改变原始数据的特征,使特征变得更适合建模,常用的包括平方、开方、取对数、差分运算等。
平方运算如下
开方运算如下
取对数运算如下
差分运算如下
函数变换常用来将不具有正态分布的数据变换成具有正态分布的数据。在时间序列分析中,简单的对数变换或者差分运算常常就可以将非平稳序列转换成平稳序列。还可以使用对数函数转换和反正切函数转换等函数转换方法对数据进行标准化。
对数函数转换是指利用以10为底的对数函数对数据进行转换,即
;反正切函数转换即
,如果要求反正切函数转换的结果全部落入
区间,那么要求原始数据全部大于等于0,否则小于0的数据会被映射到
区间。
数据标准化是机器学习预处理中的一个重要步骤。标准化通常是将数据按比例缩放,使其具有均值为0和标准差为1。在sklearn
中,可以使用StandardScaler
来完成这项任务。下面是一个示例代码,演示如何对数据进行标准化:
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
# 示例数据
data = {
'Feature1': [10, 20, 30, 40, 50],
'Feature2': [100, 200, 300, 400, 500]
}
# 创建 DataFrame
df = pd.DataFrame(data)
# 初始化 StandardScaler
scaler = StandardScaler()
# 对数据进行标准化
df_standardized = pd.DataFrame(scaler.fit_transform(df), columns=df.columns)
print("原始数据:")
print(df)
print("\n标准化后的数据:")
print(df_standardized)
fit()
, fit_transform()
, 和 transform()
是在机器学习中常用的方法,用于数据预处理和模型训练过程中的特征处理。它们的功能略有不同:
fit()
:这个方法用于对训练数据进行学习,即根据训练数据的特征分布或其他统计信息来估计模型参数。例如,在数据预处理过程中,fit() 方法可以计算并保存一些统计值(如均值、方差等)以供后续使用。transform()
:这个方法将学习到的模型参数应用于数据,对数据进行转换。例如,使用 fit() 计算得到的均值和方差可以在 transform() 中用来对数据进行标准化处理。fit_transform()
:这个方法是 fit() 和 transform() 的结合,既进行学习又进行转换。它首先使用训练数据进行学习,然后将学习到的模型参数应用于数据转换,返回转换后的结果。这个方法在某些情况下可以更高效,因为它同时执行了学习和转换步骤,避免了重复计算。需要注意的是,fit_transform()
方法通常只能在训练数据上使用,而 transform()
方法可以在训练数据和测试数据上使用。这是因为在训练数据上学习得到的模型参数,需要一致地应用于训练数据和测试数据,以保持一致性和可比性。
总结起来,fit()
用于学习模型参数,transform()
用于将模型参数应用于数据转换,而 fit_transform()
则结合了二者,先学习再转换。具体使用哪个方法取决于任务的需求和数据处理的流程。
在机器学习中,经常会遇到类型数据,如性别分为男、女,手机运营商分为移动、联通和电信,这种情况下,通常会选择将其转化为数值代入模型,如0、1和–1、0、1,这个时候往往默认为连续型数值进行处理,然而这样会影响模型的效果。
独热编码便即One-Hot编码,又称一位有效编码,是处理类型数据较好的方法,主要是使用N位状态寄存器来对N个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候都只有一个编码位有效。
对于每一个特征,如果它有m个可能值,那么经过独热编码后,就变成了m个二元特征,并且这些特征之间是互斥的,每一次都只有一个被激活,这时原来的数据经过独热编码后会变成稀疏矩阵。对于性别男和女,利用独热编码后可以表示为10和01。
独热编码有以下优点。将离散型特征的取值扩展到欧氏空间,离散型特征的某个取值就对应欧氏空间的某个点;对离散型特征使用独热编码,可以让特征之间的距离计算更为合理。
在Python中使用Scikit-learn库中preprocessing
模块的OneHotEncoder
函数进行独热编码,该函数的基本使用格式如下:
class sklearn.preprocessing.OneHotEncoder(n_values=‘auto’, categorical_features=‘all’, dtype=<class ‘numpy.float64’>, sparse=True, handle_unknown=’error’)
参数名称 | 参数说明 |
---|---|
n_values | 接收int或array of ints。表示每个功能的值数。默认为auto |
categorical_features | 接收all或array of indices或mask。表示将哪些功能视为分类功能。默认为all |
spares | 接收boolean。表示返回是稀疏矩阵还是数组。默认为True |
handle_unknown | 接收str。表示在转换过程中引发错误还是忽略是否存在未知的分类特征。默认为error |
例如,假设有一个分类特征"颜色",可能的取值有"红色"、“蓝色"和"绿色”。使用独热编码后,将创建三个新的二进制特征:“红色”,“蓝色"和"绿色”。对于每个样本,在相应的特征中,属于该类别的取值为1,其他特征都为0。
独热编码可以通过多种方式进行实现,其中最常见的是使用sklearn库中的OneHotEncoder
类。在进行独热编码之前,需要先将字符串类型的数据转换为数值类型。在处理分类特征时,一种常见的方法是使用LabelEncoder
类将字符串类型的数据转换为整数编码,然后再进行独热编码。下面是一个使用OneHotEncoder
进行独热编码的示例:
import numpy as np
import pandas as pd
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
# 示例数据
data = {'Color': ['Red', 'Green', 'Blue', 'Green', 'Red']}
# 创建 DataFrame
df = pd.DataFrame(data)
# 初始化 LabelEncoder
label_encoder = LabelEncoder()
# 将类别数据转换为数字
df['Color_encoded'] = label_encoder.fit_transform(df['Color'])
# 初始化 OneHotEncoder
onehot_encoder = OneHotEncoder(sparse=False)
# 进行独热编码
color_encoded_reshaped = df['Color_encoded'].values.reshape(-1, 1)
onehot_encoded = onehot_encoder.fit_transform(color_encoded_reshaped)
# 创建新的 DataFrame 来展示独热编码的结果
df_encoded = pd.DataFrame(onehot_encoded, columns=label_encoder.classes_)
print("原始数据:")
print(df)
print("\n独热编码后的数据:")
print(df_encoded)
运行上述代码,将得到一个独热编码后的数组,表示每个颜色的二进制特征表示。注意,fit_transform()
方法同时执行了学习和转换步骤。
独热编码常用于机器学习中对分类特征的处理,它可以将分类变量转换为数值变量,使得机器学习算法能够更好地处理这些特征。但需要注意的是,应根据具体情况决定是否使用独热编码,因为它会增加特征维度,可能会导致维度灾难问题。
离散化是指将连续型特征(数值型)变换成离散型特征(类别型)的过程,需要在数据的取值范围内设定若干个离散的划分点,将取值范围划分为一系列区间,最后用不同的符号或标签代表落在每个子区间。例如,将年龄离散化为年龄段,如图所示。
部分只能接收离散型数据的算法,需要将数据离散化后才能正常运行,如ID3、Apriori算法等。而使用离散化搭配独热编码的方法,还能够降低数据的复杂度,将其变得稀疏,增加算法运行速度。
常用的离散化方法主要有3种。等宽法、等频法和通过聚类分析离散化(一维)。
1. 等宽法
等宽法是将数据的值域分成具有相同宽度的区间的离散化方法,区间的个数由数据本身的特点决定或者用户指定,与制作频率分布表类似。
Pandas提供了cut
函数,可以进行连续型数据的等宽离散化,其基础语法格式如下。
pandas.cut(x, bins, right=True, labels=None, retbins=False, precision=3, include_lowest=False)
cut
函数常用参数及其说明如表所示。
参数名称 | 说明 |
---|---|
x | 接收数组或Series。表示需要进行离散化处理的数据,无默认值 |
bins | 接收int,list,array,tuple。表示若为int,代表离散化后的类别数目;若为序列类型的数据,则表示进行切分的区间,每两个数间隔为一个区间,无默认值 |
right | 接收boolean。表示右侧是否为闭区间,默认为True |
labels | 接收list,array。表示离散化后各个类别的名称,默认为空 |
retbins | 接收boolean。表示是否返回区间标签,默认为False |
precision | 接收int。表示显示的标签的精度,默认为3 |
2. 等频法
等频法将相同数量的记录放在每个区间的离散化方法,能够保证每个区间的数量基本一致。cut
函数虽然不能直接实现等频法离散化,但是可以通过定义将相同数量的记录放进每个区间。
3. 基于聚类分析的方法
基于聚类的离散化方法是将连续型数据用聚类算法(如K-Means算法等)进行聚类,然后利用通过聚类得到的簇对数据进行离散化的方法,将合并到一个簇的连续型数据作为一个区间。聚类分析的离散化方法需要用户指定簇的个数,用来决定产生的区间数。
K-Means聚类分析的离散化方法可以很好地根据现有特征的数据分布状况进行聚类。基于聚类的离散化不会出现一部分区间的记录极多或极少的情况,也不会将记录平均的分配到各个区间,能够保留数据原本的分布情况,但是使用该方法进行离散化时依旧需要指定离散化后类别的数目。
数据离散化是将连续数据转换为离散的类别数据的过程。这对于一些机器学习算法可能很有用,因为离散化可以简化数据或将其转换为类别形式。我们可以使用pandas
的cut
函数来实现离散化。下面是一个示例代码,演示如何将连续数据离散化成几个区间:
import pandas as pd
# 示例数据
data = {'Age': [23, 45, 35, 50, 29, 40, 60, 33, 22, 54]}
# 创建 DataFrame
df = pd.DataFrame(data)
# 定义离散化区间
bins = [0, 30, 40, 50, 60, 100]
labels = ['0-30', '30-40', '40-50', '50-60', '60+']
# 使用 cut 函数将 Age 列离散化
df['Age_group'] = pd.cut(df['Age'], bins=bins, labels=labels)
print("原始数据:")
print(df[['Age']])
print("\n离散化后的数据:")
print(df[['Age', 'Age_group']])
小结:使用Scikit-learn的库中preprocessing
模块进行数据特征变换:
StandardScaler
MinMaxScaler
OneHotEncoder
Normalizer
Binarizer
LabelEncoder
Imputer
PolynomialFeatures
特征选择是特征工程中的一个重要的组成部分,其目标是寻找到最优的特征子集。特征选择能够将不相关或者冗余的特征从原本的特征集合中剔除出去,从而有效地缩减特征集合的规模,进一步地减少模型的运行时间,同时也能提高模型的精确度和有效性。
特征选择作为提高机器学习算法性能的一种重要手段,在一定程度上也能规避机器学习经常面临过拟合的问题。过拟合问题表现为模型参数过于贴合训练数据,导致泛化能力不佳,而通过特征选择削减特征的数量能在一定程度上解决过拟合的问题。
特征选择的过程主要由4个环节组成,包括生成子集、评估子集、停止准则和验证结果,如图所示。
在生成子集的步骤中,算法会通过一定的搜索策略从原始特征集合中生成候选的特征子集。
在评估子集的步骤中会针对每个候选子集依据评价准则进行评价,如果新生成的子集的评价比之前的子集要高,则新的子集会成为当前的最优候选子集。
当满足停止准则时输出当前的最优候选子集作为最优子集进行结果验证,验证选取的最优特征子集的有效性。不满足停止准则时则继续生成新的候选子集进行评估。
在特征选择过程中,每一个生成的候选特征子集都需要按照一定的评价准则进行评估。根据评价准则是否独立于学习算法对特征选择方法进行分类,可大致分为3大类:过滤式选择、包裹式选择和嵌入式选择。
过滤式特征选择方法中的评价准则与学习算法没有关联,可以快速排除不相关的特征,计算效率较高。过滤式特征选择的基本思想为:针对每个特征,分别计算该特征对应于不同类别标签的信息量,得到一组结果;将这组结果按由大到小的顺序进行排列,输出排在前面的指定数量的结果所对应的特征。过滤式选择的运作流程如图所示。
过滤式选择中常用的度量信息量的方法有相关系数、卡方检验、互信息等,如表所示。
还存在一种方差选择法,该方法不需要度量信息量,仅通过计算各个特征的方差进行特征选择,方差大于设定的阈值的特征将会保留。
过滤式特征选择是一种通过统计方法评估每个特征的相关性,从而选择最相关的特征。sklearn
提供了SelectKBest
和chi2
等工具来实现过滤式选择。以下是一个使用SelectKBest
和chi2
进行特征选择的示例代码:
import numpy as np
import pandas as pd
from sklearn.feature_selection import SelectKBest, chi2
from sklearn.datasets import load_iris
# 加载示例数据
data = load_iris()
X, y = data.data, data.target
feature_names = data.feature_names
# 创建 DataFrame
df = pd.DataFrame(X, columns=feature_names)
df['target'] = y
# 使用 chi2 进行特征选择
selector = SelectKBest(score_func=chi2, k=2)
X_new = selector.fit_transform(X, y)
# 获取被选择的特征索引
selected_features = np.array(feature_names)[selector.get_support()]
print("原始特征:")
print(feature_names)
print("\n选择的特征:")
print(selected_features)
运行这段代码,显示出原始特征以及被选择的最相关的特征。
与过滤式选择方法不同,包裹式选择选择一个目标函数来一步步地筛选特征。最常用的包裹式特征选择方法为递归消除特征法(recursive feature elimination,RFE)。
递归消除特征法使用一个机器学习模型来进行多轮训练,每轮训练后,消除若干权值系数的对应的特征,再基于新的特征集进行下一轮训练,直到特征个数达到预设的值,停止训练,输出当前的特征子集。RFE算法运作过程如图所示。
包裹式特征选择(Wrapper Method)通过使用预测模型来评估特征子集的性能,并选择能够提供最佳模型性能的特征子集。sklearn
提供了RFE
(Recursive Feature Elimination)和RFECV
(Recursive Feature Elimination with Cross-Validation)等工具来实现包裹式特征选择。下面是一个使用RFECV
进行包裹式特征选择的示例代码:
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.feature_selection import RFECV
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
# 加载示例数据
data = load_iris()
X, y = data.data, data.target
feature_names = data.feature_names
# 创建训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 初始化支持向量机分类器
svc = SVC(kernel="linear")
# 使用 RFECV 进行包裹式特征选择
selector = RFECV(estimator=svc, step=1, cv=5)
selector = selector.fit(X_train, y_train)
# 获取选择的特征索引
selected_features = np.array(feature_names)[selector.support_]
print("原始特征:")
print(feature_names)
print("\n选择的特征:")
print(selected_features)
print("\n最佳特征数量:")
print(selector.n_features_)
运行这段代码,显示出原始特征、选择的最佳特征以及最终选出的特征数量。包裹式特征选择通过递归删除特征并使用交叉验证来评估模型性能,从而选择最能提升模型性能的特征子集。
嵌入式选择方法也使用机器学习方法进行特征选择,与包裹式选择不同之处在于,包裹式选择不停地筛选掉一部分特征来进行迭代训练,而嵌入式选择训练时使用的都是特征全集。
嵌入式选择通常使用L1正则化和L2正则化进行特征筛选,当正则化惩罚项越大时,对应的模型的系数就会越小。当正则化惩罚项大到一定的程度时,部分特征系数会变成0,当正则化惩罚项继续增大到一定程度时,所有的特征系数都会趋于0。但是一部分特征系数在整体趋于0的过程中会先变成0,这部分系数就是可以筛掉的,因此,最终特征系数较大的特征会被保留下来。嵌入式选择的流程如图所示。
嵌入式特征选择方法将特征选择过程嵌入到模型训练过程中,通常是通过正则化技术来完成的。sklearn
提供了L1正则化(Lasso)等方法来进行嵌入式特征选择,使用SelectFromModel
可以根据模型的特征重要性来选择特征。以下是一个使用SelectFromModel
和Lasso回归进行嵌入式特征选择的示例代码:
import numpy as np
from sklearn.datasets import load_iris
from sklearn.linear_model import Lasso
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import SelectFromModel
# 加载示例数据
data = load_iris()
X, y = data.data, data.target
feature_names = data.feature_names
# 创建训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 数据标准化
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# 初始化 Lasso 回归模型
lasso = Lasso(alpha=0.1) # alpha 参数控制正则化强度
# 使用 SelectFromModel 进行特征选择
selector = SelectFromModel(estimator=lasso, threshold="mean")
selector.fit(X_train, y_train)
# 获取选择的特征
selected_features = np.array(feature_names)[selector.get_support()]
print("原始特征:")
print(feature_names)
print("\n选择的特征:")
print(selected_features)
运行这段代码,显示出原始特征以及通过L1正则化选择的特征。Lasso回归通过将不重要的特征系数收缩为零来实现特征选择,因此可以在模型训练过程中自动进行特征选择。
小结:
from sklearn import feature_selection as fs
fs.SelectKBest(score_func, k)
fs.RFECV(estimator, scoring=“r2”)
coef_
或者feature_importances_
的基模型都可以作为estimator
参数传入。 fs.SelectFromModel(estimator)
除了过滤式、包裹式和嵌入式特征选择,还有一种特征选择方法考虑的是特征的“稀疏性”,这种特征选择方法的核心是稀疏编码。
稀疏编码(Sparse Coding)将一个信号表示为一组基的线性组合,而且要求只需要较少的几个基就可以将信号表示出来。稀疏编码算法是一种无监督学习方法,通常用来寻找一组“超完备”基向量来更高效地表示样本数据。
稀疏编码算法中的字典学习(Dictionary Learning)是一个矩阵因式分解问题,旨在从原始数据中找到一组特殊的稀疏信号,在机器视觉中称为视觉单词(visual words),这一组稀疏信号能够线性表示所有的原始信号。字典学习的运作过程如图所示。
在字典学习的过程中,首先需要从样本集合中生成字典,生成字典的过程实际是提取事物最本质的特征,类似于从一篇文章中提取其中的字词从而生成一本专门用于表达该文章的字典。
生成字典获得了样本集合所对应的字典集合后,通过稀疏表示的过程可以得到样本集合的字典表示,类似于使用字典中的字词对文章进行表达。
字典学习对于噪声的鲁棒性强,对大量数据处理速度问题有很大的改进和突破。但字典会带有很多原始数据中的噪声等污染,且字典冗余,当训练数据的位数增加时,计算速度和事件都将遇到瓶颈问题。