天津免费建网站,福田建设网站,360产品展示网站,网站建设规划书的目的Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 [ 项目介绍 ] [ 获取数据 ] [ 创建测试集 ]| 1/3#xff08;含分析过程#xff09; 目录
Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 [ 项目介绍 ] [ 获取数据 ] [ 创建测试集 ]| 1/3#x…Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 [ 项目介绍 ] [ 获取数据 ] [ 创建测试集 ]| 1/3含分析过程 目录
Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 [ 项目介绍 ] [ 获取数据 ] [ 创建测试集 ]| 1/3含分析过程
一、简单介绍
二、机器学习
1、为什么使用机器学习
2、机器学习系统的类型及其对应的学习算法
3、机器学习可利用的开源数据
三、房价数据中位数分析 项目介绍
1、划定问题
2、选择性能指标
3、核实假设
四、环境准备
五、获取数据该案例房价的数据集
1、下载数据
2、快速查看数据结构
3、创建测试集
附录
一、一些知识点
1、流水线
二、源码工程
三、该案例的环境 package 信息如下 一、简单介绍
Python是一种跨平台的计算机程序设计语言。是一种面向对象的动态类型语言最初被设计用于编写自动化脚本(shell)随着版本的不断更新和语言新功能的添加越多被用于独立的、大型项目的开发。Python是一种解释型脚本语言可以应用于以下领域 Web 和 Internet开发、科学计算和统计、人工智能、教育、桌面界面开发、软件开发、后端开发、网络爬虫。 Python 机器学习是利用 Python 编程语言中的各种工具和库来实现机器学习算法和技术的过程。Python 是一种功能强大且易于学习和使用的编程语言因此成为了机器学习领域的首选语言之一。Python 提供了丰富的机器学习库如Scikit-learn、TensorFlow、Keras、PyTorch等这些库包含了许多常用的机器学习算法和深度学习框架使得开发者能够快速实现、测试和部署各种机器学习模型。 通过 Python 进行机器学习开发者可以利用其丰富的工具和库来处理数据、构建模型、评估模型性能并将模型部署到实际应用中。Python 的易用性和庞大的社区支持使得机器学习在各个领域都得到了广泛的应用和发展。 二、机器学习
机器学习Machine Learning是人工智能AI的一个分支领域其核心思想是通过计算机系统的学习和自动化推理使计算机能够从数据中获取知识和经验并利用这些知识和经验进行模式识别、预测和决策。机器学习算法能够自动地从数据中学习并改进自己的性能而无需明确地编程。这一过程涉及对大量输入数据的分析和解释以识别数据中的模式和趋势并生成可以应用于新数据的预测模型。
1、为什么使用机器学习 使用机器学习的原因主要包括以下几点 高效性和准确性机器学习算法能够处理大规模数据集并从中提取有价值的信息其预测和决策的准确性往往高于传统方法。自动化和智能化机器学习能够自动学习和改进减少了对人工干预的依赖提高了工作效率和智能化水平。广泛应用性机器学习在各个领域中都有广泛的应用如图像识别、语音识别、自然语言处理、推荐系统、金融预测等为许多实际问题的解决提供了有效的方法和工具。未来趋势随着人工智能技术的不断发展机器学习已成为未来的趋势掌握机器学习技能将有助于提高职业竞争力和创造力。 2、机器学习系统的类型及其对应的学习算法 机器学习系统可以根据不同的学习方式和目标进行分类主要包括以下几种类型及其对应的学习算法 监督学习Supervised Learning 定义使用带有标签的训练数据来训练模型以预测新数据的标签或目标值。常见算法 线性回归Linear Regression用于预测连续值。逻辑回归Logistic Regression用于分类问题尤其是二分类问题。支持向量机SVM, Support Vector Machines用于分类和回归问题通过寻找最优的超平面来分割数据。决策树Decision Trees和随机森林Random Forests通过构建决策树或决策树集合来进行分类或回归。神经网络Neural Networks模仿人脑神经元的工作方式通过多层节点之间的连接和权重调整来进行学习和预测。 无监督学习Unsupervised Learning 定义在没有标签的情况下从数据中发现隐藏的结构和模式。常见算法 聚类算法Clustering Algorithms如K均值K-Means、层次聚类分析HCA等用于将数据分组为具有相似特征的簇。降维算法Dimensionality Reduction Algorithms如主成分分析PCA、t-分布邻域嵌入算法t-SNE等用于减少数据的维度以便于分析和可视化。异常检测Anomaly Detection用于识别数据中的异常点或离群点。 强化学习Reinforcement Learning 定义通过与环境的交互学习以最大化累积奖励为目标。特点强化学习不需要明确的标签或监督信号而是根据环境给出的奖励或惩罚来指导学习过程。应用场景游戏AI、机器人控制、自动驾驶等领域。 半监督学习Semi-Supervised Learning 定义处理部分带标签的训练数据通常是大量不带标签数据加上小部分带标签数据。特点结合了监督学习和无监督学习的特点旨在利用未标记数据来提高模型的泛化能力。常见算法多数半监督学习算法是非监督和监督算法的结合如自训练Self-Training、协同训练Co-Training等。 3、机器学习可利用的开源数据 开源数据集可以根据需要进行选择涵盖多个领域。以下是一些可以查找的数据的地方供参考 流行的开源数据仓库 UC Irvine Machine Learning RepositoryKaggle datasetsAmazons AWS datasets准入口提供开源数据列表 - Data Portalshttp://opendatamonitor.eu/Financial, Economic and Alternative Data | Nasdaq Data Link其它列出流行开源数据仓库的网页 Wikipedias list of Machine Learning datasetsQuora.com questionDatasets subreddit 注意代码执行的时候可能需要科学上网 三、房价数据中位数分析 项目介绍
欢迎来到机器学习房地产公司你的第一个任务是利用加州普查数据建立一个加州房价模型。这个数据包含每个街区组的人口、收入中位数、房价中位数等指标。
街区组是美国调查局发布样本数据的最小地理单位一个街区通常有 600 到 3000 人。我们将其简称为“街区”。
你的模型要利用这个数据进行学习然后根据其它指标预测任何街区的的房价中位数。 1、划定问题
问老板的第一个问题应该是商业目标是什么建立模型可能不是最终目标。公司要如何使用、并从模型受益这非常重要因为它决定了如何划定问题要选择什么算法评估模型性能的指标是什么要花多少精力进行微调。
过程 1.1 老板告诉你你的模型的输出一个区的房价中位数会传给另一个机器学习系统 见图 3 - 1 也有其它信号会传入后面的系统。这一整套系统可以确定某个区进行投资值不值。确定值不值得投资非常重要它直接影响利润。 图 3-1 房地产投资的机器学习流水线 1.2 下一个要问的问题是现在的解决方案效果如何。 老板通常会给一个参考性能以及如何解决问题。老板说现在街区的房价是靠专家手工估计的专家队伍收集最新的关于一个区的信息不包括房价中位数他们使用复杂的规则进行估计。这种方法费钱费时间而且估计结果不理想误差率大概有 15%。 1.3 OK有了这些信息你就可以开始设计系统了。 首先你需要划定问题监督或非监督还是强化学习这是个分类任务、回归任务还是其它的要使用批量学习还是线上学习继续阅读之前请暂停一下尝试自己回答下这些问题。 1.4 你能回答出来吗 一起看下答案很明显这是一个典型的监督学习任务因为你要使用的是有标签的训练样本每个实例都有预定的产出即街区的房价中位数。并且这是一个典型的回归任务因为你要预测一个值。讲的更细些这是一个多变量回归问题因为系统要使用多个变量进行预测要使用街区的人口收入中位数等等。 2、选择性能指标
下一步是选择性能指标。回归问题的典型指标是均方根误差RMSE。均方根误差测量的是系统预测误差的标准差。例如RMSE 等于 50000意味着68% 的系统预测值位于实际值的 50000 美元以内95% 的预测值位于实际值的 100000 美元以内一个特征通常都符合高斯分布即满足 “68-95-99.7”规则大约 68% 的值落在1σ内95% 的值落在2σ内99.7% 的值落在3σ内这里的σ等于 50000。公式 3-1 展示了计算 RMSE 的方法。 公式 3-1 均方根误差RMSE 符号的含义 这个方程引入了一些常见的贯穿本书的机器学习符号 m是测量 RMSE 的数据集中的实例数量。 例如如果用一个含有 2000 个街区的验证集求 RMSE则m 2000。 虽然大多数时候 RMSE 是回归任务可靠的性能指标在有些情况下你可能需要另外的函数。例如假设存在许多异常的街区。此时你可能需要使用平均绝对误差Mean Absolute Error也称作平均绝对偏差见公式 3-2 公式 3-2 平均绝对误差
MSE 和 MAE 都是测量预测值和目标值两个向量距离的方法。有多种测量距离的方法或范数 计算对应欧几里得范数的平方和的根RMSE这个距离介绍过。它也称作ℓ2范数标记为||·||₂或只是||·||。 计算对应于ℓ1标记为||·||₁范数的绝对值和MAE。有时也称其为曼哈顿范数因为它测量了城市中的两点沿着矩形的边行走的距离。 更一般的包含n个元素的向量v的ℓk范数K 阶闵氏范数定义成 ℓ0汉明范数只显示了这个向量的基数即非零元素的个数ℓ∞切比雪夫范数是向量中最大的绝对值。 范数的指数越高就越关注大的值而忽略小的值。这就是为什么 RMSE 比 MAE 对异常值更敏感。但是当异常值是指数分布的类似正态曲线RMSE 就会表现很好。 3、核实假设
最后最好列出并核对迄今你或其他人作出的假设这样可以尽早发现严重的问题。例如你的系统输出的街区房价会传入到下游的机器学习系统我们假设这些价格确实会被当做街区房价使用。但是如果下游系统实际上将价格转化成了分类例如便宜、中等、昂贵然后使用这些分类而不是使用价格。这样的话获得准确的价格就不那么重要了你只需要得到合适的分类。问题相应地就变成了一个分类问题而不是回归任务。你可不想在一个回归系统上工作了数月最后才发现真相。
幸运的是在与下游系统主管探讨之后你很确信他们需要的就是实际的价格而不是分类。很好整装待发可以开始写代码了。 四、环境准备
1、最好构建一个虚拟环境避免与其他工程公用一环境造成一些包的版本不同导致运行问题
2、环境中除了使用 Python 外还用主要用到 jupyter matplotlib numpy pandas scipy scikit-learn
pip install jupyter matplotlib numpy pandas scipy scikit-learn
3、该案例的环境 package 版本信息见附录 五、获取数据该案例房价的数据集
1、下载数据
一般情况下数据是存储于关系型数据库或其它常见数据库中的多个表、文档、文件。要访问数据你首先要有密码和登录权限并要了解数据模式。但是在这个项目中这一切要简单些只要下载一个压缩文件housing.tgz它包含一个CSV 文件housing.csv含有所有数据。
你可以使用浏览器下载运行tar xzf housing.tgz解压出csv文件但是更好的办法是写一个小函数来做这件事。如果数据变动频繁这么做是非常好的因为可以让你写一个小脚本随时获取最新的数据或者创建一个定时任务来做。如果你想在多台机器上安装数据集获取数据自动化也是非常好的。
下面是获取数据的函数
import os # 用于操作文件和目录
import tarfile # 用于处理tar压缩文件
import requests # 用于发送HTTP请求
from requests.adapters import HTTPAdapter # 用于在requests中设置重试策略
from requests.packages.urllib3.util.retry import Retry # 用于设置重试策略# 下载文件的根URL
DOWNLOAD_ROOT https://raw.githubusercontent.com/ageron/handson-ml/master/
# 存储数据的路径
HOUSING_PATH datasets/housing
# 完整的下载URL
HOUSING_URL DOWNLOAD_ROOT HOUSING_PATH /housing.tgzdef fetch_housing_data(housing_urlHOUSING_URL, housing_pathHOUSING_PATH):# 如果目录不存在则创建目录if not os.path.isdir(housing_path):os.makedirs(housing_path)# 压缩文件存储路径tgz_path os.path.join(housing_path, housing.tgz)# 设置重试机制session requests.Session()retry Retry(total5, # 重试总次数backoff_factor1, # 重试间隔时间的倍数status_forcelist[502, 503, 504] # 需要重试的HTTP状态码)adapter HTTPAdapter(max_retriesretry) # 创建HTTP适配器session.mount(http://, adapter) # 为HTTP协议挂载适配器session.mount(https://, adapter) # 为HTTPS协议挂载适配器try:# 发送GET请求以下载文件response session.get(housing_url, streamTrue)response.raise_for_status() # 如果响应状态码不是200则抛出异常# 将下载的文件内容写入本地文件with open(tgz_path, wb) as f:for chunk in response.iter_content(chunk_size8192):f.write(chunk)except requests.exceptions.RequestException as e:print(f下载文件时出错: {e})returntry:# 打开压缩文件housing_tgz tarfile.open(tgz_path)# 解压缩文件到指定目录housing_tgz.extractall(pathhousing_path)housing_tgz.close() # 关闭文件except tarfile.TarError as e:print(f解压文件时出错: {e})return# 调用函数以下载和解压文件
fetch_housing_data(HOUSING_URL,HOUSING_PATH)
现在当你调用fetch_housing_data()就会在工作空间创建一个datasets/housing目录下载housing.tgz文件解压出housing.csv。
然后使用 Pandas 加载数据。还是用一个小函数来加载数据
# 导入pandas库并给它一个别名pd这是pandas的常用别名。
import pandas as pd# 定义一个函数load_housing_data用于加载房地产数据。
# 函数接受一个可选参数housing_path默认值为HOUSING_PATH。
def load_housing_data(housing_pathHOUSING_PATH):# 使用os模块的path.join方法来拼接文件路径。# 这确保了代码在不同操作系统中的兼容性。csv_path os.path.join(housing_path, housing.csv)# 使用pandas的read_csv函数读取csv文件并返回DataFrame对象。return pd.read_csv(csv_path)
这个函数会返回一个包含所有数据的 Pandas DataFrame 对象。 2、快速查看数据结构
使用DataFrame的head()方法查看该数据集的前 5 行见图 3-2。
# 调用load_housing_data函数加载数据并将结果存储在变量housing中。
# 注意此处需要事先定义HOUSING_PATH变量指向包含housing.csv的目录。
housing load_housing_data()# 使用head()方法查看DataFrame的前五行数据这有助于快速了解数据的结构。
housing.head()
运行结果 图 3-2 数据集的前五行 每一行都表示一个街区。共有 10 个属性截图中可以看到 6 个经度、维度、房屋年龄中位数、总房间数、总卧室数、人口数、家庭数、收入中位数、房屋价值中位数、离大海距离。
info()方法可以快速查看数据的描述特别是总行数、每个属性的类型和非空值的数量。
# 使用info()方法打印DataFrame housing 的概览信息。
# 这个方法会显示每列的数据类型、非空值的数量以及内存使用情况。
# 这有助于快速检查数据集的基本信息和数据完整性。
housing.info()
运行结果
class pandas.core.frame.DataFrame
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 10 columns):# Column Non-Null Count Dtype
--- ------ -------------- ----- 0 longitude 20640 non-null float641 latitude 20640 non-null float642 housing_median_age 20640 non-null float643 total_rooms 20640 non-null float644 total_bedrooms 20433 non-null float645 population 20640 non-null float646 households 20640 non-null float647 median_income 20640 non-null float648 median_house_value 20640 non-null float649 ocean_proximity 20640 non-null object
dtypes: float64(9), object(1)
memory usage: 1.6 MB
数据集中共有 20640 个实例按照机器学习的标准这个数据量很小但是非常适合入门。我们注意到总卧室数只有 20433 个非空值这意味着有 207 个街区缺少这个值。我们将在后面对它进行处理。
所有的属性都是数值的除了离大海距离这项。它的类型是对象因此可以包含任意 Python 对象但是因为该项是从 CSV 文件加载的所以必然是文本类型。在刚才查看数据前五项时你可能注意到那一列的值是重复的意味着它可能是一项表示类别的属性。可以使用value_counts()方法查看该项中都有哪些类别每个类别中都包含有多少个街区
# 使用value_counts()方法计算ocean_proximity列中各个唯一值的出现次数。
# ocean_proximity列可能包含描述房产与海洋接近程度的分类标签。
# 这个方法会返回一个Series其中索引是ocean_proximity列中的唯一值
# 而数据是这些值在列中出现的次数。
# 这有助于了解数据集中房产与海洋接近程度的分布情况。
housing[ocean_proximity].value_counts()
运行结果
ocean_proximity
1H OCEAN 9136
INLAND 6551
NEAR OCEAN 2658
NEAR BAY 2290
ISLAND 5
Name: count, dtype: int64
再来看其它字段。describe()方法展示了数值属性的概括见图 3-3。
# 使用describe()方法生成housing DataFrame的描述性统计信息。
# 这个方法会返回一个新的DataFrame其中包含了数值型列的统计摘要。
# 描述性统计包括
# - count: 非空值的数量
# - mean: 平均值
# - std: 标准差衡量数据的离散程度
# - min: 最小值
# - 25%: 第一四分位数即所有值中位置在25%的值
# - 50%: 中位数即所有值中位置在50%的值
# - 75%: 第三四分位数即所有值中位置在75%的值
# - max: 最大值
# 这有助于快速了解数据集的分布特征和中心趋势。
housing.describe()
运行结果 图 3-3 每个数值属性的概括 count、mean、min和max几行的意思很明显了。注意空值被忽略了所以卧室总数是 20433 而不是 20640。std是标准差揭示数值的分散度。25%、50%、75% 展示了对应的分位数每个分位数指明小于这个值且指定分组的百分比。例如25% 的街区的房屋年龄中位数小于 18而 50% 的小于 2975% 的小于 37。这些值通常称为第 25 个百分位数或第一个四分位数中位数第 75 个百分位数第三个四分位数。
另一种快速了解数据类型的方法是画出每个数值属性的柱状图。柱状图的纵轴展示了特定范围的实例的个数。你还可以一次给一个属性画图或对完整数据集调用hist()方法后者会画出每个数值属性的柱状图见图 3-4。例如你可以看到略微超过 800 个街区的median_house_value值差不多等于 500000 美元。
# 设置Matplotlib的显示模式使得图表可以在Jupyter Notebook中内联显示。
%matplotlib inline# 导入matplotlib.pyplot模块并给它一个别名plt这是matplotlib.pyplot的常用别名。
import matplotlib.pyplot as plt# 使用DataFrame的hist()方法绘制housing中所有数值型列的直方图。
# bins参数设置为50表示每个直方图的柱子数量。
# figsize参数设置为(20,15)表示图表的宽度和高度英寸。
housing.hist(bins50, figsize(20,15))# 假设save_fig是一个自定义函数用于保存图表为图片文件。
# 这里调用save_fig函数将当前图表保存为名为attribute_histogram_plots的图片。
# 请注意save_fig函数需要事先定义。
plt.savefig(images/attribute_histogram_plots.png,bbox_inchestight)# 使用plt.show()显示图表。在Jupyter Notebook中这通常不是必需的因为图表已经内联显示。
# 但在其他环境中调用plt.show()可以弹出一个窗口显示图表。
plt.show()
运行结果 图 3-4 每个数值属性的柱状图 注hist()方法依赖于 Matplotlib后者依赖于用户指定的图形后端以打印到屏幕上。因此在画图之前你要指定 Matplotlib 要使用的后端。最简单的方法是使用 Jupyter 的魔术命令%matplotlib inline。它会告诉 Jupyter 设定好 Matplotlib以使用 Jupyter 自己的后端。绘图就会在笔记本中渲染了。注意在 Jupyter 中调用show()不是必要的因为代码框执行后 Jupyter 会自动展示图像。 注意柱状图中的一些点 首先收入中位数貌似不是美元USD。与数据采集团队交流之后你被告知数据是经过缩放调整的过高收入中位数的会变为 15实际为 15.0001过低的会变为 5实际为 0.4999。在机器学习中对数据进行预处理很正常这不一定是个问题但你要明白数据是如何计算出来的。 房屋年龄中位数和房屋价值中位数也被设了上限。后者可能是个严重的问题因为它是你的目标属性你的标签。你的机器学习算法可能学习到价格不会超出这个界限。你需要与下游团队核实这是否会成为问题。如果他们告诉你他们需要明确的预测值即使超过 500000 美元你则有两个选项 对于设了上限的标签重新收集合适的标签将这些街区从训练集移除也从测试集移除因为若房价超出 500000 美元你的系统就会被差评。 这些属性值有不同的量度。我们会在本章后面讨论特征缩放。 最后许多柱状图的尾巴很长相较于左边它们在中位数的右边延伸过远。对于某些机器学习算法这会使检测规律变得更难些。我们会在后面尝试变换处理这些属性使其变为正态分布。 希望你现在对要处理的数据有一定了解了。 3、创建测试集
在这个阶段就分割数据听起来很奇怪。毕竟你只是简单快速地查看了数据而已你需要再仔细调查下数据以决定使用什么算法。这么想是对的但是人类的大脑是一个神奇的发现规律的系统这意味着大脑非常容易发生过拟合如果你查看了测试集就会不经意地按照测试集中的规律来选择某个特定的机器学习模型。再当你使用测试集来评估误差率时就会导致评估过于乐观而实际部署的系统表现就会差。这称为数据透视偏差。
理论上创建测试集很简单只要随机挑选一些实例一般是数据集的 20%放到一边
# 导入numpy库并给它一个别名np这是numpy的常用别名。
import numpy as np# 这是一个自定义函数用于将数据集分割为训练集和测试集。
# 它模拟了sklearn库中的train_test_split()函数的功能仅用于示例说明。
def split_train_test(data, test_ratio):# 使用numpy的random.permutation函数生成一个随机排列的索引数组。# 这个数组的长度与输入数据集的长度相同。shuffled_indices np.random.permutation(len(data))# 计算测试集的大小基于输入的测试集比例test_ratio和数据集的总长度。test_set_size int(len(data) * test_ratio)# 根据计算出的测试集大小从随机排列的索引中选取前test_set_size个索引作为测试集索引。test_indices shuffled_indices[:test_set_size]# 剩余的索引将用作训练集的索引。train_indices shuffled_indices[test_set_size:]# 使用pandas的iloc函数根据索引从原始数据集中选取行分别创建训练集和测试集。# 返回两个数据集一个是训练集另一个是测试集。return data.iloc[train_indices], data.iloc[test_indices]
然后可以像下面这样使用这个函数
# 调用自定义的split_train_test函数传入整个数据集housing和测试集比例0.2
# 即将数据集的20%作为测试集剩余的80%作为训练集。
train_set, test_set split_train_test(housing, 0.2)# 打印训练集和测试集的大小以确认数据集已经被正确分割。
# len函数返回DataFrame的行数即数据点的数量。
# 这有助于验证我们的分割函数是否按预期工作。
print(len(train_set), train , len(test_set), test)
运行结果
16512 train 4128 test
这个方法可行但是并不完美如果再次运行程序就会产生一个不同的测试集多次运行之后你或你的机器学习算法就会得到整个数据集这是需要避免的。
解决的办法之一是保存第一次运行得到的测试集并在随后的过程加载。另一种方法是在调用np.random.permutation()之前设置随机数生成器的种子比如np.random.seed(42)以产生总是相同的洗牌指数shuffled indices。
# 导入numpy库并给它一个别名np这是numpy的常用别名。
import numpy as np# 使用numpy的random模块中的seed函数设置随机数生成器的种子。
# 这里的种子值是42这是一个常用的种子值因为42在流行文化中被认为是一个“幸运数字”。
# 通过设置种子可以确保每次运行代码时生成的随机数序列是相同的。
# 这有助于结果的可重复性特别是在需要多次运行实验或测试的情况下。
np.random.seed(42)
但是如果数据集更新这两个方法都会失效。一个通常的解决办法是使用每个实例的 ID 来判定这个实例是否应该放入测试集假设每个实例都有唯一并且不变的 ID。例如你可以计算出每个实例 ID 的哈希值只保留其最后一个字节如果该值小于等于 51约为 256 的 20%就将其放入测试集。这样可以保证在多次运行中测试集保持不变即使更新了数据集。新的测试集会包含新实例中的 20%但不会有之前位于训练集的实例。下面是一种可用的方法
from zlib import crc32
import numpy as np
import pandas as pd# 导入zlib库中的crc32函数用于生成数据的循环冗余校验码。
# CRC32是一种广泛使用的哈希函数可以用于检测数据的完整性。# 定义一个辅助函数test_set_check用于确定一个给定的标识符是否应该属于测试集。
# 这个函数接收两个参数identifier数据点的标识符和test_ratio测试集的比例。
# 它使用crc32函数生成标识符的哈希值并将其与测试集比例对应的数值进行比较
# 以确定数据点是否应该被分配到测试集中。
# 这里使用了位运算符 0xffffffff来确保crc32的结果是一个32位的无符号整数。
# 如果哈希值小于测试集比例乘以2的32次方那么这个数据点将被分配到测试集。
def test_set_check(identifier, test_ratio):return crc32(np.int64(identifier)) 0xffffffff test_ratio * 2**32# 定义一个函数split_train_test_by_id用于根据数据点的标识符来分割数据集。
# 这个函数接收三个参数data原始数据集test_ratio测试集的比例和id_column包含标识符的列名。
# 函数首先从数据集中提取id_column列的值。
# 然后使用apply函数和lambda表达式调用test_set_check函数为每个数据点生成一个布尔值
# 表示该数据点是否属于测试集。
# 最后使用loc函数和布尔索引来分别选取不属于测试集的数据点和属于测试集的数据点
# 返回训练集和测试集的DataFrame。
def split_train_test_by_id(data, test_ratio, id_column):ids data[id_column]in_test_set ids.apply(lambda id_: test_set_check(id_, test_ratio))return data.loc[~in_test_set], data.loc[in_test_set]
不过房产数据集没有 ID 这一列。最简单的方法是使用行索引作为 ID
# 使用pandas的reset_index方法重置housing DataFrame的索引
# 并将原来的索引添加为一个新的列index。
# 这是因为split_train_test_by_id函数需要一个标识符列来决定哪些数据点属于测试集。
# 通过添加索引作为列我们可以确保每个数据点都是唯一的并且可以用作标识符。
housing_with_id housing.reset_index() # adds an index column# 调用split_train_test_by_id函数传入重置索引后的housing_with_id DataFrame
# 测试集比例设置为0.2意味着20%的数据将被分配到测试集其余的80%将分配到训练集。
# 指定index作为标识符列因为我们刚刚添加了索引作为列。
# 这个函数将基于索引列的值使用crc32哈希函数和测试集比例来决定数据点的分配。
train_set, test_set split_train_test_by_id(housing_with_id, 0.2, index)
如果使用行索引作为唯一识别码你需要保证新数据都放到现有数据的尾部且没有行被删除。如果做不到则可以用最稳定的特征来创建唯一识别码。例如一个区的维度和经度在几百万年之内是不变的所以可以将两者结合成一个 ID
# 为housing_with_id DataFrame创建一个新的列id用于作为数据点的唯一标识符。
# 这里使用了一个简单的方法来生成标识符将经度值乘以1000后加上纬度值。
# 这种方法可以确保每个数据点的标识符都是唯一的因为经度和纬度的组合是唯一的。
# 请注意这种方法可能不适用于所有情况特别是在经度和纬度的范围重叠时。
# 但它在这里用于示例以展示如何创建一个基于地理位置的唯一标识符。
housing_with_id[id] housing[longitude] * 1000 housing[latitude]# 使用split_train_test_by_id函数传入housing_with_id DataFrame测试集比例为0.2
# 并指定新创建的id列作为标识符列。
# 这个函数将基于id列的值使用crc32哈希函数和测试集比例来随机分配数据点到训练集或测试集。
# 通过这种方式我们确保了数据集的分割是随机且可重复的有助于公平评估模型性能。
train_set, test_set split_train_test_by_id(housing_with_id, 0.2, id)
# 使用pandas的head()方法查看DataFrame test_set 的前五行数据。
# 这是一个快速检查数据集内容的方法可以帮助我们了解测试集的基本情况
# 比如数据的格式、列的名称以及前几个数据点的值。
# 这对于验证数据集是否正确分割以及初步了解数据特征非常有用。
test_set.head()
运行结果 Scikit-Learn 提供了一些函数可以用多种方式将数据集分割成多个子集。最简单的函数是train_test_split它的作用和之前的函数split_train_test很像并带有其它一些功能。首先它有一个random_state参数可以设定前面讲过的随机生成器种子第二你可以将种子传递给多个行数相同的数据集可以在相同的索引上分割数据集这个功能非常有用比如你的标签值是放在另一个DataFrame里的
# 从sklearn的model_selection模块导入train_test_split函数。
# 这个函数是Scikit-learn库提供的一个标准工具用于将数据集分割为训练集和测试集。
from sklearn.model_selection import train_test_split# 使用train_test_split函数将原始数据集housing分割为训练集和测试集。
# test_size0.2参数指定了测试集占原始数据集的比例即20%。
# random_state42参数用于确保每次分割时都能产生相同的随机结果
# 这有助于实验的可重复性特别是在进行多次实验或分享实验结果时。
train_set, test_set train_test_split(housing, test_size0.2, random_state42)
# 使用pandas的head()方法查看DataFrame test_set 的前五行数据。
# 这是一个快速检查数据集内容的方法可以帮助我们了解测试集的基本情况
# 比如数据的格式、列的名称以及前几个数据点的值。
# 这对于验证数据集是否正确分割以及初步了解数据特征非常有用。
test_set.head()
运行结果 目前为止我们采用的都是纯随机的取样方法。当你的数据集很大时尤其是和属性数相比这通常可行但如果数据集不大就会有采样偏差的风险。当一个调查公司想要对 1000 个人进行调查它们不是在电话亭里随机选 1000 个人出来。调查公司要保证这 1000 个人对人群整体有代表性。例如美国人口的 51.3% 是女性48.7% 是男性。所以在美国严谨的调查需要保证样本也是这个比例513 名女性487 名男性。这称作分层采样stratified sampling将人群分成均匀的子分组称为分层从每个分层去取合适数量的实例以保证测试集对总人数有代表性。如果调查公司采用纯随机采样会有 12% 的概率导致采样偏差女性人数少于 49%或多于 54%。不管发生那种情况调查结果都会严重偏差。
假设专家告诉你收入中位数是预测房价中位数非常重要的属性。你可能想要保证测试集可以代表整体数据集中的多种收入分类。因为收入中位数是一个连续的数值属性你首先需要创建一个收入类别属性。再仔细地看一下收入中位数的柱状图图 3-5处理后的图见图 3-6
# 使用pandas的hist()方法绘制housing DataFrame中median_income列的直方图。
# 直方图是一种用于展示数值型数据分布的图形表示方法它将数据分成多个“箱子”
# 并计算每个箱子中数据点的数量然后以柱状图的形式展示这些数量。
# 这种方法有助于我们快速了解数据的分布情况比如是否存在偏态分布或异常值。
# 绘制直方图是数据分析中的一个基本步骤可以帮助我们做出更好的数据预处理决策。
housing[median_income].hist()
运行结果 图 3-5 收入分类的柱状图 对该图是的收入中位数处理过后的图过程如下
大多数的收入中位数的值聚集在 2-5万美元但是一些收入中位数会超过 6。数据集中的每个分层都要有足够的实例位于你的数据中这点很重要。否则对分层重要性的评估就会有偏差。这意味着你不能有过多的分层且每个分层都要足够大。后面的代码通过将收入中位数除以 1.5以限制收入分类的数量创建了一个收入类别属性用ceil对值舍入以产生离散的分类然后将所有大于 5 的分类归入到分类 5
# 将median_income列的值除以1.5并对结果向上取整创建一个新的列income_cat。
# 这样做的目的是将收入分为更少的类别以简化模型处理。
# 向上取整确保了即使收入值在1.5的倍数之间也能被正确地分类到下一个整数类别。
housing[income_cat] np.ceil(housing[median_income] / 1.5)# 使用where方法将income_cat列中大于或等于5的值设置为5。
# where方法的第一个参数是一个条件表达式如果为True则使用第二个参数的值
# 如果为False则使用第三个参数的值。在这里我们使用5.0作为上限值。
# 这样做可以防止某些极端高收入值对模型造成过大的影响有助于数据的正则化。
housing[income_cat].where(housing[income_cat] 5, 5.0, inplaceTrue)
# 使用pandas的cut函数将median_income列的值切分为不同的区间并创建一个新的列income_cat。
# 这个操作被称为分箱binning是一种将连续数值数据离散化的方法。
# 这里定义了5个区间的边界[0., 1.5, 3.0, 4.5, 6., np.inf]表示
# - 收入在0以下不包括0的数据点将被分到第一个箱。
# - 收入在0到1.5之间的数据点将被分到第二个箱以此类推。
# - 收入大于或等于6的数据点将被分到第五个箱。
# 同时定义了与这些区间相对应的标签[1, 2, 3, 4, 5]。
# 这有助于将连续的收入数据转换为离散的类别可能用于简化模型或处理异常值。
housing[income_cat] pd.cut(housing[median_income],bins[0., 1.5, 3.0, 4.5, 6., np.inf],labels[1, 2, 3, 4, 5])# 使用value_counts方法计算income_cat列中各个类别的出现次数。
# 这有助于我们了解不同收入类别的分布情况对于数据探索和可视化非常有用。
# 例如它可以帮助我们识别是否有某个收入类别的样本数量特别多或特别少
# 这可能影响模型训练和预测的准确性。
housing[income_cat].value_counts()
运行结果
income_cat
3 7236
2 6581
4 3639
5 2362
1 822
Name: count, dtype: int64
# 使用pandas的hist()方法绘制housing DataFrame中新创建的income_cat列的直方图。
# 由于income_cat是一个分箱后的类别数据直方图的每个柱子将代表一个特定的收入区间。
# 这个直方图有助于我们可视化不同收入类别的分布情况比如哪些类别的样本数量更多
# 以及是否存在某些类别的样本数量异常少这可能需要进一步的数据探索或处理。
# 绘制直方图是数据分析中的一个重要步骤它可以帮助我们更好地理解数据的特征和分布。
housing[income_cat].hist()
运行结果 图 3-5 收入分类的柱状图处理后的图 现在就可以根据收入分类进行分层采样。你可以使用 Scikit-Learn 的StratifiedShuffleSplit类
# 从sklearn的model_selection模块导入StratifiedShuffleSplit类。
# StratifiedShuffleSplit用于分层抽样确保训练集和测试集中各类别的比例
# 与原始数据集中的相应比例相同。这在类别不平衡的情况下特别有用。
from sklearn.model_selection import StratifiedShuffleSplit# 初始化StratifiedShuffleSplit对象设置n_splits1表示只生成一个训练集和测试集的分割
# test_size0.2表示测试集占原始数据集的20%random_state42确保结果的可重复性。
split StratifiedShuffleSplit(n_splits1, test_size0.2, random_state42)# 使用split对象的split方法对housing数据集进行分层抽样。
# 该方法接受两个参数数据集housing和用于分层的列income_cat。
# 迭代生成的索引对(train_index, test_index)分别代表训练集和测试集的行索引。
for train_index, test_index in split.split(housing, housing[income_cat]):# 使用.loc方法和生成的索引创建训练集和测试集的DataFrame。# strat_train_set包含训练数据strat_test_set包含测试数据。# 这种分层抽样方法有助于保持数据的多样性提高模型评估的准确性。strat_train_set housing.loc[train_index]strat_test_set housing.loc[test_index]
检查下结果是否符合预期。你可以在完整的房产数据集中查看收入分类比例
# 使用pandas的value_counts方法计算strat_test_set中income_cat列各个类别的频率分布。
# 这个方法返回一个Series其中索引是income_cat列中的唯一值数据是这些值在列中出现的次数。
# 然后我们将这个频率分布除以strat_test_set的总行数即len(strat_test_set)
# 以得到每个类别在测试集中的相对频率。# 这种计算方法可以帮助我们了解在分层抽样后不同收入类别在测试集中的分布情况。
# 通过分析这些相对频率我们可以评估测试集是否在各个类别上保持了与原始数据集相似的比例
# 这对于确保模型评估的公正性和准确性是非常重要的。# 最终结果是一个表示每个收入类别相对频率的Series可以用于进一步的数据分析或可视化。
strat_test_set[income_cat].value_counts() / len(strat_test_set)
运行结果
income_cat
3 0.350533
2 0.318798
4 0.176357
5 0.114341
1 0.039971
Name: count, dtype: float64
比较
# 使用pandas的value_counts方法计算housing中income_cat列各个类别的出现次数
# 并返回一个Series其中索引是income_cat列中的唯一值数据是这些值在列中出现的次数。
# 这个方法有助于了解不同收入类别在数据集中的分布情况。# 然后将得到的每个类别的出现次数除以housing的总行数即len(housing)
# 以计算每个收入类别的相对频率。相对频率是一个比例值表示每个类别在数据集中所占的比例。# 这种计算方法可以帮助我们快速了解数据集中不同收入类别的分布情况
# 并为进一步的数据分析和模型训练提供有价值的信息。
# 例如如果某个收入类别的频率特别高或特别低可能需要在模型训练中特别考虑。housing[income_cat].value_counts() / len(housing)
运行结果
income_cat
3 0.350581
2 0.318847
4 0.176308
5 0.114438
1 0.039826
Name: count, dtype: float64
使用相似的代码还可以测量测试集中收入分类的比例。图 3-7 对比了总数据集、分层采样的测试集、纯随机采样测试集的收入分类比例。可以看到分层采样测试集的收入分类比例与总数据集几乎相同而随机采样数据集偏差严重。
# 定义一个函数income_cat_proportions用于计算数据集中income_cat列各个类别的相对频率。
# 这个函数接受一个DataFrame data作为参数使用value_counts方法计算income_cat列中每个类别的出现次数
# 然后除以data的总行数以得到每个类别的相对频率。
def income_cat_proportions(data):return data[income_cat].value_counts() / len(data)# 使用sklearn的train_test_split函数将housing数据集分割为训练集和测试集。
# 测试集大小设置为20%test_size0.2并设置随机状态为42以确保结果的可重复性。
train_set, test_set train_test_split(housing, test_size0.2, random_state42)# 创建一个DataFrame compare_props用于存储不同数据集中income_cat类别的相对频率
# 并进行比较。DataFrame包含三列Overall表示原始数据集的类别频率
# Stratified表示分层抽样测试集的类别频率Random表示随机抽样测试集的类别频率。
compare_props pd.DataFrame({Overall: income_cat_proportions(housing),Stratified: income_cat_proportions(strat_test_set),Random: income_cat_proportions(test_set),
}).sort_index()# 计算Random和Stratified列相对于Overall列的百分比误差。
# 百分比误差是通过计算两个频率的比值再减去100得到的。
# 这有助于评估分层抽样和随机抽样方法在保持原始数据集分布上的效果。
compare_props[Rand. %error] 100 * compare_props[Random] / compare_props[Overall] - 100
compare_props[Strat. %error] 100 * compare_props[Stratified] / compare_props[Overall] - 100compare_props
运行结果 图 3-7 分层采样和纯随机采样的样本偏差比较 现在你需要删除income_cat属性使数据回到初始状态
# 遍历包含strat_train_set和strat_test_set的元组这两个变量分别代表经过分层抽样得到的训练集和测试集。
for set_ in (strat_train_set, strat_test_set):# 对每个数据集set_使用pandas的drop方法删除income_cat列。# income_cat是我们之前为了分层抽样而创建的辅助列可能不再需要用于后续的模型训练。# axis1参数指定操作是针对列而不是行inplaceTrue参数表示直接在原始DataFrame上进行修改而不是创建一个新的DataFrame。# 删除income_cat列可以简化数据集避免在模型训练过程中不小心使用到这个辅助特征。set_.drop(income_cat, axis1, inplaceTrue)
我们用了大量时间来生成测试集的原因是测试集通常被忽略但实际是机器学习非常重要的一部分。还有生成测试集过程中的许多思路对于后面的交叉验证讨论是非常有帮助的。接下来进入下一阶段数据探索。 附录
一、一些知识点
1、流水线 一系列的数据处理组件被称为数据流水线。流水线在机器学习系统中很常见因为有许多数据要处理和转换。 组件通常是异步运行的。每个组件吸纳进大量数据进行处理然后将数据传输到另一个数据容器中而后流水线中的另一个组件收入这个数据然后输出这个过程依次进行下去。每个组件都是独立的组件间的接口只是数据容器。这样可以让系统更便于理解记住数据流的图不同的项目组可以关注于不同的组件。进而如果一个组件失效了下游的组件使用失效组件最后生产的数据通常可以正常运行一段时间。这样就使整个架构相当健壮。 另一方面如果没有监控失效的组件会在不被注意的情况下运行一段时间。数据会受到污染整个系统的性能就会下降。 二、源码工程
https://github.com/XANkui/PythonMachineLearnIntermediateLevel 三、该案例的环境 package 信息如下
Package Version ------------------------- -------------- anyio 4.4.0 argon2-cffi 23.1.0 argon2-cffi-bindings 21.2.0 arrow 1.3.0 asttokens 2.4.1 async-lru 2.0.4 attrs 23.2.0 Babel 2.15.0 beautifulsoup4 4.12.3 bleach 6.1.0 certifi 2024.7.4 cffi 1.16.0 charset-normalizer 3.3.2 colorama 0.4.6 comm 0.2.2 contourpy 1.2.1 cycler 0.12.1 debugpy 1.8.2 decorator 5.1.1 defusedxml 0.7.1 executing 2.0.1 fastjsonschema 2.20.0 fonttools 4.53.1 fqdn 1.5.1 h11 0.14.0 httpcore 1.0.5 httpx 0.27.0 idna 3.7 ipykernel 6.29.5 ipython 8.26.0 ipywidgets 8.1.3 isoduration 20.11.0 jedi 0.19.1 Jinja2 3.1.4 joblib 1.4.2 json5 0.9.25 jsonpointer 3.0.0 jsonschema 4.23.0 jsonschema-specifications 2023.12.1 jupyter 1.0.0 jupyter_client 8.6.2 jupyter-console 6.6.3 jupyter_core 5.7.2 jupyter-events 0.10.0 jupyter-lsp 2.2.5 jupyter_server 2.14.2 jupyter_server_terminals 0.5.3 jupyterlab 4.2.4 jupyterlab_pygments 0.3.0 jupyterlab_server 2.27.3 jupyterlab_widgets 3.0.11 kiwisolver 1.4.5 MarkupSafe 2.1.5 matplotlib 3.9.1 matplotlib-inline 0.1.7 mistune 3.0.2 nbclient 0.10.0 nbconvert 7.16.4 nbformat 5.10.4 nest-asyncio 1.6.0 notebook 7.2.1 notebook_shim 0.2.4 numpy 2.0.1 overrides 7.7.0 packaging 24.1 pandas 2.2.2 pandocfilters 1.5.1 parso 0.8.4 pillow 10.4.0 pip 24.1.2 platformdirs 4.2.2 prometheus_client 0.20.0 prompt_toolkit 3.0.47 psutil 6.0.0 pure_eval 0.2.3 pycparser 2.22 Pygments 2.18.0 pyparsing 3.1.2 python-dateutil 2.9.0.post0 python-json-logger 2.0.7 pytz 2024.1 pywin32 306 pywinpty 2.0.13 PyYAML 6.0.1 pyzmq 26.0.3 qtconsole 5.5.2 QtPy 2.4.1 referencing 0.35.1 requests 2.32.3 rfc3339-validator 0.1.4 rfc3986-validator 0.1.1 rpds-py 0.19.1 scikit-learn 1.5.1 scipy 1.14.0 Send2Trash 1.8.3 setuptools 70.1.1 six 1.16.0 sniffio 1.3.1 soupsieve 2.5 stack-data 0.6.3 terminado 0.18.1 threadpoolctl 3.5.0 tinycss2 1.3.0 tornado 6.4.1 traitlets 5.14.3 types-python-dateutil 2.9.0.20240316 typing_extensions 4.12.2 tzdata 2024.1 uri-template 1.3.0 urllib3 2.2.2 wcwidth 0.2.13 webcolors 24.6.0 webencodings 0.5.1 websocket-client 1.8.0 wheel 0.43.0 widgetsnbextension 4.0.11