【机器学习】完整的机器学习项目演练:第一部分

把机器学习拼接起来

通过阅读数据科学书籍或参加课程,可以感觉到你有各自的作品,但不太知道如何将它们组合在一起。采取下一步并解决完整的机器学习问题可能令人生畏,但保留和完成第一个项目将使您有信心解决任何数据科学问题。本系列文章将介绍一个包含真实数据集的完整机器学习解决方案,让您了解所有部分是如何组合在一起的。

我们将按照一般的机器学习工作流程逐步进行:

  1. 数据清理和格式化
  2. 探索性数据分析
  3. 特征工程和选择
  4. 比较性能指标上的几种机器学习模型
  5. 在最佳模型上执行超参数调整
  6. 评估测试集上的最佳模型
  7. 解释模型结果
  8. 得出结论并记录工作

在此过程中,我们将看到每个步骤如何流入下一步以及如何在Python中专门实现每个部分。该完整的项目可在GitHub上,与这里的部分的jupyter notebook。第一篇文章将介绍步骤1-3,其余内容将在后续文章中介绍。

(作为一个说明,这个问题最初是作为启动时作业屏幕的“任务”给我的。完成工作后,我得到了工作,但公司的首席技术官辞职了,他们不是'能够引进任何新员工。我想这就是启动现场的情况!)

 

问题定义

我们编写代码之前的第一步是了解我们要解决的问题和可用数据。在这个项目中,我们将使用纽约市公开提供的建筑能源数据。

目标是使用能源数据建立一个模型,该模型可以预测建筑物的能源之星得分并解释结果以找出影响得分的因素。

这些数据包括能源之星得分,这使其成为受监督的回归机器学习任务:

  • 监督:我们可以访问功能和目标,我们的目标是培训可以学习两者之间映射的模型
  • 回归:能源之星得分是一个连续变量

我们希望开发一个既准确 的模型- 它可以预测接近真实值的能源之星分数 - 并且可以解释  - 我们可以理解模型预测。一旦我们了解了目标,我们就可以在我们深入研究数据和构建模型时使用它来指导我们的决策。

 

数据清理

与您认为的大多数数据科学课程相反,并非每个数据集都是完美策划的观察组,没有缺失值或异常(查看您的mtcars和虹膜数据集)。现实世界的数据很混乱,这意味着我们需要在可以开始分析之前将其清理并加工成可接受的格式。数据清理是大多数实际数据科学问题的一个非常迷人但必不可少的部分。

首先,我们可以将数据加载为Pandas DataFrame并查看:

import pandas as pd
import numpy as np
# Read in data into a dataframe 
data = pd.read_csv('data/Energy_and_Water_Data_Disclosure_for_Local_Law_84_2017__Data_for_Calendar_Year_2016_.csv')
# Display top of dataframe
data.head()

 

这是包含60列的完整数据的子集。我们已经看到了几个问题:首先,我们知道我们想要预测,ENERGY STAR Score但我们不知道任何列的含义。虽然这不一定是一个问题 - 我们通常可以在不了解变量的情况下制作精确的模型 - 我们希望专注于可解释性,并且至少理解一些列可能很重要。

当我最初从初创公司获得任务时,我不想问所有列名称的含义,所以我查看了文件的名称,

并决定寻找“地方法84”。这导致我进入这个页面,解释这是纽约市法律要求所有规模的建筑物报告他们的能源使用情况。更多的搜索让我了解了列的所有定义。也许看一个文件名是一个显而易见的起点,但对我来说,这是一个缓慢的提示,所以你不要错过任何重要的东西!

我们不需要研究所有列,但我们至少应该了解能源之星得分,其描述如下:

基于报告年度自我报告的能源使用情况的1到100百分位排名。在能源之星的分数是用于比较建筑物的能源效率的相对度量。

这清除了第一个问题,但第二个问题是缺失值被编码为“不可用”。这是Python中的一个字符串,这意味着即使带有数字的列也将存储为object数据类型,因为Pandas会将包含任何字符串的列转换为所有字符串的列。我们可以使用以下dataframe.info()方法查看列的数据类型:

# 查看列数据类型和非缺失值
data.info()

果然,一些明显包含数字(例如ft²)的列存储为对象。我们不能对字符串进行数值分析,因此必须将它们转换为数字(特定float)数据类型!

这里有一个Python代码,用于替换所有“Not Available”条目而不是number(np.nan),可以将其解释为数字,然后将相关列转换为float数据类型:

# 将所有不可用的numpy替换为numpy而不是数字
data = data.replace({'Not Available': np.nan})

# 遍历列
for col in list(data.columns):
    # 选择应为数字的列
    if ('ft²' in col or 'kBtu' in col or 'Metric Tons CO2e' in col or 'kWh' in 
        col or 'therms' in col or 'gal' in col or 'Score' in col):
        # 将数据类型转换为float
        data[col] = data[col].astype(float)

一旦正确的列是数字,我们就可以开始调查数据了。

 

缺少数据和异常值

除了不正确的数据类型之外,处理实际数据时的另一个常见问题是缺少值。这些可能由于许多原因而产生,并且必须在我们训练机器学习模型之前填写或移除。首先,让我们了解每列中有多少缺失值(请参阅笔记本中的代码)。

(为了创建这个表,我使用了这个Stack Overflow论坛中的一个函数)。

虽然我们总是要小心删除信息,但如果列的缺失值百分比很高,那么它对我们的模型可能没用。删除列的阈值应该取决于问题(这是一个讨论),对于这个项目,我们将删除任何缺失值超过50%的列。

此时,我们可能还想删除异常值。这可能是由于数据输入中的拼写错误,单位错误,或者它们可能是合法的但是极端值。对于这个项目,我们将根据极端异常值的定义删除异常:

  • 低于第一个四分位数--3 *四分位数范围
  • 高于第三个四分位数+3 *四分位数范围

(有关删除列和异常的代码,请参阅笔记本)。在数据清理和异常删除过程结束时,我们留下了超过11,000个建筑物和49个功能。

 

探索性数据分析

既然数据清理的繁琐但必要的步骤已经完成,我们可以继续探索我们的数据!探索性数据分析(EDA)是一个开放式过程,我们计算统计数据并制作数据以查找数据中的趋势,异常,模式或关系。

简而言之,EDA的目标是了解我们的数据可以告诉我们什么。它通常以高级概述开始,然后在我们找到有趣的数据部分时缩小到特定区域。这些发现本身可能很有趣,或者它们可以用来告知我们的建模选择,例如帮助我们决定使用哪些功能。

单变量图

目标是预测能源之星得分(score在我们的数据中重命名),因此合理的起点是检查此变量的分布。直方图是一种简单而有效的方法,可视化单个变量的分布,并且易于使用matplotlib

import matplotlib.pyplot as plt
# Histogram of the Energy Star Score
plt.style.use('fivethirtyeight')
plt.hist(data['score'].dropna(), bins = 100, edgecolor = 'k');
plt.xlabel('Score'); plt.ylabel('Number of Buildings'); 
plt.title('Energy Star Score Distribution');

这看起来很可疑!能源之星得分是百分位数,这意味着我们期望看到均匀分布,每个得分分配给相同数量的建筑物。然而,不成比例的建筑物具有最高,100或最低1的分数(能量之星得分越高越好)。

如果我们回到分数的定义,我们会发现它基于“自我报告的能量使用”,这可能解释了非常高的分数。要求建筑物业主报告他们自己的能源使用情况就像要求学生在测试中报告他们自己的分数一样!因此,这可能不是建筑物能效的最客观衡量标准。

如果我们有无限的时间,我们可能想调查为什么这么多建筑物的分数非常高而且非常低,我们可以通过选择这些建筑物并看到它们的共同点。但是,我们的目标只是预测得分,而不是设计更好的建筑物评分方法!我们可以在报告中记下分数有可疑分布,但我们主要关注的是预测分数。

 

寻找关系

EDA的一个主要部分是搜索特征和目标之间的关系。与目标相关的变量对模型很有用,因为它们可用于预测目标。检查目标上的分类变量(仅接受有限的一组值)的影响的一种方法是使用seaborn库的密度图。

甲密度图可以被看作是一个平滑的直方图,因为它示出了单个变量的分布。我们可以按类别对密度图进行着色,以查看分类变量如何改变分布。以下代码根据建筑物的类型绘制能源之星得分的密度图(仅限于具有超过100个数据点的建筑类型):

# 创建超过100个测量值的建筑物列表
types = data.dropna(subset=['score'])
types = types['Largest Property Use Type'].value_counts()
types = list(types[types.values > 100].index)

# 建筑类别分数分布图
figsize(12, 10)

# 绘制每个建筑物
for b_type in types:
    # 选择建筑类型
    subset = data[data['Largest Property Use Type'] == b_type]
    
    # 密度能源之星成绩的plot
    sns.kdeplot(subset['score'].dropna(),
               label = b_type, shade = False, alpha = 0.8);
    
# label the plot
plt.xlabel('Energy Star Score', size = 20); plt.ylabel('Density', size = 20); 
plt.title('Density Plot of Energy Star Scores by Building Type', size = 28);

我们可以看到建筑类型对能源之星得分有重大影响。办公楼往往得分较高,而酒店得分较低。这告诉我们,我们应该在建模中包含建筑类型,因为它确实会对目标产生影响。作为一个分类变量,我们将不得不对建筑类型进行单热编码。

类似的情节可以用来显示自治市镇的能源之星得分:

 

自治市镇的建筑类型似乎没有那么大的影响。尽管如此,我们可能希望将其包含在我们的模型中,因为自治市镇之间存在细微差别。

为了量化变量之间的关系,我们可以使用Pearson Correlation Coefficient。这是两个变量之间线性关系的强度和方向的度量。得分+1是完全线性正关系,得分-1是完全负线性关系。相关系数的几个值如下所示:

 

虽然相关系数无法捕获非线性关系,但它是一种开始计算变量如何相关的好方法。在Pandas中,我们可以轻松计算数据帧中任何列之间的相关性:

#查找与分数的所有相关性并排序
correlations_data = data.corr()['score'].sort_values()

与目标的最负(左)和正(右)相关:

特征与目标之间存在若干强烈的负相关,而不同类别的EUI最为负(这些指标在计算方式上略有不同)。该EUI -能源使用强度  -是能量通过一座由建筑物的面积有多大划分的使用量。它旨在衡量建筑物的效率,评分越低越好。直觉上,这些相关性是有意义的:随着EUI的增加,能源之星得分趋于下降。

 

双变量图

为了可视化两个连续变量之间的关系,我们使用散点图。我们可以在点的颜色中包含其他信息,例如分类变量。例如,下图显示了建筑类型所着色的能源之星得分与场地EUI:

该图使我们可以看到-0.7的相关系数是什么样的。随着场地EUI减少,能源之星得分增加,这种关系在建筑类型中保持稳定。

我们将要做的最终探索性情节被称为Pairs Plot。这是一个很好的探索工具,因为它可以让我们看到多对变量之间的关系以及单个变量的分布。在这里,我们使用seaborn可视化库和PairGrid函数来创建一个对上图,上面的三角形上有散点图,对角线上的直方图和下三角形上的2D核密度图和相关系数。

# 提取要绘制的列
plot_data = features[['score', 'Site EUI (kBtu/ft²)', 
                      'Weather Normalized Source EUI (kBtu/ft²)', 
                      'log_Total GHG Emissions (Metric Tons CO2e)']]

# 用nan替换inf
plot_data = plot_data.replace({np.inf: np.nan, -np.inf: np.nan})

# 重命名列
plot_data = plot_data.rename(columns = {'Site EUI (kBtu/ft²)': 'Site EUI', 
                                        'Weather Normalized Source EUI (kBtu/ft²)': 'Weather Norm EUI',
                                        'log_Total GHG Emissions (Metric Tons CO2e)': 'log GHG Emissions'})

# 删除na值
plot_data = plot_data.dropna()

# 功能计算两列之间的相关系数
def corr_func(x, y, **kwargs):
    r = np.corrcoef(x, y)[0][1]
    ax = plt.gca()
    ax.annotate("r = {:.2f}".format(r),
                xy=(.2, .8), xycoords=ax.transAxes,
                size = 20)

# 创建pairgrid对象
grid = sns.PairGrid(data = plot_data, size = 3)

# 上是一个散点图
grid.map_upper(plt.scatter, color = 'red', alpha = 0.6)

# 对角线是直方图
grid.map_diag(plt.hist, color = 'red', edgecolor = 'black')

# 下方是相关性和密度图
grid.map_lower(corr_func);
grid.map_lower(sns.kdeplot, cmap = plt.cm.Reds)

# Title for entire plot
plt.suptitle('Pairs Plot of Energy Data', size = 36, y = 1.02);

 

要查看变量之间的交互,我们会查找行与列相交的位置。例如,要查看Weather Norm EUIwith 的相关性score,我们查看Weather Norm EUI行和score列,并查看相关系数-0.67。除了看起来很酷,这些图可以帮助我们决定在建模中包含哪些变量。

 

特征工程与选择

特征工程和选择通常可以为机器学习问题提供最大的时间回报。首先,让我们来定义这两个任务是什么:

  • 特征工程:获取原始数据并提取或创建新特征的过程。这可能意味着转换变量,例如自然日志和平方根,或者单热编码分类变量,以便可以在模型中使用它们。通常,我认为特征工程是从原始数据创建其他功能。
  • 特征选择:选择数据中最相关特征的过程。在特征选择中,我们删除了一些功能,以帮助模型更好地概括新数据并创建更具可解释性的模型。一般来说,我认为特征选择是减去特征,所以我们只留下最重要的特征。

机器学习模型只能从我们提供的数据中学习,因此确保数据包含我们任务的所有相关信息至关重要。如果我们不为模型提供正确的数据,那么我们将其设置为失败,我们不应期望它能够学习!

对于此项目,我们将采取以下功能工程步骤:

  • 单热编码分类变量(自治市镇和财产使用类型)
  • 添加数值变量的自然对数变换

在模型中包含分类变量需要单热编码。机器学习算法无法理解建筑类型的“办公室”,因此如果建筑物是办公室,我们必须将其记录为1,否则记录为0。

添加变换特征可以帮助我们的模型学习数据中的非线性关系。采用平方根,自然对数或各种特征的权力是数据科学中的常见做法,可以基于领域知识或在实践中最有效的方法。这里我们将包括所有数字特征的自然对数。

以下代码选择数字要素,对这些要素进行日志转换,选择两个分类要素,对这些要素进行一次热编码,并将这两个要素连接在一起。这似乎很多工作,但它在熊猫中相对简单!

# 复制原始数据
features = data.copy()

# 选择数字列
numeric_subset = data.select_dtypes('number')

# 创建包含数字列日志的列
for col in numeric_subset.columns:
    # 跳过能源之星得分列
    if col == 'score':
        next
    else:
        numeric_subset['log_' + col] = np.log(numeric_subset[col])
        
# 选择分类列
categorical_subset = data[['Borough', 'Largest Property Use Type']]

# 一个热编码
categorical_subset = pd.get_dummies(categorical_subset)

# 使用concat加入两个数据帧
# 确保使用axis = 1来执行列绑定
features = pd.concat([numeric_subset, categorical_subset], axis = 1)

在此过程之后,我们拥有超过11,000个具有110列(特征)的观测(建筑物)。并非所有这些功能都可能对预测能源之星得分有用,所以现在我们将转向功能选择以删除一些变量。

 

特征选择

我们数据中的110个功能中的许多功能都是多余的,因为它们彼此高度相关。例如,这里是Site EUI与Weather Normalized Site EUI的关系系数为0.997的图。

彼此强相关的特征被称为共线特征,并且移除这些特征对中的一个变量通常可以帮助机器学习模型概括并且更易于解释。(我应该指出,我们正在讨论功能与其他功能的相关性,而不是与目标的相关性,这有助于我们的模型!)

有许多方法可以计算特征之间的共线性,其中最常见的是方差膨胀因子。在这个项目中,我们将使用相关系数来识别和删除共线特征。如果它们之间的相关系数大于0.6,我们将丢弃一对特征中的一个。对于实现,请看一下笔记本(以及此Stack Overflow答案)

虽然这个值似乎是任意的,但我尝试了几个不同的阈值,这个选择产生了最好的模型。机器学习是一个经验领域,通常是试验和发现最佳表现!选择特征后,我们剩下64个总功能和1个目标。

# Remove any columns with all na values
features  = features.dropna(axis=1, how = 'all')
print(features.shape)
(11319, 65)

 

建立基线

我们现在已经完成了数据清理,探索性数据分析和特征工程。在开始建模之前采取的最后一步是建立一个naive基线。这基本上是我们可以比较我们的结果的猜测。如果机器学习模型没有超过这个猜测,那么我们可能必须得出结论,机器学习对于任务是不可接受的,或者我们可能需要尝试不同的方法。

对于回归问题,合理的naive基线是猜测测试集中所有示例的训练集上目标的中值。这为任何模型设定了相对较低的标准。

我们将使用的度量是平均绝对误差(mae),它测量预测的平均绝对误差。回归有很多指标,但我喜欢Andrew Ng建议选择一个指标,然后在评估模型时坚持使用它。平均绝对误差易于计算且可解释。

在计算基线之前,我们需要将数据分成训练和测试集:

  1. 训练集的特点就是我们与答案一起训练时提供给我们的模型。目标是让模型学习特征和目标之间的映射。
  2. 测试集的功能是用来评估训练的模型。不允许该模型查看测试集的答案,并且必须仅使用这些特征进行预测。我们知道测试集的答案,因此我们可以将测试预测与答案进行比较。

我们将使用70%的数据进行培训,30%的数据用于测试:

# Split into 70% training and 30% testing set
X, X_test, y, y_test = train_test_split(features, targets, 
                                        test_size = 0.3, 
                                        random_state = 42)

 

现在我们可以计算出naive 基线表现:

# Function to calculate mean absolute error
def mae(y_true, y_pred):
    return np.mean(abs(y_true - y_pred))

baseline_guess = np.median(y)

print('The baseline guess is a score of %0.2f' % baseline_guess)
print("Baseline Performance on the test set: MAE = %0.4f" % mae(y_test, baseline_guess))

naive 估计在测试集上减少了约25个点。得分范围从1-100,所以这代表25%的错误,超过相当低的标准!

 

结论

在本文中,我们介绍了机器学习问题的前三个步骤。在定义问题后,我们:

  1. 清理并格式化原始数据
  2. 执行探索性数据分析以了解数据集
  3. 开发了一组我们将用于模型的功能

最后,我们还完成了建立基线的关键步骤,我们可以据此判断机器学习算法。

第二篇文章(此处提供)将展示如何使用Scikit-Learn评估机器学习模型,选择最佳模型,以及执行超参数调整以优化模型。处理模型解释和报告结果的第三篇文章就在这里。

一如既往,我欢迎反馈和建设性的批评,可以在Twitter @koehrsen_will上联系。

 

原文:https://towardsdatascience.com/a-complete-machine-learning-walk-through-in-python-part-one-c62152f39420

 

 

 

 

 

你可能感兴趣的:(Machine,Learning,机器学习算法理论与实战)