1 Star 6 Fork 4

OpenDocCN / apachecn-ml-zh

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
3.md 54.82 KB
一键复制 编辑 原始数据 按行查看 历史
布客飞龙 提交于 2022-01-02 01:27 . 2022-01-02 01:27:33

三、数据预处理

任何对机器学习 ( ML )感兴趣的人肯定都会听说,一个数据科学家或者机器学习工程师 80%的时间都花在了准备数据上,剩下的 20%都花在了构建和评估模型上。准备数据花费的大量时间被认为是构建良好模型的投资。这是一个简单的模型,使用优秀的数据集构建的模型超过了使用糟糕的数据集开发的复杂模型。在现实生活中,找到一个可靠的数据集是非常困难的。我们必须创造和培育好的数据。你一定在想,如何创造好的数据?这是我们将在本章中发现的东西。我们将研究创建优秀且可行的数据集所需的一切。理论上,好与我们手头的任务以及我们如何感知和消费数据有关。在本章中,我们将引导您完成以下主题:

  • 数据转换
  • 特征选择
  • 降维
  • 特征生成

对于每一个主题,我们将讨论在数据集中遇到的不同类型的数据上可以做的各种事情。我们还将考虑一些自动化的开源特性准备工具,这些工具在用 Python 准备数据时会派上用场。

让我们从数据转换的第一个主题开始。

技术要求

所有代码示例都可以在 GitHub 的Chapter 03文件夹中找到。

数据转换

让我们假设我们正在研究一个 ML 模型,其任务是预测员工流失。基于我们对业务的理解,我们可能会包含一些创建一个好模型所必需的相关变量。另一方面,我们可能会选择丢弃一些没有相关信息的功能,比如EmployeeID

Identifying the ID columns is known as identifier detection. Identifier columns don't add any information to a model in pattern detection and prediction. So, identifier column detection functionality can be a part of the AutoML package and we use it based on the algorithm or a task dependency.

一旦我们决定了要使用的领域,我们就可以探索数据来转换某些有助于学习过程的特征。转换为数据增加了一些经验,这有利于 ML 模型。例如,员工起始日期为 2018 年 2 月 11 日,该日期不提供任何信息。但是,如果我们将此功能转换为四个属性—日期、日、月和年,它将为模型构建练习增加价值。

特征变换也很大程度上取决于所使用的最大似然算法的类型。大体上,我们可以将监督模型分为两类——基于树的模型和非基于树的模型。

Tree-based models handle the abnormality in most features by themselves. Non-tree-based models, such as nearest neighbors and linear regression, improve their predictive power when we do feature transformations.

理论解释已经够多了。现在,让我们直接跳到一些可以对我们经常遇到的各种数据类型执行的功能转换。我们将首先从数字特征开始。

数字数据转换

以下是一些最广泛使用的数值数据转换方法:

  • 缩放比例
  • 缺少值
  • 极端值

这里显示的技术可以嵌入到函数中,这些函数可以直接应用于在 AutoML 流水线中转换数值数据。

缩放比例

标准化标准化是行业内使用的缩放技术的两个术语。这两种技术都确保模型中使用的数字特征在其表示中具有同等的权重。大多数时候,人们交替使用标准化和规范化。虽然两者都是缩放技术,但两者之间只有一线之差。

Standardization assumes the data to be normally distributed. It rescales the data to mean as zero and standard deviation as one. Normalization is a scaling technique that assumes no prior distribution of the data. In this technique, the numerical data is rescaled to a fixed range either: 0 to 1, -1 to +1, and so on.

以下是一些广泛使用的标准化或规范化数据的技术:

  • Z 评分标准化:这里,如果数据遵循高斯分布,则数据以均值为零、标准差为 1 的方式重新缩放。一个优先的要求是使数字数据呈正态分布。数学上表示为,其中为数值的平均值,σ为数值的标准差。

Scikit-learn 提供了各种方法来标准化和规范化数据。让我们首先使用以下代码片段加载HR损耗数据集:

%matplotlib inline
import numpy as np
import pandas as pd
hr_data = pd.read_csv('data/hr.csv', header=0)
print (hr_data.head())

前面代码的输出显示了数据集的各种属性以及一些数据点:

让我们使用以下代码来分析数据集的分布:

hr_data[hr_data.dtypes[(hr_data.dtypes=="float64")|(hr_data.dtypes=="int64")].index.values].hist(figsize=[11,11])

前面代码的输出是几个不同数值属性的直方图:

例如,让我们使用sklearn.preprocessing模块中的StandardScaler来标准化satisfaction_level列的值。一旦我们导入了方法,我们首先需要创建一个StandardScaler类的实例。接下来,我们需要使用fit_transform方法来拟合和转换我们需要标准化的列。在下面的例子中,我们使用satisfaction_level属性进行标准化:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
hr_data_scaler=scaler.fit_transform(hr_data[['satisfaction_level']])
hr_data_scaler_df = pd.DataFrame(hr_data_scaler)
hr_data_scaler_df.max()
hr_data_scaler_df[hr_data_scaler_df.dtypes[(hr_data_scaler_df.dtypes=="float64")|(hr_data_scaler_df.dtypes=="int64")].index.values].hist(figsize=[11,11])

一旦我们执行了前面的代码,我们就可以再次查看satisfication_level实例直方图,并观察到值在 -21.5 之间是标准化的:

  • 最小-最大标准化:在这种技术中,变量的最小值从其实际值减去其最大值和最小值之差。从数学上讲,它由以下内容表示:

MinMaxScaler方法可在 scikit-learn 的preprocessing模块中获得。在本例中,我们对HR损耗数据集的四个属性进行了规范化— average_monthly_hourslast_evaluationnumber_projectsatisfaction_level。我们遵循类似于StandardScaler的流程。我们首先需要从sklearn.preprocessing模块导入MinMaxScaler,并创建一个MinMaxScaler类的实例。

接下来,我们需要使用fit_transform方法来拟合和变换列:

from sklearn.preprocessing import MinMaxScaler
minmax=MinMaxScaler()
hr_data_minmax=minmax.fit_transform(hr_data[[ 'average_montly_hours',
 'last_evaluation', 'number_project', 'satisfaction_level']])
hr_data_minmax_df = pd.DataFrame(hr_data_minmax)
hr_data_minmax_df.min()
hr_data_minmax_df.max()
hr_data_minmax_df[hr_data_minmax_df.dtypes[(hr_data_minmax_df.dtypes=="float64")|(hr_data_minmax_df.dtypes=="int64")].index.values].hist(figsize=[11,11])

以下直方图描述了转换后的四个属性的值分布在 01 之间:

缺少值

我们经常遇到数据集,其中并非所有的值都适用于特定的变量/属性。出现这种情况有几个原因,例如调查中被忽略的问题、打字错误、设备故障等等。在数据挖掘项目中会遇到这些缺失的值,处理这些值是必不可少的。

缺失值插补占据了数据科学家的大部分时间。我们可以通过各种方法来估算缺失值。决定性的因素是当归因于这些不可用的值时使用什么。决定什么时候用什么来输入缺失值的过程是一种天赋,来自于处理数据的经验。有时直接移除这些值更好,对于某些赋值,最好使用高级挖掘技术来估算这些值。

因此,出现了两个最重要的问题:

  • 你什么时候使用哪种插补方法?
  • 估算价值的最好方法是什么?

我们认为它来自于处理缺失值的经验;最好的开始方法是进行比较研究,对数据应用不同的插补方法,然后选择适当的技术,用偏差最小的估计值分配空值。

一般来说,当我们遇到一个丢失的值时,我们会首先尝试检查一个值为什么会丢失。是因为收集数据时的一些问题,还是罪魁祸首是数据源本身?最好是从根源上解决问题,而不是直接将价值强加于人。这是一个理想的情况,而且,大多数时候,这是不可能的。例如,如果我们正在处理一个调查数据集,而一些受访者选择不透露具体信息,在这种情况下,输入值可能是不可避免的。

因此,在我们开始输入值之前,我们可以使用以下准则:

  • 调查丢失的数据
  • 分析缺失的数据
  • 决定产生最小偏差估计的最佳策略

我们可以将此记为缺失值插补的 IAD 规则(即调查、分析和决定)。以下是我们处理缺失值的一些可用方法:

  1. 删除或删除数据:当很少的数据点缺失时,我们可以忽略数据,单独分析那些案例。这种方法被称为列表明智删除。但是,当缺少太多值时,这是不可取的,因为我们可能会丢失数据中的一些有价值的信息。成对删除是另一种技术,我们可以只删除缺失的值。这表明我们分析所有只存在感兴趣数据的情况。这是一个安全的策略,但是,使用这种方法,即使数据有微小的变化,我们也可能每次从每个样本中获得不同的结果。

我们将再次使用HR磨损数据集来演示缺失值处理。让我们首先加载数据集,并查看数据集中的空值数量:

import numpy as np
import pandas as pd
hr_data = pd.read_csv('data/hr.csv', header=0)
print (hr_data.head())
print('Nulls in the data set' ,hr_data.isnull().sum())

从下面的输出中我们可以看到,数据集相对干净,只是promotion_last_5years有一些缺失值。因此,我们将把一些缺失的值合成到一些列中:

我们使用以下代码片段将列promotion_last_5yearsaverage_montly_hoursnumber_project中的一些值替换为空值:

#As there are no null introduce some nulls by replacing 0 in promotion_last_5years with NaN
hr_data[['promotion_last_5years']] = hr_data[[ 'promotion_last_5years']].replace(0, np.NaN)
#As there are no null introduce some nulls by replacing 262 in promotion_last_5years with NaN
hr_data[['average_montly_hours']] = hr_data[[ 'average_montly_hours']].replace(262, np.NaN)
#Replace 2 in number_project with NaN
hr_data[['number_project']] = hr_data[[ 'number_project']].replace(2, np.NaN)
print('Nulls in the data set', hr_data.isnull().sum())

在本练习之后,为这三列插入了一些空值,我们可以从以下结果中看到:

在我们删除行之前,让我们首先创建一个hr_data的副本,这样我们就不会替换原始数据集,该数据集将用于演示其他缺失值插补方法。接下来,我们使用dropna方法删除缺少值的行:

#Remove rows
hr_data_1 = hr_data.copy()
print('Shape of the data set before removing nulls ', hr_data_1.shape)
# drop rows with missing values
hr_data_1.dropna(inplace=True)
# summarize the number of rows and columns in the dataset
print('Shape of the data set after removing nulls ',hr_data_1.shape)

我们可以观察到这个练习后的行数从14999减少到278。删除行必须小心使用。由于promotion_last_5years有大约 14,680 个缺失值,14,680 个记录被完全删除:

  1. 用全局常量填充缺失值:我们可以用一个全局常量,比如 NA 或者-999,把缺失值和数据集的其他部分分开。此外,还有没有任何值的空值,但它们构成了数据集的固有部分。这些值被故意保留为空白。当空值无法从缺失值中分离出来时,使用全局常数是一种安全的策略。

我们可以使用fillna方法将缺失值替换为常量值,如-999。下面的代码片段演示了该方法的使用:

#Mark global constant for missing values
hr_data_3 = hr_data.copy()
# fill missing values with -999
hr_data_3.fillna(-999, inplace=True)
# count the number of NaN values in each column
print(hr_data_3.isnull().sum())
print(hr_data_3.head())

我们可以从以下结果中看到,所有缺失的值都被-999值替换:

  1. 用属性均值/中值替换缺失值:这是数据科学家和机器学习工程师最喜欢的方法。我们可以用数值的平均值或中值以及分类值的模式来替换缺失的值。这种方法的缺点是它可能会降低属性的可变性,这反过来又会削弱相关性估计。如果我们处理的是监督分类模型,我们还可以用数值的组均值或中值以及分类值的分组模式来替换缺失的值。在这些分组均值/中值方法中,属性值按目标值分组,该组中缺失的值被替换为该组的均值/中值。

我们可以使用相同的fillna方法,以均值函数为参数,用均值替换缺失值。下面的代码演示了它的用法:

#Replace mean for missing values
hr_data_2 = hr_data.copy()
# fill missing values with mean column values
hr_data_2.fillna(hr_data_2.mean(), inplace=True)
# count the number of NaN values in each column
print(hr_data_2.isnull().sum())
print(hr_data_2.head())

我们可以从下面的输出中看到,丢失的值被替换为每个属性的平均值:

  1. 使用指示变量:我们还可以生成一个二进制变量,指示记录中是否有缺失值。我们可以将其扩展到多个属性,其中我们可以为每个属性创建二进制指示器变量。我们还可以估算缺失的值,并构建二元指标变量来表示它是真实变量还是估算变量。如果一个值因为真正的跳过而丢失,结果不会有偏差。

正如我们演示其他插补方法一样,让我们首先复制原始数据,并创建新的列来指示被插补的属性和值。下面的代码首先为那些缺少值的属性创建新的列,并将_was_missing追加到原始列名中。

接下来,丢失的值被替换为全局常数-999。虽然我们使用了全局常数插补方法,但您可以使用任何插补方法来插补缺失的值:

# make copy to avoid changing original data (when Imputing)
hr_data_4 = hr_data.copy()
# make new columns indicating what is imputed
cols_with_missing = (col for col in hr_data_4.columns 
 if hr_data_4[col].isnull().any())
for col in cols_with_missing:
 hr_data_4[col + '_was_missing'] = hr_data_4[col].isnull()
hr_data_4.fillna(-999, inplace=True)
hr_data_4.head()

我们可以从以下结果中看到,创建了新的列,表明属性中是否存在缺失值:

  1. 使用数据挖掘算法预测最大可能值:我们可以应用 ML 算法,如 KNN、线性回归、随机森林或决策树技术,来预测缺失属性的最大可能值。这种方法的一个缺点是,如果计划在另一项任务(如预测或分类)中对同一数据集使用相同的算法,则可能会过度填充数据。

在 Python 中,fancyimpute是一个提供高级数据挖掘选项来估算缺失值的库。这是我们最常使用的东西,所以我们想演示一下这个包。Python 中可能还有其他一些库也可以完成类似的任务。首先,我们需要使用以下命令安装fancyimpute库。这必须在命令提示符下执行:

pip install fancyimpute

安装完成后,我们可以返回 Jupyter 笔记本,从fancyimpute库中导入KNN方法。KNN 插补方法只适用于数值。因此,我们首先从hr_data集合中只选择数字列。接下来,创建一个 k 等于3的 KNN 模型,并为数字属性替换缺失的值:

from fancyimpute import KNN

hr_data_5 = hr_data.copy()
hr_numeric = hr_data_5.select_dtypes(include=[np.float])
hr_numeric = pd.DataFrame(KNN(3).complete(hr_numeric))
hr_numeric.columns = hr_numeric.columns
hr_numeric.index = hr_numeric.index
hr_numeric.head()

fancyimpute库使用 TensorFlow 后端,执行起来需要一些时间。一旦执行完成,我们可以向下滚动查看插补结果,如以下结果截图所示:

极端值

异常值是不符合整体数据模式的极端值。它们通常远离其他观测结果,扭曲了数据的总体分布。在模型构建过程中包含它们可能会导致错误的结果。适当地对待他们是非常重要的。异常值有两种类型——单变量和多变量。

检测和处理单变量异常值

顾名思义,单变量异常值基于数据集中的单个属性。单变量异常值是通过使用箱线图和查看属性值的分布来发现的。然而,当我们构建 AutoML 流水线时,我们没有权限可视化数据分布。相反,AutoML 系统应该能够检测异常值并自行处理它们。

因此,我们可以部署以下三种方法中的任何一种来自动进行单变量异常检测和处理:

  • 四分位数范围和过滤
  • Winsorizing
  • 整理

让我们创建一个虚拟异常数据集来演示异常检测和处理方法:

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
number_of_samples = 200
outlier_perc = 0.1
number_of_outliers = number_of_samples - int ( (1-outlier_perc) * number_of_samples )
# Normal Data
normal_data = np.random.randn(int ( (1-outlier_perc) * number_of_samples ),1)
# Inject Outlier data
outliers = np.random.uniform(low=-9,high=9,size=(number_of_outliers,1))
# Final data set
final_data = np.r_[normal_data,outliers]

让我们使用以下代码绘制新创建的数据集:

#Check data
plt.cla()
plt.figure(1)
plt.title("Dummy Data set")
plt.scatter(range(len(final_data)),final_data,c='b')

从下图中我们可以看出,在数据集的末尾有一些异常值:

我们还可以使用下面的代码生成一个方框图来观察异常值。箱线图,也称为箱线图和触须图,是一种基于五个数字汇总来表示数据分布的方法:最小值、第一个四分位数、中值、第三个四分位数和最大值。任何低于最小值和高于最大值的都被认为是异常值:

## Detect Outlier###
plt.boxplot(final_data)

从得到的方框图中,我们可以观察到存在一些超出最大和最小标记的值。因此,我们可以假设我们成功地创建了一些异常值:

去除异常值的一种方法是过滤高于最大值和低于最小值的值。要完成这个任务,首先需要计算四分位数区间 ( IQR )。

四分位数范围

四分位数之间的范围是数据集中可变性或分布的度量。它是通过将数据集分成四分位数来计算的。四分位数根据我们之前研究的五个数字的汇总将数据集分成四份,即最小值、第一个四分位数、第二个四分位数、第三个四分位数和最大值。第二个四分位数是排序数据集的中值;第一个四分位数是排序数据集前半部分的中间值,第三个四分位数是排序数据集后半部分的中间值。

四分位数范围是第三个四分位数(quartile75Q3)与第一个四分位数(quartile25Q1)之间的差值。

我们使用以下代码计算 Python 中的IQR:

## IQR Method Outlier Detection and Removal(filter) ##
quartile75, quartile25 = np.percentile(final_data, [75 ,25])
## Inter Quartile Range ##
IQR = quartile75 - quartile25
print("IQR",IQR)

从下面的代码中我们可以看到数据集的IQR1.49:

过滤值

我们可以过滤高于最大值和低于最小值的值。最小值可用公式计算:quartile25 - (IQR * 1.5)最大值为quartile75 + (IQR*1.5)

The method to calculate maximum and minimum values is based on Turkey Fences, which was developed by John Turkey. The value 1.5 indicates about 1% of measurements as outliers and is synonymous with the 3σ principle, which is practiced as a bound in many statistical tests. We can use any value other than 1.5, which is at our discretion. However, the bound may increase or decrease the number of outliers in the dataset.

我们使用下面的 Python 代码来计算数据集的MaxMin值:

## Calculate Min and Max values ##
min_value = quartile25 - (IQR*1.5)
max_value = quartile75 + (IQR*1.5)
print("Max", max_value)
print("Min", min_value)

在执行前面的代码后,我们注意到以下输出。最大值和最小值分别为2.94-3.03:

接下来,我们使用以下代码过滤低于min_value和高于max_value的值:

filtered_values = final_data.copy()
filtered_values[ filtered_values< min_value] = np.nan
filtered_values[ filtered_values > max_value] = np.nan
#Check filtered data
plt.cla()
plt.figure(1)
plt.title("IQR Filtered Dummy Data set")
plt.scatter(range(len(filtered_values)),filtered_values,c='b')

代码执行成功完成后,我们可以看到离群值被消除了,数据集看起来远比之前的数据集好:

Winsorizing

Winsorizing 是用较小的绝对值代替极值的方法。它对数值列中的非空值进行排序,计算尾值,然后用定义的参数替换尾值。

我们可以使用 SciPy 包中的winsorize方法来处理异常值。SciPy 是一个 Python 库,是科学和技术计算领域开源 Python 贡献的集合。它拥有大量的统计计算模块、线性代数、优化、信号和图像处理模块以及更多模块。

一旦导入winsorize方法,我们需要将datalimit参数传递给函数。尾值的计算和替换是通过这种方法进行的,并生成结果无异常值数据:

##### Winsorization ####
from scipy.stats.mstats import winsorize
import statsmodels.api as sm
limit = 0.15
winsorized_data = winsorize(final_data,limits=limit)
#Check winsorized data
plt.cla()
plt.figure(1)
plt.title("Winsorized Dummy Data set")
plt.scatter(range(len(winsorized_data)),winsorized_data,c='b')

从下面的图中我们可以观察到,极值已经被消除,数据看起来没有异常值:

整理

修剪与 winsorizing 相同,只是尾部值被裁剪掉了。

stats库中的trimboth方法从数据的两端分割数据集。final_data0.1的极限作为参数传递给函数,从两端修剪 10%的数据:

### Trimming Outliers ###
from scipy import stats
trimmed_data = stats.trimboth(final_data, 0.1)
#Check trimmed data
plt.cla()
plt.figure(1)
plt.title("Trimmed Dummy Data set")
plt.scatter(range(len(trimmed_data)),trimmed_data,c='b')

我们可以从下面的结果图中观察到,极值被截断,不再存在于数据集中:

多元异常值的检测和处理

多元异常值是至少两个变量的极端分数的混合。单变量离群点检测方法非常适合处理一维数据,但是当我们越过一维时,使用这些方法检测离群点就变得具有挑战性。多元异常检测方法也是异常检测方法的一种形式。一类 SVM、局部异常因子 ( LOF )和IsolationForest等技术是检测多元异常值的有用方法。

我们使用以下IsolationForest代码描述HR磨损数据集上的多元异常检测。我们需要从sklearn.ensemble包装中进口IsolationForest。接下来,我们加载数据,将分类变量转换为一个热编码变量,并使用估计量的数量调用IsolationForest方法:

##Isolation forest
import numpy as np
import pandas as pd
from sklearn.ensemble import IsolationForest
hr_data = pd.read_csv('data/hr.csv', header=0)
print('Total number of records ',hr_data.shape)
hr_data = hr_data.dropna()
data_trnsf = pd.get_dummies(hr_data, columns =['salary', 'sales'])
data_trnsf.columns
clf = IsolationForest(n_estimators=100)

然后,我们将IsolationForest实例(clf)拟合到数据,并使用predict方法预测异常值。异常值由-1表示,非异常值(也称为新数据)由1表示:

clf.fit(data_trnsf)
y_pred_train = clf.predict(data_trnsf)
data_trnsf['outlier'] = y_pred_train
print('Number of outliers ',data_trnsf.loc[data_trnsf['outlier'] == -1].shape)
print('Number of non outliers ',data_trnsf.loc[data_trnsf['outlier'] == 1].shape)

从下面的输出中我们可以看到,模型能够从14999记录的数据集中识别出大约1500个异常值:

扔掉

宁滨是一个将连续数值分组到更小数量的桶或箱中的过程。这是离散化连续数据值的重要技术。许多算法如朴素贝叶斯和 Apriori 在离散数据集上运行良好,因此有必要将连续数据转换为离散值。

有各种类型的宁滨方法:

  • 等宽宁滨:通过将数据划分为大小相等的 k 区间来确定等宽仓;

其中 w 为箱的宽度, maxval 为数据中的最大值, minval 为数据中的最小值, k 为所需的箱数

区间边界形成如下:

  • 等频宁滨:通过将数据分成 k 组来确定等频仓,其中每组包括相同数量的值。

在这两种方法中, k 的值是根据我们的要求以及试错过程确定的。

除了这两种方法之外,我们还可以明确提到创建面元的切割点。当我们知道数据并希望它以某种格式入库时,这非常有帮助。以下代码是基于预定义切割点执行宁滨的函数:

#Bin Values:
def bins(column, cutpoints, labels=None):
 #Define min and max values:
 min_val = column.min()
 max_val = column.max()
 print('Minimum value ',min_val)
 print(' Maximum Value ',max_val)
 break_points = [min_val] + cut_points + [max_val]
 if not labels:
   labels = range(len(cut_points)+1)
 #Create bins using the cut function in pandas
 column_bin = pd.cut(column,bins=break_points,labels=labels,include_lowest=True)
 return column_bin

以下代码将员工满意度分为三类:lowmediumhigh。低于0.3的被认为是low满意度,高于0.6的分数被认为是高度满意的员工分数,介于这两个值之间的被认为是medium:

import pandas as pd
hr_data = pd.read_csv('data/hr.csv', header=0)
hr_data.head()
hr_data = hr_data.dropna()
print(hr_data.shape)
print(list(hr_data.columns))
#Binning satisfaction level:
cut_points = [0.3,0.6]
labels = ["low","medium","high"]
hr_data["satisfaction_level"] = bins(hr_data["satisfaction_level"], cut_points, labels)
print('\n####The number of values in each bin are:###\n\n',pd.value_counts(hr_data["satisfaction_level"], sort=False))

一旦我们执行了前面的代码,我们可以从下面的结果中观察到为satisfaction_level属性创建了三个箱,其中low箱中有1941值,medium箱中有4788,而high箱中有8270:

对数和幂变换

对数和幂变换通常有助于非基于树的模型,使高度偏斜的分布不那么偏斜。这种预处理技术有助于满足线性回归模型的假设和推断统计的假设。这种类型转换的一些示例包括对数转换、平方根转换和对数-对数转换。

以下是使用虚拟数据集进行平方根转换的演示:

import numpy as np
values = np.array([-4, 6, 68, 46, 89, -25])
# Square root transformation #
sqrt_trnsf_values = np.sqrt(np.abs(values)) * np.sign(values)
print(sqrt_trnsf_values)

以下是前面平方根转换的输出:

接下来,让我们使用另一个虚拟数据集尝试一个日志转换:

values = np.array([10, 60, 80, 200])
#log transformation #
log_trnsf_values = np.log(1+values)
print(log_trnsf_values)

虚拟数据集上的日志转换产生以下结果:

现在,我们已经对数值数据的不同预处理方法有了一个合理的想法,让我们看看分类数据有什么储备。

分类数据转换

分类数据本质上是非参数的。这意味着它不遵循任何数据分布。然而,为了在参数模型中使用这些变量,它们需要使用各种编码方法进行转换,缺失的值将被替换,并且我们可以使用宁滨技术减少类别的数量。

编码

在许多实际的 ML 活动中,数据集将包含分类变量。它在企业环境中更为合适,在企业环境中,大多数属性都是分类的。这些变量有不同的离散值。例如,组织的规模可以是SmallMediumLarge,或者地理区域可以是AmericasAsia PacificEurope。许多 ML 算法,尤其是基于树的模型,可以直接处理这种类型的数据。

然而,许多算法并不直接接受数据。因此,需要将这些属性编码成数值,以便进一步处理。有各种方法来编码分类数据。以下部分描述了一些广泛使用的方法:

  • 标签编码:顾名思义,标签编码将分类标签转换为数字标签。标签编码更适合于有序分类数据。标签总是在 0 和 n-1 之间,其中 n 是类的数量。
  • 一热编码:这也叫伪编码。在这种方法中,为分类属性/预测器的每个类生成虚拟列。对于每个伪预测值,值的存在由 1 表示,其不存在由 0 表示。
  • 基于频率的编码:在这种方法中,首先计算每个类的频率。然后计算总类别中每个类别的相对频率。该相对频率被指定为属性级别的编码值。
  • 目标均值编码:在这种方法中,每一类分类预测因子都被编码为目标均值的函数。这种方法只能用于有目标特征的监督学习问题。
  • 二进制编码:类首先被转换为数值。然后这些数值被改变成它们相似的二进制字符串。这将在以后分成单独的列。每个二进制数字成为一个独立的列。
  • 哈希编码:这种方法也就是俗称的特征哈希。我们大多数人都知道有一个散列函数,用于将数据映射到一个数字。此方法可能会将不同的类分配给同一个桶,但在输入要素存在数百个类别或类时非常有用。

这些技术中的大多数,以及许多其他技术,也在 Python 中实现,并且在包category_encoders中可用。您可以使用以下命令安装category_encoders库:

pip install category_encoders

接下来,我们将category_encoders库导入为ce(支持在代码中轻松使用的短代码)。我们加载HR损耗数据集,并对salary属性进行一次热编码:

import pandas as pd
import category_encoders as ce
hr_data = pd.read_csv('data/hr.csv', header=0)
hr_data.head()
hr_data = hr_data.dropna()
print(hr_data.shape)
print(list(hr_data.columns))
onehot_encoder = ce.OneHotEncoder(cols=['salary'])
onehot_df = onehot_encoder.fit_transform(hr_data)
onehot_df.head()

我们可以观察到使用category_encoders库将分类属性转换为其对应的单热编码属性是多么容易:

同样,我们使用OrdinalEncodersalary数据进行标签编码:

ordinal_encoder = ce.OrdinalEncoder(cols=['salary'])
ordinal_df = ordinal_encoder.fit_transform(hr_data)
ordinal_df.head(10)
ordinal_df['salary'].value_counts()

上述代码将低、中、高工资等级映射为三个数值:012:

同样,你可以从CategoryEncoders开始尝试其他分类编码方法,使用以下代码片段,观察结果:

binary_encoder = ce.BinaryEncoder(cols=['salary'])
df_binary = binary_encoder.fit_transform(hr_data)
df_binary.head()

poly_encoder = ce.PolynomialEncoder(cols=['salary'])
df_poly = poly_encoder.fit_transform(hr_data)
df_poly.head()

helmert_encoder = ce.HelmertEncoder(cols=['salary'])
helmert_df = helmert_encoder.fit_transform(hr_data)
helmert_df.head()

下一个讨论的主题是处理分类属性缺失值的方法。

分类数据转换缺少值

对于分类变量,评估缺失值的技术也保持不变。然而,一些插补技术是不同的,一些方法类似于所讨论的数值缺失值处理方法。我们将演示 Python 代码中专门针对分类缺失值处理的技术:

  • 移除或删除数据:决定是否移除分类变量缺失的数据点的过程与我们讨论的数值缺失值处理的过程相同。
  • 用模式替换缺失值:由于分类数据是非参数数据,与数字数据不同,它们没有平均值或中值。因此,替换缺失分类值的最简单方法是使用模式。模式是分类变量中出现频率最高的一类。例如,让我们假设我们有三个类别的预测值:红色、绿色和蓝色。红色在数据集中出现频率最高,为 30,其次是绿色,为 20,蓝色为 10。然后,丢失的值可以用红色替换,因为这是预测值出现最多的地方。

我们将再次利用HR损耗数据集来解释分类属性的缺失值处理。让我们首先加载数据集,并观察数据集中的空值数量:

import numpy as np
import pandas as pd
hr_data = pd.read_csv('data/hr.csv', header=0)
print('Nulls in the data set' ,hr_data.isnull().sum())

我们从以下输出中了解到,数据集没有缺失分类属性salessalary的数据。因此,我们将综合考虑这些特征的一些缺失值:

我们使用下面的代码片段来替换sales属性中的sales值为空和salary属性为低的空值:

#As there are no null introduce some nulls by replacing sales in sales column with NaN
hr_data[['sales']] = hr_data[[ 'sales']].replace('sales', np.NaN)
#As there are no null introduce some nulls by replacing low in salary column with NaN
hr_data[['salary']] = hr_data[[ 'salary']].replace('low', np.NaN)
print('New nulls in the data set' ,hr_data.isnull().sum())

执行完代码后,我们可以在salarysales属性中找到一些空值,如下图所示:

现在,我们可以用每个列的模式来替换这些空值。正如我们对数值缺失值插补所做的那样,即使在这里,我们也首先创建hr_data的副本,这样我们就不会替换原始数据集。接下来,我们使用fillna方法用模式值填充行,如以下代码片段所述:

#Replace mode for missing values
hr_data_1 = hr_data.copy()
# fill missing values with mode column values
for column in hr_data_1.columns:
 hr_data_1[column].fillna(hr_data_1[column].mode()[0], inplace=True)
# count the number of NaN values in each column
print(hr_data_1.isnull().sum())
print(hr_data_1.head())

从下面的输出中我们可以看到sales列中缺失的值被technicalsalary列中的medium替换:

  • 使用全局常数填充缺失值:类似于数值缺失值处理,我们可以使用全局常数如AAAANA来区分缺失值与数据集的其余部分:
#Mark global constant for missing values
hr_data_2 = hr_data.copy()
# fill missing values with global constant values
hr_data_2.fillna('AAA', inplace=True)
# count the number of NaN values in each column
print(hr_data_2.isnull().sum())
print(hr_data_2.head())

前面代码的输出产生以下结果,缺失值用AAA代替:

  • 使用指标变量:类似于我们讨论的数值变量,我们可以有一个指标变量来识别缺失分类数据的估算值。
  • 使用数据挖掘算法预测最可能值:就像我们对数值属性所做的一样,我们也可以使用数据挖掘算法,例如决策树、随机森林或 KNN 方法,来预测缺失值的最可能值。同样的fancyimpute库也可以用于这个任务。

我们已经讨论了如何处理结构化数据的数据预处理。在这个数字时代,我们从各种来源捕获了大量非结构化数据。在下一节中,让我们了解预处理文本数据的方法,以便为模型使用做好准备。

文本预处理

有必要通过去除在分析期间给文本增加噪声的不必要的文本来减小文本数据的特征空间的大小。通常会执行一系列步骤来预处理文本数据。然而,并不是每个任务都需要所有步骤,只要有必要,就会使用这些步骤。例如,如果文本数据项中的每个单词都已经是小写的,就不需要修改文本的大小写来使其统一。

文本预处理任务有三个主要元素:

  • 标记化
  • 正常化
  • 代替

我们将使用nltk库演示不同的文本预处理方法。通过在命令提示符下发出以下命令来安装nltk库:

pip install nltk

安装完成后,在 Python 环境中运行以下代码片段:

##Run this cell only once##
import nltk
nltk.download()

你会得到一个 NLTK 下载器弹出窗口。从标识符部分选择全部,等待安装完成。

在本节中,我们将研究一些预处理步骤,这些步骤用于预处理文本以生成规范化的表单:

  1. 标记化—这是一种将文本分成更小块的方法,例如句子或单词。此外,一些文本挖掘任务,如准备 Word2Vec 模型,更喜欢文本是段落或句子风格。所以,我们可以用 NLTK 的sent_tokenizer把文字转换成句子。首先,我们使用以下代码片段从data文件夹中读取文本文件:
import pandas as pd
import category_encoders as ce
text_file = open('data/example_text.txt', 'rt')
text = text_file.read()
text_file.close()
  1. 接下来,为了将文本标记为句子,我们从nltk库中导入sent_tokenize方法,并将文本作为参数传递:
## Sentence tokenization ##
from nltk import sent_tokenize
sentence = sent_tokenize(text)
print(sentence[0])

上述代码产生以下输出:

  1. 类似地,一些建模方法,如单词包模型,需要文本采用单独的单词格式。对于这种情况,我们可以使用 NLTK 的word_tokenizer方法将文本转换为单词,如下面的代码片段所示:
## Word tokenization ##
from nltk import word_tokenize
words = word_tokenize(text)
print(words[:50])
  1. 以下输出显示文本中的50标记单词。我们可以看到一些非字母字符,如标点符号被标记化。这对分析练习没有任何价值,因此,我们需要移除这些变量:

  1. 标点符号等非字母字符在准备单词包模型时不会增加任何价值,因此可以删除."+~等各种标点符号。

有各种方法可以去掉非字母字符。现在我们将说明 Python 中的一种方法:

# Remove punctuations and keep only alphabets
words_cleaned = [word for word in words if word.isalpha()]
print(words_cleaned[:50])
  1. 我们可以从下面的标记中看到,不需要的符号如(从标记列表中被移除。但是,有些常用词如atof对分析没有任何价值,可以使用stop word removal方法删除:

  1. 停止词是写文本文档时常用的短虚词。它们可能是填充词或介词。NLTK 提供了一个标准的英语停止词集合,可以用来过滤我们文本中的停止词。此外,有时,特定领域的终止词可以用来消除非正式词。我们总是可以从文本中创建一个我们认为与我们的分析无关的单词列表。

为了删除停止词,我们首先从ntlk.corpus库中导入stopwords方法。然后我们从stopwords.words方法中调用english停止单词字典,并移除在标记列表中找到的任何常见单词:

# remove the stop words
from nltk.corpus import stopwords
stop_words = set(stopwords.words('english'))
words_1 = [word for word in words_cleaned if not word in stop_words]
print(words_1[:50])
  1. 从下面的输出中我们可以看到,之前出现的单词如atthe都从令牌列表中删除了。然而,一些类似的词,如Datadata作为单独的词出现在标记列表中。我们现在需要将这些词转换成类似的情况:

  1. 大小写折叠是一种将所有单词转换为相似大小写的方法,以使单词不区分大小写。它通常包括将所有大写字母转换成小写字母。

我们可以使用lower函数将所有大写字母转换为小写,如下面的代码片段所示:

# Case folding
words_lower = [words_1.lower() for words_1 in words_1]
print(words_lower[:50])

从下面的输出中我们可以看到Data等单词不再出现在列表中,被转换为全小写字母:

词干是将单词简化为基本或词根形式的过程。比如工作工作则源于工作。这种转换是有用的,因为它将所有相似的词带到一个基础形式,有助于更好的情感分析、文档分类等等。

我们从nltk.stem.porter库中导入PorterStemmer,并实例化PorterStemmer类。接下来,words_lower标记列表被传递到porter.stem类,以将每个单词简化为其词根形式:

#Stemming
from nltk.stem.porter import PorterStemmer
porter = PorterStemmer()
stemmed_words = [porter.stem(word) for word in words_lower]
print(stemmed_words[:50])

前面的代码生成了以下词干标记列表:

并非所有的特征或属性对 ML 模型都很重要。在接下来的几节中,我们将学习一些在使用 ML 流水线时减少特征数量的方法。

特征选择

ML 模型使用一些关键特征来学习数据中的模式。所有其他特征都会给模型增加噪声,这可能会导致模型精度下降,并使模型过度拟合数据。因此,选择合适的功能是至关重要的。此外,使用一组简化的重要特性可以减少模型训练时间。

以下是创建模型前选择正确特征的一些方法:

  • 我们可以识别相关变量并移除任何一个高度相关的值
  • 移除方差较小的特征
  • 测量可用特征集的信息增益,并相应地选择顶部 N 特征

此外,在创建基线模型后,我们可以使用以下一些方法来选择正确的特征:

  • 使用线性回归并基于 p 值选择变量
  • 使用逐步选择进行线性回归,并选择重要变量
  • 使用随机森林,选择顶部 N 个重要变量

在接下来的部分中,我们将看到 scikit 中可用的一些方法-学习减少数据集中可用的要素数量。

排除低方差特征

数据中没有太多差异或可变性的特征不会为学习模式的 ML 模型提供任何信息。例如,数据集中每条记录的值只有 5 的要素是一个常数,并且是一个不重要的要使用的要素。删除此功能至关重要。

我们可以使用 scikit-learn 的featureselection包中的VarianceThreshold方法来移除方差不满足特定标准或阈值的所有特征。sklearn.feature_selection 模块实现了特征选择算法。它目前包括单变量滤波器选择方法和递归特征消除算法。下面是一个例子来说明这种方法:

%matplotlib inline
import pandas as pd
import numpy as np
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
hr_data = pd.read_csv('data/hr.csv', header=0)
hr_data.head()
hr_data = hr_data.dropna()
data_trnsf = pd.get_dummies(hr_data, columns =['salary', 'sales'])
data_trnsf.columns

前面代码的输出如下:

接下来,我们将left指定为目标变量,将其他属性指定为独立属性,如下代码所示:

X = data_trnsf.drop('left', axis=1)
X.columns
Y = data_trnsf.left# feature extraction

现在我们已经准备好数据,我们基于VarianceThreshold方法选择特征。首先,我们从 scikit-learn 的feature_selection模块导入VarianceThreshold方法。然后我们在VarianceThreshold方法中将阈值设置为0.2。这意味着,如果某个属性的数据差异小于 20%,它将被丢弃,并且不会被选为特征。我们执行以下代码片段来观察精简后的功能集:

#Variance Threshold
from sklearn.feature_selection import VarianceThreshold
# Set threshold to 0.2
select_features = VarianceThreshold(threshold = 0.2)
select_features.fit_transform(X)
# Subset features
X_subset = select_features.transform(X)
print('Number of features:', X.shape[1])
print('Reduced number of features:',X_subset.shape[1])

从下面的输出中,我们可以确定20属性中有 5 个属性通过了方差阈值测试,并且表现出变异性,变异性大于 20%方差:

在下一节中,我们将研究单变量特征选择方法,该方法基于某些统计测试来确定重要特征。

单变量特征选择

在这种方法中,对每个特征分别进行统计检验。根据测试结果分数,我们只保留最佳特征。

以下示例说明了卡方统计测试,以从HR磨损数据集中选择最佳特征:

#Chi2 Selector

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

chi2_model = SelectKBest(score_func=chi2, k=4)
X_best_feat = chi2_model.fit_transform(X, Y)
# selected features
print('Number of features:', X.shape[1])
print('Reduced number of features:',X_best_feat.shape[1])

从下面的输出中我们可以看到4最佳特征被选中。我们可以通过改变k值来改变要考虑的最佳特征的数量:

下一节演示递归特征消除方法。

递归特征消除

递归特征消除是基于这样的思想:通过去除特征递归地构建模型,用剩余的特征构建模型,并计算模型的精度。重复此过程,直到数据集中的所有要素都用尽。这是一种贪婪的优化方法,寻找性能最好的特征子集,然后根据它们被淘汰的时间对它们进行排序。

在下面的示例代码中,HR磨损数据集用于说明递归特征消除 ( RFE )的使用。RFE方法的稳定性在很大程度上取决于所用算法的类型。为了演示,我们使用了LogisticRegression方法:

#Recursive Feature Elimination
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression

# create a base classifier used to evaluate a subset of attributes
logistic_model = LogisticRegression()

# create the RFE model and select 4 attributes
rfe = RFE(logistic_model, 4)
rfe = rfe.fit(X, Y)

# Ranking of the attributes
print(sorted(zip(map(lambda x: round(x, 4), rfe.ranking_),X)))

以下输出显示了按等级排序的要素:

随机森林通常用于 ML 流水线中的特征选择。所以,我们了解这项技术是至关重要的。

使用随机森林的特征选择

随机森林使用的基于树的特征选择策略自然根据它们提高节点纯度的程度进行排序。首先,我们需要构建一个随机森林模型。我们已经在第 2 章中讨论了创建随机森林模型的过程,并介绍了 使用 Python 进行机器学习的过程:

# Feature Importance
from sklearn.ensemble import RandomForestClassifier
# fit a RandomForest model to the data
model = RandomForestClassifier()
model.fit(X, Y)
# display the relative importance of each attribute
print(model.feature_importances_)
print(sorted(zip(map(lambda x: round(x, 4), model.feature_importances_),X)))

一旦模型构建成功,模型的feature_importance_ attribute用于可视化按等级排序的导入要素,如下图所示:

在本节中,我们讨论了使用不同的特征选择方法来选择特征子集的不同方法。接下来,我们将看到使用降维方法的特征选择方法。

使用降维的特征选择

降维方法通过从原始特征的组合中产生新的合成特征来降维。它们是强有力的技术,并且保留了数据的可变性。这些技术的一个缺点是很难解释属性,因为它们是通过组合各种属性的元素来准备的。

主成分分析

主成分分析 ( 主成分分析)将高维空间中的数据转换为更少维度的空间。让我们考虑 100 维数据集的可视化。几乎不可能有效地显示这种高维数据分布的形状。主成分分析提供了一种有效的降维方法,通过形成各种主成分来解释降维空间中数据的可变性。

数学上,给定一组变量, X 1 ,X 2 ,....,X p ,这里有 p 的原始变量。在主成分分析中,我们正在寻找一组新的变量, Z 1 ,Z 2 ,....,Z p ,即原始变量的加权平均值(减去其平均值后):

其中每对 Z 的相关性=0

得到的 Z 按其方差排序,其中*Z1T5】方差最大,ZpT9】方差最小。*

Always, the first component extracted in a PCA accounts for a maximum amount of total variance in the observed variables. The second component extracted will account for a maximal amount of variance in the dataset that was not accounted for by the first component and it is also uncorrelated with the first component. If we compute the correlation between the first component and second component the correlation would be zero.

我们使用HR磨损数据来演示主成分分析的使用。首先,我们将numpypandas库加载到环境中,并加载HR数据集:

import numpy as np
import pandas as pd
hr_data = pd.read_csv('data/hr.csv', header=0)
print (hr_data.head())

以下是前面代码的输出,其中显示了数据集中每个属性的前五行:

主成分分析非常适合数字属性,并且在属性标准化时效果很好。所以,我们从sklearn.preprocessing库中导入StandardScaler。我们只包括数据预处理的数字属性。使用StandardScaler方法,HR数据集的数字属性被标准化:

from sklearn.preprocessing import StandardScaler
hr_numeric = hr_data.select_dtypes(include=[np.float])
hr_numeric_scaled = StandardScaler().fit_transform(hr_numeric)

接下来,我们从sklearn.decomposition导入PCA方法,将n_components作为2传递。n_components参数定义了要构建的主要组件的数量。然后,确定由这两个主成分解释的方差:

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
principalComponents = pca.fit_transform(hr_numeric_scaled)
principalDf = pd.DataFrame(data = principalComponents,columns = ['principal component 1', 'principal component 2'])
print(pca.explained_variance_ratio_)

我们可以看到,这两个主成分解释了人力资源数据集数字属性的可变性:

有时,我们使用的原始数据没有足够的信息来创建一个好的模型。在这种情况下,我们需要创建特征。在下一节中,我们将描述几种不同的方法来创建特征。

特征生成

在现有功能的基础上创建新功能是一门艺术,可以通过许多不同的方式来实现。

The objective of feature creation is to provide ML algorithms with such predictors that makes it easy for them to understand the patterns and derive better relationship from the data.

例如在HR减员问题中,员工在组织中的停留时间是一个重要属性。但是,有时我们在数据集中没有停留时间作为特征,但是我们有员工开始日期。在这种情况下,我们可以通过从当前日期减去员工开始日期来创建停留时间特征的数据。

在接下来的部分中,我们将看到从数据中生成新特征的一些不同方法。然而,这不是一个广泛的列表,而是一些可以用来创建新特性的不同方法。一个人需要思考问题陈述,探索数据,并创造性地发现构建功能的新方法:

  • 数值特征生成:从数值数据中生成新特征比其他数据类型稍微容易一些。即使我们不理解各种数字特征的含义,我们也可以做各种运算,比如把两个或两个以上的数字相加,计算相对差,再把数字相乘和相除。在这个任务之后,我们从所有生成的特征中识别出什么是重要的特征,并丢弃其他特征。尽管这是一项资源密集型任务,但当我们不知道派生新特性的直接方法时,它有助于发现新特性。

添加和计算一对数字特征之间差异的过程称为成对特征创建

还有一种称为PolynomialFeatures创建的方法,我们自动执行特征的所有多项式组合。它有助于映射可能暗示某些独特状态的要素之间的复杂关系。

我们可以使用 scikit-learn 的PolynomialFeatures方法生成多项式特征。让我们首先创建虚拟数据,如下面的代码片段所示:

#Import PolynomialFeatures
from sklearn.preprocessing import PolynomialFeatures
#Create matrix and vectors
var1 = [[0.45, 0.72], [0.12, 0.24]]

接下来,通过首先调用带有参数度的PolynomialFeatures来生成多项式特征。该函数将生成度数小于或等于指定度数的要素:

# Generate Polynomial Features 
ploy_features = PolynomialFeatures(degree=2)
var1_ = ploy_features.fit_transform(var1)
print(var1_)

代码执行完成后,它会生成新的特性,如下图所示:

  • 分类特征创建:从分类数据中创建新特征的方法有限。然而,我们可以计算每个分类属性的频率,或者组合不同的变量来构建新的特征。
  • 时态特征创建:如果遇到日期/时间特征,可以衍生出各种新的特征,例如:
    • 一周中的某一天
    • 每月的某一天
    • 季度日
    • 一年中的某一天
    • 一天中的一小时
    • 今天的第二天
    • 一天中的一周
    • 一年中的一周
    • 一年中的月份

从单个数据/时间特征中创建这些特征将有助于 ML 算法更好地学习数据中的时间模式。

摘要

在本章中,我们学习了与机器学习流水线非常相关的各种数据转换和预处理方法。准备属性、清理数据并确保数据没有错误,可以确保 ML 模型正确学习数据。使数据无噪声并生成良好的特征有助于 ML 模型有效地发现数据中的模式。

下一章将集中讨论自动语言算法的技术。我们将讨论各种特定于算法的特征转换,自动化有监督和无监督的学习,等等。

1
https://gitee.com/OpenDocCN/apachecn-ml-zh.git
git@gitee.com:OpenDocCN/apachecn-ml-zh.git
OpenDocCN
apachecn-ml-zh
apachecn-ml-zh
master

搜索帮助