FakeOrange
预计阅读时间:54分钟49秒

数据分析2.1 —— 初识机器学习

利用泰坦尼克数据集进行机器学习的理解

0
0

概述


本文将承接上文利用图表等方式进行的探索性分析,在此我们开始新的旅途!机器学习!

阅读文中相关的代码行与注释,相信在跟随本教程进行实操之后,你就真正的入门啦!


由于本文只针对泰坦尼克数据集,在大多数情况下应当运转无误。当然也可能由于环境配置,工具包更新,或者一些小小的意外造成一些运行错误。不过无需担心,此刻, print() 永远是你的好伙伴。


通过print的方式,将之前的信息打印出观察一下,或许你就能解决问题。总之,失败,错误都是常态,解决它!!!!


理解数据科学


这是一个经典问题:预测二元事件的结果。通俗地说,这意味着事件要么发生,要么未发生。例如,你赢了或没有赢,你通过了测试或没有通过,你被录取或没有被录取,明白我的意思吧。一个常见的商业应用是客户流失或客户保留。另一个热门用例是医疗行业的死亡率或生存分析。二元事件创造了一个有趣的动态,因为我们从统计学上知道,随机猜测的准确率应达到50%,而不需要创建任何算法或写任何代码。然而,就像自动更正拼写检查技术一样,有时我们人类可能聪明得过头,实际表现不如掷硬币。由此我们借用泰坦尼克数据集,带领读者了解如何利用数据科学框架来战胜逆境。


数据科学解决问题的框架


定义问题:如果数据科学、大数据、机器学习、预测分析、商业智能或任何其他流行词是解决方案,那么问题是什么?俗话说,不要本末倒置。问题在前,需求在后,解决方案在设计之前,设计在技术之前。我们常常急于使用新的炫酷技术、工具或算法,而在确定我们要解决的实际问题之前就跳了进去。


收集数据:约翰·奈斯比特在1984年(是的,1984年)出版的书《巨变》中写道:“我们在数据中淹没,却又渴求知识。”因此,数据集可能已经以某种格式存在于某个地方。它可以是外部的或内部的、结构化的或非结构化的、静态的或流式的、客观的或主观的,等等。正如所说的,你不必重新发明轮子,只需知道在哪里可以找到它。在下一步中,我们将关注将“脏数据”转化为“干净数据”。


准备数据:此步骤通常称为数据整理,这是将“野生”数据转变为“可管理”数据的必要过程。数据整理包括实施数据存储和处理架构、制定质量和控制的数据治理标准、数据提取(即ETL和网页抓取),以及数据清洗,以识别异常、缺失或离群的数据点。


进行探索性分析:任何曾经处理过数据的人都知道,垃圾进,垃圾出(GIGO,Garbage in Garbage out)。因此,使用描述性和图形统计来寻找数据集中的潜在问题、模式、分类、相关性和比较是非常重要的。此外,数据分类(即定性与定量)也很重要,以便理解和选择正确的假设检验或数据模型。


建模数据:像描述性统计和推断统计一样,数据建模可以总结数据或预测未来结果。你的数据集和预期结果将决定可用的算法。重要的是要记住,算法是工具,而不是神奇的魔杖或银子弹。你仍然必须是那个知道如何为工作选择正确工具的高手。一个比喻是,问别人递给你一个十字螺丝刀,而他们递给你一个一字螺丝刀,甚至更糟的是一个锤子。最多,这显示出对情况完全没有理解;最糟的是,这让完成项目变得不可能。在数据建模中也是如此。错误的模型可能导致最好的情况是表现不佳,最糟的情况是得出错误的结论(作为可操作的情报使用)。


验证和实施数据模型:在你根据数据的一个子集训练模型之后,是时候测试模型了。这有助于确保你没有过拟合模型,或者使其过于具体,以至于无法准确适应来自同一数据集的其他子集。在这一步中,我们确定我们的模型是过拟合、泛化还是欠拟合我们的数据集。


优化和策略:这是“仿生人”步骤,你需要回过头去迭代这个过程,使其比之前更好、更强、更快。作为一名数据科学家,你的策略应该是外包开发运营和应用程序基础设施,以便你有更多时间专注于建议和设计。一旦你能够打包你的想法,这就成为你的“货币兑换”率。


步骤 1:定义问题


对于这个项目,问题陈述简洁明了的呈现给我们:开发一个算法来预测泰坦尼克号乘客的生存结果。

项目概述:泰坦尼克号的沉没是历史上最臭名昭著的船难之一。1912年4月15日,泰坦尼克号在其处女航中撞上冰山沉没,造成1502名乘客和船员遇难,2224人中仅有722人生还。这场轰动一时的悲剧震惊了国际社会,并促使制定了更好的船舶安全法规。

导致这场船难造成如此多生命损失的原因之一是船上没有足够的救生艇供乘客和船员使用。尽管在生还过程中有一些运气成分,但某些群体的人比其他群体更有可能生存下来,比如女性、儿童和上层社会人士。

在这个挑战中,我们要求你完成分析哪些类型的人更可能幸存。特别是,我们要求你运用机器学习工具来预测哪些乘客在这场悲剧中幸存。

练习技能:

  • 二元分类
  • Python 基础知识


步骤 2:收集数据


数据集同样像放在金盘子上一样呈现给我们,泰坦尼克号:灾难中的机器学习中的测试和训练数据(train.csv, test.csv)。


步骤 3:准备数据

由于步骤 2 的内容已完美提供给我们,步骤 3 也同样如此。因此,数据整理中的常规流程,如数据架构、治理和提取,都不在范围内。因此,仅数据清洗在范围之内


3.1 导入库


以下代码是用 Python 3.x 编写的。库提供了执行必要任务的预编写功能。这个理念是:为什么要写十行代码,而你可以写一行代码。

# 此 Python 3 环境中已安装许多有用的分析库
# 由 kaggle/python docker 镜像定义: https://github.com/kaggle/docker-python

# 加载包
import sys  # 访问系统参数 https://docs.python.org/3/library/sys.html
print("Python version: {}".format(sys.version))

import pandas as pd  # 以 SQL 类特征为模型的数据处理和分析功能集合
print("pandas version: {}".format(pd.__version__))

import matplotlib  # 用于科学和出版级的可视化的函数集合
print("matplotlib version: {}".format(matplotlib.__version__))

import numpy as np  # 科学计算的基础包
print("NumPy version: {}".format(np.__version__))

import scipy as sp  # 科学计算和高级数学的函数集合
print("SciPy version: {}".format(sp.__version__)) 

import IPython
from IPython import display  # 在 Jupyter notebook 中美观地打印数据框
print("IPython version: {}".format(IPython.__version__)) 

import sklearn  # 机器学习算法的集合
print("scikit-learn version: {}".format(sklearn.__version__))

# 杂项库
import random
import time

# 忽略警告
import warnings
warnings.filterwarnings('ignore')
print('-'*25)

#以下为输出内容
Python version: 3.6.3 |Anaconda custom (64-bit)| (default, Nov 20 2017, 20:41:42) 
[GCC 7.2.0]
pandas version: 0.20.3
matplotlib version: 2.1.1
NumPy version: 1.13.0
SciPy version: 1.0.0
IPython version: 5.3.0
scikit-learn version: 0.19.1
-------------------------


3.2 加载数据建模库


我们将使用流行的 scikit-learn 库来开发我们的机器学习算法。在 sklearn 中,算法被称为估算器,并在其自己的类中实现。为了进行数据可视化,我们将使用 matplotlib 和 seaborn 库。以下是常见的类加载方式。

# 常见模型算法
from sklearn import svm, tree, linear_model, neighbors, naive_bayes, ensemble, discriminant_analysis, gaussian_process
from xgboost import XGBClassifier

# 常见模型助手
from sklearn.preprocessing import OneHotEncoder, LabelEncoder
from sklearn import feature_selection
from sklearn import model_selection
from sklearn import metrics

# 可视化
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.pylab as pylab
import seaborn as sns
from pandas.plotting import scatter_matrix  # 已更新为 pandas.plotting

# 配置可视化默认值
# %matplotlib inline = 在 Jupyter Notebook 浏览器中显示图表
%matplotlib inline
mpl.style.use('ggplot')
sns.set_style('white')
pylab.rcParams['figure.figsize'] = 12, 8



3.3 了解数据


这是一个了解数据的步骤。通过名字与数据见面,了解一下它。它看起来如何(数据类型和值),它的工作原理是什么(独立/特征变量),它的目标是什么(依赖/目标变量)。想象一下这就像第一次约会,在深入之前先了解一下。

首先,我们导入数据。接下来,我们使用 info()sample() 函数,快速了解变量的数据类型(即定性与定量)。可以点击这里查看源数据字典。


Survived 变量是我们的结果或依赖变量。它是一个二元名义数据类型,1 表示生存,0 表示未生存。所有其他变量都是潜在的预测或独立变量。重要的是要注意,更多的预测变量并不会使模型变得更好,而是正确的变量。


PassengerIDTicket 变量被认为是随机唯一标识符,对结果变量没有影响。因此,它们将被排除在分析之外。


Pclass 变量是票类的有序数据类型,作为社会经济地位(SES)的代理,表示 1 = 上层社会,2 = 中层社会,3 = 下层社会。


Name 变量是名义数据类型。可以在特征工程中用于从称谓推导性别,从姓氏推导家庭规模,以及从称谓如医生或小孩推导社会经济地位。由于这些变量已存在,我们将利用它来看看称谓(如小孩)是否有区别。


SexEmbarked 变量是名义数据类型。它们将被转换为虚拟变量以进行数学计算。


AgeFare 变量是连续的定量数据类型。


SibSp 代表船上亲属兄弟姐妹/配偶的数量,Parch 代表船上亲属父母/孩子的数量。两者都是离散的定量数据类型。这可以用于特征工程,以创建家庭规模并作为单独变量。


Cabin 变量是名义数据类型,可以用于特征工程,推导事件发生时船上的大致位置以及来自甲板级别的社会经济地位。然而,由于存在许多空值,因此它并未增加价值,因此将被排除在分析之外。


# 从文件中导入数据: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html
data_raw = pd.read_csv('xxxx/train.csv')#你的文件地址

# 数据集应分为 3 个部分:训练集、测试集和(最终)验证集
# 提供的测试文件是竞赛提交的验证文件
# 我们将在未来的部分中将训练集拆分为训练和测试数据
data_val = pd.read_csv('xxxx/test.csv')#你的文件地址

# 为了处理我们的数据,我们将创建一个副本
# 请记住,Python 的赋值或等号是按引用传递而不是按值传递,因此我们使用 copy 函数: https://stackoverflow.com/questions/46327494/python-pandas-dataframe-copydeep-false-vs-copydeep-true-vs
data1 = data_raw.copy(deep=True)

# 然而,按引用传递是方便的,因为我们可以同时清理两个数据集
data_cleaner = [data1, data_val]

# 预览数据
print(data_raw.info())  # https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.info.html
# data_raw.head()  # https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.head.html
# data_raw.tail()  # https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.tail.html
data_raw.sample(10)  # https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.sample.html


输出:


PassengerId
SurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
85585613Aks, Mrs. Sam (Leah Rosen)female18.0013920919.3500NaNS
70170211Silverthorne, Mr. Spencer Victormale35.000PC 1747526.2875E24S
18018103Sage, Miss. Constance GladysfemaleNaN82CA. 234369.5500NaNS
40941003Lefebre, Miss. IdafemaleNaN31413325.4667NaNS
77677703Tobin, Mr. RogermaleNaN003831217.7500F38Q
40440503Oreskovic, Miss. Marijafemale20.0003150968.6625NaNS
495003Arnold-Franchi, Mrs. Josef (Josefine Franchi)female18.01034923717.8000NaNS
83683703Pasic, Mr. Jakobmale21.0003150978.6625NaNS
61962002Gavey, Mr. Lawrencemale26.0003102810.5000NaNS
89089103Dooley, Mr. Patrickmale32.0003703767.7500NaNQ

3.4 数据清洗的四个 C:纠正、补充、创建和转换


在这个阶段,我们将通过以下四个步骤来清理数据:1)纠正异常值和离群值,2)补充缺失信息,3)为分析创建新特征,以及 4)将字段转换为计算和展示的正确格式。


纠正 (Correcting):检查数据后,似乎没有任何异常或不可接受的数据输入。此外,我们可能在年龄和票价中发现潜在的离群值。然而,由于这些值是合理的,我们将在完成探索性分析后决定是否应将其包括在数据集中。值得注意的是,如果这些值是不合理的,例如年龄 = 800 而不是 80,那么现在修正它可能是一个安全的决定。然而,我们在修改数据的原始值时要谨慎,因为这可能对创建准确的模型是必要的。


补充 (Completing):在年龄、舱位和登船字段中存在空值或缺失数据。缺失值可能会造成问题,因为某些算法不知道如何处理空值并会失败,而其他算法,如决策树,则可以处理空值。因此,在开始建模之前修复这些问题是重要的,因为我们将比较和对比几个模型。常见的两种方法是删除记录或使用合理的输入填充缺失值。除非确实表示不完整的记录,否则不推荐删除记录,尤其是大比例的记录。相反,最好是对缺失值进行插补。对于定性数据,一种基本的方法是使用众数进行插补。对于定量数据,基本的方法是使用均值、中位数或均值 + 随机标准差进行插补。一种中级方法是基于特定标准使用基本方法,例如按类的平均年龄或按票价和 SES 的登船港。还有更复杂的方法,但在部署之前,应与基准模型进行比较,以确定复杂性是否真正增加了价值。对于这个数据集,年龄将使用中位数进行插补,舱位属性将被删除,而登船港将使用众数进行插补。后续模型迭代可能会修改这一决策,以确定它是否改善了模型的准确性。


创建 (Creating):特征工程是利用现有特征创建新特征,以确定它们是否提供了新的信号来预测我们的结果。对于这个数据集,我们将创建一个称谓特征,以确定它是否在生存中起了作用。


转换 (Converting):最后,但并非不重要,我们将处理格式问题。没有日期或货币格式,但有数据类型格式。我们的分类数据作为对象导入,这使得进行数学计算变得困难。对于这个数据集,我们将对象数据类型转换为分类虚拟变量。



print('训练数据中具有空值的列:\n', data1.isnull().sum())
print("-" * 10)

print('测试/验证数据中具有空值的列:\n', data_val.isnull().sum())
print("-" * 10)

data_raw.describe(include='all')


输出:


PassengerId
SurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
count891.000000891.000000891.000000891891714.000000891.000000891.000000891891.000000204889
uniqueNaNNaNNaN8912NaNNaNNaN681NaN1473
topNaNNaNNaNLindahl, Miss. Agda Thorilda ViktoriamaleNaNNaNNaN1601NaNC23 C25 C27S
freqNaNNaNNaN1577NaNNaNNaN7NaN4644
mean446.0000000.3838382.308642NaNNaN29.6991180.5230080.381594NaN32.204208NaNNaN
std257.3538420.4865920.836071NaNNaN14.5264971.1027430.806057NaN49.693429NaNNaN
min1.0000000.0000001.000000NaNNaN0.4200000.0000000.000000NaN0.000000NaNNaN
25%223.5000000.0000002.000000NaNNaN20.1250000.0000000.000000NaN7.910400NaNNaN
50%446.0000000.0000003.000000NaNNaN28.0000000.0000000.000000NaN14.454200NaNNaN
75%668.5000001.0000003.000000NaNNaN38.0000001.0000000.000000NaN31.000000NaNNaN
max891.0000001.0000003.000000NaNNaN80.0000008.0000006.000000NaN512.329200NaNNaN


3.5 清理数据


现在我们知道需要清理什么,让我们执行代码。


工具包文档


补充:在训练和测试/验证数据集中填补或删除缺失值


for dataset in data_cleaner:    
    # 用中位数填补缺失的年龄
    dataset['Age'].fillna(dataset['Age'].median(), inplace=True)

    # 用众数填补缺失的登船港
    dataset['Embarked'].fillna(dataset['Embarked'].mode()[0], inplace=True)

    # 用中位数填补缺失的票价
    dataset['Fare'].fillna(dataset['Fare'].median(), inplace=True)

# 删除训练数据集中舱位和其他先前声明要排除的特征/列
drop_column = ['PassengerId', 'Cabin', 'Ticket']
data1.drop(drop_column, axis=1, inplace=True)

# 输出训练和验证数据集中仍具有空值的列
print(data1.isnull().sum())
print("-" * 10)
print(data_val.isnull().sum())

此代码段完成了数据清理步骤,通过填补缺失值并删除不必要的列来优化数据集,使其准备好进行后续的分析和建模。


创建:为训练和测试/验证数据集进行特征工程


for dataset in data_cleaner:    
    # 离散变量
    dataset['FamilySize'] = dataset['SibSp'] + dataset['Parch'] + 1

    dataset['IsAlone'] = 1  # 初始化为是/1,表示独自一人
    dataset['IsAlone'].loc[dataset['FamilySize'] > 1] = 0  # 如果家庭成员数量大于1,更新为否/0

    # 从名字中快速提取称谓: http://www.pythonforbeginners.com/dictionary/python-split
    dataset['Title'] = dataset['Name'].str.split(", ", expand=True)[1].str.split(".", expand=True)[0]

    # 连续变量的分箱;使用 qcut 或 cut: https://stackoverflow.com/questions/30211923/what-is-the-difference-between-pandas-qcut-and-pandas-cut
    # 使用 qcut 为票价创建分箱: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.qcut.html
    dataset['FareBin'] = pd.qcut(dataset['Fare'], 4)

    # 使用 cut 为年龄创建分箱: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.cut.html
    dataset['AgeBin'] = pd.cut(dataset['Age'].astype(int), 5)

# 清理稀有的称谓名称
# print(data1['Title'].value_counts())
stat_min = 10  # 虽然小的阈值是任意的,但我们将使用统计学中常见的最小值:http://nicholasjjackson.com/2012/03/08/sample-size-is-10-a-magic-number/
title_names = (data1['Title'].value_counts() < stat_min)  # 创建一个布尔序列,以标题名称为索引

# 应用 lambda 函数是快速且简便的代码,用于用更少的代码行查找和替换: https://community.modeanalytics.com/python/tutorial/pandas-groupby-and-python-lambda-functions/
data1['Title'] = data1['Title'].apply(lambda x: 'Misc' if title_names.loc[x] == True else x)

print(data1['Title'].value_counts())
print("-" * 10)

# 预览数据
data1.info()
data_val.info()
data1.sample(10)


在这一部分,我们进行了特征工程,通过创建家庭大小、孤独状态、称谓、票价和年龄的分箱,来丰富数据集。这些新特征可以帮助提升模型的预测能力。接下来,我们还清理了稀有的称谓,确保数据的一致性和可用性。最后,通过调用 info()sample() 函数对数据进行预览,以检查数据的结构和部分内容。

输出:



Survived
PclassNameSexAgeSibSpParchFareEmbarkedFamilySizeIsAloneTitleFareBinAgeBin
65112Doling, Miss. Elsiefemale18.00123.0000S20Miss(14.454, 31.0](16.0, 32.0]
56103Sivic, Mr. Huseinmale40.0007.8958S11Mr(-0.001, 7.91](32.0, 48.0]
73711Lesurer, Mr. Gustave Jmale35.000512.3292C11Mr(31.0, 512.329](32.0, 48.0]
36813Jermyn, Miss. Anniefemale28.0007.7500Q11Miss(-0.001, 7.91](16.0, 32.0]
83703Sirota, Mr. Mauricemale28.0008.0500S11Mr(7.91, 14.454](16.0, 32.0]
29911Baxter, Mrs. James (Helene DeLaudeniere Chaput)female50.001247.5208C20Mrs(31.0, 512.329](48.0, 64.0]
27301Natsch, Mr. Charles Hmale37.00129.7000C20Mr(14.454, 31.0](32.0, 48.0]
11702Turpin, Mr. William John Robertmale29.01021.0000S20Mr(14.454, 31.0](16.0, 32.0]
81003Alexander, Mr. Williammale26.0007.8875S11Mr(-0.001, 7.91](16.0, 32.0]
23402Leyson, Mr. Robert William Normanmale24.00010.5000S11Mr(7.91, 14.454](16.0, 32.0]


3.6 转换格式


在这一部分,我们将对分类数据进行编码,以便进行数学分析。我们将使用 LabelEncoder 来对分类变量进行编码,并定义我们的自变量 X 和因变量 y 以便进行数据建模。


工具包文档:



# 导入LabelEncoder
from sklearn.preprocessing import LabelEncoder

# 创建 LabelEncoder 实例
label = LabelEncoder()

# 对训练和测试/验证数据集进行编码
for dataset in data_cleaner:    
    dataset['Sex_Code'] = label.fit_transform(dataset['Sex'])
    dataset['Embarked_Code'] = label.fit_transform(dataset['Embarked'])
    dataset['Title_Code'] = label.fit_transform(dataset['Title'])
    dataset['AgeBin_Code'] = label.fit_transform(dataset['AgeBin'])
    dataset['FareBin_Code'] = label.fit_transform(dataset['FareBin'])

# 定义因变量,即目标变量
Target = ['Survived']

# 定义自变量,即原始特征
data1_x = ['Sex', 'Pclass', 'Embarked', 'Title', 'SibSp', 'Parch', 'Age', 'Fare', 'FamilySize', 'IsAlone']  # 用于图表的美观名称
data1_x_calc = ['Sex_Code', 'Pclass', 'Embarked_Code', 'Title_Code', 'SibSp', 'Parch', 'Age', 'Fare']  # 用于算法计算的编码
data1_xy = Target + data1_x
print('原始 X Y: ', data1_xy, '\n')

# 定义去掉连续变量的分箱特征自变量
data1_x_bin = ['Sex_Code', 'Pclass', 'Embarked_Code', 'Title_Code', 'FamilySize', 'AgeBin_Code', 'FareBin_Code']
data1_xy_bin = Target + data1_x_bin
print('分箱 X Y: ', data1_xy_bin, '\n')

# 定义虚拟特征的自变量
data1_dummy = pd.get_dummies(data1[data1_x])
data1_x_dummy = data1_dummy.columns.tolist()
data1_xy_dummy = Target + data1_x_dummy
print('虚拟 X Y: ', data1_xy_dummy, '\n')

# 显示虚拟特征数据的前几行
data1_dummy.head()


输出:

PclassSibSpParchAgeFareFamilySizeIsAloneSex_femaleSex_maleEmbarked_CEmbarked_QEmbarked_STitle_MasterTitle_MiscTitle_MissTitle_MrTitle_Mrs
031022.07.2500200100100010
111038.071.2833201010000001
230026.07.9250111000100100
311035.053.1000201000100001
430035.08.0500110100100010


代码解释


  1. 导入 LabelEncoder:用于将分类变量转换为数值编码。
  2. 对每个数据集进行编码:我们通过循环对 SexEmbarkedTitleAgeBinFareBin 列进行编码,生成新的编码列。
  3. 定义因变量:我们定义了 Target 变量,这里就是 Survived 列。
  4. 定义自变量:我们分别为原始特征、分箱特征和虚拟特征创建了不同的自变量列表。
  5. 生成虚拟变量:使用 pd.get_dummies 函数将原始特征转换为虚拟变量。

通过这些步骤,我们将分类数据转换为适合进行机器学习建模的格式。接下来可以进行模型训练和评估。


3.7 再次检查清理后的数据


在这一部分,我们将对清理后的数据进行再次检查,确保所有缺失值已被处理,并查看数据的整体结构。

pythonCopy code# 检查训练数据中的缺失值
print('训练数据中缺失值的列: \n', data1.isnull().sum())
print("-" * 10)

# 查看训练数据的信息
print(data1.info())
print("-" * 10)

# 检查测试/验证数据中的缺失值
print('测试/验证数据中缺失值的列: \n', data_val.isnull().sum())
print("-" * 10)

# 查看测试/验证数据的信息
print(data_val.info())
print("-" * 10)

# 描述原始数据的统计信息
data_raw.describe(include='all')

代码解释


  1. 检查缺失值:使用 isnull().sum() 来检查训练数据和测试数据中每一列的缺失值数量,确保之前处理的缺失值已被清理干净。
  2. 查看数据结构:使用 info() 方法查看数据集的整体结构,包括数据类型和非空值的数量,以确保数据已按预期格式清理。
  3. 描述统计信息:使用 describe(include='all') 方法对原始数据进行描述,提供数值型和类别型数据的统计信息,帮助我们了解数据的分布情况。

执行这些代码后,我们可以确认数据是否已经清理完整,并为后续的建模和分析做准备。


3.8 划分训练和测试数据


在这一部分,我们将使用 sklearntrain_test_split 函数将训练数据划分为两个数据集,比例为 75/25。这样做可以避免模型过拟合,即模型对特定子集过于敏感,无法准确概括其他子集。确保模型没有见过我们用于测试的子集是很重要的,以防模型通过记忆答案而“作弊”。

# 使用 sklearn 的 train_test_split 函数进行数据划分
train1_x, test1_x, train1_y, test1_y = model_selection.train_test_split(data1[data1_x_calc], data1[Target], random_state=0)
train1_x_bin, test1_x_bin, train1_y_bin, test1_y_bin = model_selection.train_test_split(data1[data1_x_bin], data1[Target], random_state=0)
train1_x_dummy, test1_x_dummy, train1_y_dummy, test1_y_dummy = model_selection.train_test_split(data1_dummy[data1_x_dummy], data1[Target], random_state=0)

# 输出数据形状
print("Data1 Shape: {}".format(data1.shape))
print("Train1 Shape: {}".format(train1_x.shape))
print("Test1 Shape: {}".format(test1_x.shape))

# 预览训练数据的示例
train1_x_bin.head()


输出结果示例


Data1 Shape: (891, 19) #891行,19列
Train1 Shape: (668, 8)
Test1 Shape: (223, 8)

步骤4:进行统计探索性分析


现在我们的数据已经清理干净,我们将使用描述性和图形统计方法来探索数据,描述和总结变量。在此阶段,您将分类特征,并确定它们与目标变量及彼此之间的相关性。


离散变量的生存相关性分析


使用 groupby 方法(类似于透视表)来分析生存率与离散变量的相关性:

# 通过生存率分析离散变量
for x in data1_x:
    if data1[x].dtype != 'float64':
        print('根据', x, '的生存相关性:')
        print(data1[[x, Target[0]]].groupby(x, as_index=False).mean())
        print('-' * 10, '\n')

# 使用交叉表分析标题与生存的关系
print(pd.crosstab(data1['Title'], data1[Target[0]]))


代码解释


  1. 划分数据集:使用 train_test_split 函数将训练数据集划分为训练集和测试集。
  2. 输出数据形状:打印数据集的形状信息,以便检查划分是否正确。
  3. 离散变量相关性分析:循环遍历每个离散变量,计算与生存变量的平均值,了解不同特征对生存的影响。
  4. 交叉表:使用 pd.crosstab 函数来分析“标题”与“生存”之间的关系,查看不同称谓下生存率的分布。

通过这些步骤,我们可以更好地了解数据特征与生存之间的关系,为后续建模提供支持。


重要提示:不同的绘图方式仅供学习目的


以下代码展示了如何使用 matplotlibseaborn 进行可视化分析。这些图表将帮助我们理解不同特征与生存率之间的关系。


使用 Matplotlib 绘制定量数据分布


# 设置绘图的整体大小
plt.figure(figsize=[16, 12])

# 绘制箱线图
plt.subplot(231)
plt.boxplot(x=data1['Fare'], showmeans=True, meanline=True)
plt.title('Fare Boxplot')
plt.ylabel('Fare ($)')

plt.subplot(232)
plt.boxplot(data1['Age'], showmeans=True, meanline=True)
plt.title('Age Boxplot')
plt.ylabel('Age (Years)')

plt.subplot(233)
plt.boxplot(data1['FamilySize'], showmeans=True, meanline=True)
plt.title('Family Size Boxplot')
plt.ylabel('Family Size (#)')

# 绘制生存与费用的直方图
plt.subplot(234)
plt.hist(x=[data1[data1['Survived'] == 1]['Fare'], data1[data1['Survived'] == 0]['Fare']], 
         stacked=True, color=['g', 'r'], label=['Survived', 'Dead'])
plt.title('Fare Histogram by Survival')
plt.xlabel('Fare ($)')
plt.ylabel('# of Passengers')
plt.legend()

# 绘制生存与年龄的直方图
plt.subplot(235)
plt.hist(x=[data1[data1['Survived'] == 1]['Age'], data1[data1['Survived'] == 0]['Age']], 
         stacked=True, color=['g', 'r'], label=['Survived', 'Dead'])
plt.title('Age Histogram by Survival')
plt.xlabel('Age (Years)')
plt.ylabel('# of Passengers')
plt.legend()

# 绘制生存与家庭大小的直方图
plt.subplot(236)
plt.hist(x=[data1[data1['Survived'] == 1]['FamilySize'], data1[data1['Survived'] == 0]['FamilySize']], 
         stacked=True, color=['g', 'r'], label=['Survived', 'Dead'])
plt.title('Family Size Histogram by Survival')
plt.xlabel('Family Size (#)')
plt.ylabel('# of Passengers')
plt.legend()

使用 Seaborn 绘制生存与特征的关系


# 绘制各特征与生存的关系
fig, saxis = plt.subplots(2, 3, figsize=(16, 12))

sns.barplot(x='Embarked', y='Survived', data=data1, ax=saxis[0, 0])
sns.barplot(x='Pclass', y='Survived', order=[1, 2, 3], data=data1, ax=saxis[0, 1])
sns.barplot(x='IsAlone', y='Survived', order=[1, 0], data=data1, ax=saxis[0, 2])

sns.pointplot(x='FareBin', y='Survived', data=data1, ax=saxis[1, 0])
sns.pointplot(x='AgeBin', y='Survived', data=data1, ax=saxis[1, 1])
sns.pointplot(x='FamilySize', y='Survived', data=data1, ax=saxis[1, 2])

定性数据的分布比较:Pclass 和 Sex


# Pclass 与其他特征的比较
fig, (axis1, axis2, axis3) = plt.subplots(1, 3, figsize=(14, 12))

sns.boxplot(x='Pclass', y='Fare', hue='Survived', data=data1, ax=axis1)
axis1.set_title('Pclass vs Fare Survival Comparison')

sns.violinplot(x='Pclass', y='Age', hue='Survived', data=data1, split=True, ax=axis2)
axis2.set_title('Pclass vs Age Survival Comparison')

sns.boxplot(x='Pclass', y='FamilySize', hue='Survived', data=data1, ax=axis3)
axis3.set_title('Pclass vs Family Size Survival Comparison')

data/78df2c1f-e442-415d-a382-fa7925af0c4b/ee4720eb-aaa2-4896-9e44-fea9d92dbb83image.png

# Sex 与其他特征的比较
fig, qaxis = plt.subplots(1, 3, figsize=(14, 12))

sns.barplot(x='Sex', y='Survived', hue='Embarked', data=data1, ax=qaxis[0])
qaxis[0].set_title('Sex vs Embarked Survival Comparison')

sns.barplot(x='Sex', y='Survived', hue='Pclass', data=data1, ax=qaxis[1])
qaxis[1].set_title('Sex vs Pclass Survival Comparison')

sns.barplot(x='Sex', y='Survived', hue='IsAlone', data=data1, ax=qaxis[2])
qaxis[2].set_title('Sex vs IsAlone Survival Comparison')


更多侧面比较


fig, (maxis1, maxis2) = plt.subplots(1, 2, figsize=(14, 12))

# 家庭大小与性别和生存的比较
sns.pointplot(x="FamilySize", y="Survived", hue="Sex", data=data1,
              palette={"male": "blue", "female": "pink"},
              markers=["*", "o"], linestyles=["-", "--"], ax=maxis1)

# 舱位与性别和生存的比较
sns.pointplot(x="Pclass", y="Survived", hue="Sex", data=data1,
              palette={"male": "blue", "female": "pink"},
              markers=["*", "o"], linestyles=["-", "--"], ax=maxis2)

使用 FacetGrid 进行多维比较


# 航站港与舱位、性别和生存的比较
e = sns.FacetGrid(data1, col='Embarked')
e.map(sns.pointplot, 'Pclass', 'Survived', 'Sex', ci=95.0, palette='deep')
e.add_legend()

# 生存与年龄的密度图
a = sns.FacetGrid(data1, hue='Survived', aspect=4)
a.map(sns.kdeplot, 'Age', shade=True)
a.set(xlim=(0, data1['Age'].max()))
a.add_legend()

# 生存与年龄的直方图
h = sns.FacetGrid(data1, row='Sex', col='Pclass', hue='Survived')
h.map(plt.hist, 'Age', alpha=.75)
h.add_legend()

data/78df2c1f-e442-415d-a382-fa7925af0c4b/aeddf11b-c42b-4a7b-b4bf-dbe6e117ee36image.png

data/78df2c1f-e442-415d-a382-fa7925af0c4b/5ebe2808-2b40-4c8b-9d42-525c26cdbf8bimage.png

数据集的配对图


# 配对图展示整个数据集
pp = sns.pairplot(data1, hue='Survived', palette='deep', diag_kind='kde', diag_kws=dict(shade=True), plot_kws=dict(s=10))
pp.set(xticklabels=[])

特征的相关性热图


# 相关性热图函数
def correlation_heatmap(df):
    _, ax = plt.subplots(figsize=(14, 12))
    colormap = sns.diverging_palette(220, 10, as_cmap=True)
    
    _ = sns.heatmap(
        df.corr(),
        cmap=colormap,
        square=True,
        cbar_kws={'shrink': .9},
        ax=ax,
        annot=True,
        linewidths=0.1, vmax=1.0, linecolor='white',
        annot_kws={'fontsize': 12}
    )
    
    plt.title('Pearson Correlation of Features', y=1.05, size=15)

# 调用函数绘制相关性热图
correlation_heatmap(data1)

data/78df2c1f-e442-415d-a382-fa7925af0c4b/f094814d-300d-4d54-bccd-3b1073a77da4image.png


步骤5:建模数据


数据科学是一个跨学科领域,涵盖数学(如统计学、线性代数等)、计算机科学(如编程语言、计算机系统等)和商业管理(如沟通、专业知识等)。大多数数据科学家来自这三个领域中的一个,因此他们往往倾向于这个学科。然而,数据科学就像一把三脚凳,没有哪一条腿比其他更重要。因此,这一步需要较高的数学知识。但不用担心,我们只需要一个高层次的概述,我们将在这个内核中涵盖。此外,多亏了计算机科学,许多繁重的工作已经为你完成。因此,以前需要数学或统计学研究生学位才能解决的问题,现在只需几行代码。最后,我们还需要一些商业头脑来思考问题。毕竟,就像训练一只导盲犬一样,它是从我们这里学习,而不是反过来。

机器学习(ML),顾名思义,是教机器如何思考,而不是教它们思考什么。虽然这个主题和大数据已经存在了几十年,但由于进入门槛降低,机器学习正比以往任何时候都更加流行,适用于企业和专业人士。这既好又不好。好的一面是,这些算法现在对更多人可用,可以解决更多现实世界中的问题。不好的一面是,进入门槛降低意味着更多的人可能不知道他们所使用的工具,从而得出错误的结论。这就是我专注于教你不仅要做什么,还有为什么要这样做的原因。之前,我用了一个比喻,要求某人递给你一个十字螺丝刀,而他们却递给你一个平头螺丝刀,或者更糟,递给你一把锤子。最多,这表现出对问题的完全不理解。最糟糕的是,这可能使完成项目变得不可能;甚至更糟的是,实施了错误的可操作情报。因此,现在我已经阐明了我的观点,我会告诉你该做什么,最重要的是,为什么要这样做。

首先,你必须明白,机器学习的目的是解决人类问题。机器学习可以分为三类:监督学习、无监督学习和强化学习。监督学习是通过向模型提供包含正确答案的训练数据集来训练模型。无监督学习则是使用不包含正确答案的训练数据集来训练模型。而强化学习是前两者的混合,模型不会立即获得正确答案,而是在一系列事件后获得反馈以强化学习。我们正在进行监督机器学习,因为我们通过提供一组特征及其相应的目标来训练我们的算法。然后,我们希望向其呈现来自同一数据集的新子集,并在预测准确性上获得类似的结果。

机器学习算法有很多,但根据目标变量和数据建模目标,它们可以归纳为四类:分类、回归、聚类或降维。我们将聚类和降维留到另一天,专注于分类和回归。我们可以概括地说,连续目标变量需要回归算法,而离散目标变量需要分类算法。有一点需要说明的是,逻辑回归虽然名字中有回归,但实际上是一个分类算法。由于我们的问题是预测乘客是否幸存,因此这是一个离散目标变量。我们将使用来自sklearn库的分类算法来开始我们的分析。我们将使用交叉验证和评分指标(将在后面的部分中讨论)来排名和比较我们的算法性能。


机器学习选择


Sklearn估算器(estimator)概述

  • Sklearn估算器详细信息
  • 选择估算器思维导图
  • 选择估算器备忘单

现在我们确定了我们的解决方案是一个监督学习分类算法。我们可以缩小选择范围。


机器学习分类算法


  • 集成方法 (Ensemble)
  • 广义线性模型(GLM)
  • 朴素贝叶斯 (Naive Bayesian)
  • 最近邻 (KNN - K Nearest Neighbor)
  • 支持向量机(SVM)
  • 决策树 (Decision Tree)
  • 判别分析 (Discriminant Analysis)


数据科学101:如何选择机器学习算法(MLA Machine Learning Algorithm)


重要提示:在数据建模中,初学者的问题总是:“最好的机器学习算法是什么?”为此,初学者必须了解机器学习的无免费午餐定理(NFLT No Free Lunch Theorem)。简而言之,NFLT表明,没有一种超算法在所有情况下、所有数据集上表现最佳。因此,最好的方法是尝试多种MLA,对其进行调优,并根据你的特定情况进行比较。也有一些很好的研究对算法进行了比较,例如Caruana和Niculescu-Mizil 2006年的视频讲座、Ogutu等2011年由NIH进行的基因选择研究、Fernandez-Delgado等2014年比较179个分类器、Thoma 2016年sklearn比较,还有一种观点认为,更多数据胜过更好的算法。


那么,初学者该从何入手呢?我建议从树、袋装、随机森林和提升法开始。它们基本上是决策树的不同实现,决策树是最容易学习和理解的概念。它们也比SVC等算法更容易调优,后者将在下一部分中讨论。下面,我将概述如何运行和比较几种MLA,但这个内核的其余部分将重点学习通过决策树及其衍生物进行数据建模。


机器学习算法(MLA)选择和初始化


MLA = [
    # 集成方法
    ensemble.AdaBoostClassifier(),
    ensemble.BaggingClassifier(),
    ensemble.ExtraTreesClassifier(),
    ensemble.GradientBoostingClassifier(),
    ensemble.RandomForestClassifier(),

    # 高斯过程
    gaussian_process.GaussianProcessClassifier(),
    
    # GLM
    linear_model.LogisticRegressionCV(),
    linear_model.PassiveAggressiveClassifier(),
    linear_model.RidgeClassifierCV(),
    linear_model.SGDClassifier(),
    linear_model.Perceptron(),
    
    # 朴素贝叶斯
    naive_bayes.BernoulliNB(),
    naive_bayes.GaussianNB(),
    
    # 最近邻
    neighbors.KNeighborsClassifier(),
    
    # SVM
    svm.SVC(probability=True),
    svm.NuSVC(probability=True),
    svm.LinearSVC(),
    
    # 决策树    
    tree.DecisionTreeClassifier(),
    tree.ExtraTreeClassifier(),
    
    # 判别分析
    discriminant_analysis.LinearDiscriminantAnalysis(),
    discriminant_analysis.QuadraticDiscriminantAnalysis(),

    # xgboost: http://xgboost.readthedocs.io/en/latest/model.html
    XGBClassifier()    
]

使用此分割器类进行交叉验证的数据集划分


# 这是train_test_split的替代方案
cv_split = model_selection.ShuffleSplit(n_splits=10, test_size=.3, train_size=.6, random_state=0)  # 运行模型10次,60/30拆分,故意留出10%

创建表格以比较MLA指标


MLA_columns = ['MLA名称', 'MLA参数', 'MLA训练准确性均值', 'MLA测试准确性均值', 'MLA测试准确性3*STD', 'MLA时间']
MLA_compare = pd.DataFrame(columns=MLA_columns)

创建表格以比较MLA预测


MLA_predict = data1[Target]

遍历MLA并将性能保存到表中


row_index = 0
for alg in MLA:
    # 设置名称和参数
    MLA_name = alg.__class__.__name__
    MLA_compare.loc[row_index, 'MLA名称'] = MLA_name
    MLA_compare.loc[row_index, 'MLA参数'] = str(alg.get_params())
    
    # 使用交叉验证评分模型
    cv_results = model_selection.cross_validate(alg, data1[data1_x_bin], data1[Target], cv=cv_split)

    MLA_compare.loc[row_index, 'MLA时间'] = cv_results['fit_time'].mean()
    MLA_compare.loc[row_index, 'MLA训练准确性均值'] = cv_results['train_score'].mean()
    MLA_compare.loc[row_index, 'MLA测试准确性均值'] = cv_results['test_score'].mean()   
    # 如果这是一个无偏随机样本,则从均值加减3个标准差(std)应该在统计上捕获99.7%的子集
    MLA_compare.loc[row_index, 'MLA测试准确性3*STD'] = cv_results['test_score'].std() * 3   # 让我们知道最糟糕的情况是什么!
    
    # 保存MLA预测
    alg.fit(data1[data1_x_bin], data1[Target])
    MLA_predict[MLA_name] = alg.predict(data1[data1_x_bin])
    
    row_index += 1

打印并排序表格


MLA_compare.sort_values(by=['MLA测试准确性均值'], ascending=False, inplace=True)
MLA_compare
# MLA_predict


输出



MLA Name
MLA ParametersMLA Train Accuracy MeanMLA Test Accuracy MeanMLA Test Accuracy 3*STDMLA Time
21XGBClassifier{'base_score': 0.5, 'booster': 'gbtree', 'cols...0.8563670.8294780.05275460.0338062
4RandomForestClassifier{'bootstrap': True, 'class_weight': None, 'cri...0.8923220.8264930.06795250.0147755
14SVC{'C': 1.0, 'cache_size': 200, 'class_weight': ...0.8372660.8261190.04538760.0445107
3GradientBoostingClassifier{'criterion': 'friedman_mse', 'init': None, 'l...0.8666670.8227610.04987310.0715864
15NuSVC{'cache_size': 200, 'class_weight': None, 'coe...0.8357680.8227610.04936810.0524707
2ExtraTreesClassifier{'bootstrap': False, 'class_weight': None, 'cr...0.8951310.8212690.06908630.0144257
17DecisionTreeClassifier{'class_weight': None, 'criterion': 'gini', 'm...0.8951310.819030.05757040.00189724
1BaggingClassifier{'base_estimator': None, 'bootstrap': True, 'b...0.8904490.8138060.06140410.0157245
13KNeighborsClassifier{'algorithm': 'auto', 'leaf_size': 30, 'metric...0.8503750.8138060.06908630.00233798
18ExtraTreeClassifier{'class_weight': None, 'criterion': 'gini', 'm...0.8951310.8126870.06348110.00160697
0AdaBoostClassifier{'algorithm': 'SAMME.R', 'base_estimator': Non...0.8204120.811940.04986060.072931
5GaussianProcessClassifier{'copy_X_train': True, 'kernel': None, 'max_it...0.8717230.8104480.04925370.350273
20QuadraticDiscriminantAnalysis{'priors': None, 'reg_param': 0.0, 'store_cova...0.8215360.807090.08103890.0176577
8RidgeClassifierCV{'alphas': (0.1, 1.0, 10.0), 'class_weight': N...0.7966290.794030.03603020.0105472
19LinearDiscriminantAnalysis{'n_components': None, 'priors': None, 'shrink...0.7968160.794030.03603020.00550387
16LinearSVC{'C': 1.0, 'class_weight': None, 'dual': True,...0.797940.7936570.04006460.0274618
6LogisticRegressionCV{'Cs': 10, 'class_weight': None, 'cv': None, '...0.7970040.7906720.06535820.129134
12GaussianNB{'priors': None}0.7947570.7813430.08745680.00183613
11BernoulliNB{'alpha': 1.0, 'binarize': 0.0, 'class_prior':...0.7857680.7753730.05703470.00200269
7PassiveAggressiveClassifier{'C': 1.0, 'average': False, 'class_weight': N...0.7344570.7305970.1488260.00238907
10Perceptron{'alpha': 0.0001, 'class_weight': None, 'eta0'...0.7400750.7287310.1622210.00185683
9SGDClassifier{'alpha': 0.0001, 'average': False, 'class_wei...0.7370790.7261190.173720.00182471


可视化结果


import seaborn as sns
import matplotlib.pyplot as plt

# 假设 MLA_compare 是之前创建的数据框,包含机器学习算法的性能数据
# 绘制条形图
sns.barplot(x='MLA Test Accuracy Mean', y='MLA Name', data=MLA_compare, color='m')

# 美化图表
plt.title('Machine Learning Algorithm Accuracy Score \n')
plt.xlabel('Accuracy Score (%)')
plt.ylabel('Algorithm')

# 显示图表
plt.show()


5.1 评估模型性能


让我们回顾一下,通过一些基本的数据清理、分析和机器学习算法(MLA),我们能够以约82%的准确率预测乘客的生存情况。这在几行代码的情况下还不错。但是,我们总是要问的问题是,我们能做得更好吗?更重要的是,我们投入的时间是否值得回报(ROI)?例如,如果我们只增加0.1%的准确率,三个月的开发时间真的值得吗?如果你从事研究,答案可能是“是的”,但如果你在商业领域,答案大多是“不是”。因此,在改进模型时,请牢记这一点。


数据科学101:确定基准准确率


在决定如何改善我们的模型之前,我们必须确定我们的模型是否值得保留。为此,我们需要回到数据科学的基本原则。我们知道这是一个二元问题,因为只有两种可能的结果:乘客存活或死亡。因此,可以把它看作是一个掷硬币的问题。如果你有一个公平的硬币,猜测正面或反面,你就有50%的机会猜对。因此,我们将50%设定为最差的模型性能;因为低于这个水平,那我还需要你做什么呢?我可以直接掷硬币。

好的,所以在没有关于数据集的信息时,我们总是可以在二元问题中得到50%。但是我们对数据集有一些信息,因此我们应该能够做得更好。我们知道1502/2224或67.5%的人死亡。因此,如果我们仅仅预测100%的人都死了,那么我们的正确率就是67.5%。所以,我们将68%设定为糟糕的模型性能;因为同样地,低于这个水平,我还需要你做什么呢?我可以通过预测最常见的结果来得到这个结果。


数据科学101:如何创建自己的模型


我们的准确率在提高,但我们能做得更好吗?我们的数据中是否有任何信号?为了说明这一点,我们将构建自己的决策树模型,因为它是最容易理解的,并且只需要简单的加法和乘法计算。当创建决策树时,你要问一些问题来划分你的目标响应,将存活/1和死亡/0放入同质子组。这既是科学也是艺术,因此我们就玩“21个问题”的游戏来展示它是如何运作的。如果你想自己进行操作,请下载训练数据集并导入Excel。创建一个数据透视表,生存情况作为列,计数和行计数的百分比作为值,下面是描述的特征作为行。

记住,游戏的名称是使用决策树模型创建子组,将存活/1放在一个桶中,死亡/0放在另一个桶中。我们的经验法则是“多数规则”。这意味着如果大多数或50%或更多存活,则我们子组中的每个人都存活/1;但如果存活人数为50%或更少,则我们子组中的每个人都死亡/0。此外,如果子组的数量少于10,或者模型准确性停滞或下降,我们将停止。明白了吗?让我们开始吧!


问题1:你在泰坦尼克号上吗?如果是,那么大多数(62%)人死亡。请注意,我们的样本生存率与我们的人口生存率68%不同。不过,如果我们假设每个人都死了,那么我们的样本准确性为62%。


问题2:你是男性还是女性?男性,大多数(81%)人死亡。女性,大多数(74%)人存活。这样我们的准确率为79%。


问题3A(进入女性分支,计数=314):你在1、2或3等舱位中吗?1舱,大多数(97%)人存活,2舱,大多数(92%)人存活。由于死亡子组少于10,我们将停止这个分支。3舱则是50-50的平分,没有新的信息来改善我们的模型。


问题4A(进入女性3舱分支,计数=144):你是从C、Q或S港口上船的吗?我们获得了一些信息。C和Q,大多数仍然存活,所以没有变化。此外,死亡子组少于10,因此我们将停止。S港,大多数(63%)人死亡。因此,我们将把女性、3舱、从S港上船的人从假设他们存活改为假设他们死亡。我们的模型准确率提高到81%。


问题5A(进入女性3舱从S港上船的分支,计数=88):到目前为止,看起来我们做出了不错的决策。增加另一个层次似乎没有获得更多信息。该子组中55人死亡,33人存活,由于大多数人死亡,我们需要找到一个信号来识别这33人,或者一个子组来将他们从死亡转变为存活,从而提高我们的模型准确性。我们可以尝试我们的特征。我发现的一个是票价0-8,大多数人存活。这是一个小样本(11-9),但在统计学中经常使用。我们略微提高了准确率,但没有太大变化,无法超过82%。所以,我们在这里停止。


问题3B(进入男性分支,计数=577):回到问题2,我们知道大多数男性死亡。因此,我们在寻找一个特征来识别大多数存活的子组。令人惊讶的是,舱位甚至上船地点对男性并不重要,但头衔却有效,使我们达到了82%。猜测和检查其他特征,似乎没有什么能让我们超过82%。所以,我们暂时在这里停止。


你做到了,凭借很少的信息,我们达到了82%的准确率。在最差、糟糕、好、较好和最好五个级别的标准中,我们将82%设定为“好”,因为这是一个简单的模型,给我们带来了不错的结果。但是问题仍然是,我们能否做得比手动创建的模型更好?


自制的模型示例


# 重要提示:这是一个手工模型,仅用于学习目的。
# 然而,您可以在没有复杂算法的情况下创建自己的预测模型 :)

import random
import pandas as pd
from sklearn import metrics

# 创建一个示例数据框(请用您的数据替换)
data1 = pd.DataFrame({
    'Survived': [0, 1, 1, 0, 1, 0]  # 示例生存数据
})

# 通过随机数生成器创建预测
for index, row in data1.iterrows(): 
    if random.random() > .5:  # 随机生成一个浮点数 x,范围为 0.0 <= x < 1.0    
        data1.at[index, 'Random_Predict'] = 1  # 预测存活/1
    else: 
        data1.at[index, 'Random_Predict'] = 0  # 预测死亡/0

# 评估随机预测的准确性
data1['Random_Score'] = 0  # 假设预测错误
data1.loc[(data1['Survived'] == data1['Random_Predict']), 'Random_Score'] = 1  # 正确预测设为1
print('Coin Flip Model Accuracy: {:.2f}%'.format(data1['Random_Score'].mean() * 100))

# 使用scikit-learn的accuracy_score函数来简化代码
print('Coin Flip Model Accuracy w/SciKit: {:.2f}%'.format(metrics.accuracy_score(data1['Survived'], data1['Random_Predict']) * 100))


解释

  1. 手动模型:这是一个简单的“掷硬币”模型,通过随机生成的数值来预测乘客是否生存。
  2. 准确性评估:通过与真实生存数据比较,计算模型的准确性。
  3. SciKit的使用:使用Scikit-learn库的accuracy_score函数简化了准确性计算的过程。


输出示例

  • Coin Flip Model Accuracy: 47.81%
  • Coin Flip Model Accuracy w/SciKit: 47.81%

这个模型的准确率表明,随机预测的性能很差(约47.81%),这比我们设定的68%的基准性能还低。因此,这个手工模型为我们提供了一个基线,接下来可以尝试改进模型以提高预测准确性。


自制决策树模型示例


import pandas as pd
import numpy as np
from sklearn import metrics
import matplotlib.pyplot as plt
import itertools

# 手工创建的决策树模型函数
def mytree(df):
    # 初始化表格以存储预测
    Model = pd.DataFrame(data={'Predict': []})
    male_title = ['Master']  # 存活的头衔

    for index, row in df.iterrows():
        # 问题1:你是在泰坦尼克号上吗?大多数人都死了
        Model.loc[index, 'Predict'] = 0

        # 问题2:你是女性吗?大多数人存活
        if df.loc[index, 'Sex'] == 'female':
            Model.loc[index, 'Predict'] = 1

        # 问题5B:女性 - FareBin;在女性节点决策树中,设置任何低于0.5的值返回0       
        if ((df.loc[index, 'Sex'] == 'female') &
            (df.loc[index, 'Pclass'] == 3) &
            (df.loc[index, 'Embarked'] == 'S') &
            (df.loc[index, 'Fare'] > 8)):
            Model.loc[index, 'Predict'] = 0

        # 问题3B:男性 - 头衔;设置任何高于0.5的值为1,表示大多数人存活
        if ((df.loc[index, 'Sex'] == 'male') &
            (df.loc[index, 'Title'] in male_title)):
            Model.loc[index, 'Predict'] = 1

    return Model

# 模型数据
Tree_Predict = mytree(data1)
print('Decision Tree Model Accuracy/Precision Score: {:.2f}%\n'.format(metrics.accuracy_score(data1['Survived'], Tree_Predict) * 100))

# 准确性摘要报告
print(metrics.classification_report(data1['Survived'], Tree_Predict))

# 绘制准确性摘要
def plot_confusion_matrix(cm, classes, normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    if normalize:
        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
        print("Normalized confusion matrix")
    else:
        print('Confusion matrix, without normalization')

    print(cm)

    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)

    fmt = '.2f' if normalize else 'd'
    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, format(cm[i, j], fmt),
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")

    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')

# 计算混淆矩阵
cnf_matrix = metrics.confusion_matrix(data1['Survived'], Tree_Predict)
np.set_printoptions(precision=2)

class_names = ['Dead', 'Survived']
# 绘制非标准化混淆矩阵
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names,
                      title='Confusion matrix, without normalization')

# 绘制标准化混淆矩阵
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names, normalize=True,
                      title='Normalized confusion matrix')

解释

  1. 手工决策树模型:通过简单的条件语句和逻辑构建了一个决策树模型来预测乘客的生存情况。
  2. 准确性评估:模型准确性为82.04%,并输出了详细的分类报告,包括准确率、召回率和F1分数。
  3. 混淆矩阵绘制:使用plot_confusion_matrix函数可视化模型的预测结果,包括非标准化和标准化的混淆矩阵。

输出示例

  • 决策树模型准确性: 82.04%
  • 分类报告:
  • 混淆矩阵输出:


模型性能与超参数调整


1. 交叉验证(Cross-Validation, CV)的重要性


在构建机器学习模型时,交叉验证是一种重要的技术。它帮助我们有效地评估模型的表现,防止过拟合,即模型在训练集上表现良好但在未见数据上表现不佳。通过使用不同的数据子集进行训练和测试,交叉验证确保了模型的泛化能力。


示例代码:

from sklearn import tree, model_selection
import pandas as pd

# 基础模型
dtree = tree.DecisionTreeClassifier(random_state=0)
base_results = model_selection.cross_validate(dtree, data1[data1_x_bin], data1[Target], cv=cv_split)
dtree.fit(data1[data1_x_bin], data1[Target])

print('BEFORE DT Parameters: ', dtree.get_params())
print("BEFORE DT Training w/bin score mean: {:.2f}".format(base_results['train_score'].mean() * 100))
print("BEFORE DT Test w/bin score mean: {:.2f}".format(base_results['test_score'].mean() * 100))
print("BEFORE DT Test w/bin score 3*std: +/- {:.2f}".format(base_results['test_score'].std() * 100 * 3))
print('-' * 10)

2. 调整模型超参数


在初始模型中,我们使用了默认参数。为了提高模型性能,可以通过调整超参数来进行优化。决策树的一些超参数包括:

  • criterion: 用于测量分裂质量的标准(如ginientropy
  • max_depth: 树的最大深度
  • min_samples_split: 分裂内部节点所需的最小样本数


示例代码:


pythonCopy code# 超参数网格
param_grid = {
    'criterion': ['gini', 'entropy'],
    'max_depth': [2, 4, 6, 8, 10, None],
    'random_state': [0]
}

# 网格搜索
tune_model = model_selection.GridSearchCV(tree.DecisionTreeClassifier(), param_grid=param_grid, scoring='roc_auc', cv=cv_split)
tune_model.fit(data1[data1_x_bin], data1[Target])

print('AFTER DT Parameters: ', tune_model.best_params_)
print("AFTER DT Training w/bin score mean: {:.2f}".format(tune_model.cv_results_['mean_train_score'][tune_model.best_index_] * 100))
print("AFTER DT Test w/bin score mean: {:.2f}".format(tune_model.cv_results_['mean_test_score'][tune_model.best_index_] * 100))
print("AFTER DT Test w/bin score 3*std: +/- {:.2f}".format(tune_model.cv_results_['std_test_score'][tune_model.best_index_] * 100 * 3))
print('-' * 10)

3. 模型结果


  • 初始模型参数:参数: {'class_weight': None, 'criterion': 'gini', 'max_depth': None, ...}训练得分均值: 89.51%测试得分均值: 82.09%测试得分的标准差: ±5.57%
  • 调整后的模型参数:参数: {'criterion': 'gini', 'max_depth': 4, 'random_state': 0}训练得分均值: 89.35%测试得分均值: 87.40%测试得分的标准差: ±5.00%


模型优化与特征选择


在机器学习中,选择合适的特征变量比单纯增加特征数量更为重要。通过有效的特征选择,我们可以提高模型的表现并减少过拟合的风险。下面是如何使用递归特征消除(RFE)与交叉验证(CV)来优化我们的决策树模型。


1. 基本模型信息


在进行特征选择之前,首先打印出原始数据的形状和特征列信息。


示例代码:

# 基础模型
print('BEFORE DT RFE Training Shape Old: ', data1[data1_x_bin].shape) 
print('BEFORE DT RFE Training Columns Old: ', data1[data1_x_bin].columns.values)

print("BEFORE DT RFE Training w/bin score mean: {:.2f}".format(base_results['train_score'].mean() * 100)) 
print("BEFORE DT RFE Test w/bin score mean: {:.2f}".format(base_results['test_score'].mean() * 100))
print("BEFORE DT RFE Test w/bin score 3*std: +/- {:.2f}".format(base_results['test_score'].std() * 100 * 3))
print('-' * 10)

2. 特征选择


使用RFECV进行递归特征消除,该方法会自动选择最优特征并进行交叉验证。


示例代码:

from sklearn import feature_selection

# 特征选择
dtree_rfe = feature_selection.RFECV(dtree, step=1, scoring='accuracy', cv=cv_split)
dtree_rfe.fit(data1[data1_x_bin], data1[Target])

# 选择经过特征选择的特征
X_rfe = data1[data1_x_bin].columns.values[dtree_rfe.get_support()]
rfe_results = model_selection.cross_validate(dtree, data1[X_rfe], data1[Target], cv=cv_split)

print('AFTER DT RFE Training Shape New: ', data1[X_rfe].shape) 
print('AFTER DT RFE Training Columns New: ', X_rfe)

print("AFTER DT RFE Training w/bin score mean: {:.2f}".format(rfe_results['train_score'].mean() * 100)) 
print("AFTER DT RFE Test w/bin score mean: {:.2f}".format(rfe_results['test_score'].mean() * 100))
print("AFTER DT RFE Test w/bin score 3*std: +/- {:.2f}".format(rfe_results['test_score'].std() * 100 * 3))
print('-' * 10)

3. 调整特征选择后的模型


在进行特征选择之后,使用网格搜索对模型进行调优。


示例代码:

# 调整RFE模型
rfe_tune_model = model_selection.GridSearchCV(tree.DecisionTreeClassifier(), param_grid=param_grid, scoring='roc_auc', cv=cv_split)
rfe_tune_model.fit(data1[X_rfe], data1[Target])

print('AFTER DT RFE Tuned Parameters: ', rfe_tune_model.best_params_)
print("AFTER DT RFE Tuned Training w/bin score mean: {:.2f}".format(rfe_tune_model.cv_results_['mean_train_score'][rfe_tune_model.best_index_] * 100)) 
print("AFTER DT RFE Tuned Test w/bin score mean: {:.2f}".format(rfe_tune_model.cv_results_['mean_test_score'][rfe_tune_model.best_index_] * 100))
print("AFTER DT RFE Tuned Test w/bin score 3*std: +/- {:.2f}".format(rfe_tune_model.cv_results_['std_test_score'][rfe_tune_model.best_index_] * 100 * 3))
print('-' * 10)

模型结果

  • 初始模型信息:原始数据形状: (891, 7)原始特征列: ['Sex_Code', 'Pclass', 'Embarked_Code', 'Title_Code', 'FamilySize', 'AgeBin_Code', 'FareBin_Code']训练得分均值: 89.51%测试得分均值: 82.09%测试得分的标准差: ±5.57%
  • 经过特征选择后的模型信息:新数据形状: (891, 6)新特征列: ['Sex_Code', 'Pclass', 'Title_Code', 'FamilySize', 'AgeBin_Code', 'FareBin_Code']训练得分均值: 88.16%测试得分均值: 83.06%测试得分的标准差: ±6.22%
  • 调整后的RFE模型参数:最佳参数: {'criterion': 'gini', 'max_depth': 4, 'random_state': 0}调整后训练得分均值: 89.39%调整后测试得分均值: 87.34%调整后测试得分的标准差: ±6.21%

4. 可视化决策树


使用Graphviz可视化最终的决策树。


示例代码:

import graphviz 

dot_data = tree.export_graphviz(dtree, out_file=None, 
                                feature_names=data1_x_bin, class_names=True,
                                filled=True, rounded=True)
graph = graphviz.Source(dot_data) 
graph

data/78df2c1f-e442-415d-a382-fa7925af0c4b/96030788-09fa-4f81-9ba7-6ef9a3e8b487image.png

步骤 6:验证与实施


在这一步中,我们将使用验证数据进行模型评估与实施。


1. 比较算法预测


我们通过热力图比较不同模型的预测结果,以识别不同模型之间的相关性。

# 比较算法预测的热力图
correlation_heatmap(MLA_predict)

2. 投票分类器


我们使用投票分类器将多个模型结合起来,以提高最终的预测性能。根据模型的可用性,我们筛选出具有predict_proba属性的模型,并排除了与其他模型相关性为1.0的模型。


# 投票分类器配置
vote_est = [
    # 集成方法
    ('ada', ensemble.AdaBoostClassifier()),
    ('bc', ensemble.BaggingClassifier()),
    ('etc', ensemble.ExtraTreesClassifier()),
    ('gbc', ensemble.GradientBoostingClassifier()),
    ('rfc', ensemble.RandomForestClassifier()),
    
    # 高斯过程
    ('gpc', gaussian_process.GaussianProcessClassifier()),
    
    # 广义线性模型
    ('lr', linear_model.LogisticRegressionCV()),
    
    # 朴素贝叶斯
    ('bnb', naive_bayes.BernoulliNB()),
    ('gnb', naive_bayes.GaussianNB()),
    
    # 最近邻
    ('knn', neighbors.KNeighborsClassifier()),
    
    # 支持向量机
    ('svc', svm.SVC(probability=True)),
    
    # XGBoost
    ('xgb', XGBClassifier())
]

3. 硬投票分类器


使用硬投票分类器,采用简单的多数规则进行预测。


# 硬投票分类器
vote_hard = ensemble.VotingClassifier(estimators=vote_est, voting='hard')
vote_hard_cv = model_selection.cross_validate(vote_hard, data1[data1_x_bin], data1[Target], cv=cv_split)
vote_hard.fit(data1[data1_x_bin], data1[Target])

print("Hard Voting Training w/bin score mean: {:.2f}".format(vote_hard_cv['train_score'].mean() * 100))
print("Hard Voting Test w/bin score mean: {:.2f}".format(vote_hard_cv['test_score'].mean() * 100))
print("Hard Voting Test w/bin score 3*std: +/- {:.2f}".format(vote_hard_cv['test_score'].std() * 100 * 3))
print('-' * 10)

4. 软投票分类器


使用软投票分类器,利用加权概率进行预测。

# 软投票分类器
vote_soft = ensemble.VotingClassifier(estimators=vote_est, voting='soft')
vote_soft_cv = model_selection.cross_validate(vote_soft, data1[data1_x_bin], data1[Target], cv=cv_split)
vote_soft.fit(data1[data1_x_bin], data1[Target])

print("Soft Voting Training w/bin score mean: {:.2f}".format(vote_soft_cv['train_score'].mean() * 100))
print("Soft Voting Test w/bin score mean: {:.2f}".format(vote_soft_cv['test_score'].mean() * 100))
print("Soft Voting Test w/bin score 3*std: +/- {:.2f}".format(vote_soft_cv['test_score'].std() * 100 * 3))
print('-' * 10)

5. 超参数调优


使用GridSearchCV对模型的超参数进行调优,以提高模型性能。


# 超参数调优
grid_n_estimator = [10, 50, 100, 300]
grid_ratio = [.1, .25, .5, .75, 1.0]
grid_learn = [.01, .03, .05, .1, .25]
grid_max_depth = [2, 4, 6, 8, 10, None]
grid_min_samples = [5, 10, .03, .05, .10]
grid_criterion = ['gini', 'entropy']
grid_bool = [True, False]
grid_seed = [0]

# 超参数配置
grid_param = [
    # AdaBoostClassifier
    [{
        'n_estimators': grid_n_estimator,
        'learning_rate': grid_learn,
        'random_state': grid_seed
    }],
    
    # BaggingClassifier
    [{
        'n_estimators': grid_n_estimator,
        'max_samples': grid_ratio,
        'random_state': grid_seed
    }],
    
    # ExtraTreesClassifier
    [{
        'n_estimators': grid_n_estimator,
        'criterion': grid_criterion,
        'max_depth': grid_max_depth,
        'random_state': grid_seed
    }],
    
    # GradientBoostingClassifier
    [{
        'learning_rate': [.05],
        'n_estimators': [300],
        'max_depth': grid_max_depth,
        'random_state': grid_seed
    }],
    
    # RandomForestClassifier
    [{
        'n_estimators': grid_n_estimator,
        'criterion': grid_criterion,
        'max_depth': grid_max_depth,
        'oob_score': [True],
        'random_state': grid_seed
    }],
    
    # GaussianProcessClassifier
    [{
        'max_iter_predict': grid_n_estimator,
        'random_state': grid_seed
    }],
    
    # LogisticRegressionCV
    [{
        'fit_intercept': grid_bool,
        'solver': ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga'],
        'random_state': grid_seed
    }],
    
    # BernoulliNB
    [{
        'alpha': grid_ratio,
    }],
    
    # GaussianNB
    [{}],
    
    # KNeighborsClassifier
    [{
        'n_neighbors': [1, 2, 3, 4, 5, 6, 7],
        'weights': ['uniform', 'distance'],
        'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute']
    }],
    
    # SVC
    [{
        'C': [1, 2, 3, 4, 5],
        'gamma': grid_ratio,
        'decision_function_shape': ['ovo', 'ovr'],
        'probability': [True],
        'random_state': grid_seed
    }],
    
    # XGBClassifier
    [{
        'learning_rate': grid_learn,
        'max_depth': [1, 2, 4, 6, 8, 10],
        'n_estimators': grid_n_estimator,
        'seed': grid_seed
    }]
]

start_total = time.perf_counter()  # 记录开始时间
for clf, param in zip(vote_est, grid_param):
    start = time.perf_counter()        
    best_search = model_selection.GridSearchCV(estimator=clf[1], param_grid=param, cv=cv_split, scoring='roc_auc')
    best_search.fit(data1[data1_x_bin], data1[Target])
    run = time.perf_counter() - start

    best_param = best_search.best_params_
    print('The best parameter for {} is {} with a runtime of {:.2f} seconds.'.format(clf[1].__class__.__name__, best_param, run))
    clf[1].set_params(**best_param) 

run_total = time.perf_counter() - start_total
print('Total optimization time was {:.2f} minutes.'.format(run_total / 60))
print('-' * 10)

结果


  • 硬投票模型训练结果:训练得分均值: 86.59%测试得分均值: 82.39%测试得分的标准差: ±4.95%
  • 软投票模型训练结果:训练得分均值: 87.15%测试得分均值: 82.35%测试得分的标准差: ±4.85%
  • 超参数调优时间:总优化时间约为X分钟(具体时间取决于运行结果)。
警告: 该运行过程计算密集且耗时,代码仅供实验与开发使用,未准备好用于生产环境


#任意步骤,当你满意模型可以使用model.predict预测结果

data_val['Survived'] = best_search.predict(data_val[data1_x_bin])


#结果文件
submit = data_val[['PassengerId','Survived']]

print('Validation Data Distribution: \n', data_val['Survived'].value_counts(normalize = True))
submit.sample(10)


步骤7:优化和战略


结论


数据科学框架的第一轮迭代似乎收敛于 0.77990 的提交准确率。使用相同的数据集和不同的决策树实现(如 Adaboost、随机森林、梯度提升、XGBoost 等)并进行调优,均未超过 0.77990 的提交准确率。有趣的是,对于这个数据集,简单的决策树算法在默认情况下获得了最佳提交分数,并通过调优达到了相同的最佳准确率分数。

虽然基于对单个数据集测试少数算法的结果无法得出一般性结论,但对提到的数据集有以下几条观察:

训练数据集与测试/验证数据集及其分布不同。这在交叉验证(CV)准确率和实际提交准确率之间造成了较大的差距。


在相同数据集上,基于决策树的算法在适当调优后,似乎收敛于相同的准确率分数。


尽管进行了调优,但没有任何机器学习算法超过自制算法。作者推测,对于小型数据集,自制算法是需要超越的标杆。

原文链接:点击此处

搬运结语:

这是一份非常全面的机器学习入门手册,后续本人也会将内容拆分,写一份更好食用消化的,请期待~

评论
Copyright Created by DataER | 沪ICP备2024052789号-5 | 沪公网安备31010402336337号