Categories
程式開發

数据准备技巧及其对机器学习的重要性


“数据只是数千个故事的整合,讲述其中一些故事可以为数据赋予意义。”——Chip & Dan Heath

本文最初发布于Medium,经原作者授权由InfoQ中文站翻译并分享。

数据准备技巧及其对机器学习的重要性 1

数据是什么?

数据是指在某领域中能够描述你所想解决问题的例子,而数据的选择则取决于你想满足的任务目标。下面是一些提供各种开源数据常用的网站,方便各位建立属于自己的机器学习应用,向成功更进一步。

Kaggle – 井井有条的平台,学习者的乐园,在其内核的帮助下,甚至可以不用下载直接在平台上处理数据。

UCI机器学习数据库– 包含大量种类繁多的数据集,为机器学习社区服务。

Data.gov– 提供各国的公开数据下载,从政府预算到学校成绩应有尽有。

CMU图书馆– 由卡内基梅隆大学(Carnegie Mellon University)提供的各个领域高质量数据集。

谷歌数据集搜索– 无论是出版者网站,数字图书馆,或者是数据集作者的网页,你都可以通过它搜索到。

数据收集的下一步是数据转换,让数据更符合不同机器学习算法的的要求。如何准备项目中最独特的建模数据,一直都是机器学习项目中的难点。

数据准备是指如何将原始数据转化为更适合建模形式的过程,而“数据的质量比复杂的算法更重要”。为了让原始数据所包含的信息更多也更明确,我们需要有一套可以解决任何建模问题的数据准备工作。

在本文中,作者将以汽车价格预测数据集为例,带你一步步走进数据准备。

数据准备的内容包括:

  • 数据清洗
  • 特征工程
  • 数据转换
  • 特征提取

数据清洗

这一步应该算是最难的一步,大多数真实生活中的数据都或多或少有因为观察时的误会而导致的错误,这时为了拿到干净的可靠数据,就需要利用专业知识来辨别数据属性中的异常值。数据清理过程不是件容易事,你需要花费大量时间来提升数据质量。不过值得庆幸的是,数据清理方法也有很多,例如:

  1. 检测空数值或缺失行
  2. 搜索重复行并将其移除
  3. 基于统计技术,利用专业知识发现离群点
  4. 确定列的分布情况,并剔除没有方差的列

以下是用于清洗“汽车价格预估”数据集的代码片段:

# 读取数据
df_car = pd.read_csv('CarPrice_Assignment.csv')
df_car.head(10)  # 显示前十行数据
df_car.drop(['car_ID'], axis=1, inplace=True)  # 重复信息,丢弃car_ID列 
# 检查列是否包含空数值
df_car.isnull().sum()
# 丢弃重复行
df_car.drop_duplicates(inplace=True)
# 根据其类型分配列名
cat_variable = []
num_variable = []
for col in df_car.columns:
    if df_car[col].dtype == 'object':
        cat_variable.append(col)
    elif df_car[col].dtype == 'float' or df_car[col].dtype == 'int64':
        num_variable.append(col)
        
# CarName列中类别数量 
df_car['CarName'].value_counts()
# 检测数字变量中的离群点
df_car[num_variable].describe()

特征工程

特征工程指的是从现有数据中创建新的输入变量的过程。一般来说,数据清洗可以算是一个减法的过程,而特征工程则是一个加法的过程。

新功能的工程化是高度针对数据和数据类型的,因为其有助于隔离和突出关键信息,从而帮助算法“聚焦”于重要的东西,特征工程是数据科学家们为提高模型性能而做的最有价值的工作之一。

一些常用的特征工程技巧如下:

  1. 分箱(binning)
  2. 取对数(Log Transformation)
  3. 特征分割
  4. 组合稀疏类

特征工程与数据转换非常相似,但因为不同问题对相关领域知识的要求,特征工程被单独列为数据准备的技巧之一。

以“汽车价格预估”数据集为例,特征工程步骤如下:

# 分离汽车及其公司名称,从而进行特征分割
# 检查Company_name列是否有重复数据 
df_car['Company_name'] = df_car['CarName'].apply(lambda x:x.split(' ')[0])
df_car['Company_name'].value_counts()
df_car.drop(labels='CarName',axis=1,inplace=True)
# 创建字典合并相似公司名称
company_name_map_dict = {
    'maxda' : 'mazda',
    'vw' : 'volkswagen',
    'toyouta' : 'toyota',
    'Nissan' : 'nissan',
    'vokswagen' : 'volkswagen',
    'porcshce' : 'porsche'}
df_car['Company_name'] = df_car['Company_name'].replace(company_name_map_dict)
df_car['Company_name'].value_counts()
# 绘图显示price与Company_name的关系
plt.figure(figsize=(20, 8))
sns.boxplot(x = 'Company_name', y = 'price', data = df_car)
# 绘图显示数字列是否有线性相关
plt.figure(figsize=(30, 25))
for i, col in enumerate(num_variable):
    plt.subplot(5,3,i+1)
    sns.regplot(col, 'price', df_car, fit_reg=False)

数据转换

收集到的数据很少可以直接用于预测。这些数据可能格式不对或者需要先进行转换才能继续使用,而数据转换可以改变数据变量的分布类型。

数据中的属性以数字或类别值的形式存储信息,但机器学习算法的局限性在于,它只能处理数字值,而无法处理任何字符或字符串。因此我们必须先通过创建虚拟变量(dummy variables)或执行独热编码(one-hot encoding)将类别变量编码为整数。

在将类别属性转换为数字表示后,我们需要确定所有特征的数字范围,并将其调整到一个统一的比例。这是因为计算机在0-1的范围内分辨率要远大于其他更广泛的数据类型范围。数据的缩放可以通过归一化(将特征缩放至0-1的范围内)或者标准化(将特征缩放至其对应的高斯分布)实现。

在“汽车”数据集上进行数据转换的步骤如下:

    # 将类别列转换为虚拟变量,方便后续ML模型处理
df_car['fueltype'] = df_car['fueltype'].map({'gas':0, 'diesel':1})
df_car['aspiration'] = df_car['aspiration'].map({'std':0, 'turbo':1})
df_car['doornumber'] = df_car['doornumber'].map({'four':0, 'two':1})
df_car['enginelocation'] = df_car['enginelocation'].map({'front':0, 'rear':1})

# 为拥有超过两种类别的列创建虚拟变量
for col in ['carbody', 'drivewheel', 'enginetype', 'cylindernumber', 'fuelsystem', 'Company_name']:
    df_car = pd.get_dummies(df_car, columns=[col], drop_first=True)
   
# 显示将所有类型数据列转换为0-1后的新数据 
df_car.head()

# 将数据集分为训练与测试两部分 
df_train, df_test = train_test_split(df_car, test_size = 0.15, random_state=44)
# 将所有数字类别列转化为0或1 
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df_train[list(df_train.columns)] = scaler.fit_transform(df_train)
# 取price列为因变量‘y’,其余数据为自变量‘x’ 
y_train = df_train.pop('price')
X_train = df_train

注意在这里我们将数据分为两部分,一是用于算法训练,二是用于评估。在后续的分析中,我们会专注于训练数据。

特征提取

特征提取(Feature Extraction)是指从现有特征列表中选择子集,或通过各种降低维度算法来降低数据维度的过程。数据集维度则是该数据集的输入特征数量。

但是数据的维度越多(即输入的变量越多),数据集就越有可能代表该空间的某个非常稀疏且不具代表性的采样,这被称作是维度的诅咒

主要有两种方式:

* 特征选择:用于对数据集中现有特征的重要性进行排序,并剔除不重要的特征。
* 特征提取:用于将数据投影到一个较低维度的空间,但依然保留原始数据最重要的特征

特征提取/选取技巧的好处有:

  1. 降低欠拟合(underfitting)或过拟合(overfitting)
  2. 减少训练时间
  3. 提高准确率
  4. 改善数据可视化

特征提取示例:

def corr_heatmap(df, v):
    correlations = df[v].corr()
    # Create color map ranging between two colors
    cmap = sns.diverging_palette(220, 10, as_cmap=True)
    fig, ax = plt.subplots(figsize=(20,20))
    sns.heatmap(correlations, cmap=cmap, vmax=1.0, center=0, fmt='.2f',
                square=True, linewidths=.5, annot=True, cbar_kws={"shrink": .75})
    plt.show()
    
# 可视化相关矩阵以排除因变量 
corr_heatmap(df_car, num_variable[:-1])

# 丢弃高度相关的属性
df_car = df_car.drop(['citympg', 'curbweight', 'wheelbase', 'enginesize', 'carwidth'], axis=1)
# Importing RFE and LinearRegression
from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression

# Running RFE with the output number of the variable equal to 10
lm = LinearRegression()
lm.fit(X_train, y_train)               # Model_1
rfe = RFE(lm, 10)                      # running RFE
rfe = rfe.fit(X_train, y_train)
# Displaying the list of column with there rfe rank
list(zip(X_train.columns,rfe.support_,rfe.ranking_))
# taking a subset of column, that rfe has suggested important
col = X_train.columns[rfe.support_]

# Creating X_test dataframe with RFE selected variables
X_rfe = X_train[col]
lm.fit(X_rfe, y_train)              # Model_2

完成对训练数据集的特征选择之后,就可以开始训练模型了,本文例子中使用的是线性回归,因此不需要对算法进行任何预分析。我们使用矩阵对所得模型进行评估,回归问题的性能则是根据“均方误差”和“平均绝对误差”来计算的。

总结

以上都是解决机器学习问题的基本步骤,在搭建模型时,最重要并且最困难的通常都是清理预处理数据。而讽刺的是,应用算法和预测输出通常也就是几行代码而已,这部分可以说是模型构建中最简单的部分了。

如果喜欢这篇文章,欢迎在评论区留下你的见解或疑问,也可以关注原作者的领英Instagram

原文链接:

《Data Preparation Techniques and Its Importance in Machine Learning, by Data Science Whoopees》