通过提高基数水平来提升电源 BI 性能
你知道最优数据模型大小的最大“敌人”是谁吗?了解基数,并了解如何通过应用一些简单的技术来提高基数水平,从而提高报告性能
性能,性能,性能…我们一直在努力实现它(或者至少我们应该努力),但在调整 Power BI 报告时,有许多事情需要记住。优化过程的第一步是确保您的数据模型大小处于最佳状态——这意味着,尽可能减小数据模型大小!这将使 VertiPaq 的存储引擎在为您的报告检索数据时以更高效的方式工作。
当您致力于优化数据模型大小时,您知道谁是您的头号“敌人”吗?!列数据类型,对吗?文本列将比数值类型的列消耗更多的内存。那只说对了一半!
最大的对手叫基数!
在我们解释什么是基数,以及它为什么对减少数据模型大小如此重要之前,让我们首先讨论 VertiPaq 存储数据的方式,一旦您将表的存储模式设置为 Import ( DirectQuery 存储模式超出了本文的范围,因此从现在开始,本文中所有关于基数的内容都专门指导入存储模式)。
一旦将存储模式设置为 Import,VertiPaq 将从列中扫描样本行,并基于特定列中的数据(不要忘记,VertiPaq 是一个列数据库,这意味着每一列都有自己的结构,并且在物理上与其他列分离),它将对数据应用某种压缩算法。
有三种不同的编码类型:
- 值编码 —仅适用于整数数据类型
- 哈希编码 —适用于所有非整数数据类型,在某些情况下也适用于整数数据类型
- RLE(游程长度编码) —在 哈希编码后出现 ,作为一个附加的压缩步骤,在这些场景中,当列中的数据以 VertiPaq“认为”的方式排序时,可以获得比仅使用哈希算法更好的压缩率
同样,在我们解释基数的概念在整个故事中的位置之前,让我们说明一下哈希算法在后台是如何工作的:
作者插图
正如您所看到的,VertiPaq 将创建一个由来自列的不同值组成的 字典 ,为每个值分配一个位图索引,然后存储这个索引值而不是“真实”值——简单地说,它将把数字 1 存储为指向“Book”值的指针,将数字 2 存储为“Shirt”值,依此类推。就像在后台创建一个虚拟维度表一样!
现在,假设该列中没有两个不同的值,而是如下所示:
作者插图
我们的“dimension”现在将有 10 行,因此您可能会认为这个字典表的大小将比只有两个不同值的情况大得多。
现在,你可能会问自己:好吧,这很好,但是这个故事和基数有什么共同之处呢?
基数表示列中唯一值的数量。
在我们的第一个例子中,基数为 2,而在第二个例子中,基数等于 10。
基数是影响列大小的首要因素。别忘了,列的大小不仅受其中数据大小的影响。你应该考虑字典的大小,和层次结构的大小一样。对于具有高基数(大量不同的值)并且不是整数数据类型的列,字典的大小明显大于数据本身的大小。
让我给你看一个例子。我将使用 DAX Studio 来分析数据模型大小背后的不同指标。一旦我在 DAX Studio 中打开“高级”选项卡并选择“查看指标”,就会出现一系列不同的数字,以了解我的数据模型的特定部分有多大:
作者图片
让我们快速重申一下上图中的关键观点。在 Chats 表中,datetmStartUTC 列是日期/时间数据类型,精度达到第二级,它有将近 900 万个不同的值!它的大小约为。455 MBs —这个数字不仅包括数据大小(26 MBs),还包括字典大小和层次结构大小。您可以看到 VertiPaq 应用了散列算法来压缩该列中的数据,但是最大的内存占用是字典大小(几乎 358 MBs)。
显然,这远远不是我们的数据模型的最佳条件。然而,我有好消息要告诉你…
有多种技术可以提高基数水平!
在以前的一篇文章中,我解释了如何通过应用一些更高级的方法来降低基数,比如使用除法和模运算将一个高基数的数字列拆分成两个低基数的列,每行节省一些位。我还向您展示了如何将日期/时间列分成两个单独的列——一个只包含日期部分,而另一个包含时间数据。
但是,这些技术需要在报表端做额外的工作,因为所有的度量都需要重写以反映数据模型结构的变化并返回正确的结果。因此,这些高级方法不是您在常规数据模型优化中需要应用的东西,它们更多的是一种“边缘”用例,当没有其他方法来减少数据模型的大小时,简单地说,当您处理非常大的数据集时!
但是,这并不意味着您不应该努力提高基数水平,即使对于更小更简单的数据模型也是如此。相反,这应该是 Power BI 开发过程中的一个常规部分。在本文中,我将向您展示两种简单的方法,它们可以显著降低列的基数,从而降低整个数据模型的大小。
通过更改数据类型来提高基数级别
老实说,您的用户多久需要分析一次二级数据?比如,我们在 09:35:36 或 11:22:48 有多少销售?我会说,毫无意义。在 98%的情况下,业务需求是每天都有可用的数据。也许在某些情况下,用户需要了解一天中的哪一段时间最“高效”:上午、下午或晚上……但是,我们仍然要关注大多数情况,在这些情况下,数据应该在每天的级别上进行细化。
作者图片
我已经将列的数据类型从日期/时间更改为日期,因为时间部分与报告目的无关。让我们在 DAX Studio 中刷新我们的指标:
作者图片
哦,哇哦!基数约为 900 万,而现在只有 1356(这是该列中不同天数)。但是,更重要的是,列的大小从 455 MB 降到了 7 MB!那是 huuuuge!看看字典的大小:VertiPaq 现在只处理 1356 个不同的值,而不是必须为 900 万个值构建一个字典,字典大小从 358 MBs 下降到 85 KBs!
另一个你可以应用的技巧是当你处理十进制数值的时候。将数据“按原样”导入 Power BI 的情况并不少见,而且,我不止一次看到有人导入小数点后精确到 5 位的十进制数字!我的意思是,真的有必要知道你的销售总额是 27.586.398,56891,还是显示 27.586.398,57 就可以了?
需要明确的是,在 Power BI 中格式化值以显示 2 个小数位不会影响数据模型的大小,这只是一个可视化的格式化选项,在后台,数据以小数位后 5 位数存储。
作者图片
现在,让我们在 DAX Studio 中检查这个表的指标。这是一个微不足道的数据模型大小,其中只有 151 个不同的值,但是您只能想象在数百万行的表上的差异:
作者图片
现在,我将转到超级查询编辑器,并将该列的类型更改为固定十进制数:
作者图片
我们现在已经将数值四舍五入到小数点后两位,所以让我们切换回 DAX Studio 并再次检查数字:
作者图片
即使在这个极小的数据集上,差别也是显而易见的!
使用汇总和分组提高基数水平
我想向您展示的另一项技术是如何利用汇总和分组的概念来提高基数级别,并使您的数据模型更具性能。
当您创建报表时,用户可能需要在比单个事务更高的粒度级别上理解不同的指标,例如,在特定日期售出了多少产品,在特定日期有多少客户注册,等等。这意味着您的大多数分析查询不需要针对单个事务,因为汇总的数据完全没问题。
如果我们总结聊天表的数据,让我们检查一下内存占用的差异。您可能还记得本文前一部分中的原始表,它占用 555 MBs:
作者图片
现在,如果我预先汇总数据,并按日期和/或产品对其进行分组,则编写以下 T-SQL:
SELECT CONVERT(DATE,datetmStartUTC) AS datetm
,productID
,COUNT(chatID) AS totalChats
FROM Chats
GROUP BY CONVERT(DATE,datetmStartUTC)
,productID
如果最常见的业务请求是分析每个日期和/或产品的聊天次数,这个查询将成功满足这些请求。
让我们检查一下这个总结表与原始表相比的大小:
作者图片
虽然原始表(即使 datetmStartUTC 列的基数级别有所提高)需要 105 MBs,但是聚合表只需要 217 KB!而且这张表可以回答大部分的经营分析问题。即使您需要包含额外的属性,例如客户数据,这仍然是检索数据的最佳方法。
还有更多!
即使您不能在数据源端创建汇总数据,您仍然可以通过利用 Power BI 中的聚合特性获得显著的性能提升。这是 Power BI 中最强大的特性之一,值得单独撰写一篇文章,甚至是一系列文章,比如 Phil Seamark 的 this,当我需要更深入地了解聚合及其在表格模型中的工作方式时,我总是会参考它。
结论
构建一个最优的数据模型不是一件容易的事情!有许多潜在的警告,有时很难避免通往“完美”模型的道路上的所有陷阱。然而,了解减少整体数据模型大小的重要性是调整 Power BI 解决方案的关键要求之一。
记住这一点,为了能够实现最佳的数据模型大小,您需要吸收基数的概念,作为决定列大小的主要因素。通过以正确的方式理解基数,以及 VertiPaq 存储和压缩数据的方式,您应该能够应用我们刚刚介绍的一些技术来提高列的基数级别,从而提高报告的整体性能!
感谢阅读!
成为会员,阅读 Medium 上的每一个故事!
用熊猫提升你的数据分析
你需要知道的关于熊猫的一切,通过代码示例开始提高你的生产力
文斯·拉塞尔在 Unsplash 上的照片
目录
- 为什么用熊猫?
- 入门安装和导入熊猫
- 熊猫数据帧读取文件并创建数据帧
- 检查数据帧
- 用熊猫操纵数据数据帧索引行和列用 loc 和 iloc 选择数据
- 处理缺失值 检测缺失值
删除缺失值 填充缺失值 - 可视化数据直方图
散点图 - 保存到文件
- 结论
无论你是在建立复杂的机器学习模型,还是只想在 Excel 电子表格中组织每月的预算,你都必须知道如何操作和分析数据。
虽然许多工具都可以完成这项工作,但今天我们将谈论其中最常用和最适合初学者的工具之一, 熊猫 。
为什么用熊猫?
Pandas 是一个开源的 Python 库,旨在处理数据分析和数据操作。援引官网,
“pandas 是一个快速、强大、灵活且易于使用的开源数据分析和操作工具,构建于 Python 编程语言之上。”
它建立在 NumPy (用于科学计算的 Python 库)之上,它有几个用于清理、分析和操作数据的函数,可以帮助您提取关于数据集的有价值的见解。Pandas 非常适合处理表格数据,如 SQL 表或 Excel 电子表格。
Pandas中的主要数据结构是一个名为 DataFrame 的二维表。要创建 DataFrame,您可以导入几种格式的数据,例如 CSV 、 XLSX 、 JSON 、 SQL 等等。通过几行代码,您可以添加、删除或编辑行/列中的数据,检查集合的统计数据,识别和处理缺失的条目,等等。
此外,如上所述, Pandas 应用广泛,对初学者友好,这意味着你会在网上找到很多关于它的内容,找到你的问题的答案应该不难。
入门指南
首先,我们需要安装 Pandas 并且有几种不同的环境可以运行它。如果你想直接在你的机器上运行它,你应该看一下 Anaconda ,这是一个针对科学计算的发行版,附带了数百个预安装的软件包。Anaconda 可以安装在 Windows、macOS 和 Linux 上。
然而,有一种更简单的方法通过你的浏览器开始使用熊猫,使用云中的 Jupyter 笔记本。例如,你可以使用 IBM Watson Studio 或者 Google Colab 。两者都可以免费使用,并且预装了几个 Python 包。
在本文中,我使用的是 Google Colab ,因为它开箱即用,不需要任何预先设置。
安装和导入熊猫
您需要在您的环境中编写以下命令来安装 Pandas,这取决于您的软件包管理器。
pip install pandas
或者
conda install pandas
请注意,在 Google Colab 中,我们不需要使用上面的代码,因为 Pandas 是预装的。
现在,我们需要导入熊猫,这样我们就可以在我们的 Jupyter 笔记本中使用它了。
import pandas as pd
我们通常将它作为快捷方式“as pd”导入,这样我们就不需要每次需要调用熊猫函数时都要写完整的单词。
熊猫数据框
安装并导入 Pandas 之后,让我们看看如何读取文件并创建一个 Pandas DataFrame 。在这篇文章中,我们将要处理的数据集是一个简化版的数据集,它是在一个关于房价的竞赛中提供的。
包含该数据集的文件是一个**。CSV** (逗号分隔值)。如果你想自己玩,你可以在这里找到它,在我的 Github 仓库里。
读取文件和创建数据帧
要将文件读入数据帧,我们只需在下面的函数中输入文件路径作为参数:
PATH = 'https://raw.githubusercontent.com/rmpbastos/data_sets/main/kaggle_housing/house_df.csv'df = pd.read_csv(PATH)
注意,我使用了函数 read_csv ,因为我们正在处理一个 csv 文件。如上所述,熊猫可以处理几种文件扩展名,你可以在这里查看。
上面的函数读取了 csv 文件,并自动从中创建了一个数据帧。但是如果你想从 Python Dict 、 List 、 NumPy Array 或者甚至从另一个 DataFrame 中创建一个 DataFrame,你可以使用下面的函数。
df = pd.DataFrame(mydict)
让我们检查一下我们刚刚创建的数据帧的类型。
type(df)
检查数据帧
在本文的其余部分,我们将使用上面提到的住房数据集。下一件我们应该做的事情是看看我们的数据框架。我们可以用函数 head 检查第一个 n 条目。如果没有提供 n ,我们将默认看到前 5 行。
df.head()
乍一看,一切正常。我们还可以使用函数 tail 检查集合的最后一个条目。
df.tail()
接下来,让我们使用 shape 属性来检查数据的维度。
df.shape
它返回一个包含行数和列数的元组。我们的数据帧有 1460 行和 16 列,或者说特征。
接下来,我们可以使用功能信息查看数据集的概要。
df.info()
它向我们展示了关于数据帧的有用信息,比如列名、非空值、数据类型和内存使用情况。从这个总结中,我们可以观察到一些列缺少值,这个主题我们将在后面看到。
以下函数将为我们提供一些关于数据集的描述性统计数据。
df.describe()
此函数显示每个要素的计数、平均值、中值、标准差、上下四分位数以及最小值和最大值。请注意,它只显示关于数字特性的数据(数据类型为 int 或 float 的列)。
在这个序列中,在进入下一节之前,让我再向您展示一个函数, value_counts 。
df['Neighborhood'].value_counts()
该函数返回包含每列唯一值数量的序列。它可以应用于整个数据帧,但是在上面的例子中,我们只将它应用于列“Neighborhood”。
在我们继续之前,让我总结一下数据集的每个特性,以便更好地理解。
- Id —每行的唯一标识(我们将使用它作为我们的索引)。
- LotArea —以平方英尺为单位的批量
- 街道 —道路通道类型
- 邻里 —房屋的物理位置
- 住宅风格 —居住风格
- 建造年份 —建造日期
- 中央空调 —中央空调
- BedroomAbvGr —地下室标高以上的卧室数量
- 壁炉 —壁炉数量
- 车库类型 —车库位置
- 车库建造年份
- 车库面积 —车库面积,平方英尺
- 公摊面积 —公摊面积,平方英尺
- 泳池质量控制 —泳池质量
- 栅栏 —栅栏质量
- 销售价格 —房价
我们的数据集包含不同类型的数据,如数值型、分类型、布尔型,但我们不会深入研究这些概念,因为它们超出了本文的范围。
现在,让我们开始操作我们的数据框架。
用熊猫操纵数据
熊猫为我们提供了几个处理数据的工具。在这一节中,我们将看到如何操作行和列,以及在表中定位和编辑值。让我们开始为我们的数据框架设置一个索引。
数据帧索引
在检查了我们的数据之后,我们注意到第一列( Id )对于每一行都有一个唯一的值。我们可以利用它,使用这个列作为我们的索引,代替我们设置 DataFrame 时默认创建的索引。
df.set_index('Id', inplace=True)df.index
当将参数就地设置为真时,数据帧将就地更新。否则,使用默认值 inplace = False ,将返回数据帧的副本。
如果您事先知道您将使用数据集中的一列作为索引,您可以在读取文件时设置它,如下所示。
df = pd.read_csv(PATH, index_col='Id')
让我们来看看设置索引后集合是什么样子的。
数据集现在看起来更干净了!继续,我们来谈谈行和列。
行和列
正如您已经注意到的,数据框是表格数据,包含行和列。在熊猫中,单列一栏可以叫做系列。我们可以很容易地检查这些列,并用下面的代码访问它们。
df.columns
df['LotArea'].head()
type(df['LotArea'])
请注意,我们的数据框架中的一列属于类型系列。
重命名列
用熊猫来重命名你的列真的很简单。例如,让我们将我们的特性 BedroomAbvGr ,并将其重命名为 Bedroom 。
df.rename(columns={'BedroomAbvGr': 'Bedroom'}, inplace=True)
您可以一次重命名多个列。在函数 rename 内,将所有“旧名称”和“新名称”作为键/值对添加到列字典中。
添加列
您可能想在数据框架中添加一列。让我们看看你怎么做。
我将借此机会向您展示我们如何创建数据帧的副本。让我们在副本中添加一列,这样就不会改变原始数据帧。
df_copy = df.copy()df_copy['Sold'] = 'N'
这是创建新列最简单的方法。请注意,我为本列中的所有条目分配了一个值**“N”**。
检查添加了新列的下图。
添加行
现在,假设您有另一个数据帧( df_to_append ),其中包含您想要添加到 df_copy 的 2 行。将行追加到数据帧末尾的一种方法是使用函数 append 。
data_to_append = {'LotArea': [9500, 15000],
'Steet': ['Pave', 'Gravel'],
'Neighborhood': ['Downtown', 'Downtown'],
'HouseStyle': ['2Story', '1Story'],
'YearBuilt': [2021, 2019],
'CentralAir': ['Y', 'N'],
'Bedroom': [5, 4],
'Fireplaces': [1, 0],
'GarageType': ['Attchd', 'Attchd'],
'GarageYrBlt': [2021, 2019],
'GarageArea': [300, 250],
'PoolArea': [0, 0],
'PoolQC': ['G', 'G'],
'Fence': ['G', 'G'],
'SalePrice': [250000, 195000],
'Sold': ['Y', 'Y']}df_to_append = pd.DataFrame(data_to_append)df_to_append
现在,让我们将上面的 2 行数据帧添加到 df_copy 中。
df_copy = df_copy.append(df_to_append, ignore_index=True)
检查 df_copy 的最后一个条目,我们有:
df_copy.tail(3)
移除行和列
为了消除数据帧的行和列,我们可以使用函数 drop 。假设我们想要删除最后一行和列“Fence”。查看下面的代码。
df_copy.drop(labels=1461, axis=0, inplace=True)
上面的函数删除了最后一行(Id 为 Id 为 1461 的行)。您也可以一次删除几行,将索引列表作为参数传递。
axis=0,这是默认值,意味着您正在删除一行。对于列,我们需要指定 axis=1,如下所示。
df_copy.drop(labels='Fence', axis=1, inplace=True)
使用 loc 和 iloc 选择数据
选择数据的最简单方法之一是使用 loc 和 iloc 方法。
loc 用于通过标签/索引或基于布尔数组访问行和列。假设我们想要访问索引= 1000 的行。
df.loc[1000]
上面的方法选择了 index = 1000 的行,并显示了该行中包含的所有数据。我们还可以选择想要可视化的列。
df.loc[1000, ['LotArea', 'SalePrice']]
现在,让我们看看如何将条件应用于 loc 。假设我们想要选择销售价格至少为 600,000 美元的所有房屋。
df.loc[df['SalePrice'] >= 600000]
通过一行简单的代码,我们只找到了 4 栋价值超过 60 万美元的房子。
iloc 用于根据整数位置或布尔数组选择数据。例如,如果我们想要选择包含在第一行和第一列中的数据,我们有以下内容:
df.iloc[0,0]
显示的值是 ID 为 1 的行的 LotArea 。记住整数位置是从零开始的。
我们也可以选择一整行。在这种情况下,该行位于位置 10
df.iloc[10,:]
我们可以选择整个列,例如最后一列。
df.iloc[:,-1]
我们也可以选择多行和多列,如下所示。
df.iloc[8:12, 2:5]
处理缺失值
关于如何处理缺失值,可以谈很多。请记住,这里的目标不是深入主题,而是向您展示由 Pandas 提供的处理缺失值的工具。
检测缺失值
这里的第一步是用函数 isnull 找到数据集中缺失的值。该函数将返回一个与原始数据帧大小相同的对象,包含集合中每个元素的布尔值。它将认为为真值,如 None 和 NumPy。南。您可以通过下面的代码行找到它们。
df.isnull()
注意,处理上面返回的数据可能很麻烦。如果您正在处理一个非常小的数据集,您应该没问题,但是对于成千上万的行和几个列,就像在我们的例子中,我们只能添加每列缺失值的数量,如下所示。
df.isnull().sum()
现在好多了!我们可以很容易地看到每一列中缺失值的数量。我们还可以意识到大多数专栏都是完整的,这很好。 sum 函数将所有由返回为 True 的值相加为 null ,因为它们等同于 1 。假值相当于 0 。
我们还可以检查每列缺失值的比例:
df.isnull().sum() / df.shape[0]
让我们更进一步,使用 Python 只获取缺少值的列,并显示缺少值的百分比。
for column in df.columns:
if df[column].isnull().sum() > 0:
print(column, ': {:.2%}'.format(df[column].isnull().sum() /
df[column].shape[0]))
删除丢失的值
检测到丢失的值后,我们需要决定如何处理它们。在这里,我将向您展示如何消除丢失的值。
在删除整个列或行之前,我们应该非常谨慎,因为我们是在从数据集中提取数据,这会损害您的分析。
首先,我们来思考一下特性 PoolQC 。由于该列中有超过 99%的值丢失,我们将删除它。正如我们在前面的章节中已经看到的,我们可以用函数 drop 删除一个列。
这里,我将使用原始数据帧的副本。
df_toremove = df.copy()df_toremove.drop(labels='PoolQC', axis=1, inplace=True)
现在,我们来看看 GarageType 。因为它只有大约 5%的值丢失,我们可以使用函数 dropna 简单地删除该特性中丢失值的行。
df_toremove.dropna(subset=['GarageType'], axis=0, inplace=True)
填充缺失值
当我们处理缺失值时,除了删除它们之外,我们还可以用一些非空值来填充这些缺失数据。有几种技术可以帮助你确定应该在数据中插入哪些值,包括使用机器学习,我真的建议你搜索关于这个主题的文章,但这里我只展示由熊猫提供的完成这项工作的工具。
首先,我们来看看特性栅栏。请注意,它有 80%的缺失值。假设它的发生是因为这些房子没有围栏!因此,我们用字符串 NoFence 填充这些缺失的数据。
我将再次使用原始数据帧的副本。
df_tofill = df.copy()df_tofill['Fence'].fillna(value='NoFence', inplace=True)
现在,让我们检查一下特性 GarageYrBlt 。在这个例子中,我将向您展示如何用列的中值填充条目。
garage_median = df_tofill['GarageYrBlt'].median()df_tofill.fillna({'GarageYrBlt': garage_median}, inplace=True)
让我提醒你,这些例子只是为了教育目的。您可能已经意识到 GarageType 和 GarageYrBlt 有 81 个缺失值。大概是因为那些房子没有车库吧。在现实分析中,删除这些缺少 GarageType 的行并用一些值填充 GarageYrBlt 可能不是最明智的做法。
事实上,如果我没有使用原始数据帧的副本,您会看到当我们删除缺少 GarageType 的行时,这些也是缺少 GarageYrBlt 的 81 行。这显示了在修改数据之前解释和了解数据的重要性。
可视化数据
在这一节,我将谈论如何用熊猫做一些简单的绘图。如果您想构建更精细的图表,我建议您看看另外两个 Python 库: Matplotlib 和 Seaborn 。
在这里,我们将看到两种类型的图表:直方图和散点图。
直方图
直方图非常适合显示数据的分布。下面是特性销售价格的直方图,其中 x 轴包含将值划分为区间的箱,而 y 轴表示频率。
df['SalePrice'].plot(kind='hist')
散点图
有了散点图,你可以直观地看到两个变量之间的关系。使用笛卡尔坐标构建图表,将值显示为点的集合,每个点有一个变量的值确定在 x 轴上的位置,另一个变量的值确定在 y 轴上的位置。
让我们为变量 SalePrice 和year build构建一个散点图,以检查它们之间是否有任何关系。
df.plot(x='SalePrice', y='YearBuilt', kind='scatter')
嗯,我们可以看到销售价格和建造年份之间有一个小的正相关关系。
plot 函数还支持许多其他类型的图表,如折线图、条形图、面积图、饼图等。
保存到文件
在本文的最后一部分,让我们看看如何将数据帧保存为文件。
就像我们读取 csv 文件来创建我们的数据帧一样,我们也可以将我们的数据帧保存为各种格式。如果您使用的是 Google Colab ,您可以简单地编写下面一行代码,来保存一个 csv 文件。
df.to_csv('My_DataFrame.csv')
保存的文件可以在 Google Colab 左上角的文件夹中找到,如下图所示。
您也可以在计算机中指定路径。例如,在 windows 中:
df.to_csv('C:/Users/username/Documents/My_DataFrame.csv')
杰瑞米 C 在 Unsplash 上的照片
结论
我希望这篇文章能帮助你了解你可以用熊猫做什么。经过一些练习后,操纵你的数据变得相当容易。
熊猫广泛应用于数据科学。许多数据科学家在建立机器学习模型之前利用它来操纵数据,但即使在使用 Excel 完成更简单的任务时,你也可以从中受益。
如今,在工作中,我过去在 excel 中做的许多活动现在都用 Python 和 Pandas 来做。也许学习曲线有点陡峭,但是您的生产力的潜在增长是值得的,更不用说 Python 是一个非常好的工具!
完整代码请参考笔记本。
提高数据分析师或数据科学家的工资
意见
…以下是方法
来自《走向数据科学》编辑的提示: 虽然我们允许独立作者根据我们的 规则和指南 发表文章,但我们并不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
目录
- 介绍
- 硕士学位
- 突出项目经验
- 绩效考核跟踪
- 特定行业的经验
- 摘要
- 参考
介绍
工资可能会因过多的因素而经常变化,但这并不意味着你不能增加工资。你可以做四件事来确保你的薪水在数据分析和数据科学领域都具有竞争力。我将概述这两个热门技术职位在薪酬方面的主要差异,同时描述如何应用每一项改进来增加你的薪酬。我坚持这些改进来提高我自己的薪水,所以我希望它们也能为你所用。当然,有些人会成功,有些人不会。
根据 Glassdoor 的数据,数据分析师的平均薪资为:
$62,453/yr
值得注意的是,一些公司提供更高的薪水,如谷歌在$95,941/yr
和脸书在$114,18/yr
。这种差异的部分原因是因为他们需要硕士学位,以及一些用数据科学特征定义数据分析的公司。
鉴于数据科学家的平均工资为:
$113,309/yr
虽然这两个职位之间的差距相当大,但加薪的方法可以适用于这两个角色。也就是说,我将在下面讨论这四种你可以增加工资的方法。
硕士学位
科尔·凯斯特在Unsplash【4】上的照片。
数据科学家
毫不奇怪,完成数据科学硕士学位将有助于增加你的薪酬。然而,主要原因并不像你想的那么明显。具体来说,数据科学硕士学位比数据科学本科学位更常见。因此,这并不一定意味着你需要大约六年的数据科学经验,而更像是一到两年。拥有这个学位意味着你最近在数据科学的专业化方面有所实践。
数据分析师
至于数据分析师,你需要获得的硕士学位并不简单。大多数程序都有不同的命名或分类。一些关于数据分析的常见学位包括但不限于:
- 商业分析
- 商业
- 分析学
- 商业智慧
需要记住的最重要的一点是,尽管硕士项目已经迅速变得流行,尤其是在数据科学领域,但它们开始变得不如 和 重要。这种差异是因为公司承认其他形式的体验也具有可比性。另一件需要指出的事情是,当你获得数据分析相关或数据科学的硕士学位时,你很可能会处于另一个级别的 资历 ,这最终也会增加你的薪酬。我个人很难在没有硕士学位的情况下获得任何面试机会,但这几乎是因为我之前没有什么专业经验,所以如果你是第一次开始工作,并打算获得硕士学位,那么就去争取吧,但如果你已经是一名公认的数据分析师或数据科学家,那么你最有可能通过更快的课程变得专业化和认证来增加你的工资。
突出项目经验
这种增加工资的方法要简单和容易得多。假设你已经有了数据分析和数据科学的经验,大约 1-3 年后,你会发现你的薪水会因为在简历中更好地突出你的项目而增加。
这种强调并不是夸大任何项目,而是清晰地勾勒出比时间本身更有经验的适用项目。
数据科学家
作为一个操作项,您可以按以下特定方式构建您的项目:
what you did, how you did it, and what was its effect
例如:“用 Python 创建了一个带随机森林的自动产品分类器,节省了 40%的时间”
数据分析师
示例:“用 Python 在 Tableau 中创建了一个指标仪表板,处理 10 个报告的速度是手动的两倍”
如您所见,对于这两个角色,您都可以遵循这个简单的格式,快速确定您的能力及其对业务的影响。对于你的简历,你可以按照这种格式添加,比如说,大约五个你参与过的主要项目——你必须记住,除非你告诉或展示给招聘经理或招聘人员,否则他们不会知道你的任何历史。
绩效考核跟踪
LinkedIn 销售导航员在Unsplash【6】上的照片。
虽然你薪水的第一次提高可能来自一年或两年的硕士学位,第二点是更好地突出你的简历项目,但你薪水的下一次提高可能来自你在当前职位上的绩效评估。如果你的公司给你提供了一份绩效评估,这可能不是由你决定的,但如果你足够幸运有这个选择,那么你可以在整个工作经历中做一些事情,以确保你正确地强调了你的绩效,最终增加你的工资。如果你想采取一个更大胆的方法,因为你没有一个绩效评估或一些让你有机会赚更多钱的季节性评估,你可以用类似的格式向你的老板展示你一直在做什么,用什么工具,以及对公司有什么影响。
无论是数据分析师还是数据科学家的职位,你都可以关注以下追踪工具来增加你的薪水:
- 写下你的主要成就
- 比较一下 当前 空间,与 目标 ,然后你的 贡献
—例如,当前模型准确率为 50%,目标为 70%,而您的贡献为 80%,这基本上突出了您超出了预期
- 在公司内部建立你自己的个人目标,过去几个月你缺少什么,你在哪些方面有所提高
一些公司会大幅提高工资,但前提是你能给他们一个理由,所以要确保到时候,你不是在苦苦寻找过去几个月你一直在做什么,而是准备好向领导证明自己。
特定行业的经验
LinkedIn 销售导航员在Unsplash【7】上的照片。
为了在医疗保健、体育或金融等特定行业找到一份工作,你当然会希望拥有一些与行业相关的经验,以增加获得高薪的机会。对于数据分析和数据科学来说,你可以看到经验不一定是过去工作中的职位本身,而是同一行业中的其他工作。原因是行业知识对一些公司来说非常有价值。例如,一些数据科学工作要求你是主题专家。因此,如果你对医疗保健一无所知,你可能会被提供他们工资范围的低端,尽管从技术上来说你仍然有资格获得这份工作。
这里有一些方法可以提高你的行业经验:
- 来自你最终想从事的行业的经验,包括过去的职位、训练营、证书、在线课程和 YouTube 上的在线视频
- 这些经历不必是每年一次的项目,而是高度专业化的项目,突出你除了是数据分析师或数据科学家之外,还是主题专家
- 进一步对冲成功的承诺——这些可能对一些人成功,也可能不成功,有许多因素可能会发挥作用。
——添加任何你觉得可以分享的个人见解/经历,因为这些往往会给这类话题带来最大的可信度。
摘要
谈判薪资可能会令人生畏,但如果你处理得当,你或许可以完全绕过这一部分,拿到公司薪资范围内的最高薪资。完成以下任何一项:获得硕士学位,在简历上更好地突出你的项目经验,绩效评估的改进,以及获得特定行业的经验,这些都将使你更有可能增加工资。如你所见,无论你是数据分析师还是数据科学家,都有无数种方法可以提高你的薪水。我已经谈了四个主要的,但还有很多。最终,你会希望确定你目前作为数据分析师或数据科学家的薪水,我在我的文章顶部强调了这一点,然后专注于改善促成更高薪水的主要因素。
值得注意的是,这些改进对某些人来说可能成功,也可能失败,有几个因素可能会影响你的薪水如何提高。对我个人来说,这些是我增加自己薪水的步骤。虽然还不完全清楚是什么决定了一家公司的工资增长,但改善自己和自己的工作可以在你的数据分析或数据科学职业生涯中带来巨大的成就。
以下是所有的改进,总结如下:
* Master's Degree* Highlight Project Experience* Performance Review Tracking* Industry-Specific Experience
我希望你觉得我的文章既有趣又有用!如果您使用了这些数据分析师和数据科学薪资提升方法— ,请在下面随意评论,您使用了哪些方法? 现在对你的职业生涯有帮助吗?你同意还是不同意,为什么?
请随时查看我的个人资料和其他文章,也可以通过 LinkedIn 联系我。我与这些提到的公司没有关系。
感谢您的阅读!
参考
[1]照片由 Jp Valery 在Unsplash(2019)上拍摄
[2] Glassdoor,Inc .,数据分析师薪酬,(2008–2021)
[3] Glassdoor,Inc .,数据科学家薪酬,(2008–2021)
[4]科尔·凯斯特在 Unsplash 上拍摄的照片,(2017)
[6]照片由 LinkedIn 销售导航员在 Unsplash 上拍摄,(2017)
[7]照片由 LinkedIn 销售导航员在Unsplash(2015)上拍摄
提高您的数据即生产力
数据科学家可视化数据指南
使用 Altair 快速创建令人惊叹的可视化
牛郎星是天鹰座中最亮的星星
我们大多数人需要听音乐才能理解它有多美。但通常这就是我们展示统计数据的方式:我们只展示音符,不演奏音乐。—汉斯·罗斯林
数据可视化对于理解数据分析之美至关重要。通过数据可视化,我们的利益相关者了解我们分析的影响。这有助于他们集中注意力,做出明智的决定。
然而,尽管它很重要,我一直收到关于有抱负的数据科学家如何开始探索数据分析的问题。
简单,具有数据可视化。
牛郎星的力量
“Altair 是一个针对 Python 的声明式统计可视化库,基于 Vega 和 Vega-Lite ,源代码可在 GitHub 上获得 — 牛郎星即
Altair 是 Python 中的一个数据可视化包,它基于 Vega 和 Vega Lite 语法,并根据最佳可视化实践进行了更新。
宣布你的需求
Altair 支持声明式编程,它允许您基于输入数据和输出属性构建 Altair 可视化。你将只陈述你需要什么,而不是如何实现目标的细节。
来源 Unsplash
假设你要买鸡蛋。
声明式编程会说“给我找鸡蛋买鸡蛋。这是现金”
然而传统编程会说“去法院超市,去 6 号通道找到你右手角的鸡蛋,去收银台用 5 美元现金支付”。
显然,从这个例子中,声明式编程将卸载**“如何”**。与金钱和鸡蛋类似,您只需要提交输入数据和输出属性,可视化结果就会自动呈现。
你甚至不需要理解 Altair 是如何处理你的可视化的。你将简单地指导和接受结果。这就是 Altair 支持声明式编程的美妙之处。
其实这一点都不外国。许多语言也是用相同的概念构建的。 SQL(结构化查询语言)和 Kubernetes yaml 使用声明性概念。 Dash 也是基于声明式编程构建 dashboard web 应用的工具。两者都是数据科学家优化数据检索和开发流程的重要工具。
希望我让你兴奋!!让我们开始吧
牛郎星的组件
Altair 使用这些组件作为主要语法来指导可视化:
- 数据 →可视化的数据帧
- 标记 →每行观察的符号(线、条、刻度、点)
- 编码 →数据表示通道(X 位置、Y 位置、颜色、尺寸)
- 转换 →应用可视化之前的数据转换(计算、过滤、聚集、折叠)
- 标尺 →输入数据并将其显示在屏幕上的功能。
- 指南 →图表(图例)上的视觉辅助,x 轴和 y 轴上的刻度。
用 Altair 弄脏你的手
Colab 笔记本
在这篇文章中,我们将使用下面的 Colab 笔记本。
装置
要安装很容易,您可以运行 pip 安装如下
pip install altair
数据
对于我们的教程,我们将使用这些数据集:
data.cars
:来自 UCI 机器学习知识库的 AutoMPG 数据集。data.stocks
:每日收盘股票价格(AAPL、AMZN、GOOG 等)data.seattle_weather
:2012-2015 年西雅图天气
这些数据集可以在 vega_datasets 上获得,Vega _ datasets 与 Altair 一起预加载到 Pandas 数据帧中。
UCI 机器学习知识库上预装的 Cars 数据集(来源于作者)
牛郎星一步步来
图表
表示“另类的”图表是一种需要数据、标记和编码才能运行的结构。
alt.Charts()
数据
要插入数据,可将数据框输入功能图()。
alt.Charts(cars)
标记
需要标记来表示每一行观察值(线、条、记号、点)
alt.Charts(cars).mark_tick()
编码
对数据集的表示进行编码。您可以将 1 列作为 1D 表示,2 列作为 2D,3 列作为 3D,等等。
alt.Chart(cars).mark_tick().encode(
encoding_1 = value_1,
encoding_2 = value_2,
......
)
你只需要一张图表
创建一个图表很容易。您可以添加维度,在声明性语言中更改不同的符号,一切都会为您处理好。
线条、线条、点等等!
只需将 mark_line()转换为 mark_bar()
满足您可视化需求的简单声明性命令(由作者提供)
构建直方图超级简单!
alt.Chart(weather).mark_bar().encode(
x=alt.X('date', timeUnit='month', type='nominal'),
y='mean(temp_max):Q',
color='weather'
)
简单直方图(来源于作者)
使用热图进行模式发现
创建热图。你可以把符号从mark_bar()
替换成mark_rect().
alt.Chart(weather).mark_rect().encode(
x=alt.X(‘date’, timeUnit=’date’, type=’ordinal’),
y=alt.Y(‘date’, timeUnit=’month’, type=’ordinal’),
color=’mean(temp_max)’,
tooltip=[‘mean(temp_max)’]
)
基于温度的月份和日期的热图(来源于作者)
从这里我们可以发现,最高气温是从 4 月份开始到 10 月份(颜色较深)。这与旱季相对应。整洁!
许多图表可供比较
Altair 还配备了简单的语言来表示多图形表示。
例如,想象三维散点图的 3X3 散点图。你所需要的只是模块repeat.
alt.Chart(cars).mark_circle().encode(
alt.X(alt.repeat("column"), type='quantitative'),
alt.Y(alt.repeat("row"), type='quantitative'),
color='Origin:N'
).properties(
width=250,
height=250
).repeat(
row=['Horsepower','Acceleration','Miles_per_Gallon'],
column=['Horsepower','Acceleration','Miles_per_Gallon']
).interactive()
重复的列和行创建散布矩阵(来源于作者)
要比较的交互式图表
您甚至可以添加交互来比较多个图表。例如,您可以使用 altair 中的模块selection
来创建选择。
brush = alt.selection(type='interval',resolve='global')
base = alt.Chart(cars).mark_point().encode(
y='Miles_per_Gallon',
color=alt.condition(brush,'Origin',alt.ColorValue('lightgray'))
).properties(
selection=brush,
width=250,
height=250
)
base.encode(x='Horsepower') | base.encode(x='Acceleration')
互动情节(来源于作者)
您甚至可以使用稍微复杂的选择来创建基于时间的动画。例如,您可以应用year
作为选择和过滤标准。
from altair.expr import datum, if_
from vega_datasets import datapop = data.population.url
pink_blue = alt.Scale(domain=(‘Male’, ‘Female’),
range=[“steelblue”, “salmon”])
slider = alt.binding_range(min=1900, max=2000, step=10)
year = alt.selection_single(name=”year”, fields=[‘year’], bind=slider)
alt.Chart(pop).mark_bar().encode(
x=alt.X(‘sex:N’, axis=alt.Axis(title=None)),
y=alt.Y(‘people:Q’, scale=alt.Scale(domain=(0, 12000000))),
color=alt.Color(‘sex:N’, scale=pink_blue),
column=’age:O’
).properties(
width=20,
selection=year,
).transform_calculate(
“sex”, if_(datum.sex == 1, “Male”, “Female”)
).transform_filter(
year.ref()
)
时间动画图(来源于作者)
最后的想法
Altair 让绘制哪怕是复杂的剧情变得非常简单。它使我们能够直接将想法可视化,而不必担心背后的机制。用最少的努力就可以创造出从简单到复杂的惊人的情节和视觉效果。—Parul Pandey—H2O . ai 的数据科学家
同样,我爱上了牛郎星。它简单、直观、快速。我用谷歌搜索 matplotlib 来添加交互式线条并将多个图对齐在一起的日子已经一去不复返了。Altair 足够直观,可以提高我可视化数据的效率。
如果你有兴趣了解更多关于牛郎星的信息,请查看
- 我的实习实验室实验
- 帕鲁尔·潘迪关于牛郎星的介绍。这个教程启发我记录我自己的牛郎星学习。Parul 提供了关于 Altair 的清晰介绍。我强烈推荐这个作为额外的参考。
- 官方文档配有解释和实例探讨(牛郎星教程、牛郎星案例分析、实例图库)
索利·德奥·格洛丽亚
关于作者
文森特用 ML @ Google 对抗网络滥用。文森特使用高级数据分析、机器学习和软件工程来保护 Chrome 和 Gmail 用户。
除了在谷歌的工作,文森特还是《走向数据科学媒体》的特约撰稿人,为全球 100 多万观众的有志 ML 和数据从业者提供指导。
在空闲时间,文森特在佐治亚理工学院攻读硕士学位,并为铁人三项/自行车旅行进行训练。
最后,请通过LinkedIn,Medium或 Youtube 频道 联系文森特
提升您的网络性能
超参数调整和训练优化
你准备了一个有几百万样本的大数据集,设计了一个最先进的神经网络,训练了 100 个纪元,却得不到一个满意的结果?这是很多 ML 从业者都很熟悉的问题。另一个流行的观点是,神经网络是黑盒,所以除非代码中有明显的错误,否则几乎不可能识别问题并提高性能。然而,不应该是这样的。到目前为止,ML 社区对不同组件如何影响网络的训练速度和性能有了相当好的理解。在本文中,我将分享一些优秀的超参数调优和性能评估方法。
培训和验证损失
虽然很难将神经网络的内部工作可视化,但一些线索对于普通从业者来说是容易获得的,例如验证和训练损失监控。损失是模型收敛的一个强有力的指标。当额外的训练不能改进模型时,模型“收敛”。缺乏收敛性表明模型无法学习数据的底层结构。
在这种情况下,记住过拟合和*欠拟合的概念是有益的。*欠拟合是指一个模型既不能学习训练数据,也不能推广到新数据,因为它不够强大。过度拟合是一种相反的情况,其中模型太了解训练数据和噪声。训练目标是在过度适应和欠适应之间找到一个最佳点,以确保最佳训练。但是我们如何知道模型是过拟合还是欠拟合呢?
过度拟合和欠拟合之间的权衡。
上图说明了我们想要实现验证损失的水平部分,这是欠拟合和过拟合之间的平衡点。连续下降的曲线表示拟合不足,而损失增加则表示拟合过度。监控过拟合和欠拟合的早期迹象可以节省大量时间。如下图所示,在训练早期的过度拟合表明超参数的次优选择。如果超参数设置正确,它们应该在整个过程中表现良好:
黑色方块显示了训练早期过度适应的迹象(Smith,2018。)
我现在将解释各种超参数如何影响过拟合-欠拟合权衡。先说学习率。
学习率
学习率是一个超参数,它控制着我们调整网络权重的程度。低学习率会导致过度拟合,因为模型的步长很小,很容易拟合噪声,而不是实际的数据结构。较大的学习率有助于通过制定更重要的步骤来规范训练。但是,如果学习率太大,训练就会发散。循环学习率(CLR)和学习率范围测试(LRR)是为特定用例定义最合适的学习率的有用程序。
循环学习率
要使用 CLR,您需要指定最小和最大学习速率以及步长。步长是每步使用的历元数,一个周期由两个这样的步骤组成,一个是学习率线性增加,另一个是线性减少。
开始时较低的值用于训练的热身。不建议直接切换到更高的值,而是线性增加。在周期的中间,高学习率充当正则化方法,并防止网络过度拟合。它们防止模型落在损失函数的陡峭区域。在周期结束时学习率的下降让模型进入到更平滑部分的更陡峭的局部最小值。在学习率高的部分,我们看不到损失或准确性的实质性改善,验证损失有时会非常高。尽管如此,当我们最终降低学习率时,我们看到了这样做的所有好处。但是我们如何选择最小和最大的 LR 值呢?
循环学习率图
学习率范围测试
LRR 测试是一个预训练步骤,我们以逐渐增加的学习率运行模型。它提供了关于最大学习速度的有价值的信息。当以小的学习率训练时,网络收敛,并且随着学习率的增加,网络变得太大并导致验证损失和准确性的降低。在这种背离之前,学习率是可以用循环学习率进行训练的最高值。然而,当选择恒定的学习速率时,较小的值是必要的,否则网络将不会开始收敛。最小学习速率界限通常比最大界限小 10 倍。然而,其他因素也是相关的,例如学习率增加的速度(增加太快将导致不稳定)和架构的深度(网络越深,学习率越大)。
一些研究人员建议使用小于迭代/历元总数的一个周期,并允许学习率的下降小于初始学习率。这种学习率策略被称为“1 周期”,它显示出在训练结束之前允许准确度达到稳定状态。
批量
小批量具有调整效果。已经表明,对于每个数据集和架构,正则化的总量必须是平衡的。减少其他形式的正规化和学习率巨大的正规化,使培训更有效率。因此,最好使用更大的批量。一些论文建议修改批量大小,而不是学习率(Smith et al .,2017)。然而,批量大小受到硬件的限制。如果您的服务器有多个 GPU,最佳批处理大小是 GPU 上的批处理大小乘以 GPU 的数量。
周期性动量
动量或 SGD 与动量是一种方法,有助于加速梯度向量在正确的方向,从而导致更快的收敛。与学习率密切相关。动量值为 0.99、0.97、0.95 和 0.9 的短距离跑步将帮助您找到动量的最佳值。
如果使用 1 周期学习率计划,最好使用周期动量(CM)。理论上,动量的恒定值可以给出与循环相同的最终结果。然而,周期性动量消除了尝试多个值和运行几个完整周期的麻烦。此外,当学习速率增加时,减少循环动量使训练稳定,从而允许更大的学习速率。
循环动量图
重量衰减
权重衰减是一种正则化过程,它使权重指数衰减为零。权重衰减是一种简单的方法来平衡总正则化和来自增加的学习速率的正则化。虽然其他正则化技术具有固定值(即,丢弃),但是当试验最大学习速率和步长值时,很容易改变权重衰减值。需要进行网格搜索来确定适当的星等,但通常不需要超过一个有效数字的精度。
结论
如果您在训练的早期寻找验证损失的线索,超参数优化可以相当快。在过拟合和欠拟合之间找到一个最佳平衡点并优化正则化对于快速有效的训练至关重要。您可能会很高兴地得知,这些技术中的大部分已经在 fast.ai 和 PyTorch 中实现了!快乐训练:)
资源
史密斯律师事务所(2018 年)。神经网络超参数的训练方法:第 1 部分——学习速率、批量大小、动量和重量衰减。 arXiv 预印本 arXiv:1803.09820 。
纽约州史密斯市和新泽西州托平市(2017 年)。超收敛:使用大学习率非常快速地训练神经网络。arXiv 电子版,第页。 arXiv 预印本 arXiv:1708.07120 。
j .霍华德和 s .古格(2020 年)。Fastai:深度学习的分层 API。信息, 11 (2),108。
使用 Catboost 增强嵌入
本文深入探讨了 Catboost,这是一种简单且鲜为人知的方法,可以将嵌入与梯度增强模型结合使用。
由 Romain Vignes 在 Unsplash 上拍摄的照片
介绍
当处理大数据时,有必要将具有特征的空间压缩成矢量。文本嵌入就是一个例子,它是几乎所有 NLP 模型创建过程中不可或缺的一部分。不幸的是,使用神经网络来处理这种类型的数据并不总是可行的,例如,原因可能是拟合率或推断率较低。
我建议一个很有趣的方法来使用梯度增强,很少有人知道。
数据
Kaggle 的竞赛之一最近结束了,一个带有文本数据的小数据集出现在那里。我决定将这些数据用于实验,因为竞赛表明数据集被很好地标记,并且我没有面临任何令人不愉快的意外。
列:
id
-摘录的唯一 IDurl_legal
-来源的 URLlicense
-源材料许可证excerpt
-文本预测阅读的难易程度target
——读书容易standard_error
-对每个摘录在多个评价人之间的分数分布的测量
作为数据集中的一个目标,它是一个数值型变量,提出它是为了解决回归问题。但是,我决定换成一个分类问题。主要原因是我将使用的库不支持在回归问题中处理文本和嵌入。希望以后开发者消除这个不足。但是在任何情况下,回归和分类问题都是密切相关的,对于分析来说,解决哪一个问题都没有区别。
目标
让我们用斯特奇法则来计算箱子的数量:
num_bins = int(np.floor(1 + np.log2(len(train))))train['target_q'], bin_edges = pd.qcut(train['target'],
q=num_bins, labels=False, retbins=True, precision=0)
目标 _q
但是,首先,我清理数据。
train['license'] = train['license'].fillna('nan')
train['license'] = train['license'].astype('category').cat.codes
借助一个自写的小函数,我对文字进行清理和词条化。函数可以很复杂,但这对于我的实验来说已经足够了。
def clean_text(text):
table = text.maketrans(
dict.fromkeys(string.punctuation))
words = word_tokenize(
text.lower().strip().translate(table))
words = [word for word in words if word not in stopwords.words('english')]
lemmed = [WordNetLemmatizer().lemmatize(word) for word in words]
return " ".join(lemmed)
我将清理后的文本保存为新特征。
train['clean_excerpt'] = train['excerpt'].apply(clean_text)
除了文本,我还可以选择 URL 中的单个单词,并将这些数据转换成新的文本特征。
def getWordsFromURL(url):
return re.compile(r'[\:/?=\-&.]+',re.UNICODE).split(url)train['url_legal'] = train['url_legal'].fillna("nan").apply(getWordsFromURL).apply(
lambda x: " ".join(x))
我从文本中创建了几个新特征——这些是各种各样的统计信息。还是那句话,创意的空间很大,但是这个数据对我们来说已经足够了。这些特性的主要目的是对基线模型有所帮助。
def get_sentence_lengths(text): tokened = sent_tokenize(text)
lengths = []
for idx,i in enumerate(tokened):
splited = list(i.split(" "))
lengths.append(len(splited)) return (max(lengths),
min(lengths),
round(mean(lengths), 3))def create_features(df):
df_f = pd.DataFrame(index=df.index)
df_f['text_len'] = df['excerpt'].apply(len)
df_f['text_clean_len' ]= df['clean_excerpt'].apply(len)
df_f['text_len_div'] = df_f['text_clean_len' ] / df_f['text_len']
df_f['text_word_count'] = df['clean_excerpt'].apply(
lambda x : len(x.split(' ')))
df_f[['max_len_sent','min_len_sent','avg_len_sent']] = \
df_f.apply(
lambda x: get_sentence_lengths(x['excerpt']),
axis=1, result_type='expand')
return df_ftrain = pd.concat(
[train, create_features(train)], axis=1, copy=False, sort=False)basic_f_columns = [
'text_len', 'text_clean_len', 'text_len_div', 'text_word_count',
'max_len_sent', 'min_len_sent', 'avg_len_sent']
当数据稀缺时,更容易检验假设,结果通常需要更加稳定。因此,为了对结果更有信心,我更喜欢在这种情况下使用 OOF(Out-of-Fold)预测。
基线
我选择了 Catboost 作为模型的免费库。Catboost 是一个高性能的开源库,用于决策树的梯度提升。从版本 0.19.1 开始,它支持在 GPU 上开箱即用的分类文本功能。主要优点是 CatBoost 可以在数据中包含分类和文本函数,而无需额外的预处理。
在非常规情感分析:BERT vs. Catboost 中,我详述了 Catboost 如何处理文本,并将其与 BERT 进行了比较。
这个库有一个杀手锏:它知道如何处理嵌入。不幸的是,目前文档中对此只字未提,只有少数人知道 Catboost 的这一优势。
!pip install catboost
当使用 Catboost 时,我建议使用池。它是一个方便的包装器,结合了特性、标签和元数据,比如分类和文本特性。
为了比较实验,我创建了一个仅使用数字和分类特征的基线模型。
我写了一个函数来初始化和训练模型。顺便说一下,我没有选择最佳参数。
def fit_model_classifier(train_pool, test_pool, **kwargs):
model = CatBoostClassifier(
task_type='GPU',
iterations=5000,
eval_metric='AUC',
od_type='Iter',
od_wait=500,
l2_leaf_reg=10,
bootstrap_type='Bernoulli',
subsample=0.7,
**kwargs
) return model.fit(
train_pool,
eval_set=test_pool,
verbose=100,
plot=False,
use_best_model=True)
为了实现 OOF,我编写了一个简单明了的小函数。
def get_oof_classifier(
n_folds, x_train, y, embedding_features,
cat_features, text_features, tpo, seeds,
num_bins, emb=None, tolist=True):
ntrain = x_train.shape[0]
oof_train = np.zeros((len(seeds), ntrain, num_bins))
models = {} for iseed, seed in enumerate(seeds):
kf = StratifiedKFold(
n_splits=n_folds,
shuffle=True,
random_state=seed)
for i, (tr_i, t_i) in enumerate(kf.split(x_train, y)):
if emb and len(emb) > 0:
x_tr = pd.concat(
[x_train.iloc[tr_i, :],
get_embeddings(
x_train.iloc[tr_i, :], emb, tolist)],
axis=1, copy=False, sort=False)
x_te = pd.concat(
[x_train.iloc[t_i, :],
get_embeddings(
x_train.iloc[t_i, :], emb, tolist)],
axis=1, copy=False, sort=False)
columns = [
x for x in x_tr if (x not in ['excerpt'])]
if not embedding_features:
for c in emb:
columns.remove(c)
else:
x_tr = x_train.iloc[tr_i, :]
x_te = x_train.iloc[t_i, :]
columns = [
x for x in x_tr if (x not in ['excerpt'])]
x_tr = x_tr[columns]
x_te = x_te[columns]
y_tr = y[tr_i]
y_te = y[t_i] train_pool = Pool(
data=x_tr,
label=y_tr,
cat_features=cat_features,
embedding_features=embedding_features,
text_features=text_features) valid_pool = Pool(
data=x_te,
label=y_te,
cat_features=cat_features,
embedding_features=embedding_features,
text_features=text_features) model = fit_model_classifier(
train_pool, valid_pool,
random_seed=seed,
text_processing=tpo
)
oof_train[iseed, t_i, :] = \
model.predict_proba(valid_pool)
models[(seed, i)] = model
oof_train = oof_train.mean(axis=0)
return oof_train, models
我将在下面写关于 get_embeddings 函数,但是它不用于获取模型的基线。
我用以下参数训练了基线模型:
columns = ['license', 'url_legal'] + basic_f_columns oof_train_cb, models_cb = get_oof_classifier(
n_folds=5,
x_train=train[columns],
y=train['target_q'].values,
embedding_features=None,
cat_features=['license'],
text_features=['url_legal'],
tpo=tpo,
seeds=[0, 42, 888],
num_bins=num_bins
)
训练模型的质量:
roc_auc_score(train['target_q'], oof_train_cb, multi_class="ovo")AUC: 0.684407
现在我有了模型质量的基准。从数字来看,这个模型很弱,我不会在生产中实现它。
嵌入
你可以把多维向量转化为嵌入,嵌入是一个相对低维的空间。因此,嵌入简化了大输入的机器学习,例如表示单词的稀疏向量。理想情况下,嵌入通过将语义相似的输入放在嵌入空间中彼此靠近来捕获一些输入语义。
有许多方法可以获得这样的向量,我在本文中不考虑它们,因为这不是研究的目的。然而,对我来说,以任何方式获得嵌入就足够了;最重要的是他们保存了必要的信息。在大多数情况下,我使用目前流行的方法——预训练变形金刚。
from sentence_transformers import SentenceTransformerSTRANSFORMERS = {
'sentence-transformers/paraphrase-mpnet-base-v2': ('mpnet', 768),
'sentence-transformers/bert-base-wikipedia-sections-mean-tokens': ('wikipedia', 768)
}def get_encode(df, encoder, name):
device = torch.device(
"cuda:0" if torch.cuda.is_available() else "cpu")
model = SentenceTransformer(
encoder,
cache_folder=f'./hf_{name}/'
)
model.to(device)
model.eval()
return np.array(model.encode(df['excerpt']))def get_embeddings(df, emb=None, tolist=True):
ret = pd.DataFrame(index=df.index)
for e, s in STRANSFORMERS.items():
if emb and s[0] not in emb:
continue
ret[s[0]] = list(get_encode(df, e, s[0]))
if tolist:
ret = pd.concat(
[ret, pd.DataFrame(
ret[s[0]].tolist(),
columns=[f'{s[0]}_{x}' for x in range(s[1])],
index=ret.index)],
axis=1, copy=False, sort=False)
return ret
现在我已经准备好开始测试不同版本的模型了。
模型
我有几个拟合模型的选项:
- 文本特征;
- 嵌入特征;
- 嵌入特征,如一列分离的数字特征。
我一直在这些选项的各种组合中接受训练,这让我得出结论,嵌入可能是多么有用,或者,也许,它是一个过度工程。
作为一个例子,我给出了一个使用所有三个选项的代码:
columns = ['license', 'url_legal', 'clean_excerpt', 'excerpt'] oof_train_cb, models_cb = get_oof_classifier(
n_folds=FOLDS,
x_train=train[columns],
y=train['target_q'].values,
embedding_features=['mpnet', 'wikipedia'],
cat_features=['license'],
text_features=['clean_excerpt','url_legal'],
tpo=tpo,
seeds=[0, 42, 888],
num_bins=num_bins,
emb=['mpnet', 'wikipedia'],
tolist=True
)
为了获得更多信息,我在 GPU 和 CPU 上训练了模型;并将结果汇总在一个表格中。
首先让我震惊的是文字特征和嵌入的交互极差。不幸的是,我还没有对这个事实的任何逻辑解释——在这里,需要在其他数据集上对这个问题进行更详细的研究。同时,请注意,文本的组合使用和同一文本的嵌入会降低模型的质量。
更新:我收到开发者的评论:
“谢谢你的报告!这个错误已经在提交中修复,并将在下一个版本中发布”
我的另一个发现是,当在 CPU 上训练模式时,嵌入不起作用。
现在,一件好事是,如果你有一个 GPU 并且可以获得嵌入,最好的质量将是当你同时使用嵌入作为一个功能和一系列单独的数字功能时。
摘要
在这篇文章中,我:
- 选择了一个小型免费数据集进行测试;
- 为文本数据创建了几个统计特征,用于制作基线模型;
- 测试了嵌入、文本和简单特性的各种组合;
- 我得到了一些非显而易见的见解。
这些鲜为人知的信息将对社区有所帮助,并从您的项目中受益。不幸的是,Catboost 处理嵌入和文本的功能仍然是原始的。但是,它正在积极改进,我希望很快会有一个稳定的版本,开发者会更新文档。复制本文结果的完整代码可从这里获得。
用维基百科推进自然语言处理
实践教程
使用维基百科改进自然语言处理任务,如命名实体识别和主题建模
照片由 Artem Maltsev 在 Unsplash 拍摄
介绍
自然语言处理(NLP)正在兴起。计算语言学和人工智能正在联手培育突破性发现。当研究集中于显著改进 NLP 技术时,企业将该技术视为战略资产。这种由 NLP 引导的彻底革新的主要作用是由文本数据的大量可用性发挥的。当谈到数字化时,尤其是对企业而言,重要的是要记住文档本身就是数字化的,因此,文本数据是知识的主要来源。
然而,当寻求磨练自然语言处理任务时,最大的瓶颈之一是关于数据的训练。当涉及到真实世界的应用时,比如在特定的领域,我们面临着低资源数据的问题。训练数据有两个主要问题:(I)难以获得大量数据,以及(ii)注释用于训练和测试的可用数据的耗时过程。
面对这些问题,计算机科学已经投入了大量的注意力。特别是,最新的计算进展提出了两种方法来克服低资源数据问题:
- 微调预先训练的语言模型,如 BERT 或 GPT-3;
- 利用高质量的开放式数据存储库,如维基百科或概念网。
维基百科标志(图片来自维基百科)
大多数现有的计算语言学开放库都提供了基于这两种方法之一开发 NLP 工具的架构。我们现在演示如何利用维基百科来提高两个 NLP 任务的性能:命名实体识别和主题建模。
从句子中提取维基百科信息
有几种工具可以用来处理来自维基百科的信息。关于文本数据的自动处理,我们使用 spaCy 的一个开放项目 SpikeX 。
SpikeX 是准备插入 spaCy 管道的管道集合,spaCy 管道是 NLP 的 python 库。SpikeX 由一家意大利公司( Erre Quadro Srl )开发,旨在帮助构建知识提取工具,几乎不费吹灰之力。
SpikeX 有两个主要特点:
- 给定一个维基百科页面,它提取所有相应的类别。
Categories for `Natural_Language_Processing`:
Category:Artificial_intelligence
-> Category:Emerging_technologies
-> Category:Cybernetics
-> Category:Subfields_of_computer_science
-> Category:Computational_neuroscience
-> Category:Futures_studies
-> Category:Cognitive_science
-> Category:Personhood
-> Category:Formal_sciences
Category:Speech_recognition
-> Category:Artificial_intelligence_applications
-> Category:Computational_linguistics
-> Category:Human–computer_interaction
-> Category:Digital_signal_processing
-> Category:Speech
Category:Natural_language_processing
-> Category:Artificial_intelligence_applications
-> Category:Computational_linguistics
Category:Computational_linguistics
-> Category:Computational_social_science
2.给定一个句子,它会在文本中找到匹配维基百科页面标题的组块。
Elon Musk
('Elon_Musk', 'Elon_musk', 'Elon_Musk_(book)', 'Elon_Musk_(2015_book)', 'Elon_Musk_(2015)', 'Elon_Musk_(biography)', 'Elon_Musk_(2015_biography)', 'Elon_Musk_(Ashlee_Vance)')
------
Elon
('Elon_(Judges)', 'Elon_(name)', 'Elon_(Hebrew_Judge)', 'Elon_(Ilan)', 'Elon_(disambiguation)', 'Elon_(biblical_judge)', 'Elon_(chemical)', 'Elon')
------
Musk
('Musk', 'MuSK', 'Musk_(wine)', 'Musk_(song)', 'Musk_(Tash_Sultana_song)', 'Musk_(disambiguation)')
------
runs
('Runs_(baseball_statistics)', 'Runs', 'Runs_(cricket)', 'Runs_(music)', 'Runs_(baseball)', 'Runs_(Baseball)', 'Runs_(musical)')
------
Tesla Motors
('Tesla_motors', 'Tesla_Motors')
------
Tesla
('Tesla_(band)', 'Tesla_(unit)', 'Tesla_(Czechoslovak_company)', 'Tesla_(crater)', 'Tesla_(microarchitecture)', 'Tesla_(2020_film)', 'Tesla_(car)', 'Tesla_(GPU)', 'TESLA', 'Tesla_(physicist)', 'Tesla_(group)', 'Tesla_(opera)', 'Tesla_(Bleach)', 'Tesla_(company)', 'Tesla_(disambiguation)', 'Tesla_(2016_film)', 'TESLA_(Czechoslovak_company)', 'Tesla_(unit_of_measure)', 'Tesla_(vehicles)', 'Tesla_(vehicle)', 'Tesla_(film)', 'Tesla_(album)', 'Tesla_(Flux_Pavilion_album)', 'Tesla_(surname)', 'Tesla')
------
Motors ('Motors')
我们可以看到,在第一个例子中,SpikeX 提取了维基百科页面“自然语言处理”所属的所有类别。例如,“自然 _ 语言 _ 处理”属于“人工 _ 智能”、“语音 _ 识别”、“计算 _ 语言学”的范畴。Wiki 类别的树形结构可以通过更深层次的检查来进一步探究。
在第二个例子中,对于句子“埃隆·马斯克(Elon Musk)经营特斯拉汽车公司”,SpikeX 提取句子中所有可能在维基百科上有页面的页面。
我们现在看到这两个处理特性如何被用来执行命名实体识别和主题建模。
命名实体识别
命名实体识别(NER)是一项自然语言处理任务,旨在定位文本中提到的实体并将其分类到预定义的类别中(如人名、组织、位置等)。不同的方法处理这一任务以实现高精度:基于规则的系统,训练深度神经网络的方法或精细运行预训练语言模型的方法。例如,Spacy 嵌入了一个预先训练的命名实体识别系统,该系统能够从文本中识别常见类别。
我们现在着手建立一个 NER 系统,能够识别属于某个维基百科类别的文本片段。让我们考虑下面的例句:
“命名实体识别和主题建模是自然语言处理的两项任务”
这句话潜在地包含了三个实体:命名实体识别、主题建模、自然语言处理。这三个实体都有属于特定类别的各自的维基百科页面。
维基百科类别结构示例(图片由作者提供)
在这张图片中,我们可以看到不同的类别是如何分布在三个实体中的。在这种情况下,类别可以被看作是我们想要从文本中提取的实体的标签。我们现在可以利用 SpikeX 的两个特性来构建一个定制的 NER 系统,该系统接受两个输入变量:句子的(I)文本和我们想要检测的(ii)类别。
Named Entity Recognition - COMPUTATIONAL LINGUISTIC
Topic Modeling - COMPUTATIONAL LINGUISTIC
Natural Language Processing - COMPUTATIONAL LINGUISTIC
将维基百科的类别定义为 NER 任务的标签,使得定义 NER 系统避免数据训练问题成为可能。通过使用 displacy 来表示我们基于维基百科类别的 NER 系统提取的实体,展示了另一个例子。
NER 摘录示例(图片由作者提供)
在这个例子中,类别“编程语言”和“计算语言学”作为输入给出,然后在文本中搜索。
主题建模
当谈到主题建模时,我们通常指的是能够发现文本主体的“隐藏语义结构”的 NLP 工具。最近,人们讨论到“为了自动文本分析的目的,主题的定义在某种程度上取决于所采用的方法”[1]。潜在狄利克雷分配(LDA) 是流行的主题建模方法,使用概率模型从组文档中提取主题。另一个著名的方法是 TextRank ,这是一种使用网络分析来检测单个文档中的主题的方法。最近,NLP 中的高级研究还引入了能够在句子级别提取主题的方法。一个例子是 语义超图 ,“一种结合了机器学习和符号方法的优势,从句子的意思推断主题的新技术”[1]。
我们现在看到如何使用维基百科在句子和文档级别执行主题建模。
让我们考虑来自专利 US20130097769A1 的以下文本。
封装的防护服可以在污染区域穿着,以保护该防护服的穿着者。例如,当在核动力发电厂内工作或在存在放射性物质的情况下工作时,工人可能穿着密封防护服。封装的防护服可以是一次性使用类型的系统,其中在一次使用后该防护服被丢弃。在正常操作条件下,封装的防护服可以通过连接到防护服的外部气流软管接收呼吸空气。例如,空气可以由用户携带的动力空气净化呼吸器(PAPR)提供。
Sentence:
Encapsulated protective suits may be worn in contaminated areas to protect the wearer of the suit. Topics in the sentence:
Safety -> 1
Euthenics -> 1 ----Sentence:
For example, workers may wear an encapsulated protective suit while working inside of a nuclear powered electrical generating plant or in the presence of radioactive materials.
Topics in the sentence:
Safety -> 1
Euthenics -> 1
Electricity -> 1
Electromagnetism -> 1
Locale_(geographic) -> 1
Electric_power_generation -> 1
Power_stations -> 1
Infrastructure -> 1
Energy_conversion -> 1
Chemistry -> 1
Radioactivity -> 1
---- Sentence:
An encapsulated protective suit may be a one-time use type of system, wherein after a single use the suit is disposed of. Topics in the sentence:
Safety -> 1
Euthenics -> 1
Transportation_planning -> 1
Feminist_economics -> 1
Schools_of_economic_thought -> 1
Land_management -> 1
Architecture -> 1
Planning -> 1
Transport -> 1
Feminism -> 1
Urban_planning -> 1
Feminist_theory -> 1
Urbanization -> 1
Spatial_planning -> 1
Social_sciences -> 1
----Sentence:
An encapsulated protective suit may receive breathing air during normal operating conditions via an external air flow hose connected to the suit. Topics in the sentence:
Safety -> 1
Euthenics -> 1
Chemical_industry -> 1
Gases -> 1
Industrial_gases -> 1
Breathing_gases -> 1
Diving_equipment -> 1
Respiration -> 1
Electromechanical_engineering -> 1
Heat_transfer -> 1
Home_appliances -> 1
Engineering_disciplines -> 1
Automation -> 1
Building_engineering -> 1
Temperature -> 1
Heating,_ventilation,_and_air_conditioning -> 1
---- Sentence:
The air may be supplied, for example, by a power air purifying respirator (PAPR) that may be carried by the user.Topics in the sentence:
Personal_protective_equipment -> 1
Air_filters -> 1
Respirators -> 1
Protective_gear -> 1
----
专利文本的每个句子都用 SpikeX 处理,并从句子中检测到的相应维基百科页面中提取类别。我们认为主题是维基百科的类别。这样我们就有了第一个天真的话题检测。与语义超图、文本排名或 LDA 不同,这种方法在不直接引用术语的情况下找到句子主题的标签。所提取的主题标签指的是 SpikeX 匹配的维基百科页面的类别。如果我们使用这种方法来聚合每个句子的主题,我们就可以更好地表示整个文档。
段落级别的主题检测(作者图片)
对句子中类别的频率进行分类可以更广泛地查看文本的主题分布。“安全性”和“优效性”比其他类别出现得更频繁。
我们现在使用整个专利文本(可以在 Google Patent 中找到)来找到分类分布。
文档级别的主题检测(按作者分类的图片)
正如我们所看到的,我们可以自动检测整个文档的主题(或类别)(在本例中是专利)。查看前 5 个类别,我们可以推断出专利是关于什么的。这是在没有任何培训前任务的情况下完成的。
结论
十多年来,维基百科一直被用作知识的来源,并在各种应用中反复使用:文本注释、分类、索引、聚类、搜索和自动分类生成。事实上,维基百科的结构有许多有用的特性,使它成为这些应用程序的良好候选。
这篇文章展示了如何通过使用这个强大的资源来改进简单的 NLP 任务。然而,这并不是说这种方法优于其他现有技术的方法。评估自然语言处理任务准确性的典型方法精确度和召回率在这篇文章中没有给出。
此外,这种方法有优点也有缺点。主要优点是避免了培训,从而减少了耗时的注释任务。可以将维基百科视为一个巨大的训练集,其贡献者来自世界各地。对于有监督的任务(如 NER)和无监督的任务(如主题建模)都是如此。这种方法的缺点是双重的。首先,维基百科是一项公共服务,是一个由专家和非专家贡献的知识库。第二,从主题建模结果可以看出,自然语言的歧义性会导致有偏的表现。词义消歧和非专家驱动的数据管理显然会影响整个系统的可靠性。
然而,还有很大的改进余地。将维基百科视为 NLP 任务的大型开放知识库与即将到来的新范式转变相一致:所谓的人工通用智能 ( AGI ),即系统理解或学习人类可以完成的任何智力任务的假设能力。
[1]梅内塞斯,特尔莫,和卡米尔罗斯。“语义超图。” arXiv 预印本 arXiv:1908.10784 (2019)。
我要感谢Erre Quadro Srl,特别要感谢 Paolo Arduin,他开发了 SpikeX 项目,使其成为开放访问项目。
这个帖子是在研究实验室 B4DS(数据科学的商业工程) 的帮助下概念化和设计的。我要感谢团队,我的博士同事和导师,他们帮助我写了这篇文章。
见我最新发表在 PlOs one 上的《 快速检测压力下的快速创新新冠肺炎 》。
通过将树与 GLM 结合来提高绩效:基准分析
西蒙·伯杰在 Unsplash 上的照片
通过将树和 GLM 结合起来,可以获得多大的改进,它与加法模型相比如何?
统计建模的一个常见缺陷是确保建模方法适合数据结构。像逻辑回归这样的线性模型假设预测事件的可能性和独立变量之间存在线性关系。在最近研究这个话题时,我看到了这篇关于 StackExchange 的文章,讨论了使用浅层决策树作为逻辑回归的特征工程步骤。简而言之,这种策略试图通过使用树作为特征工程步骤来处理非线性数据,以将其转换为虚拟变量,然后可以在逻辑回归规范中使用。
在研究了这个策略之后,我的印象是执行这个介绍性的步骤可能会引入降低模型灵活性的缺点,同时提供模型结果的边际增加。转换数据以消除非线性,或者在模型的规格中说明非线性,可能更有用。然而,我很惊讶地看到,没有多少文章或帖子将其与其他方法进行比较!
为了了解决策树结合逻辑回归(树+GLM)的表现,我在三个数据集上测试了该方法,并根据标准逻辑回归和广义加法模型(GAM)对结果进行了基准测试,以了解这两种方法之间是否存在一致的性能差异。
树+ GLM 方法论
逻辑回归和决策树通常是最先介绍的两种分类模型。每个都有自己的陷阱。回归模型假设因变量可以用一组应用于自变量的线性函数来解释,并具有以下形式的方程:
决策树不对变量的分布做出假设,它们只是根据基尼系数等标准在决策树中创建一个新的分支。但是,决策树容易过度拟合,并且对于训练数据的变化可能不稳定。
下图说明了一元模型中因变量范围内的线性回归和决策树模型的结果:
作者图片
从上面的图表中,我们可以看到,与线性回归模型生成的平滑函数相比,决策树的预测是不连续的。虽然这对于简单的线性数据是有问题的,但是决策树策略以非线性方式改变的能力为其在非线性数据上的使用提供了理由。
为了试图弥补这两种方法的缺点,一些来源建议使用决策树作为中间步骤,这有助于消除模型中潜在的非线性。最简单的过程如下:
- 为训练数据 X 拟合一个浅层决策树 T(X)。该树将有 N 个终端节点。
- 表示为 C_n 的 n 个分类变量作为特征包含在逻辑回归规范中。
- 使用修改的数据集拟合逻辑回归。
有两种简单的方法来处理数据中的非线性。第一种选择是对原始数据使用一些其他转换步骤,使其与因变量成线性关系。不过,这种策略并不总是一种选择,其适当性因领域而异。消除它的第二种方法是将模型规范改为可以处理非线性数据的方法。
广义可加模型
有几种方法可以用来处理非线性数据,但我选择的是广义加法模型。广义加性模型是由 Trevor Hastie 和 Robert Tibshirani 在“广义加性模型”中提出的一个框架。作者接着写了统计学习的元素,这是我第一次遇到它。
它采用了逻辑回归所基于的广义线性模型的概念,放松了线性基函数的假设。在逻辑回归规范的情况下,逻辑回归的 GAM 等价于:
在这种情况下,上面的符号替代
为
在原始的逻辑回归方程中。这是因为我们已经将对每个因变量进行操作这一术语改为任意平滑函数。或者,我们可以将逻辑回归模型视为加法模型,其中:
对于某些回归系数
很明显,这为我们如何处理我们正在建模的变量提供了额外的灵活性,因此我们将在我们的数值实验中使用该规范作为 tree+logistic 回归规范的挑战者。
数据
本文中使用了三组数据进行比较,它们是:
- 合成数据集—包含 10,000 个观察值的人工数据集,包括线性和非线性变量。
- 银行数据—由银行活动生成的数据集。这里使用的数据实际上是由 Andrzej Szymanski 博士整理和格式化的,他写了一篇关于使用决策树进行逻辑回归的文章。我想包括这个,因为它可以提供一个有价值的比较,结果已经产生了。原始数据来自 Andrzej 的 GitHub。
- 成人数据—用于尝试预测收入是否超过 50,000 美元/年的人口普查数据。它可以在 UCI 机器学习库这里获得。
在下面的代码块中,我们收集了将在我们的三个测试中使用的数据,并对其进行了检查。我们首先生成合成数据,然后下载并格式化银行和成人数据集。在此之后,我们执行一些数据清理步骤,以确保我们使用的变量不被表示为字符。最后,我们将数据分为训练和测试数据:
rm(list = ls())
library(pacman)
p_load(data.table,caret,ggplot2,plotly,mgcv,rpart,magrittr,precrec,MLmetrics,partykit,gam,rmarkdown,knitr,broom,rpart.plot,reactable)
set.seed(13)
with.nonlinear <- data.table(twoClassSim(10000,
linearVars = 6,ordinal = F ) )
data.list <- list("Synthetic" = with.nonlinear,
"Banking" = fread("[https://raw.githubusercontent.com/AndrzejSzymanski/TDS-LR-DT/master/banking_cleansed.csv](https://raw.githubusercontent.com/AndrzejSzymanski/TDS-LR-DT/master/banking_cleansed.csv)") ,
"Adult" = fread("[https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data](https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data)") )
names(data.list[[3]]) <- c("age","workclass","fnlwgt","education","education-num","marital-status","occupation", "relationship", "race","sex","capital-gain","capital-loss","hours-per-week","native-country","Income-class")
# Change variables to factors for banking data-set
banking.names.for.factors <- names(data.list$Banking)[apply(data.list$Banking,MARGIN = 2, function(x){length(unique(x))})==2]
data.list$Banking[,
names(data.list$Banking)[apply(data.list$Banking,MARGIN = 2, function(x){
length(unique(x))})==2] := lapply(X = .SD,FUN = factor),
.SDcols = banking.names.for.factors]
# Change variables to factors for Adult data-set:
adult.names.for.factors <- names(data.list$Adult)[sapply(X = 1:ncol(data.list$Adult),function(x){is.character( data.list$Adult[[x]] )})]
data.list$Adult[,names(data.list$Adult)[sapply(X = 1:ncol(data.list$Adult),
function(x){is.character( data.list$Adult[[x]] )})]:= lapply(X = .SD,FUN = factor),
.SDcols = adult.names.for.factors]
data.list$Adult[,names(data.list$Adult)[sapply(X = 1:ncol(data.list$Adult),
function(x){is.integer( data.list$Adult[[x]] )})]:= lapply(X = .SD,FUN = as.numeric ),
.SDcols = names(data.list$Adult)[sapply(X = 1:ncol(data.list$Adult),
function(x){is.integer( data.list$Adult[[x]] )})]]
training.data <- list()
test.data <- list()
for( i in 1:length(data.list)){
train_inds <- sample(x = 1:nrow(data.list[[i]]) ,size = .8*nrow(data.list[[i]]))
training.data[[i]] <- data.list[[i]][train_inds]
test.data[[i]] <- data.list[[i]][-train_inds] }
names(training.data)<- names(data.list)
names(test.data)<- names(data.list)
在进行我们的基准分析之前,了解哪些变量与因变量有非线性关系是有用的。一个简单而直观的方法是绘制因变量和自变量之间的关系图,看看从视觉上看非线性关系是否明显。在这里的实现中,我使用一个 GAM 和一个变量来描述两个变量之间关系的大致形状。您可以在下面看到使用图表评估非线性的实现:
nonlinear.viz <- function(dt,dep.var,indep.var,bins = 100){
dt$y <- as.numeric(as.character(dt$y))
return.plot <- ggplot(dt,
aes_string( x = indep.var,y = dep.var) ) + stat_smooth(method = "gam",
method.args = list(family = "binomial"),) + theme_bw() + theme(axis.title = element_text(size = 16),axis.text = element_text(size = 11),plot.title = element_text(size = 20) ) +ylab(indep.var)+
xlab(dep.var)+
ggtitle(paste("Relationship between ",dep.var," and ", indep.var,sep = ""))
return(return.plot)}training.data$Synthetic[,Class := ifelse(Class == "Class1", 1,0)]
test.data$Synthetic[,Class := ifelse(Class == "Class1", 1,0)]
training.data$Adult[,"Class" := ifelse(`Income-class` == ">50K", 1,0)]
test.data$Adult[,"Class" := ifelse(`Income-class` == ">50K", 1,0)]
synthetic.plot <- lapply(X = names(training.data$Synthetic)[names(training.data$Synthetic)!="Class"],
function(x){nonlinear.viz(dt = training.data$Synthetic,dep.var = "Class",x)})
banking.plot <- lapply(X = c("age","previous","euribor3m","cons_conf_idx","cons_price_idx","nr_employed", "emp_var_rate" , "pdays"),
function(x){nonlinear.viz(dt = training.data$Banking,dep.var = "y",x)})
names(training.data$Adult) <- gsub(names(training.data$Adult),pattern = "-",replacement = "_")
names(test.data$Adult) <- names(training.data$Adult)
adult.plot <- lapply(X = c("age","education_num","capital_gain","capital_loss","hours_per_week"),
function(x){nonlinear.viz(dt = training.data$Adult,
dep.var = "Class",x )})
上面的代码将为所有变量关系生成图表。为了简洁起见,我在下面的章节中只关注非线性数据:
合成数据集
正在使用的合成数据集是从 caret 库中的 twoClassSim 函数生成的。这个函数很简单:数据集是由一个二元结果和一组线性相关或非线性相关的变量生成的。这个数据集对我们的测试很有用,因为它允许我们比较算法,而不需要关心关于问题领域的信息。下表总结了变量之间的关系:
作者图片
我使用以下公式绘制了合成数据的非线性变量与数据集中类的可能性的关系:
作者图片
我们可以看到变量是我们所期望的:在所有情况下,在实例是正类的可能性和负类的可能性之间通常存在负的非线性关系。
银行数据集
银行数据集包含 27 个独立变量。其中,7 个变量是连续变量,其余的是二元变量。该特性与因变量的关系如下表所示:
作者图片
与合成数据一样,我也制作了一组图表,显示连续特征变量和因变量(在本例中为 y)之间的关系:
作者图片
我们可以看到,在上面的每个示例图表中,因变量和自变量之间存在明显的非线性关系,使用线性基函数很难补偿这种关系。
成人数据集
与银行数据集一样,成人数据集有许多变量,这些变量是二元变量以及一组连续变量(在本例中是五个)。下表对它们进行了总结:
作者图片
我还制作了下图,展示了每个变量和因变量之间的关系:
作者图片
连续自变量和因变量之间的关系类似于银行数据集。唯一的例外是 education_num 变量,它表示在校的年数。我们可以看到,它有一个明显的积极的关系,可能是近似的线性函数,尽管凹度。
数据平衡
在拟合和试验模型之前,检查数据集中类之间的平衡是有帮助的。数据中的类别不平衡会引入偏差,这可能需要使用重采样技术来解决。
我们将使用下面显示的代码来调查这种不平衡:
sum.func <- function(x,col,model.name){
dims <- data.table( "Measure" = c("Observations","Factors") , "Count" = dim(x))
factors <- data.table( "Minority Class Percent", min(x[,.N,by = col]$N)/sum(x[,.N,by = col]$N) )
names(factors) <- c("Measure","Count")
for.return <- data.table(rbind(dims,factors) )
names(for.return)[2] <- model.name
# factors$Measure <- paste("Class = ",factors$Measure,sep = "")
return( for.return ) }
dep.vars <- c("Class", "y", "Income_class")summaries <- lapply(X = 1:length(training.data),
FUN = function(x){sum.func(training.data[[x]], dep.vars[x] ,
model.name = names(training.data)[x]) })
summaries[[1]]$Banking <- summaries[[2]]$Banking
summaries[[1]]$Adult <- summaries[[3]]$Adult
kable(summaries[[1]],digits = 3)
我对表格的结果进行了格式化,以显示与下面数据集的其余部分相比,因变量的少数类的大小。
作者图片
从上表中我们可以看出,银行和成人数据集在预测类别上存在明显的不平衡。为了纠正这一点,我们将使用合成少数过采样技术并查看结果。
快速注意:SMOTE 步骤可能需要一些时间。
banking.smote <- RSBID::SMOTE_NC(data = training.data$Banking , outcome = "y" )
adult.smote <- RSBID::SMOTE_NC(data = training.data$Adult, outcome = "Income_class" )p_load(data.table,rmarkdown,knitr)
resampled_data <- list(training.data$Synthetic,banking.smote,adult.smote)
dep.vars <- c("Class", "y", "Income_class")
names(resampled_data) <- names(training.data)
summaries <- lapply(X = 1:length(training.data),
FUN = function(x){sum.func(resampled_data[[x]],
dep.vars[x] ,
model.name = names(training.data)[x]) })
summaries[[1]]$Banking <- summaries[[2]]$Banking
summaries[[1]]$Adult <- summaries[[3]]$Adult
kable(summaries[[1]],digits = 3)
构建决策树
我们首先构建简单的决策树,作为原始分类变量的因子而不是。这些树必须很小,以避免过度生长。我已经使用最大深度为 2、3 和 4 的树测试了模型,并基于其在 ROC 和 PR AUC 中提高的准确性,选择在每个模型中使用最大深度为 4 的树。在下面的代码块中,CART 决策树与叶节点预测相匹配,然后用于在数据中创建新的因子变量。之后,新数据用于拟合逻辑回归模型。
我们首先拟合初始决策树(另一个注意:我用深度和其他细节的几次迭代测试了这个。以下选项似乎表现最佳):
synthetic.tree.2 <- rpart(data = training.data$Synthetic,
formula = Class~Linear1+Linear2+Linear3+Linear4+Linear5+Linear6+Nonlinear1 +Nonlinear2 +Nonlinear3,
control = rpart.control( maxdepth = 4 ))
banking.tree.3 <- rpart(data = banking.smote,
formula = y~.-V1, control = rpart.control( maxdepth = 4) )
adult.tree.3 <- rpart(data = adult.smote,
formula = Class~age+workclass+fnlwgt+education+education_num +marital_status+occupation+relationship+race+sex+capital_gain + capital_loss + hours_per_week+ native_country,
control = rpart.control( maxdepth = 4))synth.models <- list(synthetic.tree.2)
banking.models <- list(banking.tree.3)
adult.models <- list(adult.tree.3)tree.to.feature <- function(tree.model,dt){
require(partykit)
tree_labels <- factor( predict(as.party(tree.model), dt ,type = "node") )
return(tree_labels)}
接下来,我们将这些结果用于拟合逻辑回归模型:
synth.train.preds <- lapply(X = synth.models,FUN = function(x){ tree.to.feature(tree.model = x,dt = training.data$Synthetic) }) %>% data.frame
banking.train.preds <-lapply(X = banking.models,FUN = function(x){ tree.to.feature(tree.model = x,dt = training.data$Banking) }) %>% data.frame
adult.train.preds <-lapply(X = adult.models,FUN = function(x){ tree.to.feature(tree.model = x,dt = training.data$Adult) }) %>% data.frame
names(synth.train.preds) <- c("three.nodes")
names(banking.train.preds) <- c("four.nodes")
names(adult.train.preds) <- c("four.nodes")
training.data$Synthetic <- cbind( training.data$Synthetic,synth.train.preds )
training.data$Banking <- cbind(training.data$Banking ,banking.train.preds )
training.data$Adult <- cbind(training.data$Adult ,adult.train.preds )
synth.model.three.deep<- glm(formula = Class~Linear1+Linear2+Linear3+Linear4+Linear5+Linear6+Nonlinear1 +Nonlinear2 +Nonlinear3 + three.nodes,family = "binomial",data = training.data$Synthetic)
banking.mode.four.deep <- glm(formula = y~.-V1 ,family = "binomial",data = training.data$Banking)
adult.mode.four.deep <- glm(formula = Class~age+workclass+fnlwgt+education+education_num +marital_status+occupation+relationship+race+sex+capital_gain + capital_loss + hours_per_week+ native_country + four.nodes,
family = "binomial",
data = training.data$Adult)
最后,我们拟合了 GAM 和 GLM 模型,我们将使用这两个模型对 GAM 树模型结果进行基准测试:
# Create GAM models and the GLM models:
synth.gam <- gam(data = training.data$Synthetic,formula = Class~s(Linear1)+s(Linear2)+s(Linear3)+s(Linear4)+s(Linear5)+s(Linear6)+s(Nonlinear1) +s(Nonlinear2) +s(Nonlinear3),family = binomial)banking.gam <- gam(formula = y~s(age)+s(previous)+s(euribor3m)+s(cons_conf_idx)+s(cons_price_idx)+s(nr_employed)+s(emp_var_rate)+s(pdays)+`job_blue-collar`+
job_management+`job_other 1`+`job_other 2`+job_services+job_technician+marital_married+marital_single+ education_high.school+education_professional.course+education_university.degree+education_unknown+default_unknown+housing_unknown+
housing_yes+loan_unknown+loan_yes+poutcome_nonexistent+poutcome_success,family = "binomial",data = training.data$Banking)adult.gam <- gam(formula = Class~s(age)+(workclass)+s(fnlwgt)+(education)+s(education_num) +(marital_status)+(occupation)+(relationship)+(race)+(sex)+s(capital_gain) + s(capital_loss) + s(hours_per_week)+ (native_country),
family = "binomial",
data = training.data$Adult)
# Create GLM Modelssynth.model.glm<- glm(formula = Class~Linear1+Linear2+Linear3+Linear4+Linear5+Linear6+Nonlinear1 +Nonlinear2 +Nonlinear3 ,family = "binomial",data = training.data$Synthetic)banking.mode.glm <- glm(formula = y~.-V1-four.nodes ,family = "binomial",data = training.data$Banking)adult.mode.glm <- glm(formula = Class~age+workclass+fnlwgt+education+education_num +marital_status+occupation+relationship+race+sex+capital_gain + capital_loss + hours_per_week+ native_country ,
family = "binomial",
data = training.data$Adult)Finally, we fit the GAM and GLM models which we will be using to benchmark the GLM+Tree model results:*# Create GAM models and the GLM models:*
synth.gam **<-** gam(data **=** training.data**$**Synthetic,formula **=** Class**~**s(Linear1)**+**s(Linear2)**+**s(Linear3)**+**s(Linear4)**+**s(Linear5)**+**s(Linear6)**+**s(Nonlinear1) **+**s(Nonlinear2) **+**s(Nonlinear3),family **=** binomial)
banking.gam **<-** gam(formula **=** y**~**s(age)**+**s(previous)**+**s(euribor3m)**+**s(cons_conf_idx)**+**s(cons_price_idx)**+**s(nr_employed)**+**s(emp_var_rate)**+**s(pdays)**+**`job_blue-collar`**+**
job_management**+**`job_other 1`**+**`job_other 2`**+**job_services**+**job_technician**+**marital_married**+**marital_single**+** education_high.school**+**education_professional.course**+**education_university.degree**+**education_unknown**+**default_unknown**+**housing_unknown**+**
housing_yes**+**loan_unknown**+**loan_yes**+**poutcome_nonexistent**+**poutcome_success,family **=** "binomial",data **=** training.data**$**Banking)
adult.gam **<-** gam(formula **=** Class**~**s(age)**+**(workclass)**+**s(fnlwgt)**+**(education)**+**s(education_num) **+**(marital_status)**+**(occupation)**+**(relationship)**+**(race)**+**(sex)**+**s(capital_gain) **+** s(capital_loss) **+** s(hours_per_week)**+** (native_country),
family **=** "binomial",
data **=** training.data**$**Adult)
*# Create GLM Models*
synth.model.glm**<-** glm(formula **=** Class**~**Linear1**+**Linear2**+**Linear3**+**Linear4**+**Linear5**+**Linear6**+**Nonlinear1 **+**Nonlinear2 **+**Nonlinear3 ,family **=** "binomial",data **=** training.data**$**Synthetic)
banking.mode.glm **<-** glm(formula **=** y**~**.**-**V1**-**four.nodes ,family **=** "binomial",data **=** training.data**$**Banking)
adult.mode.glm **<-** glm(formula **=** Class**~**age**+**workclass**+**fnlwgt**+**education**+**education_num **+**marital_status**+**occupation**+**relationship**+**race**+**sex**+**capital_gain **+** capital_loss **+** hours_per_week**+** native_country ,
family **=** "binomial",
data **=** training.data**$**Adult)
模型检验
既然我们已经拟合了模型,我们可以比较它们的结果。为此,我将依赖 ROC AUC 和 PR AUC。评估结果的代码如下所示:
synth.test.preds <- lapply(X = synth.models,FUN = function(x){
tree.to.feature(tree.model = x,dt = test.data$Synthetic) }) %>% data.frame
banking.test.preds <-lapply(X = banking.models,FUN = function(x){
tree.to.feature(tree.model = x,dt = test.data$Banking) }) %>% data.frame
adult.test.preds <- lapply(X = adult.models,FUN = function(x){
tree.to.feature(tree.model = x,dt = test.data$Adult) }) %>% data.frame
names(synth.test.preds) <- c( "three.nodes")
names(banking.test.preds) <- c( "four.nodes")
names(adult.test.preds) <- c( "four.nodes")
test.data$Synthetic <- cbind( test.data$Synthetic,synth.test.preds )
test.data$Banking <- cbind(test.data$Banking ,banking.test.preds )
test.data$Adult <- cbind(test.data$Adult ,adult.test.preds )
training.for.mmdata <- data.frame(predict(banking.mode.glm,newdata = training.data$Banking, type = "response" ),
predict(banking.mode.four.deep, newdata = training.data$Banking,type = "response" ),
predict(banking.gam,newdata = training.data$Banking, type = "response" ) )
training.mdat <- mmdata(scores = training.for.mmdata,labels = training.data$Banking$y,
modnames = c("Logistic Regression", "Tree w/ GLM", "GAM"))
testing.for.mmdata <- data.frame(predict(synth.model.glm,newdata = test.data$Synthetic, type = "response" ),
predict(synth.model.three.deep, newdata = test.data$Synthetic,type = "response" ),
predict(synth.gam,newdata = test.data$Synthetic, type = "response" ) )
testing_mdat <- mmdata(scores = testing.for.mmdata,labels = test.data$Synthetic$Class,
modnames = c("Logistic Regression", "Tree w/ GLM", "GAM"))
training_ROC <- autoplot(evalmod(training.mdat),curvetype = c("ROC"))+theme(legend.position = "bottom") +ggtitle("ROC Curve - Training Data")
training_PR <- autoplot(evalmod(training.mdat),curvetype = c("PR"))+theme(legend.position = "bottom") +ggtitle("PR Curve - Training Data")
testing_ROC <- autoplot(evalmod(testing_mdat),curvetype = c("ROC"))+theme(legend.position = "bottom") +ggtitle("ROC Curve - Testing Data")
testing_PR <- autoplot(evalmod(testing_mdat),curvetype = c("PR"))+theme(legend.position = "bottom") +ggtitle("PR Curve - Testing Data")
作者图片
作者图片
作者图片
每个模型在训练和测试数据集上都表现良好,当切换到样本外数据时,模型精度几乎没有下降。在所有情况下,使用决策树作为特性工程步骤都会对性能指标有一点改进。我们还看到,在所有情况下,GAM 模型优于任何一个模型。
在一些情况下,GAM 比 GAM 树模型的表现好得多,比 GAM 树模型比逻辑回归模型的表现好得多。由此,我们可以看到,在模型规范中包含非线性数据的结构而不是转换它,可以在所有情况下提高性能。这一点很重要,因为使用 GAM 还消除了对模型的潜在决策树的调查和比较。
结论:
我比较了处理非线性数据的两种不同方法的结果。第一种是使用决策树模型作为特征工程步骤来创建一组一次性编码器,然后在逻辑回归模型中使用。第二种方法是广义的附加模型,它将数据的潜在非线性纳入模型规范。这消除了因变量和自变量之间线性关系的假设。这两种方法在三个数据集上进行了比较,以查看它们的性能是否有一致的差异。测试表明,GAM 在各个方面都优于 GAM 树模型。两个模型都优于简单的逻辑回归模型。
GAMs 优于树+GLM 模型的事实是重要的,因为它还消除了特征工程步骤和关于用于生成处理变量的树的深度的模糊性。
参考资料:
[1] T. Hastie,R. Tibshirani,J. Friedman,统计学习的要素 (2009)
[2] T. Hastie,R. Tibshirani,广义加法模型,统计科学(1986)
[3]Syzmanski,Andrzej,结合逻辑回归和决策树,走向数据科学(2020)
使用 Terraform 在 5 分钟内引导一个现代数据堆栈
设置 Airbyte、BigQuery、dbt、Metabase 以及使用 Terraform 运行现代数据堆栈所需的一切。
现代数据堆栈架构(图片由作者提供)
什么是现代数据堆栈
现代数据栈(MDS)是一系列技术的组合,使现代数据仓库的性能比传统数据仓库高 10-10000 倍。最终,MDS 节省了时间、金钱和精力。一个 MDS 的四大支柱是一个数据连接器,一个云数据仓库,一个数据转换器,一个 BI &数据探索工具。
简单的 集成通过托管的&开源工具预构建数百个现成的连接器成为可能。过去需要数据工程师团队定期构建和维护的东西,现在可以用简单用例的工具来代替。像 Stitch 和 Fivetran 这样的托管解决方案,以及像 Airbyte 和 Meltano 这样的开源解决方案,正在让这一切成为现实。
由于其高性能和成本效益,使用基于云的柱状数据仓库已经成为最近的趋势。您可以从每月 100 美元(或更少)开始支付,而不是每年支付 10 万美元购买一个本地 MPP(大规模并行处理)数据库。云原生数据仓库据说比传统 OLTP 快 10 到 10000 倍。这一类的热门选项有 BigQuery 、雪花和红移。
在过去,由于技术的限制,在数据仓库中处理数据是一个瓶颈。结果,公司不得不倾向于 ETL 而不是 ELT 来减少数据仓库的工作量。然而,随着云原生数据仓库的发展,许多数据仓库内转换工具变得流行起来。这一类别中最值得注意的是 dbt(数据构建工具)和 Dataform。
BI 工具用于处理一些转换,以减少遗留数据仓库的工作量。然而,随着现代数据堆栈的出现,BI 工具的重点已经转移到(在我看来)民主化数据访问、自助服务和数据发现。一些我认为正朝着正确方向发展的工具是 Looker 、元数据库和超集。
我们的建筑
由于涉及许多不同的工具和流程,开始使用现代数据堆栈可能会令人望而生畏。本文旨在帮助您尽可能无缝地开始这一旅程。有许多准备步骤,但是一旦完成,只需要五分钟就可以启动所有资源。
现代数据堆栈架构(图片由作者提供)
我们将使用 Terraform ,一个基础设施即代码的开源工具来提供谷歌云中的一切。如果您遵循下面的说明,这里是将要创建的资源。
- 启用了必要 API 的谷歌云项目
- 摄取:运行 Airbyte 的 GCE 实例
- 仓库:大查询数据集
- 编排(可选):运行气流的 GCE 实例
- BI 和数据发现:运行元数据库的 GCE 实例
- 不同服务的服务帐户及其 IAM 权限绑定
开始
创建一个 Google Cloud 帐户并启用计费
这个项目中的 Terraform 代码将与谷歌云平台进行交互。因此,我们创建 Google 帐户的第一步是启用计费。请注意计费页面中具有以下格式的billing ID
:######-######-######
。在下一步中,您将需要这个值。
安装 Google Cloud CLI
按照这里的说明为您各自的操作系统安装 Google Cloud SDK。安装了gcloud
CLI 后,在终端窗口中运行以下命令,并按照说明进行操作。这将让 Terraform 使用默认凭证进行身份验证。
gcloud auth application-default login
安装地形
按照这里的说明在本地安装 Terraform CLI。之后运行以下命令检查您的安装:
terraform -v
您应该会看到类似这样的内容:
Terraform v1.0.0
on darwin_amd64
+ provider registry.terraform.io/hashicorp/google v3.71.0
在本地派生或克隆此回购
https://github.com/tuanchris/modern-data-stack
您可以将此回购分支到您的帐户或克隆到您的本地机器。要克隆存储库,请运行以下命令:
git clone https://github.com/tuanchris/modern-data-stack
cd modern-data-stack
创建一个terraform.tfvars
文件
用以下内容创建一个terraform.tfvars
文件:
# The Billing ID from the first step
billing_id = ######-######-######
# The folder ID of where you want your project to be under
# Leave this blank if you use a personal account
folder_id = ""
# The organization ID of where you want your project to be under
# Leave this blank if you use a personal account
org_id = ""
# The project to create
project_id = ""
警告:这些被认为是敏感值。不要将该文件和*.tfstate
文件提交给公共回购。
自定义variables.tf
中的值
variables.tf
中的变量将用于资源的配置。
作者图片
您可以通过更改变量为不同的服务定制机器类型。如果您不想使用任何服务,请在gce.tf
文件中将其注释掉。
您还可以通过将源系统添加到源数据集字典来为源系统创建不同的数据集。
创建现代化的数据堆栈
最后,要在 Google Cloud 上提供所有这些资源,运行以下命令:
terraform apply
作者图片
研究终端中的输出,确保所有资源设置都是您想要的。键入yes
并点击enter
。
Terraform 将利用我们的现代数据堆栈创建一个谷歌云项目。整个过程大约需要 2-3 分钟。将服务安装到虚拟机实例上还需要 2-3 分钟。整个过程只需要 5 分钟或更少。
使用现代数据堆栈
检索不同服务的服务帐户
作者图片
谷歌建议不同的服务使用不同的服务账户。项目中的 terraform 代码为已经使用的不同技术创建了不同的帐户。若要检索特定服务的服务帐户,请运行以下命令:
terraform output [service_name]_sa_key
所有这些帐户的默认权限是roles/bigquery.admin
。您可以在iam.tf
文件中对此进行定制。
您得到的值是 base64 编码的。要将该值变回JSON format
,运行以下命令:
echo "[value from the previous command]" | base64 -d
您可以使用 JSON 服务帐户来验证对项目资源的服务访问。
警告:任何拥有这个服务账号的人都可以访问你的项目。
使用 Airbyte 摄取数据
Airbyte 是一款优秀的开源数据集成工具。要访问 Airbyte UI,首先,获取 gcloud SSH 命令。
作者图片
你会得到一个类似这样的命令:gcloud beta compute ssh --zone "asia-southeast1-a" "tf-airbyte-demo-airbyte" --project "tf-airbyte-demo"
。接下来,将以下内容添加到命令中,以在本地转发 Airbyte UI:-- -L 8000:localhost:8000 -L 8001:localhost:8001 -N -f
。您的最终命令将如下所示:
gcloud beta compute ssh --zone "asia-southeast1-a" "tf-airbyte-demo-airbyte" --project "tf-airbyte-demo" -- -L 8000:localhost:8000 -L 8001:localhost:8001 -N -f
**注意:**从 GCP UI 复制后,请务必删除换行符。
如果 Airbyte 实例已经完成启动,您可以通过浏览器访问localhost:8000
来访问它。如果没有,请等待五分钟,让实例完成安装。
作者图片
现在,您可以集成您的数据源,使用airbyte_sa_key
添加一个 BigQuery 目的地,并立即将您的数据放入 BigQuery 中。
您可以在虚拟机内部的/airbyte/
访问 Airbyte 安装。
使用 dbt 模型数据
dbt (数据构建工具)是一个使用 SQL 的强大的开源数据转换工具。它使数据分析师能够完成以前留给数据工程师的工作。它还帮助创建了一个全新的职位,称为分析工程师,是数据分析师和数据工程师的混合体。你可以在我的博客这里阅读更多关于这个职位的信息。
作者图片
与 Airbyte、Airflow 和 Metabase 不同,运行 dbt 不需要服务器。你可以通过访问他们的网站注册一个免费(永久)的单座账户。
利用气流协调工作流程
Airflow 是 Airbnb 打造的久经考验的工作流程编排工具。有了一个现代化的数据堆栈,希望你不需要经常使用气流。然而,在一些需要定制的情况下,气流可以成为你的首选工具。
要访问 UI,请使用 Airbyte 获得类似于上一节的 SSH 命令。使用以下命令进行端口转发:
gcloud beta compute ssh --zone "asia-southeast1-a" "tf-airbyte-demo-airflow" --project "tf-airbyte-demo" -- -L 8080:localhost:8080 -N -f
现在你可以在localhost:8080
访问气流装置。默认用户名&密码为admin
和admin
。
作者图片
您可以在虚拟机内部的/airflow/
访问气流装置。
用元数据库可视化数据
Metabase 是一个开源的数据可视化和发现工具。它超级用户友好,易于上手。
要访问元数据库 UI,请使用 Airbyte 获得类似于上述部分的 SSH 命令。然后,使用以下命令进行端口转发:
gcloud beta compute ssh --zone "asia-southeast1-a" "tf-airbyte-demo-metabase" --project "tf-airbyte-demo" -- -L 3000:localhost:3000 -N -f
作者图片
打扫
为了避免任何不必要的开销,请确保通过运行。
terraform destroy
**警告:**这将删除项目中任何持久化的数据和资源。或者,您也可以关闭未使用的 GCE 来节省成本。
结论
我希望这篇文章能够帮助您更好地理解现代数据堆栈,并且/或者激励您开始这一旅程。
请随时向我寻求反馈/问题。
快乐学习!
R 中的自助回归
回归系数的估计及其在 R 中的实现
安德鲁·雷德利在 Unsplash 上的照片
介绍
Bootstrap 是一种带替换的随机抽样方法。在它的其他应用中,如假设检验,它是检查回归系数稳定性的一种简单而有效的方法。在我们的上一篇文章中,我们探讨了置换测试,这是一个相关的概念,但是没有替换。
线性回归依赖于几个假设,公式的系数可能在 CLT 下呈正态分布。它表明,平均来说,如果我们重复实验成千上万次,这条线将在置信区间内。
bootstrap 方法不依赖于这些假设*,而是简单地执行数千次估计。
*(请注意,bootstrap 方法不违反或绕过正态假设,但它不依赖于 CLT,而是建立自己的 Bootstrap 分布,这是渐近正态的)
在这篇文章中,我们将探讨 Bootstrapping 方法和估计回归系数的模拟数据使用 r。
数据集模拟
我们将模拟来自高斯分布的一个探索变量的数据集,以及通过向探索变量添加随机噪声而构建的一个响应变量。人口数据将有 1000 个观察值。
set.seed(2021)
n <- 1000
x <- rnorm(n)
y <- x + rnorm(n)population.data <- as.data.frame(cbind(x, y))
我们将从这些数据中抽取 20 个观察值作为样本。
sample.data <- population.data[sample(nrow(population.data), 20, replace = TRUE),]
简单回归模型
让我们探索一下人口和样本数据的简单回归模型:
人口模型
样本模型
我们可以看到,截距对于样本数据是有偏差的;然而,尽管我们的样本数据集中只有 20 个观察值,斜率却非常接近于总体值。样本模型的标准误差要高得多。
如果我们绘制模型,我们可以看到这些线有多接近:
自助方法
Bootstrap 方法提出了一个问题:如果我们用替换法对数据进行重新采样并估计系数,会有多极端?
这是一个 1000 次试验的简单循环,它从我们的样本数据集中用替换重新采样这 20 个观察值,运行回归模型并保存我们在那里得到的系数。最后,我们会有 1000 对系数。
我们将取这些系数的平均值,并将它们与我们先前获得的其他模型进行比较:
我们可以看到,在这个特定的例子中,截距更接近于总体模型,斜率与样本模型的精度水平大致相同。但是我们更感兴趣的是置信区间的精确度。
我们可以看到,精度几乎与样本模型相同,截距甚至更小。
图形表示
首先,自举表示法:
仅自举线
这是我们估计的 1000 条可能的回归线。现在,让我们添加图总体、样本和平均自举线:
总体、样本、自举线
我们可以看到,它们基本上以相同的方式获取数据。现在让我们添加样本数据的置信区间:
增加了置信区间
这就完成了我们的研究:我们可以得出结论,bootstrapping 方法返回了本质上相同的结果,但在统计学上是不同的。我们不依赖假设,而是使用强力方法模拟数据。当我们对数据的来源分布有疑问时,或者想要检查系数的稳定性时,特别是对于小数据集,这可能特别有用。
你可以在 GitHub 的找到完整的代码。
结论
在本文中,我们探讨了估计回归系数的 bootstrap 方法。为了简单明了地展示这种强大的技术,我们使用了一个简单的回归模型。我们的结论是,这种方法基本上等同于 OLS 模型,但是不依赖于假设。这是一种估计系数不确定性的强有力的方法,可以与传统方法一起用来检查模型的稳定性。
感谢您的阅读!
在 LinkedIn 上连接
自举重采样
简单,直接,方便。
替代数据尺寸|照片由Rene b hmer拍摄
不,不是 Twitter Bootstrap——这种 Bootstrap 是一种数据采样的方式,最重要的是考虑数字的变化,分布的变化,分布的变化。为此,自举非常非常有效。对于数据科学家、机器学习工程师和统计学家来说,理解重采样方法至关重要。
但是为什么要使用重采样呢?我们使用重采样是因为我们只有有限的数据——至少可以说是时间和经济的限制。那么什么是重采样呢?重采样就是你取一个样本,然后你取这个样本的一个样本。你为什么要这么做?它可以让你看到有多少变化,它可以让你对你采集的样本有不同的理解。比方说,你想让 1000 人回答一项调查,但你只有 100 人。通过巧妙地对样本进行二次抽样,我们可以得到一个新的分布,我们将对样本的不确定性有所了解;我们进一步推测这与潜在人群的不确定性有关。
我们可以从数据集的多个子样本中计算统计数据,例如平均值。我们取一堆这样的物品,把它们加起来,然后除以物品的数量。自举重采样的技巧是用替换进行采样。
在 Python 中,通常在采样函数的采样代码中会有一个采样参数的布尔自变量。该布尔标志将为替换=真或替换=假。如果您有 100 个项目,随机抽样(例如,伯努利抽样)样本-记录它,并将该值替换回样本池。然后冲洗,再重复 99 次。这个子样本总体很可能是不同的。它们将具有原始 100 个值的不同比例。在大多数情况下,这些值会非常均匀地分布,但并不完美。然后重复该过程总共 10 次,以产生 1000 个样本。请注意,偶尔会发生一些奇怪的事情,例如,如果您的数据只有一个唯一的 X 值,那么您可以随机对 100 个包含 5 个 X 值的值进行二次抽样,尽管抽样总体只有一个值,但这种可能性不大,因为存在替换抽样。
对于这些样本中的每一个,我们可以计算平均值。然后我们可以看看这些平均值的分布。或者我们可以看看这些平均值。这些平均值应该非常接近总体平均值,并且应该非常接近更大样本的平均值。你可以对其他指标做同样的事情,比如标准差。自举是一种非常强大的技术,它允许你用另一种方法获得测量的不确定性。因为当你取这些值时,当你得到平均值时,所有子样本平均值的变化会告诉我们,你的样本相对于真实潜在人群的不确定性。
自助益处
Bootstrap 和重采样是广泛适用的统计方法,放宽了经典统计学的许多假设。重采样方法隐含地借鉴了中心极限定理,正如我在之前的文章中所解释的。特别是自举(和其他重采样方法):
- 允许根据有限的数据计算统计数据。
- 允许我们从数据集的多个子样本中计算统计数据。
- 允许我们做最小的分布假设。
常用的重采样方法包括:
- 随机化或排列方法(如 Fisher 精确检验)。早在 1911 年,费希尔就开创了随机化和排列方法。费希尔在他 1935 年的书中充分发展了这一理论。即使使用现代计算机,这些方法的可扩展性仍然有限。
- 交叉验证:重新取样折叠而不替换。交叉验证最初是由 Kurtz 在 1948 年提出的,现在广泛用于测试机器学习模型。
- 刀切:留一个重采样。Maurice Quenouille 最初在 1949 年提出了这种方法。折叠刀是由约翰·w·图基在 1958 年完全发展起来的,他给这种方法起了名字:这种方法是一种简单的工具,像小刀一样有多种用途。
- Bootstrap:用相等的大小和替换重新取样。bootstrap 方法首先由 Efron 和 Hinkley 在 1978 年提出,并在 1979 年由 Efron 进一步发展。
引导概述
我们将自举平均值计算为:
引导示例
为了展示 bootstrap 的威力,我将在 Jupyter 笔记本上分析来自 Galton 的身高数据集的不同人群的身高均值;以给我们一句“回归均值”w/r 到儿童身高而闻名。下面是一个参数自助估计的例子——参数,因为我们的模型有一个参数,均值,我们试图估计。
我将用一个例子来说明:
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns%matplotlib inline
导入基本库
# i imported the dataset from here, and called file_name
galton-families = pd.read_csv(file_name, index_col=0)
galton-families.head()
检查数据是否导入良好。
现在,我将按性别划分数据帧的子集,以获得数据集中男性和女性的数量。这将创建两个数据集,一个叫男性,一个叫女性。男性实际上是儿子,女性实际上是女儿。
male = galton-families[galton-families.gender == ‘male’]
female = galton-families[galton-families.gender == ‘female’]len(male), len(female)
(481, 453)
在这个数据集中,481 名男性,453 名女性看起来相当平衡。以下代码显示了这些分布中的最小值和最大值:
print(families.childHeight.min())
families.childHeight.max()
56.0
Out[11]: 79.0
它们从 56 英寸到 79 英寸不等。接下来要做的是绘制这两个直方图:
def plot_hist(x, p=5):
# Plot the distribution and mark the mean
plt.hist(x, alpha=.5)
plt.axvline(x.mean())
# 95% confidence interval
plt.axvline(np.percentile(x, p/2.), color=’red’, linewidth=3)
plt.axvline(np.percentile(x, 100-p/2.), color=’red’, linewidth=3)
def plot_dists(a, b, nbins, a_label=’pop_A’, b_label=’pop_B’, p=5):
# Create a single sequence of bins to be shared across both
# distribution plots for visualization consistency.
combined = pd.concat([a, b])
breaks = np.linspace(
combined.min(),
combined.max(),
num=nbins+1) plt.subplot(2, 1, 1)
plot_hist(a)
plt.title(a_label)
plt.subplot(2, 1, 2)
plot_hist(b)
plt.title(b_label)
plt.tight_layout() plot_dists(male.childHeight, female.childHeight, 20, a_label=’sons’, b_label=’daughters’)
plt.show()
这里需要注意的重要一点是,儿子和女儿之间有明显的重叠。你可以看到一些女儿实际上比儿子的平均身高要高。总的来说,我们可以看到女儿比儿子小,但这有意义吗?
我上一篇关于 CLT 的文章非常清楚地展示了如何区分这两种发行版。这看起来像是两个不同的发行版。然而,我们实际上可以说,儿子的分布与女儿的分布并没有太大的不同,因为用这些红色竖线表示的 95%的置信水平与平均值有些重叠。
自举手段
自助救援。我们将从样品中重新取样。Pandas 内置了从给定数据帧生成引导样本的支持。我将使用两个数据帧的sample()
方法来绘制一个引导示例,如下所示:
female.sample(frac=1, replace=True).head()
注意:使用 replace equals true 参数,您可能会多次获得同一行,或者根本不会对某些行进行采样。
上述从高尔顿数据集采样的所有子代样本具有以下平均值:
female.sample(frac=1, replace=True).father.mean()
69.0664459161148
这个女性数据框架的自举样本有 453 个女儿,平均身高为 69.1 英寸。
现在,我们将采用许多(*n_replicas*)
bootstrap 样本,并绘制样本均值的分布,以及样本均值。在下面的代码中,我们引导 1000 个子样本,每个样本的原始数据帧大小为 481 和 453。
n_replicas = 1000female_bootstrap_means = pd.Series([
female.sample(frac=1, replace=True).childHeight.mean()
for i in range(n_replicas)])male_bootstrap_means = pd.Series([
male.sample(frac=1, replace=True).childHeight.mean()
for i in range(n_replicas)])plot_dists(male_bootstrap_means, female_bootstrap_means,
nbins=80, a_label=’sons’, b_label=’daughters’)
plt.show()
自举平均值的分布完全不重叠!这证明了差异是显著的。
自举手段的差异
在下面的单元格中,我将展示如何从完整的男性+女性数据集生成引导样本,然后计算每个样本的男性和女性子女身高的均值差异,生成样本均值差异的分布。
diffs = []
for i in range(n_replicas):
sample = families.sample(frac=1.0, replace=True)
male_sample_mean = sample[sample.gender == ‘male’].childHeight.mean()
female_sample_mean = sample[sample.gender == ‘female’].childHeight.mean()
diffs.append(male_sample_mean — female_sample_mean)
diffs = pd.Series(diffs)plot_hist(diffs)
平均值中差的分布远离零。像以前一样,我们可以推断两个群体的平均值显著不同——这是为了数据集的目的,也就是说儿子和女儿确实有不同的平均身高。换句话说:置信区间不跨越零,因为它不跨越零,我们确信差异是显著的。蓝线表示来自 bootstrap 样本的儿子和女儿之间的平均差异约为 5.1 英寸,其中我们有 95%的信心,真实的人口平均差异在 4.8 英寸和约 5.5 英寸之间。这就是答案——平均来说,儿子比女儿高 5.5 英寸。
然而,我们必须验证均值差异的分布是正态的,正如 CLT 所暗示的那样。如果数据不是正态分布,那么我们不能相信 95%置信区间:
import statsmodels.api as sm
fig = sm.qqplot(diffs, line=’s’)
plt.title(‘Quantiles of standard Normal vs. bookstrapped mean’)
plt.show()
Q-Q 正态图上的点几乎在一条直线上。显然,均值差异的 bootstrap 分布确实符合 CLT,这使得我们可以相信通过 bootstrap 对原始数据集进行重采样得到的统计数据。
结论
该分析的结果证明了工具重采样对于数据科学家或机器学习工程师是多么有用。Bootstrap 不是唯一的重采样方法,有几种方法,但在 IMO 看来,它是生产模型的最佳方法,因为它对父分布做了最少的假设,并且可以很好地在许多语言和包中实现。如果没有用正确的上下文正确地处理和分析数据,数据是没有用的。正如马克·吐温的名言,“有谎言,该死的谎言,和统计数据。”
请继续,我将在贝叶斯统计和中使用 bootstrap 和回归模型构建这些概念。
在 Linkedin 上找到我
物理学家兼数据科学家——适用于新机遇| SaaS |体育|初创企业|扩大规模