TowardsDataScience-博客中文翻译-2019-二十六-
TowardsDataScience 博客中文翻译 2019(二十六)
SQL 和 Python 中的特征工程:一种混合方法
设置您的工作站,减少工作场所的混乱,保持一个干净的命名空间,并毫不费力地保持您的数据集最新

在了解 Pandas 之前,我很早就知道 SQL,Pandas 忠实地模拟 SQL 的方式引起了我的兴趣。一般来说,SQL 是为分析人员设计的,他们将数据整理成信息丰富的报告,而 Python 是为数据科学家设计的,他们使用数据来构建(并过度拟合)模型。尽管它们的功能几乎相同,但我认为这两种工具对于数据科学家高效工作都是必不可少的。根据我和熊猫相处的经验,我注意到了以下几点:
- 在探索不同的特性时,我最终得到了许多 CSV 文件。
- 当我在一个大的数据帧上进行聚合时,Jupyter 内核就会死亡。
- 在我的内核中,有多个数据帧有令人困惑的(长的)名字。
- 我的特征工程代码看起来很难看,分散在许多单元中。
当我直接在 SQL 中开始特性工程时,这些问题自然就解决了。所以在这篇文章中,我将通过一个带回家的挑战数据集来分享一些我最喜欢的技巧。如果你懂一点 SQL,是时候好好利用它了。
安装 MySQL
首先,您需要一台 SQL 服务器。我在这篇文章中使用 MySQL。你可以通过安装 MAMP、WAMP 或 XAMPP 等本地桌面服务器来获得 MySQL 服务器。网上有很多教程,值得不厌其烦的去做。
在设置好你的服务器后,确保你已经准备好三样东西:用户名,密码,端口号。通过终端输入以下命令登录(这里我们有用户名“root”,密码 1234567)。
mysql -uroot -p1234567

然后在 MySQL 控制台中创建一个名为“Shutterfly”的数据库(你可以随意命名)。这两个表将被加载到这个数据库中。
create database Shutterfly;
安装 sqlalchemy
您将需要 Pandas 和 sqlalchemy 来使用 Python 中的 SQL。我打赌你已经有熊猫了。然后通过激活您想要的环境来安装 sqlalchemy,启动 Jupyter notebook,并输入:
pip install sqlalchemy
sqlalchemy 模块还需要 MySQLdb 和 mysqlclient 模块。根据你的操作系统,这可以使用不同的命令来安装。
将数据集加载到 MySQL 服务器
在这个例子中,我们将从两个 CSV 文件中加载数据,并直接在 MySQL 中设计特性。为了加载数据集,我们需要使用用户名、密码、端口号和数据库名实例化一个引擎对象。将创建两个表:在线和订单。将在每个表上创建一个自然索引。
在 MySQL 控制台中,您可以验证表是否已经创建。
分割数据集
这可能看起来违背直觉,因为我们还没有构建任何特性。但它实际上非常简洁,因为我们所需要做的就是按照索引分割数据集。通过设计,我还包含了我们试图预测的标签(事件 2)。当加载特性时,我们将简单地用特性表连接索引。
在 MySQL 控制台中,您可以验证训练集和测试集是否已创建。
特征工程
这是最重要的部分。我直接用 Sublime 文本编写 SQL 代码,并通过将它们粘贴到 MySQL 控制台来调试我的代码。因为这个数据集是一个事件日志,所以我们必须避免将未来的信息泄露到每个数据点。正如您所想象的,每个特性都需要在历史上进行聚合!
连接表是最慢的操作,所以我们希望从每个连接中获得尽可能多的特征。在这个数据集中,我实现了四种类型的连接,产生了四组要素。细节并不重要,但是你可以在这里找到我所有的 SQL 片段。每个代码片段创建一个表。索引被保留,并且必须与训练集和测试集中的响应变量正确匹配。每个片段的结构如下:
要生成功能表,请打开一个新的终端,导航到包含 sql 文件的文件夹,并输入以下命令和密码。第一个代码片段创建了一些必要的索引来加速连接操作。接下来的四个片段创建了四个功能表。如果没有索引,连接将永远无法完成。使用索引,大约需要 20 分钟(在本地机器上还不错)。
mysql < add_index.sql -uroot -p1234567
mysql < feature_group_1.sql -uroot -p1234567
mysql < feature_group_2.sql -uroot -p1234567
mysql < feature_group_3.sql -uroot -p1234567
mysql < feature_group_4.sql -uroot -p1234567
现在,数据库中应该有下面的表。请注意,派生功能与原始事件日志分开存储,这有助于防止混淆和灾难。
加载功能
这里我写了一个从 MySQL 服务器获取数据的实用函数。
- 该函数将表名“trn_set”(训练集)或“tst_set”(测试集)作为输入,如果您只需要数据的一个子集,还需要一个可选的 limit 子句。
- 删除唯一列和大部分值缺失的列。
- 日期列映射到月份,以帮助捕捉季节性影响。
- 请注意特征表是如何连续连接的。这实际上是有效的,因为我们总是以一对一的映射来连接索引。
最后,让我们来看看 5 个培训示例及其特点。
现在您已经有了一个定义良好的数据集和特性集。您可以调整每个要素和缺失值的比例,以满足模型的要求。
对于基于树的方法,它对特征缩放是不变的,我们可以直接应用模型,只需专注于调整参数!在这里看一个普通的梯度推进机器的例子。

很高兴看到除了类别功能之外,所有有用的功能都被设计出来了。我们的努力得到了回报!此外,event2 最具预测性的特性是在 event2 中观察到多少 nulls 值。这是一个说明性的例子,我们不能用中值或平均值来代替空值,因为它们缺失的事实与响应变量相关!
摘要
如您所见,我们没有中间 CSV 文件,笔记本中有一个非常干净的名称空间,我们的功能工程代码减少到几个简单的 SQL 语句。在两种情况下,SQL 方法更加高效:
- 如果您的数据集部署在云上,您可能能够运行分布式查询。如今,大多数 SQL server 都支持分布式查询。在 Pandas 中,您需要一些名为 Dask DataFrame 的扩展。
- 如果你能负担得起实时拉取数据,你可以创建 SQL 视图而不是表。这样,每次用 Python 拉数据,你的数据都会一直是最新的。
这种方法的一个基本限制是,您必须能够用 Python 直接连接到 SQL server。如果这是不可能的,您可能需要下载一个 CSV 文件格式的查询结果,并在 Python 中加载它。
我希望这篇文章对你有所帮助。虽然我并不提倡一种方法优于另一种方法,但有必要了解每种方法的优点和局限性,并在我们的工具包中准备好这两种方法。因此,我们可以应用在约束条件下最有效的方法。
特征工程技术
介绍一些可用于为机器学习分析准备原始特征的主要技术。

Photo by "My Life Through A Lens" on Unsplash
介绍
在开始机器学习分析之前,特征工程是最重要的步骤之一。创建尽可能最好的机器学习/深度学习模型当然有助于实现良好的结果,但选择正确格式的正确功能来输入模型可以大大提高性能,从而带来以下好处:
- 使我们能够使用更简单的机器学习模型来实现良好的模型性能。
- 使用更简单的机器学习模型,增加了我们模型的透明度,从而使我们更容易理解如何进行预测。
- 减少使用集成学习技术的需求。
- 减少执行超参数优化的需求。
为了充分利用给定的数据,可以使用的其他常用技术是特征选择和提取,我在以前的帖子中已经谈到过。
我们现在将介绍一些最常见的特征工程技术。大多数基本特征工程技术包括发现数据中的不一致和通过组合/分割现有特征来创建新特征。
本文使用的所有代码都可以在我的 GitHub 账户上的链接中找到。
对于这个示例,我决定创建一个简单的数据集,它受到数据分析过程中面临的一些最常见问题的影响(例如,缺失数字、异常值、缩放问题等)。

Figure 1: Dataset Head
对数变换
使用对数变换时,原始要素的分布被变换为更接近高斯分布。这可能特别有用,尤其是当使用机器学习模型时,例如线性判别分析(LDA)和朴素贝叶斯分类器,它们假设它们的输入数据遵循高斯分布。
在本例中,我将对数据集中所有可用的数字要素应用对数变换。此外,我还决定用各自的最小值减去原始特征,然后将它们加到一起,以确保这些列中的每个元素都是正的(对数只支持正值)。

Figure 2: Logarithmically Transformed Dataset
归罪
插补是使用适当的值识别和替换数据集中缺失值的艺术。数据集中缺失值的存在可能是由许多可能的因素造成的,例如:隐私问题、使用传感器记录数据时的技术故障、人为错误等
有两种主要的插补类型:
- 数字插补:数字特征中缺失的数字可以使用许多不同的技术进行插补。使用的一些主要方法是用受影响列的总体平均值或模式替换缺失值。如果你有兴趣了解更多先进技术,你可以在这里找到更多信息。
- 分类插补:对于分类特征,缺失值通常使用整体列模式替换。在某些特殊情况下,如果分类列结构没有很好地定义,那么最好替换丢失的值,创建一个新的类别,并将其命名为“未知”或“其他”。
我们现在可以,首先,通过运行下面几行来检查哪些特性受到 NaNs(不是一个数字)的影响。

Figure 3: Percentage of NaNs in each Feature
处理缺失数字的最简单方法之一是删除受其影响的所有行。不过,最好设置一个阈值(例如 20%),只删除缺失数超过阈值的行。

Figure 4: Imputation by deleting features with excessive NaNs
另一个可能的解决方案是,对于我们的数值和分类数据,用列模式替换所有 nan。

Figure 5: Imputation using column mode
处理日期
日期对象由于其格式,对于机器学习模型来说可能很难处理。因此,有时有必要将一个日期分成多列。同样的考虑可以应用于数据分析中的许多其他常见情况(例如,自然语言处理)。
在我们的示例中,我们现在要将日期列分成三个不同的列:年、月和日。

Figure 6: Dealing with Dates
极端值
异常值是数据点的一小部分,这些数据点与特征中的其余观察值相距甚远。异常值可能被引入到数据集中,主要是因为收集数据时的错误,或者是因为我们的特定特征所特有的特殊异常。
使用四种主要技术来识别异常值:
- 数据可视化:通过目视检查数据分布来确定异常值。
- Z-Score :如果我们知道我们的特征分布是高斯分布,就使用 Z-Score。事实上,当使用高斯分布时,我们知道分布的大约 2 个标准偏差意味着大约 95%的数据将被覆盖,而远离平均值的 3 个标准分布将覆盖大约 99.7%的数据。因此,使用介于 2 和 3 之间的因子值,我们能够非常准确地删除所有异常值。如果你有兴趣了解更多关于高斯分布的信息,你可以在这里找到更多信息。
- 百分位数:是另一种识别异常值的统计方法。当使用百分位数时,我们假设数据的某个顶部和底部百分比是异常值。使用这种方法的关键点是找到最佳百分比值。一种有用的方法是在应用百分位数法检查总体结果之前和之后,将数据可视化。
- 封顶:我们不是删除异常值,而是用我们列中最高的正常值来替换它们。
其他常用于检测异常值的更高级技术有 DBSCAN 和隔离林。
继续我们的例子,我们可以从左边的两个数字特征(X2,X3)开始。通过使用 Seaborn 创建一个简单的箱线图,我们可以清楚地看到 X2 有一些异常值。

Figure 7: Examining Outliers using Data Visualization
使用 Z 分数(因子为 2)和百分位数方法,我们现在可以测试在 X2 将识别出多少异常值。如下面的输出框所示,使用 Z 分数确定了 234 个值,而使用百分位数方法删除了 800 个值。
8000
7766
7200
此外,还可以通过限制异常值来处理异常值,而不是丢弃它们。
扔掉
宁滨是一种用于平滑噪声数据的常用技术,通过将数字或分类特征划分到不同的箱中。因此,这可以帮助我们降低过度拟合的风险(尽管可能会降低我们的模型精度)。

Figure 8: Binning Numeric and Categoric Data
分类数据编码
大多数机器学习模型目前不能处理分类数据,因此通常需要在将所有分类特征输入到机器学习模型之前将其转换为数字。
可以在 Python 中实现不同的技术,例如:One Hot Encoding(转换要素)和 Label Encoder(转换标注)。
一种热编码采用一个特征,并将它分割成与原始列中不同类别的数量一样多的列。然后,它给所有没有该特定类别的行分配一个 0,给所有有该类别的行分配一个 1。使用 Pandasget _ dummies()函数可以在 Python 中实现一个热编码。
Label Encoder 通过为所有分类案例分配不同的编号并将它们存储在一列中来替代所有分类案例。
不使用具有正常特征的标签编码器是非常优选的,因为一些机器学习模型可能会混淆,并认为具有比其他值更高的值的编码情况可能对它们更重要(按层次顺序考虑它们)。当使用一种热编码时,这种情况不会发生。

Figure 9: Difference between One Hot Encoding and Label Encoding [1]
我们现在可以继续将数据集划分为特征( X )和标签( Y ),然后分别应用一个热编码和标签编码器。
缩放比例
在大多数数据集中,数字特征都有不同的范围(例如,身高与体重)。虽然,对于一些机器学习算法来说,将我们的输入特征限制在一个定义的范围内是很重要的。事实上,对于一些基于距离的模型,如朴素贝叶斯、支持向量机和聚类算法,如果它们都有不同的范围,那么几乎不可能比较不同的特征。
缩放特征的两种常见方式是:
- 标准化:缩放输入要素,同时考虑它们的标准偏差(使用标准化,我们的变换后的要素将看起来类似于正态分布)。这种方法可以降低异常值的重要性,但由于标准差的差异,可能会导致要素之间的不同范围。通过使用StandardScaler()可以在 scikit-learn 中实现标准化。
- 归一化:在 0 和 1 之间的范围内缩放所有特征,但是会增加异常值的影响,因为没有考虑每个不同特征的标准偏差。使用minmax scaler()可以在 scikit-learn 中实现归一化。
在本例中,我们将使用标准化,然后我们将处理异常值。如果您正在处理的数据集没有受到离群值的广泛影响,scikit-learn 还提供了另一个名为 的标准化函数,该函数可以在默认情况下减少离群值的影响。
自动化特征工程
为了使特征工程过程自动化,在过去几年中已经开发了不同的技术和软件包。当对我们的数据集进行第一次分析时,这些肯定会产生有用的结果,但是它们仍然不能完全自动化整个特征工程过程。关于数据的领域知识和数据科学家在对原始数据建模以最适合分析目的方面的专业知识是不可替代的。Python 中最流行的自动特征选择库之一是 Featuretools 。
结论
现在是时候通过使用随机森林分类器来最终测试我们抛光的数据集预测准确性了。如下所示,我们的分类器现在成功地实现了 100%的预测准确率。
1.40625
[[1204 0]
[ 0 1196]]
precision recall f1-score support
0 1.00 1.00 1.00 1204
1 1.00 1.00 1.00 1196
micro avg 1.00 1.00 1.00 2400
macro avg 1.00 1.00 1.00 2400
weighted avg 1.00 1.00 1.00 2400
希望您喜欢这篇文章,感谢您的阅读!
联系人
如果你想了解我最新的文章和项目,请在媒体上关注我,并订阅我的邮件列表。以下是我的一些联系人详细信息:
文献学
[1]什么是 One Hot Encoding,如何进行?迈克尔·德尔索尔,中号。访问地址:https://medium . com/@ michaeldelsole/what-one-hot-encoding-and-how-do-it-f0ae 272 f 1179
Python 中的特征工程技术
特征工程是每个机器学习项目中至关重要的一部分。在这篇文章中,我们将围绕一些技术来处理这项任务。请不要犹豫提出新的想法,我会尽可能保持这篇文章的更新。

合并训练和测试
当执行特征工程时,为了有一个通用的模型,如果你有两个文件,只需将它们合并(训练和测试)就可以了。
df = pd.concat([train[col],test[col]],axis=0)
#The label column will be set as NULL for test rows# FEATURE ENGINEERING HEREtrain[col] = df[:len(train)]
test[col] = df[len(train):]
记忆减少
有时,列的类型编码不是最佳选择,例如,用 int32 编码只包含 0 到 10 的值的列。最流行的函数之一是使用一个函数,通过将列的类型转换为尽可能最好的类型来减少内存的使用。
移除异常值
移除异常值的常用方法是使用 Z 值。
如果您希望删除至少有一列包含异常值(用 Z 得分定义)的每一行,您可以使用以下代码:
from scipy import stats
df[(np.abs(stats.zscore(df)) < 3).all(axis=1)]
南诡计
一些基于树的算法可以处理 NAN 值,但他会在 NAN 和非 NAN 值之间有一个步骤,这有时可能是无意义的。一个常见的技巧是用低于所考虑的列中最低值的值填充所有 nan 值(例如-9999)。
df[col].fillna(-9999, inplace=True)
分类特征
您可以使用标注编码来处理分类要素,将其作为数字来处理。您也可以决定将它们视为类别。我建议两者都尝试一下,并通过这一行代码(在标签编码之后)保持交叉验证。
df[col] = df[col].astype('category')
组合/拆分
有时字符串变量在一个变量中包含多个信息。比如FRANCE_Paris。您将需要使用正则表达式或使用拆分方法来拆分它,例如:
new **=** df["localisation"].str.split("_", n **=** 1, expand **=** True)
df['country'] = new[0]
df['city']=new[1]
否则,两个(字符串或数字)列可以合并为一列。例如,包含法国某个部门(75 代表巴黎)和地区代码(001)的列可以变成邮政编码:75001)
df['zipcode'] = df['departement_code'].astype(str)
+'_'
+df['disctrict_code'].astype(str)
线性组合
特征工程的一个共同特点是应用简单的数学运算来创造新的特征。例如,如果我们有一个矩形的宽度和高度,我们可以计算面积。
df['area'] = df['width'] * df['height']
计数栏
创建列从流行的value_count方法创建列对于基于树的算法来说是一种强大的技术,用来定义一个值是稀有的还是常见的。
counts = df[col].value_counts.to_dict()df[col+'_counts'] = df[col].map(counts)
处理日期
为了分析事件,处理日期和解析日期的每个元素是至关重要的。
首先,我们需要转换日期列(通常被认为是熊猫的字符串列)。其中最重要的一个领域是知道如何使用format参数。强烈推荐将这个站点保存为书签!😃
例如,如果我们要用下面的格式转换一个日期列:30 Sep 2019我们将使用这段代码:
df['date'] = pd.to_datetime(df[col], format='%d %b %Y')
一旦您的列被转换为datetime,我们可能需要提取新闻列中的日期部分:
df['year'] = df['date'].year
df['month'] = df['date'].month
df['day'] = df['date'].day
聚合/组统计
为了继续检测稀有和常见的值,这对于机器学习预测非常重要,我们可以决定基于静态方法来检测一个值在子组中是稀有还是常见。例如,我们想通过计算每个子类的平均值来了解哪个智能手机品牌用户的通话时间最长。
temp = df.groupby('smartphone_brand')['call_duration']
.agg(['mean'])
.rename({'mean':'call_duration_mean'},axis=1)df = pd.merge(df,temp,on='smartphone_brand',how=’left’)
利用这种方法,ML 算法将能够辨别哪个呼叫具有与智能手机品牌相关的 call_duration 的非公共值。
标准化/规范化
规范化有时非常有用。
为了实现列自身的规范化:
df[col] = ( df[col]-df[col].mean() ) / df[col].std()
或者可以根据另一列对一列进行规范化。例如,如果您创建一个组统计数据(如上所述),表明每周call_duration的平均值。然后,您可以通过以下方式消除时间依赖性
df[‘call_duration_remove_time’] = df[‘call_duration’] — df[‘call_duration_week_mean’]
新的变量call_duration_remove不再随着时间的推移而增加,因为我们已经针对时间的影响对其进行了标准化。
Ultime 特色工程技巧
每一篇专栏文章都为预处理和模型训练增加了时间计算。我强烈建议测试一个新特性,看看这些特性如何改进(或不改进…)您的评估指标。 如果不是这样,你应该删除创建/修改的特征。
特征工程时间
计算圆形/周期性特征的统计数据。
在本帖中,我们将讨论一种处理周期性特征的数学方法。
这可用于计算平均值。
(例如 23:50,22:40,00:30 的平均时间是多少?)
它也是聚类的一个有用的距离度量。
(比如 00:01 比 00:10 更接近 23:59,能不能优雅地解决这个?)
同样的方法在特征工程中证明是有用的,例如 ML 模型的圆形特征“一天中的时间”或“一年中的一天”或“方位角”。

问题:
- 根据事件时间对事件进行聚类。
- 当一天中的时间是重要特征时,学习一些结果的模型。
- 计算每个用户完成一个动作几次的典型时间。
一个有问题的问题:
你如何处理集群中跨越到第二天的事件?一些用户可能是夜猫子或来自另一个时区;如果他们的活动持续到午夜,数学就变得更难了。
如果用户在 23:57、23:58 和 23:59 发送消息,计算平均值是简单的。用户迟到一分钟怎么办?显然我们不想把 00:00 加到平均值上。
假设您正试图预测某个依赖于一天中某个时间的结果,一个简单的模型(回归)将在午夜遭遇中断。
尝试 1,暴力:
让我们假设我们的用户必须在某个时刻离线,在某个时刻有一个边界,在其附近没有任何活动。
然后我们可以“移动”到那个时区,添加几个小时来将日期边界放在空白处。
问题:
我们可能有遍布全球的用户,他们有不同的活动模式,我们需要找到每个用户的时移。往好里说,这是非常低效的,往坏里说,这可能是不可能的。更糟糕的是,如果我们的假设是错误的呢?如果我们看不到活动和不活动之间如此清晰的界限,会怎么样?
灵感:平均风向。

Wind direction off the East coast.
看着风的地图,我们对风的平均方向(和大小)有很好的直觉。
359 和 1 之间的任意区别丝毫不会困扰我们,因为我们取的是向量平均值。当我们用手指指向一个方向时,360°的数学跳跃并不重要。
我们如何将这种直觉形式化?让我们把方向描述为一个二维向量(x,y),而不是一个单一的标量,也就是“航向”。
是的,让我们做那件事,有时间
让我们将时间映射到 24 小时制的圆周上



The geometric representation of time of day lends itself to geometric clustering, averaging. (Code to generate these if you want to play around with them is provided in a notebook linked below.)
如果我们想要聚类,我们在 2d 空间(x,y)中聚类。如果要取平均值,就取点(重心)的几何平均值。
如果我们想在模型中使用时间作为特征,我们使用向量,(x,y)。
当平均值不在圆上时会发生什么。
尽管多于一个点的任何平均值都在圆内;范围越大,平均值离圆周越远。



Left: with small variance, the center is very close to the circle’s edge and shows the average time; Center: the high variance of the points puts the center inside the circle. The direction of the center indicates the average time, in this case 3:00. Right: A measure that is analogous to standard deviation can be derived by examining the arc defined by the chord that is bisected by that center.
摘要
通过将一天中的时间映射到代表 24 小时的 2D 圆,我们可以缓解时针从 23 到 0 时出现的不连续性。
这也适用于一年中的天,或者实际的方向。
这个新的 2D 向量(可以简单地是模型中的两列)表现得非常自然,它定义了一个将 23:59 和 00:01 放在一起的度量。同样,它把 12 月 31 日和 1 月 1 日放在一起。NNE 和 NNW 不关心它们在 360 — (0)的不同侧
这些不连续点一开始是任意的,但是只要我们只用一个维度,它们就一定会出现在某个地方。在 2D,一切又变得美好起来。
代码:
你可以克隆这个笔记本来玩代码。(还包含 pySpark 计算平均值的可行示例。)
附录,数学和操作方法:
把时间映射到圆周上很简单。
- 把时间转换成一个单一的数字[0,24]{或者[0,1],这可能是免费的}所以 12:36 可能是 12.6 {或者 0.525}。
- 然后将这个数字乘以 15,得到 360。
- 然后取 Sin 和 Cos 得到 Y 和 X 坐标。(开心注:SQL 有三角函数,别忘了换算成弧度)
- 要计算平均值,只需独立取 X 和 Y 的平均值。
- 使用(E(Y)、E( X ))变换回时间,然后反向变换(弧度到角度,取消拉伸到 24H)
奖金,标准发展:
- 用给出的勾股定理 X 和 Y 计算半径, r 。
- 代表标准差的圆弧由arccos(r)给出。证明这一点是留给读者的练习。
特征工程-将数据集转换成机器可用的格式
创建可以帮助机器学习算法工作的功能

这一步使您的数据为最终训练做好准备。特性工程的方法基于对数据的良好理解,正如我在之前关于 EDA 的文章中提到的。我将继续使用 Kaggle 上的住房数据集(在上一篇文章中用于解释 EDA),来说明我是如何对它进行特征工程的。
测试和训练数据集都被合并,以从特征工程开始。我按照以下步骤得出最终数据集:
剔除异常值
在上一篇文章中,我举例说明了如何识别异常值。第一步是删除所有带有离群值的索引,因为离群值往往会向机器传递关于数据集的不准确信息。
将分类变量转换为数字变量
其次,分类变量需要转换为数值变量,因为大多数机器学习算法只能读取数值。
请注意,一些分类变量(如 Alley、GarageQual、BsmtQual)缺少一些值,而其他变量则由“Po”、“Gd”、“TA”等值填充。数据描述文件有助于解释这些变量的含义。在这种情况下,缺失的数据仅仅意味着房子可能没有小巷、车库或地下室。因此,我使用下面的代码来估算缺失值,并将现有值转换为数字值:
combined.Alley = combined.Alley.map({np.nan:0, 'Grvl':1, 'Pave':2})
combined.BsmtCond = combined.BsmtCond.map({np.nan:0, 'Po':1, 'Fa':2, 'TA':3, 'Gd':4, 'Ex':5})*combined is the name of the merged dataset of train and test
这是可能的,因为赋值就像等级变量,例如“Fa”比“Po”好,“TA”比“Fa”好。
但是对于那些值不表示等级的分类变量,pandas 的 get_dummies 函数将行转换为列。
输入分类和数值特征的缺失值
但在此之前,重要的是估算所有分类变量的缺失值。例如,mischaracter 和 MasVnrType 缺少值,这可能表明它们不存在。有一个简单的方法来交叉检查这个。如果 MiscVal 和 MasVnrArea 是对应于缺失值的零,则假设是正确的,可以估算如下:
combined.MiscFeature.fillna('NA', inplace=True)
combined.MasVnrType.fillna('None', inplace=True)
对于 KitchenQual 或 SaleType 等其他特性,使用值的模式对它们进行估算更安全。
数字特征中缺失的值可以通过放置零来估算,具体取决于特征。例如,GarageCars 列中缺少值可能表示根本没有车库,因此零是最合适的值。
GarageYrBlt 也有缺失值,我用零来估算,因为不可能猜出缺失的年份!
处理时间变量
数据集中有 5 个时间变量。将它们转换成年龄变量似乎更有意义,因为它们提供了关于特征寿命的更多信息。类似于这样的事实,陈述“X 先生在 66 岁时去世”比陈述“X 先生在 2019 年去世”包含更多的信息。因此引入了三个年龄特征:
combined['Age'] = combined.YrSold - combined.YearBuilt
combined['AgeRemod'] = combined.YrSold - combined.YearRemodAdd
combined['AgeGarage'] = combined.YrSold - combined.GarageYrBlt
“AgeGarage”列的值似乎很少,都在 2000 左右!这是因为 GarageYrBlt 的缺失值被估算为零,如前一节所述。根据以下代码,所有这些异常值都被替换为低于某个阈值的最大可能值:
max_AgeGarage = np.max(combined.AgeGarage[combined.AgeGarage < 1000])
combined['AgeGarage'] = combined['AgeGarage'].map(lambda x: max_AgeGarage if x > 1000 else x)
负值,如果有的话,应该被替换为零,并且原始时间特征现在可以被丢弃。
将分类列转换为虚拟列
分类特征现在可以用示例代码转换成虚拟列:
Foundation_dummies=pd.get_dummies(combined['Foundation'], prefix='Foundation_', drop_first=True)
combined=pd.concat([combined, Foundation_dummies], axis=1)
combined.drop('Foundation', axis=1, inplace=True)
prefix 参数有助于以后识别列。我们只需要保留 k 列中的 k-1 列,因此 drop_first 被设置为 True。要了解更多关于这些论点的信息,请阅读此处的。
虚拟数据集与原始数据集连接在一起,实际的列被删除。
数据集几乎建立起来了,但还没有。
完整的数据集现在有 209 列!数据太多,机器消化不了。更多的特征会导致更多的噪声,并最终导致过度拟合。
删除不重要的功能
问题是如何决定放弃哪个特性?幸运的是 XGBoost 有一个答案。
组合的数据集被切片以获得训练集,并且 xgboost 对象被拟合在其上。
X = combined[:-1459]
y = targets
y = y.drop(index_drop).reset_index(drop=True)xgb = XGBRegressor()
xgb.fit(X, y)
然后计算特征重要性并排序:
imp = pd.DataFrame(xgb.feature_importances_ ,columns = ['Importance'],index = X.columns)
imp = imp.sort_values(['Importance'], ascending = False)
print(imp)
结果是这样的:

有很多功能的重要性为零。为了确定到底应该保留多少特性,我使用了一个名为 RFECV 的函数。
RFECV 告诉我们应该保留多少和哪些特性。它有一个评分参数,我用 RMSE(均方根误差)来表示,因为 Kaggle 练习也将根据相同的条件进行评估。以下函数定义 rmse,并使其成为计分器:
def rmse(y_true, y_pred):
return np.sqrt(np.mean((y_true-y_pred)**2))rmse = make_scorer(rmse, greater_is_better=False)
因为目标是减少 RMSE,所以“越大越好”的参数被设置为假。最后,RFECV 在 X 和 y 数据集上拟合,给出了最佳特征数 67!
rfecv = RFECV(estimator=xgb, step=1, cv=3, n_jobs=-1, scoring=rmse)
rfecv = rfecv.fit(X, y)
print("Optimal number of features : %d" % rfecv.n_features_)
绘制特征与交叉验证分数的关系为我们提供了下图:
plt.figure()
plt.xlabel("Number of features selected")
plt.ylabel("Cross validation score")
plt.xticks(np.arange(0,200,10))
plt.plot(range(1, len(rfecv.grid_scores_) + 1), rfecv.grid_scores_)
plt.show()

在大约 67 个特征之后,交叉验证分数几乎没有改善。这里的下一步是检查函数建议哪些列。
features_kept = X.columns.values[rfecv.support_]
X = X[features_kept]
X 的特点是:
LotFrontage
LotArea
Alley
OverallQual
OverallCond
MasVnrArea
ExterQual
ExterCond
BsmtQual
BsmtCond
BsmtExposure
BsmtFinType1
BsmtFinSF1
BsmtFinSF2
BsmtUnfSF
TotalBsmtSF
HeatingQC
CentralAir
1stFlrSF
2ndFlrSF
GrLivArea
BsmtFullBath
FullBath
HalfBath
KitchenAbvGr
KitchenQual
Functional
FireplaceQu
GarageType
GarageFinish
GarageCars
GarageArea
GarageQual
GarageCond
PavedDrive
WoodDeckSF
OpenPorchSF
EnclosedPorch
ScreenPorch
PoolArea
Fence
Age
AgeRemod
AgeGarage
MSZoning__FV
MSZoning__RL
MSZoning__RM
LotConfig__CulDSac
Street__Pave
Foundation__CBlock
Neighborhood__BrkSide
Neighborhood__ClearCr
Neighborhood__Crawfor
Neighborhood__OldTown
Neighborhood__Sawyer
Neighborhood__StoneBr
Condition1__Norm
Exterior1st__BrkComm
Exterior1st__BrkFace
Exterior1st__HdBoard
Exterior2nd__BrkFace
Exterior2nd__Wd Shng
HouseStyle__SLvl
SaleType__New
SaleCondition__Family
SaleCondition__Normal
MSSubClass__class2
这里有一些功能,根据我们在 EDA 中的分析,这些功能本不应该出现在这里。例如,我们已经推断出公摊面积不是预测销售价格的重要特征。
因此可以删除 PoolArea。
其次,有一些变量是相互关联的。因此,只保留其中一个是有意义的。运行命令时(参考上一篇关于 EDA 的文章):
print(corr['SalePrice'].sort_values(ascending=False))SalePrice 1.000000
OverallQual 0.790982
GrLivArea 0.708624
GarageCars 0.640409
GarageArea 0.623431
TotalBsmtSF 0.613581
1stFlrSF 0.605852
FullBath 0.560664
TotRmsAbvGrd 0.533723
YearBuilt 0.522897
YearRemodAdd 0.507101
GarageYrBlt 0.486362
MasVnrArea 0.477493
Fireplaces 0.466929
BsmtFinSF1 0.386420
LotFrontage 0.351799
WoodDeckSF 0.324413
2ndFlrSF 0.319334
OpenPorchSF 0.315856
HalfBath 0.284108
LotArea 0.263843
BsmtFullBath 0.227122
BsmtUnfSF 0.214479
BedroomAbvGr 0.168213
ScreenPorch 0.111447
PoolArea 0.092404
MoSold 0.046432
3SsnPorch 0.044584
BsmtFinSF2 -0.011378
BsmtHalfBath -0.016844
MiscVal -0.021190
Id -0.021917
LowQualFinSF -0.025606
YrSold -0.028923
OverallCond -0.077856
MSSubClass -0.084284
EnclosedPorch -0.128578
KitchenAbvGr -0.135907
因此,GarageArea,1stFlrSF 可能会被删除,我们减少到 64 个功能。
这里的结论是,一个人不应该盲从任何函数的结果。我们应该每时每刻问自己,这是否有意义。因此,据说通过机器学习实现高精度不仅是一门科学,也是一门艺术。特征工程通常是一个迭代过程,在实施模型时,可能需要进一步修改数据集以实现更高的精度。
我继续使用这个数据集,并用不同的算法进行训练,这将在我的下一篇博客中介绍。
欢迎提出进一步改进功能工程的建议!
面向 NLP 和 Python 的 NLTK 特征工程

Herman Melville (1860), ‘Journal Up the Straits,’ Library of Congress
上周,我复习了自然语言处理(NLP)的自然语言工具包(NLTK)的一些基本功能。通过将这些基本函数应用到赫尔曼·梅尔维尔的《莫比·迪克》中,我继续了我的 NLP 之旅。文本文档由 Gutenberg 项目提供,该网站上的几本书可以通过 python NLTK 包获得。
我在之前的博客中详细描述了清理过程,我不得不清理两部电视剧的各种脚本。这一过程将根据手头的任务而变化。今天我只是用 NLTK 方法探索数据的文本。一般流程是:
- 加载语料库或文档的文本
- 对原始文本进行标记
- 删除停用词和标点符号
- 应用词干或词汇化
古腾堡项目的文本莫比迪克的解释已经相当干净了,也就是说,只有标点符号需要从文本中删除,所有的单词都需要小写。我还删除了序言和序言,因为它不是梅尔维尔的原始故事的一部分。
我在文件中保留的唯一标点符号是撇号。NLTK 允许您定制 regex 模式,将字符视为一个标记。例如,任何带撇号的单词都被视为一个标记,即 d'ye 是一个标记,而不是两个单独的标记。应用正则表达式模式的代码是:nltk.regexp_tokenize(raw_text, pattern),其中raw_text是代表文档的字符串,pattern是代表您希望应用的正则表达式模式的字符串。
探索文本
一个单词包是一个单词在文本中出现的次数。这个计数可以是文档范围的、语料库范围的或语料库范围的。

A visualization of the text data hierarchy
NLTK 提供了一个简单的方法,可以创建一个单词包,而不必手动编写代码来遍历一个标记列表。首先创建一个FreqDist对象,然后应用令牌列表。最后,您可以使用.most_common()方法查看最常见的令牌。
要查看删除停用词后文本中唯一单词的总数,只需查看频率分布列表的长度:len(moby_dick_freqdist)。每个令牌的计数都是名义上的,如果您只研究一个文档,这是很好的。如果你有几个文档(一个语料库),那么标准化计数对于比较不同长度的文档是必要的。这可以通过对频率分布列表中每个元组的值求和,然后将每个计数除以该总数来实现。
How to make a normalized frequency distribution object with NLTK
二元模型、n 元模型和 PMI 得分
每个标记(在上面的例子中,每个唯一的单词)代表文档中的一个维度。在停用词被移除后和目标变量被添加前,莫比迪克有 16939 个维度。为了减少文档的维数,我们可以将两个或多个单词组合起来,如果它们作为一个标记而不是两个单独的标记来传达大量信息。如果我们选择一对词,这将被称为二元组。
让我们检查句子“我爱热狗。”有三对词,(“我”、“爱”)、(“爱”、“热”)、(“热”、“狗”)。前两个单词对是随机的,在一起不表达任何重要的意思。然而, hot 和 dogs 这两个词合在一起传达了一种食物的名称。如果我们将这最后一对视为一个记号,那么句子的维度就减少了一个。这个过程被称为创建二元模型。一个 ngram 与一个二元模型不同,因为一个 ngram 可以将 n 个单词或字符作为一个令牌来处理。
NLTK 提供了一个二元模型方法。导入 NLTK 后,您可以将 bigram 对象nltk.collocations.BigramAssocMeasures()存储为一个变量。然后,您可以利用 NLTK 的 collector 和 scorer 方法来查看相关联的二元模型及其规范化的频率分数。
How to make bigrams with NLTK
score_ngrams()方法返回二元模型列表及其相关的归一化频率分布。

The top five bigrams for Moby Dick
不是所有单词对都能传达大量信息。NLTK 提供了逐点交互信息 (PMI)计分器对象,该对象分配一个统计度量来比较每个二元模型。该方法还允许您筛选出出现次数少于最小次数的令牌对。在调用 bigram 方法后,您可以应用一个频率过滤器.apply_freq_filter(5),其中 5 是 bigram 必须出现的最小次数。最后,使用 PMI 参数score_ngrams(bigram_measures.pmi)调用 scorer 方法。

The top five bigrams by PMI score for Moby Dick
结论
NLTK 有许多强大的方法,允许我们用几行代码评估文本数据。二元模型、n 元模型和 PMI 分数允许我们降低语料库的维度,这在我们继续进行更复杂的任务时节省了我们的计算能量。一旦文档被清理,就可以很容易地应用 NLTK 方法。
特征工程与急流:NFL 数据碗
在开始这篇文章之前,我想减轻你对从 pandas 转换到 RAPIDS cudf 的怀疑, RAPIDS cudf 使用与 pandas 相同的 API!
RAPIDS 正在将表格数据集上的传统数据科学工作流迁移到 GPU 上。最近, George Sief 发表了一篇关于走向数据科学的文章,表明 RAPIDS cudf 库可以在 5.12 毫秒内计算包含 1 亿行的给定列的平均值,而在 pandas 上则需要 82.2 毫秒。本文将在 Kaggle NFL 数据碗挑战赛的特性工程背景下,进一步探讨 RAPIDS 和 cudf 实现的加速。我们在使用来自 Digital Storm 的 2 个 NVIDIA Titan RTX 24GB GPU 的 Data Science PC 上,几乎每个设计的功能都实现了至少 10 倍的速度提升。
在本文中,我们将从 NFL 数据碗数据集构建新的要素,如身体质量指数、防守方向与距离、余量和紧急程度。我们还将通过将特性列中的每个值除以特性的最大值来标准化特性。在每种情况下,我们都将对 pandas 和 RAPIDS cudf 库进行基准测试!
你可以从本文中的代码访问 Jupyter 笔记本,这里我也制作了一个视频来解释这个如果你想继续下去的话!

首先,我们将 NFL 数据碗数据集的大小扩大 16 倍,以展示 RAPIDS / cudf 在大型数据集上的强大功能。之所以选择 NFL 数据集,是因为它便于理解特征工程的概念。RAPIDS 确实最适合拥有数百万条记录的大型数据集。
# Increase the dataset size by stacking it on top of itself
pd_data = pd.concat([pd_data, pd_data], ignore_index=True)
# repeated 3 more times
现在我们将把熊猫数据帧转换成 cudf 和 dask-cudf 数据帧!
import cudf
import dask_cudf
cudf_data = cudf.from_pandas(pd_data)
dask_data = dask_cudf.read_csv('./cleaned_data.csv')
上面的代码突出了 RAPIDS 最好的部分之一。它完全模仿 pandas API,因此数据科学家不必担心迁移到新语法会带来的头痛。在这段代码中,我们用。from_pandas()语法和 dask 文档建议您直接从 csv 文件加载数据,所以我们这样做。
列值平均值
根据最近从 George Sief 开始的关于数据科学的教程,我们观察了计算数据帧中给定列的平均值所花费的时间:
pd_data['PlayerHeight'].mean() # 29.1 ms
cudf_data['PlayerHeight'].mean() # 466 µs
dask_data['PlayerHeight'].mean() # 1.46 ms
在这种情况下,我们看到 cudf 比 pandas 快了大约 60 倍。Cudf 在这里的表现优于 dask-cudf,但我怀疑这是因为 dask-cudf 对数据集进行了更好的优化,这种数据集一开始就不可能与 pandas 进行比较。
防守者进入禁区 vs 距离
特色创意致谢:https://www . ka ggle . com/CP mpml/initial-wrangling-Voronoi-areas-in-python
这个功能将会查看与防守方向相比第一次进攻所需的码数。
pd_data['DefendersInTheBox_vs_Distance'] = pd_data['DefendersInTheBox'] / pd_data['Distance'] # 36.9 mscudf_data['DefendersInTheBox_vs_Distance'] = cudf_data['DefendersInTheBox'] / cudf_data['Distance'] # 3.1 ms
身体质量指数
特色创意致谢:https://www . ka ggle . com/bgmello/neural-networks-feature-engineering-for-the-win
该功能将根据身高和体重计算跑步者的身体质量指数。这是我们获得最低加速的特性。
pd_data['BMI'] = 703 * (pd_data['PlayerWeight']/pd_data['PlayerHeight']**2) # 64.3 mscudf_data['BMI'] = 703 * (cudf_data['PlayerWeight'] / cudf_data['PlayerHeight']**2) # 15.7 ms
边缘
这个功能将计算得分差异,也许如果球队赢/输了一些金额,他们更有可能成功运行球。
pd_data['Margin'] = pd_data['HomeScoreBeforePlay'] - pd_data['VisitorScoreBeforePlay'] # 32 mscudf_data['Margin'] = cudf_data['HomeScoreBeforePlay'] - cudf_data['HomeScoreBeforePlay'] # 3.46 ms
紧急
此功能将计算季度利润给出的紧急程度。也许如果现在是第四节,比赛接近尾声,球员会把球跑得更远。
pd_data['Urgency'] = pd_data['Quarter'] * pd_data['Margin'] # 33.6ms
cudf_data['Urgency'] = pd_data['Quarter'] * pd_data['Margin'] #3.4ms
最大值标准化
一种惰性规范化技术是将一列中的所有值除以该列中的最大值。这样做是为了在[0,1]之间缩放值,并便于训练机器学习模型。
for col in pd_data:
pd_data[col] /= pd_data[col].max() # 1.28 sfor col in cudf_data:
cudf_data[col] /= cudf_data[col].max() # 117 ms
急流城特色工程:总结性思考
本笔记重点介绍了一些例子,在这些例子中,RAPIDS 实现了 ETL 操作的大规模加速,比如特性工程和标准化。这对于电子健康记录、保险数据、生物信息学和其他各种最好用表格数据格式描述的数据领域的应用程序来说是非常令人兴奋的!感谢阅读,请查看下面解释这一点的视频:
https://www.youtube.com/watch?v=A9lgUwA8RrY&t = 13s
特征提取技术
关于如何使用特征提取技术(如 PCA、ICA、LDA、LLE、t-SNE 和 AE)降低数据集维数的端到端指南。

(Source: https://blog.datasciencedojo.com/curse-of-dimensionality-python/)
介绍
如今,处理包含数百(甚至数千)个要素的数据集变得非常普遍。如果特征的数量变得相似(甚至更大!)多于数据集中存储的观察值的数量,那么这很可能导致机器学习模型遭受过度拟合。为了避免这种类型的问题,有必要应用正则化或降维技术(特征提取)。在机器学习中,数据集的维数等于用来表示它的变量的数量。
使用正则化当然有助于降低过拟合的风险,但是使用特征提取技术也可以带来其他类型的优势,例如:
- 精度提高。
- 过度拟合风险降低。
- 在训练中加速。
- 改进的数据可视化。
- 增加我们模型的可解释性。
要素提取旨在通过从现有要素创建新要素(然后丢弃原始要素)来减少数据集中的要素数量。这些新的精简特征集应该能够概括原始特征集中包含的大部分信息。这样,可以从原始集合的组合中创建原始特征的概括版本。
另一种减少数据集中要素数量的常用技术是要素选择。要素选择和要素提取的区别在于,要素选择旨在对数据集中现有要素的重要性进行排序,并丢弃不太重要的要素(不创建新要素)。如果你有兴趣了解更多关于特性选择的信息,你可以在我之前的文章中找到更多关于特性选择的信息。
在本文中,我将以 Kaggle 蘑菇分类数据集为例,向您介绍如何应用特征提取技术。我们的目标是试图通过观察给定的特征来预测蘑菇是否有毒。这篇文章中使用的所有代码(以及更多!)可在 Kaggle 和我的 GitHub 账户上获得。
首先,我们需要导入所有必需的库。
下图显示了我们将在本例中使用的数据集。

Figure 1: Mushroom Classification dataset
在将这些数据输入到我们的机器学习模型之前,我决定将我们的数据分为特征(【X】)和标签(【Y),并对所有分类变量进行热编码。
随后,我决定创建一个函数( forest_test )将输入数据分成训练集和测试集,然后训练和测试一个随机的森林分类器。
我们现在可以使用整个数据集来使用该函数,然后在使用简化版本而不是整个数据集时,连续使用该函数来比较这些结果。
如下所示,使用所有特征训练随机森林分类器,在大约 2.2 秒的训练时间内达到 100%的准确度。在下面的每个例子中,每个模型的训练时间将在每个片段的第一行打印出来,供您参考。
2.2676709799999992
[[1274 0]
[ 0 1164]]
precision recall f1-score support
0 1.00 1.00 1.00 1274
1 1.00 1.00 1.00 1164
accuracy 1.00 2438
macro avg 1.00 1.00 1.00 2438
weighted avg 1.00 1.00 1.00 2438
特征抽出
主成分分析
主成分分析是最常用的线性降维技术之一。当使用主成分分析时,我们将原始数据作为输入,并试图找到能够最好地概括原始数据分布的输入特征的组合,从而降低其原始维数。PCA 能够通过查看成对距离最大化方差和最小化重建误差来做到这一点。在 PCA 中,我们的原始数据被投影到一组正交轴上,每个轴按照重要性排序。
PCA 是一种无监督学习算法,因此它不关心数据标签,而只关心变化。这在某些情况下会导致数据的错误分类。
在本例中,我将首先在整个数据集中执行 PCA,将数据缩减为二维,然后用新要素及其各自的标注构建一个数据框。

Figure 2: PCA Dataset
使用我们新创建的数据框,我们现在可以在 2D 散点图中绘制我们的数据分布。

Figure 3: 2D PCA Visualization
我们现在可以重复同样的过程,保留 3 个维度,使用 Plotly 创建动画(请随意与下面的动画互动!).
在使用 PCA 时,我们还可以使用explained _ variance _ ratio _sci kit-learn 函数来探究原始数据方差保留了多少。一旦计算出方差比,我们就可以继续创建有趣的可视化图形。
使用由 PCA 构建的 3 个特征的集合(而不是整个数据集)再次运行随机森林分类器导致 98%的分类准确度,而仅使用 2 个特征 95%的准确度。
[10.31484926 9.42671062 8.35720548]
2.769664902999999
[[1261 13]
[ 41 1123]]
precision recall f1-score support
0 0.97 0.99 0.98 1274
1 0.99 0.96 0.98 1164
accuracy 0.98 2438
macro avg 0.98 0.98 0.98 2438
weighted avg 0.98 0.98 0.98 2438
此外,使用我们的二维数据集,我们现在还可以可视化随机森林使用的决策边界,以便对每个不同的数据点进行分类。

Figure 4: PCA Random Forest Decision Boundary
独立成分分析
ICA 是一种线性降维方法,它将独立分量的混合作为输入数据,其目的是正确识别它们中的每一个(删除所有不必要的噪声)。如果两个输入特征的线性和非线性相关性都等于零[1],则可以认为这两个输入特征是独立的。
独立分量分析通常用于医疗应用,如 EEG 和 fMRI 分析,以将有用的信号与无用的信号分开。
作为 ICA 应用程序的一个简单示例,让我们考虑一个音频注册,其中有两个不同的人在说话。例如,使用 ICA,我们可以尝试识别配准中的两个不同的独立成分(两个不同的人)。通过这种方式,我们可以让我们的无监督学习算法识别对话中不同的说话者。
使用 ICA,我们现在可以再次将数据集减少到只有三个特征,使用随机森林分类器测试其准确性,并绘制结果。
2.8933812039999793
[[1263 11]
[ 44 1120]]
precision recall f1-score support
0 0.97 0.99 0.98 1274
1 0.99 0.96 0.98 1164
accuracy 0.98 2438
macro avg 0.98 0.98 0.98 2438
weighted avg 0.98 0.98 0.98 2438
从下面的动画中我们可以看到,尽管 PCA 和 ICA 导致了相同的精度结果,但它们构建了两种不同的三维空间分布。
线性判别分析(LDA)
LDA 是监督学习降维技术和机器学习分类器。
LDA 的目标是最大化每个类的均值之间的距离,并最小化类本身内的扩散。因此,LDA 使用类内和类间作为度量。这是一个很好的选择,因为在低维空间中投影数据时,最大化每个类的均值之间的距离可以获得更好的分类结果(由于不同类之间的重叠减少)。
使用 LDA 时,假设输入数据遵循高斯分布(如本例所示),因此将 LDA 应用于非高斯数据可能会导致较差的分类结果。
在本例中,我们将运行 LDA 将数据集缩减为一个要素,测试其准确性并绘制结果。
Original number of features: 117
Reduced number of features: 1
因为我们的数据分布紧密遵循高斯分布,所以 LDA 表现得非常好,在这种情况下,使用随机森林分类器实现了 100%的准确性。
1.2756952610000099
[[1274 0]
[ 0 1164]]
precision recall f1-score support
0 1.00 1.00 1.00 1274
1 1.00 1.00 1.00 1164
accuracy 1.00 2438
macro avg 1.00 1.00 1.00 2438
weighted avg 1.00 1.00 1.00 2438
正如我在本节开始时提到的,LDA 也可以用作分类器。因此,我们现在可以测试 LDA 分类器在这种情况下的表现。
0.008464782999993758
[[1274 0]
[ 2 1162]]
precision recall f1-score support
0 1.00 1.00 1.00 1274
1 1.00 1.00 1.00 1164
accuracy 1.00 2438
macro avg 1.00 1.00 1.00 2438
weighted avg 1.00 1.00 1.00 2438
最后,我们现在可以可视化我们的两类分布看起来像创建我们的一维数据的分布图。

Figure 5: LDA Classes Separation
局部线性嵌入(LLE)
到目前为止,我们已经考虑了诸如 PCA 和 LDA 的方法,它们能够在不同特征之间的线性关系的情况下执行得非常好,我们现在将继续考虑如何处理非线性情况。
局部线性嵌入是一种基于流形学习的降维技术。流形是嵌入在更高维度空间中的 D 维对象。流形学习的目标是让这个对象可以在它原来的三维空间中表现,而不是在一个不必要的更大的空间中表现。
用于解释机器学习中流形学习的一个典型例子是瑞士滚动流形(图 6)。给我们一些数据作为输入,这些数据的分布类似于一个卷(在三维空间中),然后我们可以展开它,以便将我们的数据减少到二维空间中。
流形学习算法的一些例子是:Isomap、局部线性嵌入、修改的局部线性嵌入、Hessian 特征映射等

Figure 6: Manifold Learning [2]
现在,我将向您介绍如何在我们的示例中实现 LLE。根据 Scikit-learn 文档[3]:
局部线性嵌入(LLE)寻求数据的低维投影,其保持局部邻域内的距离。它可以被认为是一系列局部主成分分析,它们被全局比较以找到最佳非线性嵌入。
现在,我们可以在数据集上运行 LLE,将数据维数降低到 3 维,测试整体准确性并绘制结果。
2.578125
[[1273 0]
[1143 22]]
precision recall f1-score support
0 0.53 1.00 0.69 1273
1 1.00 0.02 0.04 1165
micro avg 0.53 0.53 0.53 2438
macro avg 0.76 0.51 0.36 2438
weighted avg 0.75 0.53 0.38 2438
t 分布随机邻居嵌入(t-SNE)
t-SNE 是一种非线性降维技术,通常用于可视化高维数据集。t-SNE 的一些主要应用是自然语言处理(NLP),语音处理等
t-SNE 通过最小化由原始高维空间中的输入特征的成对概率相似性构成的分布与其在缩减的低维空间中的等价物之间的差异来工作。然后,t-SNE 利用 Kullback-Leiber (KL) 散度来衡量两种不同分布的差异。然后使用梯度下降最小化 KL 散度。
当使用 t-SNE 时,高维空间使用高斯分布建模,而低维空间使用学生的 t-分布建模。这样做是为了避免由于转换到低维空间而导致的相邻点距离分布的不平衡。
我们现在准备使用 TSNE,将数据集减少到只有 3 个要素。
[t-SNE] Computing 121 nearest neighbors...
[t-SNE] Indexed 8124 samples in 0.139s...
[t-SNE] Computed neighbors for 8124 samples in 11.891s...
[t-SNE] Computed conditional probabilities for sample 1000 / 8124
[t-SNE] Computed conditional probabilities for sample 2000 / 8124
[t-SNE] Computed conditional probabilities for sample 3000 / 8124
[t-SNE] Computed conditional probabilities for sample 4000 / 8124
[t-SNE] Computed conditional probabilities for sample 5000 / 8124
[t-SNE] Computed conditional probabilities for sample 6000 / 8124
[t-SNE] Computed conditional probabilities for sample 7000 / 8124
[t-SNE] Computed conditional probabilities for sample 8000 / 8124
[t-SNE] Computed conditional probabilities for sample 8124 / 8124
[t-SNE] Mean sigma: 2.658530
[t-SNE] KL divergence after 250 iterations with early exaggeration: 65.601128
[t-SNE] KL divergence after 300 iterations: 1.909915
143.984375
可视化结果要素的分布,我们可以清楚地看到我们的数据是如何很好地分离的,即使是在缩小的空间中进行转换。
使用 t-SNE 缩减子集测试我们的随机森林准确性证实,现在我们的类可以很容易地分开。
2.6462027340000134
[[1274 0]
[ 0 1164]]
precision recall f1-score support
0 1.00 1.00 1.00 1274
1 1.00 1.00 1.00 1164
accuracy 1.00 2438
macro avg 1.00 1.00 1.00 2438
weighted avg 1.00 1.00 1.00 2438
自动编码器
自动编码器是一系列机器学习算法,可用作降维技术。自动编码器和其他降维技术的主要区别在于,自动编码器使用非线性变换将数据从高维投影到低维。
存在不同类型的自动编码器,例如:
- 去噪自动编码器
- 变型自动编码器
- 卷积自动编码器
- 稀疏自动编码器
在这个例子中,我们将从构建一个基本的自动编码器开始(图 7)。自动编码器的基本架构可以分为两个主要部分:
- 编码器:获取输入数据并进行压缩,以去除所有可能的噪音和无用信息。编码器级的输出通常被称为瓶颈或潜在空间。
- 解码器:将编码的潜在空间作为输入,并尝试仅使用其压缩形式(编码的潜在空间)再现原始自动编码器输入。
如果所有的输入特征都是相互独立的,那么自动编码器会发现将输入数据编码和解码到低维空间特别困难。

Figure 7: Autoencoder Architecture [4]
可以使用 Keras API 在 Python 中实现自动编码器。在这种情况下,我们在编码层中指定我们希望将输入数据减少到的特征数量(在本例中为 3)。从下面的代码片段中我们可以看到,自动编码器将 X(我们的输入特征)作为我们的特征和标签(X,Y)。
对于这个例子,我决定使用 ReLu 作为编码阶段的激活函数,使用 Softmax 作为解码阶段的激活函数。如果我没有使用非线性激活函数,那么 Autoencoder 会尝试使用线性变换来减少输入数据(因此给我们一个类似于使用 PCA 的结果)。
我们现在可以重复与前面的例子类似的工作流程,这次使用一个简单的自动编码器作为我们的特征提取技术。
1.734375
[[1238 36]
[ 67 1097]]
precision recall f1-score support
0 0.95 0.97 0.96 1274
1 0.97 0.94 0.96 1164
micro avg 0.96 0.96 0.96 2438
macro avg 0.96 0.96 0.96 2438
weighted avg 0.96 0.96 0.96 2438
希望您喜欢这篇文章,感谢您的阅读!
联系人
如果你想了解我最新的文章和项目,请在媒体上关注我,并订阅我的邮件列表。以下是我的一些联系人详细信息:
文献学
[1]用独立分量分析(ICA)深入探讨降维,Paperspace。访问网址:https://blog . paper space . com/dimension-reduction-with-independent-components-analysis/
[2]迭代非线性流形造型降维,ResearchGate。访问:https://www . research gate . net/publication/220270207 _ Iterative _ Non-linear _ Dimensionality _ Reduction _ with _ Manifold _ sculpture
[3]流形学习,Scikit-learn 文档。访问:https://sci kit-learn . org/stable/modules/Manifold . html # target text = Manifold % 20 learning % 20 is % 20 an % 20 approach,sets % 20 is % 20 only % 20 artificial % 20 high。
[4]可变自动编码器是美丽的。访问地点:http://www.compthree.com/blog/autoencoder/
使用主成分分析的特征提取——一个简化的可视化演示
理解特征和主成分之间的转换

介绍
没有扎实的线性代数基础,理解主成分分析(PCA)背后的数学是一个挑战。当我在旧金山的 General Assembly 教授数据科学时,我发现帮助学生可视化特征和主成分之间的转换极大地增强了他们的理解。 PCA 是一种降维技术,它有四个主要部分:特征协方差、特征分解、主分量变换和根据解释的方差选择分量。这个博客的目的是分享一个视觉演示,这个演示是帮助学生理解最后两步。
降维的快速回顾
维度的诅咒
为了理解 PCA,我们必须知道它的目的。为了理解这个目的,我们必须知道 维数灾难 ,这个方法解决的根本问题。虽然我们总是可以在维基百科上找到详尽的定义,但以下提供了更简单直观的解释:
随着要素或维度数量的增长,我们需要精确归纳的数据量也呈指数级增长
— Charles Isbell,佐治亚理工学院交互计算学院教授
维度的诅咒—Prasad Pore 撰写的 KDNuggets 帖子。
有两种方法可以降低维数:
- 特征消除:我们直接去掉一些特征。
- 特征提取:我们保留所有特征的重要部分。我们应用主成分分析来实现这一点。注意,PCA 不是进行特征提取的唯一方法。
主成分分析
PCA 是一种降维方法,即识别我们数据中的重要关系,根据这些关系转换现有数据,然后量化这些关系的重要性,这样我们就可以保留最重要的关系,丢弃其他关系。为了记住这个定义,我们可以把它分成四个步骤:
- 我们通过一个 协方差矩阵 来识别特征之间的关系。
- 通过协方差矩阵的线性变换或 特征分解 ,得到 特征向量和特征值 。
- 然后,我们使用特征向量将数据转换成主分量。
- 最后,我们使用特征值量化这些关系的重要性,并保留重要的主成分。

Figure 1
同样,我们可以在维基百科上找到所有术语的完整解释。我发现下面的帖子更直观,也更有帮助。
- PCA——由“阿米巴说恢复莫妮卡”(我觉得很好笑)发布的 Stackoverflow 回复。
一个伟大的可视化由 3blue1brown 为步骤 2 发布(事实上,我强烈推荐他们的整个线性代数动画系列)。以下章节包括步骤 3 和步骤 4 的可视化。
简化的视觉演示
下面的演示使用虹膜数据库中单个数据点的特征向量来呈现特征和主成分之间的线性变换。我描述计算时没有使用任何线性代数术语。但是,如果您了解两个向量之间的点积(因为我们演示了单个数据点的转换)和矩阵乘法(当我们转换所有数据点时),将会很有帮助。

Figure 2
- 特征:顶部蓝色横线表示。注意,x1、x2、x3 和 x4 代表单个虹膜的四个特征(即,萼片长度、萼片宽度、花瓣长度和花瓣宽度),而不是四个不同的虹膜。
- 特征向量:用绿色矩阵表示
- 主成分:左侧橙色竖线表示

Figure 3
图 3 包括每个部分的值。学习和理解数学方法的最好方法之一是验证数字相加。从这里,我们将做一些简单的计算。
步骤 3 的视觉演示
- 将特征转换成主成分。

Figure 4
为了将特征转换成主分量,我们将特征与特征向量逐个元素相乘,如上面的图 4 所示,并在水平方向上相加。
2。将主成分转换回特征。

当然,通过执行上面图 5 所示的计算,您可以将主成分转换回原始特征。
步骤 4 的视觉演示
在可视化了变换背后的数学之后,让我们演示一下特征消除和特征提取之间的区别。

Figure 6
如上面的图 6 所示,如果我们选择通过特征消除来降低维数,我们将完全删除一些特征,在本例中是 x3 和 x4。当我们将维度从 4 减少到 2 时,由花瓣长度和花瓣宽度提供的所有信息都丢失了。

Figure 7
相比之下,当我们通过 PCA 等特征提取方法降低维数时,我们通过选择解释特征之间大多数关系的主成分来保留最重要的信息。这就是特征值发挥作用的地方,它帮助我们了解每个主成分包含多少信息。
在我们的例子中,基于与每个特征向量相关的归一化特征值,第一和第二主成分(即 pc1 和 pc2)解释了特征变化的 95%以上,如下图 8 所示。因此,我们只保留 pc1 和 pc2。

Lambda is the eigenvalue

Figure 8
注意,每个主成分包含来自所有四个特征的信息。通过只保留 pc1 和 pc2,我们从所有四个特征中提取最重要的信息,并将维数从 4 降低到 2。
评论
我想用几句话来结束我的博客,包括 PCA 的使用,PCA 的利弊,以及何时不使用 PCA。
数据清理很重要
- PCA 对异常值和缺失值很敏感。
标准化数据
- PCA 使用欧几里德距离作为其特征向量相似性度量,因此确保我们在应用 PCA 之前缩放特征。
- 如果我们在模型中使用 GridSearchCV,确保我们将 PCA 放在管道中。在运行 GridSearch 之前拟合和转换训练数据会导致 GridSearch 内部交叉验证期间的数据泄漏。详见我上一篇博文。
优点
- PCA 在不丢失任何特征信息的情况下降低了维数。
- 减少存储数据所需的存储空间
- 加速学习算法(用更低的维度)。
- 解决多重共线性问题(所有主成分相互正交)。
- 帮助可视化高维数据(在将维度减少到 2 或 3 之后)。
缺点
- 使用主成分分析可以防止对原始特征的解释,以及它们的影响,因为特征向量是没有意义的。
五氯苯甲醚的潜在使用案例(非详尽清单)
- 我们有许多具有高度多重共线性的要素。
- 我们有太多的功能导致算法运行非常缓慢。
滥用五氯苯甲醚(并非详尽清单)
- 我们不应该使用 PCA 来降低维数,以防止过度拟合。我们应该使用正则化(如 L1 和 L2)来代替。
- 在用原始数据运行机器学习模型之前,我们不应该盲目地应用 PCA。如果使用原始数据效果不好,我们应该考虑 PCA 作为替代方法。
资源和信用
- 为了进一步研究 PCA,我推荐了 Coursera 上的斯坦福大学吴恩达教授的机器学习课程(第 8 周)。【https://www.coursera.org/learn/machine-learning? 号
- 我还引用了大会全球讲师 Matt Brems 为他的学生准备的材料。他还写了一篇关于 PCA 细节的博文。https://towards data science . com/a-一站式主成分分析-5582fb7e0a9c
特性工厂第 2 部分:MLFlow 简介

如果你读了我的第一篇文章,你有希望很好地理解什么是特性工厂,为什么它很重要,以及如何最好地培养一个特性工厂的一般想法。如果你没有,建议你先去看看。在这篇后续文章中,我想开始深入 MLFlow 并通过一些代码示例介绍主要概念。
ML 生命周期

首先,让我们对经典的机器学习生命周期有一个共同的理解:
一个商业问题被识别,其中机器学习的应用可能是有价值的
一个团队收集了大量数据
数据工程师开始清理和标准化数据,为分析做准备
主题专家挖掘数据,寻找有用的新信号(特征)
数据科学家研究手头问题的最佳算法,记录他们所有的个人运行
一旦决定了一个模型,开发-运营团队就把这个模型部署到一个服务平台,比如 Sagemaker、Azure ML,或者一个定制的实现,比如一个包装在 web 服务中的容器。
在这个过程中有一些常见的棘手问题,主要是生命周期/部署管理和分析跟踪。
部署一个训练有素的机器学习模型并不像人们预期的那样简单,如果你曾经试图弄清楚如何去做,你可能会发现围绕它的文档并不像 ML 生命周期中的其他阶段那样全面。此外,在部署之后,您仍然必须处理监控、再培训和重新部署。如果没有标准化的流程,这可能会占用您的开发-运营团队大量时间,如果您的公司或团队要转换云服务(例如从 AWS 到 Azure),您必须从头开始学习流程。

Outdated data science organization
如果你曾经看过这样的图表,你就会知道跟踪你的数据科学项目的漫长而艰难的过程。为你正在处理的每个问题创建一个新的电子表格,保存在你公司每个人共享的云存储上(希望如此),确保你的团队可以看到你所有的试验,这样他们就不会浪费时间尝试相同的设置,你明白了吧。
我总是使用 Excel 来跟踪我的项目,你可能有另一个你喜欢的工具,但它很可能是一个个人日志,很难与他人分享和扩展。很难跟踪谁尝试了什么,什么有效,什么无效,以及正在使用的数据集。正如你从上面的图片中所看到的,该表缺少了很多东西,添加东西可能会迫使你完全重新设计它。如果有人想尝试梯度推进或想添加当今使用的许多其他神经网络超参数中的一个,会怎么样?正如你所看到的,这种跟踪概念是不可持续的。
介绍 MLFlow

MLFlow 是面向数据科学家和开发运营工程师的开源解决方案。在我的上一篇文章中,我从较高的层面解释了为什么 MLFlow 如此伟大,所以在这里我想深入一下,从治理开始。我们将介绍如何使用 MLFlow 来交付治理和组织实验。我们还将谈到我们可以跟踪哪些细节来帮助模型构建过程,以及如何选择和部署模型。
治理
治理是对数据科学过程有一个完整的鸟瞰图的想法,从原始数据到摄取到实验到建模,甚至是部署。对任何可能影响有价值的东西的过程进行治理是非常重要的,无论是货币、监管,尤其是面向用户的(即 ML 模型做出实时决策)。因此,必须对数据科学过程进行跟踪和管理。在我们授权将模型部署到生产中之前,我们需要知道模型来自哪里,对它们进行了何种类型和深度的测试,以及进入它们的确切数据
MLFlow 允许您将数据科学工件组织成两个主要类别:
实验 —你试图解决的首要问题,例如:销售点欺诈。通常围绕一个公共数据集创建
运行 —特征工程和模型训练中的每个单独“尝试”:Excel 表格的每一行(具有更大的灵活性)
这种组织设计的伟大之处在于,它允许您设计希望在运行之间跟踪的元数据,并且不会强迫您使用一些公共参数。您可以在给定的运行中存储您想要的任何内容,并且它不需要与上次运行完全匹配。例如,对于一个给定的运行,您可以跟踪您想要的任何参数(模型类型、超参数、训练/测试分割)、您想要的度量标准( fpr 、 r 、 f1 ),即使它是针对不同的模型流派(二元/多类分类、回归)。
说够了,让我们开始演示:
出于本演示的目的,我将使用 Splice Machine 的 MLManager ,因为这是我通常日常使用的(因为它在 Splice DB 上本地运行),但在大多数情况下,它看起来与普通 MLFlow 相同,我将指出该示例在 Splice Machine 之外工作所需的任何更改。
基础
在这个演示中,我们将使用修改过的 Kaggle 数据集来预测供应链环境中的订单延迟。作为数据科学团队中的典型,我们将有不止一个数据科学家在这个项目上合作,每个人都执行许多转换和建模技术。
首先,我们导入必要的库并创建一个用于跟踪的 MLManager 对象(注意,对于普通的 mlflow,没有对象,只需用 ml flow 替换 Manager)。我们现在从 create _ experiment 函数开始实验,并传入一个名称。如果该实验已经存在,它将自动成为活动实验。

创建管理器后,我们可以将数据接收到数据帧中,并开始分析和转换。请务必注意将信息记录(记录)到 MLFlow 的方式:
log_param(或 lp) :允许你记录任何你想要的模型/管道参数。想想训练/测试分割、超参数、转换管道的阶段、特定源数据集的描述。
log_params : MLManager 的特定函数,该函数采用参数列表来记录并自动处理所有参数
log_metric(或 lm) :允许您记录任何模型指标。考虑训练时间、准确度、精确度、AUC 等。
log_metrics : MLManager 的特定函数,它获取一个度量列表并自动处理它们
log_artifact :允许您将对象记录到特定的运行中。想想图表、系列模型、图片等等。
log_artifacts : MLManager 的特定函数,获取工件列表并处理所有工件
log _ feature _ transformation:ml manager 特定函数,它采用未经训练的 Spark 管道并记录数据集中每个特性的所有转换步骤
log _ Pipeline _ stages:ml manager 特定函数,采用 Spark 管道(合适或不合适)并以可读格式记录所有阶段
log _ model _ params:ml manager 特定函数,采用拟合的火花模型或管道模型,并记录所有参数和超参数
log _ Evaluator _ metrics:ml manager 特定函数,采用拼接评估器并记录其所有度量
log _ spark _ model:ml manager 的特定函数,它获取一个经过训练的 Spark 模型并将其记录下来以供部署。这是因为,在拼接机的实施中,模型直接存储在拼接机数据库中,而不是 S3 或外部存储器中。
MLFlow 的伟大之处在于它足够灵活,允许您以任何您想要的方式设计您的工作流,根据您的团队需要和期望的治理流程的要求获得粒度或高级别。
如果这有点神秘,下面的演示应该会让它更容易理解。
开始跑步
我们喜欢保持非常有条理,所以我们将立即开始我们的第一次运行,并在运行过程中记录所有的特性转换和发现。当我们创建各种新功能和功能转换时,我们可以使用 log_param 函数单独记录这些功能,或者我们可以使用(特定拼接)log_feature_transformations 函数并传入一个 Spark Pipeline 对象。请注意,这个批量日志记录功能仅适用于 Spark ML 管道。

‘dataSize’ is simply an arbitrary key:value MLFlow tag


Preprocessing our dataset by converting our string columns into numeric and creating a feature vector
在上面的单元格中,我们调用 manager . log _ feature _ transformations 并传入一个 Spark 管道。如上所述,这是拼接机器的特定功能,仅适用于 MLManager。为了创建这个函数的输出,您将循环通过您的管道的阶段以及您的数据帧的列,并“跟踪”每一列的路径,查看它受哪个变压器和估计器影响,分别记录这些发现。

Feature transformations for each column
上表描述了每一列的预处理步骤。例如,首先使用一个 StringIndexer 将列 CITY_destination 从一个字符串值索引为一个整数值,然后使用一个热编码,最后在最后一个名为 featuresVec 的列中组合成一个特征向量
一旦我们有了最终形式的数据集,我们就可以使用 Splice Machine 的本地 Spark 数据源(在另一篇文章中会有更多的介绍)来创建并插入我们转换后的数据集到一个 SQL 表中,这样我们就可以跟踪这个特定运行所使用的数据。然后,我们可以记录一个参数或一个标记,指定数据库中的表名,以供以后分析时参考。这很有用,因为如果我们的模型在未来表现奇怪,我们可以看到它被训练的确切数据集,并可能找到灾难的原因。

Create a table in Splice database from the Spark Dataframe, and insert that dataframe into the DB table
因为将数据帧作为表格直接存储在拼接数据库中是拼接特定的功能,所以您可以使用普通 MLFlow 做的是使用 mlflow.log_artifact 将您的数据帧作为工件记录。这将把您的数据集链接到有问题的运行,以便将来进行分析。另一个选择是调用 df.write.sav e 并传入一个 S3 桶来将数据帧保存到 S3,在到 S3 位置的运行中记录一个参数。
培训模型
一旦我们的数据准备好了,我们就可以开始训练一些模型了。我们希望尝试许多不同的模型,看看哪一个具有最佳基准性能,然后缩小所选模型的超参数范围。我们可以通过多个模型迭代运行一个循环,跟踪每个模型的度量。对于我们运行的每个模型,我们可以开始一个新的运行,并记录数据集所来自的表(上面创建的表)。在这些运行过程中,我们还可以记录诸如接收机工作特性(ROC)曲线之类的图表。

Trying multiple models and logging all parameters and results
在我们运行一些超参数调优并且对我们选择的模型感到满意之后,我们可以将模型保存到 MLFlow(它被连续存储在 Splice DB 中),并在 MLFlow UI 中的工件中看到它。
manager.log_evaluator_metrics()的输出

manager.stop_and_log_timer()的输出

manager.log_model_params()的输出

正如您在上面所看到的,一些拼接机内置的日志记录功能为您提供了一些很好的管理,但是您也可以在 MLFlow 中实现这些功能:
要记录您的模型参数,您可以使用 model.extractParamMap()提取参数映射,并遍历您的参数,将每个参数记录到 MLFlow。
要记录根据您的数据进行训练和根据您的测试数据进行预测所花费的时间,您可以导入本地时间库,用 t1 = time.time()获得您的训练的开始和结束时间,并记录两个时间的差值。
为了记录您所有的指标,您可以使用 Spark 评估器来获取评估指标,并遍历可用的指标,使用 mlflow.log_metric 记录每个指标。
当其他数据科学家构建运行来测试他们的想法时,他们可以在同一个实验中填充,允许模型的交叉比较。任何东西都可以在这些实验和运行中保存,允许团队开发他们自己的工作标准。下面我们展示了两个比较:一个图表将特定的模型超参数与单个指标进行比较(在这种情况下,r 与树的数量),另一个图表显示了四个模型构建的整体视图,显示了每个模型的优势和劣势。

Comparing Number of Trees against r²

Comparing all metrics of each model against each other
我非常欣赏第二个图表,因为您可以看到每个模型的更全面的视图。例如,在这里您可以看到 magenta 模型明显优于所有其他模型,除了 r。因此,除非 r 是一个关键的评估指标,否则这将是一个明确的选择。
模型部署

Model tracking screen before deployment
既然一切都被跟踪和比较了,那么是时候部署您的模型了。没有部署,你的模型只不过是一个漂亮的 Jupyter 笔记本,但 MLFlow 允许轻松地基于代码部署到 AzureML 或 AWS Sagemaker。在拼接平台中,我们有一个方便的用户界面用于部署,因此无论您的云服务如何,部署都保持一致和简单。如果你使用普通的 MLFlow,你可以使用内置的 AzureML 或者sage makerAPI 来部署你的模型。

Model deployment UI
当部署到 Azure ML 或 AWS Sagemaker 时,只需选择您的地区、名称和一些其他细节,您的模型就上路了。部署完成后,您可以在 UI 中跟踪所有作业以及是谁部署了它们。

Model tracking screen after deployment
摘要
据此,我们使用 MLFlow 改造,训练,测试并部署了第一个机器学习模型。在任何时候,团队中的任何人都可以导航到 MLFlow UI,并查看从原始数据到部署模型所采取的所有步骤。随着数据的变化和模型需要被监控或调整,每个人都确切地知道去哪里看。
我真的很喜欢使用 MLFlow 及其所有令人难以置信的功能。如果你自己是 MLFlow 的粉丝,请随意评论我可能错过的任何其他伟大功能,欢迎所有建议!
要了解更多关于 MLFlow 的信息,请点击此处,要了解更多关于 Splice Machine 的 MLManager 实现,请点击此处,或请求演示此处。
神经网络的特征重要性
让机器学习变得容易理解,提供变量关系解释

Photo by Markus Spiske on Unsplash
机器学习中最大的挑战之一是让模型自己说话。不仅开发具有强大预测能力的强大解决方案很重要,而且在许多商业应用中,了解模型如何提供这些结果也很有趣:哪些变量参与最多,相关性的存在,可能的因果关系等等。
这些需求使得基于树的模型成为这个领域的一个好武器。它们是可扩展的,并且允许非常容易地计算变量解释。每个软件都提供这个选项,我们每个人都至少尝试过一次用随机森林或类似的方法计算变量重要性报告。对于神经网络,这种益处被认为是禁忌。神经网络通常被视为一个黑箱,很难从其中提取有用的信息用于其他目的,如特征解释。
在这篇文章中,我试图提供一个优雅而聪明的解决方案,用几行代码,允许你挤压你的机器学习模型并提取尽可能多的信息,以便提供特征重要性,个性化重要的相关性并试图解释因果关系。
数据集
给定一个真实的数据集,我们试图研究哪些因素影响最终的预测性能。为了实现这个目标,我们从 UCI 机器学习知识库中获取数据。特权数据集是联合循环发电厂数据集,当发电厂设置为满负荷工作时,在那里收集了 6 年的数据。特征包括每小时平均变量:环境温度(AT)、环境压力(AP)、相对湿度(RH)和排气真空(V ),以预测电厂的每小时净电能输出(PE)。
所涉及的变量通过 Pearson 相关链接进行关联,如下表所示。

Correlation Matrix
梯度增强特征重要性
我们开始构建一个简单的基于树的模型,以便提供能量输出 (PE) 预测,并计算标准的特征重要性估计。这最后一步使我们能够比标准的相关指数更多地说明变量之间的关系。这些数字总结了在内部空间划分期间(在训练阶段)指出特定特征时,所有树的杂质指数的减少。Sklearn 应用归一化,以便提供可累加为 1 的输出。这也是一个免费的结果,可以在训练后间接获得。
gb = GradientBoostingRegressor(n_estimators=100)
gb.fit(X_train, y_train.values.ravel())plt.bar(range(X_train.shape[1]), gb.feature_importances_)
plt.xticks(range(X_train.shape[1]), ['AT','V','AP','RH'])

GradientBoosting Features Importance
这个结果很容易解释,并且似乎复制了计算与我们的目标变量的相关性的初始假设(相关矩阵的最后一行):值越高,这个特定特征预测我们的目标的影响就越大。
尽管我们通过梯度推进取得了很好的结果,但我们不想完全依赖这种方法……我们想推广计算特征重要性的过程,让我们自由开发另一种具有相同灵活性和解释能力的机器学习模型;更进一步:提供变量之间存在显著伤亡关系的证据。
排列重要性
为我们的实验确定的模型无疑是神经网络,因为它们享有黑盒算法的声誉。为了揭开这个刻板印象的神秘面纱,我们将关注排列的重要性。其易于实现,结合其有形的理解和适应性,使其成为回答问题的一致候选:哪些特性对预测的影响最大?
排列重要性是在模型拟合后计算的。所以我们只能压榨它,得到我们想要的。这种方法的工作原理很简单:如果我随机打乱数据中的单个特征,让目标和所有其他特征保持不变,这会如何影响最终的预测性能?
从变量的随机重新排序中,我期望得到:
- 不太准确的预测,因为产生的数据不再符合现实世界中观察到的任何东西;
- 最差的表现,来自最重要变量的混乱。这是因为我们正在破坏数据的自然结构。如果我们的洗牌破坏了一个牢固的关系,我们将损害我们的模型在训练中所学到的东西,导致更高的错误(高错误=高重要性)。

Permutation Importance at work
实际上,这是我们真实场景中发生的事情…
我们选择了合适的神经网络结构来模拟小时电能输出 ( EP )。记住也要在一个较低的范围内调整目标变量:我经典地减去平均值,除以标准差,这有助于训练。
inp = Input(shape=(scaled_train.shape[1],))
x = Dense(128, activation='relu')(inp)
x = Dense(32, activation='relu')(x)
out = Dense(1)(x)model = Model(inp, out)
model.compile(optimizer='adam', loss='mse')model.fit(scaled_train, (y_train - y_train.mean())/y_train.std() , epochs=100, batch_size=128 ,verbose=2)
在预测阶段,梯度提升和神经网络在平均绝对误差方面达到相同的性能,分别为 2.92 和 2.90(记住要反向预测)。
至此,我们结束了培训,让我们开始随机抽样。
我们计算验证数据上每个特征的混洗(总共 4 次= 4 个显式变量),并提供每一步的误差估计;记住每一步都要把数据恢复到原来的顺序。然后,我将我们在每个洗牌阶段获得的 MAE 绘制成相对于原始 MAE 的百分比变化(大约 2.90)
plt.bar(range(X_train.shape[1]), (final_score - MAE)/MAE*100)
plt.xticks(range(X_train.shape[1]), ['AT','V','AP','RH'])

Permutation Importance as percentage variation of MAE
上图复制了射频特征重要性报告,并证实了我们最初的假设:环境温度(AT)是预测电能输出(PE) 的最重要和最相关的特征。尽管处的排气真空(V) 和与 PE (分别为 0.87 和 0.95)表现出相似且高度相关的关系,但它们在预测阶段具有不同的影响。这一现象是一个软例子,说明高相关性(皮尔逊术语)并不总是高解释力的同义词。
因果关系
为了避免虚假关系,证明相关性总是一种阴险的操作。同时,很难出示伤亡行为的证据。在文献中,有很多证明因果关系的方法。其中最重要的是格兰杰因果关系检验。这种技术广泛应用于时间序列领域,以确定一个时间序列是否有助于预测另一个时间序列:即证明(根据滞后值的 f 检验)它增加了回归的解释力。
间接的,这就是我们已经做的,计算排列重要性。改变每一个变量并寻找性能变化,我们正在证明这个特性在预测预期目标方面有多大的解释力。
为了证明因果关系,我们现在要做的是证明数据混洗为性能变化提供了有意义的证据。我们对无洗牌和有洗牌的最终预测进行操作,并验证两个预测总体的平均值是否有差异。这意味着随机的平均预测也可以被任何随机的预测子群观察到。因此,这正是我们将对每个特征所做的:我们将合并有置换和无置换的预测,我们将随机抽样一组预测,并计算它们的平均值与无置换预测的平均值之间的差异。
np.random.seed(33)
id_ = 0 #feature index
merge_pred = np.hstack([shuff_pred[id_], real_pred])
observed_diff = abs(shuff_pred[id_].mean() - merge_pred.mean())
extreme_values = []
sample_d = []for _ in range(10000):
sample_mean = np.random.choice(merge_pred,
size=shuff_pred[id_].shape[0]).mean()
sample_diff = abs(sample_mean - merge_pred.mean())
sample_d.append(sample_diff)
extreme_values.append(sample_diff >= observed_diff)
np.sum(extreme_values)/10000 #p-value
为了控制一切,可视化我们的模拟结果是一个很好的选择。我们绘制了模拟平均差异的分布(蓝色条)并标记了实际观察到的差异(红线)。我们可以看到,对于在处,有证据表明在没有滑移的情况下做出的预测在均值上存在差异(低 p 值:低于 0.1)。其他变量不会带来均值的显著提高。

Simulation distributions and relative p-values
相关性并不总是意味着因果关系!考虑到这一点,我们根据一个选定特征增加解释力的能力来证明因果关系。我们用我们的统计学家和程序员的知识,重新创造了一种方法来证明这个概念,利用了我们以前在排列重要性方面的发现,增加了关于变量关系的信息。
摘要
在这篇文章中,我介绍了排列重要性,这是一种计算特性重要性的简单而聪明的技术。它对各种模型(我使用神经网络只是作为个人选择)和每个问题都很有用(模拟程序适用于分类任务中的:在计算排列重要性时,记得选择适当的损失度量,如交叉熵,避免模糊的准确性)。我们还使用了排列来展示一种方法,证明了攻击 p 值的变量之间的因果关系!
保持联系: Linkedin
基于 Python 的遗传算法特征约简
介绍
在某些情况下,使用原始数据来训练机器学习算法可能不是合适的选择。当由原始数据训练时,该算法必须自己进行特征挖掘,以检测彼此不同的组。但是这需要大量的数据来自动进行特征挖掘。对于小数据集,数据科学家最好自己执行特征挖掘步骤,并告诉机器学习算法使用哪个特征集。
所使用的特征集必须代表数据样本,因此我们必须注意选择最佳特征。数据科学家建议使用某些类型的特征,这些特征似乎有助于根据以前的经验表示数据样本。一些特征可能证明它们在代表样本方面的稳健性,而另一些则不然。
可能有一些类型的特征可能通过降低分类问题的准确度或增加回归问题的误差来影响训练模型的结果。例如,在特征向量中可能有一些噪声元素,因此它们应该被去除。特征向量也可以包括 2 个或更多相关元素。仅仅使用一种元素将替代另一种元素。为了去除这类元素,有两个有用的步骤,即特征选择和减少。本教程重点介绍特征减少。
假设有 3 个特征 F1、F2 和 F3,每个特征有 3 个特征元素。因此,特征向量长度是 3×3 = 9。特征选择仅选择特定类型的特征,而排除其他特征。比如只选择 F1 和 F2,去掉 F3。特征向量长度现在是 6 而不是 9。在特征缩减中,每个特征的特定元素可能被排除。例如,该步骤可能会从 F3 中移除第一个元素和第三个元素,同时保留第二个元素。因此,特征向量长度从 9 减少到仅仅 7。
在开始本教程之前,值得一提的是,它是我的 LinkedIn 个人资料中先前发布的 2 个教程的扩展。
第一个教程的标题是“使用 NumPy 和 Fruits360 图像数据集的分类的人工神经网络实现”。它首先从 Fruits360 数据集的 4 个类中提取长度为 360 的特征向量。然后,它使用 NumPy 从头开始构建人工神经网络(ANN ),以便对数据集进行分类。这里有https://www . LinkedIn . com/pulse/artificial-neural-network-implementation-using-numpy-fruits 360-gad。这里有它的 GitHub 项目:【https://github.com/ahmedfgad/NumPyANN】T4。
第二个教程的标题是“使用遗传算法优化人工神经网络”。建立并使用遗传算法优化神经网络参数,以提高分类精度。这里有https://www . LinkedIn . com/pulse/artificial-neural-networks-optimization-using-genetic-Ahmed-gad。它的 GitHub 项目也在这里:https://github.com/ahmedfgad/NeuralGenetic。
本教程讨论了如何使用遗传算法(GA)来减少从长度为 360 的 Fruits360 数据集提取的特征向量。本教程首先讨论要遵循的步骤。之后主要用 NumPy 和 Sklearn 用 Python 实现步骤。
本教程的实现可以在我的 GitHub 页面这里找到:https://github.com/ahmedfgad/FeatureReductionGenetic
遗传算法从一个初始群体开始,该群体由多个染色体(即解)组成,其中每个染色体具有一个基因序列。使用适应度函数,遗传算法选择最佳解作为父代来创建新的种群。这种新群体中的新解是通过对双亲应用两种操作来创建的,这两种操作是交叉和变异。当应用遗传算法解决一个给定的问题时,我们必须确定基因的表示,合适的适应度函数,以及如何应用交叉和变异。让我们看看事情是如何运作的。

关于 GA 的更多信息
您可以从我准备的以下资源中了解有关 GA 的更多信息:
- 遗传算法优化导论
https://www . LinkedIn . com/pulse/introduction-优化-遗传-算法-ahmed-gad/
https://www . kdnugges . com/2018/03/introduction-optimization-with-genetic-algorithm . html
- 遗传算法(GA)优化—分步示例
https://www . slide share . net/AhmedGadFCIT/genetic-algorithm-ga-optimization-step by step-example
- 遗传算法在 Python 中的实现
https://www . LinkedIn . com/pulse/genetic-algorithm-implementation-python-Ahmed-gad/
https://www . kdnugges . com/2018/07/genetic-algorithm-implementation-python . html
https://towardsdatascience . com/genetic-algorithm-implementation-in-python-5ab 67 bb 124 a 6
https://github.com/ahmedfgad/GeneticAlgorithmPython
我还在 2018 年写了一本书,其中一章涉及 GA。这本书被称为“Ahmed faw zy Gad‘使用深度学习和 CNN 的实用计算机视觉应用’。2018 年 12 月,Apress,978–1–4842–4167–7”,可在施普林格https://www.springer.com/us/book/9781484241660获得。
染色体表示
GA 中的基因是染色体的组成部分。首先,我们需要确定染色体中有哪些基因。要做到这一点,要考虑到每一个可能影响结果的属性都应该被视为一个基因。因为我们问题的目标是选择最佳的特征元素集合,因此每个特征元素的选择与否都会影响结果。因此,每个特征元素被视为一个基因。染色体将由所有基因(即所有特征元素)组成。因为有 360 个特征元素,那么就会有 360 个基因。一个很好的信息是,染色体的长度是 360。
在确定了被选择的基因是什么之后,接下来就是确定基因的代表性。有不同的表示法,如十进制、二进制、浮点、字符串等。我们的目标是知道基因(即特征元素)是否在缩减的特征集中被选择。因此,分配给基因的值应该反映它是否被选择。基于这一描述,很明显每个基因有两个可能的值。一个值表示基因被选中,另一个值表示基因未被选中。因此,二进制表示是最佳选择。当基因值为 1 时,则它将在缩减的特征集中被选择。当值为 0 时,它将被忽略。
总的来说,染色体将由 360 个二进制表示的基因组成。根据下图,在特征向量和染色体之间存在一对一的映射。即染色体中的第一个基因与特征向量中的第一个元素相联系。当该基因的值为 1 时,这意味着选择了特征向量中的第一个元素。

适应度函数
通过得到如何产生染色体,初始种群可以很容易地被随机初始化为 NumPy。在初始化之后,选择双亲。GA 是基于达尔文的理论适者生存。即选择当前种群中的最佳解进行交配,以产生更好的解。通过保留好的解决方案并删除坏的解决方案,我们可以达到最优或次优的解决方案。
用于选择双亲的标准是与每个解(即染色体)相关联的适应值。适应值越高,解决方案越好。使用适应度函数来计算适应度值。那么,在我们的问题中,什么是最好的函数呢?我们的问题的目标是创建一个减少的特征向量,增加分类精度。因此,判断解决方案好坏的标准是分类精度。因此,适应度函数将返回一个数字,该数字指定每个解决方案的分类精度。精度越高,解决方案越好。
为了返回分类精度,必须有一个机器学习模型,以通过每个解决方案返回的特征元素进行训练。对于这种情况,我们将使用支持向量分类器(SVC)。
数据集分为训练样本和测试样本。基于训练数据,SVC 将使用由群体中的每个解决方案选择的特征元素来训练。经过训练后,会根据测试数据进行测试。
基于每个解的适应值,我们可以选择其中最好的作为父代。这些父母被一起放在交配池中,以产生后代,这些后代将成为下一代新群体的成员。这样的后代是通过对选择的亲本应用交叉和变异操作来创建的。让我们按照下面的讨论来配置这样的操作。
交叉和变异
在适应度函数的基础上,我们可以在当前群体中筛选出最好的解,称为父代。GA 假设将两个好的解决方案配对会产生第三个更好的解决方案。交配意味着交换父母双方的一些基因。使用交叉操作来交换基因。有不同的方式可以应用这样的操作。本教程使用单点交叉,其中一个点分割染色体。点之前的基因取自一个解,点之后的基因取自另一个解。
通过应用交叉,所有的基因都取自先前的双亲。新的后代中没有引入新的基因。如果所有父母中都有一个不好的基因,那么这个基因就会传递给后代。为此,应用突变操作以便在后代中引入新的基因。在基因的二进制表示中,通过翻转一些随机选择的基因的值来应用突变。如果基因值是 1,那么它将是 0,反之亦然。
产生后代后,我们可以创造下一代的新种群。这个群体除了后代外,还包括以前的父母。
至此,所有步骤都讨论完毕。接下来是用 Python 实现它们。请注意,我之前写了一篇名为“用 Python 实现遗传算法”的教程,用于用 Python 实现遗传算法,我将修改它的代码来解决我们的问题。还不如读一读。
Python 实现
该项目分为两个文件。一个名为“GA.py”的文件将 GA 步骤的实现保存为函数。另一个文件,也就是主文件,只是导入这个文件并在一个循环中调用它的函数,这个循环遍历所有代。
主文件首先根据下面的代码读取从 Fruits360 数据集中提取的特征。这些特征返回到数据输入变量中。有关提取这些要素的详细信息,请参见本教程开头提到的两个教程。该文件还读取变量 data_outputs 中与样本相关的分类标签。
选择一些样本进行训练,其指数存储在train _ indexes变量中。类似地,测试样本索引存储在test _ indexes变量中。
**import** numpy
**import** GA
**import** pickle
**import** matplotlib.pyplot
f = open(**"dataset_features.pkl"**, **"rb"**)
data_inputs = pickle.load(f)
f.close()
f = open(**"outputs.pkl"**, **"rb"**)
data_outputs = pickle.load(f)
f.close()
num_samples = data_inputs.shape[0]
num_feature_elements = data_inputs.shape[1]
train_indices = numpy.arange(1, num_samples, 4)
test_indices = numpy.arange(0, num_samples, 4)
print(**"Number of training samples: "**, train_indices.shape[0])
print(**"Number of test samples: "**, test_indices.shape[0])
**"""
Genetic algorithm parameters:
Population size
Mating pool size
Number of mutations
"""** sol_per_pop = 8 *# Population size.* num_parents_mating = 4 *# Number of parents inside the mating pool.* num_mutations = 3 *# Number of elements to mutate.
# Defining the population shape.* pop_shape = (sol_per_pop, num_feature_elements)
*# Creating the initial population.* new_population = numpy.random.randint(low=0, high=2, size=pop_shape)
print(new_population.shape)
best_outputs = []
num_generations = 100
它初始化遗传算法的所有参数。这包括根据 sol_per_pop 变量设置为 8 的每个种群的解数,在num _ parents _ matting变量中设置为 4 的后代数,以及在 num_mutations 变量中设置为 3 的突变数。之后,它在一个名为 new_population 的变量中随机创建初始种群。
有一个名为 best_outputs 的空列表,它保存每代之后的最佳结果。这有助于在完成所有代之后可视化 GA 的进程。在 num_generations 变量中,代数被设置为 100。请注意,您可以更改所有这些参数,这可能会产生更好的结果。
在准备好特性、类标签和 GA 参数之后,我们可以根据下一个代码进行 GA 的迭代。首先,通过调用 GA 文件中定义的名为 cal_pop_fitness() 的适应度函数来计算所有解的适应度值。该函数接受当前总体、提取的特征、类标签、训练索引和测试索引。该函数在名为 fitness 的变量中返回所有解决方案的适应值。记住,适应值代表分类精度。最佳(即最高)分类精度保存在最佳输出列表中。
基于计算的适应值,使用 GA.py 文件中定义的select _ matting _ pool()函数,选择具有最高分类精度的最佳解作为交配池中的亲代。它接受当前人口、适应值和要返回的父代数量。它将选择的双亲返回到双亲变量中。
**for** generation **in** range(num_generations):
print(**"Generation : "**, generation)
*# Measuring the fitness of each chromosome in the population.* fitness = GA.cal_pop_fitness(new_population, data_inputs, data_outputs, train_indices, test_indices)
best_outputs.append(numpy.max(fitness))
*# The best result in the current iteration.* print(**"Best result : "**, best_outputs[-1])
*# Selecting the best parents in the population for mating.* parents = GA.select_mating_pool(new_population, fitness, num_parents_mating)
*# Generating next generation using crossover.* offspring_crossover = GA.crossover(parents, offspring_size=(pop_shape[0]-parents.shape[0], num_feature_elements))
*# Adding some variations to the offspring using mutation.* offspring_mutation = GA.mutation(offspring_crossover, num_mutations=num_mutations)
*# Creating the new population based on the parents and offspring.* new_population[0:parents.shape[0], :] = parents
new_population[parents.shape[0]:, :] = offspring_mutation
下一步是对选择的父代应用交叉操作来创建后代。这是在 GA.py 文件中定义的 crossover() 函数内完成的。它接受父数组和后代数组的形状,稍后返回到后代交叉变量中。然后使用 GA.py 文件中的突变()函数对数组进行突变操作。除了交叉结果,该函数还接受突变的数量。
因为新群体除了后代之外还包括所选的父母,所以父母和后代 _ 突变数组都被保存到新 _ 群体变量中。之后,新的一代应用于新的人口。
在所有代完成之后,执行下一个代码,以便返回最佳选择的特征元素集合和所选元素的数量。在 100 代完成后,该算法使用了 174 个特征元素,以达到 99.59%的准确度。
fitness = GA.cal_pop_fitness(new_population, data_inputs, data_outputs, train_indices, test_indices)
*# Then return the index of that solution corresponding to the best fitness.* best_match_idx = numpy.where(fitness == numpy.max(fitness))[0]
best_match_idx = best_match_idx[0]
best_solution = new_population[best_match_idx, :]
best_solution_indices = numpy.where(best_solution == 1)[0]
best_solution_num_elements = best_solution_indices.shape[0]
best_solution_fitness = fitness[best_match_idx]
print(**"best_match_idx : "**, best_match_idx)
print(**"best_solution : "**, best_solution)
print(**"Selected indices : "**, best_solution_indices)
print(**"Number of selected elements : "**, best_solution_num_elements)
print(**"Best solution fitness : "**, best_solution_fitness)
matplotlib.pyplot.plot(best_outputs)
matplotlib.pyplot.xlabel(**"Iteration"**)
matplotlib.pyplot.ylabel(**"Fitness"**)
matplotlib.pyplot.show()
上面的代码还显示了一个图表,显示了 GA 在所有代中的进度,如下所示。

这里是主文件的完整代码。
import numpy
import GA
import pickle
import matplotlib.pyplot
f = open("dataset_features.pkl", "rb")
data_inputs = pickle.load(f)
f.close()
f = open("outputs.pkl", "rb")
data_outputs = pickle.load(f)
f.close()
num_samples = data_inputs.shape[0]
num_feature_elements = data_inputs.shape[1]
train_indices = numpy.arange(1, num_samples, 4)
test_indices = numpy.arange(0, num_samples, 4)
print("Number of training samples: ", train_indices.shape[0])
print("Number of test samples: ", test_indices.shape[0])
"""
Genetic algorithm parameters:
Population size
Mating pool size
Number of mutations
"""
sol_per_pop = 8 # Population size.
num_parents_mating = 4 # Number of parents inside the mating pool.
num_mutations = 3 # Number of elements to mutate.
# Defining the population shape.
pop_shape = (sol_per_pop, num_feature_elements)
# Creating the initial population.
new_population = numpy.random.randint(low=0, high=2, size=pop_shape)
print(new_population.shape)
best_outputs = []
num_generations = 100
for generation in range(num_generations):
print("Generation : ", generation)
# Measuring the fitness of each chromosome in the population.
fitness = GA.cal_pop_fitness(new_population, data_inputs, data_outputs, train_indices, test_indices)
best_outputs.append(numpy.max(fitness))
# The best result in the current iteration.
print("Best result : ", best_outputs[-1])
# Selecting the best parents in the population for mating.
parents = GA.select_mating_pool(new_population, fitness, num_parents_mating)
# Generating next generation using crossover.
offspring_crossover = GA.crossover(parents, offspring_size=(pop_shape[0]-parents.shape[0], num_feature_elements))
# Adding some variations to the offspring using mutation.
offspring_mutation = GA.mutation(offspring_crossover, num_mutations=num_mutations)
# Creating the new population based on the parents and offspring.
new_population[0:parents.shape[0], :] = parents
new_population[parents.shape[0]:, :] = offspring_mutation
# Getting the best solution after iterating finishing all generations.
# At first, the fitness is calculated for each solution in the final generation.
fitness = GA.cal_pop_fitness(new_population, data_inputs, data_outputs, train_indices, test_indices)
# Then return the index of that solution corresponding to the best fitness.
best_match_idx = numpy.where(fitness == numpy.max(fitness))[0]
best_match_idx = best_match_idx[0]
best_solution = new_population[best_match_idx, :]
best_solution_indices = numpy.where(best_solution == 1)[0]
best_solution_num_elements = best_solution_indices.shape[0]
best_solution_fitness = fitness[best_match_idx]
print("best_match_idx : ", best_match_idx)
print("best_solution : ", best_solution)
print("Selected indices : ", best_solution_indices)
print("Number of selected elements : ", best_solution_num_elements)
print("Best solution fitness : ", best_solution_fitness)
matplotlib.pyplot.plot(best_outputs)
matplotlib.pyplot.xlabel("Iteration")
matplotlib.pyplot.ylabel("Fitness")
matplotlib.pyplot.show()
遗传算法实现
下面列出了 GA.py 文件的实现。在 cal_pop_fitness() 函数中,SVC 根据每个解决方案选择的特征元素进行训练。在被训练之前,根据其基因被赋予值 1 的所选元素来过滤特征。这是在 reduce_features() 函数中完成的。除了所有样本的完整功能之外,它还接受当前的解决方案。
经过训练后,使用classification _ accuracy()函数计算其分类精度。该函数返回存储在 cal_pop_fitness() 函数中名为 accuracy 的数组中的精度。
crossover() 和 mutation() 函数的实现与我之前的教程“Python 中的遗传算法实现”中讨论的非常相似。一个主要的区别是突变()函数通过翻转它们的值来改变随机选择的基因,因为我们使用二进制表示。
import numpy
import sklearn.svm
def reduce_features(solution, features):
selected_elements_indices = numpy.where(solution == 1)[0]
reduced_features = features[:, selected_elements_indices]
return reduced_features
def classification_accuracy(labels, predictions):
correct = numpy.where(labels == predictions)[0]
accuracy = correct.shape[0]/labels.shape[0]
return accuracy
def cal_pop_fitness(pop, features, labels, train_indices, test_indices):
accuracies = numpy.zeros(pop.shape[0])
idx = 0
for curr_solution in pop:
reduced_features = reduce_features(curr_solution, features)
train_data = reduced_features[train_indices, :]
test_data = reduced_features[test_indices, :]
train_labels = labels[train_indices]
test_labels = labels[test_indices]
SV_classifier = sklearn.svm.SVC(gamma='scale')
SV_classifier.fit(X=train_data, y=train_labels)
predictions = SV_classifier.predict(test_data)
accuracies[idx] = classification_accuracy(test_labels, predictions)
idx = idx + 1
return accuracies
def select_mating_pool(pop, fitness, num_parents):
# Selecting the best individuals in the current generation as parents for producing the offspring of the next generation.
parents = numpy.empty((num_parents, pop.shape[1]))
for parent_num in range(num_parents):
max_fitness_idx = numpy.where(fitness == numpy.max(fitness))
max_fitness_idx = max_fitness_idx[0][0]
parents[parent_num, :] = pop[max_fitness_idx, :]
fitness[max_fitness_idx] = -99999999999
return parents
def crossover(parents, offspring_size):
offspring = numpy.empty(offspring_size)
# The point at which crossover takes place between two parents. Usually, it is at the center.
crossover_point = numpy.uint8(offspring_size[1]/2)
for k in range(offspring_size[0]):
# Index of the first parent to mate.
parent1_idx = k%parents.shape[0]
# Index of the second parent to mate.
parent2_idx = (k+1)%parents.shape[0]
# The new offspring will have its first half of its genes taken from the first parent.
offspring[k, 0:crossover_point] = parents[parent1_idx, 0:crossover_point]
# The new offspring will have its second half of its genes taken from the second parent.
offspring[k, crossover_point:] = parents[parent2_idx, crossover_point:]
return offspring
def mutation(offspring_crossover, num_mutations=2):
mutation_idx = numpy.random.randint(low=0, high=offspring_crossover.shape[1], size=num_mutations)
# Mutation changes a single gene in each offspring randomly.
for idx in range(offspring_crossover.shape[0]):
# The random value to be added to the gene.
offspring_crossover[idx, mutation_idx] = 1 - offspring_crossover[idx, mutation_idx]
return offspring_crossover
联系作者
- 电子邮件:ahmed.f.gad@gmail.com
- 领英:https://linkedin.com/in/ahmedfgad/
- https://kdnuggets.com/author/ahmed-gad
- YouTube:【https://youtube.com/AhmedGadFCIT
- https://towardsdatascience.com/@ahmedfgad
- GitHub:https://github.com/ahmedfgad
特征选择和降维
探索 Kaggle“不要过度适应 II”竞赛中的特征选择和降维技术

根据维基百科,“特征选择是选择相关特征子集用于模型构建的过程”,或者换句话说,选择最重要的特征。
在正常情况下,领域知识起着重要的作用,我们可以选择我们认为最重要的特性。例如,在预测房价时,卧室的数量和面积通常被认为是重要的。
不幸的是,在“不要过度适应”II 竞赛中,使用领域知识是不可能的,因为我们有一个二元目标和 300 个“神秘起源”的连续变量,迫使我们尝试自动特征选择技术。
完整的笔记本可以在这里找到。
特征选择与降维
通常,特征选择和降维被组合在一起(如本文中所示)。虽然这两种方法都用于减少数据集中的要素数量,但有一个重要的区别。
特征选择是简单地选择和排除给定的特征而不改变它们。
维度缩减将特征转换到一个更低的维度。
在本文中,我们将探讨以下特征选择和维度缩减技术:
特征选择
- 移除缺少值的要素
- 移除方差较小的要素
- 移除高度相关的要素
- 单变量特征选择
- 递归特征消除
- 使用 SelectFromModel 进行特征选择
降维
- 主成分分析
基线模型
我们将使用逻辑回归作为基线模型。我们首先分成测试集和训练集,并缩放数据:
# prepare for modeling
X_train_df = train.drop(['id', 'target'], axis=1)
y_train = train['target']# scaling data
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train_df)lr = LogisticRegression(solver='liblinear')
lr_scores = cross_val_score(lr,
X_train,
y_train,
cv=5,
scoring='roc_auc')print('LR Scores: ', lr_scores)LR Scores: [0.80729167 0.71875 0.734375 0.80034722 0.66319444]
我们可以从交叉验证分数的变化中看到模型过度拟合。我们可以尝试通过特征选择来提高这些分数。
移除缺少值的要素
检查缺失值是任何机器学习问题的良好开端。然后,我们可以删除超出我们定义的阈值的列。
# check missing values
train.isnull().any().any()False
不幸的是,对于我们的降维工作来说,这个数据集没有缺失值。
移除方差较小的要素
在 sklearn 的特征选择模块中我们找到了VarianceThreshold。它会移除方差未达到某个阈值的所有要素。默认情况下,它会移除方差为零的要素或所有样本值相同的要素。
from sklearn import feature_selection
sel = feature_selection.VarianceThreshold()
train_variance = sel.fit_transform(train)
train_variance.shape(250, 302)
比赛描述说我们的特征都是连续的。从上面我们可以看到,所有列中没有具有相同值的特性,因此我们在这里没有要删除的特性。
我们可以随时重新审视这一技术,并考虑移除方差较低的特征。
移除高度相关的要素
高度相关或共线的特征会导致过度拟合。
当一对变量高度相关时,我们可以删除其中一个以降低维度,而不会丢失太多信息。我们应该保留哪一个?与目标相关性更高的那个。
让我们探索一下我们的特征之间的相互关系:
# find correlations to target
corr_matrix = train.corr().abs()print(corr_matrix['target'].sort_values(ascending=False).head(10))target 1.000000
33 0.373608
65 0.293846
217 0.207215
117 0.197496
91 0.192536
24 0.173096
295 0.170501
73 0.167557
183 0.164146
这里我们看到了与我们的目标变量高度相关的特征。特征 33 与目标的相关性最高,但是相关性值只有 0.37,所以相关性很弱。
我们还可以检查功能与其他功能的相关性。下面我们可以看到一个相关矩阵。看起来我们的特征没有一个高度相关。

Correlation Matrix
让我们尝试删除相关值大于 0.5 的要素:
# Find index of feature columns with high correlation
to_drop = [column for column in matrix.columns if any(matrix[column] > 0.50)]
print('Columns to drop: ' , (len(to_drop)))Columns to drop: 0
使用高度相关的特性,我们没有要删除的列。让我们继续探索其他策略。
单变量特征选择
单变量特征选择通过基于单变量统计测试选择最佳特征来工作。
我们可以使用 sklearn 的 SelectKBest 来选择一些要保留的特性。该方法使用统计测试来选择与目标具有最高相关性的特征。这里我们将保留前 100 个功能。
from sklearn.feature_selection import SelectKBest, f_classif*# feature extraction*
k_best = SelectKBest(score_func=f_classif, k=100)*# fit on train set*
fit = k_best.fit(X_train, y_train)*# transform train set*
univariate_features = fit.transform(X_train)
递归特征消除
递归特征选择通过消除最不重要的特征来工作。它以递归方式继续,直到达到指定的特征数。递归消除可用于任何通过coef_或feature_importances_为特征分配权重的模型
这里,我们将使用随机森林来选择 100 个最佳功能:
from sklearn.feature_selection import RFE# feature extraction
rfe = RFE(rfc, n_features_to_select=100)# fit on train set
fit = rfe.fit(X_train, y_train)# transform train set
recursive_features = fit.transform(X_train)
使用SelectFromModel功能选择
像递归特征选择一样,sklearn 的SelectFromModel与任何具有coef_或feature_importances_属性的估计器一起使用。它会移除值低于设定阈值的要素。
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier# define model
rfc = RandomForestClassifier(n_estimators=100)# feature extraction
select_model = feature_selection.SelectFromModel(rfc)# fit on train set
fit = select_model.fit(X_train, y_train)# transform train set
model_features = fit.transform(X_train)
主成分分析
PCA(主成分分析)是一种降维技术,它将数据投影到一个更低维的空间中。
虽然有许多有效的降维技术,主成分分析是我们在这里探讨的唯一例子。
PCA 在许多情况下都很有用,但特别是在多重共线性过大或预测因子的解释不重要的情况下。
这里我们将应用 PCA 并保留 90%的方差:
from sklearn.decomposition import PCA
# pca - keep 90% of variance
pca = PCA(0.90)principal_components = pca.fit_transform(X_train)
principal_df = pd.DataFrame(data = principal_components)print(principal_df.shape)(250, 139)
我们可以看到剩下的 139 个特征解释了我们数据中 90%的差异。
结论
特征选择是任何机器学习过程的重要部分。在这里,我们探讨了几种有助于提高模型性能的特征选择和降维方法。
Airbnb 柏林数据集上的特征选择和回归
数据科学的多学科领域可以从结构化和非结构化数据中提取深刻的见解和知识。它统一了统计学、数据分析和机器学习等相关概念,用数据来理解和分析实际现象。机器学习(ML)使计算机能够在没有明确编程的情况下进行学习和解释。ML 方法广泛应用于金融、医疗保健、材料科学、运输、石油和天然气等领域。
在这项研究中,我们对柏林地区的 Airbnb 数据集[1]进行了分析,并使用回归分析预测了 Airbnb 的价格。我们使用了基于 python 的开源数据分析平台的“sci kit-learn”[3]库中可用的随机森林回归[2]模型,这些图是使用 Matplotlib 创建的。
Airbnb 数据集的形状是(22552,96)。这 96 个要素中有许多是非数字要素,并且许多要素有缺失值。使用标签编码器将一些非数字特征转换为数字特征。使用正向填充方法填充缺失值,并从“$”、“、”等中清除某些要素。使用条带法。此外,我们使用 Airbnb 的经度和纬度创建了 Airbnb 到柏林中心的新特征“距离”。这个新要素随后被追加到原始数据集中。数据处理完成后,我们使用 scikit-learn 中的“功能选择”来选择重要的功能。我们计算每个特征和目标之间的 χ2 ,并选择具有最佳 χ2 分数的期望数量的特征。最佳的 25 个特征和 χ2 分数在下面的表 1 中列出。
**Table 1\. Best 25 features of the dataset and their corresponding *χ2* scores.
Features χ2 scores**
maximum_nights 2.152729e+10
minimum_nights 8.856912e+05
security_deposit 7.716334e+05
number_of_reviews 8.341015e+04
square_feet 2.982368e+04
property_type 2.222414e+04
calculated_host_listings_count 1.072021e+04
neighbourhood_cleansed 1.071895e+04
accommodates 7.771539e+03
room_type 4.234147e+03
neighbourhood 3.254091e+03
guests_included 3.061216e+03
bedrooms 2.380380e+03
cancellation_policy 1.444670e+03
distance 9.580979e+02
host_is_superhost 6.349556e+02
neighbourhood_group_cleansed 5.957898e+02
instant_bookable 3.502228e+02
bathrooms 3.290317e+02
review_scores_rating 2.151681e+02
is_location_exact 9.053878e+01
review_scores_cleanliness 5.194306e+01
review_scores_accuracy 2.410470e+01
bed_type 7.055763e+00
longitude 1.016177e-01
接下来,只选择了前五个特性。这个子集被分成训练和测试数据。“价格”被设定为目标特征。训练数据子集用于拟合随机森林,而测试子集用于预测。通过使用包括前十、前十五、前二十和前二十五个特征的不同子集来重复这一过程。对于每种情况,我们计算的值定义为
**adjusted R^2 = 1-R^2(N-1)/(N-M-1)**
其中 N 是数据集中的点数,M 是独立要素的数量,而是拟合优度的测量值。随着模型特征数量的增加而增加。因为总是增加而从不减少,所以随着更多的术语被添加到模型中,它可能看起来更适合。在回归分析中,向数据中添加更多的变量是很诱人的,这可能会产生误导。其中一些变量可能是重要的,但人们不能确定这种重要性只是偶然的。通过对那些额外变量进行惩罚来对此进行补偿。相应的曲线如图 1 所示。

图一。图中的特征数和相应的调整后的 R
结论:
通过图表进行探索性数据分析(EDA)无疑是有用和重要的。然而,对于大型数据集,采用 EDA 变得极其困难,因为有大量的图要分析。因此,通过 scikit learn 的 SelectKBest 方法进行的特性选择提供了一种定义良好的特性选择方式,如这里所示。
通过过滤选择特征:什么会出错?(剧透:很多)
如何通过单变量过滤正确(和不正确)执行特征选择的说明
从计算机科学到心理学,处理具有比观察数量多得多的特征的数据集现在是许多领域的共同之处。这通常被称为“p > n”问题(其中 p =特征数,n =观察数),“维数灾难”,或者我个人最喜欢的“短胖数据”。
传统方法,如线性回归,在这种情况下会失效。我们可以在一个玩具示例中观察到这一点,在这个示例中,我们删减了“mtcars”数据集,以生成#行< # of columns. When trying to calculate beta coefficients, we run into singularity problems.
Feature Selection
A common strategy to deal with this issue is to perform feature selection where the aim is to find a meaningful subset of features to use in model construction. There are many different ways to carry out feature selection (for a summary check out this 概览。这里,我们将关注最简单的变体,即单变量过滤器的特征选择。与其他特征选择策略一样,单变量过滤器的目的是找到与结果相关的特征子集。这可以通过将特征与结果相关联并仅选择那些满足特定阈值的特征(例如, r >的绝对值)以非常直接的方式来实现。
为什么专注于单变量过滤器进行特征选择?
正如在统计学习的元素 (ESL)和 2002 年的论文、中指出的,许多发表的作品都错误地执行了特征选择。毫无疑问,这将继续是一个问题,因为随着研究人员努力处理更大的数据集,p > n 数据在生物、社会科学和健康领域继续变得更加常见。这并不奇怪为什么过滤通常是不正确的,乍看起来,简单地删除数据集中与结果无关的特征似乎并没有什么错。
为了说明通过过滤进行的特征选择是如何错误地和正确地完成的,我们将首先生成一个简短、丰富的数据集,其中我们的预测器在很大程度上与我们的因变量“y”无关。关于模型拟合,我们将基本上按照第 245–247 页的 ESL 进行,并通过一个例子进行编码,看看这些数字是如何产生的。首先,让我们生成一些基本正交的数据。
单变量过滤错误的方式
现在我们有了数据,我将通过筛选交叉验证的之外的数据来说明如何错误地过滤数据。也就是说,通过获取整个数据集,保持与结果相关的特征等于或高于 r = .1,并且随后建立模型。对于 100 次迭代,我们将把这个数据集分成训练集和测试集,并检查观察值和预测值之间的相关性分布。

Distribution of outcomes for the incorrectly filtered model. The thick red line indicates the mean.
总的来说,这些模型看起来很棒,观察值和预测值之间的平均相关性大约为 0.5。唯一的问题是,模拟数据在很大程度上是独立的,平均相关性应该在 0 左右!我们通过选择基于所有数据的变量引入了偏差。在变量选择后实施验证方案并不能准确描述模型在真正独立的测试数据集上的应用。现在让我们观察如何通过正确过滤来执行选择。
正确的单变量过滤方法
当正确执行单变量过滤时,特征选择发生在外部交叉验证循环中,而模型调整发生在内部循环中。如果我前面提到的样本有偏差是真的,我们应该会在内部验证循环中看到类似的高相关性,但在外部循环和后续遗漏的样本中完全缺乏泛化能力。
我们将在 SBF 的 caret 中使用一个函数来实现这一点。在“sbfControl”中对控制滤波的外部验证回路进行说明,而在“trainControl”中对参数调整进行正常说明。对于连续的结果和特征,caret 利用广义加法模型将结果与每个特征联系起来——这是一个很好的接触,可以发现非线性关系。这些模型中的每一个的 p 值被用作过滤标准。默认值设置为 p = .05,这意味着只有当特征与该级别的结果有显著关系时,才会保留这些特征。

A closer at whats happening during the nested validation for filtering. Thick lines indicate the distribution mean.
正如预期的那样,我们在内部训练循环中看到了很强的关系,但在外部训练循环中以及当我们最终将我们的模型拟合到遗漏的数据时,我们观察到了本质上正交的预测(平均值 r 正好在 0 附近)。如果你查看第 246 页的 ESL ,你会注意到类似的结果模式。
与 Glmnet 的比较
最后,作为质量保证检查,我们将把我们的结果与正则化模型(一种添加了参数以防止过度拟合的模型)进行比较,正则化模型应该给出与正确的单变量过滤方法几乎相同的结果。

Distribution of outcomes for the glmnet models. The thick green line indicates the mean.
不出所料,平均 r right 在 0 左右。
在结束之前,我们将最后快速浏览一次,并排查看正确的过滤、不正确的过滤和 glmnet 模型,注意不正确的过滤会对预测产生多大的偏差。

Resulting distributions of the three approaches
包装完毕
总之,如果您决定使用单变量过滤进行特征筛选,请确保在交叉验证方案中执行特征选择,而不是在整个数据集上执行。这样做将导致特征已经“见证”了遗漏的数据,因此不能有效地模拟将模型应用到真实的测试集。需要注意的一点是,如果没有考虑结果,这并不一定适用于筛选特征。例如,删除以下要素:彼此高度相关、不包含方差或者是其他要素集的线性组合。最后,根据您的需要,还值得记住的是,有许多防止过度拟合和/或本质上执行特征选择的正则化方法可能是比任何用于特征选择的包装器或过滤器方法更好的选择(并且更快)。
Python 中随机搜索的特征选择
如何在 Python 中使用随机搜索进行要素选择

Photo by Ian Gonzalez on Unsplash
特征选择一直是机器学习的一大任务。根据我的经验,我可以肯定地说,特征选择比型号选择本身更重要。
特征选择和共线性
我已经写了一篇关于功能选择的文章。这是一种在二元分类模型中测量特征重要性的无监督方式,使用皮尔逊卡方检验和相关系数。
一般来说,对于简单的特征选择,无监督的方法通常是足够的。然而,每个模型都有自己的方式来“思考”这些特征,并处理它们与目标变量的相关性。而且,还有不太在意共线性(即特征之间的相关性)的模型,以及其他出现非常大问题的模型(例如线性模型)。
尽管可以通过模型引入的某种相关性度量标准(例如,对线性回归系数执行的 t-test 的 p 值)对特性进行排序,但仅采用最相关的变量是不够的。想想一个等于另一个的特征,只是乘以 2。这些特征之间的线性相关性如果 1 和这个简单的乘法不影响与目标变量的相关性,所以如果我们只取最相关的变量,我们就取原始特征和相乘的那个。这导致了共线性,这对我们的模型来说是相当危险的。
这就是为什么我们必须引入一些方法来更好地选择我们的功能。
随机搜索
随机搜索是数据科学家工具箱中非常有用的工具。这是一个非常简单的技术,经常使用,例如,在交叉验证和超参数优化中。
很简单。如果你有一个多维网格,并想在这个网格上寻找使某个目标函数最大化(或最小化)的点,随机搜索工作如下:
- 在网格上随机取一点,测量目标函数值
- 如果该值比迄今为止达到的最佳值更好,则将该点保存在内存中。
- 重复预定的次数
就是这样。只是产生随机点,并寻找最好的一个。
这是寻找全局最小值(或最大值)的好方法吗?当然不是。我们寻找的点在一个非常大的空间中只有一个(如果我们幸运的话),并且我们只有有限的迭代次数。在一个 N- 点网格中得到那个单点的概率是 1/N 。
那么,为什么随机搜索会被这么多使用呢?因为我们从来没有真正想要最大化我们的绩效评估;我们想要一个好的,合理的高值,它不是可能的最高值,以避免过度拟合。
这就是为什么随机搜索是可行的,并且可以用于特征选择。
如何使用随机搜索进行特征选择
随机搜索可用于特征选择,结果相当好。类似于随机搜索的程序的一个例子是随机森林模型,它为每棵树执行特征的随机选择。
这个想法非常简单:随机选择特性,通过 k 倍交叉验证测量模型性能,并重复多次。提供最佳性能的功能组合正是我们所寻求的。
更准确地说,以下是要遵循的步骤:
- 生成一个介于 1 和特征数之间的随机整数 N 。
- 生成一个 0 到 N-1 之间的 N 整数随机序列,不重复。这个序列代表了我们的特征阵列。记住 Python 数组是从 0 开始的。
- 在这些特征上训练模型,并用 k-fold 交叉验证对其进行交叉验证,保存一些性能测量的平均值。****
- 从第 1 点开始重复,重复次数不限。
- 最后,根据所选择的性能度量,获得给出最佳性能的特征阵列。
Python 中的一个实际例子
对于这个例子,我将使用包含在 sklearn 模块中的乳腺癌数据集。我们的模型将是一个逻辑回归,我们将使用准确性作为性能测量来执行 5 重交叉验证。
首先要导入必要的模块。
**import sklearn.datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
import numpy as np**
然后我们可以导入乳腺癌数据,并将其分解为输入和目标。
**dataset= sklearn.datasets.load_breast_cancer()
data = dataset.data
target = dataset.target**
我们现在可以创建一个逻辑回归对象。
**lr = LogisticRegression()**
然后,我们可以测量所有特征在 k 倍 CV 中的平均准确度。
**# Model accuracy using all the features
np.mean(cross_val_score(lr,data,target,cv=5,scoring="accuracy"))
# 0.9509041939207385**
是 95%。让我们记住这一点。
现在,我们可以实现一个随机搜索,例如,300 次迭代。
**result = []# Number of iterations
N_search = 300# Random seed initialization
np.random.seed(1)for i in range(N_search):
# Generate a random number of features
N_columns = list(np.random.choice(range(data.shape[1]),1)+1)
# Given the number of features, generate features without replacement
columns = list(np.random.choice(range(data.shape[1]), N_columns, replace=False))
# Perform k-fold cross validation
scores = cross_val_score(lr,data[:,columns], target, cv=5, scoring="accuracy")
# Store the result
result.append({'columns':columns,'performance':np.mean(scores)})# Sort the result array in descending order for performance measure
result.sort(key=lambda x : -x['performance'])**
在循环和排序函数结束时,结果列表的第一个元素就是我们要寻找的对象。**
我们可以使用这个值来计算这个特性子集的新性能度量。
**np.mean(cross_val_score(lr, data[:,result[0][‘columns’]], target, cv=5, scoring=”accuracy”))
# 0.9526741054251634**
如您所见,精确度提高了。
结论
随机搜索可以是执行特征选择的强大工具。它并不意味着给出为什么一些特性比其他特性更有用的原因(相对于其他特性选择过程,如递归特性消除),但它可以是一个有用的工具,可以在更短的时间内达到好的结果。
自动化人工智能的特征选择评估
如何为特征选择算法设置自动选择

特征选择算法寻求在数据中有效且高效地找到相关特征的最优子集。随着要素数量和数据集大小的增加,这一预处理步骤变得至关重要。在本帖中,我们将展示我们在 TapReason 开发的独特自动流程,用于评估特定数据集的不同特征选择算法的有效性。在第一和第二部分,我们解释了执行特征选择的动机,并回顾了常见的特征选择方法(FSM)。第三部分描述了我们在研究中使用的方法及其结果。在第四部分,我们提出了 TapReason 算法来评估和选择特征选择算法。
动机
在机器学习和模式识别中,特征是正在观察的现象的单个可测量的属性或特征。收集和处理数据可能是一个昂贵且耗时的过程。因此,选择信息丰富的、有区别的和独立的特征是有效算法的关键步骤。
在我们生活的信息时代,数据集的规模在实例数量和可用特性数量上都变得越来越大。在许多不同的领域中,拥有数万个或更多要素的数据集已经变得非常常见。此外,在许多情况下,算法开发人员通过使用称为特征生成的过程来增加可用特征的数量。要素生成是从一个或多个现有要素创建新要素的过程,以便在统计分析中使用。通常,特征生成过程将现有特征的表示转换成新的更复杂的表示,这将有助于算法更有效地执行。该过程显著增加了特征的数量,尤其是如果它是自动完成的话。以如此多的特征结束的问题是,一些算法在多个维度上表现很差,并且在一些维度上,它可能导致过度拟合。
对于高维数据,通常许多特征对于给定的学习任务是不相关的或冗余的,在性能或计算成本方面具有有害的后果(“维数灾难”)。特征选择是降维、去除无关数据、提高学习精度、提高结果可理解性的有效方法【1】。
由于一些预测模型(PM)与特定的特征选择方法一起工作得更好,我们需要仔细选择 FSM 以最好地适合 PM。在 TapReason 中,人工智能算法会自动选择预测模型。因此,我们需要找到一个能够有效支持我们使用的所有 PM 的特征选择算法。此外,我们还提出了如何衡量特征选择效率的问题。我们如何知道我们在选择功能方面越来越好?以及如何利用积累的知识来改进 FS 算法?
常见特征选择方法
许多特征选择方法已经被提出并研究用于机器学习应用。它们可以分为四大类:嵌入式、包装器、过滤器和混合方法【1】。
过滤方法
滤波方法是预处理方法,独立于学习算法,具有很好的通用性。他们试图从数据中评估特征的优点,忽略了所选特征子集对学习算法性能的影响【1】。它们的计算复杂度很低,但是学习算法的精度不能保证得到提高。例如,一些方法是根据方差、相关性、单变量特征选择(基于单变量统计测试的选择,例如 chi2 测试、F 值)、通过压缩技术(例如 PCA)或通过计算与输出的相关性(例如 Gram-Schmidt、互信息)来对特征进行排序。
包装方法
包装方法是根据变量子集对给定预测器的有用性来评估变量子集的方法。这些方法使用预定学习算法的预测准确性来确定所选子集的良好性。这些方法的两个主要缺点是:
- 当观察次数不足时,过拟合风险增加。
- 当变量数量很大时,计算时间很长。
例如,在线性回归分析中提出了逐步方法,如人工神经网络的 AIC、BIC 和 MSE。
嵌入式方法
嵌入式方法将特征选择作为训练过程的一部分,并且通常特定于给定的学习算法,因此可能比前两类更有效。像决策树或人工神经网络这样的传统机器学习算法就是嵌入式方法的例子。
混合方法
提出了混合方法来结合过滤器和包装器的最佳特性。首先,使用过滤方法来减少特征维度空间,这将被后续的包装器考虑。然后,使用包装器来寻找最佳候选子集。混合方法通常实现包装器特有的高精度和过滤器特有的高效率【2】。在 TapReason 中,我们使用混合方法过滤器并嵌入到各种组合中,以自动找到最适合手头问题的特征选择方法。
评估有限状态机有效性的方法
对于研究预测方法对特征选择方法的反应的过程,我们选择了九种 FSM 和九种预测方法(PM ),并计算预测的均方对数误差。在表 1 中,您可以看到不同 PM 的均方对数误差与 FSM 的相关性。每一列的颜色分别从绿色(最好)到红色(最差)。根据表 1,对于大多数 PM,使用低方差的 FSM 是最差的 FSM。根据表 1,朴素贝叶斯是最差的 PM。全非零 FSM 是由至少一个 FSM 选择的所有特征的组合。所有 5 个非零 FSM 是由至少 5 个 FSM 选择的所有特征的组合。

Table 1: feature selection method Vs prediction methods. Image by Author.
有几种方法可以分析表 1 所示的结果,以评估不同的特性选择方法。例如:
- 绝对最小值、最佳均方对数误差 FSM/PM。在表 1 中,最佳结果是用于随机森林回归的梯度推进(FSM)。
- 对列中得分最高的 FSM 进行排名,chi 测试,LassoCV,所有 5 个非零特征,都获得了两次最高分。
- 对 FSM 得分的平均值进行排名。在表 1 中,它是所有 5 个非零功能的 FSM。
上述每种方法都指向不同的最优 FSM。因此我们引入了第四种方法:
4.最小𝛥-在要素选择算法中描述。这种方法显示出最有希望的结果。
在表 2 中,我们显示了 PM 的均方对数误差和最小均方对数误差之间的𝛥。使用 FSM 行 X 1000 的平均值分析结果表,在 LSV: L1 产生最小值。可以看到,每一列都有一个 FSM 为零,这是 PM 的最小均方对数误差。FSM、LassoCV 和所有 5 个非零具有 PM ( FSM)的最小均方对数误差中的三个。Min )但是他们都没有最小的平均𝛥.FSM LSV: L1 具有最小的平均𝛥.

Table 2: The FSM/PM table according 𝛥 from the Minimum. Image by Author.
根据这一分析,选择 Lasso 回归 FSM,因为它在所有 PMs 中对 LSV: L1 具有最好的均方对数误差。0.0384782765 的结果比较接近绝对最小值(绝对值)。Min) 最适为 0.03735447816。但是进一步的研究发现,结果相对较差的 PM 倾向于使选择过程偏向对它们表现最好的算法。因此,我们决定通过根据它们与绝对最小值(绝对值)的距离调整 PMs 的权重来抵消这种影响。最小。根据以下等式计算距离:

密克罗尼西亚联邦。min-FSM 的最小均方对数误差。
绝对。Min- 最小均方对数误差在 FSM/PM 表中。
下午。Min- 预测方法的最小均方对数误差。
PM- 最小均方对数误差。

Table 3: The FSM/PM table according to equation 1. Image by Author.
在表 3 中,我们可以看到这个例子的最终结果没有变化。但是现在 FSM 用更高的 FSM。Min 对平均值的影响(权重)较小,因此对所选的 FSM 的影响较小。这样,我们给了所有 FSM 一个机会,但不会因为大𝛥而使结果偏离最小值。
该算法
在 TapReason,我们使用几十种不同的有限状态机来搜索每个问题的最佳算法。这些 FSM 包括各种各样的滤波器、嵌入式方法以及基于过去性能构建的集合 FSM。针对每个问题,针对每个客户端定期离线更新功能选择方法。实现的算法如下:

因此,很少选择以前的方法。
未来的研究
我们知道,某些预测方法与某些特征选择方法一起使用效果更好。由于在所呈现的过程中,首先选择 FSM,所以在该过程中不表达 PM 偏好。因此,我们可以为每个 PM 收集最佳 FSM 的信息,并在选择 PM 后使用它。
[1]http://www.ijarcst.com/conference/first/conf8.pdf
[2]https://bib.irb.hr/datoteka/763354.mi pro _ 2015 _ jovicbrkicbogunovic . pdf
机器学习的特征选择(1/2)

https://images.app.goo.gl/JMcX4RLACqLSuhMw8
特征选择,也称为变量选择,是一个强大的想法,对您的机器学习工作流有重大影响。
你为什么需要它?
嗯,你喜欢把你的功能数量减少 10 倍吗?或者如果做 NLP,甚至 1000x。除了更小的特征空间,导致更快的训练和推理,还能在准确性上有可观察到的改进,或者你为你的模型使用的任何度量?如果这还不能引起你的注意,我不知道还有什么可以。
不相信我?几天前我工作时就遇到过这种事。
所以,这是一篇由两部分组成的博文,我将解释并展示如何在 Python 中进行自动特征选择,这样你就可以升级你的 ML 游戏。
将只介绍过滤器方法,因为它们比包装器方法更通用,计算量更小,而嵌入特征选择方法嵌入在模型中,不如过滤器方法灵活。
留在原处等候😉
第二部分可以访问这里
首先,最基本的
所以,你需要为你的模型找到最强大的,也就是重要的特性。我们将断言在一个重要的特征和目标变量之间有一个有意义的关系(不一定是线性的,但稍后会有更多的介绍),这类似于target ~ f(feature)。最简单的关系是线性关系,识别这种关系的强大工具是相关性。相关性意味着两个变量之间的关联或依赖,这正是我们所需要的。有很多方法来计算它,但是考虑到这篇博文的目的是密集地提供实用的建议,这里有“最简单的”2: Spearman 和 Pearson 方法,在熊猫身上
>>> # shamelessly taken from here: [https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.corr.html](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.corr.html)
>>> df = pd.DataFrame([(.2, .3), (.0, .6), (.6, .0), (.2, .1)],
... columns=['dogs', 'cats'])
>>> # df.corr(method="spearman")
>>> df.corr(method="pearson")
dogs cats
dogs 1.0 0.3
cats 0.3 1.0
因此,一旦计算出来,我们就可以得到指数,并使用它们来选择高度相关的特征,这些特征将进一步用于模型训练。在实践中,如果变量之间确实有显著的线性相关性,它应该在 0.7 以上。这些相关性测试的好处在于,在实践中,这些测试非常稳健,有时甚至可以识别非线性相关性,如果可能的话,可以用一条线进行局部近似,例如,二阶多项式相关性,或对数和平方根相关性,甚至指数相关性。相关系数会更小,可能在 0.5 到 0.7 之间。当得到这样的值时,打开 EDA 模式并绘制这些值,也许你可以发现一些依赖关系。我有时会。
pearson = pd.concat([features_df, target_df], axis=1).corr(method="pearson")
indices = pearson[abs(pearson["prediction"]) > 0.55].index
另一种方法是使用 chi2 测试。大概是这样的:
chi_sq = feature_selection.chi2(X, y)
corr_table = pd.DataFrame(zip(*chi_sq), columns = ("Correlation", "P-value"))
top_features = corr_table.sort_values("Correlation", ascending=False).head()["Correlation"]
最后一点,您需要记住:通常,数据集中的要素不仅与目标变量相关,而且它们之间也相关。你不要这个!在选择特征时,您(或算法)应尽可能选择最少数量的最重要的特征,这些特征之间尽可能正交/不相关。在第二篇博文中,将会介绍一些实现这一点的方法,敬请关注。
更大的枪:特征重要性和基于模型的选择
好的,很多这些方法都是基于统计学的,这很好,但是有时候,你只需要少一点形式主义,多一点科学知识。
scikit-learn 中的一些模型具有coef_或feature_importances_属性。一旦这些模型被训练,属性就被填充了对于特征选择非常有价值的信息。这里有两个例子,使用决策树和 L1 正则化。
基于决策树的方法
feature_importance_tree = tree.DecisionTreeClassifier()
feature_importance_tree.fit(X, y)
feature_importance_list = feature_importance_tree.feature_importances_.tolist()
indices = zip(*sorted(enumerate(feature_importance_list), key=lambda x: x[1], reverse=True)[:5])[0]
X_tree = X[:, indices]
scores = [model.fit(X_tree[train], y[train]).score(X_tree[test], y[test]) for train, test in kfcv]
既然我引起了你的注意,让我解释一下。决策树是非常好的工具,具有高度的可解释性,事实证明,它不仅仅在分类/回归问题上有用。在这个例子中,一个DecisionTreeClassifier被快速拟合到一个数据集上,然后feature_importances_被用来挑选最相关的特征,并训练一个更大、更复杂、更慢的模型。在实践中,如果您有大量数据,您可能会选择 next 方法的变体,但是对于较小的数据,这种方法非常好,能够捕获具有非线性依赖关系的特征。此外,ExtraTreesClassifier也可以很好地处理更大的数据,如果正则化(更浅的树,每片叶子更多的样本)甚至更好。永远要实验。
基于 L1 方法
对于那些还不知道的人来说,L1 正则化,由于它的性质,在模型中引入了稀疏…这正是我们所需要的,真的!
clf = linear_model.LassoCV()
sfm = feature_selection.SelectFromModel(clf, threshold=0.002)
sfm.fit(X, y)
X_l1 = sfm.transform(X)
scores = [model.fit(X_l1[train], y[train]).score(X_l1[test], y[test]) for train, test in kfcv]
与上面的树示例一样,训练了一个小模型,但与示例不同的是,coef_是驱动 sklearn 实现的特征选择的因素。因为使用了 L1,模型中的大多数系数将是 0 或接近于 0,所以任何更大的系数都可以算作一个重要特征。这适用于线性依赖关系。对于非线性模型,可以尝试 SVM,或者在线性模型之前使用 sklearn 的RBFSampler。
关于性能和大数据集的重要说明。
说你有 NLP 问题,用 TF-IDF。在一个合理的数据集上,你会有一个巨大的输出矩阵,类似于几千行(文档)和几百万列(n 元语法)。在这样的矩阵上运行任何模型都是耗时耗内存的,所以你最好使用一些速度快的东西。在这种情况下,我肯定会推荐 L1 的方法,但是用SGDClassifier(penalty="l1")代替LassoCV。这两种方法几乎是等效的,但是在大型数据集上,后者的运行速度几乎快一个数量级。所以要牢记在心。
此外,请记住,在收敛之前,您不需要训练您的要素选择模型,您不会将它们用于预测,并且最相关的要素将在模型中首先被选择。
收场白
这里大部分代码来自我在 GitHub 上的一个老项目,这里。这应该行得通,但如果行不通,不要害羞——HMU。
请注意,这篇博文和下一篇博文中的所有特征选择都适用于特征向量。这意味着,这里没有方法可以应用于视觉问题,例如,除非你想减少 CNN 最后一层的“功能”,这可能是一个好主意,idk。
此外,记住这一点,没有免费的午餐——想一想你准备做出哪些取舍,选择几个方法,进行实验,选择最适合你的问题的方法。
如果你正在读这篇文章,我想感谢你,并希望上面写的对你有很大的帮助,就像对我一样。请在评论区让我知道你的想法。你的反馈对我很有价值。
特征选择:识别最佳输入特征
在本文中,我们将了解什么是特征选择,特征选择和降维的区别。特征重要性如何帮助?了解不同的技术,如过滤方法、包装器和嵌入方法,以便用 Python 代码识别最佳特性。

什么是特征选择?
特征选择也被称为属性选择或变量选择,是特征工程的一部分。它是在数据集中选择最相关的属性或特征子集进行预测建模的过程。
在我们讲述一些机器学习金融应用之前,我们先来了解一下什么是机器学习。机器…
www.datadriveninvestor.com](https://www.datadriveninvestor.com/2019/02/08/machine-learning-in-finance/)
选定的功能有助于预测模型识别隐藏的业务洞察力。
如果我们需要预测 IT 人员的工资,那么根据我们的共同理解,我们需要工作经验、技能、工作地点和当前职位。这些是有助于薪资预测的几个关键特征。如果数据集包含人的身高,我们知道该特征与工资预测无关,因此不应作为特征选择的一部分。
特征选择是决定包含哪些相关原始特征和排除哪些不相关特征进行预测建模的过程。
特征选择和降维的区别
特征选择和降维的目标是减少数据集中属性或特征的数量。
特征选择和降维的关键区别在于,在特征选择中,我们不改变原始特征,然而,在降维中,我们从原始特征中创建新特征。这种使用降维的特征转换通常是不可逆的。
特性选择基于某些统计方法,比如我们将在本文中讨论的过滤器、包装器和嵌入式方法。
对于降维,我们使用像主成分分析(PCA)这样的技术
需要特征选择
- 帮助更快地训练模型:我们已经减少了相关特征的数量,因此训练要快得多。
- 提高模型解释能力并简化模型 —它通过仅包含最相关的特征来降低模型的复杂性,因此易于解释。这对于解释预测模型非常有帮助
- 提高模型的准确性:我们只包括与我们的预测相关的特征,这些特征提高了模型的准确性。不相关的特征会引入噪声并降低模型的准确性
- 减少过度拟合:过度拟合是指预测模型不能很好地概括测试数据或基于训练的未知数据。为了减少过度拟合,我们需要去除数据集中的噪声,并包括对预测影响最大的特征。噪声来自数据集中不相关的特征。当预测模型作为训练的一部分已经学习了噪声时,那么它将不会很好地对看不见的数据进行概括。
不同的特征选择方法
- 过滤器
- 包装材料
- 嵌入式方法
特征选择的过滤方法
过滤方法基于某个单变量度量对每个特征进行排名,然后选择排名最高的特征。一些单变量指标是
- 方差:去除常数和准常数特征
- 卡方:用于分类。这是一种独立性的统计测试,用于确定两个变量的相关性。
- 相关系数:删除重复特征
- 信息增益或互信息:评估自变量在预测目标变量时的相关性。换句话说,它决定了独立特征预测目标变量的能力。
过滤方法的优点
- 过滤方法是模型不可知的
- 完全依赖数据集中的特征
- 计算速度非常快
- 基于不同的统计方法
过滤方法的缺点
- 过滤方法着眼于单个特征,以确定其相对重要性。一个特性本身可能没什么用,但是当它与其他特性结合起来时,可能是一个重要的影响因素。过滤方法可能会遗漏这些特征。
选择最佳特征的过滤标准
使用选择独立特征
- 与目标变量高度相关
- 与其他独立变量相关性低
- 自变量的较高信息增益或互信息
特征选择的包装方法
包装器方法搜索输入要素的最佳子集来预测目标变量。它选择提供模型最佳精度的特征。包装器方法使用基于先前模型的推理来决定是否需要添加或删除新特性。
包装方法有
- 穷举搜索:评估输入特征的所有可能组合,以找到为所选模型提供最佳准确度的输入特征子集。当输入特征的数量变大时,计算开销非常大;
- 正向选择:从一个空特征集开始,并保持一次添加一个输入特征,并评估模型的准确性。这个过程一直持续到我们用预定数量的特征达到一定的精度;
- 逆向选择:从所有特征开始,然后保持一次移除一个特征,以评估模型的准确性。保留产生最佳准确度的特征集。
总是在测试数据集上评估模型的准确性。
优势
- 对每个输入要素之间的要素依赖性进行建模
- 取决于所选的型号
- 基于特征子集选择精度最高的模型
缺点:
- 计算非常昂贵,因为训练发生在每个输入特征集组合上
- 不依赖模型
特征选择的嵌入式方法
嵌入式方法使用过滤器和包装器特征选择方法的质量。特征选择嵌入在机器学习算法中。
过滤方法不包括学习,只涉及特征选择。包装器方法使用机器学习算法来评估特征子集,而不包含关于分类或回归函数的特定结构的知识,因此可以与任何学习机结合
嵌入式特征选择算法包括
- 决策树
- 正则化——L1(拉索)和 L2(山脊)正则化
通过拟合模型,使用这些机器学习技术。这些方法为我们提供了更好的准确性的特征重要性。
点击阅读更多关于 L1 和 L2 合法化的信息
在下一篇文章中,我们将使用过滤方法 在 python 中实现一些特征选择方法
参考资料:
http://ijcsit . com/docs/Volume % 202/vol 2 issue 3/ijcsit 2011020322 . pdf
https://arxiv.org/pdf/1907.07384.pdf
http://people.cs.pitt.edu/~iyad/DR.pdf
https://link . springer . com/chapter/10.1007% 2f 978-3-540-35488-8 _ 6
Python 中的要素选择—递归要素消除
寻找用于机器学习模型训练的最佳特征有时可能是一项难以完成的任务。我并不是说这个过程本身很难,只是有太多的方法可供选择。你可能已经读过很多像主成分分析这样的方法,它绝不是一个坏方法,但它不会告诉你哪些特征是最重要的——它会返回主成分,这些主成分实际上是特征的组合(用最简单的话来解释)。

为了解决这个问题,出现了递归特征消除技术。在本文中,我将讨论带交叉验证的递归特性消除( RFECV ),因为它比不带交叉验证的选项更常用。首先,让我们讨论一些术语:
递归——为了产生特定的结果或效果,多次做或说同样的事情【1】(只要谷歌一下‘递归’这个词,你马上就能找到它的要点)
特征 —被观察现象的个体可测量属性或特征[2] —数据集中的属性
交叉验证 —一种评估 ML 模型的技术,通过在可用输入数据的子集上训练几个 ML 模型,并在数据的互补子集上评估它们。使用交叉验证来检测过度拟合,即未能归纳出一个模式[3]
好了,现在解释了一些基本术语,是时候简要解释一下 RFE 背后的想法了。我会说这个街区完美地解释了它:
如前所述,递归特征消除(RFE,Guyon 等人( 2002 ))基本上是预测器的向后选择。该技术首先在整个预测因子集上建立一个模型,并计算每个预测因子的重要性分数。然后移除最不重要的预测值,重新构建模型,并再次计算重要性分数。在实践中,分析师指定要评估的预测值子集的数量以及每个子集的大小。因此,子集大小是 RFE 的调整参数。优化性能标准的子集大小用于基于重要性排名选择预测器。然后,最佳子集用于训练最终模型。[4]
说到它背后的理论,基本上就是这样了。当然,仍有一些方面需要讨论,但我不想用一堆理论来抨击你——请自行进一步探索。
好了,现在开始有趣的事情——编码!
1.数据集介绍和准备
首先,让我们讨论一下所使用的数据集。我将使用著名的泰坦尼克号数据集。我之所以选择使用这个数据集,是因为它非常有名,而且很容易清理和准备。和往常一样,你的笔记本要从导入最常见的疑点开始——【Numpy】熊猫Matplotlib。我还从 Scikit-Learn 中导入了random forestsStratifiedKFold和 RFECV 。该模型将通过随机森林进行训练,由于类别不平衡(存活/未存活),将需要分层以确保每个类别的良好代表性:
https://gist.github.com/dradecic/761479ba15e6d371b2303008c614444a#file-rfecv_1_imports-py
现在,您可以读入数据集并检查它的头部:

Head of titanic.csv
以下是关于数据清理需要做的事情:
- PassengerId 和 车票 应该被丢弃——第一个只是一个任意的整数值,第二个对每个乘客都是截然不同的。
- 性别 列的值应重新映射为 0 和 1,而不是“男性”和“女性”
- 从 姓名 中提取人物头衔,如先生、夫人、小姐……等,如果头衔常见(先生、小姐),则进一步转换为 0 和 1-0。),如果不是,则为 1(博士、牧师、上尉)。最后,名名名应该去掉
- 船舱 应替换为 船舱 _ 已知 —如果值为 NaN ,否则为 1
- 虚拟列应该从 开始创建 并且应该删除第一个虚拟列以避免共线性问题
- 年龄 中缺失的值应填入平均值的整数值
下面的代码片段将处理提到的所有问题:
https://gist.github.com/dradecic/2b6c1d81e6089cf6022b36f82b460f4b
完成后,数据集应该是这样的:

Head of cleaned titanic.csv
您的数据集现在已经清理和准备好了,您可以继续下一部分。
2.移除相关特征
RFE 的主要问题是运行起来可能很昂贵——所以你应该事先尽你所能减少功能的数量。移除相关要素是一种很好的方法,因为您可能知道,您不希望数据集中的要素高度相关,因为它们提供相同的信息-一个要素就足够了。
为了解决这个问题,你可以绘制一个相关矩阵,然后记下哪些特征是相关的,并手动删除它们,但总有一个更聪明的方法。我决定与您分享一个简单的片段,它将检查哪些特征的相关系数高于 0.8。由于显而易见的原因,这样做时,您应该总是删除目标变量。不管怎样,这是片段:
https://gist.github.com/dradecic/f8d32045aa886756f59adc1ca50eabd1
如果您现在检查 关联 _ 特性 集合中的内容,您会看到:

Correlated features
这很好,因为该数据集不包含任何相关的要素。在您的工作中,通常您的数据集不会有这么少的属性,其中一些属性可能是相关的,因此这是查找它们的最快方法。
从数据集中删除它们的过程就像调用 一样简单。drop() 和 passingcorrelated _ features作为参数。
3.运行 RFECV
现在有趣的部分终于开始了。您将需要声明两个变量— X 和 target ,其中第一个表示所有特性,第二个表示目标变量。然后你将创建一个机器学习算法的实例(我使用的是 RandomForests )。在其中,您可以有选择地传递一个随机状态种子以获得可再现性。现在你可以创建一个 RFECV 的实例,这里需要参数:
- 估算器 —您的模型实例
- 步骤 —每次迭代要移除的特征数量
- cv —您的交叉验证,使用 StratifiedKFold 并将 K 设置为 10
- 评分 —评分标准,您想要优化的内容。我使用了“准确性”,但您可以选择任何其他选项
它应该是这样的:
https://gist.github.com/dradecic/ce30af3efc6072f18e67f0d54a13f8e7
执行这个单元可能需要一些时间——我的意思是,如果你的电脑是新的,需要测试的功能很少,这可能不会发生,但仍然需要几秒钟。
一旦执行完成,您可以使用这一行代码来查看有多少特性是产生最佳准确度的最佳特性(或者您选择的任何度量):
https://gist.github.com/dradecic/4b27705203dd018168f2eb4ddfeeca79
不仅如此,您还可以绘制出使用每种特性所获得的精度:
https://gist.github.com/dradecic/94305fc88c19976aa64ffec3716d4bba

Accuracy obtained vs. Num Features used
可以看到,使用 7 特性时,精确度约为 82.5% ,这对于我们所做的大量准备工作来说当然不算糟糕。
您还可以打印出哪些特性被认为是最不重要的,并用下面的代码片段删除它们:
https://gist.github.com/dradecic/d2bb599f662c8f586b4180d5baf17038
RFECV 的实例也有一个漂亮的feature _ importances属性,值得一试:

Feature importances
好吧,好吧,先别冲我吼。我知道这不能告诉你太多。谢天谢地,这很容易想象。一种方法是创建一个 DataFrame 对象,属性作为一列,重要性作为另一列,然后简单地按照重要性降序排列数据帧。然后,您可以使用绘图库(如【Matplotlib】)来绘制条形图(这种情况下最好是水平的)以获得漂亮的视觉表示。下面是这样做的代码:
https://gist.github.com/dradecic/4bc8f929a86795c0d9c5e663293cd71f
运行此代码单元后,您将获得特性重要性的可视化表示:

RFECV — Feature Importance
这基本上就是递归特征消除!这张图表会告诉你一切。现在,您可以尝试使用这 7 个特征来训练模型,稍后,您可以尝试子集化并仅使用三个最重要的特征( 票价 、 年龄 和 性别 )。
4.结论
最后,我只想说谢谢你把这篇文章读完。RFE 当然不是唯一使用的特征选择方法,但它是其中之一,我当然觉得它没有得到应有的关注。我希望这篇文章背后的代码和逻辑能对你的日常工作和/或兼职项目有所帮助。
直到下一次…
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
[## 通过我的推荐链接加入 Medium-Dario rade ci
作为一个媒体会员,你的会员费的一部分会给你阅读的作家,你可以完全接触到每一个故事…
medium.com](https://medium.com/@radecicdario/membership)
参考
[1]https://dictionary . Cambridge . org/dictionary/English/recursive
[2]https://en . Wikipedia . org/wiki/Feature _(machine _ learning)
[3]https://docs . AWS . Amazon . com/machine-learning/latest/DG/cross-validation . html
[4]https://book down . org/max/FES/recursive-feature-elimination . html
使用过滤器方法在 Python 中选择要素
在本文中,我们将使用 Pythons 中的过滤方法实现特征选择。这里我们将探讨包括相关性、互信息和的过滤方法
先决条件:功能选择理解

特征选择的过滤方法
过滤方法基于一些单变量度量对每个特征进行排名,然后选择排名最高的特征。一些单变量指标是
- 方差:去除常数和准常数特征
- 卡方:用于分类。这是一种独立性的统计测试,用于确定两个变量的相关性。
- 相关系数:删除重复特征
- 信息增益或互信息:评估自变量在预测目标变量时的相关性。换句话说,它决定了独立特征预测目标变量的能力
过滤方法的优势
- 过滤方法是模型不可知的
- 完全依赖数据集中的特征
- 计算速度非常快
- 基于不同的统计方法
过滤方法的缺点
- 过滤方法着眼于单个特征,以确定其相对重要性。一个特性本身可能没什么用,但是当它与其他特性结合起来时,可能是一个重要的影响因素。过滤方法可能会遗漏这些特征。
选择最佳特征的过滤标准
使用选择独立特征
- 与目标变量高度相关
- 与另一个独立变量相关性低
- 自变量的较高信息增益或互信息
此处有可用的数据集
导入所需的库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
from sklearn.model_selection import train_test_split
**from sklearn.feature_selection import mutual_info_regression, mutual_info_classif**
读取数据集中的数据
dataset= pd.read_csv(‘c:\\data\\auto-mpg.csv’)
dataset.head(2)

dataset.info()

我们看到马力不是浮动的,但是上面的数据显示马力是数字的。
检查分类特征的存在
dataset.describe(include=’O’)

将马力特性更新为 int 并用 0 填充所有空值
dataset[‘horsepower’] = pd.to_numeric(dataset[‘horsepower’], errors=’coerce’).fillna(0).astype(int)
我们现在再次检查分类变量的存在
dataset.describe(include=’O’)

我们看到马力不再是一个分类变量,汽车名称是唯一的分类变量。
为汽车名称创建一个 labelEncoder,用 0 和 n_classes-1 之间的值对汽车名称进行编码。在我们的例子中,汽车名称的 n _ classes 是 305
**from sklearn.preprocessing import LabelEncoder**
labelencoder = LabelEncoder()
**X_en= dataset.iloc[:, 8].values
X_en = labelencoder.fit_transform(X_en)**
创建输入要素 X 和目标变量 y
X= dataset.iloc[:,1:7]
X[‘Car’] = X_en
y= dataset.iloc[:,0].values
在将所有输入要素转换为数字(包括目标变量)后,创建一个包含所有输入要素的数据集
full_data= X.copy()
full_data[‘mpg’]= y
full_data.head(2)

应用过滤方法的步骤 1
识别与目标变量高度相关的输入特征。
这里我们打印每个输入特征与目标变量的相关性
**importances = full_data.drop(“mpg”, axis=1).apply(lambda x: x.corr(full_data.mpg))**
indices = np.argsort(importances)
print(importances[indices])

绘制这些数据以便可视化
**names=['cylinders','displacement','horsepower','weight','acceleration','model year', 'car']** plt.title('Miles Per Gallon')
**plt.barh(range(len(indices)), importances[indices], color='g', align='center')**
plt.yticks(range(len(indices)), [names[i] for i in indices])
plt.xlabel('Relative Importance')
plt.show()

我们希望保留仅与目标变量高度相关的特征。这意味着输入特征对预测目标变量有很大的影响。
我们将阈值设置为绝对值 0.4。只有当输入特征与目标变量的相关性大于 0.4 时,我们才保留输入特征
for i in range(0, len(indices)):
** if np.abs(importances[i])>0.4:**
print(names[i])

Features with correlation >0.4 with target variable
X= dataset[ [‘cylinders’, ‘displacement’, ‘horsepower’, ‘weight’, ‘acceleration’, ‘model year’]]
我们现在已经将输入特征从 7 个减少到 6 个。汽车名称被删除,因为它与 mpg(每加仑英里数)的相关性不高
应用过滤方法的步骤 2
识别与其他独立变量相关性较低的输入特征。
根据步骤 1 遍历所有过滤后的输入要素,并检查每个输入要素与所有其他输入要素的相关性。
我们将保留与其他输入要素不高度相关的输入要素
for i in range(0,len(X.columns)):
for j in range(0,len(X.columns)):
if i!=j:
corr_1=np.abs(X[X.columns[i]].corr(X[X.columns[j]]))
**if corr_1 <0.3:
print( X.columns[i] , " is not correlated with ", X.columns[j])**
**elif corr_1>0.75:
print( X.columns[i] , " is highly correlated with ", X.columns[j])**

排量,马力,气缸,重量高度相关。我们将只保留其中的一个。
基于上述结果,我们保留气缸、加速度和车型年,并删除马力、排量和重量
X= dataset[ [‘cylinders’, ‘acceleration’, ‘model year’]]
应用过滤方法的步骤 3
求自变量相对于目标变量的信息增益或互信息
mi = mutual_info_regression(X, y)
绘制交互信息
mi = pd.Series(mi)
mi.index = X.columns
mi.sort_values(ascending=False)
mi.sort_values(ascending=False).plot.bar(figsize=(10, 4))

我们现在有了预测每加仑英里数的特征重要性。每加仑行驶的英里数可以根据汽车的汽缸数量、汽车制造年份和加速度来预测。
用于特征选择的过滤方法因此是模型不可知的,简单且易于解释
代码可用此处
特征选择技术
关于如何减少数据集中要素数量的端到端指南,包含 Python 中的实际示例。

Photo by Clem Onojeghuo on Unsplash
介绍
据《福布斯》报道,每天大约产生 2.5 万亿字节的数据[1]。然后可以使用数据科学和机器学习技术来分析这些数据,以便提供见解和做出预测。尽管在大多数情况下,最初收集的数据需要在开始任何统计分析之前进行预处理。有许多不同的原因可能需要进行预处理分析,例如:
- 收集的数据格式不正确(如 SQL 数据库、JSON、CSV 等)。
- 缺失值和异常值。
- 缩放和标准化。
- 减少数据集中存在的固有噪声(部分存储的数据可能已损坏)。
- 数据集中的某些要素可能不会收集任何信息用于我们的分析。
在本文中,我将带您了解如何使用 Kaggle 蘑菇分类数据集在 Python 中减少数据集中的要素数量。这篇文章中使用的所有代码(以及更多!)可在 Kaggle 和我的 GitHub 账户上获得。
减少统计分析过程中使用的要素数量可能会带来多种好处,例如:
- 精度提高。
- 过度拟合风险降低。
- 在训练中加速。
- 改进的数据可视化。
- 增加我们模型的可解释性。
事实上,统计证明,当执行机器学习任务时,存在每个特定任务都应该使用的最佳数量的特征(图 1)。如果添加的特性多于绝对必要的特性,那么我们的模型性能将会下降(因为添加了噪声)。真正的挑战是找出要使用的功能的最佳数量(实际上,这取决于我们可用的数据量以及我们要完成的任务的复杂性)。这就是特征选择技术帮助我们的地方!

Figure 1: Relationship between Classifier Performance and Dimensionality [2]
特征选择
有许多不同的方法可用于特征选择。其中一些最重要的是:
- 过滤方法 =过滤我们的数据集,只取其中包含所有相关特征的子集(例如,使用皮尔逊相关的相关矩阵)。
- 包装方法 =遵循过滤方法的相同目标,但使用机器学习模型作为其评估标准(例如,向前/向后/双向/递归特征消除)。我们向我们的机器学习模型提供一些特征,评估它们的性能,然后决定是否添加或删除这些特征以提高准确性。因此,这种方法可能比过滤更精确,但是计算量更大。
- 嵌入式方法 =与过滤方法一样,嵌入式方法也利用了机器学习模型。这两种方法之间的区别在于,嵌入式方法检查我们的 ML 模型的不同训练迭代,然后根据每个特征对 ML 模型训练的贡献大小来排列每个特征的重要性(例如,LASSO 正则化)。

Figure 2: Filter, Wrapper and Embedded Methods Representation [3]
实际实施
在本文中,我将利用蘑菇分类数据集,通过观察给定的特征来尝试预测蘑菇是否有毒。在这样做的同时,我们将尝试不同的特征消除技术,看看这会如何影响训练时间和整体模型精度。
首先,我们需要导入所有必需的库。
下图显示了我们将在本例中使用的数据集。

Figure 3: Mushroom Classification dataset
在将这些数据输入我们的机器学习模型之前,我决定对所有的分类变量进行一次热编码,将我们的数据分成特征( X )和标签( Y ),最后在训练集和测试集中使用。
特征重要性
基于集合(例如额外的树和随机森林)的决策树模型可以用于对不同特征的重要性进行排序。了解我们的模型赋予哪些特征最重要,对于理解我们的模型如何做出预测(从而使其更具解释力)至关重要。同时,我们可以去掉那些不会给我们的模型带来任何好处的特征(或者迷惑它做出错误的决定!).
如下所示,使用所有特征训练随机森林分类器,在大约 2.2 秒的训练时间内达到 100%的准确度。在下面的每个例子中,每个模型的训练时间将在每个片段的第一行打印出来,供您参考。
2.2676709799999992
[[1274 0]
[ 0 1164]]
precision recall f1-score support
0 1.00 1.00 1.00 1274
1 1.00 1.00 1.00 1164
accuracy 1.00 2438
macro avg 1.00 1.00 1.00 2438
weighted avg 1.00 1.00 1.00 2438
一旦我们的随机森林分类器被训练,我们就可以创建一个特征重要性图,以查看哪些特征被认为对我们的模型进行预测是最重要的(图 4)。在本例中,下面只显示了前 7 项功能。

Figure 4: Feature Importance Plot
既然我们知道了随机森林认为哪些特性是最重要的,我们可以尝试使用前 3 个来训练我们的模型。
正如我们在下面看到的,仅使用 3 个特征导致准确度仅下降 0.03%,并且将训练时间减半。
1.1874146949999993
[[1248 26]
[ 53 1111]]
precision recall f1-score support
0 0.96 0.98 0.97 1274
1 0.98 0.95 0.97 1164
accuracy 0.97 2438
macro avg 0.97 0.97 0.97 2438
weighted avg 0.97 0.97 0.97 2438
我们还可以通过可视化一个经过训练的决策树结构来理解如何执行特征选择。
0.02882629099999967
[[1274 0]
[ 0 1164]]
precision recall f1-score support
0 1.00 1.00 1.00 1274
1 1.00 1.00 1.00 1164
accuracy 1.00 2438
macro avg 1.00 1.00 1.00 2438
weighted avg 1.00 1.00 1.00 2438
位于树结构顶部的特征是我们的模型为了执行分类而保留的最重要的特征。因此,通过只选择顶部的前几个特征并丢弃其他特征,可能会导致创建一个具有可观准确度分数的模型。

Figure 5: Decision Tree Visualization
递归特征消除(RFE)
递归特征消除(RFE)将机器学习模型的实例和要使用的最终期望数量的特征作为输入。然后,它通过使用机器学习模型准确性作为度量标准来对特征进行排序,从而递归地减少要使用的特征的数量。
创建一个 for 循环,其中输入要素的数量是我们的变量,然后可以通过跟踪每个循环迭代中记录的精度来找出我们的模型需要的最佳要素数量。使用 RFE 支持方法,我们可以找出被评估为最重要的特征的名称(rfe . support返回一个布尔列表,其中 TRUE 表示一个特征被认为是重要的,FALSE 表示一个特征不被认为是重要的)。
210.85839133899998
Overall Accuracy using RFE: 0.9675963904840033
SelecFromModel
SelectFromModel 是另一种 Scikit-learn 方法,可用于特征选择。该方法可用于具有 coef_ 或feature _ importances _属性的所有不同类型的 Scikit-learn 模型(拟合后)。与 RFE 相比,SelectFromModel 是一个不太健壮的解决方案。事实上,SelectFromModel 只是根据计算出的阈值来删除不太重要的特性(不涉及优化迭代过程)。
为了测试 SelectFromModel 的功效,我决定在这个例子中使用一个 ExtraTreesClassifier。
ExtraTreesClassifier(极度随机化的树)是基于树的集成分类器,与随机森林方法相比,它可以产生更小的方差(因此降低了过度拟合的风险)。随机森林和极度随机化的树之间的主要区别在于,在极度随机化的树中,节点被采样而没有替换。
1.6003950479999958
[[1274 0]
[ 0 1164]]
precision recall f1-score support
0 1.00 1.00 1.00 1274
1 1.00 1.00 1.00 1164
accuracy 1.00 2438
macro avg 1.00 1.00 1.00 2438
weighted avg 1.00 1.00 1.00 2438
相关矩阵分析
另一种可用于减少数据集中要素数量的方法是检查要素与标注的相关性。
使用皮尔逊相关,我们返回的系数值将在-1 和 1 之间变化:
- 如果两个特征之间的相关性为 0,这意味着改变这两个特征中的任何一个都不会影响另一个。
- 如果两个特征之间的相关性大于 0,这意味着增加一个特征中的值也会增加另一个特征中的值(相关系数越接近 1,两个不同特征之间的结合越强)。
- 如果两个特征之间的相关性小于 0,这意味着增加一个特征中的值将使另一个特征中的值减小(相关系数越接近-1,两个不同特征之间的这种关系就越强)。
在这种情况下,我们将只考虑与输出变量相关度至少为 0.5 的特征。
bruises_f 0.501530
bruises_t 0.501530
gill-color_b 0.538808
gill-size_b 0.540024
gill-size_n 0.540024
ring-type_p 0.540469
stalk-surface-below-ring_k 0.573524
stalk-surface-above-ring_k 0.587658
odor_f 0.623842
odor_n 0.785557
Y 1.000000
Name: Y, dtype: float64
现在,我们可以通过创建一个相关矩阵来进一步了解不同相关特征之间的关系。

Figure 6: Correlation Matrix of highest correlated features
此分析中另一个可能的控制方面是检查所选变量是否高度相关。如果是,那么我们只需要保留其中一个相关的,而丢弃其他的。
最后,我们现在可以只选择与 Y 最相关的特征,并训练/测试 SVM 模型来评估这种方法的结果。
0.06655320300001222
[[1248 26]
[ 46 1118]]
precision recall f1-score support
0 0.96 0.98 0.97 1274
1 0.98 0.96 0.97 1164
accuracy 0.97 2438
macro avg 0.97 0.97 0.97 2438
weighted avg 0.97 0.97 0.97 2438
单变量选择
单变量特征选择是一种统计方法,用于选择与我们的对应标签具有最强关系的特征。使用 SelectKBest 方法,我们可以决定使用哪些指标来评估我们的特性以及我们想要保留的 K 个最佳特性的数量。根据我们的需求,可以使用不同类型的评分功能:
- 分类 = chi2,f_classif,mutual_info_classif
- 回归= f _ 回归,mutual _ info _ 回归
在本例中,我们将使用 chi2(图 7)。

Figure 7: Chi-squared Formula [4]
卡方(Chi2) 可以将非负值作为输入,因此,首先,我们将输入数据的范围定在 0 到 1 之间。
1.1043402509999964
[[1015 259]
[ 41 1123]]
precision recall f1-score support
0 0.96 0.80 0.87 1274
1 0.81 0.96 0.88 1164
accuracy 0.88 2438
macro avg 0.89 0.88 0.88 2438
weighted avg 0.89 0.88 0.88 2438
套索回归
当将正则化应用于机器学习模型时,我们向模型参数添加惩罚,以避免我们的模型试图过于接近我们的输入数据。通过这种方式,我们可以使我们的模型不那么复杂,并且我们可以避免过度拟合(使学习到我们的模型,不仅是关键数据特征,还有它的内在噪声)。
一种可能的正则化方法是拉索(L1)回归。当使用 Lasso 回归时,如果输入特征的系数对我们的机器学习模型训练没有积极的贡献,它们就会缩小。这样,一些特征可能会被自动丢弃,为它们分配等于零的系数。
LassoCV Best Alpha Scored: 0.00039648980844788386
LassoCV Model Accuracy: 0.9971840741918596
Variables Eliminated: 73
Variables Kept: 44
一旦训练了我们的模型,我们可以再次创建一个特性重要性图,以了解哪些特性被我们的模型认为是最重要的(图 8)。这真的很有用,尤其是在试图理解我们的模型是如何决定做出预测的时候,因此使我们的模型更容易解释。

Figure 8: Lasso Feature Importance
感谢阅读!
联系人
如果你想了解我最新的文章和项目,请通过媒体关注我,并订阅我的邮件列表。以下是我的一些联系人详细信息:
文献学
[1]什么是大数据?—大数据世界入门指南。阿努什里·苏布拉马年,爱德华卡!。访问地点:https://www.edureka.co/blog/what-is-big-data/
[2]分类中的维数灾难,假人的计算机视觉。访问:https://www . vision dummy . com/2014/04/curse-dimensionality-affect-class ification/
[3]整合化学计量学和统计学,推动成功的蛋白质组学生物标志物发现,ResearchGate。访问:https://www . research gate . net/publication/324800823 _ Integrated _ chemo metrics _ and _ Statistics _ to _ Drive _ Successful _ Proteomics _ Biomarker _ Discovery
[4]卡方检验,淡水中的生命。请访问:https://www . lifeinfreshwater . org . uk/Stats % 20 for % 20 twits/Chi-squared . html
分类的要素选择技术及其应用的 Python 技巧
关于如何使用最常见的特征选择技术解决分类问题的教程

选择使用哪些功能是任何机器学习项目中的关键一步,也是数据科学家日常工作中的一项经常性任务。在本文中,我回顾了分类问题中最常见的特征选择技术,将它们分为 6 大类。我提供了如何在机器学习项目中使用它们的技巧,并尽可能用 Python 代码给出例子。你准备好了吗?
TL;灾难恢复-汇总表
下表总结了主要方法,并在以下部分进行了讨论。

什么是特征选择,为什么有用?
机器学习中最大的两个问题是过拟合(拟合在数据集之外不可概括的数据方面)和维数灾难(高维数据的非直观和稀疏特性)。
通过减少模型中的特征数量,尝试优化模型性能,特征选择有助于避免这两个问题。这样做,特性选择还提供了一个额外的好处:模型解释。随着特征的减少,输出模型变得更简单和更容易解释,并且人类更有可能相信模型做出的未来预测。
无监督方法
减少特征数量的一个简单方法是对数据应用降维技术。这通常以无人监督的方式完成,即不使用标签本身。
降维实际上并不选择特征的子集,而是在低维空间中产生一组新的特征。这个新的集合可以用于分类过程本身。
以下示例使用降维后的特征进行分类。更准确地说,它使用主成分分析(PCA)的前 2 个成分作为新的特征集。
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.svm import SVC
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormapimport numpy as np
h = .01
x_min, x_max = -4,4
y_min, y_max = -1.5,1.5# loading dataset
data = load_iris()
X, y = data.data, data.target# selecting first 2 components of PCA
X_pca = PCA().fit_transform(X)
X_selected = X_pca[:,:2]# training classifier and evaluating on the whole plane
clf = SVC(kernel='linear')
clf.fit(X_selected,y)
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)# Plotting
cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#AAAAFF'])
cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#0000FF'])
plt.figure(figsize=(10,5))
plt.pcolormesh(xx, yy, Z, alpha=.6,cmap=cmap_light)
plt.title('PCA - Iris dataset')
plt.xlabel('Dimension 1')
plt.ylabel('Dimension 2')
plt.scatter(X_pca[:,0],X_pca[:,1],c=data.target,cmap=cmap_bold)
plt.show()

在评估特征的上下文中,降维的另一个用途是用于可视化:在较低维度的空间中,更容易从视觉上验证数据是否是潜在可分的,这有助于设置对分类准确性的期望。在实践中,我们对特征的子集执行维度缩减(例如 PCA ),并检查标签如何分布在缩减的空间中。如果它们看起来是分开的,这是一个明显的迹象,表明使用这组特征时,预期会有高的分类性能。
在下面的例子中,在一个 2 维的缩减空间中,不同的标签被显示为是相当可分的。这表明,在训练和测试分类器时,人们可以期待高性能。
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
import matplotlib.pyplot as pltfrom mlxtend.plotting import plot_pca_correlation_graphdata = load_iris()
X, y = data.data, data.targetplt.figure(figsize=(10,5))
X_pca = PCA().fit_transform(X)
plt.title('PCA - Iris dataset')
plt.xlabel('Dimension 1')
plt.ylabel('Dimension 2')
plt.scatter(X_pca[:,0],X_pca[:,1],c=data.target)
_ = plot_pca_correlation_graph(X,data.feature_names)
****
除此之外,我还绘制了相关圆,它显示了每个原始维度和新 PCA 维度之间的相关性。直观地说,该图显示了每个原始特征对新创建的 PCA 成分的贡献。在上面的例子中,花瓣长度和宽度与第一个主成分分析维度高度相关,萼片宽度对第二个维度贡献很大。
单变量滤波方法

过滤方法旨在对特征的重要性进行排序,而不使用任何类型的分类算法。
单变量过滤方法单独评估每个特征,并且不考虑特征的相互作用。这些方法包括为每个特征提供一个分数,通常基于统计测试。
分数通常或者测量因变量和特征之间的相关性(例如 Chi2 和用于回归的 Pearls 相关系数),或者测量给定类别标签的特征分布之间的差异(f 检验和 T 检验)。
分数通常对基础数据的统计属性做出假设。理解这些假设对于决定使用哪种测试是很重要的,即使其中一些假设对于违反假设是稳健的。
基于统计测试的分数提供了一个 p 值,可以用来排除一些特征。如果 p 值高于某个阈值(通常为 0.01 或 0.05),则会出现这种情况。
常见测试包括:

包sklearn实现了一些过滤方法。然而,由于大多数都是基于统计测试的,所以也可以使用统计包(比如statsmodels)。
下面是一个例子:
from sklearn.feature_selection import f_classif, chi2, mutual_info_classif
from statsmodels.stats.multicomp import pairwise_tukeyhsdfrom sklearn.datasets import load_irisdata = load_iris()
X,y = data.data, data.targetchi2_score, chi_2_p_value = chi2(X,y)
f_score, f_p_value = f_classif(X,y)
mut_info_score = mutual_info_classif(X,y)pairwise_tukeyhsd = [list(pairwise_tukeyhsd(X[:,i],y).reject) for i in range(4)]print('chi2 score ', chi2_score)
print('chi2 p-value ', chi_2_p_value)
print('F - score score ', f_score)
print('F - score p-value ', f_p_value)
print('mutual info ', mut_info_score)
print('pairwise_tukeyhsd',pairwise_tukeyhsd)Out:chi2 score [ 10.82 3.71 116.31 67.05]
chi2 p-value [0\. 0.16 0\. 0\. ]
F - score score [ 119.26 49.16 1180.16 960.01]
F - score p-value [0\. 0\. 0\. 0.]
mutual info [0.51 0.27 0.98 0.98]
pairwise_tukeyhsd [[True, True, True], [True, True, True], [True, True, True], [True, True, True]]
对特征进行分级的可视化方法
箱线图和小提琴图
箱线图/小提琴图可能有助于可视化给定类别的特征分布。对于 Iris 数据集,下面显示了一个示例。
这是有用的,因为统计测试通常只评估这种分布的平均值之间的差异。因此,这些图提供了关于特征质量的更多信息
import pandas as pd
import seaborn as sns
sns.set()
df = pd.DataFrame(data.data,columns=data.feature_names)
df['target'] = data.targetdf_temp = pd.melt(df,id_vars='target',value_vars=list(df.columns)[:-1],
var_name="Feature", value_name="Value")
g = sns.FacetGrid(data = df_temp, col="Feature", col_wrap=4, size=4.5,sharey = False)
g.map(sns.boxplot,"target", "Value");
g = sns.FacetGrid(data = df_temp, col="Feature", col_wrap=4, size=4.5,sharey = False)
g.map(sns.violinplot,"target", "Value");
****
用 ROC 曲线进行特征排序
ROC 曲线可用于按重要性顺序排列特征,这给出了排列特征性能的直观方式。
这种技术最适合二进制分类任务。为了应用于多类问题,可以使用微观或宏观平均值或基于多重比较的标准(类似于成对 Tukey 的范围测试)。
以下示例绘制了各种特征的 ROC 曲线。
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.metrics import auc
import numpy as np# loading dataset
data = load_iris()
X, y = data.data, data.targety_ = y == 2plt.figure(figsize=(13,7))
for col in range(X.shape[1]):
tpr,fpr = [],[]
for threshold in np.linspace(min(X[:,col]),max(X[:,col]),100):
detP = X[:,col] < threshold
tpr.append(sum(detP & y_)/sum(y_))# TP/P, aka recall
fpr.append(sum(detP & (~y_))/sum((~y_)))# FP/N
if auc(fpr,tpr) < .5:
aux = tpr
tpr = fpr
fpr = aux
plt.plot(fpr,tpr,label=data.feature_names[col] + ', auc = '\
+ str(np.round(auc(fpr,tpr),decimals=3)))plt.title('ROC curve - Iris features')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.legend()
plt.show()

多元滤波方法
这些方法考虑了变量之间的相关性,而没有考虑任何类型的分类算法。
mRMR
****mRMR(最小冗余最大相关性)是一种启发式算法,通过考虑特征的重要性和它们之间的相关性来寻找接近最优的特征子集。
其思想是,即使两个特征高度相关,如果它们高度相关,将它们都添加到特征集中可能不是一个好主意。在这种情况下,添加两个特征会增加模型的复杂性(增加过度拟合的可能性),但由于特征之间的相关性,不会添加重要的信息。
在一组 N 特征的 S 中,特征的相关性( D )计算如下:

其中 I 为互信息算子。
特征的冗余表示如下:

集合 S 的 mRMR 分数定义为( D - R) 。目标是找到具有最大值( D-R) 的特征子集。然而,在实践中,我们执行增量搜索(也称为前向选择),在每一步,我们添加产生最大 mRMR 的特征。
该算法由算法作者自己用 C 实现。你可以在这里找到这个包的源代码,以及原始论文。
在名称pymrmr上创建了一个(未维护的)python 包装器。如果pymrmr有问题,我建议直接调用 C 级函数。
下面的代码举例说明了pymrmr的用法。注意,pandas数据帧的列应按照 C 级包中的描述进行格式化(此处为)。
import pandas as pd
import pymrmrdf = pd.read_csv('some_df.csv')
# Pass a dataframe with a predetermined configuration.
# Check http://home.penglab.com/proj/mRMR/ for the dataset requirements
pymrmr.mRMR(df, 'MIQ', 10)
输出:
*** This program and the respective minimum Redundancy Maximum Relevance (mRMR)
algorithm were developed by Hanchuan Peng <hanchuan.peng@gmail.com>for
the paper
"Feature selection based on mutual information: criteria of
max-dependency, max-relevance, and min-redundancy,"
Hanchuan Peng, Fuhui Long, and Chris Ding,
IEEE Transactions on Pattern Analysis and Machine Intelligence,
Vol. 27, No. 8, pp.1226-1238, 2005.*** MaxRel features ***
Order Fea Name Score
1 765 v765 0.375
2 1423 v1423 0.337
3 513 v513 0.321
4 249 v249 0.309
5 267 v267 0.304
6 245 v245 0.304
7 1582 v1582 0.280
8 897 v897 0.269
9 1771 v1771 0.269
10 1772 v1772 0.269*** mRMR features ***
Order Fea Name Score
1 765 v765 0.375
2 1123 v1123 24.913
3 1772 v1772 3.984
4 286 v286 2.280
5 467 v467 1.979
6 377 v377 1.768
7 513 v513 1.803
8 1325 v1325 1.634
9 1972 v1972 1.741
10 1412 v1412 1.689
Out[1]:
['v765',
'v1123',
'v1772',
'v286',
'v467',
'v377',
'v513',
'v1325',
'v1972',
'v1412']
包装方法

包装器方法背后的主要思想是搜索哪组特性最适合特定的分类器。这些方法可以总结如下,并且在所使用的搜索算法方面有所不同。
- 选择一个性能指标(可能性、AIC、BIC、F1 分数、准确度、MSE、MAE…),记为 M.
- 选择一个分类器/回归器/ …,在这里记为 C 。
- ****用给定的搜索方法搜索不同的特征子集。对于每个子集 S,执行以下操作:
- 使用 S 作为分类器的特征,以交叉验证的方式训练和测试C;
- 从交叉验证程序中获得平均分数(对于指标 M ),并将该分数分配给子集S;
- 选择一个新的子集并重做步骤一个。
详述步骤 3
第三步未指定将使用哪种类型的搜索方法。几乎在任何情况下,测试所有可能的特征子集都是禁止的(强力选择),因为这将需要执行步骤 3 指数次(特征数量的 2 次方)。除了时间复杂性之外,由于有如此大量的可能性,很可能某个特征组合仅仅是随机地表现得最好,这使得强力解决方案更容易过度拟合。
搜索算法在实践中往往能很好地解决这个问题。它们倾向于实现接近蛮力解决方案的性能,具有更少的时间复杂度和更少的过拟合机会。
****正向选择和反向选择(又名修剪)在实践中被大量使用,以及它们的搜索过程的一些小变化。
反向选择包括从具有全部特征的模型开始,并且在每一步中,移除没有特征的模型具有最高分数。正向选择以相反的方式进行:它从一组空的特征开始,并添加最能提高当前分数的特征。
向前/向后选择仍然倾向于过度拟合,因为通常,分数倾向于通过添加更多特征来提高。避免这种情况的一种方法是使用惩罚模型复杂性的分数,如 AIC 或 BIC。
包装方法结构的图示如下。值得注意的是,特征集是(1)通过搜索方法找到的,以及(2)在打算使用的同一分类器上交叉验证的。****

第三步还开放了交叉验证参数。通常,使用 k-fold 程序。然而,使用大 k 会给整个包装器方法带来额外的复杂性。
包装方法的 Python 包
(http://rasbt.github.io/mlxtend/)是一个用于各种数据科学相关任务的有用包。这个包的包装方法可以在 SequentialFeatureSelector 上找到。它提供向前和向后的功能选择,有些变化。
该包还提供了一种通过函数 plot _ sequential _ feature _ selection 将分数可视化为要素数量的函数的方法。
下面的例子摘自包的主页。
from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from mlxtend.plotting import plot_sequential_feature_selection as plot_sfsfrom sklearn.linear_model import LinearRegression
from sklearn.datasets import load_bostonboston = load_boston()
X, y = boston.data, boston.targetlr = LinearRegression()sfs = SFS(lr,
k_features=13,
forward=True,
floating=False,
scoring='neg_mean_squared_error',
cv=10)sfs = sfs.fit(X, y)
fig = plot_sfs(sfs.get_metric_dict(), kind='std_err')plt.title('Sequential Forward Selection (w. StdErr)')
plt.grid()
plt.show()

嵌入式方法

训练一个分类器归结为一个优化问题,其中我们试图最小化其参数的函数(这里记为𝜃).这个函数被称为损失函数(记为𝐿(𝜃)).
在一个更一般的框架中,我们通常希望最小化一个目标** 函数,它考虑了损失函数和对模型复杂性的惩罚(或正则化)(ω(𝜃)😗*
obj(𝜃)=𝐿(𝜃)+ω(𝜃)
线性分类器的嵌入式方法
对于线性分类器(例如线性 SVM、逻辑回归),损失函数表示为:

其中每个 xʲ 对应一个数据样本,而 Wᵀxʲ 表示系数向量 (w₁,w₂,…w_n) 与每个样本中的特征的内积。
对于线性 SVM 和逻辑回归,铰链和逻辑损失分别为:

线性分类器的两个最常见的惩罚是 L-1 和 L-2 惩罚:

λ 的值越高,惩罚越强,最优目标函数将趋向于以收缩越来越多的系数 w_i 而结束。
众所周知,“L1”惩罚会创建稀疏模型,这简单地意味着,在优化过程中,通过使一些系数等于零,它倾向于从模型中选择一些特征。****
另一个常见的处罚是 L-2。虽然 L-2 缩小了系数,因此有助于避免过拟合,但它不会创建稀疏模型,因此它不适合作为特征选择技术。
对于一些线性分类器(线性 SVM,逻辑回归),可以有效地使用 L-1 罚分,这意味着有有效的数值方法来优化最终的目标函数。对于其他几个分类器(各种核 SVM 方法、决策树等等),情况就不一样了。因此,不同的分类器应该使用不同的正则化方法。
带有正则化的逻辑回归示例如下所示,我们可以看到,随着 C 的减少,算法排除了一些特征(想想如果 C 为 1/λ )。
import numpy as np
import matplotlib.pyplot as pltfrom sklearn.svm import LinearSVC
from sklearn.model_selection import ShuffleSplit
from sklearn.model_selection import GridSearchCV
from sklearn.utils import check_random_state
from sklearn import datasets
from sklearn.linear_model import LogisticRegressionrnd = check_random_state(1)# set up dataset
n_samples = 3000
n_features = 15# l1 data (only 5 informative features)
X, y = datasets.make_classification(n_samples=n_samples,
n_features=n_features, n_informative=5,
random_state=1)cs = np.logspace(-2.3, 0, 50)coefs = []
for c in cs:
clf = LogisticRegression(solver='liblinear',C=c,penalty='l1')
# clf = LinearSVC(C=c,penalty='l1', loss='squared_hinge', dual=False, tol=1e-3)
clf.fit(X,y)
coefs.append(list(clf.coef_[0]))
coefs = np.array(coefs)
plt.figure(figsize=(10,5))
for i,col in enumerate(range(n_features)):
plt.plot(cs,coefs[:,col])
plt.xscale('log')
plt.title('L1 penalty - Logistic regression')
plt.xlabel('C')
plt.ylabel('Coefficient value')
plt.show()

基于树的模型的特征重要性
另一种常见的特征选择技术包括从基于树的模型中提取特征重要性等级。
特征重要性本质上是由每个变量产生的分裂标准中的单个树的改进的平均值。换句话说,它是在使用特定变量分割树时分数(决策树符号上所谓的“杂质”)提高了多少。
它们可用于对要素进行分级,然后选择要素的子集。然而,应小心使用特性重要性,因为它们会受到偏差和的影响,并呈现出与高度相关特性相关的意外行为,不管它们有多强。
如本文中的所示,随机森林特征重要性偏向于具有更多类别的特征。此外,如果两个特征高度相关,无论特征的质量如何,它们的分数都会大大降低。
以下是如何从随机森林中提取要素重要性的示例。虽然是回归变量,但对于分类器来说,过程是相同的。
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressorimport numpy as npboston = load_boston()
X = boston.data
Y = boston.target
feat_names = boston.feature_names
rf = RandomForestRegressor()
rf.fit(X, Y)
print("Features sorted by their score:")
print(sorted(zip(map(lambda x: round(x, 4), rf.feature_importances_), feat_names),
reverse=True))
Out:
Features sorted by their score:
[(0.4334, 'LSTAT'), (0.3709, 'RM'), (0.0805, 'DIS'), (0.0314, 'CRIM'), (0.0225, 'NOX'), (0.0154, 'TAX'), (0.0133, 'PTRATIO'), (0.0115, 'AGE'), (0.011, 'B'), (0.0043, 'INDUS'), (0.0032, 'RAD'), (0.0016, 'CHAS'), (0.0009, 'ZN')]
额外:树模型的主要杂质分数
如上所述,“杂质”是决策树算法在决定分割节点时使用的分数。有许多决策树算法(IDR3、C4.5、CART 等),但一般规则是,我们用来分割树中节点的变量是对杂质产生最高改善的变量。
最常见的杂质是基尼杂质和熵。基尼系数杂质的改进被称为“基尼系数重要性,而熵的改进是信息增益。****

SHAP:来自树模型的可靠特征重要性
(感谢恩里克·加斯帕里尼·菲乌萨·多纳西门托的建议!)
SHAP 实际上远不止于此。它是一种算法,提供任何预测模型之外的模型解释。然而,对于基于树的模型,它特别有用:作者为这种模型开发了高速和精确(不仅仅是局部)的解释,与 X GBoost 、 LightGBM 、 CatBoost 和 scikit-learn 树模型兼容。
我鼓励检查一下 SHAP 提供的解释能力(比如特征依赖、交互效果、模型监控……)。下面,我(仅)绘制了 SHAP 输出的特征重要性,当对它们进行排序以进行特征选择时,这些特征重要性比原始树模型输出的特征重要性更可靠。这个例子摘自他们的 github 页面。
import xgboost
import shap# load JS visualization code to notebook
shap.initjs()# train XGBoost model
X,y = shap.datasets.boston()
model = xgboost.train({"learning_rate": 0.01}, xgboost.DMatrix(X, label=y), 100)# explain the model's predictions using SHAP values
# (same syntax works for LightGBM, CatBoost, and scikit-learn models)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X)shap.summary_plot(shap_values, X, plot_type="bar")

结论——何时使用每种方法?
嵌入式方法对于避免过度拟合和选择有用的变量通常非常有效。它们也是时间有效的,因为它们嵌入在目标函数中。它们的主要缺点是它们可能无法用于所需的分类器。
包装方法在实践中往往工作得很好。然而,它们在计算上是昂贵的,特别是当处理数百个特征时。但是如果你有计算资源,这是一个很好的方法。
如果特征集非常大(大约数百或数千),因为过滤方法很快,它们可以很好地作为选择的第一阶段,以排除一些变量。随后,可以将另一种方法应用于已经缩减的特征集。例如,如果您想要创建要素的组合,将它们相乘或相除,这将非常有用。
参考
回归模型中的特征选择技术
特征选择是一种减少特征数量的方法,从而降低模型的计算复杂度。很多时候,特征选择对于克服过度拟合问题变得非常有用。它帮助我们确定高精度预测响应变量所需的最小特征集。如果我们问模型,添加新特性是否一定会显著提高模型性能?如果不是,那么为什么要添加那些只会增加模型复杂性的新特性。
现在,让我们了解如何从给定数据集中的所有可用特征中选择重要的特征集。
用例子来理解总是比较好的。因此,让我们看看下面 R 中的“mtcars”数据集:

我们将删除 x 列,因为它只包含汽车型号,不会给预测增加太多价值。

在上述数据中,有 12 个特征(x、mpg、cyl、disp、hp、drat、wt、qsec、vs、am、gear、carb ),我们希望预测 mpg(英里/加仑),因此它成为我们的目标/响应变量。
让我们随机选择任何预测变量,并尝试拟合预测 mpg 的模型。让我们从“wt”开始:

三颗星(或星号)代表高度显著的 p 值。因此,截距和斜率的小 p 值表明我们可以拒绝零假设,这使得我们可以得出 mpg 和体重之间有很大关系的结论。通常,p 值为 5% (.05)或更低是一个很好的分界点。在我们的模型示例中,p 值非常接近于零。此外,R 平方值 0.74 告诉我们,模型可以解释目标变量中约 74%的方差,因此模型也很重要。
现在,让我们用两个变量 wt 和 hp(马力)来拟合模型,如下所示:(注意,我们可以使用任意两个随机选取的预测值,因为我们只是试图了解如果使用试凑法会发生什么)

现在,R 平方值从 0.74 增加到 0.81。这意味着这个模型变得更加重要。此外,查看 wt 和 hp 的星级数,我们可以说两者都与目标变量密切相关,因此两者都很重要。
可能会有这样的情况,通过添加新变量,已经添加的变量的影响会降低,在这种情况下,如果任何旧变量的 p 值超过 0.05 的上限,则意味着该变量现在变得不重要,然后我们删除该变量。
现在再添加一个变量“qsec ”,并分析模型摘要,如下所示:

从逻辑上来说,通过添加一个新的变量,它不应该减少已经添加的变量的影响,但是在这种情况下,正如我们在上面的图像中看到的,变量 hp 和“qsec”都变得不重要(p 值> . 05 也没有星号)。
现在,让我们添加所有变量,看看会发生什么:

从上面的总结中,我们看到没有一个变量是重要的,因为所有的 p 值都大于阈值限制 0.05,而且总结也没有产生任何星号作为重要代码。这有点令人惊讶。如果没有显著的变量,那么如何拟合模型?
因此,如果我们对变量的所有组合使用试凑法,那么将会有总 2^k-1 线性模型,我们必须尝试并查看哪些是重要的特征。这不是很费时间的工作吗,当然可以。那么现在该怎么办呢?接下来是特征选择技术,它帮助我们找到产生显著模型拟合的最小特征集。因此,在回归中,非常常用的特征选择技术如下:
- 逐步回归
- 预选
- 反向消除
1.逐步回归
在逐步回归技术中,我们开始用每个单独的预测因子来拟合模型,并查看哪一个具有最低的 p 值。然后选择变量,然后使用两个变量拟合模型,一个是我们在上一步中已经选择的变量,另一个是所有剩余的变量。我们再次选择具有最低 p 值的一个。还要记住,通过添加新变量,上一步中已经选择的变量的影响应该仍然很大。我们保持这个迭代,直到我们得到一个 p 值小于阈值 0.05 的组合。
让我们用一个例子来理解整个过程:
第一步:
我们用一个预测器和目标来拟合模型。我们一个接一个地尝试了每一个预测值,每一行下面分别代表模型拟合的 t 值、p 值和 R 平方值。正如我们所见,mpg ~ wt fit 具有最低的 p 值(也应小于 0.05),因此将选择 wt 并转到步骤 2。

第二步:
现在,我们将使用两个预测值来拟合模型。我们已经在步骤 1 中选择了一个作为 wt,对于第二个预测值,我们将逐一尝试所有剩余的预测值。并且将再次选择具有最低 p 值的那些。在这个例子中,我们得到了 wt 和 cyl。

第三步:
现在,我们将尝试拟合 3 个预测值,其中两个已经在步骤 2 中选择,第三个将尝试剩余的预测值。但是在这里我们看到没有一个 p 值小于 0.05,因此都不显著。

由于所有 p 值都大于 0.05,因此三个组合特征都不显著。因此我们就此打住。
因此,使用逐步回归,我们得到了对最终模型拟合有重大影响的最小特征集{wt,cyl}。这并不意味着其他特征没有影响,但如果我们得到一个只有两个变量的重要模型,它们的影响可以忽略不计。
因此,在这里,我们已经观察到,与我们必须比较 2 个⁰-1 = 1023 个模型的试凑法相比,我们的搜索空间已经急剧减小。
2.预选
向前选择几乎类似于逐步回归,然而唯一的区别是在向前选择中,我们仅不断添加特征。我们不删除已经添加的特征。在每次迭代中,我们只添加那些增加整体模型拟合度的特性。
3.反向消除
在第一步的反向消除中,我们包括所有预测值,在随后的步骤中,继续移除具有最高 p 值(> 0.05 阈值极限)的预测值。经过几次迭代后,它将产生最终的特征集,这些特征集对于以期望的精度预测结果来说是足够重要的。
我们将以 mtcars 数据集为例,按如下步骤进行:
第一步:
在步骤 1 中,我们用数据集中可用的所有特征构建模型。然后观察一些事情:

第二步

第三步

第四步

第五步

第六步

第七步

第八步

第九步

最终,我们得到了{wt,qsec}作为最小的特征集。现在让我们来看看有趣的事情,这里是否反向消除产生了我们用逐步回归得到的相同的特征集。使用逐步回归,我们得到了{wt,cyl}作为最佳可能的最小特征集。
还有一点我们可以得出结论,那就是我们用所有的特征选择技术得到的特征集并不总是相同的。我们必须根据业务问题和我们的理解,明智地选择不同的技术。
以上就是这三种特征选择技术。还有其他同样重要的技巧需要理解,我将在接下来的文章中写下这些技巧。
本文首次出现在“科技隧道”博客上,网址为https://ashutoshtripathi . com/2019/06/07/feature-selection-techniques-in-regression-model/
感谢您的阅读。
非常欢迎你对这篇文章的想法。请在评论区分享。
相关文章:
R 中基于遗传算法的特征选择
这是一篇关于在 R 中使用遗传算法进行特征选择的文章,在这篇文章中,我们将快速回顾一下:
- 什么是遗传算法?
- ML 中的 GA?
- 解决方案是什么样的?
- 遗传过程及其算子
- 健身功能
- R 中的遗传算法!
- 你自己试试
- 相关概念

Animation source: “Flexible Muscle-Based Locomotion for Bipedal Creatures” — Thomas Geijtenbeek
背后的直觉
想象一个黑盒,它可以帮助我们在无限的可能性中做出决定,并有一个标准,这样我们就可以找到一个可接受的解决方案(在时间和质量上)来解决我们提出的问题。
什么是遗传算法?
遗传算法(GA)是一种数学模型,其灵感来自著名的查尔斯·达尔文的自然选择思想。
在不同的世代中,自然选择只保留最适合的个体。
想象一下 1900 年有 100 只兔子,如果我们看看今天的数量,我们会发现其他兔子比它们的祖先更快更熟练地找到食物。
ML 中的 GA
在机器学习中,遗传算法的用途之一是挑选正确数量的变量,以便创建预测模型。
选择正确的变量子集是一个组合优化问题。
这种技术相对于其他技术的优势在于,它允许从先前的最佳解决方案中产生最佳解决方案。随着时间的推移改进选择的进化算法。
遗传算法的思想是将一代又一代的不同解决方案组合起来,从每一个解决方案中提取出最好的基因。这样它创造了新的更适合的个体。
我们可以找到遗传算法的其他用途,如超调参数、寻找函数的最大值(或最小值)或搜索正确的神经网络结构(神经进化)等
特征选择中的遗传算法
GA 的每一个可能的解,都是被选择的变量(一个单个🐇),则被视为一个整体,它不会针对目标对变量进行单独排序。
这很重要,因为我们已经知道变量在组中起作用。
解决方案是什么样的?
简单地说,假设我们总共有 6 个变量,
一个解决方案可以是选取 3 个变量,比如:var2、var4和var5。
另一种解决方法可以是:var1和var5。
这些解就是群体中所谓的个体或染色体。它们是我们问题的可能解决方案。

信用图像:Vijini mallawatarachchi
根据图像,解决方案 3 可以表示为一个热点向量:c(1,0,1,0,1,1)。每个1表示包含该变量的解决方案。这种情况:var1、var3、var5、var6。
而方案 4 则是:c(1,1,0,1,1,0)。
载体中的每个位置都是一个基因。
遗传过程及其算子

GA 的基本思想是生成一些随机的可能的解决方案(称为population),它们代表不同的变量,然后在迭代过程中组合最佳解决方案。
这种组合遵循基本的遗传算法操作,即:选择,变异和交叉。
- 选择:挑选一代中最适合的个体(即:提供最高 ROC 的解决方案)。
- 交叉:基于两种溶液的基因,创建两个新个体。这些孩子会出现在下一代面前。
- 突变:在个体中随机改变一个基因(即:翻转一个
0到1)
这个想法是每一代,我们会找到更好的个体,就像一只跑得快的兔子。
我推荐 Vijini mallawatarachchi 关于遗传算法如何工作的帖子。
这些基本操作允许算法通过以最大化目标的方式组合它们来改变可能的解决方案。
健身功能
例如,这个目标最大化是为了保持使 ROC 曲线下的面积最大化的解决方案。这在适应度函数中定义。
适应度函数采用一个可能的解决方案(或者染色体,如果你想听起来更复杂的话),并且以某种方式评估选择的有效性。
正常情况下,适应度函数采用独热向量c(1,1,0,0,0,0),创建例如带有var1和var2的随机森林模型,并返回适应度值(ROC)。
此代码中计算的适应度值为:ROC value / number of variables。通过这样做,该算法对具有大量变量的解进行惩罚。类似于赤池信息准则的想法,或者说 AIC。

R 中的遗传算法!🐛
我的意图是为您提供一个清晰的代码,这样您就可以理解背后的内容,同时尝试新的方法,比如修改适应度函数。这是至关重要的一点。
要在您自己的数据集上使用,请确保data_x(数据帧)和data_y(因子)与custom_fitness功能兼容。
主图书馆是由卢卡·斯库卡开发的GA。此处见中的小插曲与例子。
📣重要:以下代码不完整。 克隆存储库 来运行这个例子。
# data_x: input data frame
# data_y: target variable (factor)# GA parameters
param_nBits=ncol(data_x)
col_names=colnames(data_x)# Executing the GA
ga_GA_1 = ga(fitness = function(vars) custom_fitness(vars = vars,
data_x = data_x,
data_y = data_y,
p_sampling = 0.7), # custom fitness function
type = "binary", # optimization data type
crossover=gabin_uCrossover, # cross-over method
elitism = 3, # best N indiv. to pass to next iteration
pmutation = 0.03, # mutation rate prob
popSize = 50, # the number of indivduals/solutions
nBits = param_nBits, # total number of variables
names=col_names, # variable name
run=5, # max iter without improvement (stopping criteria)
maxiter = 50, # total runs or generations
monitor=plot, # plot the result at each iteration
keepBest = TRUE, # keep the best solution at the end
parallel = T, # allow parallel procesing
seed=84211 # for reproducibility purposes
)# Checking the results
summary(ga_GA_1)── Genetic Algorithm ─────────────────── GA settings:
Type = binary
Population size = 50
Number of generations = 50
Elitism = 3
Crossover probability = 0.8
Mutation probability = 0.03 GA results:
Iterations = 17
Fitness function value = 0.2477393
Solution =
radius_mean texture_mean perimeter_mean area_mean smoothness_mean compactness_mean
[1,] 0 1 0 0 0 1
concavity_mean concave points_mean symmetry_mean fractal_dimension_mean ...
[1,] 0 0 0 0
symmetry_worst fractal_dimension_worst
[1,] 0 0# Following line will return the variable names of the final and best solution
best_vars_ga=col_names[ga_GA_1@solution[1,]==1]# Checking the variables of the best solution...
best_vars_ga[1] "texture_mean" "compactness_mean" "area_worst" "concavity_worst"

- 蓝点:人口健康平均值
- 绿点:最佳健身值
注意:不要期望结果那么快😅
现在我们根据最佳选择来计算精度!
get_accuracy_metric(data_tr_sample = data_x, target = data_y, best_vars_ga)[1] 0.9508279
准确率在 95,08%左右,而 ROC 值接近 0,95(ROC =适应值*变量个数,查适应函数)。
分析结果
不喜欢分析没有分界点(得分数据)的准确性,但是和这个 Kaggle 帖子的结果对比还是很有用的。
他基于 5 个变量使用递归特征消除或 RFE 得到了类似的精度结果,而我们的解决方案保持 4 个变量。
你自己试试
尝试一个新的适应度函数,有些解还是提供了大量的变量,你可以试试求变量个数的平方。
另一件要尝试的事情是获取 ROC 值的算法,或者甚至是更改指标的算法。
有些配置可以持续很长时间。建模前平衡等级,并使用p_sampling参数进行游戏。采样技术会对模型产生很大的影响。查看样本大小和模型性能的等级平衡帖子了解更多信息。
改变变异率或者精英主义怎么样?或者尝试其他穿越方式?
增加popSize以同时测试更多可能的解决方案(以时间为代价)。
请随时分享任何见解或想法,以改善选择。
克隆存储库 运行实例。
相关概念
遗传算法和深度学习之间存在并行性,随着时间的推移迭代和改进的概念是相似的。
我添加了p_sampling参数来加快速度。而且通常都能达到目的。类似于深度学习中使用的批量概念。另一个相似之处是在神经网络训练中 GA 参数run和提前停止标准之间。
但是最大的相似之处是这两种技术都来自于对自然的观察。在这两种情况下,人类观察了神经网络和遗传学如何工作,并创建了一个模拟其行为的简化数学模型。大自然有几百万年的进化,为什么不试着模仿一下呢?🌱
—
我尽量简短地介绍 GA,但是如果你对这个庞大的话题有任何具体的问题,请在评论中留下🙋 🙋♂
如果我没有激励你去学习遗传算法,看看这个基于神经进化的项目:
—
感谢阅读🚀
在 Twitter 和 Linkedin 上找到我。
更多博文。
想了解更多?📗数据科学活书
使用 Python 解决分类问题的特征选择

介绍
在模型中包含更多要素会使模型更加复杂,并且模型可能会过度拟合数据。一些特征可能是噪声,并可能损坏模型。通过移除那些不重要的特征,该模型可以更好地概括。
Sklearn 网站列出了不同的功能选择方法。这篇文章主要是基于那个网站的主题。然而,我收集了关于这些方法背后的理论的不同资源,并将它们添加到本文中。此外,我在同一个数据集上应用了不同的特征选择方法来比较它们的性能。
阅读完这篇文章和我在文章中提供的参考资料后,您应该能够理解特性选择背后的理论以及如何使用 python 来完成它。
获取数据
我们使用虹膜数据集。有关此数据的更多信息:
https://sci kit-learn . org/stable/auto _ examples/datasets/plot _ iris _ dataset . html
iris = load_iris()
X = iris.data
y = iris.target
print(X[: 5, :])
print(X.shape)**output:** [[5.1 3.5 1.4 0.2]
[4.9 3\. 1.4 0.2]
[4.7 3.2 1.3 0.2]
[4.6 3.1 1.5 0.2]
[5\. 3.6 1.4 0.2]](150, 4)
这些数据有四个特征。为了测试不同特征选择方法的有效性,我们向数据集添加了一些噪声特征。
np.random.seed(100)
E = np.random.uniform(0, 1, size=(len(X), 10))
X = np.hstack((X, E))
print(X.shape)**output:** (150, 14)
数据现在有 14 个特征。
在应用特征选择方法之前,我们需要首先分割数据。原因是我们只基于来自训练集的信息选择特征,而不是基于整个数据集。我们应该拿出整个数据集的一部分作为测试集来评估特征选择和模型的性能。因此,在我们进行特征选择和训练模型时,无法看到来自测试集的信息。
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=100, test_size=0.3)
print(X_train.shape)**output:** (105, 14)
我们将应用基于 X_train 和 y_train 的特征选择。
I .移除具有低方差的特征
变量阈值是一种简单的特征选择基线方法。它会移除方差未达到某个阈值的所有要素。默认情况下,它会移除所有零方差特征。
https://scikit-learn . org/stable/modules/generated/sk learn . feature _ selection。variance threshold . html # sk learn . feature _ selection。变量阈值
显然,我们的数据没有任何零方差特征。但是为了演示的目的,我仍然在这里应用这个方法。
sel_variance_threshold = VarianceThreshold()
X_train_remove_variance = sel_variance_threshold.fit_transform(X_train)
print(X_train_remove_variance.shape)**output:** (105, 14)
数据仍然有 14 个特征,没有一个特征被删除。
二。单变量特征选择
单变量特征选择通过基于单变量统计测试选择最佳特征来工作。我们将每个特征与目标变量进行比较,以查看它们之间是否有任何统计上的显著关系。它也被称为方差分析(ANOVA)。当我们分析一个特征和目标变量之间的关系时,我们忽略了其他特征。这就是它被称为“单变量”的原因。每个特性都有它的测试分数。
最后,比较所有的测试分数,将选择分数最高的特征。
(1)使用卡方检验。
有关卡方检验的更多信息,请阅读:
- 统计:http://vassarstats.net/textbook/,第八章
- sk learn:https://sci kit-learn . org/stable/modules/generated/sk learn . feature _ selection . chi 2 . html # sk learn . feature _ selection . chi 2
如果你想了解更多关于 Sklearn 如何应用卡方检验的信息,请阅读源代码:https://github . com/scikit-learn/scikit-learn/blob/1495 f 6924/sk learn/feature _ selection/univariate _ selection . py # L172
sel_chi2 = SelectKBest(chi2, k=4) # select 4 features
X_train_chi2 = sel_chi2.fit_transform(X_train, y_train)
print(sel_chi2.get_support())**output:**[ True True True True False False False False False False False False False False]
数组中的前四个元素为真,这意味着前四个特征是通过这种方法选择的。由于这些特征是数据中的原始特征,卡方检验表现良好。
(2)使用 f 检验
- 统计数字
单向方差分析:
http://vassarstats.net/textbook/,第十四章
回归方差分析:
http://fac web . cs . de Paul . edu/shost/CSC 423/documents/f-test-reg . htm
- Sklearn
分类测试:
回归检验:
sel_f = SelectKBest(f_classif, k=4)
X_train_f = sel_f.fit_transform(X_train, y_train)
print(sel_f.get_support())**output:** [ True True True True False False False False False False False False False False]
f 检验也能正确选择原始特征。
(3)使用 mutual_info_classif 测试
- 用于分类:https://sci kit-learn . org/stable/modules/generated/sk learn . feature _ selection . mutual _ info _ class if . html # sk learn . feature _ selection . mutual _ info _ class if
- 对于回归:https://sci kit-learn . org/stable/modules/generated/sk learn . feature _ selection . mutual _ info _ regression . html # sk learn . feature _ selection . mutual _ info _ regression
sel_mutual = SelectKBest(mutual_info_classif, k=4)
X_train_mutual = sel_mutual.fit_transform(X_train, y_train)
print(sel_mutual.get_support())**output:** [ True True True True False False False False False False False False False False]
总之,三种单变量特征选择方法产生相同的结果。
我们使用虹膜数据作为分类问题。对于回归问题,同样,我们可以使用 f_regression,mutual_info_regression 来进行特征选择。
三。递归特征消除
给定将权重分配给特征(例如,线性模型的系数)的外部估计器,递归特征消除(RFE)是通过递归地考虑越来越小的特征集来选择特征。首先,在初始特征集上训练估计器,并且通过 coef_ 属性或通过 feature_importances_ 属性获得每个特征的重要性。然后,从当前特征集中删除最不重要的特征。该过程在删减集上递归重复,直到最终达到要选择的特征的期望数量。
关于 Sklearn 中的 RFE:https://sci kit-learn . org/stable/modules/generated/sk learn . feature _ selection。rfe . html # sk learn . feature _ selection。RFE
(1)使用逻辑回归作为模型
model_logistic = LogisticRegression(solver='lbfgs', multi_class='multinomial', max_iter=1000)
sel_rfe_logistic = RFE(estimator=model_logistic, n_features_to_select=4, step=1)
X_train_rfe_logistic = sel_rfe_logistic.fit_transform(X_train, y_train)print(sel_rfe_logistic.get_support())**output:** [False True True True False False False False False False False False False True]print(sel_rfe_logistic.ranking_)**output:** array([ 3, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 11, 10, 1])
所选择的特征是等级 1。结果表明,递归特征消除仅选择了部分原始特征和一个噪声特征。这不是我们想要的理想结果。让我们试试另一个模型:
(2)使用随机森林作为模型
model_tree = RandomForestClassifier(random_state=100, n_estimators=50)
sel_rfe_tree = RFE(estimator=model_tree, n_features_to_select=4, step=1)
X_train_rfe_tree = sel_rfe_tree.fit_transform(X_train, y_train)
print(sel_rfe_tree.get_support())**output:** [ True True True True False False False False False False False False False False]
在递归特征选择中使用随机森林作为模型可以在这种情况下选择正确的特征。
四。使用 SelectFromModel 进行特征选择
SelectFromModel 是一个元转换器,可以与拟合后具有 coef_ 或 feature_importances_ 属性的任何估计器一起使用。如果相应的 coef_ 或 feature_importances_ 值低于提供的阈值参数,则这些特征被视为不重要并被移除。
与单变量特征选择相比,基于模型的特征选择一次考虑所有特征,因此可以捕捉交互。用于特征选择的模型不需要与稍后用于训练的模型相同。
(1)基于 L1 的特征选择
具有 L1 惩罚的线性模型可以消除一些特征,因此可以在使用另一个模型来拟合数据之前充当特征选择方法。
model_logistic = LogisticRegression(solver='saga', multi_class='multinomial', max_iter=10000, penalty='l1')
sel_model_logistic = SelectFromModel(estimator=model_logistic)
X_train_sfm_l1 = sel_model_logistic.fit_transform(X_train, y_train)
print(sel_model_logistic.get_support())**output:** [ True False True True False False False False False True False False False False]
它选择了错误的功能!选择噪声中的一个要素时,原始数据集中的第二个要素被忽略。
(2)基于树的特征选择
model_tree = RandomForestClassifier(random_state=100, n_estimators=50)
model_tree.fit(X_train, y_train)
print(model_tree.feature_importances_)sel_model_tree = SelectFromModel(estimator=model_tree, prefit=True, threshold='mean')
# since we already fit the data, we specify prefit option here
# Features whose importance is greater or equal to the threshold are kept while the others are discarded.X_train_sfm_tree = sel_model_tree.transform(X_train)
print(sel_model_tree.get_support())**output:**[0.11844633 0.09434048 0.21340848 0.33708242 0.02019553 0.03081254
0.02317242 0.01962394 0.02407251 0.02193159 0.03289007 0.01836624
0.01811144 0.027546 ][ True True True True False False False False False False False False False False]
它选择正确的原始特征。
综上所述的所有特征选择方法:对于这种特定的数据集,使用 logistic 模型作为递归特征剔除或模型选择会错误地选择特征。另一方面,所有其他特征选择方法都正确地选择了前四个特征。
让我们比较特性选择前后的性能
(1)特征选择前
model_logistic = LogisticRegression(solver='saga', multi_class='multinomial', max_iter=10000)
model_logistic.fit(X_train, y_train)
predict = model_logistic.predict(X_test)
print(confusion_matrix(y_test, predict))
print(classification_report(y_test, predict))**output:** [[16 0 0]
[ 0 11 0]
[ 0 2 16]]
precision recall f1-score support 0 1.00 1.00 1.00 16
1 0.85 1.00 0.92 11
2 1.00 0.89 0.94 18 accuracy 0.96 45
macro avg 0.95 0.96 0.95 45
weighted avg 0.96 0.96 0.96 45
(2)特征选择后
我们使用基于卡方检验的特征选择的结果。
X_train_chi2 是特征选择后馈入模型的数据。
model_logistic = LogisticRegression(solver=’saga’, multi_class=’multinomial’, max_iter=10000)
model_logistic.fit(X_train_chi2, y_train)
我们还需要转换测试数据,因为特性的数量发生了变化。
X_test_chi2 = sel_chi2.transform(X_test)
print(X_test.shape)
print(X_test_chi2.shape)**output:** (45, 14)
(45, 4)
仅使用测试集中与训练集中的剩余特征相对应的特征。这种情况下有四个特征。
predict = model_logistic.predict(X_test_chi2)
print(confusion_matrix(y_test, predict))
print(classification_report(y_test, predict))**output:** [[16 0 0]
[ 0 11 0]
[ 0 1 17]]
precision recall f1-score support 0 1.00 1.00 1.00 16
1 0.92 1.00 0.96 11
2 1.00 0.94 0.97 18 accuracy 0.98 45
macro avg 0.97 0.98 0.98 45
weighted avg 0.98 0.98 0.98 45
总之,特征选择去除了噪声,更好地泛化了模型,从而提高了模型性能。
关于噪音
我们可以尝试添加不同的噪声。例如:
E = np.random.uniform(0, 10, size=(len(X), 20))
如果噪声特征的数量增加到 20,并且噪声的幅度更大,则特征选择可能会选择错误的特征。不过性能还是提升了!
**Before feature selection**[[16 0 0]
[ 0 10 1]
[ 0 4 14]]
precision recall f1-score support0 1.00 1.00 1.00 16
1 0.71 0.91 0.80 11
2 0.93 0.78 0.85 18accuracy 0.89 45
macro avg 0.88 0.90 0.88 45
weighted avg 0.90 0.89 0.89 45**After feature selection:**[[16 0 0]
[ 0 10 1]
[ 0 1 17]]
precision recall f1-score support0 1.00 1.00 1.00 16
1 0.91 0.91 0.91 11
2 0.94 0.94 0.94 18accuracy 0.96 45
macro avg 0.95 0.95 0.95 45
weighted avg 0.96 0.96 0.96 45
摘要
在本文中,我对相同的数据使用了不同的特征选择方法。然后我评价他们的表现。我首先向虹膜数据集添加噪声以形成新的数据集。然后,不同的特征选择方法被应用于这个新的数据集。如果噪声与原始数据相比较小,并且噪声特征的数量相对较少,那么大多数方法可以正确地发现原始特征。
比较使用所有特征(原始特征+噪声特征)来训练模型,如果我们仅使用特征选择之后剩余的特征,则模型表现更好。
注:完整的木星笔记本可以在这里下载:
参考:
[## [1] 1.13.功能选择-sci kit-了解 0.21.3 文档
sklearn.feature_selection 模块中的类可用于…上的特征选择/维度缩减
scikit-learn.org](https://scikit-learn.org/stable/modules/feature_selection.html)
[2]安德烈亚斯·c·米勒,萨拉·圭多(2016)。Python 机器学习导论:数据科学家指南。奥赖利
使用正则化的特征选择
拉索来救援了

Photo by Markus Spiske on Unsplash
介绍
正则化包括向机器学习模型的不同参数添加惩罚,以减少模型的自由度,换句话说,避免过度拟合。在线性模型正则化中,惩罚应用于乘以每个预测值的系数。从不同类型的正则化,套索或 L1 的属性,能够缩小一些系数为零。因此,可以从模型中删除该特征。
在本帖中,我将演示如何使用套索正则化分类问题来选择要素。对于分类,我将使用来自 Kaggle 的 Paribas 索赔数据集。
- 导入重要库
import pandas as pd
import numpy as npimport matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inlinefrom sklearn.model_selection import train_test_splitfrom sklearn.linear_model import Lasso, LogisticRegression
from sklearn.feature_selection import SelectFromModel
from sklearn.preprocessing import StandardScaler
22。加载数据集
data = pd.read_csv(‘paribas.csv’, nrows=50000)
data.shape

data.head()

3。选择数值列和
在实践中,特征选择应该在数据预处理之后进行,所以理想情况下,所有分类变量都被编码成数字,然后我们可以评估它们对目标的确定性,这里为了简单起见,我将只使用数字变量来选择数字列:
numerics = ['int16','int32','int64','float16','float32','float64']
numerical_vars = list(data.select_dtypes(include=numerics).columns)
data = data[numerical_vars]
data.shape

4。将数据分成训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
data.drop(labels=['target', 'ID'], axis=1),
data['target'],
test_size=0.3,
random_state=0)X_train.shape, X_test.shape

5 。 缩放数据,因为线性模型受益于特征缩放
scaler = StandardScaler()
scaler.fit(X_train.fillna(0))

6。使用 ***SelectFromModel***使用套索正则化选择特征
在这里,我将在一行代码中完成模型拟合和特征选择。首先,我指定了逻辑回归模型,并确保选择了套索(L1)惩罚。然后我使用来自sklearn的selectFromModel对象,它将在理论上选择系数非零的特征。
sel_ = SelectFromModel(LogisticRegression(C=1, penalty='l1'))
sel_.fit(scaler.transform(X_train.fillna(0)), y_train)

7。可视化套索正则化保留的特征
sel_.get_support()

在上面的输出中,输出标签是索引方式的。因此True是针对 lasso 认为重要的特征(非零特征),而False是针对权重收缩为零且 Lasso 认为不重要的特征。
8。列出具有所选功能的。
selected_feat = X_train.columns[(sel_.get_support())]print('total features: {}'.format((X_train.shape[1])))
print('selected features: {}'.format(len(selected_feat)))
print('features with coefficients shrank to zero: {}'.format(
np.sum(sel_.estimator_.coef_ == 0)))

系数收缩为零的特征数:
np.sum(sel_.estimator_.coef_ == 0)

9。识别被移除的特征和
removed_feats = X_train.columns[(sel_.estimator_.coef_ == 0).ravel().tolist()]removed_feats

10。从训练测试集中移除特征
X_train_selected = sel_.transform(X_train.fillna(0))
X_test_selected = sel_.transform(X_test.fillna(0))X_train_selected.shape, X_test_selected.shape

注意:
L2 正则化不会将系数缩小到零
# Separating the data into train and test set
X_train, X_test, y_train, y_test = train_test_split(
data.drop(labels=['target', 'ID'], axis=1),
data['target'],
test_size=0.3,
random_state=0)X_train.shape, X_test.shape

为了比较,我将使用岭正则化拟合逻辑回归,并评估系数:
l1_logit = LogisticRegression(C=1, penalty='l2')
l1_logit.fit(scaler.transform(X_train.fillna(0)), y_train)
现在,让我们计算零值系数的数量:
np.sum(l1_logit.coef_ == 0)
所以,现在零值系数的数量是零。因此,现在很清楚,脊正则化(L2 正则化)不会将系数缩小到零。
结论:
正如我们所见,我们用于套索正则化的逻辑回归从数据集中移除了不重要的要素。请记住,增加惩罚c会增加移除的特征数量。因此,我们需要保持警惕,不要将惩罚设置得太高,以至于删除甚至重要的功能,或者设置得太低,以至于不删除不重要的功能。
对于使用随机森林的特征选择:
随机森林是最流行的机器学习算法之一。他们如此成功是因为他们提供了…
towardsdatascience.com](/feature-selection-using-random-forest-26d7b747597f)
Python 中使用包装器方法的要素选择
亲爱的读者:
同一篇文章已被转移到下面的链接。
[## 使用 Python 中的包装器方法选择要素的综合指南
这篇文章作为数据科学博客的一部分发表。简介在当今的大数据和物联网时代,我们…
www.analyticsvidhya.com](https://www.analyticsvidhya.com/blog/2020/10/a-comprehensive-guide-to-feature-selection-using-wrapper-methods-in-python/)
请你点击上面的链接。对造成的不便表示歉意。
快乐阅读!
特征选择为什么和如何解释
了解特征冗余和检测措施的影响
机器学习不仅仅是花哨的模型和复杂的优化算法。如果获胜者采用了更先进的模型或设计了一些出色的功能,包括我在内的许多 Kagglers 都乐于承认失败,但只有少数人在这种情况下忍心接受失败——被一个训练有更少功能的简单模型击败。是的,并不是所有的从业者都认可极简主义在模特训练中的作用。在这第一篇文章中,我将解释冗余特征如何损害模型和一些直观的检测方法。
越多越好,真的吗?
我理解在你的模型中尽可能多的打包功能是多么棒的感觉。我记得我多么喜欢看着我的训练 CSV 文件的大小,想象这个模型会有多好。然而,情况并非总是如此。原因如下:
1.多余的功能减缓了训练过程
这是显而易见的,特征的数量与训练时间正相关。功能越多,计算速度越慢。然而,还有一个隐藏的因素会显著降低训练速度。
在训练集中具有相关的特征使得损失图变得病态(稍后定义)。例如,在一个 2-D 线性回归问题中,如果两个特征都是标准化的,损失的等高线图应该接近圆形(参见我的另一篇关于数据标准化的文章)。
# two features
X1 = np.random.randn(1000,1)
X1 = (X1 -np.mean(X1))/np.std(X1)
X2 = np.random.randn(1000,1)
X2 = (X2 - np.mean(X2))/np.std(X2)
Y = 2.3*X1-0.8*X2 + 0.3*np.random.rand(1000,1)

gradient descent is smooth when features are independent, learning rate = 2e-3
当要素相互关联时,景观会变成椭球形,梯度下降往往会采用之字形路径。
# two features
X1 = np.random.randn(1000,1)
X1 = (X1 -np.mean(X1))/np.std(X1)
X2 = X1 + 0.5* np.random.randn(1000,1)
X2 = (X2 - np.mean(X2))/np.std(X2)
Y = 2.3*X1-0.8*X2 + 0.3*np.random.rand(1000,1)

learning rate = 2e-3
这可能会有问题,因为梯度下降算法可能会振荡,并且需要太长时间才能收敛(您可以通过降低学习速率来减少振荡,但这也会延长训练时间)。请看两个特征高度相关的极端情况:
X1 = np.random.randn(1000,1)
X1 = (X1 -np.mean(X1))/np.std(X1)
X2 = X1 + 0.02*np.random.randn(1000,1)
X2 = (X2 - np.mean(X2))/np.std(X2)
Y = 2.3*X1 - 0.8*X2 + 0.3*np.random.rand(1000,1)


Loss, learning rate = 2e-3(learning rate greater than this may diverge)
正如你所看到的,梯度下降在条件恶劣的损失景观中挣扎,需要更多的迭代(甚至 18,000 次迭代也不够!)实现合理亏损。基于高度相关特征的模型需要更多的迭代,因此需要更长的训练时间。
2.降低的估计能力
我们现在知道,基于高度相关特征的模型需要更多的迭代来训练。因此,如果您在其他项目中使用合理的迭代次数,如 6000 或 10000(在大多数情况下非常足够),算法可能会过早终止,这会损害模型的性能(请参见上面的损失表)。
还有另一个原因:在下面的多元线性回归模型中

X is the training data matrix, Y is the output vector
理论上的最佳估计值是:

如果 X 有冗余(共线性或多重共线性),则 X 的秩未满。因此,我们不能得到最好的估计,因为 X^T*X 的逆不存在。(延伸阅读)
3.你的模型很难解释
机器学习并不总是做预测,在某些应用中,支持基于模型的决策更重要。当涉及到模型分析时,简单的统计方法,如假设检验,就变得又好又方便。例如,人们使用标准误差及其相关的 p 值来判断某个特征是否与结果相关。
当数据集中存在共线性时,这些要素的 t 分值通常较小,因为相关要素的权重具有较大的方差。换句话说,基于不同分区的模型可能具有非常不同的特征参数。例如:
X1 = np.random.randn(1000,1)
X1 = (X1 -np.mean(X1))/np.std(X1)
X2 = X1
Y = 0*X1 + 6*X2 + 0.3*np.random.rand(1000,1)
#essentially 0*X1(X2) + 6*X1(X2) + 0.3*np.random.rand(1000,1)

anywhere on the blue line has the minimum loss

distribution of the estimated coefficient of X1 (w1)
由于蓝线上的任何位置都能成功地使 MSE 最小化,因此只要 w1+w2=6,w1 和 w2 就可能相差很大。如果我们用 X1 不相关的零假设进行显著性检验,很可能我们无法拒绝零假设,因为标准误差很大。例如:
import statsmodels.api as sma
X1 = np.random.randn(1000,1)
X1 = (X1 -np.mean(X1))/np.std(X1)
X2 = X1 + 0.01*np.random.randn(1000,1)
X2 = (X2 - np.mean(X2))/np.std(X2)
X = np.concatenate((X1,X2), axis=1)
Y = 0.5*X1 + X2 + 0.3*np.random.rand(1000,1)**# we know both X1 and X2 contribute to the outcome**
mod = sma.OLS(Y,X)
res= mod.fit()
print(res.summary()

X1 和 X2 的系数都具有较小的 t 值(因此具有较大的 P 值)。因此,我们错误地得出结论,X1 和 X2 都不相关(解释 P 值)。特征冗余会导致较大的系数标准误差,并掩盖特征在回归中的真实作用。
现在我们了解了特征冗余是如何损害建模和数据分析的,识别并移除特征冗余(也称为特征选择)对我们最有利。
如何识别特征冗余?
检测相关特征:条件编号
检测数据中多重共线性的一种流行方法称为特征系统分析,它使用条件数的概念。条件数的定义是:

condition number
给定一个矩阵(输入数据 X),求 X Corr(X)的相关矩阵:

correlation matrix of X
如果 X 的相关矩阵有很大的条件数,说明共线性严重。

下面是一个简短的示例,可以帮助您理解条件编号是如何工作的:

correlation between X1 and X2 determines the condition number

检测不相关的特征
检测无关特征的核心是发现输出是否受到给定特征的影响。基于输出的类型(连续或分类)和特性1,有几种数学方法可以使用。分类特征分类反应—卡方检验

Categorical feature categorical response
卡方检验的本质是假设输入和输出之间没有关系,并检查该假设的有效性。如果该特征确实影响响应,我们期望看到低 P 值。因此,通过选择具有低 p 值的特征来完成特征选择。点击此处查看更多卡方检验信息。
我创建了一些简单的人工数据集,让你更好地理解卡方为什么有用
import pandas as pd
import scipy.stats as sstX = np.random.randint(5,size=1000)
Y = np.ones(X.shape)
index = X<=2
Y[index] =0 # when X <=2, Y = 0\. Therefore X influences Y
crosstab = pd.crosstab(X,Y)
chi2, p,_,_ = sst.chi2_contingency(crosstab)
**###P value = 3.569412779777166e-215-> reject the null hypothesis ->feature is relevant**X = np.random.randint(5,size=1000)
Y = np.random.randint(2, size = 1000) # no relatinoship
crosstab = pd.crosstab(X,Y)
chi2, p,_,_ = sst.chi2_contingency(crosstab)
**###P value = 0.5244308199595783-> large P value, relationship is statistically insignificant**
2。类别特征连续反应—方差分析

Categorical feature continuous response
方差分析(ANOVA)比较不同组的平均值(每个分类特征数据的响应平均值),并检验组间差异是否具有统计学意义。如果一个特征是相关的,我们期望看到不同组的均值之间的显著差异。点击此处查看更多关于方差分析的信息
示例:
X = np.random.randint(3,size=1000)
Y = np.random.rand(1000) # no relationship
one = Y[X==1]
two = Y[X==2]
zero = Y[X==0]
sst.f_oneway(one,two,zero)
**###statistic=0.07592457518151591, pvalue=0.9268914727618249-> large P value-> relationship is insignificant** X = np.random.randint(3,size=1000)
Y = np.random.rand(1000) + 0.1*X # X is part of Y
one = Y[X==1]
two = Y[X==2]
zero = Y[X==0]
sst.f_oneway(one,two,zero)
**### F_onewayResult(statistic=38.076396290550555, pvalue=1.1607768540773696e-16)-> reject the null hypothesis-> feature is relevant**
3。连续特征连续反应—相关或卡方/方差分析

continuous features and continuous response
最常见的相关系数称为皮尔逊系数,用于测试特征和响应之间的线性关系。当关系为非线性时,Pearson 相关返回低值。一个更好的选择叫做 Spearman 相关性,它测试单调关系是否存在。以下是一些例子:

Pearson’s Correlation Vs. Spearman’s Correlation
如您所见,Spearman 的相关性对于我们的应用程序有更好的性能,因为我们想要检测不相关的特征(Pearson 的给出更多的错误警报)。然而,当关系是非单调的(第四行)时,这两个指标的表现都很差。
另一种方法是将两个变量离散化,并使用卡方检验,正如您所看到的,它成功地确定了非单调关系:
X = 10*np.random.rand(1000)
Y = np.sin(X)
X_ca = pd.qcut(X,10, labels=False) # discretize into 10 classes
Y_ca = pd.qcut(Y,10, labels=False)crosstab = pd.crosstab(X_ca,Y_ca)
chi2, p,_,_ = sst.chi2_contingency(crosstab)
print ('P value = ', p)
**### P value = 0.0-> a strong relationship**
4。连续特征分类反应—卡方检验

continuous feature categorical response
对于这个问题,我们可以简单地通过将数据分成不同的“组”来离散化特征,就像我们在第 3 部分中所做的那样。离散化后,我们可以再次应用卡方检验。
摘要
好的特征可以显著提高模型的准确性和稳定性。因此,添加尽可能多的功能,以便找到好功能的可能性更高,这似乎是合理的。然而,使用大量特征在训练速度、准确性和可解释性方面可能是有害的,这就是为什么特征选择受到许多数据从业者的青睐。在下一篇文章中,你会看到一些系统化的算法,这些算法会自动选择相关特征并消除共线特征。
特征选择为什么和如何解释
Python 中特征选择算法的实现
在的上一篇文章中,我解释了在模型构建中包含不相关或相关特征的问题。在本文中,我将向您展示选择算法的几个简洁的实现,它们可以很容易地集成到您的项目管道中。
在深入详细的实现之前,让我们先来看一下我创建的数据集。数据集有 20 个要素,其中 5 个对输出有贡献,2 个是相关的。
1.包装特征选择
检测能力:★★★☆速度:★☆☆☆
包装算法很简单。通过训练和验证模型来检查特征的有效性。这意味着包装器在处理大型数据集时非常慢。

wrappers are iterative/recursive in nature
尽管添加/删除特性的具体标准可能不同,但核心思想是相同的。因此,我将重点介绍一种叫做反向选择的特殊方法。
— 1.1.包装算法:向后选择
提供具有特征系数(例如回归)或重要性因子(例如树)的模型,算法从所有特征开始,贪婪地消除最不重要的特征。一旦删除了所有特征,该算法就返回给出最佳性能的子集。
— 1.2.Python 中的向后选择:
Scikit-Learn 提供了一个很好的实现,称为 RFECV (递归特征消除和交叉验证选择),一种基于验证分数的算法来消除不相关的特征。
估计器 : 对象
使用
*fit*方法的监督学习估计器,通过*coef_*或*feature_importances_*提供关于特征重要性的信息评分:字符串,可调用或无,可选,(默认=无)
Scikit-Learn 中有许多估值器满足标准。在这个例子中,使用了一个简单的 SVM 分类器。评分函数是评估模型性能(如准确性)的度量。

RFECV 选择了 7 个特征,包括所有相关特征和冗余特征。这表明 RFECV 不擅长移除多重共线性(因为共线性要素的系数往往变化很大)。因此,最好先识别多重共线性(例如使用条件号识别)。
2.过滤特征选择
检测能力:★★☆☆☆速度:★★★
在使用数百万个数据点的真实应用程序中,包装器变得不切实际。过滤算法通过统计测试(例如相关系数)而不是训练实际模型来估计特征的有效性,从而解决问题。
虽然有很多先进的过滤方法,如 mRMR,但我更喜欢这种简单的两步方法。

— 2.1.移除相关要素:条件编号
正如第一篇文章中介绍的,共线性或多重共线性是有害的。下面是我自己开发的一个算法,可以消除两者。

— 2.2.移除不相关的特征:单变量选择
单变量选择对特征和输出执行统计测试,并且仅保留具有高分的特征。常见的测试包括卡方检验、方差分析或互信息。
同样,Scikit-Learn 提供了一个很好的函数,称为 GenericUnivariateSelect。该功能需要 3 个参数
score_func : 可调用
函数采用两个数组 X 和 y,并返回一对数组(分数、pvalues)。对于“百分位数”或“kbest”模式,它可以返回单个数组分数。
模式 : { '百分位',' k_best ',' fpr ',' fdr ',' fwe'}
param : float 或 int 取决于特征选择模式
选择您想要的统计方法(如卡方检验、F 值)作为评分函数。我将使用卡方,因为 y 是绝对的。

该算法会丢弃 P 值大于 0.5 的要素。使用不太小的阈值是一个很好的实践,这样我们就不会丢失有用的特性(我们在这里丢失了一个有用的特性)。
3。嵌入式特征选择
检测能力:★★★☆☆速度:★★★☆☆
嵌入式特征选择在训练期间智能地丢弃不必要的特征。常见的算法包括 Lasso 回归、决策树和自动编码神经网络。我将在这里解释套索回归。
— 3.1.嵌入式算法:Lasso 回归

Lasso adds a penalty term to the loss function
套索回归因使用大的要素权重/过多的要素而对模型不利。因此,模型倾向于使用尽可能少的参数,从而产生稀疏的结果(例如,一些特征的权重为 0)。
— 3.2.Python 中的套索回归
像往常一样,你可以在 Scikit-Learn 中找到 lasso 回归。在初始化模型时,您必须提供的唯一参数是 Alpha,它决定了惩罚的强度。更大的 alpha 导致更稀疏的结果,alpha=0 实质上意味着没有套索惩罚。

an example of how alpha affects feature selection
如您所见,选择强度由 Alpha 决定。问题是我们不知道应该用什么α。这个问题可以通过选择在交叉验证中表现最好的值来解决。
该模型最终使用 5 个参数,这意味着所有冗余和相关的特征都被排除在模型之外。
摘要
每种类型的选择算法都有其优点和缺点,并且没有使用什么方法的经验法则。在项目环境中考虑权衡是很重要的。
- 包装器提供了最高的选择质量,因为它实际上在不同的特征子集上训练模型。高质量的代价是培训时间。
- 过滤器的选择基于统计数据,速度非常快。但是,它可能无法检测到最佳的特征子集。
- 中间嵌入选择,以公允的价格提供合理的选择力。
使用 sklearn 和 Pandas 进行功能选择
要素选择方法及其在 Python 中的实现简介

在执行任何机器学习任务时,特征选择都是首要且重要的步骤之一。在数据集的情况下,一个特征仅仅意味着一列。当我们获取任何数据集时,不一定每个列(要素)都会对输出变量产生影响。如果我们在模型中添加这些不相关的特性,只会让模型变得更糟(垃圾进垃圾出)。这就产生了进行特征选择的需要。
当在熊猫中实现特征选择时,数字特征和分类特征要区别对待。这里我们将首先讨论关于数字特征的选择。因此,在实现以下方法之前,我们需要确保数据帧只包含数字特征。此外,下面的方法是讨论回归问题,这意味着输入和输出变量都是连续的性质。
特征选择可以通过多种方式完成,但大致有 3 类:1。过滤方法
2。包装方法
3。嵌入式方法
关于数据集:
我们将使用内置的波士顿数据集,它可以通过 sklearn 加载。我们将使用上面列出的方法为预测“MEDV”列的回归问题选择特征。在下面的代码片段中,我们将导入所有需要的库并加载数据集。
#importing libraries
from sklearn.datasets import load_boston
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
%matplotlib inline
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.feature_selection import RFE
from sklearn.linear_model import RidgeCV, LassoCV, Ridge, Lasso#Loading the dataset
x = load_boston()
df = pd.DataFrame(x.data, columns = x.feature_names)
df["MEDV"] = x.target
X = df.drop("MEDV",1) #Feature Matrix
y = df["MEDV"] #Target Variable
df.head()

1.过滤方法:
顾名思义,在这种方法中,您只需要过滤并提取相关特性的子集。模型是在选择特征之后构建的。这里的过滤是使用相关矩阵完成的,最常用的是使用皮尔逊相关。
在这里,我们将首先绘制皮尔逊相关性热图,并查看自变量与输出变量 MEDV 的相关性。我们将只选择与输出变量的相关性高于 0.5(取绝对值)的特征。
相关系数的值在-1 到 1 之间
—越接近 0 的值意味着相关性越弱(恰好 0 意味着不相关)
—越接近 1 的值意味着正相关性越强
—越接近-1 的值意味着负相关性越强
#Using Pearson Correlation
plt.figure(figsize=(12,10))
cor = df.corr()
sns.heatmap(cor, annot=True, cmap=plt.cm.Reds)
plt.show()

#Correlation with output variable
cor_target = abs(cor["MEDV"])#Selecting highly correlated features
relevant_features = cor_target[cor_target>0.5]
relevant_features

如我们所见,只有特征 RM、PTRATIO 和 LSTAT 与输出变量 MEDV 高度相关。因此,我们将放弃除此之外的所有其他功能。然而,这并不是过程的结束。线性回归的假设之一是自变量需要彼此不相关。如果这些变量是相互关联的,那么我们只需要保留其中一个,去掉其余的。因此,让我们检查所选特征彼此之间的相关性。这可以通过从上面的相关矩阵或下面的代码片段中直观地检查来完成。
print(df[["LSTAT","PTRATIO"]].corr())
print(df[["RM","LSTAT"]].corr())

从上面的代码中可以看出,变量 RM 和 LSTAT 是高度相关的(-0.613808)。因此,我们将只保留一个变量,丢弃另一个。我们将保留 LSTAT,因为它与 MEDV 的相关性高于 RM。
删除 RM 后,我们只剩下两个特性,LSTAT 和 PTRATIO。这些是皮尔逊相关给出的最终特征。
2.包装方法:
包装器方法需要一个机器学习算法,并使用其性能作为评估标准。这意味着,您将特征输入到选定的机器学习算法中,并根据模型性能添加/移除特征。这是一个迭代且计算量大的过程,但比滤波方法更精确。
有不同的包装方法,如向后消除、向前选择、双向消除和 RFE。我们将在这里讨论逆向淘汰和 RFE。
一、逆向淘汰
顾名思义,我们首先将所有可能的特征输入到模型中。我们检查模型的性能,然后迭代地逐一移除性能最差的特征,直到模型的整体性能在可接受的范围内。
这里用来评估特性性能的性能指标是 pvalue 。如果 pvalue 大于 0.05,那么我们移除该特征,否则我们保留它。
我们将首先在这里运行一次迭代,只是为了得到一个概念的想法,然后我们将在一个循环中运行相同的代码,这将给出最终的特性集。这里我们使用的是代表“普通最小二乘法”的 OLS 模型。该模型用于执行线性回归。
#Adding constant column of ones, mandatory for sm.OLS model
X_1 = sm.add_constant(X)#Fitting sm.OLS model
model = sm.OLS(y,X_1).fit()
model.pvalues

正如我们所看到的,变量“年龄”的 p 值最高,为 0.9582293,大于 0.05。因此,我们将删除此功能,并再次构建模型。这是一个迭代过程,可以在 loop 的帮助下立即执行。这种方法在下面实施,它将给出最终的一组变量,即 CRIM、ZN、CHAS、NOX、RM、DIS、RAD、TAX、PTRATIO、B 和 LSTAT
#Backward Elimination
cols = list(X.columns)
pmax = 1
while (len(cols)>0):
p= []
X_1 = X[cols]
X_1 = sm.add_constant(X_1)
model = sm.OLS(y,X_1).fit()
p = pd.Series(model.pvalues.values[1:],index = cols)
pmax = max(p)
feature_with_p_max = p.idxmax()
if(pmax>0.05):
cols.remove(feature_with_p_max)
else:
breakselected_features_BE = cols
print(selected_features_BE)**Output:***['CRIM', 'ZN', 'CHAS', 'NOX', 'RM', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT']*
二。RFE(递归特征消除)
递归特征消除 (RFE)方法的工作原理是递归移除属性,并在那些保留的属性上建立模型。它使用准确性度量来根据特征的重要性对其进行排序。RFE 方法将使用的模型和所需特征的数量作为输入。然后给出所有变量的排名,1 是最重要的。它也给出了支持,真的是相关特征,假的是不相关特征。
model = LinearRegression()#Initializing RFE model
rfe = RFE(model, 7)#Transforming data using RFE
X_rfe = rfe.fit_transform(X,y) #Fitting the data to model
model.fit(X_rfe,y)
print(rfe.support_)
print(rfe.ranking_)**Output:***[False False False True True True False True True False True False
True]
[2 4 3 1 1 1 7 1 1 5 1 6 1]*
这里我们采用了 7 个特征的线性回归模型,RFE 给出了如上的特征排名,但是数字“7”的选择是随机的。现在,我们需要找到最佳数量的特征,其精度是最高的。我们通过使用从 1 个特征开始到 13 个特征的循环来实现。然后我们取精确度最高的那个。
#no of features
nof_list=np.arange(1,13)
high_score=0
#Variable to store the optimum features
nof=0
score_list =[]
for n in range(len(nof_list)):
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size = 0.3, random_state = 0)
model = LinearRegression()
rfe = RFE(model,nof_list[n])
X_train_rfe = rfe.fit_transform(X_train,y_train)
X_test_rfe = rfe.transform(X_test)
model.fit(X_train_rfe,y_train)
score = model.score(X_test_rfe,y_test)
score_list.append(score)
if(score>high_score):
high_score = score
nof = nof_list[n]print("Optimum number of features: %d" %nof)
print("Score with %d features: %f" % (nof, high_score))**Output:***Optimum number of features: 10
Score with 10 features: 0.663581*
从上面的代码可以看出,特性的最佳数量是 10。我们现在向 RFE 输入 10 个特征,并得到由 RFE 方法给出的最终特征集,如下所示:
cols = list(X.columns)
model = LinearRegression()#Initializing RFE model
rfe = RFE(model, 10) #Transforming data using RFE
X_rfe = rfe.fit_transform(X,y) #Fitting the data to model
model.fit(X_rfe,y)
temp = pd.Series(rfe.support_,index = cols)
selected_features_rfe = temp[temp==True].index
print(selected_features_rfe)**Output:***Index(['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'DIS', 'RAD', 'PTRATIO',
'LSTAT'],
dtype='object')*
3.嵌入式方法
嵌入式方法在某种意义上是迭代的,它负责模型训练过程的每次迭代,并仔细提取那些对特定迭代的训练贡献最大的特征。正则化方法是最常用的嵌入式方法,其在给定系数阈值的情况下惩罚特征。
这里我们将使用套索正则化进行特征选择。如果特征是不相关的,套索惩罚它的系数,使其为 0。因此,系数= 0 的特征被移除,其余的被采用。
reg = LassoCV()
reg.fit(X, y)
print("Best alpha using built-in LassoCV: %f" % reg.alpha_)
print("Best score using built-in LassoCV: %f" %reg.score(X,y))
coef = pd.Series(reg.coef_, index = X.columns)

print("Lasso picked " + str(sum(coef != 0)) + " variables and eliminated the other " + str(sum(coef == 0)) + " variables")

imp_coef = coef.sort_values()
import matplotlib
matplotlib.rcParams['figure.figsize'] = (8.0, 10.0)
imp_coef.plot(kind = "barh")
plt.title("Feature importance using Lasso Model")

在这里,套索模型已经采取了除氮氧化物,查斯和印度河的所有功能。
结论:
我们看到了如何使用多种方法为数字数据选择特征,并比较了它们的结果。现在出现了在什么情况下选择哪种方法的困惑。以下几点将帮助你做出这个决定。
- 过滤方法不太准确。在做 EDA 的时候很棒,也可以用来检查数据中的多重共线性。
- 包装器和嵌入式方法给出了更精确的结果,但是由于它们的计算量很大,所以这些方法适用于特性较少的情况(~20)。
在下一篇博客中,我们将会看到更多的用于选择数字和分类特征的特征选择方法。
功能存储:数据科学工厂的组件

几年前,优步发现自己面临着任何拥有复杂机器学习业务的公司或组织都熟悉的困境:昂贵而低效的特征工程方法。特征工程需要将原始数据转换为预测模型可理解的格式,但其数据工程师花费了无数时间来为客户人口统计数据、过去的购买历史和数字渠道互动等流行类别重新创建和重用相同的精选建模属性。
识别问题
随着机器学习的某些方面赢得了混乱过程的名声,各个组织的领导者都认识到了反复出现的瓶颈。
1.数据工程师在模型服务期间没有遵循访问要素的最佳实践。
2.数据科学家通常在没有协作的情况下独立完成项目。
3.用于训练的特征和用于服务的特征之间出现不一致。
4.必须运行整个管线,而不是在新数据可用时重新计算选择的特征。
5.为每个项目重新发明轮子所花费的时间导致成本膨胀和速度减慢。
所有这些因素都拖慢了项目的进度;由于团队无法重建预测模型或产生一致的结果,企业冒着失去客户和利益相关者对结果的信任和支持的风险。
特色商店开始获得关注
2017 年,优步推出了米开朗基罗,这是其机器学习平台的一个新的数据管理方面。在其他属性中,米开朗基罗为内部数据科学家和工程师提供了一个功能存储,允许他们与其他团队和机器学习管道中的未来项目一起摄取、编目和共享功能。
这是一个革命性的非常及时的新概念,其他公司纷纷效仿,要么自己建立特色商店,要么提供服务为他人实施和维护该技术。2019 年初,谷歌宣布了 Feast ,这是一个开源的功能商店,为机器学习团队提供解决方案。
随着要素存储的运行,数据团队面临着新的挑战
虽然功能商店的新概念仍处于起步阶段,但专业人士遇到了新的困境。流动运营需要在线和离线分析环境,公司努力缩小两者之间的差距。要运营一个高功能的数据科学工厂,必须弥合这一差距,并确保跨两个平台的高效通信和操作。
由于两个平台之间的不一致,结果的质量、准确性和可靠性出现了问题。
了解强大的离线功能商店
这些操作托管着包含多年历史的大规模、大规模并行环境,并针对同时分析许多记录进行了调整。离线操作可能包括用于揭示大量历史数据中的洞察力和模式的发现环境。处理这些查询可能需要 10 秒到几个小时的时间。
常见的技术包括 Hadoop、数据湖、云存储(S3、Azure Blob、谷歌大查询)、雪花、红移、Netezza、Vertica 和 Teradata。
利用 nimble 在线功能商店
在线运营的运作方式有所不同,在运行任务关键型应用程序、网站和移动应用程序的低延迟环境中提供卓越的可用性。凭借高速响应时间,在线要素商店可在数毫秒内将模型部署到应用程序中,并依靠它们做出近乎实时的决策。
常见的技术有 RESTful 服务、关系数据库(Oracle、MySQL、SQL Server)、NoSQL 数据库(MongoDB、Couchbase、Cassandra 等)。).
弥合线下和线上功能商店之间差距的技巧
1.在离线和在线要素存储之间创建数据奇偶校验,以确保离线训练的模型在用于实时决策时与在线数据相匹配。
2.使用现代数据目录来管理和控制各种功能集,以确保字段、自动评估的质量、数据类型、数据所有者和 SME 都得到很好的记录。专业提示: Alation 和 Colibra 是最佳的数据目录。
3.使用批处理环境创建无法实时进行的长期聚合和推导。创建完成后,将其发布到在线要素存储中,以进行实时模型服务。
4.将实时变量和上下文数据从在线商店推送到线下。
5.从在线环境中创建反馈循环,并记录模型输入、输出和业务成果或响应,以便为自动化模型性能监控和最终自动化模型保留进行自我定位。
随着两家商店的同步,团队可以达到新的效率水平
数据团队正在从成本中心向利润中心转变。随着领导者越来越多地将数据支持的见解融入战略,数据团队承担了更多责任。由于创造过程的高度复杂性,研究人员将机器学习称为“T2 技术债务的高息信用卡”。随着新程序和技术的出现,减少了纠缠和死胡同的数量,效率成为当今的主流。
有了必要的资源,任何团队都可以运营一个高功能的数据科学工厂。特色商店的引入使该行业朝着正确的方向前进了一步,同时也暴露了更多的低效问题。投入时间和精力同步线上和线下商店可以提高可靠性、准确性和效率,并有助于确保客户和团队成员的持续成功。
如需了解更多有关快速通道如何工作的信息,请点击此处访问我们。
这篇文章最初出现在 Quickpath 的博客上。
Python 中多元线性回归的特征变换
用于建模的数据处理和转换
数据处理和转换是一个迭代过程,在某种程度上,它永远不可能是“完美的”。因为随着我们对数据集的了解越来越多,例如目标变量和特征之间的内在关系,或者业务环境,我们会想到处理它们的新方法。最近,我开始利用多元线性回归研究媒体组合模型和一些预测模型。在这篇文章中,我将介绍建模的思维过程和处理变量的不同方法。
我就用金县房价数据集(为了更好玩的修改版)来举例。
我们先导入库,看看数据!
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as stats% matplotlib inlinedf = pd.read_csv(“kc_house_data.csv”)
df.head()

Image by Author
识别缺失值和明显不正确的数据类型。
df.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 21597 entries, 0 to 21596
Data columns (total 21 columns):
id 21597 non-null int64
date 21597 non-null object
price 21597 non-null float64
bedrooms 21597 non-null int64
bathrooms 21597 non-null float64
sqft_living 21597 non-null int64
sqft_lot 21597 non-null int64
floors 21597 non-null float64
waterfront 19221 non-null float64
view 21534 non-null float64
condition 21597 non-null int64
grade 21597 non-null int64
sqft_above 21597 non-null int64
sqft_basement 21597 non-null object
yr_built 21597 non-null int64
yr_renovated 17755 non-null float64
zipcode 21597 non-null int64
lat 21597 non-null float64
long 21597 non-null float64
sqft_living15 21597 non-null int64
sqft_lot15 21597 non-null int64
dtypes: float64(8), int64(11), object(2)
memory usage: 3.5+ MB
在大多数统计模型中,变量可以分为 4 种数据类型:
- 连续:在一个选定的范围内可以有无限个可能值。“float”通常用于 python 中的连续数据。例如生活区的长度
- 标称:标称变量使用数字表示来解释对象的类型或属性。它们是具有两个或更多可能值的分类值,没有固有的顺序或排列次序。例如,汽车的原产国,美国可以是 1,日本 2,德国 3
- 序数:序数变量实际上是呈现数值。它们通常在有限的范围内有两个或更多的可能值,并且这些值具有有序的类别。比如房子的档次。当你用 target 绘制序数值时,你经常会看到清晰的垂直线。在数据集中,序号数据可能被读取为整数或浮点数,因此数据可视化总是有助于检测它们
- 二进制:只有 2 个可能的值,通常是 0 和 1。虚拟变量是二进制的
下图清楚地显示了这种关系。

Common data types in statistics (Image by Author)
连续变量的变换
- Log: 当您有偏斜的数据时,Log 转换有助于减少偏斜。
以房价为例。
df_log[‘price’] = np.log(df[‘price’])
sns.distplot(df_set['price'], fit=norm)
fig = plt.figure()

before log transformation (Image by Author)

after log transformation (Image by Author)
- 幂:如果我们从本质上知道自变量与目标变量存在指数或递减关系,我们可以使用幂变换。例如,当我们试图建立电视广告支出与销售额的模型时,我们知道在某个时候,电视广告对销售额的影响会降低。这就是所谓的“收益递减”。所以我们通常使用 0.3 到 0.7 的幂来转换电视支出,以便我们可以获得更好的模型拟合。
- 标准化:减去平均值,除以标准差。标准化并没有使数据更正常,它只是改变了平均值和标准误差。

scaled_price = (logprice -np.mean(logprice))/np.sqrt(np.var(logprice))
- 均值归一化:分布的值介于-1 和 1 之间,均值为 0。

- 最小-最大缩放:带来 0 到 1 之间的值

离散变量的变换
- 标签编码:当实际值是文本,并且您想要为变量创建数值时。例如:
origin = [“USA”, “EU”, “EU”, “ASIA”,”USA”, “EU”, “EU”, “ASIA”, “ASIA”, “USA”]
origin_series = pd.Series(origin)
cat_origin = origin_series.astype('category')
cat_origin.cat.codes0 2
1 1
2 1
3 0
4 2
5 1
6 1
7 0
8 0
9 2
dtype: int8#Or use scikit-learn's LabelEncoder:from sklearn.preprocessing import LabelEncoder
lb_make = LabelEncoder()origin_encoded = lb_make.fit_transform(cat_origin)origin_encodedarray([2, 1, 1, 0, 2, 1, 1, 0, 0, 2])
- 宁滨:宁滨在处理序数值时非常得心应手。在模型中,我们可以通过基于值分布创建箱,然后创建虚拟变量,将变量转换为分类变量,而不是将顺序值作为整数读取。
在金县房价例子中,等级是一个与房价正相关的顺序变量。

Image by Author
df[‘grade’].describe()count 21596.000000
mean 7.657946
std 1.173218
min 3.000000
25% 7.000000
50% 7.000000
75% 8.000000
max 13.000000
Name: grade, dtype: float64
我们可以基于百分点值创建 4 个箱。
bins = [3,7,8,10,13]
bins_grade = pd.cut(df[‘grade’],bins)bins_grade.value_counts().plot(kind='bar')

bins_grade (Image by Author)
bins_grade0 (3, 7]
1 (3, 7]
2 (3, 7]
3 (3, 7]
4 (7, 8]
5 (10, 13]
6 (3, 7]
7 (3, 7]
8 (3, 7]
9 (3, 7]
10 (7, 8]
11 (3, 7]
12 (3, 7]
13 (3, 7]
14 (3, 7]
15 (8, 10]
然后我们为它们创建虚拟变量,因为一些建模技术需要数值。
bins_grade = bins_grade.cat.as_unordered()
grade_dummy = pd.get_dummies(bins_grade, prefix=’grade’)
- 创建虚拟变量
另一种创建虚拟变量的方法是使用 sklearn.preprocessing 包中的 LabelBinarizer
from sklearn.preprocessing import LabelBinarizerlb = LabelBinarizer()
grade_dummies = lb.fit_transform(grade)
# you need to convert this back to a dataframe
grade_dum_df = pd.DataFrame(grade_dummies,columns=lb.classes_)
使用哑元的好处是,无论你使用什么算法,你的数值都不会被误解为连续的。接下来,重要的是要知道对于线性回归(以及 scikit-learn 中的大多数其他算法),在回归模型中添加分类变量时需要一键编码!
同样,特征转换涉及多次迭代。有许多方法可以为模型获取正确的数据。保持好奇和耐心就好!
用闪光灯揭开神经网络“看到”了什么
py torch 中用于神经网络的开源特征可视化工具包

Visualisation of what AlexNet “sees” in these images of birds, using FlashTorch. Source
设置场景
几周前,作为伦敦科技周的一部分,我在由 AnitaB.org[组织的伦敦hopper x1T7]上做了一次演讲。滑梯可在](https://anitab.org/)这里获得。
我在演讲后收到了如此积极的反馈,所以我决定写一个稍微长一点的演讲版本,以便以前向世界介绍【T0:)
该软件包可以通过pip进行安装。查看源代码的 GitHub repo 。你也可以在 Google Colab 的笔记本上使用它,而不需要安装任何东西!
但是首先,我将简要回顾一下特征可视化的历史,给你一个关于什么&为什么的更好的背景。
特征可视化介绍
特征可视化 是一个活跃的研究领域,旨在通过探索我们可以“通过他们的眼睛”看东西的方式来理解神经网络如何感知图像。它的出现和发展是为了响应让神经网络更容易被人类理解的日益增长的愿望。
最早的工作包括分析神经网络在输入图像中关注什么。例如, 特定于图像的类别显著性图 通过经由反向传播计算类别输出相对于输入图像的梯度,使输入图像内对相应输出贡献最大的区域可视化(稍后将在帖子中详细介绍显著性图)。

Earliest work on image-specific class saliency maps. Source
特征可视化的另一个技巧是。这允许我们迭代地更新输入图像(最初由一些随机噪声产生)以生成最大限度地激活目标神经元的*图像。它提供了一些关于个体神经元如何对输入做出反应的直觉。这是所谓的深度梦背后的技术,由谷歌推广。*

Deep Dream: what does the network sees in the sky? Source
这是一个巨大的进步,但有缺点,因为它没有提供足够的洞察力来了解整个网络是如何运行的,因为神经元不是孤立运行的。这导致了可视化神经元之间相互作用的努力。Olah 等人通过两个神经元之间的相加或插值,演示了激活空间的 算术性质 。

Neuron arithmetic. Source
然后奥拉在阿尔。通过分析给定特定输入时每个中子在隐藏层中发射的数量,进一步定义了更有意义的可视化单位。 将一组被强烈激活在一起的神经元可视化 揭示出似乎有一组神经元负责捕捉诸如耷拉的耳朵、毛茸茸的腿和草等概念。

A group of neurons detecting floppy ears. Source
该领域内的最新发展之一是 激活图谱 (卡特等人,2019)。在这项研究中,作者解决了可视化过滤器激活的一个主要弱点,因为它仅给出了网络如何响应单输入的有限视图。为了更好地了解网络如何感知无数的对象,以及这些对象在网络的世界中如何相互关联,他们设计了一种方法,通过显示神经元的常见组合来创建“通过网络之眼看到的全球地图”。

Different ways to visualise the network. Source
手电筒背后的动机
当我发现特征可视化的世界时,我立即被它在使神经网络更易解释和说明方面的潜力所吸引。然后我很快意识到,没有工具可以轻松地将这些技术应用于我在 PyTorch 中构建的神经网络。
所以我决定建一个— [FlashTorch](https://github.com/MisaOgura/flashtorch),现在可以通过 *pip*安装!我实现的第一个特征可视化技术是显著图。
我们将在下面更详细地看看什么是显著图,以及如何使用FlashTorch在你的神经网络中实现它们。
显著图
显著性**,在人类视觉感知中,是一种主观品质使视野中的某些事物突出并抓住我们的注意力。计算机视觉中的显著图可以给出图像中最显著区域的指示。**

Examples of saliency maps. Source
从卷积神经网络(CNN)创建显著图的方法最早于 2013 年在论文 深入卷积网络内部:可视化图像分类模型和显著图 中提出。作者报告称,通过计算目标类别相对于输入图像的梯度,我们可以可视化输入图像内的区域,这些区域对该类别的预测值有影响。
使用闪光灯的显著性图
事不宜迟,让我们使用FlashTorch并自己可视化显著图!

FlashTorch还附带了一些utils功能,让数据处理变得更加简单。我们将用这张great grey owl的图片作为例子。
然后,我们将对图像应用一些变换,使它的形状、类型和值适合作为 CNN 的输入。

我将使用已经用ImageNet分类数据集预训练过的AlexNet进行可视化。事实上,FlashTorch支持所有自带torchvision的型号,所以我鼓励你也尝试其他型号!

Backprop类是创建显著图的核心。
在实例化时,它接受一个模型Backprop(model)并且将自定义挂钩注册到网络中感兴趣的层,这样我们就可以从计算图中抓取中间梯度以便可视化。由于PyTorch的设计方式,这些中间梯度不会立即提供给我们。FlashTorch帮你整理一下:)
现在,在计算梯度之前,我们需要的最后一件事是目标类索引。

概括地说,我们对目标类相对于输入图像的梯度感兴趣。然而,该模型是用ImageNet数据集预先训练的,因此其预测被提供为1000 个类别的概率分布。我们希望从这 1000 个值中找出目标类的值(在我们的例子中是great grey owl),以避免不必要的计算,并且只关注输入图像和目标类之间的关系。
为此,我还实现了一个名为ImageNetIndex的类。如果你不想下载整个数据集,只想根据类名找出类索引,这是一个方便的工具。如果你给它一个类名,它会找到对应的类索引target_class = imagenet['great grey owl']。如果您确实想要下载数据集,请使用最新版本torchvision==0.3.0中提供的[ImageNet](https://pytorch.org/docs/stable/torchvision/datasets.html#imagenet) 类。
现在,我们有了输入图像和目标类索引(24),所以我们准备好计算渐变!
这两行是关键:
gradients = backprop.calculate_gradients(input_, target_class)
max_gradients = backprop.calculate_gradients(input_, target_class, take_max=**True**)
默认情况下,将为每个颜色通道计算渐变,因此它的形状将与输入图像相同——在我们的例子中为(3, 224, 224)。有时,如果我们采用颜色通道的最大梯度,会更容易看到梯度。我们可以通过将take_max=True传递给方法调用来实现。渐变的形状将是(1, 224, 224)。
最后,让我们想象一下我们得到了什么!

From far left: input image, gradients across colour channels, max gradients, an overlay of input image and max gradients
我们可以理解,动物所在区域的像素对预测值的影响最大。
但这是一种噪音…信号是传播的,它没有告诉我们多少关于神经网络对猫头鹰的感知。
有办法改善这一点吗?
引导返回救援
答案是肯定的!
在论文 力求简单:全卷积网 中,作者介绍了一种降低梯度计算中噪声的巧妙方法。

Guided backpropagation. Source
本质上,在导向反向传播中,对目标类的预测值没有影响或负面影响的神经元被屏蔽掉并被忽略。通过这样做,我们可以阻止梯度流通过这样的神经元,从而减少噪音。
您可以通过将guided=True传递给calculate_gradients的方法调用,在FlashTorch中使用引导式反向传播,如下所示:

让我们想象引导梯度。

差别是惊人的!
现在我们可以清楚的看到网络在关注一只猫头鹰的凹陷的眼睛圆头。这些都是“说服”网络将对象归类为great grey owl的特征。
但是它并不总是聚焦在眼睛或头部…
****
正如你所看到的,这个网络已经学会关注那些与我们认为这些鸟最有区别的特征相一致的特征。
特征可视化的应用
有了特征可视化,我们不仅可以更好地理解神经网络对物体的了解,而且我们可以更好地进行:
- 诊断网络出了什么问题以及为什么
- 发现并纠正算法中的偏差
- 从只看准确性向前迈一步
- 理解网络行为的原因
- 阐明神经网络如何学习的机制
今天就使用闪光灯吧!
如果你有在 PyTorch 中使用 CNN 的项目,FlashTorch可以帮助你使你的项目更具可解释性和可解释性。
如果用了请告诉我你的想法!我将非常感谢您的建设性意见、反馈和建议🙏
谢谢,祝编码愉快!
特征相关性:数据泄露、混淆的特征和其他可能使你的深度学习模型失败的事情
在现象学科学中,尤其是在深度学习中,最容易被忽视的概念之一是输入特征之间的相关性。
什么是相关性?查看这幅漫画:

从 boss 正在展示的情节来看,剃光头的员工越多,公司销售额增长越多。如果你是老板,你会考虑对你的员工采取同样的行动吗?大概不会。
事实上,你认识到两组事件之间没有的因果关系,它们的行为相似只是偶然。更清楚的是:剃光头不会导致销售。
所以,我们发现了至少两种可能的相关性:没有因果关系和有因果关系。我们还一致认为,只有第二个是有趣的,而另一个是无用的,当不误导。但是让我们更深入一点。
深度学习中的相关性
让我介绍一个深度学习领域的例子,特别是在计算机视觉领域,特征之间的非因果相关性实际上使模型失败。
一张图有哪些「特色」?
一般来说,要素是模型用来构建预测的任何东西。但是,在这种情况下,指定什么是特性可能更好:
考虑一张脸的照片。功能可以是:
- 大量的绿色像素
- 充满绿色像素的圆圈
- 一组白色像素,包含由绿色像素填充的圆圈
- …以上所有内容构成了一只眼睛
- …所有之前的和更多的来构成脸部。
都是特色。为了保持关注,特征“虹膜”与特征“眼睛”相关联。
给老虎分类
现在,假设您训练一个模型来识别老虎,并假设训练集由成千上万张类似于下图的图片组成。

Tiger examples in the Training set.
当然,训练集包括描述什么不是老虎的其他图片;也许是其他动物,汽车,直升机等。
假设您在这样的数据集上训练的模型在测试集上表现得非常好。你对自己的模型很满意,于是你去了一个动物园,想用一张老虎的照片来试试:

The picture you took in the zoo.
您希望模型举起绿色旗帜,将图片识别为包含一只老虎。但是这个模型实际上完全失败了,说它不是。哪里出了问题?
有罪者:相关的、非因果的特征
也许您已经发现了这个问题:训练集中的图片显示的是森林中的老虎,而动物园中的图片显示的是笼子中的老虎。用技术术语来说,你拍的照片属于与训练集不同的发行版。
正在发生的是:模型认为老虎是你所知道的被“森林”包围的动物,因为在我们的训练集中它总是这样。因为在动物园的照片中没有森林,所以模型不认为照片中有老虎。
现在我们可以更好地区分相关性,引入第三个类别,即由混杂因素引起的相关性。它的效果实际上可以混淆模型,让它相信在动物和森林之间存在实际上并不存在的因果关系。
在我们的例子中,混杂因素,又名隐藏特征* (H)导致特征“老虎”和“森林”出现在同一张图片中。h 是一系列事件,如:生物学,发现老虎的机会,还有摄影师的心情,媒体的兴趣,以及任何能让老虎出现在森林中的事物。*
所以当这一组事件 H 发生时,就生成了一张森林中有老虎的图片。但是 H 是不可概括的:并不是每一次老虎出现的时候,森林也会出现,就像动物园里的照片或者雪地里的老虎。
换句话说,我们的模型 do 相信老虎是隐藏的特征 H* ,而不仅仅是我们所知道的老虎这种动物。*
示意图
我们刚刚介绍了在构建数据模型时应该辨别的三种类型的相关性。让我们在一个模式中看到它们

- 第一种情况最容易识别,就像文章开头的漫画。这种情况在机器学习领域的一种表现形式被称为数据泄露。
作为例子,考虑老虎分类器,并且假设每张包含老虎的图片都被标注了一些标记,比如图片左上角的绿色十字。由于像梯度下降这样的最小化方法是懒惰的,所以模型识别标记比识别老虎更容易。因此,模型将只检查标记的存在来识别老虎图片。显而易见的推论是,这样的模型实际上是没有用的,因为在生产中,你不会找到你在训练集中得到的分数。 - 第二种情况是我们唯一可以放心接受的。老虎身上的条纹就是一个例子:因为没有没有条纹的老虎,所以当且仅当老虎身上有条纹时,模型对老虎进行分类是安全的。
- 第三种情况更加微妙,很难向模型教授。但往往可以简单忍受。
—微妙,因为它在培训阶段很容易被忽略,只有在生产中才能发现,就像 tiger 的例子。
—由于统计数据而难以教授:假设您刚刚发现了我们在上一段中描述的模型中的问题,为了解决它,您将您拍摄的照片添加到训练集中。在这样的基础上,模型仍然很难忽略“森林”这一特征,而当面对笼子里的老虎时,训练过程更有可能接受更大的误差。从技术上来说,你添加的图片并没有改变超空间权重的最小值,因为它只是一个超过 1001 的样本。这意味着当你在除了森林以外的任何环境中面对老虎时,你的模型很可能会在生产中失败。
但是也许你的模型被期望用于森林,所以你可以接受你的模型包括这个混杂因素。
结论和进一步阅读
对特征相关性的控制可能是至关重要的,尤其是当您根据不适合您的应用的数据来训练模型时。
原则上,非因果关联问题可以通过选择一个符合I . I . d .的数据集来训练模型来解决,但正如您所理解的,这对于大多数用例来说是不可能的。
有一些非常聪明的人专门研究这些被低估的问题。作为参考,查看崔鹏教授的作品。
数据科学家实际上(应该)做的是建立一个相当好的训练数据集,也使用数据扩充,以便获得足够的数据方差,并最终缓解这些问题。而且,非常重要的是,在部署到生产环境中之后,要监控模型做出的预测。
最大化互信息的特征,它们看起来怎么样?
我们可以通过最大化交互信息来创建潜在特征,但是它们看起来会是什么样的呢?

Image from pixabay
我要感谢Fran ois Fleuret博士、Devon Hjelm博士、yo shua beng io博士提供了令人惊叹的论文和参考资料。最后,我要感谢我的导师布鲁斯博士的鼓励、耐心和有益的讨论。
简介
由于最近的发展,如 MINE 和 DeepInfoMax ,我们不仅能够估计两个高维随机变量的互信息,而且能够创建潜在特征,使输入数据的互信息最大化。
就我个人而言,我认为创造有意义的潜在变量的想法与本吉奥博士的演讲有些关联。简而言之,更好的数据表示(也称为抽象表示)对机器学习模型有益。这也与我们想要最小化/最大化的目标函数不在像素空间中,而是在信息空间中的想法有关。
在这篇文章中,我将把重点放在可视化潜在变量上,这是通过最大化交互信息而产生的。而不是选择可用于任何下游任务的最佳“最佳”表示。
方法

蓝色球体→ 来自 STL 数据集的输入图像(96*96) 蓝色矩形→ 编码器网络 绿色矩形→ 局部信息最大化器 红色矩形→ 全局信息最大化器 黄色矩形→ 先验分布作为正则化
请注意,输入图像已经转换为灰度,因此没有任何颜色通道。此外,我们可以注意到,我们有三个网络作为目标函数,但是,它们都不是在像素空间,而是在信息空间。(详情请阅读论文 DeepinfoMax )。
此外,请注意,所有的目标函数网络都采用两个变量来最大化它们之间的互信息。在我们所有的例子中,我们给这些网络提供了原始图像(如果需要,可以调整大小)和编码的潜在变量。
实验设置
情况 A)潜在变量具有较小的维数
这种情况下,编码器仅由卷积层组成,没有任何填充。因此,在每次卷积操作之后,图像的空间维度将减小。
维数:(96,96)→(94,94)→(92,92)→(90,90)→(88,88)
情况 B)潜在变量具有更大维度
这是编码器由转置卷积层组成的情况。因此,在每一层之后,图像的空间维度将增加。
维度:(96,96)→(98,98)→(100,100)→(102,102)→(104,104)
情况 C)潜在变量具有相同的维度
在这种情况下,我们使用零填充执行典型的卷积运算,因此空间维度不会改变。
维数:(96,96)→(96,96)→(96,96)→(96,96)→(96,96)
情况 D)潜在变量具有相同的维数(反向自动编码器)
在这种情况下,我们首先通过转置卷积来增加空间维度,但是在没有任何填充的情况下应用卷积来立即减小空间维度。(因此反向自动编码器)。
维数:(96,96)→(98,98)→(100,100)→(98,98)→(96,96)
对于上述所有方法,我们将通过创建两幅图像的直方图来测量原始图像和潜在变量之间的互信息,更多细节在此或在此。此外,所有超参数保持不变,例如学习速率、迭代次数和批量大小。
结果

当我们比较 50 次迭代的损失时,我们可以清楚地看到,当我们保持空间维度与输入数据相同时,互信息最大化。

让我们按照情况 a、b、c 和 d 的顺序,比较每种方法创建的不同特征图。


Left Case a Right Case b


Left Case c Right Case d
当我们考虑原始图像和 32 个特征图之间的平均互信息时,我们可以看到互信息对于情况 c 是最高的。

让我们比较另一幅图像,这次是一匹马的图像。


Left Case a Right Case b


Left Case c Right Case d
对于马的图像也获得了类似的结果。




最后,当我们比较 STL 数据集中所有 5000 个图像的最大互信息时,我们可以看到,相对于输入数据保持维度相同具有最高频率的生成具有高互信息的潜在变量。
结论/交互代码
要访问案例 a 的代码,请点击此处。
要访问案例 b 的代码,请点击这里。
要获取案例 c 的代码,请点击此处。
要访问案例 d 的代码,请点击此处。
要访问用于创建可视化效果的代码,请点击此处。
请注意,我已经修改了来自 DuaneNielsen 的原始实现。
遗言
为什么这和我有关系?我认为这种创造潜在变量的形式可能是克服过度拟合的一个好主意。诚然,我们可以使用生成模型来创建新的数据点,这些数据点可以用作数据扩充,但它们也有自己的一系列问题,如模式崩溃。拥有多样化的数据扩充方法对每个人都有好处。
最后,所有的参考文献都被链接到这里。
二月版:数据可视化
8 篇关于可视化数据的最佳文章

数据可视化是任何数据科学过程中必不可少的一步。这是数据科学家和最终用户之间的最后一座桥梁。它交流、验证、对抗和教育。如果做得正确,它会向更广泛的受众展示来自数据科学项目的见解。
伟大的数据可视化不仅仅是用数字描绘一幅美丽的图画。事实上,这通常只是其中的一小部分。我们还需要考虑其他因素,如观看视觉效果的受众类型、数据素养水平、交互性需求以及多个图表讲述的整体故事。
这 8 篇文章组成了我们的首选帖子,这些帖子提供了数据可视化的有用工具,对实践方向的新见解,以及如何有效使用图表讲述故事的有趣示例。
本杰明·库利,TDS 编辑助理。
用 Datashader 进行大规模可视化和制图
由芬乔 — 6 敏念
如果你曾经试图用 Matplotlib 或 Seaborn 创建一个超过 100,000 点的群体图或关系图,你会对在加载图时摆弄拇指的场景并不陌生。幸运的是,Datashader 是以有意义的方式快速表示大型数据集的绝佳选择。
第三波数据可视化
以利亚·米克斯(Elijah Meeks)——12 分钟阅读
首先是如何可视化数据的清晰性。然后是指导最佳实践的新系统。现在,我们正在进入数据可视化的第三次浪潮:融合。Elijah Meeks 解释了为什么从业者需要将他们的重点从单个图表转移到那些图表出现的产品的构建、评估和交付上。
Airbnb 租房房源数据集挖掘
萨朗·古普塔 — 11 分钟阅读
纽约市一直是 Airbnb 最热门的市场之一,截至 2018 年 11 月,已有超过 52,000 个房源。从 Airbnb 内部对 Airbnb 数据集进行的探索性分析通过静态和交互式可视化调查了纽约市的租赁情况。
Python 中 5 个快速简单的数据可视化代码
由乔治·赛义夫 — 7 分钟阅读
Matplotlib 是一个流行的 Python 库,可以用来非常容易地创建数据可视化。然而,每次做新项目时,设置数据、参数、图形和绘图可能会变得相当混乱和乏味。这篇博文着眼于 5 种数据可视化,以及如何用 Python 的 Matplotlib 为它们编写一些快速简单的可重用函数。
使用交互式地图和动画可视化伦敦的自行车移动性
通过 Eden Au — 9 分钟读取
报告显示,77%的伦敦人同意骑自行车是短途旅行最快的方式。让我们看看如何使用图形、地图和动画来可视化自行车共享系统。
Python 中数据可视化的下一个层次
到时,将 Koehrsen — 8 分钟读取
沉没成本谬误是人类遭受的许多有害认知偏见之一。它指的是我们倾向于继续将时间和资源投入到一个失败的事业中,因为我们已经在追求中花费了太多的时间。
对 NBA 原始投篮日志数据的洞察和对热手现象的探究
由艾蒙汗 — 14 分钟读完
数据科学和篮球比赛每年都变得越来越交织在一起。这篇文章通过可视化 NBA 投篮数据来探索“热手”现象的流行。
117 天我的火绒简介数据
由布雷登杰拉德 — 5 分钟阅读
数据往往是非常私人的,就像这个对 Tinder 四个月活动的可视化探索。布雷登·杰拉德深入探讨了他的喜欢、超级喜欢和信息可能会也可能不会导致真正的约会。
我们也感谢最近加入我们的所有伟大的新作家,安德鲁·克鲁格,凯利·鲁希德,伊夫泰彻·马蒙,伦纳德·梅尔伍德,艾米·沃格尔,史蒂夫·德里斯科尔,普雷斯顿·林,里卡多·奥坎波,萨曼莎·班西尔,勒内·布雷默 姜黎黎,克里斯托弗·道蒂,海伦娜·T·施,乔尔·奎萨达,阿尤什·潘特,保罗·皮纳德,罗曼·莫泽,伊格纳西奥·哈戈皮安,奇拉格·查达等等。 我们邀请你看看他们的简介,看看他们的工作。
根据推特,联邦选举…投票给谁
介绍
澳大利亚 2019 年联邦选举即将到来(5 月 18 日),像往常一样,我不知道该投票给谁。这不是真的,通常,我是俗称的“驴选民”——不投票的人。你知道,在民主的澳大利亚,投票是强制性的。来自澳大利亚选举委员会:
根据 1918 年《联邦选举法》第 245 条,没有有效和充分的理由不参加联邦选举投票是犯罪行为。你需要支付 20 美元的罚款。
因此,我行使我的民主权利驴投票。
我并不是不关心我们伟大而幸运的国家的状况(我是真心实意地这么说),而是我们面临的选择本质上是一样的,只有政策上最微小的差异。我们很幸运,因为我们的投票不是自由或压迫的投票,所以这可能是我无知行为的驱动力?
在今年的投票中,自由党的斯科特·莫里森(代表非澳大利亚人的蓝色右倾政党)将与工党的比尔·肖恩(红色左倾政党)对决。斯科特·莫里森,简称 ScoMo,在 2018 年 8 月 24 日成功政变反对马尔科姆·特恩布尔后成为自由党领袖。
尽管今年有所不同——没有两大政党之间的明显差异——但今年我想进行一次投票。但要做到这一点,我将使用的信息之一是我更喜欢哪位领导人的推文活动。这不会是我投票的唯一依据(我没有完全疯掉),但这将是一个开始。
我从一张完全空白的白纸开始了这项分析。此外,我甚至不知道我会根据 Twitter 上的什么活动来投票。我只是继续收集 ScoMo 和 Shorten 的推文,并从那里获取。
这些方法
该分析使用统计编程语言 R(版本 3.4.3)创建,并在 RStudio 中完成。
Twitter 数据是在 2019 年 5 月 3 日使用 rtweet 包为两位政党领导人收集的。
分析中使用的包如下所示:
rtweet: for tweets
ROAuth: for twitter authorisation
tidyverse: data munging and visualisations
lubridate: working with dates
scales: plotting axis in a formatted way
zoo: working with yyyy-mm dates
分析
该分析着眼于两位领导人自斯科莫上任以来一直在创建的推文。
我会问一些问题,每个问题的获胜者会得到一分。谁在分析结束时得分多,谁就能获得我对这一决策过程中 Twitter 部分的投票。
然而,这种分析有一个真正的局限性——Twitter 只是一个平台,所以考虑所有的社交媒体平台可能会产生不同的结果。此外,任何一方的选民的人口统计可能或可能不完全适合这个平台。既然这样说,为什么要为小事而烦恼呢?!
谁发的微博多?
自 ScoMo 上任以来,他已经发布或转发了 551 次,而同期 Shorten 发布了 977 次。
与此同时,比尔·肖顿(Bill Shorten)整月都在更频繁地发推特,只有今年 1 月两者之间的差距相当接近。

此外,自 2018 年初以来,ScoMo 仅在 5 月和 6 月连续两个月发了两次超过 Shorten 的推文(以他当时的财务主管身份,毫无疑问,这是当年的预算)。
获胜者:
账单缩短(Shorten 1,ScoMo 0)
谁的推文更受欢迎?
这将是一个分为两部分的问题。第一部分将看谁的推文得到更多的喜爱,而第二部分将看谁得到更多的转发。
谁更受欢迎?
当用收到的“收藏夹”数量来衡量推文的受欢迎程度时,Shorten 用巧克力来衡量,平均有 536 个收藏夹,ScoMo 有 325 个。
ScoMo 获得了两个最高的喜爱计数,下面的推文获得了 15,936 个喜爱:
参议员弗雷泽·安宁(Fraser Anning)指责新西兰一名暴力右翼极端恐怖分子在移民问题上实施的谋杀袭击的言论令人作呕。这些观点在澳大利亚没有立足之地,更不用说澳大利亚议会了。
谁会忘记,参议员安宁可笑的麻木不仁的言论…


谁被转发的次数更多?
Shorten 显然获得了更多的转发(平均转发数为 216 到 ScoMo 的 61)。ScoMo 上面关于参议员安宁的推文也是被转发最多的推文。Shorten 转发最多的推文是关于 ScoMo 学校的削减:
斯科特·莫里森已经锁定了对公立学校的大规模削减。没有人投票支持莫里森削减公立学校的经费,但他还是要削减。请分享,这样每个人都知道刚刚发生了什么。

获胜者:
账单缩短(Shorten 2,ScoMo 0)
谁的微博更长?
我为什么关心这个?嗯,我可能不知道,但也许这是一个代理谁提供了更详细的成分水平…或行话?
ScoMo 显然拥有更长的推文,中值字符长度为 204,而 Shorten 的中值推文长度为 153,并且每条推文使用更多的单词,中值单词数为 33,而 Shorten 为 27。
获胜者:
ScoMo(缩短 2,ScoMo 1)
谁是标签之王?
ScoMo 在超过 20%的推文中包含了一个标签。另一方面,Shorten 在他的推文中只有超过 7%的标签。好老臀部 ScoMo 是新铸造的标签王!

获胜者:
ScoMo(缩短 2,ScoMo 2)
谁使用更多的媒体(照片、视频)?
当涉及到政治家时,媒体的使用是很有趣的…谁不喜欢在摄像机周围笨拙地握手或亲吻几个婴儿的好机会呢?!
与 ScoMo 相比,Shorten 倾向于在更多的推文中包含照片,几乎 50%的 Shorten 推文中包含照片。ScoMo 也不甘落后,他 47%的推文包含照片,但他需要解除他的婴儿亲吻游戏。我把这叫做平局。就像网球一样,两位候选人都需要赢两场或更多场比赛(是的,我意识到这不是网球,我是根据他们的推文选择政党的,所以这里没有规则)。

获胜者:
Draw(缩短 2,ScoMo 2)
推文内容分析
Shorten 在推文中最常用的术语是“劳动”,这个词出现在他所有推文中的 30%以上。相比之下,ScoMo 自己的政党并没有出现在他最常用的 30 个术语中。
Shorten 的推文似乎在谈论皇家委员会(我认为目前的委员会是银行贷款,因为银行也出现了),同时也提到了令人揪心的项目——削减、学校、家庭和医疗保险。他似乎也经常提到他的对手。
ScoMo 似乎一直将他的推文内容集中在经济上,诸如经济、就业、企业、预算等术语和一些标签( strongeraus 和 buildingyoureconomy )出现得相当频繁。
测量他们推特行为的情感得分将会很有趣。


获胜者:
ScoMo(缩短 2,ScoMo 3)
每位领导人多久提到一次他们的对手
需要做一些假设来确定每个候选人是否在他们的推文中提到这个职位。如果 ScoMo 的推文中出现了“Bill”、“Shorten”、“Labor”或“Opposition”这些词,那么我们就说他提到了他的竞争对手。另一方面,如果 Shorten 的推文中包含“斯科特”、“莫里斯森”、“自由派”、“自由派”中的任何一个,那么我们说他在推文中提到了他的竞争对手。
Shorten 的推文中提到了很多他的对手,几乎五分之一的推文(19%)提到了他的对手,而 ScoMo 只在超过 7%的推文中提到了 Shorten。我认为这提出了一个有趣的问题;我们希望我们的领导专注于手头的工作,还是其他人正在做的事情?

领导在提到对手的时候都在说什么?
好吧,更让我好奇的是,两位领导人在他们的推文中提到或没有提到他们的对手时最常用的词如下。我这个垃圾真人秀爱好者真的希望这是一场大型的口水战…
嗯,看来我的愿望不会实现了…
Shorten 谈的是公平、时间、交货和家庭而不是 ScoMo。他在的时候,似乎深受银行和皇家委员会、削减到罚款的税率和学校的影响。


另一方面,ScoMo 在谈到他的政府的实力、工作岗位、经济、支持以及有计划时没有提到缩短,而他在提到缩短时最常用的词是关于税收、边境、拘留和退休人员正如我们之前看到的,缩短或分娩在 ScoMo 的推文中并不突出。


获胜者:
ScoMo(缩短 2,ScoMo 4)
谁的微博更积极?
使用由芬恩·厄普·尼尔森创建的一个叫做 T42·阿芬恩词典的情感分析常用词典,我们可以测量每个候选人推文中包含的词汇的积极度。
使用这种方法,很明显 ScoMo 一直在使用 Twitter 推送比 Shorten 更积极的信息,与 Shorten 的 346 相比,整体积极指数为 578。


随着时间的推移,当我们看到这一点时,很明显,Shorten 已经开始以越来越消极的态度发布推文。我想知道他的信息策略是不是利用恐惧和消极来让选民投票给工党…
获胜者:
ScoMo(缩短 2,ScoMo 5)
结论

在提出的八个问题中,从我的角度来看,ScoMo 赢了五个,缩短了两个。两位候选人抽出一个问题。虽然缩短推文的频率更高,使用图像的频率也更高,但 ScoMo 推文的内容让他在我 2019 年的联邦推特选举中脱颖而出。
有什么想法可以随意评论。
想查看更多数据分析项目?头过来不怪数据博客https://www.dontblamethedata.com
使用 PySyft 的联邦学习
具有设备上能力的训练机器学习模型的新时代。在本教程中,我将使用 PyTorch 和 PySyft 来训练一个使用联邦方法的深度学习神经网络。
联邦学习简介

什么是联合学习?
联合学习是一种分布式机器学习方法,它能够在大型分散数据语料库上进行模型训练。联合学习使手机能够协作学习共享的预测模型,同时将所有训练数据保留在设备上,将机器学习的能力与将数据存储在云中的需求分离开来。这超越了在移动设备上进行预测的本地模型的使用(如移动视觉 API 和设备上智能回复),还将模型训练带到了设备上。
目标是机器学习设置,其中目标是利用分布在大量客户端上的训练数据来训练高质量的集中式模型,每个客户端都具有不可靠且相对较慢的网络连接。
这个新领域由一系列技术组成,这些技术允许 ML 工程师在不直接访问用于训练的数据的情况下训练模型,并避免他们通过使用密码术获得关于数据的任何信息。
该框架依赖于三种主要技术:
- 联合学习
- 差异隐私
- 安全多方计算
在本文中,我将介绍联合学习及其在预测波士顿房价中的应用。
谷歌如何使用联邦学习?
How google uses Federated Learning to make more accurate keyboard suggestions
应用程序是如何工作的?
随着 PySyft、Tensorflow Federated 等很多著名库的兴起。对于普通开发者、研究人员和机器学习爱好者来说,创建一个去中心化的机器学习训练模型变得更加容易。在这个项目中,我使用 PySyft——一个用于安全、私人深度学习的 Python 库——来训练一个数据集,其目的是预测波士顿市所列房产的房价。PySyft 使用 PyTorch 中的多方计算(MPC)将私有数据从模型训练中分离出来。
在训练深度学习预测模型时,数据会与 Alice 和 Bob 一起安全地保存在本地。对于一个私人训练,我使用了一种联合的方法,其中 ML 模型是用 Alice 和 Bob 两方拥有的移动设备的设备上能力在本地训练的。随着移动设备的计算机性能的提高,以更高的效率训练 ML 模型变得更加容易。
联合学习方法中涉及的步骤
- 移动设备下载全球 ML 模型
- 当用户使用与 ML 模型关联的应用程序时,数据正在生成
- 随着用户开始更多地与应用程序进行交互,用户可以根据其使用情况获得更好的预测
- 一旦模型准备好与服务器进行预定的同步。利用设备上的能力进行训练的个性化模型被发送到服务器
- 收集来自所有设备的模型,并且使用联合平均函数来生成比先前模型改进得多的模型版本
- 一旦被训练,改进的版本被发送到所有设备,在那里用户获得基于全球所有设备的使用的体验
安装 PySyft
PySyft 是一个用于安全、私人深度学习的 Python 库。PySyft 使用 PyTorch 中的联邦学习、差分隐私和多方计算(MPC) ,将私有数据从模型训练中分离出来。
为了安装 PySyft,建议您先设置一个 conda 环境
conda create -n pysyft python=3
conda activate pysyft
conda install jupyter notebook
然后你需要安装软件包
pip install syft
使用联邦学习方法开发神经网络的分步指南
1)导入库
以下 Python 库用于开发项目。
- Numpy — NumPy 是 Python 编程语言的库,增加了对大型多维数组和矩阵的支持,以及对这些数组进行操作的大量高级数学函数。
- PyTorch — PyTorch 是基于 Torch 库的开源机器学习库,用于计算机视觉、自然语言处理等应用。它主要是由脸书人工智能研究小组开发的。它是免费的开源软件。
- PySyft — PySyft 是一个用于安全、私人深度学习的 Python 库。PySyft 使用 PyTorch 中的联邦学习、差分隐私和多方计算(MPC)将私有数据从模型训练中分离出来。
- Pickle—Pickle 模块实现了二进制协议,用于序列化和反序列化 Python 对象结构。

Importing libraries in the Jupyter Notebook
2)初始化训练参数
我们已经对神经网络进行了 100 多次的训练,以获得良好的结果。通过创建总共 8 个批次的记录,我们能够获得良好的结果。的学习率设置为 0.001,以使用随机梯度下降作为网络的优化器。到目前为止,PySyft 只支持 SGD 优化器用于反向传播算法来计算误差和更新网络参数。
梯度下降是一种寻找函数最小值的一阶迭代优化算法。为了使用梯度下降找到函数的局部最小值,人们采取与当前点的函数的梯度(或近似梯度)的负值成比例的步骤。

Initiating the learning parameter in the Jupyter Notebook
3)数据集预处理
下一步包括将数据集读入 Jupyter 笔记本,并在用数据训练神经网络之前对其进行预处理。预处理数据有助于我们更好地了解数据集,并帮助我们选择有助于预测输入结果的最佳特征。

Data Preprocessing for the Boston Housing dataset
在这个项目中,我们使用了最常见的波士顿住房数据集来训练神经网络。我们根据不同的定价特征来预测各种房产的价格。下面列出了其中的一些。你可以在这里找到数据集。
数据库中的每条记录都描述了波士顿的一个郊区或城镇。数据取自 1970 年波士顿标准大都市统计区(SMSA)。
属性定义如下
- CRIM: 城镇人均犯罪率
- ZN: 面积超过 25,000 平方英尺的住宅用地比例
- 印度河:每个城镇的非零售商业用地比例
- CHAS: 查尔斯河虚拟变量(= 1,如果区域边界为河流;0 否则)
- 氮氧化物:氮氧化物浓度(百万分之一)
- RM: 每个住所的平均房间数
- 楼龄:1940 年前建成的自住单位比例
- DIS: 到五个波士顿就业中心的加权距离
- RAD: 放射状公路可达性指标
- 税:每万美元的全价值财产税
- PTRATIO: 按城镇分列的师生比率
- b:1000(Bk 0.63)2 其中 Bk 是按城镇划分的黑人比例
- LSTAT: %
- MEDV: 以千美元为单位的自有住房中值我们可以看到,输入属性混合了多种单位。
4)用 PyTorch 创建神经网络

我们现在必须使用 PyTorch 为模型定义神经网络架构。深度学习网络由 2 个不同的隐藏层组成,并涉及网络中所有层的 Relu 激活函数的使用。输入层由 13 个不同的感知器组成,对应于训练数据集的每个输入特征。
5)将数据与远程移动设备连接
鲍勃和爱丽丝是参与整个循环的两个人。出于模拟目的,我们向所有使用全局 ML 模型与应用程序交互的网络客户端发送批量数据集。
我们可以看到,使用 PySyft 库及其 PyTorch 扩展,我们可以使用张量指针执行操作,就像我们可以使用 PyTorch API 一样(但仍有一些限制需要解决)。


现在,我们联系名为 Alice 和 Bob 的工作人员,他们使用各方提供的数据来训练神经网络。

6)训练神经网络
多亏了 PySyft,我们能够在不访问远程和私有数据的情况下训练一个模型:对于每一批,我们将模型发送给当前的远程工作者,并在将它发送给下一批的工作者之前,将它返回到本地机器。
然而,这种方法有一个限制:通过取回模型,我们仍然可以访问一些私人信息。假设 Bob 的机器上只有一条数据记录。当我们取回模型时,我们可以用更新的权重来检查 Bob trained 用来重新训练模型的数据。
为了解决这个问题,有两种解决方案:差分隐私和安全多方计算(SMPC)。差分隐私将用于确保该模型不会提供对某些隐私信息的访问。SMPC 是一种加密计算,作为回报,它允许您私下发送模型,这样拥有数据的远程工作人员就看不到您使用的重量。



您可以使用下面的 Jupyter 笔记本来检查整个项目。我还留下了联邦学习项目的链接,可以在下面我的 GitHub 知识库中找到。
资源
如果你有兴趣学习更多关于安全和私人人工智能以及如何使用 PySyft 的知识,你也可以在 Udacity 上查看这个免费课程 。这是一门很棒的初学者课程,由 Andrew Trask 教授,他是 OpenMined Initiative 的创始人。
在 GitHub 上查看项目
关于我
我是印度计算机科学与工程专业的大四学生,过去 5 年来一直从事编码工作。在从事了几个机器学习和 iOS 开发项目之后,我正在学习联邦学习的新技术,因为未来十年你如何在移动设备上训练深度学习模型取决于联邦协议。你可以在我的网站上了解我更多。更多项目请查看我的 GitHub 档案。
联合学习:一种新的人工智能商业模式

联合学习不仅是一项有前途的技术,也是一种全新的人工智能商业模式。事实上,作为一名顾问,我最近受命就一家医疗保健公司如何通过创建联合学习框架与一些竞争对手建立“数据联盟”提出建议。这篇文章的目标是向你解释 FL 将如何产生一个新的数据生态系统和创建数据联盟。
什么是联邦学习(FL) ?
在不涉及太多技术细节的情况下,FL 可以被定义为一个分布式机器学习框架,它允许从分布在数据所有者之间的数据构建一个集体模型。
AI 项目需要的数据涉及多个要素。我会说,我们创造伟大的人工智能项目的能力总是有限的。对外部数据的访问非常受限,这是构建高级人工智能应用程序的一个现实问题。更糟糕的是,由于行业竞争、隐私安全和其他管理程序,即使是同一公司不同部门之间的数据集成也是一个挑战。
总的来说,集中式 ML 远非完美。事实上,训练模型需要公司积累大量相关数据到中央服务器或数据中心。在一些项目中,这意味着收集用户的敏感数据。

因此,大多数企业往往无法实现集中式机器学习。不用说,收集项目所需的所有数据的“简单”任务是相当昂贵和耗时的。
在从事 ML 项目时,我经常会遇到两个问题:
根据项目的不同,您需要的数据的所有者可能只是不想与您的公司共享这些数据。当涉及竞争敏感数据或受法律保护的医疗数据时,情况就是如此。
其次,大量有价值的训练数据是在缓慢且不可靠的网络边缘的硬件上创建的,例如智能手机或工业设施中的设备。我意识到,对于公司来说,使用这样的设备进行沟通可能会很慢,而且成本很高。
联合学习为与传统机器学习相关的大多数问题带来了答案。事实上,算法训练转移到了网络的边缘,因此数据永远不会离开设备,无论是手机还是医院分支机构的服务器。一旦模型从数据中学习,结果被上传并与来自网络上所有其它设备的更新相结合。改进后的模型将在整个网络中共享。(1)
新的商业模式?
云计算模式正受到前所未有的挑战。公司不能再忽视数据隐私和数据安全日益增长的重要性。而且一个公司的利润和它的数据之间的关系在 AI 时代越来越明显。然而,联合学习的商业模式为利用数据的应用提供了一个新的范例。
联合学习的目标是,当每个公司使用的独立数据集无法创建准确的模型时,联合学习的机制使公司可以共享统一的模型,而无需直接的数据交换。企业将有可能获得更多数据,更好地训练它们的模型。
公平的数据共享可以通过从每一方建立的子模型建立一个元模型来实现,以便只传输模型参数,或者通过使用加密技术来允许不同方之间的安全通信。区块链技术也有助于加强数据控制。
简而言之,联合学习使不同的数据所有者在组织层面上协作和共享他们的数据成为可能。在最近的一篇论文中,研究人员(杨强等人)设想了发生这种情况的不同配置。
纵向和横向联合学习
让我们以来自同一个国家的两家银行为例。尽管它们有不重叠的客户群,但是它们的数据将有相似的特征空间,因为它们有非常相似的业务模型。在水平联合学习的例子中,他们可能会走到一起合作。

在垂直联合学习中,两家提供不同服务(如银行和电子商务)但拥有大量客户的公司可能会在他们拥有的不同特征空间上找到合作空间,从而为双方带来更好的结果。

在这两种情况下,由于区块链技术等原因,数据所有者可以在不泄露各自客户隐私的情况下进行协作。他们都将获得更多数据,以更好地改善他们的人工智能计划。
现在,联合学习似乎非常适合医疗保健和银行业。对于银行,我们可以想象一个系统,其中多家银行可以训练一个通用的强大的欺诈检测模型,而无需通过联合学习相互共享敏感的客户数据。关于医院和其他医疗保健机构,如果他们同意以保护隐私的方式共享用于模型训练的患者数据,他们可能会受益。
建立数据联盟
当我负责围绕联合学习框架建立数据联盟时,我注意到公司通常对数据隐私非常怀疑。事实上,他们都不想通过分享他们的数据来帮助竞争。主要的挑战将在这里…如何说服公司打开他们的数据战箱,并与他人分享?
我的观点是,这种基于联合学习的新商业模式必须得到工业数据联盟的支持,否则注定会失败。联盟可以有几个实体,通过加入联盟,实体可以在联邦学习框架下使用数据进行合作。

我正在研究的数据联盟将是这样的:
它将是一个多方系统,由两个或两个以上的组织组成联盟,通过联合学习在各自的数据集上训练共享模型。将鼓励选定的公司和组织加入该联盟,该联盟将有明确的激励机制。
我认为,为了将不同组织间的联合学习完全商业化,需要开发一个公平的平台和激励机制。
联盟成员享有权利和利益,也履行责任。在我看来,联盟必须利用区块链来建立各方的共识,在一个永久的数据记录机制中记录各方的贡献,并奖励做出突出贡献的各方。
“保持数据私有是每个参与实体实现共同目标的联合学习的主要附加值。”(2)
我建议依靠中立的第三方。他们可能负责“提供基础设施来聚合模型权重,并在联盟中的公司之间建立信任”。(3)
此外,数据结构和参数通常是相似的,但不需要相同,但在每个客户端需要大量预处理来标准化模型输入。一个中立的第三方可以完美地处理项目的这一部分。

目前,数据孤岛和对数据隐私的关注是人工智能的重要挑战,但联合学习可能是一个解决方案。它可以为多个组织建立一个统一的模型,同时保护本地和敏感数据,以便他们可以共同受益,而不必担心数据隐私。
联合学习的挑战
将联合学习从概念过渡到生产并非没有挑战。事实上,在联邦学习的效率和准确性方面已经取得了很多成就,在我看来,更重要的挑战与安全性有关。
联合学习的关键因素是保护与数据相关的隐私。似乎即使没有公开实际数据,也可以利用重复的模型权重更新来揭示不是对数据全局的而是对单个贡献者特定的属性。( 4
这种推断既可以在服务器端执行,也可以在客户端执行。一个可能的解决方案是使用“差别隐私”技术来降低这种风险。
结论
联合学习使得在监管和竞争行业中应用机器学习变得更容易、更安全、更便宜。通过 FL,公司可以改进他们的模型,增强他们的人工智能应用。在医学领域,FL 可能是更好的治疗和更快的药物发现的同义词。
我认为,当前大公司为了竞争优势而集中汇总数据和创建孤岛的思维模式将是推动采用联合学习的主要障碍。大多数公司最近才开始他们的人工智能之旅……我们将需要有效的数据保护政策、适当的激励措施和围绕分散数据的商业模式,以解决这些问题并开发联合人工智能生态系统。
简而言之,我期望在许多垂直市场中看到更多的工业数据联盟,例如,金融行业可以形成金融数据联盟,而医疗行业可以形成医疗数据联盟。从长远来看,我们还可以期待来自不同行业但拥有相同人工智能愿景的公司之间的数据联盟。
如果你有兴趣有更多的技术细节,我推荐这个 网站 。
联合学习和隐私保护人工智能
通过联合学习,保护用户隐私安全,并使您的模型训练有素。
上个月,我在寻找一些在我的新项目中使用的技术,然后,我发现了一些关于从保持隐私的人那里获得训练数据的东西。我了解到这被称为“联合学习”,这是机器学习保持数据隐私的最佳方式。但是它是如何工作的呢?请继续阅读,寻找答案!

from slideshare.net
联合学习与一个设备网络一起工作,这些设备能够在没有中央服务器的情况下进行自我培训,因此我们可以将联合学习定义为分散式学习,我们用作培训平台的设备是我们的智能手机,足够智能来做到这一点。使用这种技术,我们不仅可以保护用户的隐私,还可以改善用户的体验。
那么它是如何工作的呢?设备只有在有资格参与时才能参与(空闲状态),但开发人员正在努力将对性能的影响降至最低。然后,符合条件的设备只从自身获取有用的数据,并将其放入数据集,利用该数据集训练模型,然后将其发送到服务器,服务器只获取加密数据,而不使用密钥。然后,服务器可以在没有数据本身的情况下读取经过训练的模型。
但是服务器怎么区分我最近的搜索是不是例外呢?也许有人有对模型没有用的独特数据,所以我们需要通过限制手机对模型的贡献来排除这些数据,并添加噪声来掩盖罕见的数据。这被称为差分隐私,当共享模型的参数受唯一数据影响太大时非常有用。
如果设备忙于训练,我们如何测试模型?我们可以使用其他设备来测试我们的模型!通过这种方式,我们可以在每天使用模型的用户手中测试模型。然后我们就有了一个由同型号的训练员和测试员私下组成的网络,没有任何侵犯隐私的地方!
一个真实的例子是在医疗保健行业,由于严格的隐私保护法律,组织不能获取患者的数据并使用它们来训练一个可以有效帮助他们的模型。联合学习可以保护患者的隐私,同时也可以利用数据。英特尔与宾夕法尼亚大学生物医学图像计算和分析中心成功演示了联合学习如何应用于医学成像,要了解更多信息,请点击此处。
最后,联合学习是训练一个完美模型的最好方法,我认为几年后它将无处不在,但它也是现在。使用联合学习的应用程序的一个例子是我开始写这篇文章的 Gboard,在这种情况下,键盘可以建议你输入单词,分析你以前在网上做过的任何事情。
如果你想了解更多关于联邦学习的知识,我推荐你访问 https://federated.withgoogle.com/。
前馈神经网络简介
深度前馈网络或者也称为 多层感知器 是大多数 深度学习 模型的基础。像CNN和 RNNs 这样的网络只是前馈网络的一些特例。这些网络主要用于 监督机器学习 任务,其中我们已经知道目标功能,即我们希望我们的网络实现的结果,并且对于实践机器学习极其重要,并且形成了许多商业应用的基础,这些网络的存在极大地影响了诸如 计算机视觉 和 NLP 等领域。
前馈网络的主要目标是逼近某个函数 f*。例如,回归函数 y = f *(x)将输入 x 映射到值 y。前馈网络定义了映射 y = f(x;θ)并学习导致最佳函数逼近的参数θ的值。
这些网络被称为前馈网络的原因是,信息流发生在正向,因为 x 用于计算隐藏层中的某个中间函数,该中间函数又用于计算 y。在这种情况下,如果我们将最后一个隐藏层的反馈添加到第一个隐藏层,它将代表一个递归神经网络。
这些网络由许多不同功能的组合来表示。每个模型都与描述功能如何组合在一起的非循环图相关联。例如,我们可能有三个函数 f (1)、f (2)和 f (3)连接成一条链,形成 f (x) = f(3)(f (2)(f (1)(x))。其中 f(1)是第一层,f(2)是第二层,f(3)是输出层。
输入层和输出层之间的层称为隐藏层,因为训练数据不会显示这些层的预期输出。网络可以包含任意数量的隐藏层和任意数量的隐藏单元。一个单元基本上类似于一个神经元,它接受来自前几层单元的输入,并计算自己的激活值。
现在出现了一个问题,当我们有线性机器学习模型时,为什么我们需要前馈网络,这是因为线性模型仅限于线性函数,而神经网络不是。当我们的数据不是线性的时,可分离的线性模型在逼近时会遇到问题,而对于神经网络来说却很容易。隐藏层用于增加非线性并改变数据的表示,以便更好地概括函数。
对于设计任何前馈神经网络,有一些事情你需要决定,大多数网络需要一些成分,其中一些对于设计机器学习算法是相同的。
【计算机】优化程序
优化器或优化算法用于最小化成本函数,这在每个训练周期或时期之后更新权重和偏差的值,直到成本函数达到全局最优。
优化算法有两种类型:
一阶优化算法
这些算法使用成本函数相对于参数的梯度值最小化或最大化成本函数。一阶导数告诉我们函数在特定点是减少还是增加,简而言之,它给出了与曲面相切的线。
二阶优化算法
这些算法使用二阶导数来最小化成本函数,也被称为 Hessian 。由于二阶导数的计算成本很高,所以二阶导数并不常用。二阶导数告诉我们一阶导数是增加还是减少,这暗示了函数的曲率。二阶导数为我们提供了一个二次曲面,它触及误差曲面的曲率。
有许多算法用于优化,例如:

The architecture of the feedforward neural network
网络的架构
网络的架构是指网络的结构即隐藏层数和每层中隐藏单元的数量。根据通用逼近定理具有线性输出层和至少一个具有任何“挤压”激活函数的隐藏层的前馈网络可以从一个有限维空间到另一个空间以任何期望的非零误差量逼近任何 Borel 可测函数,只要网络被给予足够的隐藏单元。这个定理简单地陈述了无论我们试图学习什么函数,总有一个 MLP 能够代表这个函数。
我们现在知道总会有一个 MLP 能够解决我们的问题,但是没有特定的方法来确定这个架构。没有人可以说,如果我们使用 n 个层和 M 个隐藏单元,我们将能够解决给定的问题,找到这种没有试凑法的配置仍然是一个活跃的研究领域,目前只能通过试凑法来完成。
找到正确的架构很难,因为我们可能需要尝试许多不同的配置,但是即使我们有了正确的 MLP 架构,它仍然可能无法代表目标功能。发生这种情况有两个原因,首先是优化算法可能无法找到对应于所需函数的参数的正确值,另一个原因是训练算法可能由于过度拟合而选择错误的函数。
价值函数
成本函数在训练的任何一点都显示了我们的模型得出的近似值与我们试图达到的实际目标值之间的差异并且总是单值的,因为它的工作是评估网络作为一个整体如何。就像机器学习算法一样,也使用基于梯度的学习来训练前馈网络,在这种学习方法中,使用像随机梯度下降这样的算法来最小化成本函数。
整个训练过程在很大程度上取决于我们的成本函数的选择,对于其他参数模型,成本函数的选择或多或少是相同的。
在我们的参数模型定义分布 p(y | x;𝛳),我们简单地使用训练数据和模型预测之间的交叉熵作为成本函数。我们还可以采取另一种方法,通过预测以 x 为条件的 y 的一些统计量,而不是预测 y 上的完整概率分布。
为了将函数用作反向传播算法的成本函数,它必须满足两个特性:
成本函数必须能够写成平均值。
成本函数必须不依赖于输出层之外的网络的任何激活值。
成本函数主要是 C(W,B,Sr,Er)形式,其中 W 是神经网络的权重,B 是网络的偏差,Sr 是单个训练样本的输入,Er 是该训练样本的期望输出。
一些可能的成本函数是:
二次成本

quadratic cost function
这个函数也被称为均方误差、最大似然和平方和误差。
交叉熵成本

Cross-entropy cost function
这个函数也被称为伯努利负对数似然和二元交叉熵
指数成本

Exponential cost function
海灵格距离

Hellinger distance cost function
这是一个也被称为统计距离的函数。
输出单位
输出单元是存在于输出层的那些单元,它们的工作是给我们期望的输出或预测,从而完成神经网络必须执行的任务。输出单位的选择与成本函数的选择紧密相关。神经网络中任何可以作为隐单元的单元也可以作为输出单元。
可供选择的输出单位有:
线性单位
最简单的输出单元是用于高斯输出分布的线性输出单元,这些单元基于仿射变换,其不会给输出层带来非线性。给定 h 特征,线性输出层产生矢量:

linear unit function
对于线性图层,最大化对数似然相当于最小化均方误差,最大似然使得更容易获得高斯分布的协方差。
这些线性单元的优点是它们不会饱和,即它们的梯度总是保持不变,永远不会接近零,因此这些单元不会给基于梯度的优化算法带来困难。
乙状结肠单位

sigmoid unit function
为了解决二元分类问题,我们将 Sigmoid 输出单元与最大似然相结合。一个 Sigmoid 输出单元有两个组件,一个是使用线性层计算 z = w*h+b,然后使用激活函数将 z 转换为概率。当使用其他损失函数时,例如均方误差,损失可以在任何时候饱和,即梯度可以收缩得太小而对学习无用。因此,最大可能性是首选。
Softmax 单位
Softmax 单元用于众多输出分布,它用于具有 n 个可能值的离散变量的概率分布,这也可以看作是代表二元变量概率分布的 sigmoid 函数的推广。Softmax 函数由下式定义:

Softmax unit function
像 Sigmoid 函数一样,Softmax 函数也可以饱和,即梯度可以收缩得太小,对学习没有用处。在 Softmax 的情况下,由于它有多个输出单元,只有当输入值之间的差异变得极端时,这些单元才会饱和。
这些单元受赢家通吃原则支配,因为总概率总是 1 且不能超过,其 1 的值越接近 1,则其他输出单元的输出值肯定越接近 0。
隐藏单位
选择隐藏单元的类型也是积极的研究,没有特定的单元可以保证它在每个问题上都优于所有其他单元,但我们仍然有一些单元在开始时是默认选择,例如,校正线性单元或通常称为 Relu 的单元是最常用的,这是由于直观的原因而不是实验性的,在现实中,通常不可能预先预测哪一个将工作得最好。选择隐藏单元需要反复试验,凭直觉判断某种隐藏单元可能工作良好,然后进行测试。
隐藏单元的可能选择有:
整流线性单位
这些函数使用由 g(z)定义的激活函数

Relus 易于优化,因为它们类似于线性单元,它们之间的唯一区别是它们一半域的输出为 0。它们如此著名的原因是,每当单位活动时,它们总是有一个恒定的大梯度。与引入二阶效应的激活函数相比,方向的梯度对学习更有用。
ReLU 有一个缺点,即它们不能通过基于梯度的方法学习,因为它们的激活是零。
Relu 有很多概括,这些是;
绝对值整流
泄漏继电器
参数 ReLU
最大输出单位
最大输出单元应用元素式函数 g(z),最大输出单元将 z 分成 k 个值的组。然后,每个最大输出单元输出这些组之一的最大元素。最大输出单元被认为是 ReLU 的最佳概括,因为它们具有冗余,这是由于每个单元都由多个过滤器驱动而导致的,这些过滤器帮助它们抵抗灾难性遗忘,在这种遗忘中,神经网络忘记如何执行它们被训练的任务。
逻辑 sigmoid 和双曲正切
逻辑 s 形由下式给出:

Logistic sigmoid
双曲正切由下式给出:

Hyperbolic tangent
这些单元密切相关,如:

The relation between the hyperbolic tangent and sigmoid
在 ReLU、之前,这些是神经网络最著名的选择,但现在它们的使用被忽略了,因为当 z 非常大时它们饱和到一个高值,当 z 非常小时饱和到一个低值,并且只有当 z 接近 0 时对它们的输入非常敏感。s 形单位的普遍饱和会使基于梯度的学习变得非常困难。
2019 年 CVPR 的少数镜头学习
TL;全民博士(?)来自 CVPR 2019 的几篇论文。这些是我最初为自己写的笔记,所以不要全信。有些论文我有幸在海报会议上与作者讨论过,有些我读过,有些我只是浏览了一下。如果我做错了什么,请让我知道。
背景
少镜头物体识别最近成为热门话题(从 CVPR18 的 4 篇少镜头论文到 CVPR19 的 20 篇左右)。通常的设置是你有许多例子的类别,你可以在训练时使用;然后在测试的时候,给你一些新奇的类别(通常是 5 个),每个类别只有几个例子(通常是 1 或 5 个;称为“支持集”)并从相同的类别中查询图像。
接下来,我试着把少拍法分解到不同的家庭。虽然这些族没有被很好地定义,并且许多方法属于不止一个族。
“较老的”建议方法基于度量学习,其目标是学习从图像到嵌入空间的映射,在嵌入空间中,来自相同类别的图像靠在一起,而来自不同类别的图像相距很远。希望它能适用于看不见的类别。
随后,出现了元学习方法。这些是以当前任务为条件的模型,因此使用不同的分类器作为支持集的函数。想法是找到模型超参数和参数,使得它将容易适应新的任务,而不会过度适应可用的少数镜头。
与此同时,数据增强方法也非常流行。我们的想法是学习数据扩充,这样我们就可以从少数可用的例子中生成更多的例子。
最后,基于语义的方法正在兴起。它受零触发学习的启发,零触发学习只基于类别名称、文本描述或属性进行分类。这些额外的语义问题在缺少可视例子时也会有所帮助。
本文涉及的论文
度量学习方法
【李】等.al; 现实场景中本地化的少镜头学习 ,Wertheimer 等。al; 【密集分类与植入】用于少镜头学习 ,Lifchitz 等。al; 变型原型-编码器:用原型图像一次性学习 ,Kim et al .艾尔。
元学习方法
元学习与可微凸优化 ,李等人。al; 边缘标记图神经网络用于少拍学习 ,Kim 等。al; 少镜头学习的任务不可知元学习 ,Jamal et al .al; 元迁移学习对于少投学习 ,孙等。al; 用 GNN 去噪自动编码器生成分类权重进行少拍学习 ,Gidaris et。al;【李】等。铝****
数据扩充方法
LaSO:多标签少拍学习的标签集运算网络 ,Alfassy et。al;【张】等。al;【Spot and Learn:一个用于少镜头图像分类的最大熵面片采样器 ,Chu 等。al; 图像变形元网络的一次性学习 ,陈等。铝
基于语义的方法
婴儿迈向多语义少镜头学习 ,Schwartz 等。al; 通过对齐的变分自动编码器 、Schonfeld et。al; 网:面向低镜头学习的任务感知特征嵌入 ,王等。al; 大规模少投学习:知识随班级等级转移 ,李等。铝
面目全非(其他任务的少量学习)
RepMet:用于分类和少镜头对象检测的基于代表的度量学习 ,Karlinsky 等人。al; CANet:具有迭代求精和专注少拍学习的类不可知分割网络 ,张等。铝
mini-ImageNet 基准测试的性能
只有相关的和被报道的报纸;根据单镜头性能排序

度量学习方法
再论基于局部描述子的图像到类的度量用于少镜头学习,李等。铝

在这项工作中,作者感到有点怀旧,回到了单词袋时代的本地描述符,但特征是用 CNN 提取的,一切都是端到端学习的。结果在基准的低端。
在现实环境中进行局部化的少镜头学习,Wertheimer 等人。铝

他们理所当然地声称标准的少量测试基准不现实,因为他们人为地平衡了类和测试 5 路任务,他们建议了一个新的数据集/基准。该模型学习同时定位和分类;明显的缺点是需要带有边界框注释的数据集。分类器基于原型网络,但是所使用的特征向量是聚集的前景和背景表示的串联。
针对少镜头学习的密集分类和植入,Lifchitz 等。铝

密集地执行分类,即,代替最终的全局平均汇集,所有空间位置都需要被正确分类。此外,他们不是在测试时微调最后一层,而是通过添加神经元来加宽每一层,并对其进行微调(只训练额外的权重,旧的被冻结)。
变型原型-编码器:用原型图像进行一次性学习,Kim 等人。艾尔。

这更像是一次性分类在标识或路标分类中的具体应用。在这种情况下,他们将图形图像(与野外的标识/标志形成对比)视为原型。并且他们通过学习将野外徽标/标志图像映射到原型图像的元任务来学习良好的表示。
元学习方法
可微凸优化元学习,李等。铝

这项工作是基准测试中的佼佼者之一(对于不使用语义信息的方法而言)。该方法非常简单,一个强大的骨干加上 SVM 分类器训练端到端。他们表明,即使只是使用主干网和原型网络也能达到很高的精度。SVM 使用也提高了性能。特征维数(分类器输入)保持得相当高,我想知道 SVM 是否能更好地处理这种高维度,这就是提升的原因。
边缘标记图神经网络用于少拍学习,Kim 等。铝

图形神经网络以前曾被用于少镜头学习。基本思想是,每个图像可以表示为图中的一个节点,并且信息(节点表示)可以根据它们的相似程度在它们之间传播。虽然通常根据节点之间的距离表示隐式地进行分类,但是这里作者建议每条边具有描述其节点之间相似性的显式特征。
用于少量学习的任务不可知元学习,Jamal 等人。铝
在这项工作中,为了避免元学习模型对训练任务的过度拟合,将正则化项添加到输出预测中。正则化或者迫使预测具有更高的熵(即,预测的概率不表现得像独热向量),或者迫使模型在任务之间具有更低的不平等(即,在不同的任务上表现得同样差或好)。显然,拥有一个强正则化对于少炮是重要的,但是我没有一个好的直觉为什么这些特殊的正则化项是需要的。他们在 MAML 上测试了这种方法,结果显示它的性能更好,看看在其他方法上使用这种方法是否也有帮助将会很有趣。
少镜头学习的元迁移学习,孙等。铝

该方法有两个主要元素:1 .微调预训练模型,其中权重被冻结,并且仅学习每层的比例和偏移 2。硬任务挖掘。如果我没有弄错的话,在 MAML,finetune 被应用于批处理规范层,它的效果不就和学习缩放和移位一样吗?当应用于 MAML 之上时,硬批量挖掘(从先前任务中精度较低的类组成一个任务)似乎也是有益的。
使用 GNN 去噪自动编码器生成分类权重,用于少量拍摄学习,Gidaris 等人。铝

此处,模型再次预测了未知类别的分类器权重。此外,所有分类器权重都经过去噪自动编码器(基本和任务新颖类),该编码器被实现为图形神经网络,因此 1。允许分类器适应当前的任务类别。从基本分类器向新分类器传播知识。使用去噪自动编码器是有意义的,因为它可以帮助固定预测的分类器,这些分类器是基于仅仅几个例子预测的并且明显有噪声。
【通过类别遍历寻找少镜头学习的任务相关特征】李等。铝

这项工作是基准测试中的佼佼者之一(对于不使用语义信息的方法而言)。给定特征提取器,该模型基本上预测特征向量上的注意力图。“集中器”查看每个类(或图像?我不确定),而“投影仪”将任务中所有类别的信息结合起来,生成注意力地图。“集中器”和“投影仪”都是作为一个小 CNN 实现的。我喜欢它是一个简单的模块,当在几个已知的基于度量的方法上使用时,它们显示出持续改进的性能。
数据扩充方法
LaSO:多标签少镜头学习的标签集运算网络,Alfassy et。铝

为这篇处理多标签少镜头分类的很酷的论文向我的同事们欢呼吧。这里,模型被训练为在嵌入空间中对多标签样本的标签集执行集合运算(并、减和交)。例如,通过对一只狗和一只猫的图像应用联合操作,我们得到了一个同时包含一只狗和一只猫的图像的表示。然后,这些操作用于扩充数据并提高分类性能。
显著性引导的样本幻觉少镜头学习,张等。铝

使用预先训练的(不相交类别上的)显著性模型来分割前景与背景。训练两个特征提取器,一个用于前景和背景,第三个模型用于组合它们。明显的缺点是,你需要一个预先训练好的显著性模型。我认为这是增加数据的一种很酷的方式,但是我不确定背景的增加对于正确分类(不同于检测)有多重要,如果你已经能够分割前景,那么只使用前景进行分类不是更好吗?
Spot and Learn:一种用于少镜头图像分类的最大熵面片采样器。铝

这里,不是学习整个图像的表示,而是使用 RNN 对每个片计算该表示,并且使用增强学习模型预测片上的最优轨迹(即,接下来应该使用哪个片的决定)来聚集该表示。我猜想,与简单的注意力模型相比,它更好,因为数据增加了(在同一张图像上使用不同的轨迹)。然而,基准测试的结果还可以。我想知道仅仅使用随机轨迹的增强效果是否会很好。
图像变形一步学习元网络,陈等。铝

一种非常酷的数据扩充方法。类似于“混合”,但是图像根据网格划分,并且对于每个单元使用不同的(预测的)混合系数。训练是端到端的,同时学习分类器和最优混合(在更好的分类意义上的最优)。它们与 mixup 相比较,但我想知道当系数是预测的而不是随机的时,mixup 对整个图像的作用有多好,即每个单元不同的 mix 有什么影响。
基于语义的方法
婴儿迈向多语义少镜头学习,Schwartz 等人。铝

不要脸的插上我在 CVPR 语言和视觉研讨会上发表的作品。我们建立在 AM3 模型的基础上。al,2019]并将其概括为使用多种语义。我们使用类别的简短文本描述(这些描述是 ImageNet 的一部分,但到目前为止还没有用于少数镜头)来提高性能。我们从视觉原型开始,用一系列语义嵌入迭代更新它们。通过这样做,我们在 miniImageNet 上观察 SOTA 结果。
通过对齐的变分自动编码器进行的广义零次和少次学习。铝

训练两个 vae,一个用于视觉特征,一个用于语义特征。目标是能够从潜在的视觉特征中重建语义特征,反之亦然。我以为这就足够了,但是他们表明,迫使两个潜在空间有相同的分布也有帮助。
-Net:面向低镜头学习的任务感知特征嵌入,王等。铝

在这项工作中,标签嵌入(手套)用于预测视觉特征提取器模型的权重。他们提出了一个分解权重的好技巧,因此只需要预测一个较低维度的权重向量。此外,语义嵌入和视觉嵌入之间的对齐是通过“嵌入损失”来强制的。这篇论文很有趣,因为它结合了两种方法——元学习(基于任务预测模型)和使用语义信息(标签)。然而,对于少杆任务,结果似乎低于 SOTA。
【大规模少投学习:知识转移与班级层级,李等。铝

据称,目前的方法在大规模的少数镜头上失败,即在 imagenet 上进行预训练,而不是在 mini-imagenet 中的 64 个基类上,并且不比朴素基线好。这里,再次使用了语义标签。他们使用标签嵌入来无监督地建立类层次结构,这是一种有趣的方法,据说学习以这种层次方式分类有助于模型捕捉在看不见的类上表现更好的特征。一个警告是,看不见的类标签被用来构建类的层次结构,这是不是有点欺骗?
面目全非(其他任务的少量学习)
RepMet:用于分类和少量拍摄对象检测的基于代表的度量学习,Karlinsky 等人。铝

不要脸的塞,再一次,塞到我合著的一部作品里。我们是第一批研究少量拍摄物体检测的。我们的解决方案是将基于度量的方法(如原型网络)扩展到检测。我们使用现成的检测器架构(FPN-DCN)并用基于度量的分类器代替(线性)分类器头,其中基于到已学习的类代表的距离来完成对每个提议区域的分类。我们提出了一个新的少数镜头检测基准,并显示了竞争方法的改进。
CANet:具有迭代细化和专注的少数镜头学习的类别不可知分割网络,张等人。铝

将度量学习扩展到少量镜头分割的密集情况。将查询图像中的所有局部特征与支持集中的对象上的所有局部特征进行比较是非常昂贵的。因此,他们选择将查询中的局部特征与支持集图像的全局表示进行比较。
收集数据时可以使用的一些技巧
在为我最新的 DS 项目收集数据时,我是如何解决一些问题的
几周前,我花了大量的时间从互联网上为我最近的项目收集数据(网络抓取、API 调用等等)。我将在以后的帖子中分享一个总结。第一天非常令人沮丧,因为我的代码总是失败,或者我没有得到预期的结果。在我的任务中遇到的问题的解决是由于几个小的修复。下面几段我和大家分享四个。
1-组织你的脚本

I consider each scripts executing specific tasks separate to keep my Jupyter Notebook clean and neat…
收集数据时保持有条理是很重要的。这有助于轻松找到你的错误所在。编写一段代码没有任何帮助。我建议养成两个习惯:保留代码注释和在笔记本上写代码(我用 Jupyter 笔记本,很少去其他 IDE 写 Python 代码)
一边写代码一边写注释是跟踪交付成果的一种方式。想象一下,两年后回到你的旧剧本,却不记得你以前想要达到的目标。清晰的评论真的很有帮助。此外,我们还为其他数据科学家编写代码(记住,Python 是一种‘免费’的编程语言)。通过注释我们的代码,我们使我们的同行变得容易。
关于 Jupyter 笔记本的意见有分歧,一些数据科学家不喜欢它,更喜欢使用其他 IDE。你的电话。在一天结束时,审查和执行短代码比长代码更可取。我在使用 Jupyter 时发现的主要优点是,我可以有几个脚本,而不是一个完整的块。我可以打印我的变量;检查我的数据帧的描述或很容易找到我的列表的长度。
在检查错误或调试时,对每一小块代码进行注释可以节省大量时间。同样,将执行特定任务的脚本放在同一个区域可以让我的 Jupyter 笔记本保持整洁。
2-使用功能

The function is only written once. It can be used multiple times to produce different outputs depend on the input.
函数是避免重复和更有效的好方法。在我的例子中,一旦我知道我的函数起作用了,我就有信心在数据收集过程的不同阶段使用它。我不用再写步骤了,远离了错误。例如,我不得不从同一个网站的不同部分或者去不同的页面收集一些数据。在那种情况下,我的数据收集结构并没有真正改变。我必须确定我将不得不多次执行的任务,并使用一个函数来使它对我来说更容易。
3-按需保存
在我第一次尝试抓取一些数据时,几分钟后我的脚本失败了。我丢失了所有被废弃的东西,因为我只是添加了一个收集到的数据列表。大错特错。想象一下,如果 2 或 3 个小时的数据收集没有了,我会有多沮丧。
我不得不经历的一个简单的解决办法是保存我得到的每一组新数据。在一个“for 循环”中,我正在 Pickle…Pickle 和 Json 在这方面非常有用。pickle 的主要优点是你可以保存任何东西:列表、Pandas 数据框、字典作为一个 pickle,并在以后使用初始数据类型中保存的项目。Json 格式只要求您稍后通过它来获取数据。
让我分享我在最近的项目中使用的一个脚本。客观地说,我从 Spotify 上收集歌曲。我在一个用于 API 调用的列表中有一个歌曲/艺术家的初始组合。出于什么其他原因,我应该在获得歌曲功能时保存它们?首先,Spotify 上可能找不到歌曲/艺术家的组合。这会导致错误并终止请求。其次,请求可能会超时。保存迄今收集的所有数据变得很重要。如果我遇到任何问题,我知道从哪里着手。

After each songs, my list containing all features had been appended and saved
4-用 try 捕捉错误…除了
在我的数据收集之旅中,我不得不求助的一个最佳解决方案是用‘Python try…except’处理错误。这个想法是让 Python 评估代码,根据某些条件处理错误,并根据评估和错误处理来执行代码。“尝试…例外”的三个步骤通过以下方式实现:
测试代码块错误的try块;不管 try- and except 块的结果如何,except块处理错误,而finally块执行代码。当错误发生时,或者我们称之为异常时,Python 通常会停止并生成一条错误消息。这些异常可以使用“try”语句来处理。
在数据收集过程中,我如何使用异常?基本上,我没有等待错误来破坏我的代码,而是让 Python 捕捉错误并继续收集数据,即使没有找到请求的歌曲。
使用 Python 收集数据时,有不同的方法来处理类似的问题。我分享了一些与我的情况相关并且非常有帮助的例子。如果你有任何其他建议,欢迎在评论中与我分享,或者在 LinkedIn 或 Twitter 上与我联系,我们可以讨论。
FGO·斯泰尔根:这种英雄精神并不存在

Sample outputs from FGO StyleGAN. Also here is a the link to view it for free
当我第一次看到 Nvidia 的 StyleGAN 的结果时,我觉得它看起来像一堆黑色的魔法。我在 GANs 领域的经验不如深度学习的其他部分,这种经验的缺乏和我真的缺乏 GPU 火力来培养自己的风格的想法阻碍了我更快地投入进来。对于规模,在 StyleGAN github 上,Nvidia 列出了 GPU 规格,基本上是说在 8 个 GPU 上从头开始训练需要大约 1 周时间,如果你只有一个 GPU,训练时间大约为 40 天。因此,运行我的一个 GPU 设备 40 天,从时间和我的电费来看,听起来很可怕。有了这些限制,我暂时放下了培养一名时尚达人的雄心。

FGO StyleGAN outputs
信仰的飞跃:自定义 FGO StyleGAN
虽然我和其他人一样喜欢黑魔法,但我也喜欢理解正在发生的事情,尽可能地揭开事物的神秘面纱,并构建自己的事物版本。
几周前,我的一个队友在 LinkedIn 上给我发了一些视频,视频中时装模特以一种视频风格相互变形,我认为这是 StyleGAN 的一种应用。深入调查后,我发现自从我上次查看以来,在 StyleGAN 周围的社区中已经进行了很多工作。就我个人而言,这些天我在 Pytorch 中做了很多工作,但是我认为当你试图将研究应用到你自己的项目中时,通常最简单的方法是使用研究中使用的任何工具。在这种情况下,虽然有一个 Pytorch port 看起来功能相当不错,但最好的做法是使用基于 Tensorflow 的代码,这项研究是用 Nvidia 的开源的代码完成的。
然而,是一个名叫 Gwern Branwen 的人制作了一个网站这个外服并不存在。坦率地说,如果我没有看到 Gwern 关于他们如何走过他们的 S tyleGAN 的帖子,并好心地为一个以 512x512 分辨率训练的基于动漫的 StyleGAN 提供预训练的重量,我就不会真的花时间和资源来训练 StyleGAN。
Gwern 展示了基于动画的 StyleGANs,他们使用自己提供的重量训练或由他人训练。虽然我的项目与那里的项目相似,但社区中有人训练了一个“剑脸”StyleGAN,这篇文章中的 StyleGAN 是一个普通命运大令 StyleGAN。
GAN 背景简介
生成对抗网络(GAN)是深度学习的一个有趣领域,其中训练过程涉及两个网络:生成器和鉴别器。生成器模型开始自己创建图像,它从随机噪声开始,而鉴别器通过查看训练示例和生成器输出来给出反馈,并预测它们是“真”还是“假”。随着时间的推移,这种反馈有助于生成器创建更真实的图像。
StyleGAN 是对英伟达之前一款名为 ProGAN 的型号的改进。ProGAN 经过训练可以生成 1024x1024 的高质量图像,并通过实施渐进的训练周期来实现这一点,在该周期中,它以低分辨率(4x4)开始训练图像,并通过添加额外的层来随着时间的推移提高分辨率。训练低分辨率图像有助于使训练更快并提高最终图像的质量,因为网络能够学习重要的较低水平特征。然而,ProGAN 控制生成图像的能力有限,这正是 StyleGAN 的用武之地。StyleGAN 基于 ProGAN,但增加了生成器网络,允许控制三种类型的功能。
- 粗糙:影响姿势、一般发型、脸型等
- 中:影响更精细的五官、发型、眼睛睁开/闭上等。
- 精细:影响配色方案(眼睛、头发和皮肤)和微观特征。
这只是对 StyleGAN 的简单描述,更多信息请查看论文或其他关于媒体的文章。

This one shows a few male faces. However a lot of them turn into super evil looking images? Maybe guys are just evil? who knows. This one also shows a number of lower quality generated images probably due to me not removing low resolution images properly when I created the dataset.
数据集构建和准备
为了让 StyleGAN 运行起来,最困难的部分是决定我想如何处理这个问题,并获得一个格式正确的数据集。我在前进的道路上犯了一些错误,我也将走过这些错误。根据最初的论文和我看到的许多其他风格,我决定制作一个头像数据集。至于话题,我手头仅有的足够大的数据集与《FGO》有关。






I used a previously built Tensorflow Object detector to crop out the heads from around 6K FGO wallpaper/fan art images.
我使用的 FGO 数据集由大约 6000 张不同尺寸的壁纸、游戏图片、粉丝艺术等图片组成。我通过一个基于 Tensorflow 的 FGO 调谐头部检测器在这个数据集上提取了大约 8K 个头部。完成后,我用手快速检查并清理了数据集,将最终数据集削减到大约 6K 头。我学到的一个教训是,我应该在过程的这一部分花更多的时间,因为有许多质量较低的图像,还有背景、盔甲、非人物图像的图像留在数据集中,这导致生成的图像中出现奇怪的伪影或只是质量较低的图像。
像其他 Tensorflow 网络一样,StyleGAN 依赖于使用 tfrecord 文件,并且可以由 StyleGAN repo 中的 dataset_tool.py 文件生成,用于训练。训练 StyleGAN 的一个重要注意事项是,数据集中的图像需要与用于预训练权重的 StyleGAN 具有相同的大小和相同的颜色格式。所以对我来说,使用 1024x1024 的原始网络很困难,因为找到那个尺寸的动漫头像有点困难,我的 GPU 可能无法处理它。这使得 Gwern 训练的 512x512 网络非常有吸引力,因为它更容易找到我可以获得接近该分辨率的头部的图像,并且我的 GPU 可以更容易地处理它。我把所有的图片都转换成 sRGB 图片,并把它们的尺寸调整到 512x512。
为了调整大小,我使用了 Python 的 PIL 库进行处理和重新格式化。一旦完成,我就准备开始漫长而艰苦的过程,根据我的 FGO 用例对 StyleGAN 进行微调。
培养
虽然从预训练的重量开始很有帮助,但训练绝不是一个短暂的过程。毕竟,在我的 1080 卡上花费了一周多一点的 GPU 时间(约 180 小时)后,我停止了训练,这相当于约 85 个“滴答”,这是 StyleGAN 用作纪元的 30K 图像周期……因此它生成了 250 万张 FGO 图像。

Images generated during training for 85 ticks. A few boxes were based on low quality of non headshot images so they just never developed well. However it is interesting to see the model trying to generate headgear (to varying success) as well as a few male characters who show up and I think have cool overly dramatic facial features and hair
StyleGAN 的体能训练过程由 Nvidia repo 中的 2 个脚本控制。为此,我主要遵循了 Gwern 在他们的博客中提出的建议。
培训/training_loop.py
要调整的代码的主要区域从第 112 行开始。本节设置了许多默认参数,因为 StyleGAN 不接受命令行参数,所以必须在这里设置它们。

要设置的主要参数之一是 resume_kimg 参数。如果它被设置为 0,那么 StyleGAN 将在看似随机的初始化开始训练。因此,我将它设置为 resume_kimg=7000 在这个级别,斯泰勒根能够利用预训练的重量,并在一个比其他情况好得多的点开始。

我遇到了 OOM 问题,并最终追踪到它与第 266 行的度量行有关,所以我最终只是注释掉了它。到目前为止,我还没有遇到其他问题。
Train.py
有两个地方需要调整,第一个是指定使用哪个数据集的地方。为此,我添加了我准备好的名为“脸”的数据集。
desc += '-faces'; dataset = EasyDict(tfrecord_dir='faces',resolution=512); train.mirror_augment = True

第二个方面是详细说明培训过程。因为我有 1 个 GPU,所以我将 GPU 的数量设置为 1,并指定每个迷你批次应该有 4 个图像。之后是一些未调整的学习率调度和总数为 99000K 的图像。这实质上设置了模型将训练多长时间。
未来的改进
对于这一个,我认为我可以在两个方面改进我的工作流程。一个是硬件,这是一个非常耗时的项目,如果我决定做更多的 GAN 工作,我认为升级到我的主 GPU 装备将是一个很好的生活质量改善。其次是数据集质量。我没有彻底清理我的数据集,并为此付出了代价,我认为这是一个很好的警示故事。
五金器具
这个项目是我经历过的时间更密集的项目之一,所以坦率地说,它让我考虑是否值得升级我的计算资源。为此,我可以采取两条主要路线,要么使用更多的云资源,如 AWS,要么物理升级我的 GPU。
虽然 AWS 可能是更容易的方法,但我认为通过我所做的大量培训,我会很快产生足够的 AWS 费用,让我希望我升级了我的 GPU。社区中的人们提到,构建自己的 GPU 平台比使用 AWS 等资源大约便宜10 倍。
对我来说,花数百美元来测试一种风格似乎很可怕。
因此,我目前的主要想法是升级到 1080 ti ~ $ 800–1000,或者如果我真的想全押,一次性成本约$1300 的 2080TI 卡应该会有收益。如果任何人对此有想法,请随时告诉我!
两者都应该有助于减少所需的训练时间,因为它们比我目前的主 1080 GPU 快得多。

More selected outputs
数据集质量
与数据科学中的所有事情一样,数据集的初始质量和清理工作可以决定一个项目的成败。为了这个项目,我加快了清洁过程,以便快速训练初始风格。我觉得这很好,但也是我的一个错误。如果我花更多的时间来清洁,结果可能会好得多。
在我的辩护中,我第二天要飞去参加一个咏春研讨会,我想早点开始练习咏春,而不是失去我离开时可以得到的 48 小时的训练时间。
一些初步想法,去除低质量图像,去除非头像图像,去除重复。
去除低质量图像的一个简单过程是删除低于某一分辨率的图像或者没有高于某一截止值(比如 300 像素)的边的图像。
去除非头部图像有点复杂,但可以用 CNN 和一个相当简单的标记头部和非头部图像的过程来完成。但是它需要额外的时间来构建。
重复的图像可能对这个过程没有太大帮助,但我也不确定这会有多严重?我为这个项目建立了一个数据集,使用了 google image pulls 来收集一些与 FGO 相关的术语和字符。这个过程会产生大量的副本。例如,如果我搜索类似“FGO 军刀”的东西,这将会给我“军刀类”或通常被称为“军刀”的 FGO 人物。接下来,如果我搜索“for 阿尔托莉亚·潘德拉贡”,他通常被称为“军刀”,我会得到很多重复的图像。
我可以使用感知哈希之类的东西对数据集进行重复数据删除,使用 ImageHash 库,这是我为工作或 CNN 做的,效果也很好。
到目前为止,我认为最重要的事情是确保图像的质量足够高,StyleGAN 将学会制作好的 512x512 图像。希望对高分辨率数据集进行仔细的手动修剪可以去除大多数非头部图像,复制可能会使 GAN 倾向于制作更多的这些字符,但目前对我来说这似乎不是最糟糕的问题。

One of the things I was happy about is that the GAN learned to generate Jeanne’s head piece that you can see appear for a bit in both of these examples
结束语
正如我之前所说,这是我参与过的计算量更大的项目之一,但结果非常有趣,我想再做几个。从最初训练 from 斯泰勒根中学到的经验应该有助于理顺这些过程。
在看这些的时候,我有一个有趣的想法,那就是我是否可以在计算机视觉问题中使用 StyleGANs 进行数据增强。Gwern 指出,其他人已经在小于 5K 样本的训练模型上取得了相当大的成功,而 FGO·斯泰勒根也取得了类似的成功。这对于样本相对较少的领域非常有用,并且可以通过 NVIDIA 添加到 StyleGAN over ProGAN 中的功能控件来控制样本的生成。虽然这种方法的可行性还有待观察,但至少它可能很酷,而且可能会被宣传为 R&D 的工作项目。谁知道呢?
与此同时,我将享受为这个 FGO 风格产生更多的输出样本和图像。我目前有第二个 StyleGAN 训练,我使用动画重量作为人物照片数据集的起点。我不确定结果会如何,但我会在适当的时候分享这些结果。
随意查看 Git 回购这里

Both of these showcase some of the issues I had where low resolution or non head images were in the dataset so it rotates through some weird/creepy stages.
虚拟自我游戏
强化学习向自我游戏迈进了一步

Photo by Markus Spiske on Unsplash
更新:学习和练习强化学习的最好方式是去 http://rl-lab.com
介绍
在零和游戏中使用强化学习需要一些比标准的虚拟游戏更复杂的方法。标准虚拟游戏用于不考虑时间的标准形式游戏。要将强化学习应用于零和游戏,需要另一种方法。这篇文章是基于约翰尼斯·海因里希&大卫·西尔弗的论文“广泛形式游戏中的虚拟自我游戏”。
扩展形式游戏
正常形式的游戏被建模为一个表格,其中每个玩家的行动(称为策略)是行和列的标题,每个单元格的内容是每个玩家采用的策略的收益。
这种形式的问题是它没有捕捉到序列或时间。出于这个原因,扩展形式游戏使用树状表示,其中每个节点是玩家,每个边是策略,并且可以清楚地看到从一个节点到另一个节点的顺序。

请注意,对于每个扩展形式的游戏,我们可以找到一个正常形式的游戏,产生相同的结果。
学问
学习的目的是找到该玩什么策略才能达到纳什均衡,这样我们才不会输(平均而言)。
换一种方式说同样的话,学习的过程就是找到使用每一个策略(或行动)的概率(或概率分布),这样我们平均起来就不会输。
ε-最佳对策
在标准的虚拟游戏中,每个玩家都将寻求对对手行动的最佳反应。
最佳对策是指在当前情况下能带来最佳价值或回报的策略。认为 R 是对最佳反应的奖励。我们将ε-最佳反应定义为导致奖励 r 使得 r ≥ R-ε 的反应集合。
广义弱化虚拟游戏
广义的弱化虚拟博弈是一个迭代过程,使用当前策略计算混合策略∏ ,ε-最佳对策 bɛ ,以及其他一些扰动 m

从这种方法中得到的重要收获是它在两人零和游戏的情况下收敛。这种方法的原因稍后会更清楚。
莱斯利·柯林斯写的一篇论文中可以找到广义弱化虚拟游戏的细节。
广泛形式的虚拟游戏(XFP)
广泛形式的虚拟游戏采用广义弱化虚拟游戏的概念来迭代计算纳什均衡,因此在两人零和游戏中,XFP 收敛。

但是,有一个问题。
XFP 饱受维度诅咒之苦。在每次迭代中,游戏的所有阶段都需要进行计算。这将导致指数计算。
为此,强化学习开始发挥作用,以创造虚拟的自我游戏。
虚构的自我游戏(FSP)
考虑一个扩展形式的游戏。在他们的论文中,Heinrich 和 Silver 证明了扩展形式的博弈是由马尔可夫决策过程(MDP)定义的,因此它适合于强化学习方法。我们称之为虚构的自我游戏(FSP)。
请记住,XFP 使用最佳对策和策略更新的计算,但这种计算是指数级的。FSP 所做的是用机器学习方法取代这两种计算,如下所示:
- 最佳反应:FSP 使用强化学习方法逼近最佳反应。顾名思义,近似意味着最佳响应不是计算出来的,而是与之接近的东西,即ε-最佳响应。
- 策略更新:FSP 将平均策略更新为一项监督学习任务,每个玩家学习自己行为的转换模型。
重要细节:
确定 FSP 符合广义弱化虚拟对策(记住我们需要收敛性质)。已经证明,数据的采样应该是这样的,即以概率η从最佳响应中采样,以(1η)从当前策略中采样。
这可以从下面算法的generated data方法中看出:
σ←(1η)π+ξβ

算法解释
- 游戏的情节是从代理人的策略中模拟出来的,使用生成数据的方法。
- 数据以当前状态、动作、下一个奖励、下一个状态的过渡元组集的形式存储。

- 由此产生的经验或数据存储在两种代理大小受限的 FIFO 存储器中:
- Mʀʟ存储代理对手行为的经验。换句话说,它模拟了代理人面对的“环境行为”。这就需要使用强化学习算法来学习最佳对策。
- Msʟ存储代理自己的行为,并用于监督学习以更新代理策略。
- 每个代理通过从 Mʀʟ.学习偏离策略来计算近似最佳响应
- 每个智能体通过监督学习记忆 Msʟ.来更新自己的平均策略
实验
在两个参数化零和不完全信息博弈中对这两种算法进行了评估。Leduc 德州扑克和 River poker。
在两人零和游戏中,策略组合的可利用性π被定义为δ = R1 + R2
其中 R1 是参与人 1 的最佳对策的报酬,R2 是参与人 2 的最佳对策的报酬。
理想情况下,纳什均衡的δ应该趋于零。
图 2 显示,在 6 张牌和 60 张牌的 Leduc Hold'em 扑克中,XFP 的表现明显优于 FSP。

在图 3 中,在两次河牌游戏中,FSP 改进其平均策略的速度比全宽策略快得多。在扑克游戏中,FSP 在 30 秒后获得 0.11 的可利用性,而 XFP 在 300 秒后获得 0.26 以上的可利用性

结论
Heinrich & Silver 认为,XFP 是一种虚拟游戏算法,它保持了在具有虚拟游戏属性的游戏中保证的收敛性。然而,它遭受了维数灾难。
FSP 是一种基于样本的方法,在机器学习框架中实现广义弱化虚拟游戏。在每次迭代计算预算有限的情况下,能否保证收敛仍然是一个悬而未决的问题。
相关文章
战斗行动预测者(又名 FAP)

Be water, my friend
对于那些没有打过太多次仗的人来说,了解对手是至关重要的。如果你做得好,你几乎肯定会赢。但是如果你做错了,你可能会发现你在地上的速度比你打字的速度还快(“Hello World”)。
本文将介绍我用来创建卷积神经网络模型的方法,该模型可以检测战斗动作并在以后预测它们。
模型的输入是几个人挥拳的简短视频(这个简化的模型只使用了刺拳和左钩拳,尽管可以很容易地添加更多的动作)。
1.采取的方法
首先,我必须收集一个视频数据集,其中包括我想要的武术动作(考虑到我必须为特定动作编辑每个视频,这并不太容易)。在花了相当多的时间在网上找到几个视频并对它们进行剪辑后,我选择从不同的角度拍摄自己的每个动作(别担心,我是专业的)。对于这个危险的任务,我用手机的摄像头记录下来,我女朋友的猫作为观众。如你所见,这只猫很享受。

A thrilled cat
2.使用 Python 的魔力来编辑视频
我用 Jupyter Notebook 给文件重新命名(给 Jab0,Jab1… Hook0…),把它们放在相应的文件夹里,并把它们整理成框架。由于我的电脑没有我需要的那么快,我在建模项目的剩余部分使用了 Google Colab。

3.Google Colab
我安装了我的驱动器(没有双关语的意思),并使用了谷歌的神话般的 GPU。然后,我采取了 CNN 通常的步骤——导入库,标记类,为训练/评估进行分割。、定义模型、创建生成器、设置一些参数(提前停止等。),最后拟合模型。
我运行了 25 个时期的模型,并获得了以下结果。

Don’t you just love graphs?
4.预言;预测;预告
正如你所看到的,这个模型惊人的准确。


4.思想
最初我计划在这个数据集上使用 Inception V3 模型来节省一些时间。Inception V3 模型是一个非常好的图像识别模型,在 ImageNet 数据集上进行了训练。然而,在看到 25 个周期后的结果后,我意识到这可能有点过头了。
5.未来的改进
这个项目只是我今天早餐前做的一个有趣的活动。有很多方法可以改进它,增加它,并用它创造出真正奇妙的东西(或危险的东西)。

What would you do?
6.谢谢
感谢女友的猫对我的表现评价很大(一如既往),鼓励我远离他珍贵的安息之地。还要感谢把猫借给我,鼓励我运动的女朋友。
完整的代码可以在我的 GitHub 上找到,还有许多其他适合全家人的有趣活动。
直到下一次,
山猫。
用数据应对客户流失
到底是怎么做到的
用数据推动客户参与和保持

Fighting Churn With Data?
这篇文章是关于使用数据和分析技术减少客户流失,提高客户参与度和忠诚度的系列文章的第一篇。该系列基于我正在写的一本书的内容,书名也是用数据对抗流失:我将从这本书中提取这些帖子的关键要点,并为这本书留下细节,包括代码。(如果你想了解更多细节,这本书已经在有了电子版,完整的印刷手稿将于 2020 年初出版。本书完成部分的代码在 github 上有。)
1.什么是客户流失,你为什么反对它?
任何产品或服务的主要目标都是增长。当通过营销和销售增加新客户时,增长就会出现,但当客户离开时,增长就会抵消,甚至会导致收缩。对于提供此类服务的企业而言,客户退出服务被称为“流失”。大多数服务提供商专注于收购,但要取得成功,服务还必须努力减少流失。如果客户流失得不到解决,一项服务将无法发挥其全部潜力。
术语“流失”实际上起源于“流失率”:流失率是指在给定时期内离开的客户的比例,这将在下面更详细地讨论。这导致客户或用户群随着时间的推移而变化,这就是为什么术语“搅动”是有意义的:因为单词“搅动”最初的意思是“剧烈地移动”,就像搅动黄油一样。但在商业语境中,术语“客户流失”现在也用作动词:“客户正在流失”;“客户搅动了”;作为一个名词:“客户是一个客户流失者”。如果你更愿意看到半满的杯子,没有从某项服务中流失的客户也可以从积极的角度来看待。在这种情况下,人们谈论“客户保留”。
下面的草图总结了应对客户流失的典型情况:

Schematic of How Fighting Churn With Data Works
这些是关键组件:
- 通常(但不一定)在订阅基础上提供的产品或服务
- 与产品交互的订户(或用户)
- 订户通常签订订阅以接收产品或服务。订阅通常(但不总是)会产生金钱成本。替代方案包括应用内购买和添加支持的产品。
- 订阅可以终止或取消,称为流失。一些订阅必须定期更新,而另一些则只是持续到用户选择流失。
- 订阅(或其他购买)的时间、价格和付款正在某种数据库中被捕获。
- 当用户使用产品或服务或与之交互时,这些事件被跟踪并存储在数据仓库中。
考虑到这些因素,数据流失的应对方式通常是这样的:
- 订阅数据用于识别客户流失和创建客户流失指标。流失率是流失率指标的一个例子。流失数据库还允许识别流失和更新的订户的例子以及他们这样做的确切时间,这些都是进一步分析所需要的。
- 事件数据仓库用于创建总结与每个订户相关的事件的行为指标。行为度量是允许数据仓库中的事件被解释的关键步骤。
- 已识别的流失和更新的行为指标在流失分析中一起使用。流失分析以严格的方式识别订户的哪些行为是更新的预测,哪些是流失的预测,并且可以为每个订户创建流失风险预测。
- 基于他们的特征和风险,用户被分成组合了他们的风险水平、他们的行为和任何其他重要特征的组或段。这些细分的目的是针对他们进行干预,旨在最大限度地延长用户寿命和服务参与度。干预措施可以包括电子邮件营销、电话营销、培训和折扣。另一种类型的长期干预是对订阅产品或服务本身的改变,并且来自流失分析的信息对此也是有用的。
2.为什么流失很难对抗
有那么难吗?如果你已经是一个数据科学家,你可能会看到这是怎么回事,并认为这应该是小菜一碟。嗯,有几件事让你变得与众不同。
2.1 客户流失难以预测
即使使用最新的机器学习技术,客户流失也很难预测。如果你考虑一下自己上次取消订阅的行为,就很容易理解为什么预测客户流失很难:你可能很长时间没有充分利用订阅,但你花了这么长时间取消订阅,因为你太忙了,或者你花了一些时间研究替代方案,或者你只是忘记了。如果数据人员或分析系统在这段时间观察你的行为,它会将你标记为风险,并在你下定决心并找到时间取消计划的整个时间内都是错误的(从你没有流失的意义上来说)。变动的时刻是由太多无法预测的外部因素决定的。
更一般地说,对于那些希望减少客户流失的人来说,许多重要的方面通常是不可知的。考虑以下附加信息,如果您知道这些信息,它们将有助于预测客户流失,但您可能永远也不会知道:
- 顾客的支付能力通常是未知的。同样,对于免费(feed 或广告支持)产品,用户在这类内容上花费的时间和注意力是未知的。
- 与用户已经采用的任何替代解决方案(包括但不限于竞争产品)一样,到替代服务或实现相同目的的手段的转换成本也常常是未知的。)
- 服务的效用或享受是一种基本的主观体验,因人而异,即使在完全相同的情况下也是如此。这对于消费者服务尤其重要,因为消费者服务的流失率很难预测。当决定更新或取消服务时,企业倾向于使用更合理的成本效益分析,但即使这样也会受到主观因素的影响。
如果你在一个项目中预测到了客户流失,并且发现很容易预测到很高的准确度,那么你可能预测客户流失太晚了,因为这是不可能停止的——我将在以后的文章中讨论这个问题。
2.2 流失更难预防
准确预测客户流失很难,但防止客户流失更难。原因很简单:为了以长期可靠的方式防止客户流失,服务必须实际提高服务带来的收益,或者降低使用服务产生的成本。想一想,怎样才能让你不再重复使用你过去用过但现在不再使用的产品:更好的内容和功能会让你不再重复使用吗?也许吧。更低的价格会让你停止生产吗?也许吧。一个改进的用户界面怎么样——这会让你停止鼓捣吗?可能不会,除非用户界面一开始就很糟糕。来自该产品的更频繁的电子邮件和通知会让你停止搅拌吗?肯定不是!
因此,要减少用户流失,你确实需要增加用户获得的价值,但这比让人们首先注册一项服务要困难得多。因为这些人已经确切地知道服务是什么样的,所以通过营销或销售代表做出的承诺不太可能获得很大的吸引力。正如稍后将详细讨论的,可用的最佳选项之一是试图确保订户利用已经可用的服务的所有最佳方面,但是这有局限性。
作为数据人员,你可能需要“银弹”来减少流失,但坏消息是:
没有减少客户流失的灵丹妙药
当然,另一种选择是降低服务成本。但降低货币成本是付费服务的核心选择——收入流失或降价销售可能比完全和全部流失要好,但它仍然是流失。
警告降价是对付流失的“钻石子弹”:它总是有效的,但你负担不起。
3.如何应对客户流失
3.1 防止客户流失是企业的工作
防止流失并不是数据人员的工作,或者至少不是数据人员一个人能够完成的。过去几年,人工智能和数据科学取得了显著进步,但在很大程度上,防止流失需要与客户或用户进行人性化接触。考虑以下能够真正减少流失的角色示例:
- 产品经理和工程师(针对软件)以及制作人、人才和其他内容创作者(针对媒体)通过改变产品功能或内容来提高用户从服务中获得的价值或乐趣,从而减少客户流失。这是减少客户流失的最主要或最直接的方法。软件中的一个相关方法是试图增加“粘性”,这大致意味着通过提供难以复制或设计为难以转移的有价值的功能,修改产品以增加客户转向替代产品的转换成本。
- 营销人员通过精心策划有效的大众传播,将用户引向最受欢迎的内容和特色,从而减少用户流失。这实际上更多的是一种营销教育功能,而不是销售功能:记住,用户已经有了访问权限,知道服务是什么样的,所以承诺不会有帮助。尽管如此,这一职能通常由市场营销人员承担,因为他们有能力打造有效的大众传播。
- 客户成功和支持代表确保客户接受产品,并在他们无法接受时给予帮助,从而防止客户流失。客户支持是传统上帮助客户的部门,但客户成功在许多组织中是一个新的独立职能,其明确设计为比客户支持更主动。当客户寻求帮助时,客户支持会帮助客户,而客户成功会尝试监控客户,发现客户何时需要帮助,并在客户请求帮助之前联系客户。“客户成功”还负责“接纳”客户或确保他们采取一切必要措施开始利用订阅。因此,如果有一个客户成功团队,它通常是数据人员每天最密切合作的团队。数据人员通常会帮助设计指标或模型,客户成功代表将使用这些指标或模型来检测陷入困境的客户。此外,当营销教育活动通常与客户成功紧密配合时,客户成功部门可能会为此设计内容,而营销的角色将只是使其看起来不错,并管理分发内容的电子邮件活动(等等)。**
- 销售部门的客户经理(如果有的话)可能是阻止客户流失的最后手段,假设这项服务有金钱成本的话。客户经理是实际上可以降低价格或更改订购条款的人,他们管理着客户降售更便宜版本的过程。在没有销售部门的付费消费者服务中,这一角色通常由拥有类似权限的高级客户支持代表承担。虽然我们将销售的角色描述为防止客户流失的“最后手段”,但在许多组织中,更主动的方法是首先“确定合适的销售规模”,这意味着更好地销售最适合客户的产品版本,而不是销售最昂贵的版本。这可能会损害每次销售对服务的短期收益,但如果处理得当,将会减少客户流失,并最终提高每个客户的平均终身价值。这也可能损害客户经理的销售佣金,因此可能需要调整销售补偿系统(这是一个复杂的主题,超出了本书的范围。).
这些角色因产品和组织的类型而异,我的描述只是概括。但关键是,最终减少客户流失取决于采取行动影响客户,而这些行动通常是由不同业务部门的专家采取的,而不是由争论数据的程序员采取的。
这些角色显然非常多样化,由于没有更好的术语,我将所有这些功能称为“业务”。这并不意味着数据人员不是公司的一部分,但数据人员通常对具体的业务成果(如收入)没有直接的责任,而其他角色的人通常有。从数据人的角度来看,“业务”是流失分析结果的最终用户。
3.2 数据在支持业务中的作用
由于上面提到的所有因素,业务人员需要帮助他们对抗客户流失的是一套简明的事实或规则来理解客户参与。这些规则需要对业务具有可操作性,这意味着它们知道如何根据调查结果减少客户流失。这反过来要求你发现的事实是客户流失和参与的真正原因或驱动因素。这些事实通常表现为客户行为与流失或保留之间的关系。举个简单的例子,您可能会发现这样的业务规则:“每月使用(查看)功能 X 超过五次的客户流失率是每月使用不到两次的客户的一半”。像使用或查看某个特定功能的更多部分这样的事情可能看起来并不复杂,但是只要它是对数据的正确读取,它就真的很有用。企业的每个部分都可以不同地使用这样一个事实:产品创造者将知道 X 提供的价值,并改进它或使它更像它。营销部门可以设计一个活动来吸引用户使用该功能。当客户成功/支持人员与客户交谈时,他们可以询问客户是否正在使用该功能,如果没有,鼓励客户尝试一下。
这种类型的分析不是当今媒体和大学教育中最受关注的那种人工智能算法。这可能会让一些读者失望:为了减少流失,部署一个能够赢得数据科学预测竞赛的“人工智能”系统是不够的。如果你试图交付一个预测客户流失的分析,而没有用具体的规则来解释它,企业将不能容易地使用它,所以他们可能根本不会使用它。虽然有一些技术可以使黑盒机器学习模型更具可解释性,但我将向您展示,您可以通过更直接的方法得出更好的结论。
通过使用简单的方法,您可以真正地让业务人员成为调查过程的一部分,获取他们更多的领域专业知识,并给予他们对结果更大的信心和更好的解释能力。客户流失的预测模型可能是有用的,但是当预测是从数据团队到企业的调查和知识转移计划的自然延伸时,它们更有用。
4.案例研究:重要的度量标准(对抗流失的武器)
在本节中,我将介绍
4.1 Klipfolio 的活跃用户和许可证使用情况
Klipfolio 是一款数据分析云应用,用于构建和共享实时业务仪表盘。这些指示板可以由多个用户创建,对于允许多个用户使用一个订阅的任何产品,一个通用指标是活动用户的数量。下图展示了 Klipfolio 客户每月活跃用户数量与客户流失之间的关系。

Behavioral Cohort Churn vs. Klipfolio’s Active Users
上图使用了一种称为行为群组的技术来显示行为和流失之间的关系。你将在下面和书中看到很多这样的图,并学习如何创建它们,现在我将简要解释它是如何工作的:给定一个客户池和一个行为指标,如每月活跃用户数,客户根据他们对该指标的测量被组织成群组。通常使用十个群组,因此第一个群组包含该指标下的 10%客户,第二个群组包含接下来的 10%,直到最后一个群组包含该指标下的前 10%客户。一旦群体形成,你就可以计算出每个群体中有百分之多少的顾客流失了。结果显示在类似上图的图中:图中的每个点对应于一个群组,该点的 x 值由群组中所有客户的指标平均值给出,该点的 y 值由群组中客户流失百分比(即流失率)给出。
关于群组图,我应该提到的另一件事是,它没有显示实际的流失率,只显示了群组之间的相对差异。这是因为流失率是一个非常重要的运营指标,在案例研究中揭示公司的真实流失率是不合适的。类似地,书中没有一个行为群组流失率图用 y 轴上的标签显示实际流失率。但是,行为群组图的底部总是被设置为零流失率,因此这些点与群组图底部的距离可用于比较相对流失率。例如,如果一个点与另一个点之间的距离是图底部的一半,这意味着该群组中的流失率是另一个点的一半。
转到流失群组图的细节,它显示最低群组每月有不到一个活跃用户(多个月的平均值),最高群组每月有超过 25 个活跃用户。每月活跃用户最少的群组的流失率大约是活跃用户最多的群组的 8 倍。同时,流失率的大部分差异发生在每月 1 到 5 个活跃用户之间。
虽然衡量活动用户的数量是应对客户流失的一个很好的指标,但下图显示了一个更好的指标:这是通过将活动用户的数量除以用户购买的座位数量计算出来的许可证利用率指标。许多 SaaS 产品是按“座位”销售的,这意味着允许的最大用户数(这称为许可座位数)。如果活动用户数除以许可的席位数,则得出的指标衡量客户对席位许可证的利用率百分比。

Behavioral Cohort Churn vs. Klipfolio’s License Utilization (active users per seat)
上图中的结果显示,许可证利用率是一个非常有效的应对客户流失的指标:许可证利用率最低的群组的平均利用率略高于零,而最高群组的许可证利用率约为 1.0。最低群组的流失率大约是最高群组的 7 倍,并且不同群组之间的流失率或多或少是连续变化的——与活跃用户群组数量的流失率相反,实际上不存在更高利用率不再产生影响的水平。这使得它比单独的活跃用户更有效地区分流失风险。正如后面的帖子和本书将进一步解释的那样,每月活跃用户的原因实际上合并了两个不同的与流失相关的潜在因素:向客户出售了多少座位,以及典型用户活跃的频率。利用率是衡量用户活跃度的指标,与售出的座位数量无关,通常对于根据客户参与度和流失风险对客户进行细分非常有用。
4.2 广义的促进者和贬低者的数量和比率
广义而言是一项在线服务,帮助中小型企业(SMB)管理他们的在线状态,包括评论。对 wide 的客户来说,一个非常重要的事件是该业务获得正面评价或推广的次数。下图显示了广泛客户每月的推广人数与客户流失之间的关系。(有关群组图的详细说明,请参见最后一节的开头。)在下图中,每月具有最少启动子的群组(平均略高于零个启动子)的流失率比具有最多启动子的群组高约 4 倍;流失的减少主要发生在每月 0 到 20 名推广人员之间。这是一个重要事件的明确关系,很容易理解为什么拥有更多在线推广人员的客户更有可能继续使用 Broadly service,因为收到积极的评论是客户使用 broad 的主要目标之一。

Behavioral Cohort Churn vs. Broadly’s Customer Promoters (per Month)
对 wide 的客户来说,与推广者数量相关的另一个重要事件是诋毁者的数量,或业务被负面评价的次数。下图显示了广泛客户每月的批评者数量与流失率之间的关系:每月批评者最少的群体(略高于零)的流失率比批评者最多的群体(平均每月不到 5 名批评者)高出约 2 倍;流失率的降低通常发生在每月 0 到 1 个批评者之间。
虽然这种关系看起来很像上面显示的客户促销员之间的关系,但这里是不是有点不对劲?获得负面评价是一件坏事,大概也不是 browide 的客户所期待的结果——那么为什么负面评价与客户流失减少有关呢?

Behavioral Cohort Churn vs. Broadly’s Customer Detractors (per Month)
为了理解为什么更多的坏事,如贬低者,可能与更少的客户流失有关,看看另一个更好的衡量标准会有所帮助:如果你用贬低者的数量除以评论的总数(推广者加贬低者),那么结果就是贬低者的百分比,我称之为贬低率。下图显示了流失率和贬损率之间的关系——这可能更像是您预期的对客户不利的产品事件的关系:贬损率越高,流失率越高,而且非常显著。

Behavioral Cohort Churn vs. Broadly’s Detractor Rate
那么,为什么单从贬低者数量来看,贬低者越多越好,而从贬低率来看,贬低者越多越不好呢?答案是,中的贬损者总数实际上与中显示的推广者总数相关,因为总体上收到大量评论的客户很可能会收到更多的好评和差评,这仅仅是因为有大量的评论。因此,当你以简单的方式查看对批评者数量和流失之间关系的影响时,它合并了驱动该指标的两个潜在因素:有大量评论(这是好的),以及有高比例的差评(这是坏的)。当单独分析差评的比例时,你会得到更有用的结果。
4.3 Versature 的通话和每次通话成本指标
Versature 为企业提供电信服务。作为统一通信提供商,他们许多最重要的事件都是语音呼叫,其持续时间存储在与每个事件相关的字段中。下图显示了 Versature 客户拨打的语音电话数量与客户流失之间的关系。就本地通话而言,最低群组实际上没有通话,流失率比每月有数千次本地通话的客户群组高三倍左右。

Behavioral Cohort Churn vs. Versasture’s Local Calls
当试图理解客户流失时,重要的是不仅要考虑客户使用服务的数量,还要考虑他们支付的费用。每月经常性收入(MRR)是计算客户为使用订阅服务而支付的金额的标准指标:它是客户每月为使用服务而支付的总额,但不包括任何安装费或不定期费用。(我在书中更多地谈到了 MRR 以及如何计算它。)也可以使用行为群组方法分析客户支付的金额,以寻找与流失的关系,如下所示。

Behavioral Cohort Churn vs. Versature’s Customers Monthly Recurring Revenue (Score)
下面的行为群组图做了一些新的事情:它没有直接显示群组的平均 MRR,而是显示了每个 MRR 测量值被转换为分数后的平均 MRR。如果你熟悉“曲线分级”的概念,那么度量分数也是同样的概念:度量从一种尺度转换到另一种尺度,但顺序保持不变。因此,如果将指标转换为分数,某个特定群组(如指标中倒数 10%的群组)仍然是同一组客户,并且该群组具有完全相同的流失率。这意味着将指标转换为分数只会影响群组沿群组图横轴的分布,而不会影响点的垂直位置,即流失率。当水平访问上的重新缩放使结果更容易理解时,指标被转换成分数。我在书中更多地说了度量分数以及如何计算它们。
群组流失率表明,MRR 也与流失率相关,尽管没有打电话那么强烈:不同群组中的流失率并不完全一致,最低群组流失率仅比最高流失率群组少大约一半或三分之一。但这是另一个例子,它让你停下来想想它在说什么:支付更多的人流失更少。这是你所期望的吗?这可能令人惊讶,但实际上很常见,尤其是在商业产品中。这是因为大客户的商业产品售价更高,而大客户流失更少的原因有很多:他们有更多的员工,所以当涉及到打电话或使用软件等产品使用时,为产品支付更多费用的客户通常也使用得更多。因此,支付较高 MRR 的客户流失率较低,实际上与上面显示的通话次数较多的客户流失率较低有关。
但是,我们可以从另一个角度来看客户支付金额与客户流失的关系:MRR 指标除以每月通话次数。这将产生一个指标,即客户每次通话的成本。我称之为“价值衡量”,因为它解释了顾客花了多少钱得到了多少服务。就像 MRR 的数字一样,每次通话的成本数字显示的是群组平均得分,而不是美元。每次通话成本的行为群组流失率图显示,当根据所消费的服务量来衡量付费时,付费更多的客户确实会流失更多。每次通话成本最高的群组的流失率比每次通话成本最低的群组高 6 倍左右。像这样的价值衡量标准是理解客户流失原因的重要武器,也是将在后面章节中全面探讨的重要主题。

Behavioral Cohort Churn vs. Versature’s Customers Cost per Call
5.未来的战斗
这个系列将不同于许多关于数据科学的文章,因为我不会告诉你你需要一些闪亮的新技术或复杂的算法。相反,我强调使用你的大脑(通过科学方法)和以下领域:
- 全栈分析:从原始数据开始,经过特性工程和分析,一直到向企业解释结果。
- 度量设计也称为“特征工程”受到关注,因为这些是成功的最重要的活动
- 节俭和敏捷:正确地做,快速地做,因为你将不得不一遍又一遍地做
- 可解释性和沟通:如果你不能向人类解释,客户流失就是一个失败的领域(我会在下面解释原因……)
用深度学习对抗污染

A brick kiln spewing smoke. Photo by koushik das on Unsplash
每年 11 月,新德里都会被厚厚的烟雾笼罩。作为回应,政府采取了一些照本宣科的措施,比如偶尔关闭学校,实施臭名昭著的单双号限行计划,迫使一半城市的汽车离开街道。德里的污染有几个原因:邻近邦的季节性残茬燃烧、车辆排放、以及发电厂和遍布首都地区的砖窑排放的烟雾。抗击污染需要多管齐下的方法——仅靠政府政策是不够的,它们需要与实地行动相结合。
照片不会说谎
我们以砖窑为例。在我们解决它们造成的污染之前,我们需要确切地知道有多少这样的窑和它们的位置,它们的数量是在增加还是在减少,以及有多少按照法律的要求采用了减少排放的技术。卫星图像与深度学习相结合可以回答这些问题,增强问责制并推动实地成果。这篇博文描述了我们如何做到这一点。
砖窑
随着快速城市化,对砖的需求不断增加。在亚洲的许多地方,制砖是一项非常庞大的传统产业。印度的制砖业虽然没有组织,但规模巨大。印度是世界上第二大砖块生产国——中国以 54%的份额占据主导地位[1]。

Brick kilns are a major contributor to air pollution in North India. Photo by Ishita Garg
砖窑是印度北部空气污染的主要原因。生产砖块会导致有害气体和颗粒物质的排放,包括一氧化碳和硫氧化物(SOx)等有毒化学物质。接触这些排放物对健康极其有害,并会影响儿童的身体和智力发育。
印度的大多数砖窑都是固定烟囱公牛沟窑 (FCBTK)型。然而,有一种更新的窑炉设计,被称为之字形窑炉,它更节能,造成的空气污染更少。旧设计的窑炉可以转换成之字形设计,从而提高效率——但是,改造需要成本,这导致这项技术的采用速度较慢。

Source: Towards Cleaner Brick Kilns in India by Sameer Maithel
中央污染控制委员会(CPCB)于 2017 年 6 月发布了一项 指令 ,强制要求全印度所有的砖窑转换成锯齿形设计。该指令明确指出未经许可的砖窑将被关闭[2]。尽管有指令,仍有许多砖窑没有按照规定的设计规范运行。
在卫星图像中,FCBTK 窑看起来是椭圆形的,而新设计的锯齿形窑是矩形的(只有空气以锯齿形流动!)这可以用来从航空/卫星图像中识别窑的类型。

We can identify Zigzag (left) and FCBTK kilns (right) from their layout, as seen in satellite imagery.
在这篇文章中,我们描述了我们如何使用深度学习来检测德里周围的所有砖窑,绘制它们的地图,并根据它们的设计进行分类。这将有助于发现那些没有遵守政府指令的窑炉,并在执法方面大有作为,同时增加问责制和透明度。
使用 ArcGIS 进行深度学习
深度学习是一种久经考验的卫星图像对象检测方法。我们使用以下步骤对砖窑进行检测和分类:
- 使用 ArcGIS Pro 收集训练数据
- 使用 arcgis.learn 训练深度学习模型
- 使用 ArcGIS Pro 部署训练好的模型
最后,我们创建了一个 ArcGIS Operations 仪表盘来传达分析结果。
我们使用 ESRI 世界影像图层来训练模型,为了进行对比分析,我们使用了 2014 年的同一图层(此历史影像可在 Esri 的 Living Atlas 上找到,可使用 wayback 影像工具 进行浏览)。
收集培训数据
我们使用 ArcGIS Pro 在 Esri World 影像上标记了两种砖窑的位置。我们创建了一个表示砖窑位置的点要素类,并设置了一个指示砖窑类型的属性字段(0=FCBTK/Oval design。1 =锯齿形/矩形)。为了简化我们的工作,我们只标记了窑的中心位置——我们只对它们的位置感兴趣,而不是精确的大小。

Marking locations of brick kilns and their types (red=FCBTK, blue=Zigzag) using ArcGIS Pro. This served as training data that our AI model learnt from.
导出培训数据

这些数据被用来训练深度学习模型,以检测图像中的砖窑。我们使用了 ArcGIS Pro 中提供的“导出深度学习训练数据”工具来导出包含多个砖窑示例及其在每个芯片中的位置的图像芯片。
我们选择每个窑炉位置周围的缓冲半径为 75 米,因为每个窑炉大约是该尺寸的两倍(即大约 150 米长)。
需要根据将要训练的模型类型来选择元数据格式。在这种情况下,我们必须训练一个对象检测模型。“Pascal 可视对象类”格式是一种用于对象检测任务的流行元数据格式。
在“环境”选项卡中,我们可以调整“单元大小”参数,以便每个芯片可以容纳两到三个砖窑。对于这个项目,我们使用不同的单元大小参数值导出芯片。这个技巧使我们能够通过从相同数量的标记数据引导中创建更多的训练芯片来增加训练数据。此外,它还帮助我们的模型学会为砖窑创建更合适的边界框。如果我们只输入一个单元大小的模型数据,它将总是为每个窑预测相同的大小(大约 150m ),因为这是它已经看到的所有数据。
砖窑检测人员培训
我们使用 Jupyter 笔记本和 ArcGIS API for Python 中的arcgis.learn模块来训练模型。arcgis.learn模块建立在 fast.ai 和 PyTorch 之上,只需几行代码就能训练出高度精确的模型。安装和设置环境的详细文档可在此处获得。
我们训练的模型类型是 SingleShotDetector,之所以这样叫是因为它能够一眼找到图像(芯片)中的所有对象。
**from** **arcgis.learn** **import** SingleShotDetector, prepare_data
数据扩充
我们使用prepare_data()函数对训练数据进行各种类型的转换和扩充。这些增强使我们能够用有限的数据训练更好的模型,并防止模型过度拟合。prepare_data()取 3 个参数。
path:包含训练数据的文件夹路径。
chip_size:与导出培训数据时指定的相同。
对于这个项目,我们在 11GB 内存的 GPU 上使用了 64 个的批处理大小。
此函数返回 fast.ai 数据束,在下一步中用于训练模型。
**from** **arcgis.learn** **import** SingleShotDetector, prepare_datadata = prepare_data(path=r'data\training data 448px 1m',
chip_size=448,
batch_size=64)
从您的训练数据中可视化一些样本
为了理解训练数据,我们将在arcgis.learn中使用show_batch()方法。show_batch()从训练数据中随机选取几个样本,并将其可视化。
data.show_batch(rows=5)

Some random samples from our training data have been visualized above.
上面的图像芯片标出了砖窑的边界框。标有 0 的方框是椭圆形(FCBTK)砖窑,标有 1 的是锯齿形砖窑。
加载单发探测器模型
下面的代码实例化了一个SingleShotDetector模型——它基于一个流行的对象检测模型,它的缩写形式“SSD”更为人所知。该模型返回检测到的特征的类型和边界框。
model = SingleShotDetector(data)
找到一个最佳的学习速度
一个新初始化的深度学习模型就像一个刚出生的孩子的大脑。它不知道从什么开始,并通过查看它需要学习识别的对象的几个示例来学习。如果它学习的速度非常慢,它需要很长时间才能学会任何东西。另一方面,如果孩子很快就下结论(或者,在深度学习术语中具有“高学习率”),它将经常学习错误的东西,这也不好。
类似地,深度学习模型需要用学习率来初始化。这是一个重要的超参数,其值应在学习过程开始前设置[3]。学习率是一个关键参数,它决定了我们如何根据损失梯度调整网络的权重[4]。
arcgis.learn利用 fast.ai 的学习率查找器为训练模型找到最佳学习率。我们可以使用lr_find()方法来找到能够足够快地训练我们的模型的最佳学习速率。
model.lr_find()

根据上面的学习率图,我们可以看到 lr_find()为我们的训练数据建议的学习率大约是 1e-03。我们可以用它来训练我们的模型。在最新发布的arcgis.learn中,我们甚至可以在不指定学习率的情况下训练模型。它在内部使用学习率查找器来查找最佳学习率并使用它。
符合模型
为了训练模型,我们使用fit()方法。首先,我们将使用 10 个纪元来训练我们的模型。Epoch 定义了模型暴露于整个训练集的次数。
model.fit(epochs=10, lr=0.001)

fit()方法的输出给出了训练集和验证集的损失(或错误率)。这有助于我们评估模型对未知数据的泛化能力,并防止过度拟合。在这里,只有 10 个时期,我们看到了合理的结果-训练和验证损失都大幅下降,表明模型正在学习识别砖窑。
下一步是保存模型,以便以后进一步训练或推断。默认情况下,模型将保存到本笔记本开头指定的数据路径中。
保存模型
我们将把我们训练的模型保存为'深度学习包'('。dlpk’格式)。深度学习包是用于在 ArcGIS 平台上部署深度学习模型的标准格式。
我们可以用save()的方法来保存训练好的模型。默认情况下,它保存在我们的培训数据文件夹中的'模型'子文件夹中。
model.save('ssd_brick-kiln_01')
加载一个中间模型来进一步训练它
为了重新训练一个保存的模型,我们可以使用下面的代码再次加载它,并按照上面的解释进一步训练它。
*# model.load('ssd_brick-kiln_01')*
可视化验证集中的结果
查看模型 viz-a-viz 地面真相的结果是一个很好的实践。下面的代码选取了随机样本,并向我们并排展示了基本事实和模型预测。这使我们能够在笔记本中预览模型的结果。
model.show_results(rows=8, thresh=0.2)

Detected brick kilns, ground truth on left, and the predictions on the right.
在这里,来自训练数据的地面实况的子集与来自模型的预测一起被可视化。正如我们所看到的,我们的模型运行良好,预测与地面实况相当。
砖窑模型部署与检测
我们可以使用保存的模型,使用 ArcGIS Pro 和ArcGIS Enterprise中提供的“使用深度学习检测对象”工具来检测对象。对于该项目,我们使用 ESRI 世界影像图层来检测砖窑。

Detected brick kilns in ArcGIS Pro. See the web layer with the detected kilns
该工具返回一个地理要素图层,可使用定义查询和非最大值抑制工具进一步细化该图层。该图层也已在线发布并作为托管要素图层共享。
一旦我们有了一个经过训练的模型,我们就可以用它来以完全自动化的方式检测感兴趣的对象,只需查看图像即可。为了进行对比分析,我们使用相同的模型在 2014 年的旧版本 ESRI 世界影像图层上检测砖窑,该图层是使用 wayback 影像工具找到的。

Web map showing the detected brick kilns.
使用 ArcGIS Dashboard 交流结果
数据科学可以帮助我们从数据中获得洞察力,但将这些洞察力传达给利益相关者和决策者或许同样重要,如果不是更重要的话。
以仪表板的形式显示结果可以回答关键问题,提高透明度和问责制,并推动实地成果。我们使用 ArcGIS Operations Dashboard 创建了以下仪表盘来传达我们的分析结果:

Online Dashboard showing growth in number of brick kilns and very poor (less than 14%) compliance with order mandating conversion to Zigzag design
从仪表板上可以明显看出,砖窑的数量在过去几年中有所增加,我们在将它们改造成新设计方面还有很长的路要走。不到 14%的人转向了污染更少的锯齿形设计。像这样有真实数据支持的信息产品可以增加透明度、分配责任和推动实地成果。
参考
[1]谢赫·阿费法。(2014).砖窑:大气污染的原因。
[2]https://www.cseindia.org/content/downloadreports/9387
[3]https://en . Wikipedia . org/wiki/Hyperparameter _(机器学习
加入我们吧!
在新德里的 ESRI 研发中心,我们正在应用前沿的人工智能和深度学习技术来革新地理空间分析,并从图像和位置数据中获得洞察力。我们正在寻找数据科学家和产品工程师。在线申请这里!
对抗气候危机:深度学习使 6 个未来游戏规则改变者成为可能

Figure 1: A Deep Learning algorithm predicts, localizes, and simulates tropical cyclones and their precursors [1].
想象你自己在一个关键的、高风险的气候研究和发展部门/工业综合体中,忍受着详尽的分析,只找到大环境数据中的几个趋势。你的气候研究部门负责每天完成数千个最新观察到的趋势和预测。因此,你和你的部门处理数百万个数据点,等待下一个“重大突破”的发现和结果。更重要的是,你要明白,收集、预处理和利用这些趋势对于预测来年的下一次自然灾害、干旱、飓风或高强度气旋是至关重要的。在过道的另一边,您的研究同事正在使用一种经典的、处理速度缓慢的化学发现算法,来潜在地锁定和优化最可持续和最节能的太阳能燃料分子。你突然意识到——你和你的同事正试图使用经典、传统的数据科学方法来揭示数十亿种环境数据的排列。
气候 R&D 设施的呆滞记录遵循现场实地测试和卫星数据可视化的习惯路线。按照惯例,气候科学家手动处理和分析卫星和野外数据库,以识别/预测离散的天气模式和现象。通过对这些数据的逐步逐块大规模解释,工程师和科学家有了有用的全球气候信息。在发现可持续能源的环境工程和材料科学领域,工程师必须在初始阶段考虑成百上千的现有能源材料。相比之下,深度学习(DL)提供了一个以监督、半监督和非监督的方式从气候数据中分析和推断关键模式的机会。从新的太阳能燃料分子的发现到更好的能源管理,机器学习有能力解释和重建大量数据,以便更好地可视化、预测和解决正在出现的气候危机。
1.新型节能材料的发现
在某些方面,我们可以将高度优化的分子能量结构的搜索与深度学习和药物发现的持续发展进行比较。更具体地说,强化学习(RL)、生成/预测网络和递归神经网络(RNN)的使用可以作为基于属性的化学设计的垫脚石。结合起来,RNNs 和生成模型能够制造新颖的文本副本,同时仍然保留周围角色的上下文。以长短期记忆(LSTM)网络的文本生成为例,这是一种 RNN,可以解析顺序数据,将数据存储在存储单元中,并学习在数据序列中传输数据。进一步,我们可以看一下文本生成的类比

Figure 2: Sentence Generation via predictions by an LSTM network.
LSTM 网络可以使用示例输入句子,并提取上下文中的单词种子,以提供下一个预测单词。这种句子的递归和连续生成只有通过首先向网络输入上下文中可理解的和结构化的句子才是可能的。网络能够将单词的种子序列存储到其存储器状态中,以提供下一个预测单词的预测,直到短语结束。现在,考虑到这一点,用一个包含数百万节能化学配方的数据库来取代这个包含数百万个句子的数据集。在药物发现中,这种类似的“完成句子”的方法被用于完成化学式的下一个字符。这种中央堆叠 RNN 可以由数百层构成,更重要的是,它包括一个强化学习(RL)网络,该网络基于结果预测优化对架构进行奖励和折扣。

Figure 3: RNN sequence prediction and optimization of drug SMILES, finally converted into molecular structures.
合成的节能复合物,如太阳能燃料中发现的那些,在所用化合物的配方中具有特定的物理和化学性质。理论上,研究人员将能够优化和放大,例如,在可持续能源中发现的光电化学电池或太阳能电池的特性。通过定位和表达这些离散的化学属性,研究人员可以将高效材料的搜索和验证范围缩小十倍以上。
2.自然灾害预测

Figure 4: Comparison of Acoustical data and Time to Failure in Kaggle’s 2019 Earthquake prediction challenge
另一方面,通过对大数据的预测和解释,防止自然灾害的间接和破坏性影响至关重要。事实证明,预测股票走势的相同预测算法也有可能预测地震活动、飓风前兆等。预测和预报高强度飓风、气旋和地震可以节省数十亿美元的经济成本,并保护分散在高风险地区的不发达地区的生计。大气气象数据的非线性和异常性造成了一种不稳定的局面,弱势群体中的科学家不知道会发生什么。然而,Sheila Alemany 等人题为“使用递归神经网络预测飓风轨迹”的研究论文利用完全连接的 RNN 来模拟飓风行为。作者基本上使用 RNN 自动回归和预测潜在飓风影响的轨迹和方向路线。以下轨迹揭示了模型预测的网格位置:

预测的 RNN 轨迹和验证轨迹之间的地面事实比较强调了深度学习的潜力,特别是完全连接的 RNNs-它们有能力根据之前训练的记忆状态预测数据中的输入序列。理论上,气候科学家可以将先前存在的气候数据专门化并扩展到更强大的存储库中,以巩固一个考虑到全球发生的自然灾害的模型。通过优化自然灾害预测的 rnn,人们可以在天气现象影响他们所在地区的几小时甚至几天前得到警告。此外,下一个序列的相同 RNN 预测可以用于从地震预测地震活动。
3.可扩展的农业设计和监测

Figure 6: Segmentation and highlighting of crop regions to predict their respective yields.
通过利用可追溯到 20 世纪晚期的实时哨兵卫星图像数据集,可训练模型可以部署到超精确水平上监测作物条件和特征。尽管已经实施了传统的作物监测方法,但卷积神经网络(CNNs 计算机视觉的使用可以分割负面的作物结果、干旱或破坏。更重要的是,一种称为“类别激活映射”(CAM)的方法可以用来监控给定田地中作物状态的强度,这种方法使用高度集中的特征对图像区域进行热图绘制。下面演示了航空影像上的类激活映射示例。网络的全局平均池层可防止过度拟合,并允许将多种图像输入尺寸输入到模型中,它采用每个传入特征地图的平均值,以给出网络聚焦位置的“某种”热图样式的可视化:

Figure 7: A Global Averaged neural network reveals localized “areas of trouble” on aerial satellite imagery
4。森林砍伐模式的空中追踪

Figure 8: Detection and Segmentation of Deforestation Patterns
森林砍伐占全球温室气体排放的近 10%,然而,人工追踪的古老方式可以被部署的计算机视觉应用程序所取代。最近的经济开发和森林砍伐的增加造成了一个关键的跷跷板,在这个过程中,一些地区的土地被置换、再利用或彻底毁坏。归根结底,森林砍伐是一个大规模的成本效益问题,需要自动化系统的密切监控。由于公共/私人毁林过程涉及多个部门和行业,计算机视觉可以被用来通过地面传感器和实时航空图像了解树木覆盖的损失,所有这些都在一个不断的反馈回路中相互连接。此外,可以利用机器学习来检测链锯的声音,以自动向当地执法部门发出非法砍伐森林活动的警报。通过实施分割算法(例如 UNET ),甚至可能是实时对象检测技术,可以实时检测森林砍伐区域的裸露斑块,并且可以通过算法按比例计算消除的森林面积。
5。创建更高效的基础设施
自从注入工业冷却、加热、通风、照明和电气维护的基础设施出现以来,由于对周围环境的不适当响应/敏感性,建筑物已经使用了过量的能量。换句话说,建筑中的物联网系统无法有效应对外部气候波动,从而相应地调整其能耗。通过使用天气预报、环境因素和一般建筑占用的聚合,机器学习-物联网主干可用于调整室内建筑温度,并进一步与电网通信以减少广泛的电力消耗,这取决于在给定时间低碳电力供应是否稀缺。理论上,建筑物可以使用 RNN 预测,并创建一个反馈回路,对 RNN 算法预测的阈值做出响应。一旦预测超过特定的敏感度阈值,建筑物就可以相应地调节其温度和能源效率,从而减少对电网的电力依赖。

Figure 9: Predictive/Analytical features of A.I. in Power Systems
6.可视化区域气候变化的影响

Figure 10: A Cycle-Consistent Style-Transfer GAN predicts Streetview images before and after extreme weather events.
传统的气候预测模型通常使用可量化的严重程度和轨迹来了解天气模式的风险。然而,对于土地所有者和公众来说,想象极端天气事件的后果可能很难理解或者难以理解。仅仅看图表上的轨迹和数字并不能告诉普通人潜在损害的程度。可视化预计的财产损失可以为房主、保险公司和城市承包商提供在高风险地区做好准备的更好的概念。Schmidt 等人的题为“使用周期一致的对抗网络可视化气候变化的后果”的研究论文利用周期一致的生成对抗网络(CycleGANs)在遭受破坏的房屋和正常的街景房屋之间转移风格,以预测极端天气的表面后果。
生成对抗网络(GANs)是一种特殊的生成模型,它使用对抗过程来匹配输出结果和输入分布。粗略地说,这个网络由两个竞争的极小极大模型组成——生成器和鉴别器。生产者可以被认为是一个欺诈性的伪造者,生产训练数据的虚假副本,而鉴别者是“执法人员”,识别和承认这些数据的虚假模仿。最终,鉴别器使用与发生器反向相关的损失函数。因此,如果鉴别器变得善于区分这些错误的分布副本,生成器就会自我改进。下图描述了 GAN 模型。

Figure 11: General Schematic of a GAN distinguishing the distribution probability of A (fake, generated) vs B (real).
在研究论文中,Schmidt 等人更进了一步——他们实施了周期一致损失/方法。循环一致性通过映射分布 A 和 B 来实现。如研究论文中所述,分布 A 可以是正常房屋的数据输入,而 B 被定义为毁坏的属性。通过领域适应(两个数据集之间特征的映射和连接),生成器学习如何将风格从一个被破坏的房子对抗性地转移到一个正常的房子。这种周期一致的风格转换过程可以帮助想象一个正常的房子如何承受极端天气模式的后果的高分辨率图像。下面的示意图是从最初的 CycleGan 论文中提取的,该论文的标题为“使用循环一致的对抗网络进行不成对的图像到图像的翻译”,该论文描述了映射函数和对抗鉴别器之间的映射函数。

Figure 11: Schematic of associated mapping functions and adversarial discriminators in a CycleGAN
结论
随着气候科学家在气候变化的环境中以更大的分析“马力”和更紧迫的条件前进,深度学习在大环境数据的监督-非监督操纵和解释中的出现是一个关键因素。从表面上看,气候变化是一种复杂且相互依存的过渡力量,它会根据农业、工业使用和能源效率水平等因素影响几代人。变化的环境不是一个具有连续变量的统一系统,而是一个具有不同变量以不同速率变化的无序系统。通过在高能材料创造、自然灾害预测、农业和森林砍伐监测以及基础设施物联网电力系统等用例中利用深度学习,效率的前沿可以在未来十年得到优化。
文献学
Alemany,S. (2018 年)。用递归神经网络预测飓风轨迹。 Arxiv,1–8。检索于 2019 年 6 月 30 日,来自 https://arxiv.org/pdf/1802.02548.pdf.
Cho,R. (2019)。人工智能——气候变化和环境的游戏规则改变者。检索自https://blogs . ei . Columbia . edu/2018/06/05/人工智能-气候-环境/
郝,k .(2019 . 6 . 20)。以下是人工智能帮助对抗气候变化的 10 种方式。检索于 2019 年 6 月 30 日,来自https://www . technology review . com/s/613838/ai-climate-change-machine-learning/
恩迪库马纳,E. (2018)。使用法国 Camargue 的多时相 SAR Sentinel-1 进行农业分类的深度循环神经网络。遥感,1–16。检索时间 2019 年 6 月 30 日。
鲁斯托维茨河(未注明日期)。多时相卫星图像的作物分类。斯坦福。检索自http://cs 229 . Stanford . edu/proj 2017/final-reports/5243811 . pdf
张,x(未注明)。通过深度学习方法用地震台网络定位地震。语义学者,1–14。检索时间 2019 年 6 月 30 日。
用数据科学的方法计算出一辆二手车的合理价格
使用 DS 方法计算二手车合理价格的整个过程。

介绍
计算二手车价格的一般方法是什么?你搜索类似的车辆,估计大致的基线价格,然后根据当前的里程、颜色、选择数量等进行微调。你使用领域知识和当前市场状态分析。
如果你深入了解,你可能会考虑在该国平均价格较高的不同地区销售该车。你甚至可以调查汽车在目录中列了多长时间,并检测定价过高的样品,以做出更明智的决定。

Original ad of late 1990s VW Passat estate in “Rosso corsa” color, which turned out to be the “average car” in Belarus according to dataset statistics
所以有很多要思考的,我在这里面对的问题是“有没有可能使用数据科学的方法(收集和清理数据,训练 ML 模型等。)能在痛苦的决策过程中节省你的时间和脑力?
我打开笔记本电脑,创建了一个新项目,并打开了计时器。
第一阶段。收集数据
不涉及太多细节:我已经设法在两天内收集了一个数据集,包含大约 40,000 个汽车广告,有 35 个特征(大部分是分类的)。收集数据本身并不太难,但是以一种有组织的方式来组织它需要一点时间。我用过 Python,Requests,Pandas,NumPy,SciPy 等。
关于这个特定数据集有趣的是,大多数分类特征没有以任何方式编码,因此可以很容易地解释(如engine _ fuel =“diesel”)。
第二阶段。着眼大局,处理不良数据
最初的数据分析很快发现了可疑的样本,包括 800 万公里里程表状态、10 升引擎掀背车、600 美元的混合柴油汽车等。我花了大约 6 个小时编写脚本来检测和处理这些问题。
可视化数据(我已经使用了 MatPlotlib 和 Seaborn )让我对整个市场形势有了很好的认识。

Odometer_value distribution (it is a distance traveled by a vehicle in kilometers)
大多数汽车都被频繁使用,平均里程数为 250,000 公里,这是一个很大的数字!我还注意到,人们更喜欢给里程表值分配一个好听的数字,比如 250,000 公里、300,000 公里、350,000 公里等等。一堆车有一百万公里里程表 _ 数值如果你看数值分布就没什么意义了。我可能认为 100 万公里更像是一种说法“这辆车见过很多,我真的不知道它的确切公里数。”

汽车定价背后的总体趋势非常直观:车越旧,价格越低。我希望车的年龄是整体特征层次中的第一特征。
此外,一般来说,车越老,里程表值越高,这是合理的。
为了构建价格 _ 美元散点图,我将最高汽车价格限制在大约 50,000 美元,并移除了几个百万级别的公里数里程表 _ 价值异常值。
实际上,价格低于 50,000 美元的汽车占目录的 99.9 %,因此散点图可以很好地反映价格趋势。
关于车龄:大部分车已经使用了一段时间,平均年 _ 产值为 2002。我认为,生产年份的分布(如下图所示)受从国外进口汽车的关税政策影响很大。

Distribution of the cars in the catalog by their production year.
价格分布( price_usd 将是模型训练期间该项目的目标值)高度向右倾斜,相应的平均价格和中间价格分别为 7,275 美元和 4,900 美元。

price distribution
像 up_counter (广告被手动推广的次数)这样的一些功能根本不能反映汽车的参数,但是既然这个数据已经有了,我决定把它包含到项目中。这种分布是如此的偏斜,以至于正确绘制的唯一方法就是使用对数标度。

distribution of up_counter metric
品牌受欢迎程度的分布对我来说并不意外,目录中最受欢迎的车型是大众帕萨特,白俄罗斯交通工具的传奇来源。

我还使用了 Tableau 来更好地显示制造商的市场份额和每个品牌的平均价格。

汽车拥有的照片数量的分布形状类似于价格 _ 美元分布(分布向右倾斜)。

Distribution of number of photos that listed cars have
也许汽车的价格越高,照片的数量就越多?

我做了一个联合图,它显示了轻微的相关性,但更重要的是,它清楚地表明,大多数汽车都很便宜,照片少于 15 张。
一些功能,如传动系统只是有趣的探索。在下面的柱状图中,你可以看到后轮驱动汽车的比例在过去 30 年中是如何下降的。

Histogram that illustrates the migration from rear-wheel drive to front- and all- wheel drive over the past years
数据集的整个相关矩阵如下所示(数据集中的大多数特征都是不言自明的,除了feature _ 0…feature _ 9:这些是布尔列,表示汽车具有合金车轮、空调等特征。)

Correlation matrix for the whole dataset
我不打算在这里发表完整的探索性分析,你可以在内核中查看。我花了大约 6 个小时(我还需要 60 个小时来修复数据集中有问题的样本)来挖掘数据和工程特征,直到那时我才进入模型训练。
第三阶段。模特培训
由于我已经清理了数据集,并应用了一些未来机器学习的特征工程,因此构建和训练基线模型轻而易举。
为了以最少的努力获得最大的结果,我使用了(Yandex 开发并开源的具有全面分类特征支持的决策树库上的梯度提升)。我已经在这个项目上花了太多时间,所以我只是将数据扔进模型中,调整学习率、树深度和系综中的数量树,训练几个模型,并开始用 SHAP (由斯科特·伦德伯格等人开发)探索模型决策。总时间:4 小时。
有趣的事实:在最初的预测探索阶段,我对模型的性能感到失望,开始探索错误,并发现我的数据集中的价格列被错误地解析,一些价格使用美元货币,一些价格使用白俄罗斯的国家货币:BYN。我已经修复了解析器的代码,再次收集数据,然后运行清理、特征工程和分析工作,并开始训练模型,结果好得多。
为了训练和评估第一个模型,我过滤掉了价格超过 30,000 美元的汽车(在探索阶段,我发现这些样本需要一个单独的模型)。
第四阶段。模型评估
我没有时间和计算资源来运行适当的网格搜索和顺序特征选择(SFS)作业,所以我只是调整了几次树的数量和学习速率,并使用 5 重交叉验证来评估模型的性能(检查完整的内核)。

Plot illustrates model training: number of trees on X-axis and MAE $ on the Y-axis
第一个像样的 CatBoost 模型让我达到了大约 1000 美元(平均绝对误差),这是 price_usd target 平均值的 15 %。准确地说,得分是:
**Best validation MAE score: $1019.18 on iteration 6413 with std $12.84**
我还使用 early_stopping 参数在验证分数停止变好时跳过进一步的训练。在没有试图以任何方式改进模型的情况下,我进入了预测分析。

Distribution of errors
查看真实值和预测值的误差分布和 2d 直方图不会告诉您太多关于预测质量的信息。

2d histogram plot for true and predicted values. You can notice the dense region below $3000 threshold
除了里程表 _ 值要素位置较低之外,数据集中要素的 SHAP 值等级(仅显示前 20 个要素)对我来说并不奇怪。你可以在类似和的文章中找到关于非参数模型解释的很好的解释。**

Summary_plot for top 20 features
车龄、品牌、车身类型、发动机容量和动力传动系统都是最重要的,这看起来非常合理。
**陈述事实证明这是一个有趣的特征:绝大多数汽车都是“被拥有的”,但也有一小部分“新”汽车(它们很贵)和一些“应急”车辆被损坏。
受损汽车的问题是显而易见的:该列是布尔型的,没有这种“紧急状态”的程度(稍后将详细介绍)。
第五阶段。使用领域知识探索个人预测,并找出模型的局限性
探索个人预测很快让我意识到模型和数据的局限性。我将列出一些样本,以便更好地理解模型的决策过程。
样品 0: 大众 T5 Caravelle,2009,机械变速箱,28.7 万公里,柴油。标价为13600 美元。** 预测值低 1,200 美元(该模型的 MAE 约为 1,000 美元,因此这是一个相当典型的情况)。****

Prediction interpretation (force_plot) for T5 Caravelle
你可以看到大众品牌和小型货车 body_type 做出个人贡献,使得一个样品的预测价格更高。**

Prediction interpretation (decision_plot) for T5 Caravelle
使用 SHAP,我们可以通过使用“决策图”功能以更好的方式绘制决策解释。
决策图最近被添加到库中,并提供了模型内部工作的更详细的视图。
与力图相比,决策图的主要优点是它们能够清楚地展示更多的特征。
你可以在这里阅读更多关于这种类型的剧情。
样本一: 奔驰 E270,2000,机械变速箱,46.5 万公里,柴油。标价为4999 美元。** 预测高出 198 美元。那一点也不坏!****

Prediction interpretation for E270 Mercedes
样品二: Jeep 大切诺基,2007,自动变速箱,16.6 万公里,柴油。标价是14500 美元。** 预测低 2796 美元。差预测,一看就是,但是车型已经列入目录 498 天了!感觉这个样品的价格定得太高了。也是在全国最穷的地区上市,那里的车一般都比较便宜,上市时间也比较长。****

Prediction interpretation for Jeep Grand Cherokee
样本三: 大众帕萨特,2012,自动变速箱,10.2 万公里,汽油。上市价格为11499 美元。** 预测低 64 美元。****

Prediction interpretation for VW Passat
样本四: VAZ 2107(俄罗斯车),1987 年,机械变速箱,12 万公里,汽油。标价是 $ 399 。 预测高出 34 美元。这是一个从价格范围的低端。

Prediction interpretation for VAZ 2107
我们可以看到这个样本的所有特征值是如何对预测价格产生负面影响的。
样本五:大众帕萨特 ,1992 年,机械变速箱,39.8 万公里,汽油。标价是750 美元。** 预测高出 721 美元(几乎是挂牌值的两倍)!为什么?****

Prediction interpretation for emergency VW Passat

Actual image of the “emergency” VW Passat sample
如果我们仔细观察模型解释,我们可以看到状态=紧急状态是预测价格的重要因素。对这一特殊情况的进一步人工调查显示,一辆汽车被倒下的树损坏。**
这显然是现有数据的局限性:布尔 T20 状态列根本不能反映损害程度的总体范围。我相信这个问题可以通过应用另外两种机制来“容易地”解决:使用某种预训练的深度 CNN 进行图像分析,以及使用 RNNs 从样本描述中提取实体。
我将以一辆豪华宝马 3 系结束这次样品挑选。
样本六:宝马 316 ,1994 年,机械变速箱,32 万公里,汽油。标价为1650。**** 预测高出 55 美元。我们可以清楚地看到,作为一个奢侈品牌给样本加分。****

Prediction interpretation for BMW 316
我花了大约 3 个小时研究模型的预测,并手动探索样本。
技术结论
我在整个数据集上使用 CatBoost 回归器获得了大约 1,000 美元的 MAE。但我也尝试对不同的型号使用相同的方法,并立即将误差减半至 500 美元。我相信,如果我们根据 year_produced 特征将数据集划分为子数据集,并在这些子数据集上训练多个模型,模型的性能会更好。
我也一直在想, duration_listed 特性可以用来惩罚数据集中的样本权重。例如,如果汽车上市一年,这可能意味着价格设置得太高,因此我们可以使用池功能将该样本的权重设置得较低。
总的来说,我觉得我训练的模型表现得相当不错,但是还有很大的改进空间。
关于这个项目中使用的技术:CatBoost 似乎是正确的选择,因为它提供了很好的开箱即用的分类特性支持。在 2019 年的 MacBook pro 上进行 5 重交叉验证的训练需要大约 13 分钟,但如果我在一个数据集中有数百万个样本,这不是问题,因为 CatBoost 支持在 GPU 上进行训练。它还具有非常快的预测时间,这有助于模型进入生产。
总体结论
在这次冒险中,我试图回答的问题是“如果你打算出售自己的汽车,使用数据科学方法是否合理?”这个问题的明显答案是否:如果你只是在网上手动搜索类似的车辆,你会更容易算出价格。我花了几天时间以一种简单的方式完成了这个项目,我还可以做很多事情来提高模型的性能,如应用适当的特征选择、网格搜索等。****
同时,如果你有成百上千辆车要卖,答案肯定是是的。使用 Python 和丰富的数据科学包生态系统,您可以自动化收集数据、构建分析和训练预测模型的工作。你还可以发现趋势,预测市场的未来。您可以将模型隐藏在一些 API 之后,这样它们就可以以可靠和方便的方式服务于业务。****
还有更多。如果你经营从国外进口二手车的业务,你可以训练单独的 ML 模型,根据它们对公司的盈利能力对样本进行排序。你也可以预测这些样品在实际交易前的上市时间。你甚至可以选择合适的地区进行销售。如果你有足够的数据,可能性是无限的。
但如果你只是为自己做这种兼职项目,你会像我一样获得巨大的乐趣!
感谢阅读,我希望你喜欢这篇文章,你可以检查我的内核和一个数据集。如果您有任何反馈,可以通过 LinkedIn 联系我。
最终确定:棒球是精英中的精英
今天看迈克·特劳特就相当于看…不穿鞋的乔·杰克逊?

Hank Aaron (https://baseballhall.org/discover/hank-aaron-715-hr-ticket)
棒球迷喜欢将现在的球员与过去的传奇进行比较。迈克·特劳特和米奇·曼托一样有天赋,比利·汉密尔顿可以和里奇·亨德森一起大步奔跑,或者哈维尔·贝兹的电动风格可以和杰基·罗宾逊相媲美。年长的粉丝会做这些比较,但是对于像我这样的年轻粉丝,我怎么知道他们让终身粉丝想起了谁?我没有看到汉克·亚伦的进攻优势。皮特罗斯铁路捕手在本垒板。雷吉·杰克逊无可匹敌的力量。旧视频可以展示这些职业生涯的精彩时刻,但不能真实地反映这些球员每天都在做什么。
更不用说也许我们的祖父母也不知道他们在说什么。据我所知,你不能在 MLB 的电视节目上看 27 年的扬基队或 55 年的道奇队。我们都偏向于我们最喜欢的球员,并认为他们比生活更重要。米奇·曼托被认为是一代人中最伟大的中外场手,但他是最受欢迎的球队中最好的球员。有没有可能他所有的炒作都是因为人们太喜欢他了?
但是如果我们能更进一步呢?与其拿球员相互比较,不如拿单个球员赛季相互比较,看看哪个单个赛季的表现是相称的。
今天我会给大家上一堂课,告诉大家我们如何比较 MLB 历史上最伟大的常规赛进攻表现,并对有史以来最好的进攻赛季和球员进行排名。
对于任何对数据集和代码感兴趣的人,你可以在我的 GitHub 上看到我的项目:https://GitHub . com/anchor p34/MLB-Clustering/blob/master/MLB % 20 Clustering % 20 analysis . ipynb
进攻统计细目
首先,让我们从提供的攻击性统计数据中看一个例子。我最喜欢的球员,科罗拉多洛矶队的诺兰·阿雷纳多,有 2013-2018 年的比赛。

Offensive statistics from Nolan Arenado’s seasons in the MLB
这显示了他进攻赛季的基本统计数据,如比赛,击球,跑垒,安打,本垒打,打点等。凭借我的棒球背景,我知道一些用于评估进攻表现的其他指标。

Baseball statistics that are common in evaluating a player’s performance
随着新的变量的加入,有一个完整的进攻分解,这应该给一个球员一个赛季的进攻表现足够的洞察力。
进攻性百分位数
为了比较不同赛季的球员,直接比较原始数据是没有效率的。例如,这是一个赛季中击球次数超过 50 次的球员的平均联盟击球率的时间序列:

League average of batting averages from players with more than 50 at bats.
如果一名球员在 1975 年打出了 0.300 的胜率,这将比一名球员在 2000 年打出 0.300 的胜率更令人印象深刻。所以,我们应该从每个进攻类别的那个赛季的球员中抽取百分位数,并在从 0 到 1 的范围内对他们进行排名。在 1975 年达到 0.300 的人可能在 0.9 百分位(或 90 百分位),在 2010 年达到 0.300 的人可能在 0.75 百分位。
做了这些调整后,阿雷纳多现在每个进攻类别的百分位数是:

Offensive percentiles of Nolan Arenado for each season.
下面是诺兰每一季表现的快速分析:
- 2018 年,诺兰在打点的第 99 个百分点,这意味着只有 1%的 MLB 人比他有更多的打点
- 他的 OBP 每年都增加或保持不变,显示了他在进攻方面的进步
- 2015 年,他的击球率、全垒打和打点都排在第 99 位,这表明他是一个非常强大的击球手
但这是否意味着诺兰·阿伦纳多在 2015 年是一个有价值的击球手?
诺兰是在大多数进攻类别的上百分位数,但这些是正确的类别被认为是一个有价值的击球手吗?MVP 候选人在这些类别中也很普遍吗?聚类算法可以帮助识别这些字段。
但是,我们不希望聚类算法将每个聚类分成相等的部分。我们希望真正有价值的球员都聚集在一起,并与其他人分开,与其他人相比,这应该是一个较小的百分比。
聚类后,应该有三组玩家:
第一组:平均或低于平均水平的玩家
第二组:高于平均水平的玩家
第三组:该赛季的最佳球员
在对标准化数据进行一些分析后,我们得到了以下细分:
- 平均或低于平均水平的玩家:MLB 人口的 40%
- 高于平均水平的玩家: 35%的 MLB 人口
- 那个赛季的最佳球员: 25%的 MLB 人口
这种分布有点太均匀了。4 个球员中有 1 个被认为是联盟最好的?要真正得到我们感兴趣的顶尖选手,需要更少的钱。再做一个调整,无论那个球员在那个赛季的进攻类别中是否在前 5 个百分点,数据都可以被解释为更多的二进制:

Data converting data into binary outputs depending on whether Nolan Arenado was in the top 5 percentile of that offensive category for that season.
现在我们可以看到,在 2015 年,诺兰在我们感兴趣的 18 个类别中的 13 个类别中都位于前 10%。运行与以前相同类型的聚类算法,新的细分变成
- 平均或低于平均水平的玩家: 82%的 MLB 人口
- 高于平均水平的玩家:MLB 人口的 10%
- 给定赛季的最佳球员:MLB 人口的 8%
完美!这将大多数 MLB 球员的脂肪切除,以确保最佳赛季被确定。为了仔细检查,我们可以查看 2015 赛季所有顶级球员:

Some players who were in the top group for the 2015 season
2015 赛季的 MVP 乔什·唐纳森和布莱斯·哈珀以及阿雷纳多都被列入其中。算法正如我们预期的那样工作。
在确定最佳球员时,哪些进攻类别最具优势?
随着球员现在每年被分配到一个集群,我们可以看到有百分之多少的球员在那个赛季的进攻类别。下面的热图对此进行了分解。

Cluster 2 is considered the best players in the MLB. We can see that offensive categories like RBI, OPS, and HR/G are the values that separate them from the rest of the pack
浏览图表,聚类 0 属于低于平均水平的玩家,聚类 1 属于高于平均水平的玩家,聚类 2 属于最高水平的玩家。从 RBI 类别开始,第 2 组中 86%的球员在他们的赛季中处于该类别的前 5 个百分点。就 OPS 而言,83%的集群 2 玩家在该赛季处于前 5%的位置。
总的来说,分类 RBI、HR、OPS、SLG 和 HR/G 是将集群 2 与其他分类区分开的字段。所有这些领域都与幂数有关。当确定谁是联盟中最好的球员时,你首先应该看的是实力数字。
季节对比
有些赛季是由个别明星的影响力决定的,有些则是由主流球队决定的。下面的线形图显示了集群 2 中的普通玩家有资格参与的进攻类别的数量。例如,在 2001 赛季中,第 2 组的玩家平均在大约 12 个类别中处于前 5%的位置。

This line chart shows the average number of offensive categories a player in cluster 2 has in each season. The high points show the dominance of them in a season, low points show that the season was more evenly distributed throughout the league.
从图中可以看出,自 2003 赛季以来,随着波动性的平息,背靠背的几年中有很多起伏。20 世纪 80 年代和 90 年代,棒球的类固醇时代每年都在创造新的超级明星,这种趋势越来越明显。
那么,谁是有史以来排名第一的人?

The best individual offensive seasons in MLB history
但是谁拥有有史以来最具统治力的进攻赛季呢?当谈到最具统治力的球员时,人们通常会想到汉克·亚伦、巴里·邦兹和贝比·鲁斯这样的球员;然而,左边的图表有一些其他的想法。首先,圣·穆萨尔和卢·格里克分别三次名列前茅,这很了不起。许多人都认为迈克·特劳特是下一代伟大的天才,这是有道理的。在过去的 15 年里,他是唯一一个拥有真正统治性赛季的球员。甚至三冠王米格尔·卡布雷拉的 2012 赛季也以总共 16 个赛季结束,显示了这些赛季是多么不可思议。
能够看到迈克·特劳特与泰德·威廉姆斯在 1946 年或格赫里希在 1927 年的统治地位,这是一种理解这些赛季有多不可思议的惊人方式。
最后,我们可以看看那些在 MLB 排行榜上拥有至少 5 年职业生涯的球员。

Players with the highest career Top Tier Percentage who played at least 5 years in the MLB
这个数据集表明,弗兰克·罗宾逊,按照聚类标准,在他 20 年的职业生涯中有 19 年是顶级球员,是有史以来接触棒球场最具攻击性的巨头。就我个人而言,我看着汉克·亚伦和巴里·邦兹的黄金标准,他们都有着惊人的数据,是我玩过这个游戏的最顶尖的两个玩家,但是算法说不是这样。最后,今天比赛中最好的球员,迈克·特劳特,以这种速度将不得不成为有史以来最好的球员之一。到目前为止,谁是他最好的对手?不是别人,正是臭名昭著的赤脚乔·杰克逊。
像威利·梅斯这样被人们认为是有史以来最伟大的球员呢?上面的图表显示了在给定的赛季中,他们被认为是联盟中最好的球员之一的次数除以职业生涯赛季总数。由于他在联盟的最后几年,梅斯在这个排名中受到了惩罚。在他 22 年的大联盟生涯中,有 16 年他被认为是伟大的,这让他获得了 73%的顶级百分比。
通过聚类算法,我们能够跨赛季比较游戏中的球员,以给出当今哪些球员与棒球不朽者匹配的真实视角。理解像 RBI,HR,OPS,SLG 和 HR/G 这样的进攻类别是这些球员在一个赛季中被认为是精英的驱动力,可以更准确地描述是什么让球员成为 MVP 候选人和全明星。
对于那些因为自己最喜欢的球员没能上榜而感到沮丧的人,我很抱歉。我还在想怎么让 Arenado 也来。
但是坚持住,坚持住!杰夫·巴格韦尔榜上有名,但德瑞克·基特榜上无名?皮特·罗斯在哪里?他们不被认为是令人讨厌的坏男孩吗?一定是哪里出了问题,对吗?还是……他们不应该属于这一类?这可能需要另一次深度潜水…
财务分析—股票数据的探索性数据分析

随着分析越来越多地渗透到我们生活的方方面面,金融业无疑是最早抓住这一趋势的行业之一。鉴于金融科技和金融领域的市场规模不断扩大,传授一些金融技能将是一件好事!
注意:这篇文章旨在教你一些操作股票数据集的基础知识,而不是让你成为一个定量分析师/股票经纪人/算法交易员。为了在这些领域取得进步,你肯定需要更多的交易、金融和计算方面的知识。
1。导入数据
你需要的大部分金融数据都可以在雅虎财经上轻松找到!你只需要输入股票数据,点击历史数据

source: https://sg.finance.yahoo.com/quote/FB?p=FB&.tsrc=fin-srch
之后,设置合适的时间段,记得点击应用!(见鬼,当我第一次开始时,我一直想知道为什么我下载的 csv 没有给我正确的时间框架)

source: https://sg.finance.yahoo.com/quote/FB/history?p=FB
对于本教程,我将使用脸书 2019 年 6 月 18 日至 6 月 16 日的数据。下面是我将用来操作数据的库
import pandas as pd
import numpy as np
from scipy.stats import norm
像往常一样,在我们做任何进一步的分析之前,我们应该总是检查数据并理解数据帧
df = pd.read_csv('stock data/FB_16June18_16June19.csv')
df.head()

接下来,我们将使用 df.info()检查数据帧的数据类型

在这里,我们可以看到“日期”列显示为对象,而不是日期时间数据类型。当我们绘制直方图/折线图时,这可能会有问题,所以我们首先将其转换为日期时间对象
df['Date'] = pd.to_datetime(df['Date'])
现在让我们再次检查数据类型

不错!我们现在已经将“日期”列变成了日期时间对象!
2.测绘
是时候进口神奇的东西了
import matplotlib.pyplot as plt
%matplotlib inline
matplot 库给了我们制作强大图表的能力,让我们对股票有了更深入的了解
plt.figure(figsize=(20,8))
plt.plot('Date','Close',data=df)plt.xlabel('Date')
plt.ylabel('Close Price')
plt.xticks(rotation=45)

如果你一直关注财经新闻,你会知道 7 月份的大幅下降是因为脸书没有达到他们的收入目标,而 1 月份是因为隐私问题。
3.创建列
现在,让我们创建一些有用的列来对股票进行一些有趣的推断
首先,我们将创建“每日滞后”列,它基本上只是将“收盘”价格向后移动一天。(注意:我们通常可以使用各种指标,但为了方便起见,我选择了“接近”)
df['Daily Lag'] = df['Close'].shift(1)
df.head()

创建“每日滞后”的原因是为了让我们创建“每日回报”列

source: http://www.crsp.com/products/documentation/crsp-calculations
每日回报告诉我们在股价收盘后一天内获得的回报(咄!)
df['Daily Returns'] = (df['Daily Lag']/df['Close']) -1
df.head()

4.更多绘图
现在,让我们看看“每日收益”

我们意识到,如果我们使用默认 bin=10,就很难辨别形状。让我们把它增加到 20。在此之前,让我们找到均值和标准差。
mean = df['Daily Returns'].mean()
std = df['Daily Returns'].std()
print('mean =',mean)
print('Std deviation =',std)

哎哟,一般来说回报是负的,但是请记住,这只考虑了每天的回报,所以它只是告诉你,一般来说,如果你在同一天买入和卖出,你将会亏损。
df['Daily Returns'].hist(bins=20)
plt.axvline(mean,color='red',linestyle='dashed',linewidth=2)
#to plot the std line we plot both the positive and negative values
plt.axvline(std,color='g',linestyle='dashed',linewidth=2)
plt.axvline(-std,color='g',linestyle='dashed',linewidth=2)

最后,我将向您介绍峰度值。

source: https://community.plm.automation.siemens.com/t5/Testing-Knowledge-Base/Kurtosis/ta-p/412017
峰度告诉你尾部的“肥度”,这很重要,因为它告诉你这些值能有多“极端”。

在我们的例子中,该值为正,因此这表明出现“极端”值的可能性很小。(注意:实际确定这一点的正确方法是使用 Z 值,我将在另一个单独的教程中展示它!)
5.结论
这是一个非常简单的演练,对股票数据的一些操作进行探索,并发掘一些简单的见解!肯定还有更多要发现,但我认为这是一个教程已经很多了!
我将在以后的教程中使用 python 写更多关于进一步的统计分析,甚至一些交易技术,如快慢线和布林线。
所有代码都来自我自己的 Jupyter 笔记本,我可能会很快上传到我的 github,所以保持警惕!
金融机器学习第 0 部分:棒线
前言
最近,我拿到了一份由马科斯·洛佩兹·德·普拉多撰写的金融机器学习进展。洛佩兹·德·普拉多是著名的量化研究员,在他的职业生涯中管理了数十亿美元。这本书对任何对数据科学和金融感兴趣的人来说都是一个惊人的资源,它提供了关于如何将先进的预测技术应用于金融问题的宝贵见解。
本文是致力于将 Lopez de Prado 介绍的方法应用于真实(有时是合成)数据集的系列文章的第一篇。我的希望是,通过写这些帖子,我可以巩固我对材料的理解,并分享一些经验教训。
事不宜迟,让我们继续这篇文章的主题:酒吧。

wrong kind of bar
什么是酒吧?
构建一个好模型的第一步是将数据聚集成一种方便的格式,以便进一步分析。条指的是包含金融资产价格变动最基本信息的数据表示。典型的棒线可能包含时间戳、最高价、开盘价和收盘价等特征。

HLC bar chart (stockcharts.com)
条形图通常是用于训练和测试 ML 预测器的输入数据格式。很容易想象,原始数据的聚合方式会对整个模型产生重大的下游影响。
动机
尽管以固定的时间间隔(例如每天/每小时/每分钟等)进行价格观察似乎很直观。,这不是一个好主意。通过市场的信息流在一段时间内不是均匀分布的,并且在某些时段会有较高的活动,例如在开市后的一个小时内,或者就在期货合约到期之前。
我们的目标必须是一个条形图,其中每个条形图包含相同数量的信息,但是基于时间的条形图会对慢速时段进行过采样,而对高活动时段进行欠采样。为了避免这个问题,这个想法是抽样观察作为市场活动的一个功能。
设置
使用交易账簿数据集,我们将为实际金融工具构建多种类型的棒线。我将使用在 BitMex 上市的比特币永久掉期合约的数据作为 XBT ,因为谈论比特币是这些天令人兴奋的事情,也因为交易账簿数据在这里可用。我们将比较时间棒线和分笔成交点棒线、成交量棒线、美元棒线和美元不平衡棒线。提供了 Python 3 片段以供参考。
首先,做一点设置:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime# raw trade data from [https://public.bitmex.com/?prefix=data/trade/](https://public.bitmex.com/?prefix=data/trade/)
data = pd.read_csv(‘data/20181127.csv’)
data = data.append(pd.read_csv(‘data/20181128.csv’)) # add a few more days
data = data.append(pd.read_csv(‘data/20181129.csv’))
data = data[data.symbol == ‘XBTUSD’]
# timestamp parsing
data[‘timestamp’] = data.timestamp.map(lambda t: datetime.strptime(t[:-3], “%Y-%m-%dD%H:%M:%S.%f”))
时间条
我们现在已经为 BitMex 上的 XBTUSD 股票载入了几天的交易数据。让我们来看看以 15 分钟为间隔计算的成交量加权平均价格是什么样的。如前所述,这种表示与市场信息流不同步,但是,我们将使用它作为基准进行比较。
def compute_vwap(df):
q = df['foreignNotional']
p = df['price']
vwap = np.sum(p * q) / np.sum(q)
df['vwap'] = vwap
return dfdata_timeidx = data.set_index('timestamp')
data_time_grp = data_timeidx.groupby(pd.Grouper(freq='15Min'))
num_time_bars = len(data_time_grp) # comes in handy later
data_time_vwap = data_time_grp.apply(compute_vwap)

XBT time bars
请注意,我们在最后一个系列中保存了小节数。为了比较不同的方法,我们希望确保我们有大致相同的分辨率,以便比较是公平的。
刻度条
分笔成交点背后的想法是每 N 个交易采样一次观察值,也称为“分笔成交点”,而不是固定的时间段。这允许我们在很多交易发生时获取更多的信息,反之亦然。
total_ticks = len(data)
num_ticks_per_bar = total_ticks / num_time_bars
num_ticks_per_bar = round(num_ticks_per_bar, -3) # round to the nearest thousand
data_tick_grp = data.reset_index().assign(grpId=lambda row: row.index // num_ticks_per_bar)data_tick_vwap = data_tick_grp.groupby('grpId').apply(compute_vwap)
data_tick_vwap.set_index('timestamp', inplace=True)
这与时间条系列相比如何?

XBT time and tick bars
将两者放在一起,你可能会注意到时间条(蓝色)中隐藏了约 10%的暴涨和暴跌(黄色)。根据你的策略,这两个事件可能意味着巨大的交易机会(均值回归)或交易成本(滑点)。
音量条
分笔成交线的一个缺点是,不是所有的交易都是平等的。考虑购买 1000 份合约的订单作为一个交易执行,100 份合约的 10 份订单将计为 10 个交易。考虑到这种有些武断的区别,不管发生了多少交易,对每 N 份交易合同进行抽样观察可能是有意义的。由于 XBT 是 BTC 掉期合约,我们将以 BTC 来衡量交易量。
data_cm_vol = data.assign(cmVol=data['homeNotional'].cumsum())
total_vol = data_cm_vol.cmVol.values[-1]
vol_per_bar = total_vol / num_time_bars
vol_per_bar = round(vol_per_bar, -2) # round to the nearest hundreddata_vol_grp = data_cm_vol.assign(grpId=lambda row: row.cmVol // vol_per_bar)data_vol_vwap = data_vol_grp.groupby('grpId').apply(compute_vwap)
data_vol_vwap.set_index('timestamp', inplace=True)

XBT time and volume bars
请注意,成交量图显示了比分笔成交点更剧烈的上涨和下跌(4100+对 4000+的峰值和 3800-3900+的低谷)。到目前为止,为条形选择的聚合方法会影响数据的表示方式,这一点应该已经很明显了。
美元吧
即使这里使用的数据集很小,您可能会注意到,当 BTC 相对于美元的价值在短短 3 天内移动超过 20%时,作为 BTC 交易数量的函数对数据进行采样是没有意义的。在 11 月 27 日上午买入 1 BTC 和在 11 月 29 日晚上买入 1 BTC 是一个截然不同的决定。这种价格波动是美元棒线背后的基本原理——作为美元(或你选择的货币)交换函数的采样,理论上应该使频率对价值波动更加稳健。
# code omitted for brevity
# same as volume bars, except using data['foreignNotional'] instead of data['homeNotional']

XBT dollar bars
请注意,BTC 成交量棒线在 11–28 00 和 11–29 00 附近显示了几乎相同的跳跃,但是美元棒线在 11–28 00 的初始峰值与后者相比看起来相对温和。

dollar bars vs BTC volume bars
这是由抽样引起的差异的一个主要例子——尽管许多比特币在 01 年 11 月 28 日左右易手,但当时它们的美元价值相对较低,因此该事件被描述为不太严重。
不平衡条
不平衡棒是 MLDP 称之为“信息驱动”的那种棒。这些扩展了替代酒吧的想法,以更先进的方法。不平衡棒线特别试图在买卖活动异常不平衡时取样,这可能意味着市场参与者之间的信息不对称。基本原理是,消息灵通的交易者要么大量买进,要么大量卖出,但很少同时做两件事。当不平衡事件发生时,取样可以让我们关注大的波动,忽略不太有趣的时期。
实施美元失衡杠
实现不平衡棒保证了更详细的解释。给定每个分笔成交点的美元数量和价格,流程如下:
- 计算签名流:
- 计算分笔成交点方向(价格变化的标志)。
- 将分笔成交点方向乘以分笔成交点体积。
2.累积不平衡棒线:
- 从第一个数据点开始,遍历数据集,跟踪累积的带符号流量(不平衡)。
- 只要不平衡的绝对值超过预期的不平衡阈值,就取样。
- 当您看到更多数据时,更新不平衡阈值的期望值。
让我们进一步展开这些步骤。
1.1 计算滴答方向:
给定一系列 N 分笔成交点{ ( p [ i ],v[I]},其中I∈1……N的 p [ i 是相关价格, v [ i 是美元交易量,我们首先计算变化
δp[I]:=p[I-p[I-1]
b[I]:= b[I-1]如果δp[I]= 0
b[I]:= sign(δp[I])否则
幸运的是,在我们的数据集中,刻度方向已经给了我们,我们只需要将它们从字符串转换成整数。
def convert_tick_direction(tick_direction):
if tick_direction in ('PlusTick', 'ZeroPlusTick'):
return 1
elif tick_direction in ('MinusTick', 'ZeroMinusTick'):
return -1
else:
raise ValueError('converting invalid input: '+ str(tick_direction))data_timeidx['tickDirection'] = data_timeidx.tickDirection.map(convert_tick_direction)
1.2 计算每个节拍的符号流:
签约流量[I]:=b[I]**v[I*]为第 i 步的美元成交量
data_signed_flow = data_timeidx.assign(bv = data_timeidx.tickDirection * data_timeidx.size)
2。累积美元不平衡棒线
为了计算美元不平衡棒线,我们通过数据向前推进,跟踪自上一个样本以来的不平衡,并在不平衡的幅度超过我们的预期时取样。该规则详述如下。
样品栏何时:
|不平衡| ≥预期不平衡
哪里有
实验。不平衡:=(每根棒线的预期分笔成交点数)* |每笔分笔成交点的预期不平衡|
我们将 t 分笔成交点子集的不平衡定义为θ[t]:=∑b[I]** v[I]对于 i∈1…t*
让 T 表示每根棒线的分笔成交点数量,它不是常数。然后,Eₒ[ T 是每根棒线的预期分笔成交点数量,我们将其估计为先前棒线的 T 值的指数加权移动平均值。
最后,我们估计每一个分笔成交点的预期不平衡,eₒ[b * v**,为先前棒线的 b[i]*v[i]值的指数加权移动平均值。
综上所述,我们必须逐步迭代数据集,每隔 T* 个节拍采样一次,定义如下
t := arg min(t)s . t . |θ[t]|≥eₒ[t]** |eₒ[b * v**|
本程序的重要注意事项:
- 开始时,我们没有任何先前的棒线作为我们估计的基础,所以我们必须为计算第一个阈值提供初始值。
- 随着算法累积越来越多的条,EWMA 估计“忘记”初始值,取而代之的是更新的值。确保您设置了足够高的初始值,以便算法有机会“预热”估计值。
- 该算法对用于 EWMA 的超参数非常敏感。因为没有直接的方法来获得与前面演示中相同数量的条形,所以我们将只选择最方便/合理的超参数。
记住这一点,让我们把逻辑放到代码中。我使用来自 stackexchange 的 EWMA 的快速实现。
from fast_ewma import _ewmaabs_Ebv_init = np.abs(data_signed_flow['bv'].mean())
E_T_init = 500000 # 500000 ticks to warm updef compute_Ts(bvs, E_T_init, abs_Ebv_init):
Ts, i_s = [], []
i_prev, E_T, abs_Ebv = 0, E_T_init, abs_Ebv_init
n = bvs.shape[0]
bvs_val = bvs.values.astype(np.float64)
abs_thetas, thresholds = np.zeros(n), np.zeros(n)
abs_thetas[0], cur_theta = np.abs(bvs_val[0]), bvs_val[0] for i in range(1, n):
cur_theta += bvs_val[i]
abs_theta = np.abs(cur_theta)
abs_thetas[i] = abs_theta
threshold = E_T * abs_Ebv
thresholds[i] = threshold
if abs_theta >= threshold:
cur_theta = 0
Ts.append(np.float64(i - i_prev))
i_s.append(i)
i_prev = i
E_T = _ewma(np.array(Ts), window=np.int64(len(Ts)))[-1]
abs_Ebv = np.abs( _ewma(bvs_val[:i], window=np.int64(E_T_init * 3))[-1] ) # window of 3 bars return Ts, abs_thetas, thresholds, i_sTs, abs_thetas, thresholds, i_s = compute_Ts(data_signed_flow.bv, E_T_init, abs_Ebv_init)
我们来绘制|θ[ t ]|和不平衡阈值(eₒ[t]* |eₒ[b * v]|)看看是怎么回事。

Threshold vs. magnitude of imbalance
似乎在上升趋势加快的地方和同一趋势反转的地方附近,采样频率较高。在我们可视化条形之前,我们需要相应地对分笔成交点进行分组。
根据计算出的边界将分笔成交点聚集成组
n = data_signed_flow.shape[0]
i_iter = iter(i_s + [n])
i_cur = i_iter.__next__()
grpId = np.zeros(n)for i in range(1, n):
if i <= i_cur:
grpId[i] = grpId[i-1]
else:
grpId[i] = grpId[i-1] + 1
i_cur = i_iter.__next__()
综合来看:美元不平衡棒线
data_dollar_imb_grp = data_signed_flow.assign(grpId = grpId)
data_dollar_imb_vwap = data_dollar_imb_grp.groupby('grpId').apply(compute_vwap).vwap

Dollar imbalance bars
我们看到,当趋势发生变化时,DIB 倾向于采样。它可以被解释为包含相同数量的关于趋势变化的信息的 DIB,这可以帮助我们开发趋势跟踪的模型。
摘要
我们已经使用了一个交易账簿数据集来计算 BTC 掉期合约的时间、价格点、美元、成交量和美元不平衡棒线。每种替代方法讲述了一个略有不同的故事,每种方法都有优势,这取决于市场微观结构和特定的用例。
为了进一步探索这一点,考虑测量每个条形序列的统计属性,如峰度和序列相关性,以查看哪些条形更容易用 ML 算法建模。我希望你喜欢这个演示,如果你发现错误或有任何问题,请联系我们!
金融机器学习第 1 部分:标签

设置监督学习问题
介绍
在的上一篇文章中,我们探讨了几种为金融工具收集原始数据的方法,以创建被称为棒线的观察值。在这篇文章中,我们将关注机器学习管道的下一个关键阶段——标记观察。提醒一下,机器学习中的标签表示我们想要预测的随机变量的结果。
就像本系列的其余部分一样,这篇文章中显示的技术是基于 金融机器学习的进步 由 马科斯洛佩兹德普拉多 。我推荐你去看看这本书,它对这个问题有更详细的论述。话虽如此,是时候跳进水里游一游了。
标记观察
在金融环境中,解决监督学习问题的一个简单方法是尝试预测某个工具在未来某个固定时间范围内的价格。请注意,这是一个回归任务,即我们试图预测一个连续的随机变量。这是一个很难解决的问题,因为价格是众所周知的嘈杂和序列相关的,并且所有可能的价格值的集合在技术上是无限的。另一方面,我们可以将此视为一个分类问题—我们可以预测离散化的回报,而不是预测精确的价格。
大多数金融文献使用固定范围标记方法,即根据未来某个固定步数的回报来标记观察值。标签由利润和损失阈值离散化:

这种标记方法是一个良好的开端,但它有两个可解决的问题。
- 阈值是固定的,但波动性不是——这意味着有时我们的阈值相距太远,有时又太近。当波动性较低时(例如,在夜间交易时段),我们将获得大部分的 y= 0 标签,即使低回报是可预测的且具有统计显著性。
- 标签是路径独立的,这意味着它只取决于地平线上的回报,而不是中间回报。这是一个问题,因为标签没有准确反映交易的现实——每个策略都有止损阈值和止盈阈值,可以提前平仓。如果中间回报触及止损阈值,我们将实现亏损,持有头寸或从中获利是不现实的。相反,如果中间回报达到获利阈值,我们将关闭它以锁定收益,即使回报为零或为负。
计算动态阈值
为了解决第一个问题,我们可以将动态阈值设置为滚动波动率的函数。我们假设在这一点上我们已经有了 OHLC 酒吧。我使用 BitMex:XBT 的美元条,这是上一篇文章中的比特币永久互换合约——如果你是从零开始,这个代码片段将帮助你赶上。
在这里,我们将估计每小时的回报率波动,以计算利润和损失的阈值。下面你会发现一个直接来自 Lopez De Prado 的稍加修改的函数,为了清楚起见还添加了注释:
def get_vol(prices, span=100, delta=pd.Timedelta(hours=1)):
# 1\. compute returns of the form p[t]/p[t-1] - 1 # 1.1 find the timestamps of p[t-1] values
df0 = prices.index.searchsorted(prices.index - delta)
df0 = df0[df0 > 0] # 1.2 align timestamps of p[t-1] to timestamps of p[t]
df0 = pd.Series(prices.index[df0-1],
index=prices.index[prices.shape[0]-df0.shape[0] : ]) # 1.3 get values by timestamps, then compute returns
df0 = prices.loc[df0.index] / prices.loc[df0.values].values - 1 # 2\. estimate rolling standard deviation
df0 = df0.ewm(span=span).std()
return df0
添加路径依赖:三重屏障法
为了更好地结合假设交易策略的止损和止盈场景,我们将修改固定范围标记方法,以便它反映哪个障碍首先被触及——上限、下限或范围。因此得名:三重屏障法。

Triple-Barrier Label y=0 | Source: quantresearch.org
标签模式定义如下:
y= 1 :首先碰到顶部护栏
y= 0 :先击中右侧护栏
y= -1 :首先碰到底部护栏
赌的一方呢?
上面的模式适用于只做多的策略,但是当我们同时考虑多头和空头时,事情就变得复杂了。如果我们做空,我们的盈利/亏损与价格走势相反——价格下跌时我们盈利,价格上涨时我们亏损。

“Because I was inverted” — Maverick | Top Gun
为了说明这一点,我们可以简单地将侧表示为长 1,短-1。因此,我们可以将我们的收益乘以边数,所以每当我们做空时,负收益变成正收益,反之亦然。实际上,如果侧 =-1,我们翻转 y =1 和 y =-1 标签。
让我们试着实现一下(基于 MLDP 的代码)。
首先,我们定义了获取视界屏障时间戳的程序:
def get_horizons(prices, delta=pd.Timedelta(minutes=15)):
t1 = prices.index.searchsorted(prices.index + delta)
t1 = t1[t1 < prices.shape[0]]
t1 = prices.index[t1]
t1 = pd.Series(t1, index=prices.index[:t1.shape[0]])
return t1
现在我们有了我们的水平障碍,我们定义一个函数,根据前面计算的波动率估计值设置上限和下限:
def get_touches(prices, events, factors=[1, 1]):
'''
events: pd dataframe with columns
t1: timestamp of the next horizon
threshold: unit height of top and bottom barriers
side: the side of each bet
factors: multipliers of the threshold to set the height of
top/bottom barriers
''' out = events[['t1']].copy(deep=True)
if factors[0] > 0: thresh_uppr = factors[0] * events['threshold']
else: thresh_uppr = pd.Series(index=events.index) # no uppr thresh
if factors[1] > 0: thresh_lwr = -factors[1] * events['threshold']
else: thresh_lwr = pd.Series(index=events.index) # no lwr thresh for loc, t1 in events['t1'].iteritems():
df0=prices[loc:t1] # path prices
df0=(df0 / prices[loc] - 1) * events.side[loc] # path returns
out.loc[loc, 'stop_loss'] = \
df0[df0 < thresh_lwr[loc]].index.min() # earliest stop loss
out.loc[loc, 'take_profit'] = \
df0[df0 > thresh_uppr[loc]].index.min() # earliest take profit
return out
最后,我们定义一个函数来计算标签:
def get_labels(touches):
out = touches.copy(deep=True)
# pandas df.min() ignores NaN values
first_touch = touches[['stop_loss', 'take_profit']].min(axis=1)
for loc, t in first_touch.iteritems():
if pd.isnull(t):
out.loc[loc, 'label'] = 0
elif t == touches.loc[loc, 'stop_loss']:
out.loc[loc, 'label'] = -1
else:
out.loc[loc, 'label'] = 1
return out
将所有这些放在一起:
data_ohlc = pd.read_parquet('data_dollar_ohlc.pq')
data_ohlc = \
data_ohlc.assign(threshold=get_vol(data_ohlc.close)).dropna()
data_ohlc = data_ohlc.assign(t1=get_horizons(data_ohlc)).dropna()
events = data_ohlc[['t1', 'threshold']]
events = events.assign(side=pd.Series(1., events.index)) # long only
touches = get_touches(data_ohlc.close, events, [1,1])
touches = get_labels(touches)
data_ohlc = data_ohlc.assign(label=touches.label)
元标记
在概念层面上,我们的目标是在我们预计会赢的地方下注,而不是在我们预计不会赢的地方下注,这就归结为一个二元分类问题(其中失败的情况既包括下注方向错误,也包括在我们应该下注的时候根本没有下注)。下面是我们刚刚生成的标签的另一种查看方式:

二进制分类问题提出了 I 型错误(假阳性)和 II 型错误(假阴性)之间的折衷。增加真阳性率通常以增加假阳性率为代价。
为了更正式地描述这一点,让我们首先定义:
ŷ ∈ {0,1,-1} : 观测的主模型预测
r:观察的价格回报
然后在预测时,主模型的混淆矩阵如下所示。

我们不太担心假阴性——我们可能会错过一些赌注,但至少我们没有赔钱。我们最担心的是假阳性,这是我们损失金钱的地方。
为了反映这一点,我们的元标签 y* 可以根据图表来定义:
y=* 1 : 真正
y=* 0 :
实际上,主模型应该有高的回忆——它应该以许多假阳性为代价,正确地识别更多的真阳性。第二模型然后将过滤掉第一模型的假阳性。
元标记实现
首先,我们创建一个主模型。在我们这样做之前,一个重要的预处理步骤是确保我们的训练数据有平衡的标签。

原始数据集中的标签在很大程度上受 0 值支配,所以如果我们在这些标签上训练,我们会得到一个每次都预测 0 的退化模型。我们通过应用合成少数过采样技术来创建标签计数大致相等的新训练数据集,从而解决这一问题。
from imblearn.over_sampling import SMOTE
X = data_ohlc[['open', 'close', 'high', 'low', 'vwap']].values
y = np.squeeze(data_ohlc[['label']].values)
X_train, y_train = X[:4500], y[:4500]
X_test, y_test = X[4500:], y[4500:]
sm = SMOTE()
X_train_res, y_train_res = sm.fit_sample(X_train, y_train)
接下来,我们将逻辑回归模型拟合到重新采样的训练数据中。注意,在这一点上,我们不应该期望我们的模型做得很好,因为我们还没有生成任何特征,但是当在基线模型上使用元标记时,我们仍然应该看到 F1 分数的提高。
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression().fit(X_train_res, y_train_res)
y_pred = clf.predict(X_test)

我们可以看到,我们的模型比我们的测试数据预测了更多的 1 和-1。最左和最右列的蓝色部分代表假阳性,我们打算通过元标记和训练二级模型来消除假阳性。
让我们将三重障碍预测映射到前面介绍的二元正/负元标签中,并检查混淆矩阵:
def true_binary_label(y_pred, y_test):
bin_label = np.zeros_like(y_pred)
for i in range(y_pred.shape[0]):
if y_pred[i] != 0 and y_pred[i]*y_test[i] > 0:
bin_label[i] = 1 # true positive
return bin_labelfrom sklearn.metrics import confusion_matrix
cm= confusion_matrix(true_binary_label(y_pred, y_test), y_pred != 0)

primary model
正如所料,我们没有看到假阴性和大量的假阳性。我们会尽量减少假阳性而不增加太多的假阴性。
.
.
# generate predictions for training set
y_train_pred = clf.predict(X_train)
# add the predictions to features
X_train_meta = np.hstack([y_train_pred[:, None], X_train])
X_test_meta = np.hstack([y_pred[:, None], X_test])
# generate true meta-labels
y_train_meta = true_binary_label(y_train_pred, y_train)
# rebalance classes again
sm = SMOTE()
X_train_meta_res, y_train_meta_res = sm.fit_sample(X_train_meta, y_train_meta)
model_secondary = LogisticRegression().fit(X_train_meta_res, y_train_meta_res)
y_pred_meta = model_secondary.predict(X_test_meta)
# use meta-predictions to filter primary predictions
cm= confusion_matrix(true_binary_label(y_pred, y_test), (y_pred * y_pred_meta) != 0)

secondary model
二级模型中的结果表明,我们引入了一些假阴性,但是我们从初级模型中消除了超过 30%的假阳性。虽然这并不总是一个有价值的交易,但记住交易的背景——我们错过了一些交易机会(假阴性),但这是减少许多在我们面前爆炸的交易(假阳性)的廉价代价。分类报告证实了我们的直觉,即通过 F1 分数测量,分类器的效率提高了。
# WITHOUT META-LABELING label precision recall **f1-score** support
0 1.00 **0.66** **0.79** 1499
1 0.14 1.00 **0.24** 81
micro avg 0.67 0.67 **0.67** 1580
macro avg 0.57 0.83 **0.52** 1580
weighted avg 0.96 0.67 **0.76** 1580# WITH META-LABELING label precision recall f1-score support
0 0.97 **0.76** **0.85** 1499
1 0.12 0.59 0.20 81
micro avg 0.75 0.75 **0.75** 1580
macro avg 0.55 0.68 **0.53** 1580
weighted avg 0.93 0.75 **0.82** 1580
虽然这两个模型都不太好,但请记住,我们只是在演示一种提高分类器效率的技术,这种技术可以在更大的数据集、更好的模型和更强大的功能上很好地工作。
元标记:定量方法
总的来说,元标注+二级模型的解释就是预测一级模型的置信度。在我们的例子中,主模型和次模型都是数据驱动的,但是并不总是这样。
除了提高 F1 分数,元标记还有另一个极其强大的应用——它可以在非 ML 模型之上添加一个机器学习层,包括计量经济学预测、基本面分析、技术信号,甚至是酌情策略。这提供了人类直觉/专业知识和数量优势的强大组合,因其可解释性和稳健性而受到许多资产经理的青睐。
摘要
标注观察值是监督学习的重要组成部分。在这个演示中,我们开发了一种标记金融资产观察结果的方法,以及一种元标记技术,以帮助在分类问题中获得更好的 F1 分数。我鼓励您将这些标注技术与其他数据集和参数相结合,并分享您的结果。感谢您的阅读,欢迎您随时提出意见/建议!
金融机器学习从业者一直在使用错误的烛台:原因如下
在本文中,我们将探讨为什么传统的基于时间的蜡烛图是一种低效的价格数据汇总方法,特别是在以下两种情况下:(a)高度波动的市场,如加密货币;(b)使用算法或自动交易时。为了证明这一点,我们将分析比特币-美元历史价格的行为,我们将研究为什么市场不再遵循阳光周期,以及为什么我们使用的数据类型可以成为竞争对手的优势。最后,我们将简要介绍替代和最先进的价格汇总方法,如成交量或分笔成交点不平衡棒线,旨在减轻传统烛台的缺点。

高度波动市场中的过采样和欠采样
加密货币市场极其不稳定。价格变化很快,在几分钟内价格下跌或下跌 5-20%之前,价格横向移动几个小时是很常见的。虽然长期交易策略可能在忽略日内波动的情况下仍然有利可图,但任何中期或短期策略(更不用说高频交易)都必须以某种方式解决波动问题。
在下面的图中,我们使用 5 分钟蜡烛图分析了 Bitfinex 交易所自 2013 年 3 月至 2019 年 4 月的比特币-美元对的波动性(数据从 CryptoDatum.io 获得)。具体来说,我们显示:(1)绝对价格变化直方图(计算为收盘价相对于开盘价的变化百分比),(2)直方图右侧特定点的蜡烛线比例(红线)以及(3)右侧蜡烛线产生的价格变化总量的比例(绿线)。

因此,我们可以观察到:
- 大多数 5 分钟蜡烛线(约 70%)的价格变化低于 0.25%,其中大部分几乎没有价格变化(第一个直方图峰值在 0.00–0.05%)。
- 20% (0.2049)的烛台解释了近 67% (0.6668)的价格变化总量。
- 2%的烛台解释了 21%的价格变化总量,表明在 2%的 BTC-美元烛台中,巨大的价格变化发生在 5 分钟的短时间内(高波动性)。
从第 1 点我们可以得出的结论是,基于时间的烛台显然对低活动期(活动被理解为价格变化)进行了过采样。换句话说,在 70%的蜡烛中,没有任何事情真正发生,所以问题是:如果我们想训练一个基于 ML 的算法,我们需要所有这些没有观察到变化的蜡烛吗?找到一种移除或丢弃大多数无意义蜡烛的方法对丰富我们的数据集有用吗?
基于时间的蜡烛图过采样低活动期和欠采样高活动期
另一方面,第 2 点和第 3 点表明,大多数价格变化发生在几个百分比的烛台上,这表明基于时间的烛台低于样本高活动期。这意味着,如果价格在 5 分钟内变化 10%,并且我们使用 5 分钟蜡烛线,我们的算法将无法看到基于时间的蜡烛线在开盘和收盘之间发生的任何事情,可能会错过一个良好的交易机会。因此,理想情况下,我们希望找到一种方法,在市场活动增加时多取样一些蜡烛,在市场活动减少时少取样一些蜡烛。
市场可能不再遵循人类的日光周期
我们使用基于时间的烛台的主要原因是因为我们人类生活在时间中,因此,时间是我们组织自己和同步我们的生物节律的非常方便的东西。此外,阳光周期对人类至关重要,因为它决定了清醒-睡眠周期,这与我们的生存有着生物学上的相关性。作为白天周期的结果,传统的证券交易所仍然在上午 9:30 开门(这样真正的人可以在醒着的时候交易)在下午 4 点关门(这样交易者可以安心睡觉——但是……他们会吗?).
随着技术的出现,自动交易机器人已经开始取代真正的人类交易员,特别是在加密货币领域,市场不再遵循白天周期,因为它们仍然全天候开放。在这种情况下,继续使用基于时间的烛台,一个纯粹的标准,人类便利的结果,有意义吗?洛佩兹·德·普拉多在他的书金融机器学习的进步中对此做了很好的总结:
尽管时间条可能是从业者和学者中最受欢迎的,但还是应该避免。[……]市场不会以固定的时间间隔处理信息。[……]作为生物,人类根据阳光周期组织自己的一天是有意义的。但是今天的市场是由算法操作的,这些算法在松散的人工监督下进行交易,对这些算法来说,CPU 处理周期比时间间隔更重要
每个人的数据都不是任何人的优势
为什么大多数好的交易算法是一个很好的秘密,因为当从一个不平衡的情况下,我们可以预测到另一个平衡时,钱就赚了。一般来说,平衡意味着每个人都已经意识到发生了什么,并且有足够的积极和消极的力量来保持新达到的平衡。
为了预测均衡的变化,我们必须反向而行,同时又是正确的:也就是说,我们必须知道一些其他人不知道的事情,并且我们的断言是正确的。我们必须找出大多数交易者不知道的失衡,否则我们就又回到了平衡状态。它通常被称为“零和”游戏,尽管我并不特别喜欢这个定义。
每个人的数据都不是任何人的优势
为了成为反向投资者,我们必须用新的创造性方法来看待和分析数据,让我们在尊重他人的基础上获得一定的优势。这就是我们用来训练算法的数据类型等小细节能够产生重大影响的时候。实际上,这意味着如果每个人都使用基于时间的蜡烛棒,为什么我们要和其他人一样使用呢?如果有更好的替代物存在,为什么我们还会使用基于时间的烛台?
另类烛台
很少有比读完德·普拉多的《金融机器学习的进步》一书的前几章更让我感到受启发的了。在他的书中,这位经验丰富的基金经理揭示了他 20 多年来一直用来管理千万富翁基金的常见做法和数学工具。特别是,他从第一手资料中知道,这些年来市场的行为发生了巨大的变化,与交易机器人竞争是一种规则而不是例外。在这种情况下,de Prado 介绍了一些替代类型的烛台,旨在取代传统的基于时间的烛台,并为金融领域带来必要的创造性和新颖性。
以下是德普拉多推荐的一些替代烛台或烛台的例子:
- 分笔成交点:每次预定数量的交易发生时,我们对一根棒线进行采样。例如,每次在交易所进行 200 笔交易,我们通过计算 OHLCV 棒线的开盘价-盘高-收盘价来取样。
- 音量条:每次交换预定音量时,我们都会对音量条进行采样。例如,我们在交易所中每交易 10 个比特币就创建一个条形。
- 不平衡棒线:我们分析交易顺序的不平衡程度,每次不平衡超过我们的预期时,我们就取样一根棒线。
在以后的文章中,我将分析书中描述的每一种可供选择的烛台。我们将了解如何构建它们,将它们的统计属性(如正态性和序列相关性)与传统烛台进行比较,并分析这些条形如何克服传统烛台的缺点。
感谢阅读,更多内容即将推出,敬请关注!
您愿意自己尝试一下我们在文章中提到的另类酒吧吗?在 CryptoDatum.io,我们努力提供最先进的加密货币数据集,以插入您自己的基于 ML 的交易算法。在 查看我们 https://cryptodatum . io。
所提供的信息仅用于教育目的。它绝不代表任何财务建议,信息必须“按原样”采用,没有任何形式的保证。
使用时间序列分类的金融故事
让我们教一台机器来描述市场是如何运动的。

Source: Pexels
说到叙事,金融市场特别有吸引力,因为它们只会随着时间的推移沿着一个轴移动。一个人可能会感到饥饿而快乐,但在金融领域,事情会起起落落;变强或变弱;膨胀或收缩。
作为人类,当我们看市场走势图时,我们可以很容易地得出一个市场在一段时间内表现的基本故事:

但市场用噪音弥补了这种简单。在上面的例子中,下面的三幅图像描述了一个相似的场景,第四幅描述了一个不同的场景:

对我们来说,这一切都很清楚。但是你能教计算机把这些输入转化成适当的叙述吗?
一种方法是,对于给定的财务表现时间序列,决定哪种给定的叙述最能描述它。
请注意,虽然这是一个自然语言生成(NLG)问题,但机器吐出的实际文本并不完全是由机器生成的;大部分是由人类预先写好的。
一些 NLG 从业者现在认为这种方法相当过时。这部分是因为确定选定的叙述让人感觉要么文本不够丰富,要么有太多的叙述不值得努力。(如果你不得不写 1000 种不同的叙述,这真的省去了麻烦吗?)
我们自以为是。我们可能相信人类渴望无限的叙事多样性,但即使在小说中,我们实际上也被很少几种类型的情节所吸引(七种,根据布克的说法;六,据冯内古特)。
在金融学中,最简单的叙述形式只考虑起点和终点,而忽略了中间的一切。例如:“黄金价格在一个月内上涨”。让我们称之为两点叙述。
这种形式的叙述是可以的,但是当讨论市场时,很大程度上取决于时间框架和背景。比方说,对于股票,你可能想更详细地了解一年来的涨跌情况;一个下午,不太多。以下是稍微丰富一些的例子:

现在,让我们来看看由资产管理公司施罗德发布的这些写得很好的总结要点:

请注意,第一个和第三个项目符号是 3 点记叙文,第二个和第四个是 2 点记叙文。
我们不介意叙述简单,因为句子的后半部分丰富了文本。第一部分是对发生的事情的叙述,第二部分——非常松散地——谈论为什么。

回到我们自己的问题,我们将限制自己不到 20 种叙事类型,包括 2 点和 3 点叙事。编写文本组件不会花很长时间,我们可以很容易地为相同的场景提供不同的措辞来进行混合。
我们的任务
很明显,这是一个分类的问题,因为我们问:给定一个新系列,它最像我们选择的哪种叙事类型?因此,我们将构建某种形式的分类器。
但是我们用什么来训练它呢?
我们没有任何真实世界的标签数据(也就是说,一大堆过去的性能曲线,每一条都标有最合适的叙述类型)。获取这些数据既费时又费力。因此,我们将制造贴有标签的样品,看起来非常接近真实的东西。
对于本文的剩余部分,以下是计划:
1.我们将为我们的数据生成基础:每个故事的大量样本。
2.我们将在每个数据样本中添加噪声,这样看起来就像金融市场的行为方式。
3.我们将使用现在有噪声的数据来训练分类器。
4.我们将在人造数据和真实数据上测试它,看看我们会得到什么。
1。生成叙述样本[这是容易的部分]
假设在一个月内,一个指数从 100 开始,下降,然后再次上升,以高于开始的位置结束。现在我们知道这是一个三点叙事。
但是,有各种各样的事情可以变化,同时仍然与故事保持一致:指数何时达到最低点?这个低点有多低?而适当上升又是什么意思呢?
我们的第一点总是固定的:在时间 0,我们将该值归一化为 100。但其余的可以摇摆不定,上面的问题说明了参数,把我们从一个叙事的一般描述到一个具体的实例。
因此,每种叙事类型都有几个参数,这些参数可以在一个范围内变化。为了生成大量的样本,我们从这些范围中随机选取值。比方说,对于自下而上的叙述,我们可能会得到一堆类似这样的例子:

但这些看起来一点也不像现实生活中金融市场的运行方式。在现实生活中,市场是不稳定的,所以它们会来回波动。我们需要做的是把我们整洁的叙事样本,并添加一些噪音。
2。添加噪声【这是统计部分】
我的问题是我需要添加足够多的噪声。
如果我加入的噪音太少,这些线会看起来不自然的直,完全不像真实市场的行为方式。但是如果我添加太多,或者不控制随机性,那么我华丽的叙述点可能会淹没在噪音中。
就我个人而言,我不知道这个问题的答案。
所以我求助于我丈夫的瑞士军刀,他建议我使用一种叫做布朗桥的东西。
这将是对它是什么以及它为什么起作用的一个没有痛苦的解释。出于这个目的,姑且说你严重醉酒。
酒吧刚刚关门,你的家在一英里外,你需要在日出前赶到那里。无论你多么努力想走直,你都会摇摇晃晃。当你试图移动时,有时你向前迈一步,有时后退一步。喝得这么醉,你完全不记得你之前做了什么。
一个小时后,你有什么进展吗?你追踪了一条随机路径,所以我们不能肯定。但是由于你沿着随机路径走的方式,我们知道你离起点的距离像钟形曲线一样分布,其方差取决于你的步幅和你走的步数。
又一个小时过去了。因为你没有变得更清醒,你离起点的距离再次像钟形曲线一样分布,但是现在方差是原来的两倍。就这样继续下去。
我的朋友,你正在经历布朗运动。
布朗桥是强制结束点的布朗运动:它只看布朗运动在特定时间(日出)结束于特定点(比如你家)的轨迹。所以,这又是一个统计过程——我们无法确切知道在开始和结束之间的任何给定时间点你在哪里。但这是一个过程,是以在特定时间具有特定价值为条件的。
在开始和结束之间,你仍然像个醉鬼一样走路。但是因为你的未来已经被预言(你将在日出时结束在家里),你不像以前那样自由自在地漫游。你在中距离左右最自由,越接近终点,你的自由就越小。
好了,别再用喝醉的比喻了,回到我们的话题。
我们希望固定的叙述点没有噪音,这样它们就不会移动。在这两者之间,我们希望它变得嘈杂,这样我们的股票市场运动就会跳跃。布朗桥正是这样做的:

3。分类【这是机器学习部分】
因为我们的数据是一个时间序列,我们完全处于时间序列分类的世界中。
但这是一个很大的世界。为了选择一种分类方法,我们将用一把大砍刀把它不相关的部分切掉。
首先,我们删减了用于预测目的的方法(例如,根据过去的数据猜测股票未来是涨还是跌)。那不是我们的任务。
然后,有一整类算法试图学习曲线的明确特征;2016 年对这一学派的评论见[1]。
显式特征对于那些对自己的数据没有太多假设的人来说非常有用,他们希望识别关键属性,以便能够生成类似的样本,或者描述哪些品质“造就”了某种具有代表性的事物。同样,也不是我们:
首先,我们已经对数据做了很多假设。另外,在样本和真实数据中,我们认为曲线的大部分只是随机噪声。
转向神经网络方法。在这里,我们分类,但不能完全描述一个类与另一个类的区别。最方便的是,今年 3 月发表的一篇文章回顾了时间序列分类的深度学习的不同方法[2]。
它有一个方便的图表,可以帮助我们进一步削减:和以前一样,我们对学习如何创建新样本的解决方案不感兴趣,我们对特征工程也不感兴趣:

这给我们留下了端到端的方法。此外,这个名字承载着让生活变得简单的承诺,我觉得这非常有吸引力。
上述论文的结果表明,对于我们的一维问题(=所有东西都在一个轴上运动,这个轴就是时间),在大多数数据集上表现最好的端到端方法是 ResNet。
作为一个最先进的图像分类器,这并不令人震惊。(这里有 FastAi 解释它,这里有 PyTorch 源代码。我最终用 ResNet-34 获得了足够好的结果。
4。结果【这是好玩的部分】
我几乎要让它听起来好像一切都立即工作。因为在现实世界中,任何人都只会关心:这有用吗?
起初,当然没有。但是出于非常有趣的原因。
答:关于捏造数据
当我看到最初的结果时,成功率并不大。但是很快就清楚了,对于许多曲线来说,尽管分类不同于的意图,但它的解释同样有效:

一旦我去除了噪音,重新训练了一个模型,当然,成功率激增;但这些数据不再像市场实际运行的那样。这意味着,一旦真正的噪音被加入,某些叙事类型之间的区别往往会变得模糊。
我发现这相当美妙:分类器本身往往不会出错;它获得了我所希望的显著意义,只是以一种我认为不可信的方式。但这确实提出了一个问题:我到底应该致力于改善什么?
有一件事几乎立刻引起了注意,那就是,仅仅以时间序列作为输入,该模型很难推断出序列开始和结束之间的关系。但这意味着失去重要的信息,万一一个故事的结尾是相对于起点来定义的。
考虑到 ResNet 的工作方式,这并不奇怪,而且通过将输入更改为双倍序列也很容易解决:

进一步的测试显示,对它来说,最难学习的事情之一是什么时候忽略噪音,看什么时候东西保持“差不多一样”。因此,解决性能问题的方法不是摆弄网络,而是更仔细地观察用于定义叙事的参数。我永远也不会完全摆脱因为能够讲述一条曲线的多个故事而产生的模棱两可。因此,我的目标是让它在捏造的数据上做得很好,但不是争取接近 100%的分类。最终,它在嘈杂的测试数据中达到了 85%以上的分类,错误很可能出现在密切相关的叙述中。
b .关于真实的过往数据
有了真实数据,一些正常化是必要的,以解释日本股市对油价的不同反应。例如,一些市场比其他市场有更多的内在差异。这些调整是利用我从 Investing.com得到的历史数据完成的。
但是,即使试图做到严格,这样的调整也不是没有问题。例如,发达市场的股票一直受到永无止境的超级宽松货币政策的支撑,这导致了长期以来创纪录的低波动性。这意味着,即使分类器的噪声参数根据“真实市场数据”进行了调整,它们仍然偏向特定的经济环境。
尽管如此,结果还是不错的。
很自然,我想给这种说法加上一个数字,但这又让我们回到了起点:获取标签数据这个非常常见的问题。
因此,我将以最近几个月真实市场数据的一个小样本作为结束:
**
结论
前面,我提到了为什么人们不喜欢将解释局限于一些固定的叙述的方法。但我从没说过是什么让它如此伟大。
我们不仅仅在金融市场上有折线图。我们到处都有。
由于叙述结构不会改变,将现有系统应用到另一个领域的成本将是编写少量的自然语言组件,并可能微调参数(特别是噪声模式)。这似乎不是一个巨大的代价。
无论如何——原谅这个蹩脚的双关语——这是一个很好的学习曲线。希望你也喜欢它!
来源:
[1] A. Bagnall,J. Lines,A. Bostrom,J. Large,e .基奥,The great time series class ification bake off:最近算法进展的回顾和实验评估 (2016),数据挖掘和知识发现。
[2] H. Ismail Fawaz,G. Forestier,J. Weber,L. Idoumghar,和 P.-A. Muller,时间序列分类的深度学习:综述 (2019),Arxiv。
从头开始在 20 行代码中查找人脸边缘

我在读一本机器学习的书,了解到边缘是机器学习图片中是否有物体(在这种情况下是人脸)的重要特征输入。看左边只有边缘的图形,人眼很容易分辨出是一张脸,不是吗?这同样有助于机器。
最初我认为寻找边缘本身需要一些“人工智能”。但我记得 Python PIL 库有 find_edges 过滤器,这绝对不是机器学习功能。然后,我开始思考如何找到边缘,并最终找到了一个非常简单的方法,只需要从头开始构建大约 20 行 Python 代码。
逻辑
我的逻辑很简单如下。
- 从上到下,从左到右扫描所有像素
- 如果像素与右侧或底部的相邻像素非常不同,则将其标记为边缘。
履行
我用 Python 代码实现了这一点,但是算法本身是独立于语言的。
以下是我们需要的库:
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import math
如果你是 Python 新手,先安装 Python3 ,然后用 pip 安装需要的库。
pip install -U Pillow numpy matplotlib
首先,我们将图像读取为像素的 2D 阵列。每个像素是 3 个值[红、绿、蓝]的阵列,每个颜色值是 0 到 255,例如像素值[0,0,0]是黑色。
filename = 'old.jpg'
im = Image.open(filename)
im_array = np.asarray(im)
如果图像太大,您可以先调整其大小。
n = 2 # resize times
im = im.resize( (int(im.size[0]/n), int(im.size[1]/n)) )
其次,我们定义一个函数来度量两个像素之间的差异。我使用三个颜色值之间的均方差的平方根,就像均方根偏差( RMSD )。

difference between pixel a and b
例如,黑色像素 A 为[0,0,0],白色像素 B 为[255,255,255],因此 A 和 B 的平方根差为:
sqrt(([255–0]**2 + [255–0]**2 + [255–0]**2)/3) = sqrt(255**2) = 255。
注:x**2 表示 x*x。
这里有一个窍门。我们需要首先将像素值转换为 int 类型,即int(a[i]),以进行减法运算,因为像素值为 ubyte[0–255],减法运算可能会变为负值,从而导致类型溢出问题。
然后我们从上到下,从左到右迭代图像数组。如果像素与其右侧或底部相邻像素之间的平方根差大于预定义的阈值,则将其标记为边缘像素,并将其设置为黑色[0,0,0],否则将其设置为白色[255,255,255]作为背景。
main part
您可以微调图像的阈值diff_threshold。10 是我用来创建顶部图像的值。增加较少边的阈值。例如,通过增加到 20,我得到了一个新的图像如下。

最后,使用 matplotlib 将新的边缘图像保存到本地。
plt.imshow(new_array)
plt.savefig('new.jpg')
如果您想在弹出窗口中显示新图像,请使用下面的代码。
plt.imshow(new_array)
plt.show()
你可以在这里找到组合的完整代码。
笔记
请注意,这种算法不是专门针对人脸检测的。它可以找到所有图像的边缘。你自己试试。
感谢阅读。如有任何疑问,您可以通过 Linkedin 联系我。
仅用空间查找最低公共祖先子树和最短依赖路径
使用空间作为一个解决所有问题的工具

Photo by Paula May on Unsplash
在之前的帖子:如何用 spaCy 和 StanfordNLP 寻找最短依赖路径中,我讲过如何用 spaCy 和 NetworkX 提取最短依赖路径(SDP)。
但是使用 NetworkX 有个问题。我们无法获得头部实体或尾部实体的索引。例如,我们有下面的句子。三联是Convulsions->(caused_by)->fever。但是这句话里有两个fever。
Convulsions that occur after DTaP are caused by a fever, and fever may cause headache.
NetworkX 解决方案
一个解决方案是为每个令牌添加一个索引,并指定要查找哪个fever。
import spacy
import networkx as nx
nlp = spacy.load("en_core_web_sm")
doc = nlp(u'Convulsions that occur after DTaP are caused by a fever, and fever may cause headache.')# Add pair to edges
edges = []
for token in doc:
for child in token.children:
**edges.append(('{0}-{1}'.format(token.text, token.i),
'{0}-{1}'.format(child.text, child.i)))**# Construct Graph with nextworkx
graph = nx.Graph(edges)# Get the length and path
**entity1 = 'Convulsions-0'
entity2 = 'fever-9'**print(nx.shortest_path_length(graph, source=entity1, target=entity2))
print(nx.shortest_path(graph, source=entity1, target=entity2))##### output #####
3
['Convulsions-0', 'caused-6', 'by-7', 'fever-9']
下面的edges看起来像。
In [6]: edges
Out[6]:
[('Convulsions-0', 'occur-2'),
('occur-2', 'that-1'),
('occur-2', 'after-3'),
('caused-6', 'Convulsions-0'),
('caused-6', 'DTaP-4'),
('caused-6', 'are-5'),
('caused-6', 'by-7'),
('caused-6', ',-10'),
('caused-6', 'and-11'),
('caused-6', 'cause-14'),
('by-7', 'fever-9'),
('fever-9', 'a-8'),
('cause-14', 'fever-12'),
('cause-14', 'may-13'),
('cause-14', 'headache-15'),
('cause-14', '.-16')]
这样,我们可以确保尾部实体令牌是fever-9而不是fever-12。
这个解决方案有点麻烦,因为 NetworkX 只接受字符串类型,我们必须在字符串中包含这样的信息。
只有空间的解决方案
sapCy 中的令牌类非常强大。它在每个标记中都有索引信息。但是怎么用 spaCy 找 SDP 呢?
经过一些研究,我发现我们可以利用 get_lca_matrix 函数。
In [11]: doc = nlp(u"This is a test")
...: lca_matrix = doc.get_lca_matrix()In [12]: lca_matrix
Out[12]:
array([[0, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 2, 3],
[1, 1, 3, 3]], dtype=int32)
Doc.get_lca_matrix:计算给定
*Doc*的最低公共祖先(lca)矩阵。返回包含祖先整数索引的 LCA 矩阵,如果没有找到共同祖先,则返回*-1*。
我们可以使用这个函数来查找 SDP。
import spacy
nlp = spacy.load("en_core_web_sm")doc = nlp(u'Convulsions that occur after DTaP are caused by a fever, and fever may cause headache.')**def get_sdp_path(doc, subj, obj, lca_matrix):
** lca = lca_matrix[subj, obj]
current_node = doc[subj]
subj_path = [current_node]
if lca != -1:
if lca != subj:
while current_node.head.i != lca:
current_node = current_node.head
subj_path.append(current_node)
subj_path.append(current_node.head)current_node = doc[obj]
obj_path = [current_node]
if lca != -1:
if lca != obj:
while current_node.head.i != lca:
current_node = current_node.head
obj_path.append(current_node)
obj_path.append(current_node.head)
return subj_path + obj_path[::-1][1:]
# set head entity index and tail entity index
**head = 0
tail = 9**
**sdp = get_sdp_path(doc, head, tail, doc.get_lca_matrix())** print(sdp)##### output #####
[Convulsions, caused, by, fever]
get_sdp_path()可以找到头实体和尾实体之间的 SDP。我们唯一需要的是输入头部实体索引和尾部实体索引。
在get_sdp_path()函数中,它实际上首先找到从头实体到 LCA 令牌的 SDP 路径,然后找到从尾实体到 LCA 令牌的 SDP 路径。最后,我们将两个子树组合在一起并返回结果。
查看我的其他帖子 中 同 一个分类查看 !
GitHub:bramble Xu LinkedIn:徐亮 博客:bramble Xu
参考
使用微软 NNI 查找 scikit-learn 管道的超参数

Source: https://www.pexels.com
什么是超参数搜索?
任何机器学习算法的目标都是在样本数据中找到模式,然后使用这些模式来预测给定的未知样本(测试)数据的结果。
机器学习算法学习的模式被编码在模型的权重(也称为参数)中。然而,机器学习算法可以应用于不同类型的数据,即使对于数据“类型”相同的情况,数据的分布也可能不同。
一个具体的例子,让我们看看 scikit-learn 的支持向量分类器(SVC)的 API。

Source : Screenshot of https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html
根据上图,您可以理解为不同类型和分布的输入数据集找到 C、核、度、Gamma 和 Coef0 的适当组合并不是一项简单的任务。
这些类型的参数(机器学习算法的输入,即上述示例中的 C、内核等)被称为超参数,找到它们的任务被称为超参数搜索或调整。
什么是微软 NNI?
微软 NNI(神经网络智能)是一个开源的神经架构搜索和超参数调整的 AutoML 工具包。
我了解超参数调优,但什么是 AutoML?
实际上,在许多方面,超参数搜索/调整是一个叫做 AutoML 的大领域的一个子领域。也就是说,根据你在哪里阅读 AutoML,你可能会得到不同的印象。在这里,我提供了通常与 AutoML 相关的三个主要特性
超参数搜索。我的神经网络有多少层、神经元、宽度和深度?我的 SVM 的 C & gamma 值应该是多少?
网络架构搜索(NAS) 。神经网络中的哪种层组合可以产生最佳性能。例如,使用 NAS 发现了名为 EfficientNet 的最新领先 ImageNet 网络。我把这个定义过于简化了,但是希望你能明白其中的要点!
基于云的 AutoML 。云提供商公开了一项服务,并告诉我提供您的训练数据集并指定任务(分类或回归),我将为您构建模型。回家睡觉吧!。这个定义意味着一个典型的机器学习管道的大部分(如果不是全部的话)方面(数据规范化、标准化、找到合适的算法、优化等)都是为你考虑的。这是一个自动化的机器学习。
还可以看到,上述特征很容易相互重叠。例如,有人可能认为网络架构搜索将/应该主要包括超参数搜索。
要进行超参数和架构搜索,一个简单的方法是尝试大量的组合,看看哪一个胜出!….但这不会有成效,不是吗?。这就是为什么有算法和技术的研究,为其他机器学习算法找到超参数。
这些超参数和架构搜索算法通常被称为 调谐器 ,因为大多数时候它们从一组参数开始,然后根据看到的结果选择下一组参数。简而言之,他们反复调整它们。
微软 NNI 被称为工具包,因为
- 它已经实现了许多已知的艺术调谐器,并为您编写自己的框架。
- 它为您的实验提供编写、运行、记录和可视化支持。
- 您不仅可以在本地机器上运行您的实验,而且它还可以与各种编排系统(如 Kubeflow、OpenPAI 等)很好地集成。还有足够的扩展点来集成新的或专有的机器学习平台。
- 它支持大多数机器学习框架(Tensorflow、PyTorch、MXNET、Chainer 等)以及库(scikit-learn、xgboost、lightgbm 等)。
因此,它不仅提供了运行和可视化的工具,而且还可以扩展(因此是框架的概念)进行定制。
编写和运行一个简单的 NNI 实验(试用版)
尽管 GitHub repo 列出了支持的框架(Tensorflow、scikit-learn 等),但对于大多数用例,微软 NNI 并不知道您正在使用的框架,甚至是您的培训模型。
下图显示了撰写典型试验所涉及的 3 种类型的文件:

Source : Screenshot of a trial example from https://github.com/microsoft/nni

Source: Screenshot of search-space.json
search-space.json该文件用于指定您正在搜索的超参数的范围!
这是您最终要编写的一种示例代码。

在这个例子中,您有一个 SVC 模型。进行试验包括 4 个步骤:
1)你加载你的数据。
2)你请求 NNI 给你试验参数。
3)将参数应用到正在训练的模型
4)你回分数。
基于config.yml文件中的optimize_mode(见下文),调谐器将通过建议下一组模型的超参数来帮助最大化或最小化分数。
这里是一个例子config.yml的文件,它被用来指定这个试验的配置选项。即在哪里运行、应该运行多长时间以及使用哪个调谐器。所以让我们看看 config.yml 文件。

Source: Screenshot of example config.yml
在配置文件中,您还可以指定到search_space.json和main.py.的路径
运行实验的时间:
nnictl create --config <path_to>/config.yml
在执行上述命令后,您将在终端上看到如下所示的响应:
INFO: Starting restful server...
INFO: Successfully started Restful server!
INFO: Setting local config...
INFO: Successfully set local config!
INFO: Starting experiment...
INFO: Successfully started experiment!
--------------------------------------------------------------------
The experiment id is egchD4qy
The Web UI urls are: http://223.255.255.1:8080 http://127.0.0.1:8080
--------------------------------------------------------------------You can use these commands to get more information about the experiment
--------------------------------------------------------------------
commands description
1\. nnictl experiment show show the information of experiments
2\. nnictl trial ls list all of trial jobs
3\. nnictl top monitor the status of running experiments
4\. nnictl log stderr show stderr log content
5\. nnictl log stdout show stdout log content
6\. nnictl stop stop an experiment
7\. nnictl trial kill kill a trial job by id
8\. nnictl --help get help information about nnictl
--------------------------------------------------------------------
你现在可以用你的浏览器来查看 NNI 进行的各种实验。每个试验本质上对应于炒作参数的一个组合。
还可以选择使用命令(如上所示)来获取终端本身的实验和试验状态。命令应该是不言自明的。
sci kit-学习管道
scikit-learn 是任何机器学习工程师最重要的库之一。它与许多优秀算法的实现捆绑在一起。scikit-learn 的一个我非常喜欢的特性是管道的概念。
管道旨在反映典型的机器学习实验,该实验由对输入特征的各种变换步骤组成,并最终以估计器(分类器或回归器)终止。转换器和估算器被称为流水线的组件。

在上图中,组件是缩放、降维、学习算法和预测模型。看看这些组件如何实现fit, transform and predict方法。
如果您与接口兼容,您还可以实现一个定制的转换器或估计器。
以下是如何使用 python 编写管道的示例:

Source: Author
上图中显示了两条管道-
a)make _ svc _ pipeline-标准化数据,然后应用 SVC
b) make_pca_svc_pipeline —规格化 pca(用于 dim 减少)和最终 svc
请注意,在这两个管道中,我是如何将所有的超参数(pca_components & svc_hparams) 作为函数的输入参数传递的。这样,我现在可以从 NNI 调谐器获得这些,并把它们传递给函数。
下面是 NNI 网络用户界面的一个标签的截图,显示了一个调谐器最后尝试的各种组合。

非常酷的可视化!我现在想编写由其他分类器组成的管道,例如,不使用 SVC,我想尝试 xgboost、lightgbm 等,但是我开始发现对于我的任务来说,这个过程是相当重复的,即对于每个实验编写config.yml, search-space.json and pipelines + entrypoint script to get the parameters from NNI Tuner, return the score and invoke the pipelines.
使用微软 NNI 进一步简化寻找 scikit-learn 管道的超级参数的任务不是很棒吗?
这就是我最终构建的东西,一个使用自省和配置文件来自动化上述步骤的小包。目前,我支持分类(很快也会支持回归)算法。
如果您对如何使用该软件包感兴趣,请继续阅读本文的其余部分。
scikit-nni:一个小 python 包,用于自动搜索 scikit-learn 管道的超参数
pip install scikit-nni
sci kit-nni(【https://github.com/ksachdeva/scikit-nni】T2)是一个辅助工具(和一个软件包),它:
- 生成 NNI 所需的配置(config.yml & search-space.json)
- 根据您的规范自动构建 scikit-learn 管道,并成为微软 NNI 运行的实验/试用代码
scikit-nni 的特点
- 使用微软 NNI 搜索 scikit-learn 管道的超参数
- 定义管道不需要代码
- 内置数据源读取器,用于读取 npz 文件进行分类
- 支持使用自定义数据源读取器
- 定义 NNI 配置和搜索空间的单一配置文件
该软件包及其使用方法在 https://github.com/ksachdeva/scikit-nni有很好的文档记录,所以请查看完整的文档和示例。
简而言之,这是你最终会做的事-
在使用微软 NNI 时,你不用写 3 个(或更多)文件,你只需写一个配置文件。一个例子是-
看看search-space.json 和config.yaml是如何嵌入到这个配置文件中的。他们遵循与微软 NNI 相同的语法和惯例,所以我没有发明任何新的语法。最大的区别在于,您不是编写 python 代码,而是在配置文件本身中指定 scikit-learn 管道。阅读上述 YAML 文件中的注释。
你会意识到,这种自动化不仅有助于找到超参数,而且现在很容易尝试各种管道,而不必经历用代码编写它们、从 NNI 检索参数、发回分数和编写入口点脚本的麻烦。
一旦您编写了这个配置文件,然后发出以下命令:
sknni generate-experiment --spec example/pca_svc.nni.yml --output-dir experiments
像往常一样运行您的 NNI 实验,即现在发出以下命令
nnictl create --config experiments/pca-svc-classification/config.yml
然而,您需要做的是编写一个返回训练和测试数据的 callable。在存储库的文档中,我展示了如何根据项目的需求编写这样一个数据源。在不久的将来,我将为分类和回归需求捆绑一些常见的数据源。
将来会添加更多的数据源,但是您可以根据您的项目需求编写一个数据源。定制数据源的一个简单例子是
结束语
- 微软 AutoML 是一个真正设计良好的,可扩展的和文档化的解决方案
- 感谢 NNI,使用最先进的调谐器现在只是一个简单的配置问题,你不必狩猎和学习如何使用各种调谐器算法。
- scikit-nni 利用微软 nni 来自动执行超参数搜索任务,而不需要在 python 代码中定义管道和实验。它还通过使用一致的语法将文件数量减少到 1 个来简化配置文件。
scikit-nni 非常新,请随时提交您可能遇到的任何问题(https://github.com/ksachdeva/scikit-nni)以及进一步改进它的想法。贡献总是被感激的!。
找到重要的差异
确定波士顿最昂贵的社区

A view of Back Bay, Boston
你运行一个分析,你会得到这样的结果:

平均值实际上是不同的吗?当有方法将统计的严谨性放在发现的后面时,不应该对“这看起来足够不同”做出决定。结果可能在两个方向上都令人惊讶——微小的差异可能具有重大意义;巨大的差异可以缺少它。
当我在我正在从事的 MSBA 项目中了解到这些概念时,我认为做一个案例研究来解释这些概念会很有趣。
在 Github 上的项目代号是 这里是 。
波士顿的街区都一样贵吗?
我以前写的一篇文章试图预测我居住的城市波士顿的房价。提高性能的一个关键因素是为属性所在的邻域添加一个变量。
如果邻域很重要,这意味着我们应该能够首先从统计上证明平均值之间存在差异,然后进入下一个层次,了解邻域之间的比较。
数据探索
为简单起见,我将提取最昂贵且位于市中心的街区:芬威/肯莫尔、后湾、比肯山、西区、北端、市中心和南端:

我还通过按邻域移除值的顶部和底部的 10%来将值修剪到中间的 80%,以考虑异常值和极值。由于分析是基于受异常值影响的平均值进行的,我们希望尽可能创建最具代表性的数据集。我们将分析每平方英尺的价格,作为最昂贵的替代。
让我们先来看看数据的分布:

我们看到不同群体之间存在一些差异。比肯山的中间值最高,但市区的第 75 个百分点高于比肯山。西区、北端、南端和肯莫尔都有类似的分布,但在第 25 和第 75 百分位之间有不同程度的分离。哪些是真正不同的?你能自信地说吗?

当看平均值时,我们看到比箱线图多一点的变化。视觉上,我们会说比肯山最贵,北端最便宜。这是真的吗?
确定差异
ANOVA 或方差分析衡量的是数据中一系列组的平均值相同的可能性。它通过测量“组间方差”和“组内方差”来做到这一点。
组间方差
组间方差和:每组的平均值减去总数据集平均值的平方,再除以自由度。
让我们把它翻译成英语。修整数据集中所有值的平均值为 788.55 美元。按邻域划分的平均值如上图所示。对于每一个平均值,我们减去 788.55 美元,对结果求平方,然后求和。自由度是样本平均数(7)减 1。
组内方差
简单地说,对于每个组,这是组的数量(7)减去 1 倍组的方差。然后,我们将这些值相加,除以观察总数(25,028)减去样本平均数(7)。
方差分析
与 ANOVA 相关的统计值 F 值是组间方差的除以组内方差的,并创建一个分布,我们根据该分布计算所有组具有相同均值的可能性(p 值):

解释上述内容的方法是,如果 p 值(Pr(>F))小于我们的显著性水平(通常为 0.05),我们“拒绝零假设”,即所有平均值都相等,并得出组间平均值不同的结论。
这并没有告诉我们哪些群体是不同的,只是说有差异。
这个发现给我留下了疑问。主要是,哪些不一样?
成对比较和 Tukey HSD
成对比较和 Tukey HSD(“真正的显著差异”…是的,确实是这个名字)是一种查看特定值对并确定其差异显著性的方法。
再说一次,让我们把它变成现实。笔架山比市区贵吗?肯莫尔比北端贵吗?
下面左边的成对表的解读方式是对于每一对(即左上角的北端和西端),均值相等的概率是多少?较低的值意味着平均值不太可能相等。
对于 Tukey HSD,它计算平均值的差异并围绕该差异创建一个置信区间,查看 95%置信区间(“lwr”和“upr”)是否包含范围内的 0。如果有,那么平均值没有显著差异,因为 0-表示没有差异-是一个看似合理的值。否则,差异是显著的。如果“diff”为负,则第一个邻域比第二个更便宜,反之则为正。


Pairwise Comparison (left) and Tukey HSD (right)
我更喜欢视觉,所以我们来看一下图中的 Tukey HSD:

但这意味着什么呢?
比肯山看起来比市中心更贵。现在,我们可以说它是。95%置信区间不包含 0,差异具有统计学意义。我们现在有把握地知道,在 2018 年,每平方英尺的评估值在灯塔山最高,其次是市中心,然后是后湾。
事实上,在我们的样本社区中,唯一没有统计学意义的一对是肯莫尔和北端;我们不能断定这两个街区的平均值在每平方英尺的水平上是不同的。
业务含义和结论
方差分析和相关统计在商业中的应用非常广泛。例如,如果我们关注市场中不同公司的表现,我们想问的第一个问题是:
- 是否有机会在市场中脱颖而出?
如果所有平均值在统计上是相同的,那么公司不得不超越业绩来真正影响客户赢得率(服务、产品范围等)。).换句话说,如果产品性能是赢得合同的基准标准,那么下一级投资资本应该投向哪里才能真正实现差异化?
一旦我们确定了差异化的机会所在,我们就想知道:
- 公司差异化在哪里?
这时,我们可以进行成对比较或 Tukey HSD 来确定公司之间的差异。文章开头图表中 A 组的 5.95 分和 C 组的 5.83 分实际上有区别吗?
结果是在给定的市场中理解:
- 哪里有差异化的机会?
- 如今的公司差异化在哪里?
- 公司应该如何分配资本(M&A、R&D 等)。)是在差异化标准上加倍努力,还是加快对表现不佳的改进?
我坚信学习的实际应用,虽然对我每天都知道和经历的社区进行分析很有趣,但对商业的影响以及这些可以做什么的探索更重要也更有趣。
递归程序设计

如何通过假装你已经解决了问题来解决问题
尽管递归的概念经常在大多数项目的早期就被引入到编程中,但在第一次遇到它时,它可能看起来很奇怪,并且可能会令人不快。这看起来几乎是自相矛盾的:我们如何能够用一个问题的解决方案找到另一个问题的解决方案?

Recursion can be a bit of a headache
对于那些试图掌握递归概念的人来说,我经常觉得首先认识到递归不仅仅是一种编程实践是有益的——它是一种解决问题的哲学,适用于可以处理和部分解决的问题,而问题的其余部分保持同样的形式,但在某些方面更容易或更小。这不仅仅适用于编程中的函数;我们可以用递归构造简单的日常问题。举个例子,就拿我来说,写这篇文章:假设我想写 1000 字左右,如果我的目标是每次打开都写 100 字,那么第一次写 100 字,给自己留 900 字。下一次,我写 100 字,只剩 800 了。我可以继续这样写,直到我剩下 0 个字。每次,我都部分地解决了问题,剩下的问题正在减少。
撰写我的帖子的代码可能是这样的:
write_words(words_left):
if words left > 0:
write_100_words()
words_left = words_left - 100
write_words(words_left)
我也可以迭代地实现这个算法:
write_words(words_left):
while words_left > 0:
write_100_words()
words_left = words_left - 100
如果你用任一实现完成函数调用write_words(1000),你会发现它们有完全相同的行为。事实上,每一个我们可以用递归解决的问题,我们也可以用迭代来解决(for和while循环)。那么我们为什么会选择使用递归呢?

Me writing this post recursively
为什么是递归?
信不信由你,一旦我们掌握了它,有些问题用递归比用迭代更容易解决。有时候递归效率更高,有时候可读性更强;有时递归既不更快也不更可读,但实现起来更快。有些数据结构,如树,非常适合递归算法。甚至有一些编程语言没有循环的概念——纯函数式语言如 Haskell 完全依赖递归来迭代解决问题。要点很简单:你不必理解递归就能成为一名程序员,但是你必须理解递归才能成为一名优秀的 T4 程序员。事实上,我甚至可以说理解递归是成为一个好的问题解决者的一部分,除了编程!
递归的本质
一般来说,使用递归,我们试图将一个更复杂的问题分解成一个简单的解决步骤和一个更简单的相同问题的剩余部分。然后,我们可以重复这个过程,每次都朝着解决方案迈出相同的一步,直到我们用一个非常简单的解决方案解决我们的问题(称为基础案例)。我们基础案例的简单解决方案与我们实现目标的步骤聚合在一起,然后形成我们原始问题的解决方案。

We can solve P by breaking the problem into a step towards the solution and a remaining smaller problem of the same form as the original, until we reach a simple solution to a small problem (a base case).
假设我们得到一些数据类型的实际数据,称之为 dₒ.递归的思想是假装我们已经解决了问题,或者根据我们需要定义的某种难度度计算了比 dₒ简单的这种数据类型的所有形式的期望函数f。然后,如果我们能找到一种方法,用一个或多个 f(d) s 来表达 f(dₒ) ,其中所有这些 d s 都比 dₒ 更容易(具有更小的度),那么我们就找到了一种方法来简化和求解 f(dₒ) 。我们重复这个过程,希望在某个时候,剩下的 f(d) s 会变得如此简单,以至于我们可以轻松地对它们实现一个固定的、封闭的解决方案。然后,当我们对越来越简单的问题的解决方案聚集并级联到顶部时,我们对原始问题的解决方案就会显现出来。
在上面写这个帖子的例子中,数据是这个文档中包含的等待写的文本,难度的度是文档的长度。这是一个有点做作的例子,但假设我已经解决了如何写 900 字的问题 f(900) ,那么解决 f(1000) 我需要做的就是写 100 字,然后执行我的 900 字的解决方案, f(900) 。
一个更好的例子是考虑斐波那契数列,其中第一个斐波那契数列是 0,第二个是 1,第个 ᵗʰ斐波那契数列等于前两个的和。假设我们有一个斐波纳契函数,它告诉我们 n ᵗʰ斐波纳契数:
fib(n):
if n == 0:
return 0
if n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
这个函数的执行是什么样子的?让我们试试fib(4):

Visualisation of a recursion tree showing the recursive computation that leads to fib(4) = 3, notice that computation is performed depth-first.
递归解决问题时采用的一个有用的口头禅是'假装它'直到你成功',也就是说,假装你已经解决了一个简单情况的问题,然后尝试减少更大的问题,以使用这个更简单情况的解决方案。如果一个问题适合递归,实际上应该只有少量的简单情况需要你显式地解决,也就是说,这种化简为简单问题的方法可以用来解决所有其他情况。这在斐波纳契例子fib中举例说明,其中定义fib(n)我们只是像已经计算过fib(n-1)和fib(n-2)一样行动,正如我们所希望的,这级联并减少问题到逐渐更简单的情况,直到我们到达fib(0)和fib(1),它们有固定和简单的解决方案。

Fake it ’til you make it!
递归策略
递归有点微妙,实际上取决于你试图解决什么问题。然而,我们可以想出一些大致的步骤,这些步骤或多或少可以把我们引向正确的方向。这一战略包含三个步骤:
- 订购您的数据
- 解决小案子
- 破大案
正如我以前说过的,我认为在我们学习的时候举一个例子是有用的,但是记住递归是依赖于问题的,所以在这里试着把重点放在一般的原则上。我们将使用反转字符串的简单示例,即我们希望编写函数reverse使得reverse('Hello world') = 'dlrow olleH'。我建议回去看看这些步骤是如何应用于斐波那契函数的,然后在其他一些例子中尝试一下(网上有很多练习)。
订购您的数据
这一步绝对是开始递归解决问题的关键,然而它经常被忽视或被隐含地执行。无论我们在操作什么数据,无论是数字、字符串、列表、二叉树还是人,都有必要明确地找到一个合适的排序,给我们一个方向,让问题变得更小。这种排序完全取决于问题,但一个好的开始是考虑明显的排序:数字有自己的排序,字符串和列表可以按长度排序,二叉树可以按深度排序,人可以按无限多种合理的方式排序,例如身高、体重或在组织中的排名。如前所述,这种排序应该与我们试图解决的问题的难度相对应。

Order that data, yee-haw!
一旦我们对数据进行了排序,我们就可以认为它是我们可以减少的东西。事实上,我们可以把我们的排序写成一个序列:
0 , 1 , 2 ,…, n 为整数(即对于整数数据 d ,度(d) = d )
[],[■],[■,■],…,[■,… , ■]用于列表
(注意 len = 0,len = 1,…,len = n,即用于列表数据 d ,度(d) = len(d) )
从右到左,我们通过一般(“大”)情况,到基本(“小”)情况。对于我们的reverse例子,我们正在操作一个字符串,我们可以选择字符串的长度作为问题的排序或度。
解决小案子
这通常是容易的部分。一旦我们有了正确的排序,我们需要查看排序中的最小元素,并决定如何处理它们。通常有一个显而易见的解决方案:在reverse(s)的情况下,一旦我们到了len(s) == 0并且我们有了reverse(''),那么我们就知道我们的答案,因为反转空字符串不会做任何事情,也就是说,我们只是返回空字符串,因为我们没有要移动的字符。一旦我们解决了我们的基本情况,并且我们知道我们的排序,那么解决一般情况就像以这样一种方式减少问题一样简单,即我们正在操作的数据的度向基本情况移动。我们需要小心,不要遗漏任何小的情况:它们被称为基本情况的原因是因为它们覆盖了排序的基础——在更复杂的递归问题中,遗漏基本情况是常见的,因此归约步骤越过了我们排序的合理末端,并开始对无意义的数据进行操作,或者导致错误。
破大案
这里,我们在排序中向右处理数据,也就是高度数据。通常,我们考虑任意次的数据,并旨在通过将其简化为包含较小次的相同问题的表达式来找到解决问题的方法,例如,在我们的 Fibonacci 示例中,我们从任意 n 开始,并将fib(n) 简化为fib(n-1) + fib(n-2),这是一个包含我们开始时的问题的两个实例的表达式,具有较小的次(n-1 和 n-2

Big case?
当谈到reverse时,我们可以考虑长度为 n 的任意字符串,我们可以假设我们的reverse函数对所有长度小于 n 的字符串都有效。我们如何利用这一点来解决长度为 n 的字符串的问题呢?嗯,我们可以把字符串反过来,除了最后一个字符,然后把最后一个字符放在前面。在代码中:
reverse(string) = reverse(string[-1]) + reverse(string[:-1])
其中string[-1]对应最后一个字符,string[:-1] 对应没有最后一个字符的字符串(这些都是 pythonisms)。最后一个reverse(string[:-1])项是我们的原始问题,但是是在一个长度为 n-1 的字符串上操作的,也就是说,我们已经将我们的原始问题表述为向解决方案迈进了一步,并结合了相同的降阶问题。
将我们的reverse函数的解放在一起,我们得到如下结果:
reverse(string):
if len(string) == 0:
return ''
else:
return string[-1] + reverse(string[:-1])

Visualisation of recursive function reverse operating on some sample data.
通常需要考虑不止一种递归情况,因为给定数据类型的数据可以采用稍微不同的形式,但这完全取决于问题。举个例子,如果我们想展平一个项目列表,其中一些项目本身就是列表,我们需要区分从列表中取出的项目是单个项目还是子列表,这至少会导致两种递归情况。
最终提示
提高递归的唯一真正方法是练习。在网上看看成千上万的递归问题中的一些,或者挑战自己提出你认为可能适合递归的问题。一旦你不可避免地掌握了递归的窍门,记住如果你发现自己很难递归地解决问题,那么尝试迭代。除了学习成为更好的程序员,递归还是一种解决问题的方法,让你的生活变得更容易。如果一个问题不适合递归,它就是不适合递归;当你花更多的时间处理那些适合递归或迭代方法的问题时,你会对此有一种感觉。
有时在更困难的递归问题中,我们上面看到的策略中的第 2 步和第 3 步采取了更循环的反馈循环过程的形式。如果你不能快速找到问题的整体解决方案,最好的过程是解决你能想到的递归/'大'案例,解决你能想到的基本/'小'案例,然后看看你的方法如何在不同的数据片段上突破。这将揭示任何缺失的基础和递归案例,或者任何相互之间交互不良并需要重新思考的案例。
最后,回想一下,知道您的排序是解决递归问题的最重要的步骤,您的目标始终是涵盖这种排序的向右(递归)和最左(基本)情况,以解决给定类型的所有数据的问题。

这是一个总结——感谢阅读!
如果你喜欢这篇递归介绍,请随时联系我( 汤姆·格里格 ),对未来的博文有任何想法、疑问或建议!
现在,回到我的数据科学帖子,敬请关注!
使用 Trie(前缀树)查找字符串中的所有单词
如何使用 Trie 来存储前缀,以便在字符列表中查找句子。

A Trie example, Wikipedia Commons. https://en.wikipedia.org/wiki/Trie
找到字符列表中的所有单词是一个有趣的挑战,可能有很多方法可以做到。例如,我们以字符串“thedogatethecat”为例,它可以通过两种方式进行解析。第一个是“那只狗吃了猫”&第二个是“那只做门猫”,两个版本在语法分析上都是正确的,但其中只有一个对我们人类是正确的。
下面的解决方案将找到两个版本,并不会试图找出哪一个在语法等方面更正确。
这个想法是使用英语中的所有单词创建一个前缀树或 Trie 树,然后逐个字母地找出 Trie 树中是否有单词。很明显,有一些关于复杂性的问题,但是使用 Trie,我们将我们的词汇表中的搜索复杂性降低到 O(M ),其中 M 是您要搜索的文本的长度,而不是搜索 O(N ),其中 N 是词汇表的长度。值得注意的是,Trie 的空间复杂度更高,如果你对它感兴趣,可以在 Google 上搜索这个话题。
然而,最终的时间复杂度略高于 O(M ),因为我们是逐字母递归搜索,所以我把计算留给读者作为练习。
在运行下面的代码之前,请通过“pip install pytrie”来安装 PyTrie 。我们用字符串中所有可能的单词填充 Trie,显然,我们可以添加额外的单词,但这不会增加这个例子的价值。对于真实世界的例子,请使用适当的英语词典。我们一个字母一个字母地迭代,直到找到一个有效的前缀,递归地发送字符串的其余部分,继续搜索,在返回的路上收集所有结果。
输出应该很清楚,第一个例子是测试 Trie 中的' t ',' th ',然后是' The ',一旦找到,字符串的其余部分将从单词' the '的末尾开始递归处理。
我们可以看到,我们要找的两个句子被算法找到了。在收集了所有有效的句子之后,我们大概可以使用一个英语单词嵌入模型来看看我们的句子在全球范围内是否有意义。
Ori Cohen 博士拥有计算机科学博士学位,专注于机器学习。他领导着 Zencity.io 的研究团队,试图积极影响市民的生活。
寻找贝叶斯乐高——第二部分
一次拯救一只脚

Photo by Markus Spiske on Unsplash
上一集《寻找贝氏乐高积木》,我和我亲爱的朋友乔试图找到一种方法来帮助乔树立信心,让他相信他已经捡起了他的孩子们留下的所有乐高积木。我的第一个建议是,我们通过对乔进行实验来确定他在任何给定的房间范围内捡一个乐高积木的概率,从而确定乔在捡乐高积木方面有多好。乔说,“不,继续这个计划!我真的不喜欢拿起乐高玩具。你就不能找个更好的办法吗?”
"告诉我更多关于你如何收拾孩子的事."
乔向我解释说,他通常会在孩子们上床睡觉后再去拿乐高玩具。乔喜欢每天晚上读点书或看看电视来放松一下。在这本书的两章之间,或者在电视广告中,他会拿起乐高玩具快速扫一扫。第一次扫描似乎获得了大部分的乐高积木,每次扫描都获得了前一次扫描中遗漏的部分。他一直这样做,直到他收工睡觉。
“乔,我想我们可以这样做。”
下次乔拿起乐高玩具时,我会让他做一点小工作。我让他数一数每次他捡了多少个并记录下来。他很不情愿地同意了,但是任何可以帮助他获得信心的事情,当他第二天早上起来走向咖啡机的时候,他都不会踩到乐高积木。
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-darkgrid')np.random.seed(42) # It's nice to replicate even virtual experimentsimport pymc3 as pm
import scipy.stats as stats
print('Running on PyMC3 v{}'.format(pm.__version__))
> Running on PyMC3 v3.6
乔汇报道
几天后,我收到乔的电子邮件:他已经按照要求做了。Joe 报告说,他扫了六次,每次都捡了以下数量的乐高积木:
Removed[1:]
> array([135, 48, 11, 4, 3, 0])
那天晚上,我邀请乔过来吃晚饭,做一些贝叶斯分析。
与 PyMC 共进晚餐
在吃完我们的羽衣甘蓝、鳄梨和黑豆玉米煎饼碗的晚餐后,乔和我舒适地围着大屏幕进行分析。
我向乔解释说,每次他打扫房间时,就好像每个乐高都有可能被捡起来。我们不知道到底有多少乐高积木,这就是我们试图用概率来确定的。然后,Joe 可以将这个数字与他获得的总数进行比较,看看他是否想再进行一次传递。因为我花了几天时间来解决这个问题,所以我画了一个模型,然后回去解释它:

在这种情况下, p 表示乔捡起一个乐高积木的概率。概率 p 被建模为贝塔分布(在第 1 部分中我们介绍了为什么使用贝塔分布)。剩余的乐高数是 N_i ,在 i 扫完被乔捡起的乐高后;乔总共扫了 n 次。每扫一次,我就移除一个乐高积木。我们试图确定的是 N₀ ,乔的后代最初留下的乐高积木的数量。 R₀ 被设置为零处理边界条件,其中乔在第零次扫描时拾取零个乐高积木。
为了完成这个模型,我们需要一些关于 N₀ 、 α 、 β 的先验分布。与第一集一样,我们选择参数,使建模前的分布信息较弱:

先验概率分布的参数与第一集相同。我问乔他的孩子可能会留下的绝对最大乐高玩具。Joe 估计大约有 2000 个,所以为了安全起见,我选择了最大值,N _ max3000 个,并使用 0 和 N_max 之间的先验概率的均匀分布来模拟留下的乐高积木的初始数量。
现在剩下的就是在 PyMC 中制作模型,并检查结果,看看它们是否能帮助 Joe。
模型设置和拟合
从模型到 PyMC 代码的转换实际上是一种直接的转换,并且适合默认的不掉头采样器。经过一番折腾后,调优样本的数量超过了默认值,目标接受率也比正常情况下设置得稍高一些;这样做是为了处理这个问题中的一些有问题的后验。很好,PyMC 库通过模型拟合检查和突出可能的问题,很好地帮助用户调整这些参数。
我想让乔在每次清扫后大概知道还剩多少乐高积木。这将有助于 Joe 更好地决定何时停止,因为我知道 Joe 非常想减少他在乐高玩具收集上投入的精力。这样,我们将从最少的信息开始,仅是在第一次扫描中删除的数字。此外,还包括了剩余乐高玩具估计数量的确定性计算,主要是为了让 Joe 更容易确定剩余的乐高玩具数量。即使 n_remaining_est 是确定性的,它也是随机变量,因为 n_remaining_est 的两个输入变量是随机变量。
sweep_model = pm.Model()with sweep_model:
n_0_est = pm.DiscreteUniform('n_0_est', lower=0, upper=3000)
alpha_sweep = pm.HalfCauchy('alpha_sweep', beta = 2.)
beta_sweep = pm.HalfCauchy('beta_sweep', beta = 2.)
p_sweep = pm.Beta('p_sweep', alpha=alpha_sweep, beta=beta_sweep)
removed_sweep_1 = pm.Binomial('removed_sweep_1', p=p_sweep,
n=n_0_est, observed=Removed[1] )
n_remaining_est = pm.Deterministic('n_remaining_est', n_0_est - removed_sweep_1)with sweep_model:
trace = pm.sample(50000, tune=7500, random_seed=123, progressbar=True, nuts_kwargs={"target_accept":0.95})
模型评估
完成模型拟合后,Joe 和我快速浏览了一下轨迹图。我们关注的是乐高玩具的初始数量和第一次扫描后剩余的估计数量。
不幸的是,仅仅一次清扫后,剩余的乐高积木数量与清扫前相比并没有减少多少。从数学直觉来看,这是有意义的:有两个参数需要估计,捡起一个乐高的概率和乐高的初始数量,但只有一个已知变量,第一遍捡起的乐高数量。仅仅用一个已知方程来求解两个未知变量是很困难的。
pm.traceplot(trace, varnames=['n_0_est', 'n_remaining_est'], combined=True);

pm.summary(trace, varnames=['n_0_est', 'n_remaining_est']).round(2)

第二次扫描
在这一点上,Joe 不确定这项工作是否值得。我鼓励乔,我们应该继续,因为他的脚如果没有别的。我们坚持下去,根据 Joe 提供的第一个和第二个观察到的扫描数据创建一个模型。模型创建代码开始看起来有点重复,但是为了快速实验,我做了一些代码的“复制-粘贴”并运行模型,想着如果它工作了,我以后会重构它。
在此模型中,我们从估计的初始乐高数中减去第一次扫描后剩余的乐高数,并将其用作第二次扫描剩余乐高数的输入。我们正在模拟重复的、连续的二项式试验。
sweep_model = pm.Model()with sweep_model:
n_0_est = pm.DiscreteUniform('n_0_est', lower=0, upper=3000)
alpha_sweep = pm.HalfCauchy('alpha_sweep', beta = 2.)
beta_sweep = pm.HalfCauchy('beta_sweep', beta = 2.)
p_sweep = pm.Beta('p_sweep', alpha=alpha_sweep, beta=beta_sweep)
removed_sweep_1 = pm.Binomial('removed_sweep_1', p=p_sweep,
n=n_0_est, observed=Removed[1] )
n_1_est = n_0_est - removed_sweep_1
removed_sweep_2 = pm.Binomial('removed_sweep_2', p=p_sweep,
n=n_1_est, observed=Removed[2] )
n_remaining_est = pm.Deterministic('n_remaining_est', n_0_est - removed_sweep_1 - removed_sweep_2)with sweep_model:
trace = pm.sample(50000, tuning=7500, random_seed=123, progressbar=True,
nuts_kwargs={"target_accept":0.95})
“乔,我们现在有所进展了。”
快速浏览一下模型结果,我们看到乐高剩余部分的估计范围已经缩小到 9 到 62 之间,置信度为 95%。在这一点上,Joe 受到了鼓舞,认为清点所有这些乐高积木可能是值得的。他想继续,但我有其他计划。
有点晚了,我决定是时候重构这段代码了。乔回家过夜,我们答应明天聚一聚。有了一点干净的代码,我希望能够移动得更快。
pm.traceplot(trace, varnames=['n_0_est', 'n_remaining_est'], combined=True);

pm.summary(trace, varnames=['n_0_est', 'n_remaining_est']).round(2)

通过数学清理代码
第二天晚上,乔手里拿着外卖来了。我抓起我的笔记本电脑,与乔分享了一夜之间发生的事情。
"这确实是一个多项问题."
多项式分布模拟 k 个可能事件中的某个事件发生若干次 n 的次数。它是二项分布的推广。想象重复投掷一个六面骰子(6 个可能的事件,1 到 6,骰子的每一面一个),并计算它每一面朝上落地的次数。多项式模型允许每个事件有不同的发生概率。继续 6 面骰子的类比,如果骰子以某种方式具有不同的每一面向上降落的概率。这就是多项式分布模型的过程。乔有点迷惑地看着我,所以我继续说。
在我们的场景中,每个落在 n 清扫或中的乐高积木都还没有被拾起。每扫一次,加上没被捡的事件,就像是骰子的一面。乐高在第一次扫描中被捡起的概率是未知的,被建模为 p 。乐高在第二次扫描中被捡起的概率是乐高在第一次扫描中没有被捡起的概率和在第二次扫描中被捡起的概率。剩余乐高积木的概率就是在所有的扫描中,将所有的乐高积木被选中的概率相加后剩下的概率。

这里 R 是每个给定概率的计数向量(也就是说,它是每次扫描中捡出的乐高积木的估计数量), N₀ 是试验次数(或者在我们的例子中,是最初留下的乐高积木的估计数量), p_i 是在扫描中捡出一个乐高积木的概率, i , p_remains 是一个乐高积木没有被捡出的概率
虽然 PyMC3 具有多项分布,但它不具有这种“不完全多项分布”,多项总体的大小是未知的,并且是待估计的。已经有几篇关于从观察到的图中估计多项人口规模的论文;我最喜欢的是 Sanathanan 的。我致力于采用贝叶斯方法并使用 PyMC3。经过一点试验,我确定可行的解决方案是推广之前尝试的重复二项式方法。我写了一个封装了移除模型的函数,称这个新模型为RemovalMultinomial。虽然我肯定有更好的方法来做到这一点,乔和我已经准备好了。
# Done quickly to make code a bit easier to deal with
# Doesn't do much / any error checking of the inputs, assumes observed information
def RemovalMultinomial(basename, n, p, observed):
remaining = n
results = []
for idx, p_idx in enumerate(p):
i = idx+1
name = basename + '_{}'.format(i)
removed = pm.Binomial(name, n=remaining, p=p_idx, observed=observed[idx])
results.append(removed)
remaining = remaining - removed
remaining = pm.Deterministic( basename+'_remaining', remaining)
results.append(remaining)
return results
有了这个新函数,模型的设置就短得多,只需通过扫描次数就可以参数化。我们将用 3 次扫描来做这个模型,比上次多一次。这有助于乔和我了解我们一次又一次地收集了多少信息。此外,这可能有助于 Joe 根据他自己的风险模型确定他何时可以在传球中“提前”停止,这些风险模型是指地板上残留的一定密度的乐高玩具,如果他踩到它们,会给他带来疼痛。现在,乔一直在寻找乐高积木,直到他经过时,没有看到任何要捡起的乐高积木。
n_sweeps = 3sweep_model = pm.Model()with sweep_model:
n_0_est = pm.DiscreteUniform('n_0_est', lower=0, upper=3000)
alpha_sweep = pm.HalfCauchy('alpha_sweep', beta = 2.) # Weakly regularizing prior
beta_sweep = pm.HalfCauchy('beta_sweep', beta = 2.) # Weakly regularizing prior
p_sweep = pm.Beta('p_sweep', alpha=alpha_sweep, beta=beta_sweep)
p_multinomial = [ pm.Deterministic('p_{}'.format(str(i+1)), p_sweep) for i in range(0,n_sweeps) ]
removed = RemovalMultinomial('rr', n=n_0_est, p=p_multinomial, observed=Removed[1:] )with sweep_model:
trace = pm.sample(50000, tuning=7500, random_seed=123, progressbar=True,
nuts_kwargs={"target_accept":0.95})
“这看起来相当不错!”乔说
乔和我都很兴奋。在这最后一轮,经过三次捡乐高积木的扫描后,现在有 95%的置信区间是零(!)和剩余的十三块乐高积木。乔对这些结果非常满意,并认为这可以帮助他决定是继续传球还是收工。
pm.plot_posterior(trace, varnames=['n_0_est', 'rr_remaining']);

pm.summary(trace, varnames=['n_0_est', 'rr_remaining']).round(2)

一个最终模型
在最后一个模型中,我们想测试 Joe 目前的启发式方法对停止有多好。乔通常会在经过时停止寻找乐高积木,因为他发现没有更多的乐高积木可捡。现在我们有了数学,我们可以量化,至少基于这些结果和模型的假设,Joe 采用的停止策略的质量。我们创建了一个所有六个回合的模型,并使用它来估计剩余的乐高积木的数量。这里没有显示代码,但是可以在文章末尾链接的笔记本中找到。
plt.figure(figsize=(4,3), dpi=300)
plt.hist(trace['rr_remaining'], density=True, bins=range(0,5), align='left')
plt.xlabel('Estimated Legos Remaining')
plt.xticks(range(0,5))
plt.ylabel('Probability')
plt.title('Probability Density Function of Estimated Legos Remaining')
plt.grid(True)
plt.show();

有些模型是有用的
此时,Joe 和我对模型结果非常满意。看起来,至少在这个例子中,有大约五分之一(20%)的机会至少有一个乐高剩余。Joe 说,如果我把它清理干净并粘贴到移动应用程序中,他就可以拿走并使用它。乔是一个非常有趣的人!我同意将此代码签入 GitHub,并向 Joe 展示如何使用它。
一切都好,乔回家了。
然后几天后,电话来了,“汉克,第二天我还是发现乐高玩具,而且比我认为模型预测的要多得多。”
在简单模型中发现商业价值
现实世界中的数据科学
复杂模型失败时,一般线性模型如何成功

直到今天,我在野外看到的最有用的预测模型是一般的线性模型。这不是最强大的模型,它没有做出最准确或最精确的预测,房间里的工程师们也没有对谈论它感到兴奋。然而,业务用户信任并理解它的输出,并且该模型被吸收到他们的决策过程中。
我相信有一个很好的解释,为什么这个模型被非技术团队所接受,并在许多复杂模型失败的地方取得了成功。该模型完成了比重复推荐或标签更有价值的事情——它促进了需要做出重要产品决策的团队之间的对话。尽管这个模型很简单,但它的影响波及了整个企业。
我从这一结果中看到了一些有价值的教训,在整篇文章中,我的目标是阐明这些教训。
第一课:给非技术团队一个关心的理由
在技术团队和业务团队之间往往有一堵墙。聪明人生活在这堵墙的两边,但总的来说,他们说着不同的语言,被不同的动机所驱使。对工程师来说似乎重要的事情对产品经理来说可能显得微不足道,反之亦然。
如果我们对什么是真正重要的看法不一致,很自然的,电线会交叉,沟通会发生错误。对于我参与的许多项目来说,这的确是事实。这种现象并不仅限于技术人员和非技术人员;当顾问为他们的客户工作时,这种情况经常发生。如果我们不能持续地在期望值上保持一致,那么客户想要的和实际交付的路径自然会出现分歧。
我们可以从中学到的是:你讨论模型的方式很重要。如果你面对的是技术观众而不是非技术观众,你可以用不同的方式组织讨论,注意强调对你的观众来说重要的观点。您可能对业务开发演示中讨论的细节不感兴趣,所以不要错误地假设任何人都和您一样对您的模型的技术细节感兴趣。
我相信这有助于解释为什么一般的线性模型能够吸引决策者的注意力,即使“更好的”模型被忽略。线性模型易于理解,它给了企业一个关注它的理由,因为它清楚地说明了过去的决策、客户行为和业务绩效之间的关系。在这种情况下,该模型能够解决业务用户的问题,并且能够更好地为他们的决策过程提供信息。
第二课:强调结果,而不是技术
随着我们的技术团队所拥有的工具不断进步,我们考虑技术团队和非技术团队之间日益扩大的鸿沟变得越来越重要。例如,想想那些喜欢你不喜欢的爱好的人,想想当他们开始谈论你听不懂的行话时,你有多快就听不进他们的谈话。现在考虑一下技术人员的词汇和他们的非技术同行的词汇有什么不同。这与智力没有任何关系,但这确实意味着根据你属于哪个团队,会有不同的“商业方言”。
尽管工作场所的不同学科之间存在差异,但在那里工作的团队都存在,因为他们服务于一些有益于业务的功能。就像你在国外旅行时可能会和不讲你的语言的人找到共同点一样,你也可以和你的同事找到共同点,不管你是做什么工作的。关键是不要失去你们共同的目标。
因此,当开发一个旨在推进业务目标的预测模型时,您应该后退一步,考虑该模型对您的业务对手意味着什么。你可能爱上了一个新的框架,或者熬夜思考一个新的算法,但是这对业务真的重要吗?
我认为这无关紧要。是的,你应该和其他同样对你感兴趣的领域感兴趣的同行一起庆祝你的成功,但是预测模型只有在被使用时才是有用的。如果企业确信你的模型能产生结果,他们才会使用你的模型,所以关注结果似乎是一个成功的策略。
给我的客户的业务用户留下深刻印象的一般线性模型并没有给他们留下深刻印象,因为它的实现或编写它的编程语言。这给他们留下了深刻的印象,因为它产生的结果可以融入他们的决策过程。结果以他们理解的行话呈现给业务部门,这是数据科学团队在门口检查自我的一个例子,以便在共同点上满足业务需求,从而推进整个业务的目标。
第三课:把结果解释为指导,而不是答案
我倾向于假设我的模型中存在大量的偏差。这些年来与数据的密切合作对我产生了清醒的影响,现在我知道香肠是如何制作的了。建立一个模型需要很多假设;在生成数据、收集数据、清理数据、处理数据时有一些假设,最后还有一个假设,即分析历史数据可以准确预测未来的行为。就像“电话”游戏一样,这种假设系统几乎可以确保输出是有缺陷的。
出于这个原因,我明白了把模型的结果当作一盏指路明灯,而不是某个真理,通常会更好。我们在雾中飞行,我们模型的结果是指引我们的灯,但它们不一定是最终的目的地。
我发现这种方法引起了企业决策者的共鸣。如果数据是有缺陷的,并且数据通过的所有中间系统都是有缺陷的,那么将模型的结果解释为纯粹的真理是不明智的。此外,许多人不喜欢被告知该做什么,如果你告诉非技术人员听你的模型,他们就会忽视你。
然而,如果你把你的模型当作对话的向导,并且你能证明你的模型所暗示的关系是真实的,那么你就处于有利地位。对于非技术团队来说,这是一个味道更好的配方,他们可能不一定相信数据是所有事情的答案。
这是一般线性模型如此成功的原因之一。开发这一模型的分析团队并没有声称拥有所有答案,而是小心翼翼地专注于他们的模型所确定的关系。商业用户对该模型的结果反应良好,我认为他们的积极反应可能是由于该模型量化了他们在某种程度上已经意识到的某些关系。
第四课:好的模型并不总是保证好的决策
我留给你们的最后一则轶事表明,即使是设计良好的模型也可能导致糟糕的结果,这取决于对它的解释。就我提到的一般线性模型而言,在它取得初步成功几个月后,关于如何利用该模型帮助量化的关系,不同的观点开始出现。
例如,该模型指出的一个观点是,购买产品“X”的客户与倾向于导致其他有利可图的购买的行为相关联。总之,谈话最终变得过度集中在如何销售更多的产品“X”上。最终的商业决策是为了不惜一切代价卖出更多的产品“X ”,这违背了分析团队的建议,并且结果远不如业务预期的那样有利可图。他们选择的策略导致了他们的产品市场中的非预期行为,这点击了模型所揭示的关系的重置按钮。
在我看来,这不是模型的失败。该模型正确地识别了存在的关系。问题是,该企业尝到了最初见解的成功滋味,却草率行事了。他们暂时接受了数据驱动的流程,但一旦他们觉得事情朝着正确的方向发展,他们很快就会转身将这些数据驱动的流程从他们的决策中删除。对话很快变成了政治话题,业务团队内部形成了不同的阵营。人们加强了他们的防御阵地,随之而来的沟通失误降低了预测模型最初的成功。
我敢肯定,从这种情况下,我们还可以学到更多的东西。我在这里的目的是说明技术团队,特别是分析和面向数据的团队,可以做些什么来提高他们的工作被业务利用的几率。虽然这里描述的结果并不完全是一个童话故事的结局,但该模型在渗透到战略性商业对话的能力方面是独一无二的成功。
最终,我认为如果他们的业务部门继续与构建预测模型的技术团队合作,该公司会受益更多。也许他们已经意识到他们所做的一些糟糕的假设,即把所有的鸡蛋放在增加产品 X 销售的篮子里。总的来说,我认为分析团队应该在决策过程中拥有更大的发言权。不过,那是改天再聊。
我希望你喜欢阅读!谢谢收听。
寻找循环模式:如何用 Python 实现 STFT 的教程
如果你听说过 Python 和傅立叶名词,你可能会发现这篇文章很有用:在这里,我将探索一种简单的方法来实现 Python 中的短时傅立叶变换,以便运行频率分析来检测给定信号中的循环模式。我们开始工作吧!
去年,我和我研究组的同事应邀参加了一个 Kaggle 的比赛。这个想法对我来说听起来非常有趣,因为它是关于地震数据的,即时间序列,这在某种程度上与我习惯处理的信号类别有关——出于好奇,我将小波应用于从大脑信号中检测睡意。顺便说一下,当 Stéphane Mallat 将小波分析应用于地震和地质数据时,它就变得很有名了。
但是在我们开始处理小波之前(我打算在不久的将来回到小波),这里的主要思想是熟悉周期信号。我的意思是,一个时间序列代表一些周期性事件,不时重复。因此,这通常可以被称为一种模式。
你可能会发现以最不同的形式表现的模式,但有一个是特别的圆:白天和晚上的时间,一年中的季节,水力发电产生的电力,所有这些都与一个圆形源有关(或者至少是一个近似的,因为地球是一个扁球体,它以椭圆路径围绕太阳运行。
从数学的角度来看,任何循环模式都可以用正弦和余弦三角函数来表示,正如法国数学家和物理学家约瑟夫·傅立叶所证明的那样,他在十九世纪初建立了今天所知的傅立叶级数和傅立叶变换。

The sine and cosine functions are the projections of a circular movement over a vertical and horizontal axis, respectively.
我假设你已经熟悉三角信号及其与频率的关系。在任何情况下,重要的是要注意,周期性或循环信号将在给定的时间内重复自身,称为周期 ( T )。而频率 ( f )则由每秒的重复次数组成,以赫兹(Hz)为计量单位,以此向德国物理学家海因里希·赫兹致敬。
在接下来的章节中,我们将以一种非常实用的方式探索这些概念(我还假设您已经习惯了 Python)。我们将首先弄清楚如何生成和可视化一些周期波(或信号),然后移动到短时傅立叶变换实现。最后,我们将看到一个例子,说明如何将这种分析应用于一个非三角函数,而是一个节奏函数,如大脑信号。
1.产生周期性信号
在数据科学——这里我考虑的是与之相关的所有学科,比如模式识别、信号处理、机器学习等等——深入了解我们的数据(也就是信号)在插入分析或分类工具时的表现总是有用的。问题是,起初我们通常对我们的数据知之甚少——在现实世界中,这些数据可能是复杂和嘈杂的——事实上,寻找分析工具就是试图理解它们。
在这种情况下,最好的替代方法是使用简单且众所周知的数据来探索分析工具。诺贝尔奖获得者理查德·费曼在 1988 年去世后,在他的黑板上留下了下面这句名言:“我不能创造的东西,我不理解”——我们将首先产生自己的周期信号。
时间序列通常是抽样过程的结果。当这个信号产生时,正如这里的情况,我们还必须确定信号应该出现的时间。下面的函数模拟采样过程,并将初始和最终时间样本作为输入。换句话说,它返回我们的信号将被放置的 x 轴基准。
让我们记住正弦信号是由函数产生的:

其中 f 是信号 y(t) 的频率,单位为赫兹, t 是上述代码生成的时间序列。然后将该等式转录为 Python:
几乎与生成信号同样重要的是提供一种可视化信号的方法。使用 Matplotlib 库,可以修改以下函数来查看任何时间序列:
编写一个包装函数来创建和可视化一个正弦函数,并将频率和幅度作为输入参数,这听起来也很实用:
最后,我们现在将一起执行这些函数,以便获得一些正弦波:
然后给出以下结果:


它可能看起来很简单,但重要的是要有一个我们确切知道它是如何产生的信号——我们持有它的模型,即它的公式。在上面的第一张图中观察到,频率为 0.1Hz 意味着信号将每 10 秒重复一次。在第二张图中,我们复制了信号的振幅,现在信号每秒钟重复一次。
将我们用 numpy.concatenate 方法定义的函数放在一起,如下所示,我们可以生成更复杂的波形:

从我们的第一张图表可以清楚地看出,当我们处理低频和单一频率时,我们仍然只能通过时域信号中的视觉检查来识别循环模式(例如,在一秒钟内计数顶部)。然而,对于更复杂的波形(见上图中 1s 和 2s 之间的间隔),显然需要一种工具来识别任何循环模式。
2.短时傅立叶变换
要完全理解频域中的时间序列表示,需要深化线性代数和微积分,以便将信号展开成一系列函数。
不过,这里不会采用这种方法:那些已经理解这种数学抽象的人会发现用 Python 实现频率分析的实用代码。同理,第一次面对周期性信号的人,可能会从这些简单的例子开始加深自己的理论理解。
在我们开始编码之前,我将提供一个简短的定义(请不要害怕这些符号……如果你愿意,你可以直接向下翻页跳过这一部分):傅立叶已经证明,可以将大量函数展开为纯调和函数级数(即正弦和余弦之和)。顺便说一下,从欧拉公式我们知道我们可以将调和函数表示为一个复指数:

现在考虑我们可以将给定信号 y(t) 展开成傅立叶级数:

其中 c_ω 为信号经过傅里叶变换得到的傅里叶系数;而 e^iωt 是代表频率ω的谐波基函数。
重要的是要记住,每个系数——我们的计算机将为我们计算的系数——都与一个谐波基函数相关联,而谐波基函数又将该系数与特定频率相关联。然后,如果我们知道每个系数的值,我们可以知道信号 y(t) 中存在的每个频率分量的强度。
应该考虑的另一个重要方面是,这些基函数具有无限的持续时间,因此不适合表示具有不连续性的信号,或者也需要其时间或空间位置的信号(即,当我们试图知道给定频率或节奏在信号的哪个周期中出现得最多时)。
短时傅立叶变换 (STFT) 就是克服这一点的方法。其策略包括将变换的每个基函数乘以窗口函数 w(t)。因此,后者限于给定的时间段,即仅在有限的时间间隔内具有非空值。
考虑到傅立叶变换中的函数空间可以由复数形式的三角函数定义,对于 STFT,我们创建了由频率(ω)和位置(τ)定义的新基:


必须根据分析兴趣选择 w(t) 的形状和长度。形状取决于用来产生它的函数,并决定了它的频率分辨能力。另一方面,长度( N )定义了窗口间隔,并因此定义了其时间分辨率。
可以使用和尝试许多窗口功能(可以在这里找到其主要功能和差异的概述)。就我们的目的而言,或者在犹豫该选择哪一个时,汉恩的窗口可能是一个令人满意的选择:

现在让我们回到我们的 Python 代码。一旦我们理解了 STFT 所依赖的基本原理,我们就可以利用 SciPy 库中的 信号模块来实现频谱图——它由绘制 STFT 系数的平方幅度组成。
因此,使用下面的代码,我们将计算第一个信号的 STFT(向上翻页,寻找信号 1 )。此外,我们必须考虑到 SciPy 实现将始终将频率轴设置为采样频率的一半,这也是我设置 plt.ylim() 命令的原因。

好吧,我同意你的观点,这个结果和我预期的相差甚远。好消息是(几乎)一切都是调整的问题。为了使事情变得简单,让我们编写下面的函数:
为了测试它,我们将创建一个新信号,其值易于理解和操作,一个振幅为 1000 的 100Hz 正弦波。由于我们将保持它持续 10s,以相同的采样率 1kHz ,我们可以使用之前定义的数组时间。

从一个简单的信号开始的目的是,它允许我们检查我们的代码中是否有重大错误。还好不是这样!正如预期的那样,绘图频率范围上升到 500Hz,是采样频率的一半。此外,我们可以精确地看到 100Hz 分量,其值被归一化(你会在这里找到归一化策略的细节)。
更进一步,我们可以为我们的 STFT 指定其他参数。为了创建下面的图表,我为参数 nperseg 设置了不同的值,这些值对应于窗口函数的大小。对于左图, nperseg = 64 ,而对于右图, nperseg = 2048 。请注意,窗口越大,频率分辨率越精确。反之亦然。


没有特定的规则来确定每个片段的样本数。然而,考虑采样率的 1/4 是有用的。例如,对于 1kHz 采样速率, nperseg 参数默认设置为 256 。如果我们分析我们的复合信号,这种权衡会变得更加清晰,复合信号是由 0.25 至 123Hz 的分量产生的:

在上面的图中,信号的所有已知频率成分都变得明显,尽管时间分辨率较低。对于某些应用来说, 250ms 并不重要。对其他人来说,这可能是限制性的。我必须预见到,没有一种分析工具可以像海森堡原理所证明的那样,在时间和频率上同时提供准确性——但这将是下一篇文章的主题。
3.大脑信号分析
为了举例说明 STFT 分析如何在“现实生活”中发挥作用,我们现在将应用我们在从脑电图(EEG)中获得的大脑信号片段上构建的函数。如果你想自己尝试一下,数据在这里有。
由于信号之前是在 Matlab 上处理的,我们首先必须使用 loadmat 方法将数据结构转换成 Python 字典。知道信号被标记为 sD_W ,我们可以继续加载它:


与处理正弦信号时不同,现在我们不能仅通过查看其时域图来推断 EEG 信号中存在的节律(即频率分量)。我们甚至可以说在 20 秒后会发生一些事情,但是我们不能给出任何细节。
另一方面,上面的声谱图向我们显示,在大约 20s 的事件之后,从 20s 到 36s,然后从 44s 到 56s,出现了一个良好建立的低频节奏。与任何数据科学活动一样,解释总是依赖于上下文。在这种情况下,该 EEG 信号来自接受多导睡眠描记术的受试者。所识别的频率范围对应于阿尔法节律,指示患者可能是清醒的和放松的,但是闭着眼睛。最后,50Hz 附近的黑暗区域对应于信号采集期间应用的带通滤波器,以便过滤来自交流电源的 50Hz 干扰。
4.结论
这篇文章的目标不仅是展示如何用 Python 实现 STFT,还简要介绍了这个强大的分析工具背后的理论——它支持更复杂的数学思想。
此外,通过能够理解在这种分析中应该观察的主要参数,并借鉴大脑信号的例子,我希望读者可以定制代码,并将这样的工具应用于他/她可能找到的任何循环信号:从生物医学或地震信号,甚至到股票市场价格或恒星的运动。
原载于 2019 年 2 月 23 日http://tbnsilveira . info。
寻找捐赠者:PySpark 分类项目
了解如何使用 Apache PySpark 来增强您的分类预测

Picture from Unsplash
介绍
本文的目的是对机器学习中的分类问题做一个温和的介绍,并通过一个全面的指南来成功地使用 PySpark 开发一个类预测。
所以没有进一步的行动,让我们投入进去吧!
分类
如果你想深入了解分类问题、它的主要算法以及如何使用机器学习技术来处理它们,我强烈建议你去看看下面这篇文章,在这篇文章中,我详细地解释了这个概念。
了解机器如何分类
towardsdatascience.com](/supervised-learning-basics-of-classification-and-main-algorithms-c16b06806cd3)
什么是分类?
分类是监督学习的一个子类,其目标是基于过去的观察来预测新实例的分类类别标签(离散的、无序的值、组成员)。
分类问题主要有两种类型:
- 二元分类:典型的例子是邮件垃圾邮件检测,其中每封邮件都是垃圾邮件→ 1 封垃圾邮件;或者不是→ 0。
- 多类分类:像手写字符识别(类从 0 到 9)。
下面的例子很有代表性地解释了二元分类法:
有两个类别,圆和十字,以及两个特征,X1 和 X2。该模型能够找到每个数据点的特征与其类别之间的关系,并在它们之间设置边界线,因此当提供新数据时,它可以估计其所属的类别,给定其特征。

Figure by Author
在这种情况下,新数据点落入圆形子空间,因此,模型将预测其类别为圆形。
分类主要算法
为了预测某些样本的类别,可以使用几种分类算法。事实上,在开发我们的机器学习模型时,我们会对其中的一定数量进行训练和评估,我们会保留那些预测性能更好的模型。
一些最常用的算法的非穷尽列表如下:
- 逻辑回归
- 决策树
- 随机森林
- 支持向量机
- k-最近邻(KNN)
分类评估指标
当对事件进行预测时,我们可以得到四种结果:
- 真阳性:TP
- 真阴性:TN
- 误报:FP
- 假阴性:FN
所有这些都在下面的分类矩阵中表示:

准确性衡量分类器做出正确预测的频率。它是正确预测的数量与预测总数(测试数据点的数量)的比率。

Precision 告诉我们我们归类为某一类的事件的比例,实际上是那个类。它是真阳性与所有阳性的比率。

回忆(敏感度)告诉我们实际上属于某一类别的事件中有多少被我们归类为该类别。它是真阳性与所有阳性的比率。

特异性是被正确识别为阴性的类别占阴性类别总数的比例。

对于分类分布有偏差的分类问题,准确性本身并不是一个合适的度量。反而精度和召回率更有代表性。
这两个指标可以结合起来得到 F1 分数,它是精确度和召回分数的加权平均值(调和平均值)。这个分数的范围从 0 到 1,1 是可能的最佳 F1 分数(我们在处理比率时采用调和平均值)。

皇家对空观察队
最后,我们将在项目中使用的指标是接收机操作特性或 ROC 。
ROC 曲线告诉我们该模型区分两个类别的能力有多强。它可以得到从 0 到 1 的值(€[0,1])。模型越好,越接近 1 值。

Figure by Author
从上图中可以看出,我们的分类模型将在类别和之间绘制一个分离边界:
- 落在阈值左边的每个样本将被归类为负类。
- 落在阈值右边的每个样本将被分类为阳性类别,
预测的分布如下:

Figure by Author
灵敏度&特异性之间的权衡
当我们降低阈值时,我们最终会预测更多的正值,并提高灵敏度。因此,特异性降低。
当我们提高阈值时,我们最终会预测更多的负值并提高特异性。因此降低了灵敏度。
如敏感性 ⬇️ 特异性 ⬆️
如特异性 ⬇️ 敏感性 ⬆️
为了优化分类性能,我们考虑(1-特异性)而不是特异性。所以,当敏感性增加时,(1-特异性)也会增加。这就是我们计算 ROC 的方法。

Figure by Author
性能示例
如前所述,赋值器越接近 1,模型的预测性能就越好,类之间的重叠区域就越小。

Figure by Author
寻找捐助者项目
可以在以下文章中找到该项目的完整介绍:
使用此分类模型查找并预测谁将向慈善机构捐款!
towardsdatascience.com](/classification-project-finding-donors-853db66fbb8c)
在本文中,我们将关注该项目的 PySpark 实现。
总之,在整个项目中,我们将使用许多不同的监督算法,使用从 1994 年美国人口普查中收集的数据来精确预测个人收入。
然后,我们将从初步结果中选择最佳候选算法,并进一步优化该算法以最佳地模拟数据。
我们实现的目标是构建一个模型,准确预测个人收入是否超过 50,000 美元。根据我们之前的研究,我们发现最有可能向慈善机构捐款的人是那些年收入超过 5 万美元的人。
因此,我们面临着一个二元分类问题,我们想确定一个人的年收入是否超过 5 万美元(第 1 类)或没有(第 0 类)。
这个项目的数据集来源于 UCI 机器学习知识库。
数据
人口普查数据集由大约 45222 个数据点组成,每个数据点有 13 个特征。
特性
age:年龄workclass:工人阶级(私营、自营企业、自营企业、联邦政府、地方政府、州政府、无薪、从未工作)education_level:教育水平(学士、专科学校、11 年级、高级研究生、专业学校、acdm 协会、voc 协会、9 年级、7-8 年级、12 年级、硕士、1-4 年级、10 年级、博士、5-6 年级、学前班)education-num:完成的教育年数marital-status:婚姻状况(已婚-未婚-配偶、离婚、未婚、分居、丧偶、已婚-配偶不在、已婚-配偶)occupation:工作职业(技术支持、工艺维修、其他服务、销售、行政管理、专业教授、搬运工人、清洁工、机器操作员、行政文员、农业渔业、运输搬运、私人服务、保安服务、武装部队)relationship:关系状态(妻子、亲生子女、丈夫、非家庭成员、其他亲属、未婚)- 种族(白人、亚洲太平洋岛民、美洲印第安爱斯基摩人、其他人、黑人)
sex:性别(女,男)capital-gain:货币资本收益capital-loss:货币资本损失hours-per-week:每周平均工作时间native-country:本土国家(美国、柬埔寨、英国、波多黎各、加拿大、德国、美国外围地区(关岛-USVI 等)、印度、日本、希腊、中国、古巴、伊朗、洪都拉斯、菲律宾、意大利、波兰、牙买加、越南、墨西哥、葡萄牙、爱尔兰、法国、多米尼加共和国、老挝、厄瓜多尔、台湾、海地、哥伦比亚、匈牙利、危地马拉、尼加拉瓜、苏格兰、泰国、南斯拉夫、萨尔瓦多、特立尼达和多巴哥&多巴哥、秘鲁、香港、荷兰)
目标变量
income:收入阶层(< =50K,> 50K)
导入数据和探索性数据分析(EDA)
我们将从导入数据集并显示数据的前几行开始,以对探索性数据分析进行初步近似。
**# File location and type**
file_location = "/FileStore/tables/census.csv"
file_type = "csv"**# CSV options**
infer_schema = "true"
first_row_is_header = "true"
delimiter = ","**# The applied options are for CSV files. For other file types, these will be ignored.**
df = spark.read.format(file_type) \
.option("inferSchema", infer_schema) \
.option("header", first_row_is_header) \
.option("sep", delimiter) \
.load(file_location)display(df)

我们现在将通过使用。describe()方法。
**# Display Dataset's Summary**
display(df.describe())

让我们也找出数据集的模式。
**# Display Dataset's Schema**
display(df.describe())

准备数据
因为我们想预测个人年收入是否超过 50K 美元,我们将把标签“收入”替换为“> 50K”。
为此,我们将创建一个新列,根据个人年收入是否超过 5 万美元,该列的值将为 1 或 0。然后我们将删除收入栏。
**# Import pyspark functions**
from pyspark.sql import functions as F**# Create add new column to the dataset**
df = df.withColumn('>50K', F.when(df.income == '<=50K', 0).otherwise(1))**# Drop the Income label**
df = df.drop('income')**# Show dataset's columns** df.columns

向量化数字特征和一次性编码分类特征
为了对模型的训练进行处理,Apache Spark 中的特征必须被转换成向量。这个过程将使用我们现在将要探索的某些类来完成。
首先,我们将导入相关的库和方法。
from pyspark.ml import Pipeline
from pyspark.ml.feature import StringIndexer, OneHotEncoder, VectorAssembler
from pyspark.ml.classification import (DecisionTreeClassifier, GBTClassifier, RandomForestClassifier, LogisticRegression)
from pyspark.ml.evaluation import BinaryClassificationEvaluator
现在,我们将选择分类特征。
**# Selecting categorical features**
categorical_columns = [
'workclass',
'education_level',
'marital-status',
'occupation',
'relationship',
'race',
'sex',
'hours-per-week',
'native-country',
]
为了对这种分类特征进行一次性编码,我们将首先将它们通过索引器,然后传递给编码器。
**# The index of string values multiple columns**
indexers = [
StringIndexer(inputCol=c, outputCol="{0}_indexed".format(c))
for c in categorical_columns]**# The encode of indexed values multiple columns**
encoders = [OneHotEncoder(dropLast=False,inputCol=indexer.getOutputCol(),
outputCol="{0}_encoded".format(indexer.getOutputCol()))
for indexer in indexers]
现在,我们将把分类编码的特征与数字特征结合起来,并用它们两者构成一个向量。
**# Vectorizing encoded values**
categorical_encoded = [encoder.getOutputCol() for encoder in encoders]
numerical_columns = ['age', 'education-num', 'capital-gain', 'capital-loss']
inputcols = categorical_encoded + numerical_columns
assembler = VectorAssembler(inputCols=inputcols, outputCol="features")
现在,我们将建立一个管道来自动化这个阶段。
pipeline = Pipeline(stages=indexers + encoders+[assembler])
model = pipeline.fit(df)**# Transform data**
transformed = model.transform(df)
display(transformed)

最后,我们将只选择具有相关特征的数据集。
**# Transform data**
final_data = transformed.select('features', '>50K')
初始化模型
对于这个项目,我们将研究三种不同分类算法的预测性能:
- 决策树
- 随机森林
- 梯度增强树
**# Initialize the classification models**
dtc = DecisionTreeClassifier(labelCol='>50K', featuresCol='features')
rfc = RandomForestClassifier(numTrees=150, labelCol='>50K', featuresCol='features')
gbt = GBTClassifier(labelCol='>50K', featuresCol='features', maxIter=10)
拆分数据
我们将在训练和测试数据之间执行经典的 80/20 拆分。
train_data, test_data = final_data.randomSplit([0.8,0.2])
训练模型
dtc_model = dtc.fit(train_data)
rfc_model = rfc.fit(train_data)
gbt_model = gbt.fit(train_data)
获得预测
dtc_preds = dtc_model.transform(test_data)
rfc_preds = rfc_model.transform(test_data)
gbt_preds = gbt_model.transform(test_data)
评估模型的性能
如前所述,我们的评估者将是中华民国。我们将初始化它的类,并将预测值传递给它,以便获得值。
my_eval = BinaryClassificationEvaluator(labelCol='>50K')**# Display Decision Tree evaluation metric**
print('DTC')
print(my_eval.evaluate(dtc_preds))

**# Display Random Forest evaluation metric**
print('RFC')
print(my_eval.evaluate(rfc_preds))

**# Display Gradien Boosting Tree evaluation metric**
print('GBT')
print(my_eval.evaluate(gbt_preds))

最佳预测器是梯度提升树。实际上,0.911 是一个非常好的值,当显示其预测值时,我们将看到以下内容:

提高模型性能
我们将尝试通过执行网格搜索交叉验证技术来做到这一点。有了它,我们将使用先前超参数值集的不同组合来评估模型的性能。
我们将调整的超参数有:
- 最大深度
- 最大箱数
- 最大迭代次数
**# Import libraries**
from pyspark.ml.tuning import ParamGridBuilder, CrossValidator**# Set the Parameters grid**
paramGrid = (ParamGridBuilder()
.addGrid(gbt.maxDepth, [2, 4, 6])
.addGrid(gbt.maxBins, [20, 60])
.addGrid(gbt.maxIter, [10, 20])
.build())**# Iinitializing the cross validator class**
cv = CrossValidator(estimator=gbt, estimatorParamMaps=paramGrid, evaluator=my_eval, numFolds=5)**# Run cross validations. This can take about 6 minutes since it is training over 20 trees**
cvModel = cv.fit(train_data)
gbt_predictions_2 = cvModel.transform(test_data)
my_eval.evaluate(gbt_predictions_2)

我们在预测性能上获得了微小的改进。而计算时间,几乎到了 20 分钟。因此,在这些情况下,我们应该分析这种改进是否值得努力。
结论
在整篇文章中,我们做了一个端到端的机器学习分类项目。我们还学习并获得了一些关于分类模型的见解,以及使用 PySpark、其方法和实现来开发一个具有良好性能的分类模型的关键。
我们还学会了一旦识别出一个性能良好的模型,如何调整我们的算法。
在接下来的文章中,我们将学习如何在 PySpark 中开发回归模型。所以,如果你对这个话题感兴趣,我强烈建议你继续关注!
如果你喜欢这篇文章,那么你可以看看我关于数据科学和机器学习的其他文章 这里 。
如果你想了解更多关于机器学习、数据科学和人工智能的知识 请在 Medium 上关注我,敬请关注我的下一篇帖子!
寻找多莉,隐马尔可夫模型和简化生活!

前几天和我的一个同事聊天,我刚刚意识到我可能是健忘的。或者至少比她“更多”的记忆——“更少”?在你开始把我的失忆和迪士尼海底总动员/多莉系列中可怜的多莉的失忆联系起来之前,T5 被描绘成(有点不公平和不准确地说一条实际上拥有良好记忆力的鱼!),让我澄清一下,我以为我是无记忆的,尽管像少数概率分布。如果你面临概率挑战,不要担心——那是因为概率不在你这边(双关语!)我马上就要讲到了——但基本上她是在告诉我她不会忘记别人说过的事情或发生过的事情!?这引发了一系列关于生活和生活窍门的讨论,以及后来更多的反思和一连串的思考——我刚刚意识到,随着生活的进程变得势不可挡,在我无数的愚蠢和失败中,我经常拯救自己的东西——是我大脑的“无记忆”性质(或者更确切地说是思维过程!).像马尔可夫模型一样没有记忆!
那么这些马尔可夫模型是什么?马尔可夫模型是简单的系统(尽管他们的名字!)保持其状态有规律地变化。只是它每次都进入一个新的状态,只是基于现在,而不是看过去。当它改变状态时,它向世界表达(输出)一些信息。所以它去哪里和说什么纯粹是基于现在,使它没有记忆。事实证明,这些马尔可夫系统中的一些倾向于对它们的状态保密——虽然它在改变状态时会说些什么,但它从不透露它的状态。这些相当神秘的无记忆系统被称为隐马尔可夫模型(HMM)。下图显示了一个简单的例子。
A Simple Example of Hidden Markov Models to predict weather from clothing patterns
有趣的是,这些看似简单的健忘 hmm 其实是我们身边一些令人难以置信的技术的核心——当你对着亚马逊 Echo 设备喊“Alexa”或要求谷歌将句子翻译成一种新语言时。很长一段时间以来,这些语音识别或翻译系统都是由 HMMs 驱动的(我作为一名数据科学家也曾在此基础上开发过无数可能的应用程序,但我们不会以简化事物为目标!).
如果人工智能(AI)系统可以在没有太多过去包袱的情况下做得很好(顺便说一下,国家本身可以有很多关于过去的信息编码在里面!),有没有我们人类可以借鉴的地方?我知道这听起来很奇怪,因为人工智能系统通常是在人类知识的基础上训练的,事实上人工智能系统现在正试图让自己“长期和短期记忆”就像人类更好地完成工作一样,我在说人类希望向无记忆人工智能系统学习。
嗯,我认为毫无疑问,世界正变得越来越复杂,我开始相信,当我们在这个每时每刻都被信息冲击的复杂世界中航行时,对人类的救赎在于简化。简化我们看待周围世界的方式。正如一句流行的名言所说,一个人无法控制外部世界,但你可以控制如何做出反应。
《T4 人》中的尤瓦尔谈到了 21 世纪人类的三大追求——神性、不朽和幸福。尤瓦尔还相当怪异地补充道,对幸福的追求可能是通过生化手段!虽然我认为这些预测似乎有一点可怕的必然性,但就目前而言,我敢说简化我们的思维系统是生存的关键,也是通往幸福的希望之路。
所以,忘掉记忆,忘记过去,在你的人生追求中,做好你目前的状态。事实上,对我们来说,要表现得善良,多一点宽容和不报复的本性,或者表现出毅力和耐心,就需要一种无记忆的元素。让我用一句哲学的话来结束我的演讲——当我们说时间治愈时,这仅仅意味着我们的记忆会随着时间消逝,痛苦也会消失。所以,你越快养成无记忆,你就能越快前进:)当你做决定时,不考虑过去的包袱,这确实简化了生活!
做马尔可夫!
PS:我已经决定在 14 年后再次写作,这几乎是一个自我放逐的时期,因为我曾经用泰米尔语写短篇小说和博客。现在,这是一个担心和后悔的原因吗?错过了记录无数的观察,推论和在此期间生命旅程的细节的机会吗?嗯,我是马尔科夫。我没有记忆;)
使用 Tensorflow 对象检测器、Pytorch 特征提取器和 Spotify 的 airy 查找熟悉的面孔
在一些不同的帖子中,我已经使用对象检测器和类似的暹罗网络将面部识别管道或类似的东西放在一起进行特征提取,以找到类似的图像,但我还没有真正深入研究如何以更实际的大规模方式使用这些特征向量。我在这里的意思是,你不希望在整个数据库中进行成对的比较,这是不实际的。所以在这篇文章中,我将演示如何使用 Spotify 的近似最近邻居库(骚扰)来根据一些初始图像找到相似的游戏角色。
Annoy 是 Spotify 开发的一个库,用于帮助大规模推荐音乐。它还有一些其他有用的属性,因为您可以预先计算索引,稍后可以调用这些索引来查找类似的项目,这在您进行大规模工作时很有帮助。它通过传递你所关心的任何东西(音乐、文本、动画人物的图片)的某种向量表示来工作,并基于此建立模型和生成索引。
这个管道的最简单的版本是使用一些神经网络进行特征提取,然后将这些特征向量传递给 airy,然后使用 airy 来查找相似的图像。这取决于您的用例。在这个具体的例子中,我想看看我是否会因为在不同的图像中返回相似或相同的字符而感到恼火。我对此进行了测试,并最终通过建立面部识别网络和传递面部生成的特征向量来尝试和集中管道以找到相似的字符,从而完成了两个阶段的方法。
对于这个项目的数据集,我将使用相同的命运大订单数据集,我已经在一些职位。主要是因为它是我最近建立的约 410 张图片的小数据集中最大的,所以它是这类事情的一个很好的测试案例。
进入这个项目时,我发现关键(像许多数据科学项目一样)是找到一种好的方法来表示我想要解决的问题的数据集。对于这种类型的任务,将 3D 像素阵列压缩成 1D 特征向量在机械上是正确的,但是确保最终的 1D 特征向量描述了您想要的也是重要的。
请随意查看我为这个在这里使用的笔记本。这些笔记本不是超级可读的,因为我很快就把东西处理完了。我也不像往常一样在 repo 中提供模型/数据集文件。
版本 1:完整图像特征提取
因为这是最容易做到的事情,并且是为后面的测试设置我的通用管道的好方法,所以我使用了 pytorch 预训练网络(一个 ResNet101,它为每个矢量生成 1000 个特征以输入到 aroy ),并向它传递完整的图像,然后由 aroy 使用这些图像来查找类似的图像。这个初始过程产生了如下结果。最左边的图像是基础图像,接下来的 4 个是“数据库”中最相似的 4 个。

Left most image is the seed image followed by 4 of the top ones. (there was a duplicate in the dataset for this one)
看起来特征提取器从原始图像中获得了许多类似的深色细节,其中红色和黑色是焦点。然而,在这种情况下,我试图看看我是否可以得到类似的字符,所以这不是最好的输出。
这是另一个图像示例。

Left most image is the seed image followed by 4 of the top ones.
这一个有点棘手,因为在主图像中有两个角色,但是当整个图像被传递到烦人的模型中时,相似的图像有相似的颜色味觉(可能…)。
因此,从机械上来说,这一过程是可行的,但我要说的是输出很差。下一步是想办法让输出更连贯。在这种情况下,因为我想建立的是一些显示类似的字符给定一些基础图像,为什么不尝试,使它的图像是基于只是字符,而不是完整的图像?我可以想到很多方法来做到这一点,但对我来说最简单的是建立一个单一的类对象检测器来识别图像中的人脸,将该人脸传递到一个预训练的网络进行特征提取,然后将该特征向量传递到 are。当骚扰拉相似的图像,我可以让它返回基本图像,所以基本上它会找到数据库中的哪些面部与新提取的面部最相似,并返回这些面部出现的图像。
基于人脸识别的相似度
对于这个项目,我建立了一个简单的 1 类对象检测器,只识别图像中的动画人物的脸。我使用 Labelimg 来快速注释图像,我非常确定我只花了大约 20 分钟来标记我的测试和训练分割的 400 张图像。在这一点上,我已经做了相当多的工作,只有一个单独的类可以大大加快这个过程。在训练探测器时,我使用了一个更快的 RCNN-Inceptionv2 模型,该模型具有来自 Tensorflow 模型 zoo 的 coco 权重,并训练该模型大约 3 个小时。我从周五午夜开始训练,一直到周六凌晨 3 点左右,这打乱了我的睡眠时间表,因为那时我还在忙其他的事情。









对象检测器训练得相当快,输出看起来相当干净。这是令人振奋的,因为这将是在这个管道中找到更多的角色特定的相似图像的关键部分。

Example of some cropped image outputs
当使用对象检测器从原始数据集中裁剪头部时,我保存了头部到其原始图像的 csv 映射。我的想法是,我可以在头像上运行一个特征提取器,并将其存储在骚扰模型中,到时候我可以将骚扰输出与原始图像进行匹配。
Pytorch 和 Annoy 的特征提取
现在我可以从图像中提取头部,我所要做的就是将这些头部通过特征提取器(再次是 ResNet101),然后将这些特征向量传递给 aroy。
作为一个演示,这里有一张之前的图片,原始图片模型有一些问题。这是对象检测器检测图像中的两张脸的示例输出。因此,每张图像都有从中提取的特征,然后与更大的数据库进行匹配。

第一个输出来自主图像左边的角色(他出现在数据集中), 4 个相似图像的前两个和最后一个图像是该角色。这是对该图像的原始图像输入的改进,其中有 0 个匹配。

第二个角色(右边的一个)实际上并没有出现在数据库中…但是她的面部特征与左边的一个基本相同,所以四个图像 2 匹配(第一相似和第四相似)

Left is the base image, followed by 4 most similar
与只使用基本图像相比,这似乎是一个很好的改进,因为目标是返回相似的字符。现在让我们看看我之前为基本模型使用的另一个示例图像。


Left is the base image, followed by 4 most similar
因此,在这张照片中,他们看起来更有针对性,而不是仅仅得到一堆红色和黑色的图像。虽然第一、第二和第四个相似图像具有不同的特征,但是第三个图像具有相同的特征。这一次,所有人都至少与基本图像的性别相同。另一个只是让所有的男性角色和一个女性角色的基本图像配对。虽然这不是一个很好的结果,但似乎比以前的版本有所改进。
结束语
在查看了这两条管道的输出后,我觉得这些结果是可以接受的,但不是很好。使用基本图像返回具有相似感觉但不一定相似特征的图像。虽然面部检测器有助于将输出集中到相似的字符,但它通常不会返回总体相似风格的图像。

original full image model
虽然 4 个返回图像中的 2 个不是相同的字符,但我确实喜欢中间的第二个结果,因为它与基础图像有相似的“感觉”。

headshot based model
正如我之前提到的,基于头像的模型很好地聚焦于所讨论的角色。在这种情况下,所有字符都是相同的。然而,它不符合原始图像的感觉。我真正想要的是两者的某种结合,我可以得到相似的角色和相似的整体形象(基本上我自私地想要鱼和熊掌兼得)。
经过一些试验后,我发现我能够非常接近那个。

New model!
正如我在本文开始时提到的,从这条管道获得更好的输出基本上可以归结为修改哪些数据被压缩到最终的特征向量中,这些向量被传递到 annoy 中。虽然在大多数情况下鱼与熊掌兼得是不可行的,但在这种情况下却是可行的!我认为,这个“新模型”比其他两个做得更好,因为它获得了所有正确的字符(击败了基本图像模型),并且显示的图像比头像模型更接近基本图像。
通常情况下,这只是一种我必须从新的角度解决问题的情况。

Still really enjoy big hero 6 and code to Immortals as a theme song for my life
我只是不得不重新思考我在最终的特征向量中编码了什么信息。我最终做的是将来自检测到的头像和基础图像的信息传递给一个组合的特征向量,该向量捕获了关于角色面部(以获得相似的角色)和基础图像(以获得整体“感觉”)的信息。
然而,这个最终模型并不那么简单,我花了一点时间才弄明白,所以我会再发一篇后续文章来保持这篇文章的合理长度。

因此,下次请继续收听,我将介绍我是如何将面部和基本图像信息组合成密集的表示形式,以让 Spotify 的 annoy 在性格和感觉方面找到相似的图像。
跟进博文 此处
再一次,在这里你可以随意查看我为这个使用的笔记本。这些笔记本不是超级可读的,因为我很快就把东西处理完了。我也不像往常一样在 repo 中提供模型/数据集文件。
寻找车道线-用于车道检测的简单管道。
识别道路的车道是人类驾驶员执行的非常常见的任务。这对于保持车辆在车道的限制内是很重要的。这对于自动驾驶汽车来说也是非常关键的任务。并且利用简单的计算机视觉技术,非常简单的车道检测流水线是可能的。本文将使用 Python 和 OpenCV 描述可用于简单车道检测的简单管道。这个练习是作为“uda city 无人驾驶汽车 nano degree”的一部分完成的。
请注意,该管道有其自身的局限性(如下所述),并且可以改进。改进将在后续文章中描述。
车道检测管道:
- 将原始图像转换为灰度。
- 使灰度图像变暗(这有助于降低道路变色区域的对比度)
- 将原始图像转换到 HLS 颜色空间。
- 将黄色从 HLS 中分离出来,得到黄色遮罩。(适用于黄色车道标线)
- 将白色从 HLS 中分离出来,得到白色面膜。(适用于白色车道标线)
- 逐位或黄色和白色掩码以获得公共掩码。
- 逐位并使用暗化图像进行屏蔽。
- 应用轻微高斯模糊。
- 应用 canny 边缘检测器(调整阈值—试凑法)获得边缘。
- 定义感兴趣的区域。这有助于剔除 canny 边缘检测器检测到的不需要的边缘。
- 检索霍夫线。
- 巩固和外推霍夫线,并绘制在原始图像上。
原始测试图像






Original Test Images
转换为灰度
将原始图像转换成灰度有它的好处。我们必须找到黄色和白色的车道,将原始图像转换为灰度可以增加车道相对于道路的对比度。






Original Images to Grayscale
使灰度图像变暗
这样做的目的是降低道路变色部分的对比度。






Darkened grayscale images
将原始图像转换到 HLS 颜色空间
原始图像是 RGB 的,但我们也应该探索其他颜色空间,如 HSV 和 HLS 。当并排看时,可以很容易地看到,我们可以从道路的 HLS 颜色空间中获得更好的颜色对比度。这可能有助于更好的颜色选择,进而有助于车道检测。






RGB vs HSV vs HLS
颜色选择
这里我们使用 OpenCV 的 inRange 来获取 thresh hold 值之间的掩码。经过反复试验,我们可以找出阈值的范围。
对于黄色面具:
- 色调值介于 10 和 40 之间。
- 我们使用较高的饱和度值(100–255)来避免山丘的黄色。
对于白色面具:
- 我们使用更高的亮度值(200-255)作为白色蒙版。
我们按位或两个掩码来得到组合掩码。
下图显示了组合蒙版是逐位的,并带有暗化图像。






高斯模糊
高斯模糊(高斯平滑)是预处理步骤,用于减少图像中的噪声(或平滑图像)。我们使用这个预处理步骤来移除许多检测到的边缘,并且只保留图像中最突出的边缘。
OpenCV 的 GaussianBlur 期望模糊的内核大小(奇数值)。在尝试了一些值之后,我们使用了 7。






Gaussian Blur
应用 Canny 边缘检测
现在我们将 Canny 边缘检测应用于这些高斯模糊图像。Canny 边缘检测是基于梯度变化检测边缘的算法。并不是说 Canny 边缘检测的第一步是使用默认的核大小 5 进行图像平滑,我们仍然在上一步中应用显式高斯模糊。Canny 边缘检测的其他步骤包括:
- 寻找图像的强度梯度
- 非最大抑制
- 滞后阈值
这个链接很好的解释了 Canny 边缘检测。






选择感兴趣区域
即使在应用 Canny 边缘检测之后,仍然有许多边缘被检测到,它们不是车道。感兴趣区域是一个多边形,它定义了图像中我们感兴趣的边缘所在的区域。
注意,图像中的坐标原点是图像的左上角。行坐标自上而下增加,列坐标自左而右增加。
这里的假设是摄像机保持在恒定的位置,并且车道是平坦的,因此我们可以“猜测”感兴趣的区域。






Region of Interest applied to Canny Edge Detected Images.
霍夫变换线检测
霍夫变换是一种通过识别直线上所有点来找出直线的技术。这是通过将线表示为点来实现的。并且点被表示为线/正弦曲线(取决于笛卡尔/极坐标系统)。如果多条直线/正弦曲线通过该点,我们可以推断这些点位于同一条直线上。


Line in polar co-ordinates and sinusoidal that represent point , intersection point represent line.
更多信息可在这里找到。
从感兴趣区域图像中找出 Hough 线,然后在原始图像上绘制 Hough 线。





Hough Lines
霍夫线的合并和外推
我们需要追踪完整的车道标志。为此,我们首先需要区分左车道和右车道。有一个简单的方法来识别左车道和右车道。
- 左车道:随着列坐标值的增加,行坐标值减少。所以梯度一定是负的。
- 右车道:随着列坐标值的增加,行坐标值也增加。所以梯度一定是正的。
- 我们将忽略垂直线。
在识别左车道和右车道霍夫线之后,我们将外推这些线。我们做两件事:
- 车道检测到多条线,我们将对这些线进行平均
- 有一些部分检测到的线,我们将推断它们。






将管道应用于视频
现在让我们将这个管道应用于视频。
管道在直道上非常好用。
但是这对于弯曲的车道来说效果不好。
缺点
- 基于直线的霍夫线不适用于弯曲的道路/车道。
- 有许多试错法来获得正确的超参数。此外,感兴趣的区域假设摄像机停留在相同的位置,并且车道是平坦的。因此,在决定多边形顶点时涉及到“猜测”工作或硬编码。
- 一般来说,有许多道路可能没有车道标志,这是行不通的。
未来的改进
- 代替直线,使用在弯曲道路上有用的更高程度的曲线将是有益的。
- 即使当我们使用来自先前帧的信息时,仅仅平均车道可能不是很好的策略。可以是加权平均值,或者某种形式的优先级值也可以。
该代码可在 GitHub 上获得
如果你喜欢这篇文章,请一定给我一个掌声,并关注我以获得我未来文章的更新。
还有,随时在LinkedIn上联系我或者在Twitter上关注我。
如果你喜欢我的作品,请考虑赞助我,它会帮助我推出更多这样的作品。
在芝加哥寻找低风险餐厅享受美食

Photo by Robin Stickel on Unsplash
作为一个美食爱好者,我总是对探索餐馆很感兴趣。因此,在这个项目中,我决定满足自己的渴望,处理食物关节数据。我建立了这个项目的目标,根据给定的位置探索芝加哥的餐馆。我去了 Kaggle,找到了完美的数据集。
来自芝加哥市公开数据
www.kaggle.com](https://www.kaggle.com/chicago/chicago-food-inspections)
我开发了一个 Kaggle 内核,在那里我使用各种可视化工具探索了数据集,然后创建了一个方法来获取距离当前位置(使用纬度和经度定义)一定数量公里附近的餐馆。
使用来自多个数据源的数据
www.kaggle.com](https://www.kaggle.com/bhanotkaran22/facilities-in-chicago-risk-and-result-analysis)
导入库和数据集
第一步是导入将要使用的库。我导入numpy和pandas进行数据操作。为了绘制图表和地图,我使用了plotly。注意,为了使用地图,我们需要一个访问令牌。我把令牌保存在input目录下的一个方法中,在执行过程中把它复制到working目录,使用它,最后会删除它。
最后,我导入数据集并将其保存到dataset变量中。有很多列。要开始了解每一列的值,请参考 Kaggle 数据集的Data部分并向下滚动。

仪表板可见。在右上方,可以看到Views。选择第三个选项,你会看到每一列的名称、类型、发行版等等。
数据分析
第一步是识别所有设施,并对每个设施进行最近的检查。我还将删除所有Risk、Facility Type、DBA Name、Latitude或Longitude为空的行。一些企业已经停止运营或不存在,因此也可以被移除。我将创建一个新列Name,它从AKA Name和DBA Name中提取姓名,并优先选择AKA Name。Name还将包括每个工厂的地址。
latest_data是我将在余下的探索中使用的数据集。
我还将为绘图中使用的颜色创建列。All、Risk 1 (High)、Risk 2 (Medium)和Risk 3 (Low)的风险颜色分别为黑色、红色、黄色和绿色。检查时,Pass和Pass w/ Conditions设置为绿色,而Fail、No Entry和Not Ready设置为红色。
所有的设置现在都完成了。让我们继续对我们的数据进行可视化。
数据可视化
设施类型
首先,我将为这次食品检查中包括的各种类型的设施绘制一个饼图。有一些设施不到 1%,因此,我会将它们全部组合在一起,并将其视为Others。

Plotly饼图提供了许多样式选项,如高度、宽度、内圆大小等。
大约 55%的设施是餐馆。
风险分析
接下来,我将基于上面创建的Risk Color列在芝加哥地图上绘制设施列表。

看来红点的数量在图中确实很高。这意味着大多数设施处于高风险状态。我们也可以从数据中证实这一点。
高风险贷款的数量多于中低风险贷款的总数。
成功和失败
接下来,我将探究最近的检查中有多少是Pass和多少是Fail。我认为Pass w/ Conditions是好的,而No Entry和Not Ready是坏的。

正如我们从上面的柱状图中所看到的,尽管许多设施处于高风险中,但它们中的大多数也通过了检查。
最大检查结果为
Pass。
地图上的检查结果如下图所示。

大多数设施通过了检查,只有极少数没有通过。
逐区分析
每个设施都有一个与之相关联的Ward。我根据病房对所有设施进行了分组,然后在柱状图上绘制了合格与不合格数。

似乎 36 号病房通过检查的次数最多。
大多数检查是针对 36 号病房的,它具有最大的通过率。
多数违反
对于许多餐馆来说,在一定数量的检查中存在违规行为。因此,我决定在所有违规行为中找出关键的违规行为。
每个违规都作为句子添加到Violations栏的一个段落中。我必须从每个列值中提取所有的数字,创建一个数字列表,并计算数值计数。完成后,我选择了超过 100 的违规,并绘制了一个条形图。

最大违规发生在 41 号,它没有正确使用和存放抹布。
所有的可视化现在都完成了,让我们创建一个方法来找到我们的餐馆。
探索附近的餐馆
我将创建一个方法,该方法将接受一个位置列表,用户的当前位置(以纬度和经度表示),您想要搜索的风险级别,以及需要搜索的餐馆的距离(以公里为单位)。
让我们首先创建一个方法,根据两个位置的纬度和经度值来获取它们之间的距离。
完成后,我们可以使用上面使用的代码来绘制所有设施点,但有基于数据值的限制。
我已经添加了默认值,以防某些值没有提供。在该方法中,我遵循以下步骤:
- 过滤位置以仅包含餐馆
- 根据
risk_level选择要搜索的风险级别。这可能是Low、Medium、High或All - 过滤搜索距离内的餐馆并重置索引
- 在地图上标出所有点
我使用以下命令来运行该方法:
get_plot(latest_data, 41.8781, -87.6298, 'Medium', 5)
我取latest_data中所有最近的数据,设定搜索中心为芝加哥中部,风险等级为Medium,搜索距离为 5 公里。

我们可以看到,这些地点被标为绿色,表示低风险,黄色表示中等风险。它看起来像一个半径为 5 公里的半圆。您可以调整搜索参数以获得想要的结果。
结论
在本文中,我探索了数据科学的数据可视化方面,以及如何用它来以一种简单的方式理解数据。
您可能还喜欢:
[## 使用 Python 中的多个绘图库在地图上绘制商业位置
比较地图打印库
towardsdatascience.com](/plotting-business-locations-on-maps-using-multiple-plotting-libraries-in-python-45a00ea770af) [## 使用 React 和 Flask 创建一个完整的机器学习 web 应用程序
用 UI 和后端构建完整的 ML 应用程序
towardsdatascience.com](/create-a-complete-machine-learning-web-application-using-react-and-flask-859340bddb33) [## 使用机器学习预测心脏病的存在
机器学习在医疗保健中的应用
towardsdatascience.com](/predicting-presence-of-heart-diseases-using-machine-learning-36f00f3edb2c) [## 在 Kaggle 上使用 CNN 进行深度学习以识别疟疾细胞
医学领域的深度学习
towardsdatascience.com](/deep-learning-to-identify-malaria-cells-using-cnn-on-kaggle-b9a987f55ea5)
我希望你喜欢这篇文章。请和我分享你的想法和建议。我很乐意收到你的来信。
发现意义:小区域分析的情况和数据可用性的进展
作为纽约市的居民和教师,我的经历促使我着手一个研究项目,对纽约市的街区进行小区域分析比较。当时我并不知道,我最初的尝试发生在早几个月的时候,在之前的一篇中有详细描述。
接近并不意味着相似
你不必相信我的话。首先,看看这两张在纽约大通银行同时发现的收据就知道了。

I remembered seeing the photo circulating social media while I was living there, and had to dig it up for this article. I can’t access the original post through Facebook anymore, but the photo was extremely easy to find even after all these years — I still remembered the caption it had: “a tale of two New Yorks.”
一位匿名银行家是百万富翁,另一位的账户里连 100 美元都没有。他们在同一天使用了同一个自动取款机!考虑到纽约市的经济不平等,这并不奇怪,但这是这种不平等的明显表现。
小区域分析的必要性和好处

Economic, education, and health needs in New York City neighborhoods from Case Studies — Scientific Figure on ResearchGate
在小范围内发现有意义的差异有明显的好处。小区域分析通常涉及分析大型数据库,以比较通常被视为整体的群体的子集内的资源可用性和使用情况。在这种情况下,整个就是组成纽约市的五个区。这个想法是发现差异有助于确定社区的需求。这将最终导致资源的重新分配或调查为什么分配的资源没有被一个子群体使用,并找到一种方法使它们更容易获得。
小区域分析在历史上一直是一件很难实现的事情,尤其是对于我所追求的细粒度。可靠的、有统计学意义的数据很难获得。这种情况正在改变,纽约市是带头冲锋的城市之一,因为该市承认,其巨大的多样性给平等带来了巨大的挑战,而且它有资源应对其中的一些挑战。
开放数据来到纽约
2012 年,纽约市通过了“开放数据法”,要求到 2018 年底,所有市政府的公共数据都可以通过一个单一的在线门户免费获取。
世界上最大的城市之一的公共记录的大规模发布是迈向数据民主化的一大步。纽约人已经将这种资源很好地用于 T2 的 T3 航站楼。

http://storymaps.esri.com/stories/2016/wealth-divides/index.html
一年前,认输并比较各区要容易得多,但我坚持捕捉较小社区之间的差异——这在当时导致了一些相当不起眼的分析。这一次,由于开放数据倡议,这将容易得多。虽然不是所有的数据对我分析小于全市或区范围的区域都有用,但大部分是有用的。
纽约市的怪癖
如果你从未逛过纽约,可能很难理解为什么我这么想“放大”纽约的街区。我是说,至少布鲁克林是布鲁克林,布朗克斯是布朗克斯。有哈莱姆区,某处有唐人街,中央公园周围有价值百万的公寓。但仅此而已,对吧?
嗯……数据科学家徐仅在布鲁克林的威廉斯堡社区就能识别出 75 个不同的社区。她通过分析纽约市出租车和豪华轿车委员会的数据做到了这一点,正如她在这篇文章中概述的那样。诚然,这些“社区”中的大多数并不居住在布鲁克林,小区域分析通常集中在居住地,75 仍然很多。这是她的地图,显示了她所识别的五个社区的出租车数据。

Five Communities of Williamsburg
在纽约住了十年之前,我不知道街区变化有多快(跨越时空)。我想我从电影和电视节目《老友记》中获得了关于这座城市的大部分信息。虽然我知道这些是不切实际的描绘,但当我第一次冒险进入这座城市的不同地方时,它仍然让我感到惊讶。
不到半英里外的世界
2015 年,我住在离纽约长老会医院两个街区远的地方,那里是哥伦比亚大学医学中心的所在地,当时我正在和一名住院医生约会。这套公寓是新装修的,保养得很好,地下室有洗衣房。这个社区安静、高档、专业,有相当多的种族多样性。地铁站就在我住的街区的尽头,我步行一分钟就能到达附近的一家墨西哥小吃店和一家星巴克,附近还有一些不错的餐馆,虽然价格有点高。

Economic, education, and health needs in New York City neighborhoods from Case Studies — Scientific Figure on ResearchGate.
当住院医生和我分手时,我是那个必须搬家的人,因为我们住在哥伦比亚大学的宿舍里。这很容易,我最好的朋友独自住在八个街区外,我的新公寓每月便宜 700 多美元,尽管哥伦比亚的住房有部分补贴。赢。
这个社区也很有趣。除了街对面的塔可钟之外,在几分钟的步行距离内没有任何大型连锁餐馆。有一些非常好的波多黎各和多明尼加小餐馆,不过,大多数都很实惠。无论白天黑夜,任何时候都会有一群人坐在外面的小凳子或折叠椅上演奏加勒比音乐。哦,这个社区几乎完全是拉丁裔的 T4 人。街上听到的西班牙语比英语多。我的朋友是波多黎各人,所以她感觉非常自在。另一方面,我绝对是周围唯一的亚裔美国人和白人。我确实会说一些西班牙语,但是是轻微的美国口音加上墨西哥口音/方言。加勒比西班牙语几乎是另一种语言…**
无论如何,更便宜的房租也意味着,总的来说,这个社区更穷。我现在正好在两个地铁站的中间,从大门到城市的其他地方大约需要走 7-8 分钟。相信我,在炎热潮湿的夏天和寒风凛冽的冬天,这几分钟非常重要。我注意到我每天见到的警察单位数量急剧增加。街道更加肮脏,建筑物更加破败。我在通勤开始和结束时看到的人似乎都很疲惫。这似乎是一件奇怪的事情,但我也累了——因为我每月节省的 350 美元(700 美元中我的份额)是有代价的。

This is the block I actually lived on. On the right a few buildings down is my old apartment building. The restaurant in the center, was one of my go-to spots. This photo was among the top twenty photos when I googled “Washington Heights.” Unfortunately, it belongs to a news article about a stabbing.
我的公寓里有坏掉的炉子和烤箱,一个我们必须定期维修以便冲洗的厕所,墙上有大量的蟑螂和老鼠,一扇通往消防通道的窗户没有锁好,一个散热器将蒸汽泄漏到整个公寓,使它成为一个桑拿浴室,顺便说一下,也为黑色霉菌提供了理想的条件。如果问题很容易解决,大楼的负责人就很好,否则就不可避免地会很忙。此外,洗衣服从去地下室的几次旅行变成了去四个街区外最近的自助洗衣店的两个小时的旅行。
请记住,从高档社区到普通建筑的巨大变化都发生在八个街区之内。就规模而言,通常认为大约 20 个纽约市街区等于 1 英里,平均一个街区大约需要步行一分钟。我还没有核实,但听起来是对的。
*
**
A definitely-not-completely-accurate-but-sometimes-funny-sometimes-just-weird takes on Brooklyn and Manhattan neighborhoods by Urbane demonstrates how neighborhoods have identities unto themselves.*
在布鲁克林教书
我在纽约教了四年高中数学、科学和计算机科学,我在一所公立特许学校工作,它是多元化特许学校联盟的成员,所以我的课堂是社会经济、种族和民族多元化的。很自然,我的不同学生从纽约市的许多不同社区来到学校。虽然大多数学生来自布鲁克林,但包括斯塔滕岛在内的每个区都有代表。一些学生的通勤单程时间超过两个小时。
面对全国重新种族隔离的趋势,我仍然钦佩学校对多样性的承诺。然而,在那里教书让我面对了一些令人不安的事实。我让一些学生带着所有最新的小玩意来学校,去街对面昂贵的咖啡店闲逛,而其他学生则报名参加任何周末聚会的俱乐部(比如我们的野营俱乐部),因为这意味着他们可以在周末得到食物。纽约市各社区的经济差异非常显著,学生在学校的成绩往往与这些差异相关。
虽然整合学校可能是所必需的,以消除《不让一个孩子掉队法案》( 2001 年由老布什签署)和《力争上游》( 2009 年由奥巴马签署)试图消除的成绩差距,但这还不够。
当在学校学习、参与和集中注意力的能力依赖于一系列其他因素时,高质量的教师和学校资源只能做这么多,其中许多因素由学生的家庭和社区决定或与之相关。
不同社区之间的不平等可能是巨大的,甚至在小区域内也是如此。小区域分析是一种工具,有助于全面了解不同社区的需求,并在制定公共政策和资源分配决策时提供指导。虽然纽约市在这方面并不独特,但它是一个这些不平等经常被放大的城市,而且似乎是一个现在转向公开解决这些问题的战略的城市。这个开放数据项目将如何发展将会很有趣…
寻找意义:社会科学中小区域预测分析的数据问题(下)
如果你还没有看过我这个项目的第一部分,请随意看看。虽然没有必要理解我在这里提出的问题,但我解释了什么是小区域分析,我对这个项目的灵感,我如何收集和准备我的数据,以及我在做这件事时面临的困难。这是一项正在进行的工作,在接下来的几天里将会有这个系列的第 3 部分。
本文原载于 2018 年 8 月。这是原物稍加编辑后的拷贝,被取了下来。
在这里,我将谈谈我在对纽约市五个区的 59 个指定社区的公共卫生、环境和教育数据建模时遇到的困难。

Community Districts of New York City
我将在这篇文章中讨论这三个主题。
- 我考虑和尝试的不同模型
- 有限数据的过拟合问题
- 使用机器学习进行解释和分析,而不是预测
甚至在开始使用模型之前,我就发现了每个特性与教育程度列之间的相关性。有些很有趣。看看不同社区的健康相关指标——尤其是“含糖饮料”的相关性。


在某些情况下,空气质量和“社区健康”指标(一个社区是否繁荣的指标)也有很强的相关性。


最后,种族与国家比率有着同等的相关性…

数据建模
开始时最明显的模型是简单的线性回归。虽然可以将 scikit-learn 的LinearRegression用于多个输出(在这种情况下,没有高中文凭的成年人的比率、有高中文凭(可能有大学文凭)的成年人的比率以及有大学或更高学位的成年人的比率),但该模型假设输出是独立的,因此基本上创建了三个完全独立的线性回归模型,每个模型对应一个输出。
我继续做了,完成了对所有数据的线性回归,得到了 0.95 的 R 值(三个输出的平均值)。这一切都很好,但有两个突出的问题…
第一个问题是,三个产出显然不是独立的;事实上,它们是如此紧密地联系在一起,以至于在每种情况下,这三个比率的总和应该是 100(这是百分比的趋势)。因此,创建三个完全独立的线性模型实际上并不能准确描述情况。

seaborn pairplot of the educational attainment data
第二个问题是,虽然这个模型似乎告诉我们,模型中包含的特征似乎确实与教育成果数据相关,但当我将我的数据分为训练集和测试集时,无论大小如何,我总是得到训练数据大于 0.95 的 R 值,而测试数据通常是负值——我得到的最高值是 0.49,这完全是基于数据的随机分割的运气。所以这里肯定存在过度拟合的问题。只有 59 个样本,因为只有 59 个社区区,这似乎是一个不可避免的问题,无论模型。
以几种不同的方式在第一个相关教育成就特征的问题上取得进展。首先,我研究了可以处理多个输出的模型,同时考虑到它们是相关的。唯一有希望的途径似乎是尖端的神经网络。然而,这些只是在研究论文中提到过,还没有生产模型,所以在这一点上它真的只是在理论阶段…所以对我来说是一个死胡同。
唯一的另一种方法是将我的三个输出合并成一个单一的分数。最简单的方法是给每个教育程度分配一个值:0 代表没有完成高中学业,1 代表高中学历和大学学历,2 代表大学学历或更高。对于每个地区,我将每个百分比乘以其相应的值,然后将它们加在一起。这为每个地区创建了一个单一的,虽然有点抽象的“教育分数”,然后我可以用它来运行一个单一的线性回归。
educational_attainment['edu_score'] = educational_attainment.apply(
lambda row: row['Eduhsdegreeorsomecollege_rate'] + 2 *
row['Educollegedegreeandhigher_rate'], axis=1)
在整个数据集上,这种线性回归产生了 0.96 的 R 值;然而,当分成训练集和测试集时,它陷入了同样的过度拟合陷阱。
有序回归
我并不期望过拟合问题会有更好的结果,所以决定使用mord模块尝试一些有序回归。所以我用我的教育成绩,用平均值和标准差把数据分成 4 类。
# Get the mean and standard deviation (indices 1 and 2 respectively)
statistics = educational_attainment['edu_score'].describe()# Define my categories
def get_ordinal(row):
if row.loc['edu_score'] >= (statistics[1] + statistics[2]):
ordinal = 1
elif row.loc['edu_score'] >= (statistics[1]):
ordinal = 2
elif row.loc['edu_score'] >= (statistics[1] - statistics[2]):
ordinal = 3
else:
ordinal = 4
return ordinal
educational_attainment['ordinal'] = educational_attainment.apply(
lambda row: get_ordinal(row), axis=1)
令我惊讶的是,当我使用mord.LogisticIT()(使用即时阈值变量的有序逻辑回归)时,这在过度拟合方面稍微好一些。当作为一个整体用于数据集时,R 值为 0.93。当分成训练集和测试集时,测试数据集的 R 值至少是总是正值,有时高达 0.5——仍然没有什么值得夸耀的,但却是一个相当大的改进。
对抗这种过度拟合的另一种方法可能是找到不同年份的相同数据,或者找到更多组邻域的相同数据。然而,看到找到这个数据有多么困难,我将把它留到以后再说。因此,虽然我们似乎可以找到相关性,但该模型的任何预测能力本质上都是无意义的,因此没有因果关系可以合理建立。
更有希望的途径似乎是使用 L2 正则化线性回归(也称为岭回归)来计算特征重要性,以伴随我们的原始相关性,以追求理解和解释数据,而不是试图建立一个纯粹的预测模型。对这种分析有用的其他模型是随机 L1 (Lasso)回归和 scikit-learn 的额外树回归。以下是我在实施这些策略时发现的结果。
岭回归、随机套索和额外树
这些模型是解释数据的绝佳工具,因为这些模型是稳定的并且有用的特征往往具有非零系数/分数——这是算法中的冗余和平均试验和/或惩罚高系数以确保特征不会被过度呈现的结果。
作为另一层,我使用了包含对sklearn的支持的eli5包来实现PermutationImportance,其中,一列接一列的值被打乱,以查看模型仍然可以预测目标的程度。
# Using robustly scaled columns...
def get_scores(X, Y, scale_method):
index_tuples = []
model_data = []
for level in Y.columns:
ridge_model = RidgeCV()
et_model = ExtraTreesRegressor(n_estimators=50,
bootstrap=True)
randL_model = RandomizedLasso()
models = [ridge_model, et_model]
y = Y[level] for model in models:
model.fit(X, y)
score = model.score(X, y)
model_name = f'{model}'.split('(')[0]
try:
coefs = model.coef_
except:
try:
importances = model.feature_importances_
except:
importances = np.array(None)
else:
importances = np.absolute(coefs)
finally:
perm = PermutationImportance(model).fit(X, y)
perm_importances = perm.feature_importances_
index_tuple1 = (level, 'importances', score,
model_name)
index_tuple2 = (level, 'perm_importances', score,
model_name)
if importances.any():
index_tuples.append(index_tuple1)
model_data.append(importances)
index_tuples.append(index_tuple2)
model_data.append(perm_importances)
当我对这些不同模型的得分进行平均,并筛选出作为社区和环境健康指标的特征时,我得到了下面的结果。

The most important factors of Environmental and Community Health in predicting Educational Attainment of adults in the community. “Extremes” is an aggregation of rates for “Did Not Complete HS” and “College Degree or Higher”
在继续这项研究的过程中,我计划通过查看相关性和 p 值来选择适当的特征,从而减少特征的数量,希望将过度拟合的影响降至最低。我还计划继续寻找其他数据来源——尤其是可以显示随时间变化的数据。这是一项正在进行的工作。生活也是如此。
用 Apache Spark 大海捞针

“HearU Music” by Yi Mao is licensed under CC BY-NC-ND 4.0
TL;DR: 客户流失对企业来说是一个真实的问题,在不断增长的(大)数据中预测哪个用户可能会流失可能很困难。Apache Spark 允许数据科学家在大数据中轻松地进行大规模数据清理/建模/预测。
大多数数据科学家都知道处理数据并不总是一帆风顺。像清理、输入缺失值、特征工程、建模和预测这样的过程,即使数据小到可以放入笔记本电脑的内存中,其本身也是一个巨大的问题。如果数据比这大得多,事情很容易变得更复杂。解决这个问题的一个非常常见的方法是将数据放在 SQL 或非 SQL 数据库中,在汇总数据并将汇总的数据移动到本地工作站进行建模之前,在那里进行大部分的争论/清理。
然而,有时数据科学家需要大量数据输入到他们的模型中,并根据大数据进行训练和预测。对于传统的库,如 Python Pandas、scikit-learn 或 R dplyr,这并不容易,因为我们只有有限的内存来容纳。
Apache Spark 是大数据生态系统中最大的明星之一。它允许数据科学家使用熟悉的工具工作,但允许 Spark 做所有繁重的工作,如并行化和任务扩展。它提供了类似 Spark 数据帧的工具,类似于 R 数据帧或 Pandas 数据帧。如果您更喜欢传统的 SQL,您可以使用 SQL 来处理数据,而不是使用数据框。Spark 通过 MLlib 库支持许多开箱即用的机器学习算法。它还通过 Spark Streaming 为数据工程师提供流支持,最后,它通过 GraphX 支持原生图形处理。Spark 是数据科学家/工程师在处理大数据时的一把瑞士军刀。
在本帖中,我们将研究一个商业案例,这是许多数据科学家的共同任务,因为它对商业营销或战略工作有非常直接的影响。我们将尝试预测可能会为音乐流媒体平台流失的用户。基于良好的预测,通过促销或折扣来保持用户的参与,以流失用户,这一点至关重要。
在本例中,我们有完整数据集(12GB)的一个小子集(128MB)。由于我在本地集群上工作(仅指我的笔记本电脑,而不是一组服务器),我将对小数据集进行分析,但我们将探索的所有方法都适用于较大的数据,没有什么根本不同,只是 Spark 将在大多数情况下处理并行性。
我们使用的数据来自 Udacity 数据科学家纳米学位计划 Apache Spark 顶点计划
Import necessary libraries. We will import some of them again for clarity later
Spark 需要一个叫做 Spark Session 的东西,这是你的代码和主节点之间的驱动。让我们创建一个,如果还没有的话,或者如果已经有了就返回它。
Creating/getting Spark session. Note that our cluster is a local one.
加载和清理 Sparkify 数据集
我们将用户活动数据集mini_sparkify_event_data.json导入 Spark,然后清除一些缺失的值。
Read data into Spark and do few simple checks on data
让我们浏览一下前几行,感受一下我们的数据
data.take(5)[Row(artist='Martha Tilston', auth='Logged In', firstName='Colin', gender='M', itemInSession=50, lastName='Freeman', length=277.89016, level='paid', location='Bakersfield, CA', method='PUT', page='NextSong', registration=1538173362000, sessionId=29, song='Rockpools', status=200, ts=1538352117000, userAgent='Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0', userId='30'), Row(artist='Five Iron Frenzy', auth='Logged In', firstName='Micah', gender='M', itemInSession=79, lastName='Long', length=236.09424, level='free', location='Boston-Cambridge-Newton, MA-NH', method='PUT', page='NextSong', registration=1538331630000, sessionId=8, song='Canada', status=200, ts=1538352180000, userAgent='"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.103 Safari/537.36"', userId='9'), Row(artist='Adam Lambert', auth='Logged In', firstName='Colin', gender='M', itemInSession=51, lastName='Freeman', length=282.8273, level='paid', location='Bakersfield, CA', method='PUT', page='NextSong', registration=1538173362000, sessionId=29, song='Time For Miracles', status=200, ts=1538352394000, userAgent='Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0', userId='30'), Row(artist='Enigma', auth='Logged In', firstName='Micah', gender='M', itemInSession=80, lastName='Long', length=262.71302, level='free', location='Boston-Cambridge-Newton, MA-NH', method='PUT', page='NextSong', registration=1538331630000, sessionId=8, song='Knocking On Forbidden Doors', status=200, ts=1538352416000, userAgent='"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.103 Safari/537.36"', userId='9'), Row(artist='Daft Punk', auth='Logged In', firstName='Colin', gender='M', itemInSession=52, lastName='Freeman', length=223.60771, level='paid', location='Bakersfield, CA', method='PUT', page='NextSong', registration=1538173362000, sessionId=29, song='Harder Better Faster Stronger', status=200, ts=1538352676000, userAgent='Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0', userId='30')]
每行标识一个用户活动,如当前艺术家、歌曲、会话 Id、用户 Id,以及用户正在从哪个设备收听、unix 时间戳、性别、用户是否登录和付费用户、用户详细信息..等等
数据清理
我们应该检查数据集中的空值。根据结构,我们可以省略一些列或估算(如果我们的分析需要的话)。让我们从统计和视觉两方面来看看
Checking Null and NaN values in our dataset
我们有大量的空值,特别是在某些列中,比如artist、length或song。
我们还观察到,在列组之间的空值计数方面可能存在相关性,例如artist、length或song都具有相同数量的空值。对于firstName、gender、lastName、location、registration、userAgent都是如此。
其他列都没有任何缺失值。
同样,让我们直观地检查缺失值,看看我们之前的相关性声明是否得到支持。如果这是真的,我们可以说不仅空计数是相同的,而且空值出现的行数也应该是相同的。
由于 pyspark 没有可视化库,我们采样 spark 数据帧并将其转换为 pandas 数据帧,然后使用 Seaborn 进行可视化。
plt.figure(figsize=(12, 12))
sns.heatmap(data.sample(False, 0.1, 42).toPandas().isnull())

Using Python Seaborn heatmap we can visualise NULL values in the dataset
上面的热图支持我们的说法,列在缺失值、数字和索引方面是相关的。
我想检查的最后一件事是,如果firstName和其他类似字段为空,那么artist和类似字段是否也为空?
sns.heatmap(data.filter(data.firstName.isNull()).toPandas().isnull())

Filelds like artist, location.. etc all are null when fistName is null
是的,它们是相关的,所以不仅空计数相等的组内部相关,而且组间空模式也相关。用简单的英语来说,如果firstName为空,那么gender也为空,因为它们的空计数相等,但是上面的图表也表明artist col 也将为空
根据下面的两个表,当用户注销或作为来宾收听时,firstName、gender、lastName、location、registration、userAgent和auth列具有空值。因为我们真正感兴趣的是那些已经注册并可能流失的用户,所以我们可以删除这些行,因为它们不是登录用户。
data.filter(data.firstName.isNull())\
.groupBy('firstName','gender', 'lastName',
'location', 'registration','userAgent', 'auth')\
.count()\.show()

data.groupBy('auth').count().show()

Rows where firstName .. etc are null they all are either Guest or Logged out.
# Drop the rows with null values for the columns below
data = data.na.drop(subset=['firstName','gender',
'lastName','location','registration',
'userAgent', 'auth'])sns.heatmap(data.toPandas().isnull())

Removing logged out users gives us a cleaner dataset
现在,我们有了一个更清晰的用户登录数据集。
ts 列是 unix epoch 格式的时间戳,我们最好转换成时间戳格式,并添加到timestamp列中
data = data\
.withColumn('timestamp', from_unixtime(data.ts/1000))data.take(2)[Row(artist='Martha Tilston', auth='Logged In', firstName='Colin', gender='M', itemInSession=50, lastName='Freeman', length=277.89016, level='paid', location='Bakersfield, CA', method='PUT', page='NextSong', registration=1538173362000, sessionId=29, song='Rockpools', status=200, **ts=1538352117000**, userAgent='Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0', userId='30', **timestamp='2018-10-01 00:01:57'**), Row(artist='Five Iron Frenzy', auth='Logged In', firstName='Micah', gender='M', itemInSession=79, lastName='Long', length=236.09424, level='free', location='Boston-Cambridge-Newton, MA-NH', method='PUT', page='NextSong', registration=1538331630000, sessionId=8, song='Canada', status=200, **ts=1538352180000**, userAgent='"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.103 Safari/537.36"', userId='9', **timestamp='2018-10-01 00:03:00'**)]
对于我们的模型,我们将删除一些我们不需要的字段,可能它们在其他列中有重复的信息或者只是不相关。在这一点上,我决定不在我的模型中使用它们,但是由于模型构建是一个迭代过程,如果我相信添加这些列会有所不同,我可能会在以后决定不删除这些列。
cols_to_drop = ['userAgent', 'artist', 'firstName',
'lastName', 'location', 'song',
'ts', 'registration', 'length',
'method', 'status']data = data.drop(*cols_to_drop)data.show()

User music after adding **timestamp** and removing irrelevant columns for prediction
下表告诉我们,免费和付费用户都可以取消他们的服务,但只有付费用户可以降级服务。嗯,有道理。我相信我们可以进一步过滤我们的数据框架来清理几行,因为像设置或帮助这样的页面不会告诉我们太多关于客户流失的信息。

Pages vs User Level. Next Song is the dominant as expected. There are 52 Cancellations (churns)
pages_to_keep = ['NextSong', 'Downgrade',
'Cancellation Confirmation', 'Upgrade',
'Add to Playlist', 'Cancel', 'Submit Upgrade', ]data = data.filter(col('page').isin(pages_to_keep))data.groupBy('auth', 'page').count().orderBy('auth').show()

After filtering out pages like Help, we are left with these above. Cancellation Confirmation is the churn KPI
探索性数据分析
Cancellation Confirmation是对流失最明确的 KPI。Downgrade页面被访问了数百次,但是数据并没有清楚地告诉我们用户在访问该页面后是否被降级,比如我们没有Downgrade Confirmed后面跟有Downgrade页面。此外,数据集中的Downgrade行数远远大于唯一用户数。因此,我坚持使用Cancellation Confirmation作为客户流失指示器。
# In our final dataset we do not have any null values
data.select([count(when(col(c).isNull(), c)).alias(c)
for c in data.columns])\
.head()\
.asDict(){'auth': 0,
'gender': 0,
'itemInSession': 0,
'level': 0,
'page': 0,
'sessionId': 0,
'userId': 0,
'timestamp': 0}
我们的数据集中有多少不同的用户?
data.select('userId').distinct().count()>> 225
让我们来看看一个随机用户的听力活动。 *itemInSession* 连续收听时单调递增。我们可以提取每个会话的最大值,以了解用户参与度(稍后在功能工程部分)
data.filter(col('userId') == 30).show(20)

User activity for user with userId 30. Note that itemInSession increases when the page changes. sessionID stays the same for a continuous music streaming session.
让我们简要地看一下被搅动的用户/订户。看起来free和paid的客户都会流失。我们将这些数据保存到churn_data中,因为我们将在以后加入特征工程部分时使用这些数据
churn_data = data.filter(col('page')=='Cancellation Confirmation')
churn_data.show(10)

List of churned users, there are both free and paid customers. We store the userId for joining later with other summarised dataset
我们稍后将需要一个用户列表,当在特征工程部分加入数据集时,创建一个新的列名churned
churn_data_summary = churn_data.select('userId')\
.withColumn('churned', F.lit(True))churn_data_summary.show(10)

Churned userIds
特征工程
在这一部分中,我们将构建一些特征/预测器来预测不稳定的用户。这通常意味着使用现有的列创建新的列,并进行转换,以便我们可以输入到 ML 管道中。
平均会话间隔时间
衡量用户参与度的一个有用功能是查看用户收听 Sparkify 平台的频率。我们会查看用户有多少个会话以及会话之间的平均时间。如果用户经常访问我们的应用程序/平台,他们可能会更加投入,因此,对于投入的用户来说,平均会话间隔时间应该更短。

userId 10 has 6 streaming sessions and mean time between sessions is 7 days.
在我们的模型中,我们只需要将sessionsFreqDay作为我们的特征,而userId是连接下面其他数据集的关键,剩余的列是不必要的,所以我们要过滤掉
listen_freq = listen_freq.select('userId', 'sessionsFreqDay')listen_freq.show(10)

we use userId as key when joining with other data. sessionsFreqDay is a new feature engineered predictor
会话计数,平均歌曲计数/会话
另外两个好的预测指标是给定用户的总会话数和每次会话收听的平均歌曲数。如果用户有更多的会话,并且每个会话听很多首歌,他们可能会更投入。
下面是另一个聚合数据集,显示了每个用户的收听活动摘要、他们收听了多少个会话以及所有会话的平均歌曲数。

user with id 10, has 6 sessions and on average he/she listened to 133.7 songs/session, whereas user with id 100006 has only 1 session and listened to 45 songs. User 100006 is engaged less.
下面是上面用于user_engagement的 pyspark 代码的 SQL 等价物,我只是在这里添加作为参考,它给出了完全相同的数据,但是使用了更容易阅读的参考。对于那些更喜欢 SQL 而不是 Dataframe 方法的人来说,他们会很高兴地看到他们几乎可以只用 SQL 来进行数据操作。

Same result is achieved with SQL
用户功能
像 *gender* 和 *level* 这样的用户特征在预测客户流失时会很有用,因为免费用户和付费用户或者女性和男性用户的行为可能会有所不同。让我们将这些特征保存到 *user_feature* 数据帧中
user_features = data.select('gender', 'userid', 'level')\
.distinct().orderBy('userId')user_features.show(10)

User Features
最后,让我们将上面创建的所有功能加入到一个joined数据框架中

churned column is the target variable and all other columns (except userId) are predictors
现在我们有 5 个流失预测列:
gender: M 或 Flevel: 付费或免费sessionCount:用户听了多少次音乐?每个会话有多首歌曲meanSongCount:对于给定的会话,每个用户平均听了多少首歌?(一次会议持续多长时间)sessionFreqDay:会话之间的平均天数是多少?更低意味着更多的参与。
在继续之前,让我们做一个理智检查,看看我们的预测是否有意义。下面是每个churned和gender组的sessionCount、meanSongCount和SessionFreqDay的平均值
joined.groupBy('churned','gender')\
.agg({'sessionCount': 'mean',
'meanSongCount': 'mean',
'sessionsFreqDay': 'mean'})\
.orderBy('churned','gender')\
.show()

根据上表,易激动用户倾向于拥有较低的sessionCount,他们在会话中听较少的音乐,但是更频繁地使用平台(易激动用户的会话间隔为 2 - 2.5 天,非易激动用户为 4 - 4.8 天)。最后一个观察结果与我们的预期有点相反,但这仍然是一个有用的预测。一个有趣的发现是,女性用户比男性用户更喜欢这个平台,这对我们的模型也很有用
joined.groupBy('churned','level')\
.agg({'sessionCount': 'mean',
'meanSongCount': 'mean',
'sessionsFreqDay': 'mean'})\
.orderBy('churned','level')\
.show()

当我们从免费或付费用户的角度看数据时,也有类似的观察。sessionCount和meanSongCount对于非搅动用户来说都高于预期,但是搅动用户更频繁地使用该平台。这是否意味着他们不是因为缺乏兴趣,而是因为找不到想要的音乐而烦恼,并因此离开了平台?这将是一个有趣的想法,供以后探讨。但是我们不会在这篇博文中看到它。问问题总是一个好习惯,如果有矛盾的发现,问更多的问题来发现潜在的事实。
我们有一个矛盾发现的另一个原因可能只是统计学上的怪癖。辛普森悖论在某些情况下可能会困扰我们。为了确保我们没有陷入辛普森悖论的统计谬误,让我们一起来看看 T10 和 T11。
joined.groupBy('churned','level', 'gender')\
.agg({'sessionCount': 'mean',
'meanSongCount': 'mean',
'sessionsFreqDay': 'mean'})\
.orderBy('churned','level', 'gender')\
.show()

通过level和gender的划分仍然显示,被搅动的用户有更频繁的参与。从这个角度来看,不存在辛普森悖论
Spark 中的机器学习模型只能处理数字,因此我们需要将我们的分类特征转换成数字。
我们将使用StringIndexer,将数字分配给level和gender,并使用OneHotEncoderEstimator将它们编码成SparseVector。
Converting Strings into indexes then One Hot Encoding into Sparse Vector
# Dropping gender and level (temporarily), just to fit the cell into # screen for readabilityjoined.drop('gender', 'level').show()

String Indexers created level_idx and gender_idx and OneHotEncoderEstimator created dummy columns
如果数字列中的数据是偏斜的,预测值强度会降低,因此我们还应该检查数字列sessionCount、meanSongCount和sessionsFreqDay的分布。让我们目测一下
joined_pandas = joined.select('sessionCount',
'meanSongCount',
'sessionsFreqDay')\
.toPandas()f, axes = plt.subplots(2, 3, figsize=(14, 7), sharex=False)sns.distplot( joined_pandas["sessionCount"] , color="skyblue", ax=axes[0, 0])sns.distplot( joined_pandas["meanSongCount"] , color="olive", ax=axes[0, 1])sns.distplot( joined_pandas["sessionsFreqDay"] , color="gold", ax=axes[0, 2])

All numeric features seems left skewed. We better transform them before modelling.
所有看起来都向左倾斜,让我们应用log和sqrt变换来处理倾斜

Original distribution (top row) and features after transformation. Transformation helped to fix skew.
这看起来更好,我们回到我们的 spark 数据框架joined并在那里应用转换。
joined = joined\
.withColumn('logSessionCount', F.log('sessionCount'))\
.withColumn('sqrtMeanSongCount', F.sqrt('meanSongCount'))\
.withColumn('sqrtSessionsFreqDay', F.sqrt('sessionsFreqDay'))joined.cache()
joined.take(1)

New Columns logSessionCount, sqrtMeanSongCount and sqrtSessionsFreqDay are created
最后,我们需要创建两列,label和features。Spark ML 需要label列,它必须是数字,这意味着我们需要通过转换成整数将churned列转换成label
我们还需要所有的预测器都在一个向量中,Spark ML 处理向量,所以我们需要连接所有这 5 个预测器。
Spark needs a features vector, therefore we add all our predictors into a Dense Vector using VectorAssembler

nonScaledFeatures is a DenseVector created from other features
仅关注nonScaledFeatures和label:
joined_vector.select('nonScaledFeatures', 'label')\
.show(10, truncate=False)

机器学习模型更好地处理标准化/规模化数据,因此我们需要在进入建模阶段之前标准化/规模化我们的特征
scaled = StandardScaler(inputCol="nonScaledFeatures",
outputCol="features",
withStd=True,
withMean=True)\
.fit(joined_vector)\
.transform(joined_vector)scaled.select('features', 'label').show(10, truncate=False)

建模
现在我们在向量中有了缩放/标准化的特征,并标记为预测因子,我们可以进入建模阶段。
这里,我们需要将完整的数据集分成训练集、测试集和测试几种机器学习方法。我们将评估各种模型的准确性,必要时调整参数。我们将根据测试准确性确定获胜模型,并在验证集上报告结果。
我们评估三个模型,DecisionTreeClassifier和RandomForestClassifier和GBTClassifier。RandomForestClassifer和GBTClassifier将许多DecisionTreeClassifiers结合起来,通过使分类器不容易过度拟合来获得更好的精度。DecisionTreeClassifer一般倾向于过度拟合。
我们的第一个模型是带有默认输入的DecisionTreeClassifier
train, test = scaled.randomSplit([0.8, 0.2], seed=42)tree = DecisionTreeClassifier()
prediction_tree = tree.fit(train).transform(test)
prediction_tree.groupBy('label', 'prediction').count().show()

DecisionTreeClassifier predicted 9 churns correctly, but missed 8 churns
为了更深入的了解,我们可以看看每个预测的概率值
prediction_tree.select('label', 'prediction', 'probability')\
.show(20, False)

labels, predictions and probability values for each user
流失事件相当稀疏,基本决策树分类器能够成功预测 9 个流失事件,错过 8 个,错误预测 2 个客户将流失,但没有。
现在我们来看一下带有默认超参数的RandomForestClassifier
prediction_forest = RandomForestClassifier()\
.fit(train)\
.transform(test)prediction_forest.groupBy('label', 'prediction').count().show()

RandomForestClassifier predictions
与DecisionTreeClassifier相比,RandomForestClassifier在这种情况下并没有表现得特别好
最后让我们看看梯度增强树分类器
from pyspark.ml.classification import GBTClassifiergbt = GBTClassifier(featuresCol='features', labelCol='label')
pred_gbt = gbt.fit(train).transform(test)pred_gbt.groupBy('label', 'prediction').count().show()

GBTClassifier has better prediction capability in this test set
GBTClassifier能够超越简单的DecisionTreeClassifier和RandomForestClasssifier。它能够成功地预测 11 次搅拌,只错过了 6 次搅拌。
模型评估、管道、交叉验证和网格搜索
尽管我们通过创建几个表来检查我们的预测准确性,但是 Apache Spark ML 库已经内置了评估器来评估我们的模型是否成功。在我们的例子中,我们的标签是我们可以使用的唯一一个二进制类BinaryClassificationEvaluator。
另一方面,管道对于创建健壮的 ML 管道和避免测试数据泄漏到模型训练中非常有用。管道也让我们的代码更加整洁。
交叉验证允许我们将训练数据集划分为折叠,并在每个折叠上进行训练,以找到更好的模型。
许多模型、转换器和索引器都有超参数。GridSearch 允许我们尝试许多不同的超参数,并使用这些超参数的笛卡尔乘积自动构建许多模型。找到理想的参数被称为模型调整,GridSeach 使这变得轻而易举。
让我们再次获取连接的数据,并在输入管道之前应用log或sqrt转换
joined = user_features\
.join(churn_data_summary,
on=['userId'],
how='left')\
.join(user_engagement,
on=['userId'],
how='left')\
.join(listen_freq,
on=['userId'],
how='left')\
.orderBy('userId')\
.fillna(False, subset=['churned'])\
.withColumn('churned',
col('churned').cast('integer'))\
.withColumn('logSessionCount', F.log('sessionCount'))\
.withColumn('sqrtMeanSongCount', F.sqrt('meanSongCount'))\
.withColumn('sqrtSessionsFreqDay', F.sqrt('sessionsFreqDay'))joined.cache()
joined.show(5)

cleaned dataset, ready for Pipeline
现在定义 ML 管道的每个阶段
Stages define the order of transformations to be applied
[StringIndexer_68305429c317,
StringIndexer_c98ace58d733,
OneHotEncoderEstimator_0216ca1e6769,
VectorAssembler_45b1ace90533,
MinMaxScaler_bfcb63301c7e,
StandardScaler_dd543c1f0b07,
RandomForestClassifier_dce35260ff2f]
创建一个网格搜索对象,我们定义不同的RandomForestClassifer参数,如maxDepth或featureSubsetStrategy
还要定义评估器,该评估器将评估由网格搜索创建的模型的性能,并且将选择具有基于评估器的 bets 度量的模型。
"Number of models to be tested with 5 crossfolds = {}".format(5*len(params))
> Number of models to be tested with 5 crossfolds = 60
由于我们正在安装 60 个模型,这可能需要一些时间。Spark 可以并行拟合模型以加速训练,但在这种情况下,我们串行进行,因为我们只有本地集群。
train, test = joined.randomSplit([0.8, 0.2], seed=42)
model = cv.fit(train)model.avgMetrics

Evaluator results for each model 3 x 4 hyperparameters
我们确实有很多模型,但是cv.transform() 会根据评估器自动选择最佳模型。
让我们用最好的模型来预测
predictions = model.transform(test)
predictions.groupBy('churned', 'prediction').count().show()

Prediction results with RandomForestClassifier with optimum params
在这种情况下,与普通的RandomForestClassifier相比,我们没有得到不同的结果,但是我们看到了如何使用管道、交叉验证和评估器使我们的生活变得更容易。
结论
作为数据科学家,我们总是试图用数据回答问题,并遵循数学和统计的严谨性。在这篇文章中,问题是预测用户流失,使用一种系统的方法,我们取得了一些成功。这是第一次迭代,通常模型构建需要多次迭代,通过添加更好的预测器或更多数据来进一步优化预测。
我们还看到了如何使用 Apache Spark 来处理数据、创建新功能、构建模型并对其进行评估。尽管这一转换基于一小部分数据,但同样的转换可以应用于更大的数据集,而无需更改代码。
这是一个很长的阅读,但希望你喜欢它
天蓝色的海底总动员
使用 Azure 自定义视觉服务探索对象识别

Marlin and Dory are on a search for Nemo — Marlin’s only son, who was taken by a fishing trawler. I just figured out a way to utilize the power of Azure cloud to help them find Nemo in the vast sea.
我正在浏览我童年时收藏的珍贵电影,这时我发现了《海底总动员》。小时候,我一直想帮助尼莫回到他爸爸身边。我只是不知道怎么做。
现在我在技术的陪伴下成长,我想我终于找到了出路。
我们可以用 Azure 帮助马林找到尼莫。这是我们需要的:
什么是 Azure 自定义视觉?
Azure Custom Vision 是一项认知服务,让我们可以构建、部署和改进我们自己的图像分类器和对象检测器。图像分类器是一种人工智能服务,它根据图像的视觉特征将标签应用于图像,而对象检测器是一种人工智能服务,它在图像中找到特定的对象,在我们的情况下,就是 Nemo。
自定义视觉服务使用机器学习算法将标签应用于图像。图像在提交时会被贴上标签。然后,该算法根据这些数据进行训练,并通过在这些相同的图像上测试自己来计算自己的准确性。一旦算法经过训练,我们就可以测试、重新训练,并最终根据我们应用程序的需求使用它来对新图像进行分类。
创建 Azure 自定义 Vision 帐户
要开始构建我们的人工智能解决方案,首先我们需要获得一个 Azure 自定义视觉帐户。让我们开始吧:

Azure Custom Vision Portal
2.登录与 Azure 订阅关联的 Microsoft 帐户,并接受服务条款

Terms of Service
建立一个识别尼莫的模型
现在,我们都准备建立一个机器学习模型,它将能够识别尼莫,并告诉他在哪里。要使用自定义视觉服务,我们需要在 Azure 门户网站中创建自定义视觉训练和预测资源。这将创建训练和预测资源。
创建新项目
- 要创建您的第一个项目,选择新建项目。将出现创建新项目对话框。

Azure Custom Vision projects
2.输入项目的名称和描述。然后选择一个资源组。如果您的登录帐户与 Azure 帐户相关联,资源组下拉列表将显示您的所有 Azure 资源组,其中包括自定义 Vision 服务资源。

3.从项目类型中选择对象检测,并从域中选择常规(精简)。紧凑域针对移动设备上的实时对象检测的约束进行了优化。紧凑域生成的模型可以导出到本地运行。
分类和对象检测:自定义视觉功能可分为两个特征。图像分类将一个或多个标签应用于图像。对象检测与此类似,但它也返回图像中可以找到应用标签的坐标。从而使我们能够在图像中找到物体的位置。
4.最后,选择创建项目。
选择训练图像
作为最低要求,Azure Custom Vision service 要求每个标签至少有 15 张图像,以便在初始训练集中进行对象识别。我们还需要一些额外的图像来测试模型一旦训练。
为了有效地训练模型,我们将使用具有视觉多样性的图像,即尼莫的不同图像。
此外,所有训练图像必须满足以下标准:
- 。jpg,。png,或者。bmp 格式
- 大小不超过 6MB(预测图像为 4MB)
- 最短边不少于 256 像素;任何短于此长度的图像将被自定义视觉服务自动放大
上传和标记图像
在本节中,我们将上传并手动标记图像,以帮助训练对象检测器。
- 要添加图像,请单击添加图像按钮,然后选择浏览本地文件。选择打开以上传图像。

2.与分类器不同,在使用 Nemo 选择图像区域后,必须手动标记单个图像。单击上传的图像以打开标记窗口。

训练模型
一旦所有的图像都被标记,我们需要训练模型使用它。开始训练
- 选择训练按钮。对象识别服务使用所有当前图像来创建识别 Nemo 的视觉质量的模型。

Training the model
2.选择 1 小时培训预算的高级培训,点击培训。

Selecting Training Type
训练过程应该只需要几分钟。在此期间,有关培训过程的信息显示在 Performance 选项卡中。
评估模型
训练完成后,评估并显示模型的性能。定制视觉服务使用我们提交的用于训练的图像来计算精确度和召回率,使用一种称为 k-fold 交叉验证的过程。精确度和召回率是模型有效性的两种不同的度量:
- 精度表示正确的已识别图像的比例。例如,如果模型将 100 幅图像识别为 Nemo,并且其中 99 幅图像实际上是 Nemo,那么精度将是 99%。
- 回忆表示被正确识别的实际图像的比例。例如,如果实际上有 100 个尼莫的图像,并且模型识别出 80 个是尼莫,则召回率将是 80%。
平均精度(mAP): mAP 告知海底总动员处物体探测器的整体精度。
注 :紧凑域比一般域精度低,无法构建可在计算资源有限的智能手机上本地运行的模型。

Evaluating the model
概率阈值
这是在计算精度和召回率时被认为是正确的预测概率的阈值。
用高概率阈值解释预测调用倾向于以回忆为代价返回高精度的结果,这意味着发现的检测是正确的,但是许多甚至没有被模型发现;低概率阈值则相反,即模型检测到大多数实例,但在该集合中存在假阳性。考虑到这一点,我们应该设置概率阈值,这样才能找到尼莫。
测试我们的模型
- 选择顶部菜单栏右侧的快速测试。此操作会打开一个标记为快速测试的窗口。

2.在快速测试窗口中,我们点击提交图像字段,并输入要测试的图像的 URL。要使用本地存储的图像,我们点击浏览本地文件按钮并选择一个本地图像文件。

Quick Test window
所选图像出现在页面中间。然后结果以两列表格的形式出现在图像下方,分别标记为标签和置信度。查看结果后,我们可以关闭快速测试窗口。

Results in a Quick Test window
使用预测图像进行训练
我们可以使用之前提交的图像进行培训,具体步骤如下:
- 打开自定义视觉网页并选择预测选项卡查看提交给物体检测器的图像。

2.将鼠标悬停在图像上以查看模型预测的标签。要将图像添加到训练数据,选择图像,选择图像中正确的标记区域,然后选择窗口右上角的 X 按钮。图像从预测中移除并添加到训练图像中。可以通过选择训练图像选项卡进行查看。

我们现在可以用更新的训练数据重新训练模型,它将被保存为一个新的迭代。
最后,我们已经使用 Azure 自定义视觉服务构建、训练和测试了一个对象检测模型,现在可以帮助 Marlin 寻找 Nemo。
下一步是什么?
我们将导出训练好的模型,使用 CoreML 在 iPhone 上的应用程序上本地运行
感谢阅读。如果你喜欢它,有什么要分享的或者有什么问题,请在评论中告诉我。
在 R 中寻找 R

相关系数(R)、回归的相关系数(R)和编程语言 R 都是数据科学的三个标志性 R。相关系数(r)是一个非常著名的统计测试,在机器学习模型中非常常用。回归的相关系数可以确定斜率与相关值的一致程度,是回归目标预测的重要指标。r 是一种函数式语言,它植根于语言 S,是数据科学家非常流行的选择。
是的,我们在 R 中找到 R,因为我觉得这很有趣。

r 是一种函数式语言,其历史和计算一样悠久。虽然这有时是一种优势,但 R 也一直是一种较小的语言。重要的是要记住,尽管 R 对于数据科学家来说的确是一件大事,并且是一门伟大的语言;r 将不会像 Python 或 C 语言那样拥有广泛的支持。当然,Python 和 C 也有其局限性,Julia 和 Scala 也是如此。总的来说,虽然我发现自己使用 R 的次数比 Python、Julia 甚至 Scala 少得多,但我仍然认为 R 是一个很棒的工具,我甚至比现在更想使用它。
为什么?
我喜欢低级的数据科学,也喜欢高级的。令人惊讶的是,经常会遇到那些对相关系数、模型等非常熟悉的人。在更大的范围内。但是了解一个模型的内部运作,或者至少了解一个模型如何与统计数据一起工作来证明一个更准确的结果是有价值的信息。
在不使用库的情况下设计自己的算法肯定会让你在面试中成为众矢之的,并让你在你的领域中与众不同。这是因为为了开发复杂的算法,可能需要大量的计算机科学技能。我不想详细说明,但是 CS 技能经常会成为工作中非常重要的一部分。
除了正当理由,它的乐趣!
函数公式

formula for the correlation coefficient
在首先,这个计算似乎相当全面和困难。当游戏中的每个部分都被分解成小块时,这个公式就变得简单多了。
- n —样本量。
- σxy——x 和 y 的点积之和。
- σx——x 的总和。
- σy——y 的总和。
- σx——x 的点平方之和。
- (σx)—x 的二次幂之和。
- σy——y 的点平方之和。
- (σy)—y 的二次幂之和。
分解方程后,显然我们需要计算每个单独的部分。幸运的是,R 是在考虑线性代数的情况下构建的。R 中几乎所有的操作数都可以用于线性代数。
- n —我们可以使用 R 的基函数“length”来计算 n
- xy——我们首先需要 x 和 y 的乘积,幸运的是,我们可以使用*操作数来乘以数组。
- σ()基本适马可以使用 sum()函数来计算。
n = length(x)
xy = x * y
Σx = sum(x)
Σy = sum(y)
Σxy = sum(xy)
很简单,对吧?
现在我们只缺少 x 和 y 的点指数的和,以及 x 的平方的和。
x 的和与 x 的和…..
x2 = x ^ 2
y2 = y ^ 2
sx2 = sum(x2)
sy2 = sum(y2)
注意σx 和(σx)之间的区别,它们可能读起来一样,但是它们不是同一个。
编写我们的函数
结合上面所有的数学,我们可以得到我们的值,然后把它们代入 R 函数的公式中,就像这样:
correlationcoeff <- function(x,y){
n = length(x)
yl = length(y)
xy = x * y
sx = sum(x)
sy = sum(y)
sxy = sum(xy)
x2 = x ^ 2
y2 = y ^ 2
sx2 = sum(x2)
sy2 = sum(y2)
r = ((n*sxy) - (sx * sy)) / (sqrt((((n*sx2)-(sx^2)) * ((n*sy2)-(sy^2)))))
return(r)
}
不用说,挺酷的!但是让我们更进一步,多做一点,只是说我们做到了。
相关系数…的平方
回归的相关系数(相关系数的平方)也是一个非常酷的概念,您应该非常熟悉。r 分数因其用作连续模型回归度量而众所周知。r 通常以百分比形式计算,表示回归相似性或差异(对于机器学习来说,与训练集相似是件好事。)

好消息是,我们可以循环使用之前的函数,以获得与平方(r)的相关系数
r2 <- function(x,y){
r = correlationcoeff(x,y)
r2 = r ^ 2
return(r2)
}
简单,简单,柠檬榨汁机
创建回归模型
使用我们用于特征选择的相关系数(r ),以及我们的相关决定系数(r ),我们现在可以着手编写一些重要的加权推理模型。让我们使用线性最小二乘回归方程!如果再正式一点的话,我们会创建一个可以放入预测函数的构造函数,这通常是处理模型时的一个好习惯,因为它们被视为对象类型,而不是一个单一的数学函数(可能是因为拟合变换)。)幸运的是,这个函数不是我的作品集,也不是任何形式的专利,所以我们只是将 x 和 y 放入参数中!
pred_linearleastsquare **<-** **function**(x,y,xt){*# Summatation of x*y*xy **=** x ***** ysxy **=** sum(xy)*# N*n **=** length(x)*# Summatation of x^2*x2 **=** x **^** 2sx2 **=** sum(x2)*# Summatation of x and y*sx **=** sum(x)sy **=** sum(y)*# Calculate the slope:*slope **=** ((n*****sxy) **-** (sx ***** sy)) **/** ((n ***** sx2) **-** (sx)**^**2)*# Calculate the y intercept*b **=** (sy **-** (slope*****sx)) **/** n*# Empty prediction list:*y_pred **=** c()**for** (i **in** xt){pred **=** (slope*****i)**+**by_pred **=** append(y_pred,pred)}return(y_pred)}
让我们做一些数据

显然,随机数据并不代表真实世界的情况,但是不必篡改数据清理来测试功能无疑是避免使用真实世界数据的一个可行的借口。
使用我们的功能
首先,让我们从预测线性最小二乘函数开始:
y_pred = pred_linearleastsquare(trainX,trainy,testX)
然后我们可以使用 r2 来验证我们的模型做得有多好。

但是 r 呢?
相关系数可以非常有效地提供两个不同特征之间的相关性信息,或者更好地提供特征和目标之间的相关性信息。我确实在条件模型中看到了很多 r 的使用,尤其是那些关于连续目标的模型。
所以,不用说,R,R 和 R 都是统计学和数据科学中非常酷和重要的概念。期待看到未来几年 R 的位置,以及其他语言的位置。
用 Q-学习算法寻找最短路径

图是用于模拟对象之间成对关系的数学结构。图是由边连接的顶点组成的。在无向图中,我会找到两个顶点之间的最短路径。
Q-learning 是一种无模型强化学习算法。Q-learning 的目标是学习一个策略,它告诉代理在什么情况下采取什么行动。它不需要一个环境模型,它可以处理随机转移和奖励的问题,而不需要适应。
首先,我导入必要的模块。我将使用 networkx 库来定义一个图形

让我们定义并形象化这个图表

Q-learning 算法包括一个代理、一组状态和每个状态的一组动作。它在某种程度上使用 Q 值和随机性来决定采取何种行动。q 值根据所采取行动的回报和可能的未来回报进行初始化和更新。
数学上,
q 值可通过下式计算:
我想找到从 0 到 10 的最短路径。我需要吸引走到涉及 10 的边,因此我给这些行动很高的奖励。在 networkx 库中,G[node]给出了与该节点形成一条边的所有节点。
- 这里我初始化奖励和 Q 矩阵:
- 除了到达节点 10 的动作,我把所有的奖励都设为 0。这些动作是从 8 到 10 或者从 9 到 10。像奖励一样,Q 值在矩阵中初始化。为了消除不可能的动作,他们的 Q 值被设置为-100。例如,在图中,不可能直接从 2 到 10,因此其 Q 值设置为-100。可能的操作被初始化为 0。让我们来看看 R 和 Q 矩阵:
- 现在我将定义一个函数,它接受一个起始节点并返回下一个节点。它也接受随机探索的探索率。否则,它根据可能动作的最高 Q 值来选择动作。
- 之后,我们需要一个函数来更新所采取行动的 Q 值
- 现在是时候提高 Q 值了,从随机节点开始,走 50000 步
让我们检查一下最终的 Q 值
现在,我们可以通过在决定行动时从 Q 矩阵中选择最高 Q 值,找到 0 到 10 之间的最短路径:

参考
**
https://en.wikipedia.org/wiki/Graph_theory
https://everything.explained.today/Q-learning/
http://first time programmer . blogspot . com/2016/09/getting-ai-smarter-with-q-learning . html

After, we need a function for updating Q-value of the action taken

Now it is time to improve Q-values by starting at random nodes and making 50000 walk

Let’s check the final Q-values

Now we can find shortest path between 0 and 10, by choosing highest Q-value from Q matrix when deciding an action:

References
https://en.wikipedia.org/wiki/Graph_theory
https://everything.explained.today/Q-learning/
http://firsttimeprogrammer.blogspot.com/2016/09/getting-ai-smarter-with-q-learning.html
使用深度学习和位置敏感哈希查找相似图像
使用 FastAI & Pytorch 通过 ResNet 34 的图像嵌入找到相似图像的简单演练。也在巨大的图像嵌入集合中进行快速语义相似性搜索。

Fina output with similar images given an Input image in Caltech 101
在这篇文章中,我们试图实现上述结果,即给定一幅图像,我们应该能够从 Caltech-101 数据库中找到相似的图像。这篇文章用一个端到端的过程来指导我如何构建它。复制项目的全部代码都在我的 GitHub 库中。实现上述结果的过程可以分为以下几个步骤-
- 使用 FastAI 和 Pytorch 从 ResNet-34 模型(在 ImageNet 上训练)转移学习以检测 Caltech-101 数据集中的 101 个类。
- 从已训练的 ResNet 34 模型中取倒数第二个完全连接的层的输出,以得到所有 9,144 个 Caltech-101 图像的嵌入。
- 使用位置敏感散列来为我们的图像嵌入创建 LSH 散列,这使得快速近似最近邻搜索成为可能
- 然后给定一个图像,我们可以使用我们训练的模型将其转换为图像嵌入,然后在 Caltech-101 数据集上使用近似最近邻搜索相似的图像。
第 1 部分—数据理解和迁移学习
正如我上面提到的,对于这个项目,我的目标是查询任何给定的图像,并在 Caltech-101 数据库中找到语义相似的图像。该数据库包含 9,144 幅图像,分为 101 个类别。每个类别大约有 50-800 张图片。

Image examples from Caltech-101 database
我们项目的第一个练习是获得一个深度学习网络,它可以准确地对这些类别进行分类。对于此任务,我们将使用预训练的 ResNet 34 网络,该网络在 ImageNet 数据库上进行训练,并使用 Pytorch 1.0 和 FastAI 库对 Caltech-101 数据库的 101 个类别进行分类。正如我在我的前一篇博客中所写的,我将在这篇博客中概述这个过程。你可以参考这本笔记本找到代码做同样的事情。找到下面的步骤做迁移学习分类加州理工学院-101 图像-
- 使用 FastAI 库使用 Pytorch 的数据集加载器加载数据
- 采用预训练的网络,在这种情况下,ResNet 34,并删除其最后完全连接的层
- 在网络末端添加新的完全连接的层,并仅使用 Caltech-101 图像训练这些层,同时保持所有其他层冻结
- 通过解冻所有层来训练整个网络
第 2 部分—使用 Pytorch 钩子提取图像嵌入
现在我们有了一个预先训练好的网络,我们需要从这个网络中为我们所有的 Caltech-101 图像提取嵌入。嵌入只不过是一个对象在 N 维向量中的表示。在这种情况下,图像嵌入是图像在 N 维中的表示。基本思想是给定图像与另一图像越接近,它们的嵌入在空间维度上也将是相似和接近的。

Image embedding visualization. Credit — Blog
你可以在上面这张取自博客的图片中看到,图像嵌入是一种矢量化形式的图像空间表示,其中相似的图像在空间维度上也很接近。
我们可以从 ResNet-34 获得图像嵌入,方法是取其倒数第二个全连接层的输出,该层的维数为 512。为了在 Pytorch 的深度学习模型中保存中间计算以供检查,或者在我们的情况下提取嵌入,我们使用 Pytorch 钩子。钩子有两种类型——向前和向后。前向钩子用于保存在网络中向前传递的信息以进行推断,而后向钩子用于在反向传播期间收集关于梯度的信息。在我们的例子中,我们需要在推理阶段输出倒数第二个完全连接的层,这意味着我们需要使用一个前向钩子。让我们来看看创建钩子的代码(也在我的笔记本的“提取特征”部分)
**class** **SaveFeatures**():
features=**None**
**def** __init__(self, m):
self.hook = m.register_forward_hook(self.hook_fn)
self.features = **None**
**def** hook_fn(self, module, input, output):
out = output.detach().cpu().numpy()
**if** isinstance(self.features, type(**None**)):
self.features = out
**else**:
self.features = np.row_stack((self.features, out))
**def** remove(self):
self.hook.remove()
创建 Pytorch 钩子只需要上面的代码。SaveFeatures 类从的 torch.nn 模块调用 register_forward_hook 函数,并给定任何模型层,它将把中间计算保存在 numpy 数组中,该数组可以使用 SaveFeatures.features 函数检索。让我们看看使用这个类的代码—
*## Output before the last FC layer*
sf = SaveFeatures(learn.model[1][5])*## By running this feature vectors would be saved in sf variable initated above*
_= learn.get_preds(data.train_ds)
_= learn.get_preds(DatasetType.Valid)## Converting in a dictionary of {img_path:featurevector}
img_path = [str(x) **for** x **in** (list(data.train_ds.items)+list(data.valid_ds.items))]
feature_dict = dict(zip(img_path,sf.features))
第 1–2 行:调用类 SaveFeatures,使用模型层引用将倒数第二个全连接层的输出作为输入。
第 4–6 行:传递 Caltech-101 数据以获得他们的预测。请注意,我们对保存预测不感兴趣,这就是我们使用“_”的原因在这种情况下,倒数第二层的中间输出保存在名为“sf”的变量中,该变量是 SaveFeatures 类的一个实例。
第 8–10 行:创建一个 python 字典,其中图像路径是键,图像嵌入是值。
现在我们的字典中有了 Caltech-101 中每个图像的嵌入表示。
第 3 部分—快速近似最近邻搜索的位置敏感散列法
我们可以使用我们新生成的加州理工学院 101 图像嵌入,并获得一个新的图像,将其转换为嵌入,以计算新图像和所有加州理工学院 101 数据库的距离 b/w,以找到类似的图像。这个过程本质上是计算昂贵的,并且作为新的图像嵌入,必须与加州理工学院 101 数据库中的所有 9K+图像嵌入进行比较,以找到最相似的图像(最近邻),这在计算复杂度符号中是 O(N)问题,并且随着图像数量的增加,将花费指数更多的时间来检索相似的图像。
为了解决这个问题,我们将使用位置敏感哈希(LSH ),这是一种近似的最近邻算法,可以将计算复杂度降低到 O(log N)。这篇博客从时间复杂度和实现方面很详细地解释了 LSH。简而言之,LSH 为图像嵌入生成一个哈希值,同时牢记数据的空间性;特别是;高维度相似的数据项将有更大的机会接收到相同的哈希值。
下面是 LSH 如何转换大小为 K-的散列中的嵌入的步骤
- 在嵌入维中生成 K 个随机超平面
- 检查特定嵌入是在超平面之上还是之下,并指定 1/0
- 对每个 K 超平面执行步骤 2,以获得哈希值

the hash value of the orange dot is 101 because it: 1) above the purple hyperplane; 2) below the blue hyperplane; 3) above the yellow hyperplane. Image Credit — Link
现在让我们看看 LSH 将如何执行人工神经网络查询。给定一个新的图像嵌入,我们将使用 LSH 为给定的图像创建一个散列,然后比较来自共享相同散列值的 Caltech-101 数据集的图片的图像嵌入的距离。以这种方式,代替在整个 Caltech-101 数据库上进行相似性搜索,我们将仅对与输入图像共享相同散列值的图像子集进行相似性搜索。对于我们的项目,我们使用 lshash3 包进行近似最近邻搜索。让我们看看做同样事情的代码(你可以在我的笔记本的“使用位置敏感散列法查找相似图片”部分找到代码)
**from** **lshash** **import** LSHash
k = 10 *# hash size*
L = 5 *# number of tables*
d = 512 *# Dimension of Feature vector*
lsh = LSHash(hash_size=k, input_dim=d, num_hashtables=L)*# LSH on all the images*
**for** img_path, vec **in** tqdm_notebook(feature_dict.items()):
lsh.index(vec.flatten(), extra_data=img_path)
上面的代码获取图像嵌入字典,并将其转换为 LSH 表。要查询 LSH 表,我们可以使用下面的代码—
# query a vector q_vec
response = lsh.query(q_vec, num_results= 5)
第 4 部分—将所有内容放在一起
现在我们已经创建了 LSH 表,让我们编写一个脚本,它可以将图像 URL 作为输入,并从加州理工 101 数据库中给我们 N 个(用户定义的)相似图像。这部分的代码在我的 Github 这里。

Process flow of the find similar image script.
该脚本执行以下任务-
- 加载 LSH 表和我们的 ResNet 34 模型(load_model 函数)
- 从用户调用中获取图像 URL 并下载图像(download_img_from_url 函数)
- 从 ResNet-34 传递图像以获得 512 维图像嵌入(image_to_vec 函数)
- 用 LSH 表查询,找到 N 个(自定义)相似图像及其路径(get_similar_images 函数)
- 在所需的输出路径返回输出,也可以使用 Open CV (get_similar_images 函数)显示输出
我们可以在各种应用程序中使用类似的概念,如在我们的照片库中查找类似的图像,相似外观项目的项目-项目推荐,对图像进行网络搜索,查找近似重复的图像等。
总结(TL;博士)。
在博客中,我们看到了深度学习在寻找语义相似图像中的应用,以及如何使用位置敏感哈希(LSH)进行近似最近邻查询,以加快大型数据集的查询时间。此外,值得注意的是,我们不是在原始特征(图像)上使用 LSH,而是在嵌入上,这有助于在庞大的集合中进行快速相似性搜索。
我希望你喜欢阅读,并随时使用我在 Github 上的代码来为你的目的进行测试。此外,如果对代码或博客文章有任何反馈,请随时联系 LinkedIn 或发电子邮件给我,地址是 aayushmnit@gmail.com。您也可以在 Medium 和 Github 上关注我,了解我将来可能会写的博客文章和探索项目代码。
用凝聚聚类法寻找“最佳”最差电影

This is a man entirely composed of veins. From The Room (2003).
首先,一点背景。
当我的父母终于有了有线电视,这对我来说是一件大事。一个我从来不知道存在的奇怪表演的整个世界展开了。我最喜欢的节目(除了《辛普森一家》之外)是一部低成本的连续剧,讲述了一个男人和两个机器人被困在太空,被迫观看可怕的电影。它被称为神秘科学剧场 3000 。因为《T4 》,我这个年纪的一代孩子突然发现了故意看那些糟糕透顶的电影的价值。二十年过去了,我仍然热爱它(鉴于 MST3K 的重启——我并不孤单)。
不过,一些糟糕的电影,就其糟糕程度而言,更纯粹一点。他们的坏是如此纯粹,以至于他们自己变得令人愉快。尽管 Ringer 在几年前提出了相当好的标准,但识别“好”的坏电影与仅仅是坏电影的区别仍然是一个短暂的过程:
1.电影的乐趣必须来自于它的糟糕。它的坏需要是创造一种迷惑的享受感的东西。
2.肯定有一种普遍的感觉,那就是那些制作这部电影的人认为他们所做的很棒,或者至少是好的。好的坏的电影都有最低限度的自我意识。这里有两个例子可以帮助解释这种情绪:(1) 马克格鲁伯不是一部好的坏电影,它是对好的坏电影的致敬;(2)快速五部不是一部好的坏电影,它是一部故意陷入荒谬的电影(然后制造一种类似于好的坏电影自然引发的反应)。
3.这部电影发行时一定是一个严重的失败。评论家们,上帝保佑他们,将电影作为一种艺术形式保持高标准,通常不会因为电影质量差而奖励它。这样,他们是一个有用的,尽可能客观的资源来决定哪些电影是坏的,因此有资格成为好的坏的。
就像我说的,很好,但更多的是直觉。我想知道是否有一种方法可以定量识别这些电影。我觉得有!看看下面的用户评分分布。
一、好电影,《教父》第二部(1974) :

评论分布非常左倾。大部分评分在 4 到 5 之间。
现在吉利(2003) ,一部没有人真正喜欢的电影:

非常右偏,大多是 0.5 和 1.0 的评分。
下面是邻居 (2014) :

一个相当正常的分布。对大多数人来说,这似乎是一部相当不错的电影。
但是现在,《房间》(2003),成了衡量其他“好”坏电影的基准:

嗯,有些事。最高收视率在 0.5 桶,第二收视率在 5.0 桶。所以也许我们要找的分布是重尾分布。然而,从上述分布来看,0.5 和 1.0 的评级仍然比 4.5 和 5.0 的评级多得多。因此,我们可能要寻找的是一个高收视率到低收视率的比例高于一般烂片的分布。让我们做一些特征,看看我们是否能抓住它。
首先,让我们看看我们是否能测量一部给定电影向高或低评级倾斜的程度。使用 MovieLens 大型数据集,我创建了一个新的数据帧:

现在,首先我需要创建一个特征来测量高评分或低评分分布的偏斜程度。首先,我们将找到 0.5/1 或 4.5/5 的评论百分比。
df_master[‘percent_0.5to1’] = (df_master.iloc[:,[5,6]].sum(axis=1)) / df_master[‘total_count’]
df_master[‘percent_4.5to5’] = (df_master.iloc[:,[13,14]].sum(axis=1)) / df_master[‘total_count’]
那我们就简单的取高低评级百分比的差。这将告诉我们分布是更倾向于低评级还是更高评级:
df_master['percent_polarity'] = df_master['percent_0.5to1'] - df_master['percent_4.5to5']
为了确保我们选择了那些尾部很重的电影,我们将增加一个功能:
df_master[‘total_tails’] = df_master[‘percent_0.5to1’] + df_master[‘percent_4.5to5’]
现在我们需要考虑样本大小。就目前情况来看,一部有 1000 条评论的电影和一部有两条评论的电影一样有可能出现在我们的最终名单上。我们需要建立一个最低数量的审查。但是我们如何得到这个数字呢?
我决定找出一些评论数量较高的电影的平均值,并计算在达到平均值之前需要多少评论。下面是一些简单的代码:
import statistics as st
from statistics import mean
def rolling_mean(column):
mean = round(column.mean(),2)
roll = []
for i in column:
roll.append(i)
new_mean = round(st.mean(roll),2)
if mean == new_mean:
return mean, len(roll)
else:
continue
最后,我将对这些值进行平均,以确定我的临界值。最后,我的最低评论数是 25 条。我们准备聚类吧!:
df_trim = df_master.loc[df_master.total_count >= 25]
cols = ['percent_polarity','total_tails']
lg_test = df_trim[cols]scaler = StandardScaler()
bigtest_scaled = scaler.fit_transform(lg_test)
bigtest_features = pd.DataFrame(bigtest_scaled, columns=lg_test.columns)
好吧!现在是有趣的部分——凝聚聚类(不,我是认真的)!聚集聚类的神奇之处在于它考虑了数据点之间的个体关系。我已经编写了几个函数来完成这项工作。
首先,我们需要计算出正确的链接和距离参数,看看我们应该创建多少个集群:
def agg_cluster(df,linkage=’ward’,distance=’euclidean’):
# Takes in a dataframe, cluster linkage and distance
# produces a dendogram + cophenet score
Z = shc.linkage(df,linkage,distance)
# Check cophenet correlation score.
c, coph_dists = cophenet(Z, pdist(df))
print(‘Cophenet Correlation:’,c)
plt.title(‘Hierarchical Clustering Dendrogram (truncated)’)
plt.xlabel(‘No. of clusters’)
plt.ylabel(‘distance’)
dendrogram(
Z,
truncate_mode=’lastp’, # show only the last p merged clusters
p=12, # show only the last p merged clusters
leaf_rotation=90.,
leaf_font_size=12.,
show_contracted=True, # to get a distribution impression in truncated branches
)
plt.show()
#Shows the distance of the 12 final splits.
print(‘Last 12 cluster distances:’,Z[-12:,2])
然后,我们将实际执行群集本身,看看我们会得到什么:
def agg_clust(df,n,affinity,linkage):
agg_clust = AgglomerativeClustering(n_clusters=n,affinity=affinity,linkage=linkage)
assigned_clust = agg_clust.fit_predict(df)
return agg_clust,assigned_clust
我最好的选择是平均连锁和欧几里德距离。这是树突图:

注意最后一次合并的距离有很大的跳跃。这表明这些集群并不特别相似。看起来我们最终应该有三个集群。让我们运行它,并将我们分配的群集添加到数据框中:
bigtest_agg,as_bigtest = tqdm(agg_clust(bigtest_features,n=3,affinity=’euclidean’,linkage=’average’))
bigtest_avg = df_trim.copy()
bigtest_avg[‘Cluster’] = as_bigtest
现在,让我们来看看我们的好/坏集群。我们有房间吗?我们有。:

我们还有有肌肉的圣诞老人,歌舞女郎,巨魔 2 ,麦克和我,来自外太空的计划 9,妖精 4:在太空等等。看起来相当不错!
发现单词的复杂性
认识一个单词需要多少个单词?

假设我们想玩一个拼字游戏的升级版,我们仍然根据单词的复杂程度得分,但使用不同的度量标准。我们将根据单词理解的难易程度来评分,而不是使用字母。
这是我们衡量难度的方法。我们会拿一本梅里亚姆-韦伯斯特词典来查这个单词。为了学习一个英语单词,我读了它的定义。至此,我已经明白了原词的意思。然而,要完全理解一个词,我需要知道每个词在其定义中的含义。我需要对每个定义都这样做。考虑下面的例子。
假设我用单词大学开始玩拼字游戏。Meriam-Webster 将大学定义为(这听起来开始像是告别演说……)提供教学和研究设施并被授权授予学位的高等教育机构。对于这个游戏,假设我们已经学会了英语中所有的停用词。这些词像 an、of、for、your、only、to 等。至此,我知道了大学这个词的定义。但我仍然需要了解以下单词的含义:机构、高等、学习、提供、设施、教学、研究、授权、授予、学术和学位。所以,我们看着一个机构的定义,我们学习这个定义中的所有词汇,我们继续前进。

这个游戏有点难以捉摸。不知道院校是什么意思,怎么学大学这个词?没有太大意义。但是我们需要这个警告来防止无限循环。
举个例子,假设我想学习单词数字。它的定义是单位之和。过一会儿,我需要看一下一个单位的定义,它是第一个也是最少的自然数。同样,过一会儿,我需要学习数字的含义。数字是单位的总和。一个单位是最小的自然数。一个数是一个单位。一个单位就是一个数字。我们创造了一个无限循环,所以我们什么也学不到!

我也认识到这是一种简化的方法。例如,假设我想从事计算机科学,所以我想查找单词 Python。我们只考虑第一个定义,即各种大型蛇类中的任何一种。下面的代码是用 Python 写的,但不是用大蟒蛇写的。然而,我们利用这种情况来减少计算的复杂性。
该算法
Meriam Webster 有一个 API——一个应用程序编程接口。把它想象成一个在线词典。我可以告诉我的电脑向梅里亚姆和韦伯斯特询问定义,他们会把它送回来。我们将不得不清理和格式化它一点,但之后,我们将在我们的道路上。
我们需要记录三套。第一个是一组停用词(像 a、and、for、that 等词。从上面)。第二个是已知单词集——这些单词的定义我们已经查过了。我们最后一组是一组不认识的单词。当我们得到一个定义,我们可以看看每个单词。如果是停用词,我们已经知道了。如果是我们之前查过的词,太好了!我们知道定义,可以继续前进。然而,如果我们遇到一个不认识的单词,我们就必须把它添加到不认识的单词集中,以后再看(也许现在,也许以后)。
对于任何有兴趣看代码的人来说,在这里查看一下。
结果呢
我试用的第一个词是 python。为了理解单词 python 的定义,我们需要学习 11227 个其他单词!下面是我们代码的一些输出。
------------------
Definition of python: any of various large constricting snakes
Learnign the word: large
------------------
Definition of large: exceeding most other things of like kind especially in quantity or size
Learnign the word: like
------------------
Definition of like: to feel attraction toward or take pleasure in
Learnign the word: kind
------------------
Definition of kind: a group united by common traits or interests
Learnign the word: group
------------------
Definition of group: two or more figures forming a complete unit in a composition
Learnign the word: two
------------------
Definition of two: being one more than one in number
Learnign the word: feel
下图显示了每 500 次迭代中未知单词的数量。在未知单词数量的图表中,我们看到开始时有一个尖峰。经过大约 25,000 次迭代,对于任何一个任意的定义,我们知道的定义中的单词比不知道的要多。这种模式继续下去,我们慢慢地缩小未知单词的范围。

我们需要学习 11227 个其他单词来理解 python 。但是,要理解傻的定义,只需要 4999 个字。博士取 11221 字。而电脑取 11202。我相对随意地选择了这些单词,所以其中三个需要大约 11,210 个单词似乎有点可疑。尤其是因为梅里亚姆·韦伯斯特在他们的字典中列出了超过 171,476 个单词。
如果您想进一步研究这个问题,或者您想研究一下代码,请在这里查看资源库。你需要一个来自 Meriam-Webster 的 API 密匙,但是你可以在这里很容易地得到一个。寻找快乐!
感谢您的阅读!
疑问?评论?发邮件给我andrew.oliver.medium@gmail.com。我很想收到你的来信!
寻找 NBA 最好(和最差)的合同

Image by Keith Allison on Wikimedia Commons
多年来,NBA 有一些臭名昭著的糟糕合同(也有一些抢断)。像任何理性的人一样,我想做的只是对这些合同进行统计分析。所以在这篇文章中,我有几个目标:
- 确定好合同和坏合同的分界线(并根据球员的工资找到合理的期望值)
- 分析历史上一些最臭名昭著的合同,并确定哪个合同表现最差
- 分析一些当今 NBA 最大的合同,看看谁表现不佳,谁表现过度
如果你想看到的只是结果,那么直接跳到靠近底部的“结论”部分。
方法学
韵律学
方法非常简单。我需要一种精确的方法来测量:
- 工资水平(根据不同年份进行调整)
- 玩家的净贡献
我决定衡量合同的大小,用它在合同期限内占工资帽的百分比来衡量。这似乎是最明显的选择,因为球队主要受到工资帽的限制,因此这反映了球员所占用的相对资源。
第二个指标更复杂。没有一个数字可以完美地概括一个球员在球场上的贡献,但我必须选择一个尽可能好的数字。最终,出于几个不同的原因,我选择了 Win Shares。赢股衡量进攻和防守,它说明了玩家的总影响。例如,如果一名球员错过了时间(由于受伤、停赛或教练的选择),统计数据应该反映他们的整体影响已经减弱。
样品
接下来,我必须确定我要分析的样本。我首先提出了一些选择样本的客观标准:
- 只有完成的合同才应该被衡量,因为合同的签署是基于球员在整个交易期间的预期价值。例如,一个 30 岁的人签了一份 5 年的合同,平均来说,他的收入会逐年下降。因此,这份合同在头一两年可能看起来很划算,但那只是因为预计他们在接下来的时间里表现会差得多。
- 没有入门级的合同将被分析,因为他们设定的工资往往是一个重大的讨价还价。我们将能够得出的一个有趣的结论是,在给定的工资水平下,球员的合理期望——但入门级别的交易可以解释这一点。
- 不会对一年期交易进行分析。这有两个原因。首先,一年期合约是为了低风险而设计的。一份“糟糕”的一年期合同其实并没有那么糟糕。其次,样本量太不稳定了。因伤错过几场比赛很容易让交易看起来很糟糕,一场强有力的比赛会让合同看起来像是一生的交易。
考虑到这些标准,是时候找到并过滤我的样本了。尽管添加每份合同很诱人,但数据的性质意味着需要大量的手动电子表格工作。因此,我不得不挑选一个更小的样本来代表整体。为此,我使用了 Spotrac 并按照 2014 年的工资进行了排序(因为这些合同现在应该已经完成了)。我进入了每个球员(按降序排列),并添加了他们职业生涯中符合上述标准的所有合同。我总共手工记录了不到 150 个不同玩家的 250 份合同。此时,我认为我的样本已经足够大了。
回归
从这里开始,这是一个简单的问题,根据球员的表现回归合同的大小。

图一。赢股与使用的薪金上限百分比的回归
薪资与绩效关系的 R 为 0.324。因此,签署好的合同对通用汽车来说有点冒险。

表 1。数据集统计学。
我们还可以看到,两个数据集的范围相当大。

表二。数据集的取值范围。
在样本中,从技术上讲,最有价值的合同来自布兰登·巴斯。巴斯从 2007 年到 2009 年以不到 160 万美元的价格与达拉斯小牛队签订了一份为期 2 年的合同,并在此期间贡献了大约 840 万股 Win 股票。大多数最划算的合同都是类似的低薪交易。如果我们排除这些低薪合同(低于工资帽的 5%),最有价值的合同是斯蒂芬·库里 2013-2017 年 4400 万美元的合同。在合同期内,他贡献了约 59.6 Win 股份,并获得了 2 次 MVP。按照工资帽的百分比,库里每个赛季贡献 0.97 份 Win 股份(这一指标将被称为“调整后的 Win 股份”或“调整后的 WS ”)。
样本中价值最差的合同是科比的最后一份合同。布莱恩特签署了一份 2014-2016 年的两年合同,他获得了 4850 万美元的薪酬,同时贡献了约 0.2 Win 的股份。科比每个赛季贡献大约-0.003 的调整后赢球份额。
将这种方法应用到 NBA 历史上最糟糕的合同中
现在,当我们用同样的方法量化 NBA 历史上一些最糟糕的合同时。

表 3。选择历史合同的估价。
使用这种方法在一些选择的最高(或接近最高)合同中,吉尔伯特·阿里纳斯 6 年 1.11 亿美元的合同看起来可能是 NBA 有史以来最糟糕的合同。然而,布兰顿·罗伊的交易并不遥远。
将该方法应用于当前玩家
为了应用当前的方法,有几个重要的考虑因素。首先,我们需要考虑基于玩家年龄的性能期望的重要性。
按年龄预测绩效趋势
为了预测这一点,我需要确定当以 Win 份额衡量时,典型的职业轨迹是什么样的。为了衡量这一点,我从 1990 年以来入选 NBA 最佳阵容,但现在已经退役的球员中抽取了一小部分样本(n=50)。我使用了全明星球员,因为我们将分析的大多数现役球员都是全明星球员。

图二。50 名 NBA 全明星球员按年龄划分的平均胜率。
这些数据几乎完全符合你的预期;球员稳步提高,直到 27 岁达到顶峰,从那时起开始永久下降。职业生涯确实有一些不寻常的起伏,这可能是由于样本有限,但由于时间限制,我决定不进一步扩大我的样本。
使用这个指数,我可以确定对球员表现的期望,同时考虑到在合同的剩余时间里他们的表现是否会下降或提高。
为了说明这一点,我将以拉塞尔·维斯特布鲁克为例。威斯布鲁克是他合同的第一年,目前已经 30 岁了,所以他的表现应该会在合同的剩余时间里下降。因此,如果威斯布鲁克要达成“中值交易”(MVD),他将不得不在交易的前几年超额完成交易,以弥补后几年的表现不佳。
从我们之前的数据集,我们知道调整后的年度 WS 0.327 构成了 MVD(在这种情况下使用中值来排除低价值交易中的高价值异常值)。因此,威斯布鲁克实现 MVD 的期望如下:

表 4。调整后的 WS 对拉塞尔·维斯特布鲁克的期望示例。
利用这些值,我们可以将他目前的实际表现与预期表现进行比较。今年,威斯布鲁克有望以约 0.175 的调整后 WS 结束——显著低于 MVD。
样品
为了确定应该分析什么样的合同,我们只需按降序使用最大的活跃合同。

表 4。所有现役 NBA 合同达到或超过 1 亿美元。
样本中不包括合同直到 2019 年才生效的球员,以及处于交易第一年的球员,因为这些球员太不稳定(勒布朗·詹姆斯的交易到目前为止看起来很糟糕,因为他因伤错过了很多时间)。
创建价值类别

图三。调整后 WS 的 250 个合约的直方图。
接下来,为了便于阅读,我想创建一些任意的类别,用于放置球员合同的价值。这些类别如下(根据 MVD 的期望值衡量):
垃圾箱火灾 : -0.24 以下
低值:-0.16–0.24
低于平均水平:-0.08–0.16
公允价值 : +/- 0.08
高于平均水平:+0.08–0.16
高值:+0.16–0.24
偷 : +0.24 以上
评估合同
对这些合同进行评估和分类后,结果如下:

表 5。符合选择标准的价值超过 1 亿美元的有效 NBA 合同的估计值。
请记住,这些代表了团队到目前为止从合同中获得的“价值”——它们并没有预测未来的价值。同样,这里也有一些有趣的结果。按照这个标准,斯蒂芬·库里显然没有履行他的合同。这可能反映了 Win Shares 的缺陷,以及一些伤病的结果。
结果
所以,总结一下这个分析的结果:
- 历史上最差的合同可能是吉尔伯特·阿里纳斯,他签了一份 6 年 1.11 亿美元的合同,但总共只贡献了 2.4 胜股份
- 我能找到的价值最差的合同是科比·布莱恩特的最后一份合同,他在两个赛季中被支付了 4850 万美元,贡献了-0.2 胜股份
- 我发现的最好的合同,排除低价值交易(
- 在价值超过 1 亿美元的有效合同中(不包括第一年的球员),詹尼斯·阿特托孔波提供了最佳价值,而戈登·海沃德提供了最差价值
如何为神经网络找到正确的架构并微调超参数

Image by Gordon Johnson from Pixabay
这篇博文是我上一篇博文的续篇,讲述了如何使用 LSTMs 来预测一只股票的股价,给出它的历史数据。我们已经看到了如何编译一个 Keras LSTM 模型。在这里,我们将看到找到正确架构和超参数的一些方法。
以下是我们的一些选择:
- 手动调谐或手动搜索 —这是找到正确配置的最痛苦的方法,其中你逐个尝试每个参数的具体值。但是有了一些经验,对最初结果的仔细分析和直觉,它可能真的会有帮助。
- 网格搜索——这确实是唯一能从所有选项中给出最佳参数集的方法。您为想要优化的每个参数传递一系列值,然后训练并查找每个组合的验证损失。可以想象,这是最耗时的方法,而且通常不可行。
- 随机搜索 —这是网格搜索的子集,随机选择所有可能组合的子集。
- 贝叶斯优化/其他概率优化 —这种(贝叶斯优化)方法确实涉及数学,老实说,我还没有探索过其中的数学。我将给出它真正做什么的概述,即使你不知道它的内部工作原理,你仍然可以在程序中应用它,就像我们将在后面看到的。贝叶斯优化使用称为高斯过程的东西来猜测或建模目标函数(我们希望通过找到正确的超参数集来最小化的函数)。在这种方法中,对我们想要评估目标函数的次数设置了限制,因为它被认为是非常昂贵的。首先,在参数范围内确定一组随机点,以观察函数值。然后使用高斯过程来猜测目标函数。使用“采集函数”来决定下一个采样点。并且这个过程重复上面设置的“极限”次数。深入了解可以参考本和本。还有其他技术,如使用 Hyperopt 实现的 TPE (代码如下)。可以查看这篇论文了解 TPE。
网格搜索实现
如果你正在使用 SK-Learn 模型,那么你可以直接使用它们的 GridSearchCV 。使用起来相当简单。您可以访问上面的文档链接。一个优点是它也可以选择并行运行作业。如果你正在使用 Keras 模型,那么你将不得不使用 Keras 模型的包装器,如这里的所解释的。
但是如果它不起作用或者你不想学习新包的语法,你可以像这样实现一个简单的最小网格搜索:
其他更智能的搜索实现
有几个开源软件包可以使用其他“更智能”的搜索算法来最小化目标函数。我将展示远视和距骨的例子。下面是如何使用 Hyperopt 实现超参数调整,Hyperopt 使用 TPE 算法最小化函数。
Sample code for Hyperopt
在上面的代码片段中,变量“search_space”保存您想要搜索的参数及其值。最后的“fmin”函数是进行最小化的实际函数。这个程序本身非常简单,我已经给它添加了注释,所以理解起来应该不成问题。但是我想更深入地挖掘搜索空间字典的形成,因为这有点尴尬。让我们把它放在显微镜下:
主字典保存了我们想要优化的参数的所有键。我们将主要处理两个函数(或者他们称之为随机表达式),即选择和一致。" hp.choice "接受一个值列表,从中进行尝试。这个函数然后返回其中一个选项,应该是一个列表或元组。我们传递给 hp.choice 和 hp.uniform 的字符串(第一个参数)主要供 Hyperopt 内部使用。" hp.uniform "统一返回第二个和第三个参数之间的值(低和高)。棘手的地方在于,当你有“lstm_layers”这样的参数时,它定义了网络中 lstm 层的数量。对于这样的参数,我们有两组新的参数要测试:第一组是当网络中的 LSTM 层数为 1 时,第二组是当使用两个 LSTMs 层时。在这种情况下,您可以看到我使用 hp.choice 告诉系统我对该参数(lstm_layers)的选择,该参数的值由键“layers”给出。当 Hyperopt 使用两个 lstm 层测试模型时,它将考虑另外两个参数进行测试,即第二个 LSTM 层中的节点数(lstm2_nodes)和用于第二个 LSTM 层的漏失(lst m2 _ 漏失)。我保留了第一个 lstm 层空白,但你也可以包括其他参数来测试。
我希望我清楚如何为实际目的构建样本空间。现在让我们看看另一个声称使用概率方法(结合网格或随机)来减少评估数量的库— Talos 。
Code snippet for Talos
Talos 的用法类似于以前的工具;你必须创建一个函数来建立模型,训练它,根据验证数据对它进行评估。唯一的区别是模型函数返回 Keras 历史对象和模型,而不是字典。这里要注意的另一件事是,我们在 Scan 函数中传递 X 和 Y,但它们从未被使用过,因为我们想要在当前迭代中基于所选的“batch_size”和“time_steps”构建数据。但是对于更简单的问题,它会更容易。
你也可以使用Hyperas(hyperpt+Keras),它是 hyperpt 的一个包装器。主要的优点是你不需要学习任何新的语法/功能。你所要做的就是像前面一样定义一个搜索空间字典,然后像下面这样建立你的模型。您所要做的就是将您想要测试的参数值放在双花括号中(例如{{ [1,2,3] }})。
但是 Hyperas 在这种情况下不起作用,因为我是从' model '函数调用' data '函数,使用双花括号的语法导致了一些问题。我没有深究这个问题,因为我已经有了其他工具。尽管如此,我认为这里值得一提,因为这要容易得多。
这很好,但是如何知道这些工具返回的结果是最好的呢?如果你问我,我们不会知道。您必须权衡利弊——您必须在微调上花费多少时间,以及有多少验证损失对您来说是足够好的。在我看来,如果你试着花一些时间对上述工具的结果进行手动调整,那将是最好的。
例如,当我处理股票数据集时,我首先编写了自己的网格搜索实现,并在云上运行。然后,我尝试了上面的这些工具进行智能调优,但不幸的是,云虚拟机非常慢(比我的笔记本电脑还慢!)那天,我已经没有耐心了。幸运的是,当我读到这些工具,实现并开始在云上运行它们时,我的网格搜索已经完成了。我对这些值进行了网格搜索:
search_params = {
"batch_size": [20, 30, 40],
"time_steps": [30, 60, 90],
"lr": [0.01, 0.001, 0.0001],
"epochs": [30, 50, 70]
}
这个只有 4 个参数(81 个组合)的搜索运行了 24 个小时!由于兴奋中忘记实现登录(错误 1 ),所以没有结果在此分享。通过使用网格搜索的结果,我得到了令人失望的预测:

Initial Result
但是我还没有优化其他的东西,比如层数等等(我从一个有 2 个 LSTM 层和 1 个密集层的神经网络开始— 错误 2。总是从更简单的模式开始,先试水,然后逐步发展。反正我决定进一步手动优化。我采用了网格搜索的最佳结果,并尝试了其他参数。无论我如何努力,我都不能改善损失。我知道这是过度拟合,所以我试图增加辍学,但无济于事。然后我顿悟了,可能是我太努力了,1 个 LSTM 层就够了(是的,我知道我很蠢— 错误 3 低估了神经网络的力量)。但是你怎么知道模型是否过度拟合呢?其实挺简单的。在我的例子中,训练错误与验证错误看起来像这样:

首先,当你看到训练数据和验证数据之间的巨大差距时,这是过度拟合。这背后的逻辑是,您的模型从您的训练数据中学习得很好,但它无法将其推广到验证数据(新数据)。第二件事情是验证错误情节的诡异形状。到处都是。这意味着模型只是预测新数据的随机值,这就是为什么跨时代的验证损失之间几乎没有关系。另一件要找的事情是纪元日志。如果你看到训练损失持续减少,但验证损失波动或一段时间后保持不变,这可能是过度拟合。
因此,我删除了 LSTM 的第二层,并添加了下降层,其值高于我一直使用的值(0.2 到 0.5)。瞧啊。

final plot

final train vs validation loss. Notice how the gap is gradually reducing
巨大的进步对吗?我很确定,如果我们付出更多的努力,我们会做得更好。
我没有分享训练模型的代码片段,因为它与上面的代码片段相同;只有正确的参数。你可以在我的 Github 简介这里找到所有完整的程序。
在的下一篇文章中,我将分享一些重要的工具/技巧,它们在这个项目中对我帮助很大,但通常没有给予足够的关注。
寻找正确的模型参数

Photo by Jason Wong on Unsplash
如果您一直在阅读有关数据科学和/或机器学习的书籍,那么您一定会接触到与 MNIST 数据集相关的文章和项目。该数据集包括一组 70,000 张图像,其中每张图像都是从 0 到 9 的手写数字。我还决定使用相同的数据集来了解微调机器学习模型参数如何产生差异。
这篇文章解释了我如何使用GridSearchCV来寻找这个数据集的最佳拟合参数,并使用它们来提高精确度和改善混淆矩阵。您可以在下面的 GitHub 资源库中找到代码:
该项目包括使用 GridSearchCV 来确定估计器参数的最佳组合。…
github.com](https://github.com/kb22/Digit-Recognition-with-Parameter-Tuning)
导入库和数据集
我首先导入必要的库。我用训练和测试数据作为.csv从到这里。数据集中的每一行都由一个标签和 784 个像素值组成,以表示 28x28 的图像。
训练数据包括 60,000 幅图像,而测试数据集包括 10,000 幅图像。
一旦我有了数据,我就从中获取特征和标签,并将其存储在train_X、train_y、test_X和test_y中。
探索数据集
分析阶级分布
正如我在以前的文章中所讨论的,每个类的数据应该大致相同,以确保正确的模型训练没有偏见。

Count of images for each digit (0–9)
如果我们看这个图,每个数字的计数都有一些差异。然而,差别不是太大,模型仍然能够很好地根据数据进行训练。因此,我们可以更进一步。
查看训练图像
让我们看看真实的图像。我从训练数据中随机选择 10 张图像,并用plt.imshow()显示出来。

10 Randomly selected images from the dataset
在这 10 张随机图像中,我们立即看到的是任何一种类型的数字之间的差异。看看上面 10 张图中的所有4。第一个是粗直的,第二个是粗斜的,第三个是细斜的。如果模型可以从数据中学习,并实际检测出4的所有不同风格,那将是非常令人惊讶的。
应用机器学习
我决定使用随机森林分类器对训练数据进行训练,并对测试数据进行预测。我使用了所有参数的默认值。
接下来,利用预测,我计算了准确度和混淆矩阵。
该模型达到了 94.4%的准确率。混淆矩阵表明,该模型能够正确预测大量图像。接下来,我决定调整模型参数,尝试改善结果。
参数调谐
为了确定模型参数值的最佳组合,我使用了GridSearchCV。这是由sklearn库提供的一种方法,它允许我们定义一组我们希望为给定模型尝试的可能值,它根据数据进行训练,并从参数值的组合中识别最佳估计值。
在这个特例中,我决定为几个参数选择一个值范围。估计器的数量可以是 100 或 200,最大深度可以是 10、50 或 100,最小样本分裂为 2 或 4,最大特征可以基于sqrt或log2。
GridSearchCV期望估计量,在我们的例子中是random_forest_classifier。我们将可能的参数值作为param_grid传递,并将交叉验证设置为 5。将verbose设置为 5 会向控制台输出一个日志,而njobs设置为-1 会使模型使用机器上的所有内核。然后,我拟合这个网格,用它来寻找最佳估计量。
最后,我使用这个最佳模型来预测测试数据。
看看上面的精度,我们看到,仅仅通过改变模型的参数,精度就从 94.42% 提高到了 97.08% 。混淆矩阵还显示更多的图像被正确分类。
机器学习不仅仅是读取数据和应用多种算法,直到我们得到一个好的模型,但它也包括微调模型,使它们最适合手头的数据。
确定正确的参数是决定使用哪种算法并根据数据充分利用算法的关键步骤之一。
结论
在本文中,我讨论了一个项目,其中我通过使用GridSearchCV选择参数值的最佳组合来提高随机森林分类器的准确性。我使用 MNIST 数据集,将准确率从 94.42%提高到 97.08%。
阅读更多文章:
基于搜索查询推荐文章
towardsdatascience.com](/lets-build-an-article-recommender-using-lda-f22d71b7143e) [## 使用 Flask、Flask RESTPlus 和 Swagger UI 处理 API
Flask 和 Flask-RESTPlus 简介
towardsdatascience.com](/working-with-apis-using-flask-flask-restplus-and-swagger-ui-7cf447deda7f) [## 使用机器学习预测心脏病的存在
机器学习在医疗保健中的应用
towardsdatascience.com](/predicting-presence-of-heart-diseases-using-machine-learning-36f00f3edb2c) [## matplotlib——让数据可视化变得有趣
使用 Matplotlib 创建世界各地人口密度的美丽可视化。
towardsdatascience.com](/matplotlib-making-data-visualization-interesting-8bac1eb3d25c)
请随意分享你的想法和想法。也可以通过 LinkedIn 联系我。
用 BigQuery 寻找顶级编程语言

如果你一直关注谷歌的云平台,你对 BigQuery 并不陌生。在我看来,BigQuery 是谷歌武库中最具差异化的工具。凭借 Pb 级的仓储能力、数百个开源数据集和熟悉的 SQL 接口,开发人员的准入门槛非常低。
上周,我在浏览 BigQuery 中的公开数据集时,碰巧发现了 Github 的数据集。它有提交数据、编程语言数据、存储库内容数据和许多其他酷表。
在浏览了整个数据集之后,我可以想到很多分析查询,我可以在数据集上运行这些查询来获得一些天才的见解。例如,我可以使用存储库内容数据集来找出最流行的 Java 包,使用提交数据集我可以找出哪个作者提交了最多的提交,或者我可以使用语言数据集来根据存储库计数找出顶级编程语言(这恰好是本文的主题)。
在回答这个有名无实的问题时,我们将学习 BigQuery 中的三个概念。
- UNNEST()和重复数据类型
- 窗口和 Rank()函数
- 命名子查询
设置
首先让我们将数据集添加到我们的 BigQuery 控制台。在 GCP web 控制台中导航到 BigQuery。
在左侧面板上点击添加数据>锁定项目。
输入项目名称 bigquery-public-data 。点击 pin ,你应该会看到项目被固定在窗格中。
您也可以转到数据集页面这里并点击查看数据集,这也应该会将项目添加到您的 BigQuery 仪表板。
该项目包含 BigQuery 托管的所有公共数据集,您需要导航到 github_repos 数据集。在里面你会看到 9 张桌子。

Public Github data set in BigQUery
出于本帖的目的,我们将使用 语言 表,该表具有以下模式。

Schema for languages table
让我们通过查询记录来了解数据。
SELECT * FROM `bigquery-public-data.github_repos.languages` LIMIT 1000
您会注意到表中的 language 字段是记录类型(也称为 STRUCT)的嵌套重复列,这是正确的,因为一个存储库可以有用多种语言编写的代码。你可以在这里读到更多关于他们的信息。
这使我们想到了使用重复(数组)类型的第一个概念。
UNNEST()和重复数据类型
为了使用 language 字段,我们首先需要展平该列,这样我们可以在单独的行中获得每个值,而不是在一行中获得该字段的所有值。我们将使用 UNNEST()函数,正如它的名字一样,它将嵌套的记录解嵌套到单独的行中。
SELECT
repo_name,
arr.name AS LANGUAGE,
arr.bytes AS language_bytes
FROM
`bigquery-public-data.github_repos.languages`,
UNNEST(LANGUAGE) arr
LIMIT
10
每种语言记录都有语言名称和语言字节,其中存储了库中有多少字节的代码是用任何特定的语言编写的。在这篇文章中,我们将用代码中最大字节数的语言标记一个存储库,这将我们带到窗口和排名的下一个概念。
窗口和 Rank()函数
对于每个存储库,我们想找出哪种语言拥有最大的字节数,我们将通过对每个存储库中的语言进行排序来实现这一点。我们将使用前面的查询作为子查询来构建我们的查询。
SELECT
repo_name,
LANGUAGE,
RANK() OVER (PARTITION BY t1.repo_name ORDER BY t1.language_bytes DESC) AS rank
FROM (
SELECT
repo_name,
arr.name AS LANGUAGE,
arr.bytes AS language_bytes
FROM
`bigquery-public-data.github_repos.languages`,
UNNEST(LANGUAGE) arr ) AS t1
LIMIT
10
这将向我们的结果添加一个等级列,我们需要选择等级=1 的行,因为这些行代表每个存储库的顶级语言。
SELECT
t2.repo_name,
t2.LANGUAGE
FROM (
SELECT
repo_name,
LANGUAGE,
RANK() OVER (PARTITION BY t1.repo_name ORDER BY t1.language_bytes DESC) AS rank
FROM (
SELECT
repo_name,
arr.name AS LANGUAGE,
arr.bytes AS language_bytes
FROM
`bigquery-public-data.github_repos.languages`,
UNNEST(LANGUAGE) arr ) AS t1 ) AS t2
WHERE
rank = 1
LIMIT
10
现在我们的数据集已经准备好了,可以根据存储库的数量找到最常用的语言。我们将创建一个命名查询,它可以在后续查询和子查询中使用,以获得更好的可读性。
命名子查询
BigQuery 支持 WITH 关键字来创建命名查询,这有助于您从中进行选择。命名查询没有具体化,每次引用时都会计算整个查询。你可以在官方文档这里阅读更多关于 WITH keyword 的内容。
WITH
repositories AS (
SELECT
t2.repo_name,
t2.LANGUAGE
FROM (
SELECT
repo_name,
LANGUAGE,
RANK() OVER (PARTITION BY t1.repo_name ORDER BY t1.language_bytes DESC) AS rank
FROM (
SELECT
repo_name,
arr.name AS LANGUAGE,
arr.bytes AS language_bytes
FROM
`bigquery-public-data.github_repos.languages`,
UNNEST(LANGUAGE) arr ) AS t1 ) AS t2
WHERE
rank = 1)
如果您复制粘贴这个查询,您将得到一个语法错误,,因为如果不在任何后续的 SELECT 语句或表达式中使用它,您就不能拥有一个命名的子查询。
该查询将返回两列存储库名称(repo_name)及其语言。
从这里开始,通过计数找到顶级存储库应该是小菜一碟。
该查询应该产生以下结果集。

Top 10 programming languages by repository counts on Github
这个查询的结果并不令人震惊,随着 web 应用程序和 SPAs 的出现,JavaScript 在图表中名列前茅。
现在你知道了。按存储库数量排名的前 10 种编程语言。
如果你在代码中发现任何错误或者有任何问题,请在下面留下你的评论。
直到那时快乐编码。
用人工智能找到你需要的东西
路径搜索算法介绍

大三的时候上了第一堂人工智能课。我神采奕奕地走进去。我期待着所有的热门词汇——神经网络、支持向量机、贝叶斯网络等等。第一堂课是我杜克经历中最令人失望的 75 分钟。我得到了深度优先搜索和递归,而不是听起来很酷的时髦词汇。
这是我最喜欢的课程之一,我的教授非常清楚他在做什么。我们必须先学习基础知识,然后才能做大事。如果我们不了解内部和外部的基本原理,我们就没有对抗深度学习的机会。宫城先生在教我们给汽车打蜡。路径搜索打开,路径搜索关闭…

Copyright 1984 Columbia Pictures — The Karate Kid
尽管如此,随着我了解的越来越多,我记得我对路径搜索算法嗤之以鼻。他们看起来一点也不聪明。你是说你只是生成所有可能的路径,猜测其中最好的一条,然后探索那条路径?人类比这聪明多了。我们处于食物链的顶端是有原因的!
但那次演讲后我不得不去办点事。我得去吃午饭,买课本,然后去上另一节课。所以,我想“我可以吃午饭,然后去上课,然后拿我的课本。啊,但是等等,那时候商店已经关门了。也许我会去拿书,然后吃午饭,然后去上课。但是,不,我现在饿了。好吧,我去吃午饭,拿书,然后去上课。“如果你错过了这里的讽刺,再读一遍。

要么是计算机非常聪明,要么是人类没有我们想象的那么聪明。我倾向于后者。
概观
在本文中,我们将介绍四种搜索算法来解决下面的问题。假设给你一个二维网格。你从左上角开始,只能向下或向右移动。网格中的每个单元格都有一个点值。收集路径上所有像元的点值。你的目标是收集尽可能多的分数。

递归
我们首先尝试一种递归方法。递归简单易行。我们的基本情况是到达右下角的单元格。否则,我们将递归调用向下向右移动。然后我们在每一步取这两个调用的最大值。这允许我们将问题分解成更小的子问题。这种方法的实现如下。

Code source here.
深度优先搜索
这种方法可行,但是递归调用代价很高。随着网格的增长,我们可能会遇到堆栈溢出。当我们进行的递归调用超过内存中的堆栈所能处理的数量时,就会发生这种情况。因此,我们转向一种深度优先搜索(DFS)方法。
在 DFS 中,我们会先充分探索一条道路,然后再转向另一条。我们使用堆栈来跟踪我们的路径。我们尽可能向下移动,然后向右移动。我们仍然在探索与递归解决方案一样多的路径,但是我们使用了更少的内存并防止了堆栈溢出。对于长度为 2 的二维网格,我们的堆栈如下所示。

我们必须做一些跑腿的工作来编写我们的 DFS 算法。我们将定义一个 GridCell 类。对于每个单元格,我们将记录我们是从这个单元格向下还是向右。对于每个完整路径,我们将记录分数。如果它大于我们当前路径的分数,我们将把我们的最大值和最优路径更新为当前值和路径。这显示在下面的代码中。

Code source here.
双向搜索
我们仍然没有解决算法中的一个主要问题。我们重复计算来自同一个单元的所有路径。这导致了许多重叠的子问题。例如,考虑下面大小为 n = 5 的网格。大致有 nn种方法可以到达中间的细胞。从中间的单元格,有大约 n 多种方式到达右下角的单元格。这意味着我们正在为此路径和所有其他路径进行 n 计算。这是非常低效的。

如果我们能从角落开始呢?如果我们从左上和右下开始工作,然后在中间相遇会怎么样?然后,我们可以从上半部分和下半部分分别选择最佳路径,并将它们配对。我们现在只做 n 计算,而不是 n 计算。这是一个巨大的进步,尤其是对于大 n 来说。这被称为双向搜索,如下图所示。注意箭头的方向。

但是,我们可以做得更多。我们可以利用现代计算机的处理能力。当你的 Python 代码运行时,它被分解成你的 CPU 可以执行的代码。开箱即用,Python 代码不支持并行性。出于我们的目的,我们认为并行是功能的同时执行。现在,我们的代码线性运行。如果 funcA() 在 funcB() 之前, funcA() 需要在 funcB() 开始之前终止。

然而,事实并非如此。如果 funcA() 和 funcB() 是独立的,我们可以同时运行。我们可以使用 Python 中的多处理来利用并行性。这意味着我们可以同时计算左上和右下的路径。
我们并行执行双向搜索,并在运行时看到了相当大的改进。这个实现的代码可以在这里找到。但是,对于非常大的 n ,这个问题还是需要一段时间。为了解决这个问题,我们求助于启发式搜索。

启发式搜索
我们的下一个算法利用了搜索和人工智能中的一个关键概念。 启发式 的想法。启发式是一种评估功能,帮助我们估计达到目标的最佳路径。在每次迭代中,我们将启发式算法应用于所有可能的未来状态。然后我们探索哪个状态具有最高启发值的路径。
我们需要为我们的问题定义一个试探法。假设我们位于网格的左上角。无论我们向右还是向下移动,所有灰色的单元格都是可以访问的。然而,如果我们向下移动,就会失去橙色细胞,如果我们向右移动,就会失去蓝色细胞。因此,我们可以查看橙色单元格和蓝色单元格的平均单元格值。我们将朝着更高的平均单元格值的方向移动。我们可以对每个细胞都这样做。

这个解决方案不精确。但是,它快得令人难以置信。我们也看到相对最优的解决方案。如果我们的单元格值范围从 1 到 5,我们会看到对于网格大小为 n = 13 的解决方案有 90%是最优的。随着单元格范围的增加,我们看到的最优解会越来越少。例如,在 1 到 100 的范围内,我们看到对于 n = 13 有 85%的最优解。这可能不会给你一条去月球的路,但会给你一条去杂货店的路。
这种启发式搜索算法的实现如下。如果你感兴趣,可以随意克隆库并尝试启发式方法。更精确的启发式方法会导致更优的解决方案。

Code source here.
结束语
这个问题在现实世界中有很多应用。一个明显的例子是 GPS 应用程序规划路线。我们可以根据道路的速度限制除以距离来给道路赋值。在这里找到一个解决方案可以让我们找到一条快捷的路线。但是如果我们能给代表一个问题的状态分配点值,我们可以使用这些算法中的任何一个。我们可以很容易地使用这些完全相同的算法来为新 iPhone 的发布找到最佳的营销策略。
这也凸显了很多搜索问题的问题。随着这些问题越来越多,找到最佳解决方案需要太长时间。因此,我们必须在速度和优化之间做出权衡,正如我们在这里所做的那样。
感谢阅读!
疑问?评论?在【andrew.oliver.medium@gmail.com】给我发邮件。我很想收到你的来信!
数小时内找到梦想中的家——数据驱动的方式
使用数据和经过测试的流程寻找梦想家园,减少时间和困惑。不需要编码。

时间太少,选择太多,而且没有明显的好选择
找到你梦想中的家是生活中更令人兴奋的追求之一。但这也是一项极其艰巨的任务。我们许多人花费大量的时间、思想、精力,当然还有金钱,去寻找一个我们称之为家的地方。我知道这一点,因为这是我在街上和地铁里听到的所有人谈论的话题。这是一个全面的项目,几乎不可能与生活、家庭和工作的其余部分一起管理。许多人开玩笑说,他们最好辞掉工作,以便最终能够给找房子以适当的关注。他们说,太糟糕了,如果他们这样做了,也无法获得抵押贷款。
真的,谁能责怪他们呢?使用一些基本参数,在您选择的酒店门户上启动搜索。如果你住在一个城市,你可能会看到大量的结果,每个结果的缺点和好处,你必须以侦探的方式去发现。一个。由。一个。
几个月前,当我开始梦想在伦敦有一个新家时,我就在那里(尽管,就本文而言,它也可能在纽约、新加坡或旧金山)。幸运的是,我在一家分析公司工作,因此有一种即时的冲动,认为一定有更好的方法——即使你自己不是数据科学家。确实有。对我来说,答案在于数据驱动的方法。
接下来仍然是大量的工作——不要误会我的意思。像以往一样,找到正确的数据,然后找到处理它的方法仍然很棘手,所以我在下面总结了我的过程,希望对你有所帮助。您的位置和要求会有所不同,但我相信这个过程会与您的搜索和个人限制相类似。
位置,位置,还有…什么?
列出你的优先事项
在选择房产时,有许多方面很重要。找房子总是要妥协的。有些事情你可能不太愿意向前看。重要的是你设定自己的极限,并坚持下去。一旦你开始改变你的想法太多,你基本上回到考虑所有的选择。然而,这是一个毫不妥协的排斥游戏,只有当你清楚什么对你来说是重要的,你才能成功地做到这一点。我还将这个列表保留为主要的环境因素,因为我们将在缩小地理区域后看看你喜欢什么类型的房产。
我的一些优先事项是:
- 我的预算+/- 20%
- 最少一间卧室(无工作室)
- 步行 15 分钟内有一大片绿地
- 不到 45 分钟的通勤时间(对我和我妻子来说)
- 入室盗窃和暴力犯罪率低
- 高空气质量
获取数据,并绘制地图
既然我们有了优先事项,我们需要做一些研究。正如我在数据咨询公司 QuantumBlack 的工作一样,找到并探索正确的数据是最关键的一步。我是一个视觉型的人,所以我总是寻找已经以地图的形式出现的数据,或者可以使用免费的在线工具放入其中的数据。
预算

One of the many considerations FindProperly can help you with.
对于我的首要任务来说,一个惊人的资源是find proper。该网站提供了一系列惊人的工具来帮助你找到你实际上能负担得起的住处,等等。
您的“位置”

Which places do you visit often?
为了找出你实际上应该住在哪里,制作一张你最重要地点的地图。通过创建一个你的位置历史热图,它也可以帮助你标出你过去去过的很多地方。
思考一下:
- 工作场所
- 学校
- 最好的朋友
- 机场
- 公园
- 主要交通枢纽
- 运动设施
- 等等。
一旦你规划好了关键地点,你就可以使用 Mapumental 这样的工具来设定出行时间的限制。
噪音

I like my peace and quiet.
噪音水平会对你新家的生活质量产生重大影响。你可能想留意汽车交通、铁路线和飞行路线。伦敦的前两个可以在这里找到。
空气污染

This air pollution map really made me want to live in N6
空气污染地图可能很难找到,但是对于伦敦来说,这张应该可以帮你开始。就污染物而言,你可能要注意这四种:
- 二氧化氮
- 臭氧
- PM10
- PM2.5
犯罪

Explore the Metropolitan Police’s crime dashboard
不是所有的罪行都是平等的。区分哪种犯罪对你作为居民的影响最大是值得的。就我个人而言,我优先考虑盗窃和暴力犯罪。前往遇见警察浏览他们的地图。
为你的房产寻找优先区域
组合您的数据
现在你已经有了对你来说很重要的所有东西的地图,你需要把它们结合起来找到你要寻找的区域。简单的方法是并排看地图,或者抓图,然后用 photoshop 等图片编辑工具叠加。
如果你想更进一步,你可以从上面的网站下载数据(大多数网站都有下载选项),并将其加载到地图网站,如 kepler.gl 。

Crime data from the Metropolitan Police loaded into Kepler.gl
探索入围区域
厌倦了坐在电脑前?很好,因为下一步你必须站起来出去。理想情况下,你会把搜索范围缩小到两三个区。现在,你需要去拜访他们。去逛逛,感受一下这个地方,看看是不是“你”。你能想象自己在街角的咖啡馆里度过一个上午吗?你喜欢那边的古董市场吗?这些学校看起来像什么?离开大街。你能想象自己住在什么安静的树叶茂密的街道上吗?
心选择一个地方就像选择一个家一样。访问你的入围地区应该有助于你进一步缩小搜索范围。
找到你的新家
决定代理机构
现在,也只有现在,打开一个房产搜索网站。定义您在地图要素上选择的区域,并浏览结果列表。这还是研究。你实际上还没在找唯一的房产。即使你正在寻找一个家,在许多市场,当一个地方出现在比如说 Zoopla 上时,你已经太晚了。这是因为房产中介公司会给邮件列表上的客户第一次浏览的机会,所以一旦你在网上看到一个广告,成百上千的其他人可能在一周前就已经收到了。
相反,你要找的是哪些代理机构代表了吸引你的房产类型。在网上列出你喜欢的地方,然后检查它们是由哪个代理机构代理的。你可能会发现有几个是由同一家公司代理的。当我完成这一步时,我喜欢每个区域的 10 个地方。四个坐在代理处 A,三个坐在代理处 B,三个坐在代理处 c。挑选合适的房产数量最多的代理处,走进去,和他们握手,然后进入他们的电子邮件列表。
等待
从现在开始,这是一个等待的游戏。我的最后一条建议是,如果可以的话,耐心点。如果你遵循了所有这些步骤,你很快就会在你的收件箱里找到你梦想中的家,并且可以安排看房——而不用几个月来每天晚上都去搜索房产网站。
回家
我们的结果?整个任务总共花了我几个小时(我希望你现在能少花点时间)。我们最后看了四处房产。我们选择了我们看到的第三个。当然,这是谈判和合同真正疯狂的时候,但那是另一回事了。为此,我只能祝你好运。
如果你用数据找到了梦想中的家,请在评论中分享你的经历。我很想听听。
最初发表于【https://www.thomasessl.com】
句子嵌入的细粒度分析
将单词或句子表示为高维空间中的实值向量,使我们能够将深度学习方法融入自然语言处理任务中。这些嵌入作为从序列标签到信息检索的各种机器学习任务的特征。单词 2Vec(及其变体)是通过使用分布假设(出现在相同语境中的单词往往具有相似的含义)来生成单词嵌入的 go 模型。
然而,当一个人想要对可变长度的句子进行编码时,事情就变得更加棘手了。在自然语言处理领域,为可变长度的句子生成固定长度的嵌入已经有了很多的研究。这些固定长度的句子嵌入对于涉及句子级语义的任务(如文本摘要)至关重要。

Similar sentences have similar embeddings. Image from TechViz
生成句子嵌入的最简单方法是简单地连接组成单词的单词嵌入。然而,这为不同长度的句子产生了可变长度的嵌入。对于期望固定长度的特征向量的下游模型,这是有问题的。因此,我们需要想出一种方法,将单词嵌入简化为一个固定长度的向量,该向量可以捕捉句子的所有重要方面。
有两种流行的生成固定长度句子嵌入的技术:
- 连续单词袋(CBOW)
- 序列对序列模型,如 rnn 或 LSTMs
连续的单词袋
CBOW 方法不考虑句子中单词的顺序。这是一种非常天真的方法,只需对所有组成单词的单词嵌入进行求和(或求平均值)来生成句子嵌入。
CBOW 是一个重载术语,也是用于训练 word2vec 算法的一种算法的名称。基本上,单词包用于描述任何忽略单词顺序的算法或嵌入技术。连续来自于这样一个事实,即我们在实值向量的域中操作。因此,CBOW 是一种建模方法,在这种方法中,人们在组合单词嵌入时忽略了单词排序。
人们可能会觉得这是一种非常糟糕的获取句子信息的方式,因为它完全忽略了词序。
例如:“努力学习,不要玩!”、“玩命,不学习!”将具有相同的【CBOW 嵌入,尽管它们的含义与相反。
然而,CBOW 在实践中被证明是一种有效的嵌入技术,并且在更复杂的模型出现之前是标准的。
序列对序列模型
像 RNN、LSTM 这样的序列到序列模型逐个处理单词嵌入,同时保持存储上下文信息的隐藏状态。在处理一个句子结束时的隐藏状态实质上编码了来自整个句子的信息,因此代表了该句子的嵌入。

Input is the word embedding for (t+1)th word, and the (t+1)th output is the context aware embedding for that word. The state at this stage represents the context embedding till the (t+1)th word.
该架构以编码器-解码器的方式训练,其中 RNN 或 LSTM 充当编码器。由编码器产生的嵌入被馈送到执行一些其他任务的解码器。在这个任务上的损失训练了编码器-解码器架构,此后可以丢弃解码器,而仅仅使用编码器作为嵌入生成器。
这种产生句子嵌入的方法能够基于句子中的单词排序进行区分,因此可能提供比 CBOW 模型更丰富的嵌入。
分析嵌入
由上述两种方法生成的句子嵌入对于解释来说是高度不透明的,并且不能直接评估它们的强度。评估这些嵌入的唯一可能的方法是使用利用这些嵌入的下游任务,然后比较这个复合模型在任务上的性能。很明显,这不是最理想的,而且还有很多需要改进的地方。要打开这个黑箱, 使用辅助预测任务对句子嵌入进行细粒度分析(阿迪等人) 步骤在。他们提出了一种方法,在细粒度级别上比较句子嵌入的基本句子特征,如句子长度、句子中的项目及其顺序。
方法学
对于句子的每个低级特征,制定一个预测任务,并为该任务训练一个分类器网络。分类器的性能显示了句子嵌入能够多好地捕捉该特征。由于这些特征是可直接解释的句子的低级属性,因此该实验提供了对句子嵌入质量的洞察。
正在考虑的特征是:
- 句子长度:给定一个句子嵌入,分类器网络必须预测句子的长度(句子被分入不同的类别)。
- 单词出现:给定一个句子嵌入 s 和一个单词嵌入 w ,分类器要预测这个单词是否出现在句子中。
- 单词排序:给定一个句子嵌入 s 和单词嵌入 w1 和 w2,分类器需要预测哪个单词先出现。
考虑中的嵌入是使用 word2vec 嵌入上的平均的 CBOW,以及 LSTM 自动编码器-解码器。自动编码器-解码器意味着网络被训练产生与输出相同的输入。解码器和编码器都是 LSTM,编码器 LSTM 用于生成句子嵌入。
注:图例中的“Perm”指的是句子中的某些单词随机排列的实验。
结果
- 长度实验

From the paper
显然,LSTM 架构能够对句子长度进行编码,准确率超过 85%。然而,更令人惊讶的是,与 20%的大多数预测准确性相比,CBOW 表现得非常好(65%)。
当人们看到句子嵌入的规范与句子长度的关系图时,可以解释 CBOW 的这种令人惊讶的表现。

随着越来越多的单词嵌入被平均在一起,总和接近于零。单词嵌入在原点周围相当均匀地分布,因此根据中心极限定理,可以预期随着越来越多的嵌入被添加,总和接近零。因此,嵌入的规范作为句子长度的指标。
- 词出现

CBOW 的真正实力在这里可见一斑。对于低维嵌入,CBOW 能够比更复杂的顺序模型更好地捕捉单词身份。令人惊讶的是,性能随着维数的增加而降低。
- 词序

正如预期的那样,LSTM 嵌入能够很好地捕捉单词的排序,并且随着嵌入维度的增加,性能也增加。然而,当考虑到 CBOW 模型没有试图保留任何单词顺序时,CBOW 的性能也是值得注意的,正如我们在上面看到的那样。CBOW 在平衡类设置中给出 70%的准确度,即随机预测准确度为 50%。
作者假设大部分词序信息是在词序统计中获得的,即统计上某些词出现在其他词之前。这一假设得到了一项实验的进一步支持,在该实验中,作者完全放弃了句子嵌入,而只使用单词嵌入来预测单词排序。

编码器-解码器架构的性能也下降到 CBOW 的水平。这表明一些关于词序的信息是由词序统计本身捕获的,而额外的排序信息是由句子排序提供的。
即使去除句子嵌入,CBOW 模型的性能也几乎不受影响。
最后的想法
我们看到,尽管 CBOW 是一个如此简单的模型,但它在某些任务上却惊人地有效。对于低维嵌入,它能够很好地保持单词的同一性,并且在一定程度上能够对句子长度和单词排序进行编码。
LSTM 嵌入在编码低级句子属性方面非常有效。然而,增加嵌入的维度超过某个点提供了边际收益,事实上在某些情况下是有害的。
最后,实验只考虑了低级的句子属性。这显示了嵌入在捕捉句子的表面方面的有效性,但是没有显示语义概括或更深的句法方面的许多细节。对于这种概括,需要考虑单独的辅助任务,作者将其作为未来的工作。
免责声明:本文提供了一个构建来介绍在 中提到的使用辅助预测任务对句子嵌入进行细粒度分析的结果(Adi 等人;17) 。所有显示结果的图片都来自论文,许多讨论和假设都直接取自论文,只是稍加转述。
干杯!
Python 中的细粒度情感分析(第 1 部分)
在这篇文章中,我们将评估和比较 5 类斯坦福情感树库(SST-5)数据集的几个文本分类结果。

Source: Pixabay
“学会选择很难。学会做好选择更难。在一个充满无限可能的世界里,学会做出正确的选择更难,或许太难了。”—巴里·施瓦茨
当开始一个新的 NLP 情感分析项目时,为一个给定的应用缩小选择方法的范围可能是一个相当艰巨的任务。我们是使用基于规则的模型,还是根据自己的数据训练模型?我们应该训练一个神经网络,还是一个简单的线性模型就能满足我们的要求?我们是否应该花时间和精力来实现我们自己的文本分类框架,或者我们是否可以使用一个现成的框架?解释结果和理解为什么做出某些预测有多难?
本系列旨在回答上述一些问题,重点是细粒度的情感分析。在余下的章节中,我们将使用 Python 中几个著名的 NLP 库来比较和讨论分类结果。下述方法分为三大类:
基于规则的方法:
- TextBlob :用于情感分析的简单的基于规则的 API
- VADER :基于简约规则的社交媒体文本情感分析模型。
基于特征的方法:
- 逻辑回归:sci kit-learn 中的广义线性模型。
- 支持向量机(SVM):sci kit 中的线性模型——用随机梯度下降(SGD)优化器学习梯度损失。
嵌入- 基础方法:
- FastText :一个 NLP 库,使用高效的基于 CPU 的单词嵌入表示来完成分类任务。
- Flair :一个基于 PyTorch 的框架,用于序列标记和分类等 NLP 任务。
在 Python 中,每种方法都是以一种面向对象的 方式实现的,以确保我们可以轻松地替换实验模型,并在未来用更好、更强大的分类器扩展框架。
为什么要细粒度的情感?
在今天的大多数情况下,情感分类器被用于二元分类(仅仅是正面或负面的情感),并且有充分的理由:细粒度的情感分类是一个 显著的 更具挑战性的 任务!细粒度情感的典型分解使用五个离散类,如下所示。正如人们可能想象的那样,由于人类语言的微妙之处,模型很容易在情绪强度的强弱方面出错。

Typical class labels (or intensities) for fine-grained sentiment classification
二元类别标签可能足以研究文本数据(如推文、产品评论或客户反馈)中的大规模积极/消极情绪趋势,但它们确实有其局限性。当使用比较表达式执行信息提取时,例如:“这款 一加 X 型比三星 X 型好得多。”——精细分析可以为优先解决客户投诉的自动化系统提供更精确的结果。此外,像“这样的双重性句子的位置实在令人厌恶...但是那里的人民是光荣的。“会混淆二元情感分类器,导致不正确的分类预测。
以上几点为解决这个问题提供了足够的动力!
斯坦福情感树库
斯坦福情感树库 (SST-5,或 SST-fine-grained)数据集是测试我们应用程序的合适基准,因为它旨在帮助评估模型理解句子结构的表达的能力,而不仅仅是孤立地查看单个单词。SST-5 由从带有细粒度情感标签的电影评论中提取的 11,855 个句子组成[1-5],以及组成数据集中每个句子的 215,154 个短语。
带有基于短语的细粒度情感标签的原始数据采用树结构的形式,旨在帮助从他们 2015 年的论文中训练一个 递归神经张量网络 (RNTN)。组成短语是通过使用斯坦福解析器解析每个句子(论文的第 3 节)并创建如下图所示的递归树结构来构建的。然后,在每个句子的树形结构上训练深度神经网络,以对每个短语的情感进行分类,从而获得整个句子的累积情感。

Example of Recursive Neural Tensor Network classifying fine-grained sentiment (Source: Original paper)
最先进的是什么?
最初在斯坦福论文【Socher et al .】中实现的 RNTN,在整句情感分类上获得了 45.7% 的准确率。最近,一个增强了 ELMo 嵌入的双注意分类网络(BCN)已经被用于在 SST-5 数据集上实现了 54.7% 的显著更高精度。SST-5 数据集上当前(截至 2019 年)最先进的准确度是 64.4% ,通过一种使用句子级嵌入的方法最初设计用于解决转述任务——它最终在细粒度情感分析上也表现得令人惊讶。
尽管神经语言模型自 2018 年以来变得越来越强大,但可能需要更大的深度学习模型(具有更多参数)和基于知识的方法(如图表)来实现足够的语义上下文,以在细粒度情感分析中达到 70-80%的准确率。
将数据集转换为表格形式
为了评估我们的 NLP 方法以及它们之间的区别,我们将只使用训练数据集中的 完整样本 (忽略组成短语,因为我们没有使用像斯坦福论文那样的递归的基于树的分类器)。使用 pytreebank 库将短语的树形结构转换为原始文本及其相关的类标签。这个树到表格转换的代码在这个项目的 GitHub repo 中提供。
Convert SST-5 tree data to tabular form that we can more easily work with
完整的句子文本及其类别标签(对于train、dev和test集合)被写入单独的文本文件,在句子和类别标签之间使用制表符分隔符。
探索性数据分析
然后,我们可以使用 Pandas 更详细地研究表格数据集。首先,以数据帧的形式读入训练集,同时指定制表符分隔符以区分类标签和文本。注意“truth”列中的类标签被转换为 Pandas 中的数据类型category,而不是将其保留为字符串。
import pandas as pd# Read train data
df = pd.read_csv('../data/sst/sst_train.txt', sep='\t', header=None, names=['truth', 'text'])df['truth'] = df['truth'].str.replace('__label__', '')
df['truth'] = df['truth'].astype(int).astype('category')
df.head()

Sample of SST-5 training data
使用命令df.shape[0]告诉我们我们有 8544 个训练样本。
数据集平衡吗?
在分析情感分类数据集之前,需要注意的一个重要方面是训练数据中的类别分布。
import matplotlib.pyplot as pltax = df[‘truth’].value_counts(sort=False).plot(kind=’barh’)
ax.set_xlabel(“Number of Samples in training Set”)
ax.set_ylabel(“Label”)

很明显,大多数训练样本属于类别 2 和类别 4(弱负/正类别)。相当数量的样本属于中性类。只有 12%的样本来自强负 1 类,这是我们评估分类器准确性时要记住的一点。
测试集呢?快速浏览一下,我们有 2,210 个测试样本,其分布与训练数据非常相似,同样,与其他类别相比,属于强阴性/阳性类别(1 或 5)的样本要少得多。这是可取的,因为我们的分类器进行预测的测试集分布与训练集的分布没有太大的不同。

原论文中提到的一个有趣的点是,很多真正的短文本例子都属于中性类(即 3 类)。这在熊猫身上很容易想象。我们可以创建一个新列来存储每个文本样本的字符串长度,然后按照文本长度的升序对 DataFrame 行进行排序。
df['len'] = df['text'].str.len() # Store string length of each sampledf = df.sort_values(['len'], ascending=True)
df.head(20)

Class labels for the really short examples in the test set
具有明显极性单词的样本,例如“good”和“loved”,将为情感分类器提供更大的上下文,然而,对于中性发音的单词(例如“Hopkins”或“Brimful”),分类器将不得不不仅与极小的上下文,即单个单词样本一起工作,而且还能够处理未出现在训练词汇中的模糊或看不见的单词。
数据标签并不完美!
正如论文中提到的,SST 数据集是由人类注释者通过 Amazon Mechanical Turk 标记的。注释者被展示随机选择的短语,他们从一个连续的滑动条中选择标签。基于多个标注者选择的标签的平均值,重构属于五个类别之一的离散情感标签。在标注过程中使用了随机抽样,以确保标注不受前面短语的影响。

Labelling interface for SST dataset (source: Original Paper)
上面的例子清楚地说明了为什么这是一个如此具有挑战性的数据集来进行情感预测。例如,注释者倾向于将短语“书呆子”归类为有点负面的,因为“书呆子”这个词在我们社会当前对书呆子的看法中有一些负面的含义。然而,从纯语言学的角度来看,这个样本也可以被归类为中性的。
因此,记住文本分类标签总是受人类感知和偏见的影响是很重要的。在现实世界的应用中,在主观的基础上看待某些边缘情况绝对有意义。没有一个基准数据集——推而广之,分类模型——是完美的。
记住这几点,我们可以继续设计我们的情感分类框架!
方法学
模型训练和评估的一般工作流程如下所示。

Sentiment classification: Training & Evaluation pipeline
模型训练:每个分类器(除了基于规则的分类器)使用监督学习算法在来自 SST-5 训练集的 8,544 个样本上进行训练。项目的 GitHub repo 中提供了单独的培训脚本。
预测:按照我们面向对象的设计理念,我们避免在不同的分类方法中重复执行相同任务的代码块。Python 中定义了一个Base类,包含常用的方法:一个用于将 SST-5 数据读入 Pandas DataFrame ( read_data),另一个用于计算模型的分类精度和 F1 值(accuracy)。以这种方式将数据集存储在 Pandas DataFrame 中,可以非常方便地应用自定义转换和用户定义的函数,同时避免过度使用 for 循环。
Base utilities class for all classifiers
接下来,添加到我们框架中的每个单独的分类器必须继承上面定义的Base类。为了使框架一致,每个新的情感分类器都包含了一个score方法和一个predict方法,如下所示。score方法为文本样本输出一个唯一的情感类,而predict方法将 score 方法应用于测试数据集中的每个样本,以在测试数据帧中输出一个新列'pred'。然后通过使用在Base类中定义的accuracy方法来计算模型的准确性和 F1 分数是很简单的。
Example sentiment predictor class
评估:为了评估模型的准确性,使用 scikit-learn 和 matplotlib(GitHub 上的 plotter.py )绘制模型的 混淆矩阵 。混淆矩阵将每个类别的正确预测数与错误预测数进行列表,这样就可以更容易地看出对于给定的分类器,哪些类别的预测最不准确。请注意,我们的 5 类情况的混淆矩阵是一个标准化的反对角线矩阵——理想情况下,分类器的预测几乎 100%正确,因此反对角线之外的所有元素尽可能接近零。

Idealized confusion matrix (normalized) — termed an “anti-diagonal matrix”
培训和模型评估
在这一部分,我们将讨论每种方法的培训、情感评分和模型评估的一些要点。
1 —文本块
TextBlob 是一个用于处理文本数据的流行 Python 库。它构建在 NLTK 、之上,后者是 Python 的另一个流行的自然语言处理工具箱。TextBlob 使用情感词典(由预定义的单词组成)来为每个单词分配分数,然后使用加权平均值进行平均,以给出句子的总体情感分数。对每个单词计算“极性”、“主观性”和“强度”三个分值。
# A sentiment lexicon can be used to discern objective facts from subjective opinions in text.
# Each word in the lexicon has scores for:
# 1) polarity: negative vs. positive (-1.0 => +1.0)
# 2) subjectivity: objective vs. subjective (+0.0 => +1.0)
# 3) intensity: modifies next word? (x0.5 => x2.0)
一些直观的规则被硬编码在 TextBlob 中,以检测增加或减少句子整体极性得分的修饰语(如英语中的副词:“ very good ”)。在这篇博文中有关于这些规则的更详细的描述。
情感评分:要将 TextBlob(一个范围[-1,1]内的连续值浮点数)返回的极性评分转换为细粒度的类标签(一个整数),我们可以利用宁滨。在 Pandas 中使用pd.cut函数很容易做到这一点——它允许我们通过在结果中所有 TextBlob 分数的浮动区间中使用相同大小的 bin,从连续变量变为分类变量。
评估:由于我们在训练和测试期间都在处理不平衡的类,所以我们关注宏 F1 分数(它是宏平均精度和召回率的调和平均值)以及分类准确度。可以看出,TextBlob 分类方法的准确度非常低,F1 分数也是如此。

混淆矩阵图显示了分类器预测最不正确的类别的更多详细信息。

Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
要阅读上述混淆矩阵图,请查看沿反对角线的单元。单元格[1,1]显示分类器正确预测的属于类别 1 的样本的百分比,单元格[2,2]显示正确的类别 2 预测的百分比,依此类推。远离反对角线的单元格显示了对每个相应类别做出的错误预测的百分比-例如,查看单元格[4,5],我们可以看到实际属于类别 5 的所有样本中有 47%被 TextBlob(不正确地)预测为类别 4。
很明显,我们的 TextBlob 分类器预测大多数样本为中性或轻度阳性,即类别 3 或 4,这解释了为什么模型精度如此低。很少有预测是强烈负面或正面的——这是有意义的,因为 TextBlob 对每个样本中的所有单词使用加权平均情感得分。这可以很容易地分散出单词之间极性差异很大的句子的效果,例如“这部电影是关于撒谎、欺骗,但爱你背叛的朋友
2 — VADER
“ValenceAwareDictionary and sEentimentReasoner”是另一个流行的基于规则的情感分析库。像 TextBlob 一样,它使用一个情感词典,其中包含基于人工标注标签的每个单词的强度测量。然而,一个关键的区别是,VADER 的设计侧重于社交媒体文本。这意味着它非常重视捕捉社交媒体上常见文本本质的规则——例如,带有表情符号的短句、重复的词汇和大量使用标点符号(如感叹号)。以下是 VADER 输出的情感强度分数的一些例子。

在上面的文本示例中,对同一个句子做了微小的改动。请注意,VADER 将情绪强度分数分解为积极、消极和中性成分,然后将其标准化并压缩到[-1,1]范围内作为“复合”分数。随着我们添加更多的感叹号、大写字母和表情符号,强度变得越来越极端(朝着+/- 1)。
情感评分:为了返回 SST-5 数据集上的离散类值,我们应用了与 TextBlob 类似的技术——通过 pandas pd.cut函数使用宁滨将连续的“复合”极性得分(float)转换为离散值。这将为每个测试样本返回五个类中的一个,作为新列存储在结果数据帧中。
评估:上面使用的宁滨方法是将来自 VADER 的连续(浮点)值平均分成我们需要的五个离散类之一的一种相当粗糙的方法。然而,与 TextBlob 相比,我们确实看到了总体分类准确性和宏 F1 分数的提高。

VADER 的混淆矩阵显示了更多正确预测的类(沿着反对角线)-然而,关于对角线的不正确预测的传播也更大,给了我们一个更加“混乱”的模型。

Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
VADER 的更大传播(在反对角线之外)可以归因于这样一个事实,即它只给具有大写、lot的文本分配非常低或非常高的复合分数。由于 SST-5 实际上没有这样的注释文本(它与社交媒体文本有很大不同),因此该数据集的大多数 VADER 预测都在-0.5 到+0.5 的范围内(原始分数)。当转换为离散类标注时,这将导致更窄的分布,因此,许多预测可能会在真实标注的任一侧出错。
虽然 VADER 的结果准确度仍然很低,但很明显,与 TextBlob 相比,它基于规则的方法确实捕捉到了情绪的大量精细分级——很少真正负面的情况被归类为正面,反之亦然。
3 — 逻辑回归
从基于规则的方法向前移动,下一个尝试的方法是逻辑回归,这是最常用的分类监督学习算法之一。逻辑回归是一种基于标记数据的线性模型——术语线性很重要,因为它意味着算法仅使用输入和参数的线性组合(即和而非积)来产生类别预测。
Sebastian Raschka 在他的博客文章中对逻辑回归如何等同于一个非常简单的单层神经网络给出了一个非常简洁的解释。输入特征及其权重被输入到激活函数(用于二分类的 sigmoid,或用于多分类的 softmax)。分类器的输出只是 sigmoid/softmax 向量的索引,其中最高值作为类标签。

Source: Sebastian Raschka’s blog
对于多类逻辑回归,通常使用一对其余方法——在该方法中,我们训练 C 单独的二元分类模型,其中 C 是类的数量。每个分类器 f_c ,for c ∈ {1,…, C }被训练来预测样本是否属于类别 c 。
将单词转换为特征:要将文本转换为特征,第一步是使用 scikit-learn 的[CountVectorizer](https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html).这将我们训练数据的整个语料库(即所有句子)转换为令牌计数矩阵。令牌(单词、标点符号等。)是使用 NLTK 的标记器创建的,常用的停用词如“a”、“an”、“the”被删除,因为它们不会给情感评分增加太多价值。接下来,计数矩阵被转换成 TF-IDF(术语频率逆文档频率)表示。来自 scikit-learn 文档:
Tf 表示术语频率,而 tf-idf 表示术语频率乘以逆文档频率。这是信息检索中常见的术语加权方案,在文档分类中也有很好的用途。使用 tf-idf 而不是给定文档中记号出现的原始频率的目的是按比例缩小记号的影响,这些记号在给定语料库中非常频繁地出现,因此在经验上比在一小部分训练语料库中出现的特征信息少。
情感评分:一旦我们获得了训练语料库的 TF-IDF 表示,就通过使其适合现有特征来训练分类器。“newton-cg”解算器用于优化逻辑回归中的损失,默认情况下使用 L2 正则化。为每个测试样本返回一个情感标签(使用 scikit-learn 的learner.predict方法),作为 softmax 输出向量中最大类别概率的索引。
评估:从基于规则的方法切换到基于特征的方法,总体分类精度和 F1 值都有显著提高,如下图所示。

然而,混淆矩阵显示了为什么在多类问题中查看整体准确性度量不是很有用。

Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
逻辑回归模型将大部分真实标签 1 和 5(强阴性/阳性)归类为属于它们的邻居类(2 和 4)。此外,几乎没有任何例子被正确归类为中性(第 3 类)。因为大多数训练样本属于类别 2 和 4,看起来逻辑分类器主要学习了* 在这些主要类别中出现的特征。*
4 —支持向量机
支持向量机(SVM)在如何优化损失函数以生成数据点之间的决策边界方面非常类似于逻辑回归。然而,主要的区别是“核函数”的使用,即,将复杂的非线性决策空间转换为具有更高维度的决策空间的函数,以便可以找到分离数据点的适当超平面。SVM 分类器使用将每个距离表征为向量的“支持向量”,来最大化每个数据点与该超平面的距离。
支持向量机的一个关键特征是它使用了一个铰链损失,而不是逻辑损失。这使得它对数据中的异常值更加稳健,因为铰链损失不会像逻辑损失那样迅速发散。
训练和情感评分:sci kit-learn 中的线性 SVM 是使用与前面描述的逻辑回归类似的管道建立的。一旦我们获得了训练语料库的 TF-IDF 表示,我们就通过使其适合训练数据特征来训练 SVM 模型。使用具有随机梯度下降(SGD)优化器的铰链损失函数,并且在训练期间应用 L2 正则化。情感标签作为 softmax 输出向量中最大类别概率的索引返回(使用 scikit-learn 的learner.predict方法)。
评估:由于相当多的要素可能是现实数据集中的异常值,因此 SVM 在实践中产生的结果应该略好于逻辑回归。看看准确性和 F1 分数的提高,这似乎是真的。

与逻辑回归相比,优化器的选择与 SVM 模拟更复杂超平面的能力相结合,将样本分成各自的类别,从而略微改善了混淆矩阵。

Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.

Side by side: Logistic Regression vs. SVM
SVM 模型预测强负/正类(1 和 5)比逻辑回归更准确。然而,它仍然不能预测足够多的样本属于类别 3——大部分 SVM 预测再次偏向主要的类别 2 和 4。这告诉我们,特性定义的方式还有改进的余地。与 TF-IDF 转换相结合的计数矢量器实际上并不了解任何关于单词如何相互关联的信息* —它们只是查看每个样本中单词共现的数量来做出结论。输入单词嵌入。*
5 —快速文本
FastText 是一个高效、可扩展、基于 CPU 的文本表示和分类库,由脸书人工智能研究(FAIR)团队于 2016 年发布。FastText 的一个关键特征是其底层神经网络学习表示,或嵌入,即考虑单词之间的相似性。虽然 Word2Vec (一种更早于 2013 年发布的单词嵌入技术)做了类似的事情,但 FastText 有一些突出的关键点。
- FastText 使用 n-gram 的集合来考虑子词:例如,“train”被分解为“tra”、“rai”和“ain”。以这种方式,单词的表示更能抵抗拼写错误和微小的拼写变化。
- FastText 可以更好地处理未知单词,因为它能够将长单词分解成子单词,这些子单词也可能出现在其他长单词中,从而提供更好的上下文。
Python 模块:虽然 FastText 的源代码是 C++,但是 2019 年 6 月 FAIR 还是发布了一个官方的 Python 模块(在社区内混乱了几个月后)。这使得完全在 Python 中训练和测试我们的模型非常方便,不需要使用任何外部二进制文件。但是,为了找到最佳的超参数,建议使用 FastText 的命令行界面。
训练 FastText 模型:要训练 FastText 模型,请使用[fasttext](https://fasttext.cc/docs/en/support.html#building-fasttext-as-a-command-line-tool) 命令行界面(仅限 Unix 这包含一个非常有用的超参数自动调整实用程序。根据文档,该实用程序针对最大 F1 分数优化所有超参数,因此我们不需要手动搜索特定数据集的最佳超参数。这是在终端上使用以下命令运行的,在 CPU 上大约需要 5 分钟。
Command to find optimum hyperparameters for FastText using the command line interface
上面的命令告诉 FastText 在训练集上训练模型,并在开发集上验证,同时优化超参数以实现最大 F1 分数。标志-autotune-modelsize 10M告诉 FastText 优化模型的量化参数(如下所述),使得最终训练的模型大小小于 10 MB,并且启用-verbose选项,以查看哪个超参数组合给出最佳结果。
💡提示:量化 FastText 模型: 量化通过使用 16 位或 8 位整数,而不是标准的 32 位浮点,减少了存储模型权重所需的位数。这样做极大地减小了模型的大小(几个数量级)。 FastText 使量化变得非常方便在其最新发布的命令行接口或其 Python 模块如下(量化模型的扩展是**.ftz**,不是.bin作为父模型)。根据超参数优化期间获得的值设置截止选项,这确保了最终模型大小保持在 10 MB 以下。
*# Quantize model to reduce space usage model.quantize(input=train, qnorm=True, retrain=True, cutoff=110539) model.save_model(os.path.join(model_path, "sst5.ftz"))*
下面的代码片段显示了如何使用最佳超参数在 Python 中训练模型(这一步是可选的,如果愿意,只能使用命令行训练工具)。
关于每个超级参数的含义以及 FastText 如何在幕后工作的更多细节,这篇文章给出了一个很好的描述。
情感评分:通过加载经过训练、量化的(.ftz ) FastText 模型来进行情感预测。该模型有一个predict方法,根据从 softmax 输出层提取的概率输出最可能的标注。为了进行分类预测,我们简单地从这个概率列表中选择最可能的分类标签,直接将其提取为整数。
评估:可以看出,对于该数据集,FastText 模型的准确性和 F1 分数在 SVM 上没有显著提高。

然而,FastText 的 F1 分数略高于 SVM。

Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
两个模型并列的混淆矩阵更详细地强调了这一点。

Side by side: SVM vs. FastText
快速文本和 SVM 结果之间的关键区别是中性类的正确预测百分比,3。在大多数类别(2 和 4)中,SVM 比 FastText 正确地预测了更多的项目,这突出了基于特征的方法在不平衡类别的文本分类问题中的弱点。FastText 使用的单词嵌入和子单词表示法固有地给了它额外的上下文。在对未登录词进行分类时尤其如此,这在中性类中相当常见(尤其是只有一两个词的非常短的样本,大多是看不见的)。
然而,我们的 FastText 模型是使用单词三元模型训练的,因此对于中途改变极性的较长句子,该模型必然会“忘记”之前几个单词的上下文。RNN 或 LSTM 等序列模型将能够更好地捕捉更长期的背景,并对这种过渡情绪进行建模。
6 —天赋
2018 年, Zalando Research 发表了一个最先进的深度学习序列标记 NLP 库,名为 Flair 。这也很快成为分类任务的流行框架,因为它允许将不同种类的单词嵌入在一起,从而为模型提供更好的上下文感知。
Flair 的核心是一个名为字符串嵌入的上下文化表示。为了获得它们,来自大型语料库的句子被分解成字符序列,以预训练双向语言模型,该模型在字符级“学习”嵌入。通过这种方式,该模型可以学习区分大小写的字符(例如,发音相似的普通名词中的专有名词)和自然语言中的其他句法模式,这使得它对于命名实体识别和词性标注等任务非常强大。

Illustration of a BiLSTM sequence labeller with contextual character embeddings (Source)
训练用于分类的 Flair 模型:使 Flair 极其方便而强大的是它能够用“Flair”(即字符串)嵌入来“堆叠”单词嵌入(如 ELMo 或 BERT)。以下示例显示了如何使用 Flair 嵌入实例化 BERT(基本,有大小写)或 ELMo(原始)嵌入的堆栈嵌入。堆叠表示被转换成嵌入的文档,即对整个文本样本进行单次嵌入(不管有多少个句子)。这允许我们将复杂的任意长度表示压缩为固定大小的张量表示,我们可以在 GPU 内存中进行训练。**
以这种方式堆叠嵌入(BERT 或 ELMo)的能力来自于这样一个事实,即字符级字符串嵌入捕获潜在的语法语义信息 ,而不使用单词的概念(它们明确地专注于子单词表示),而来自外部预训练神经网络模型的堆叠单词嵌入给出了附加的单词级上下文。这增强了模型在给定文本中识别各种句法特征的能力,使其能够超越经典单词嵌入模型的性能。
关于训练的注意事项:Flair 模型需要一个 GPU 来进行训练,并且由于其 LSTM 架构与 transformer 架构相比不能高效地并行化,因此即使在这个相对较小的 SST-5 数据集上的训练时间也在几个小时的数量级*。对于这个项目,运行了 25 个时期的训练,当训练停止时,验证损失仍在减少,这意味着模型相当不适合。因此,在现实世界的大型数据集上使用 Flair 进行分类任务可能会带来巨大的成本损失。*
情感评分:和以前一样,评分技术是在 Pandas 现有的框架下实现的。首先加载训练好的模型,然后将文本转换成一个Sentence对象(这是样本中每个句子的标记化表示)。调用 Flair 模型的predict方法,使用 softmax 输出图层中的最大索引来预测类别标注,然后将最大索引提取为整数并按样本存储在 Pandas 数据帧中。因为即使在 GPU 上,模型推断也需要相当长的时间,所以实现了一个[tqdm](https://github.com/tqdm/tqdm/blob/master/examples/pandas_progress_apply.py) 进度条来显示模型完成了多少测试样本的预测。
评估:使用两个独立的堆叠表示来训练两个独立的模型——一个使用 BERT(基本、有壳),另一个使用 ELMo(原始)。使用每个模型进行推理,得出以下结果。

与快速文本和 SVM 模型相比,在准确性和 F1 分数方面都有相当大的提高!查看每种情况下的混淆矩阵,可以洞察哪些类别比其他类别预测得更好。
**
上面的图强调了为什么使用 BERT 嵌入的堆叠比使用 ELMo 嵌入的堆叠得分低得多。BERT 案例几乎没有对第 1 类做出正确的预测——但是它对第 4 类做出了更多正确的预测。ELMo 模型似乎与 Flair 嵌入堆叠得更好,并且为少数类(1 和 5)生成更大部分的正确预测。
Flair + BERT 模式在训练中哪里出了问题?可能是重新投影和减少隐藏维度的数量(在堆叠期间)导致预训练 BERT 模型的知识丢失,这解释了为什么该模型在强负样本上学习得不够好。与使用 BERT 进行堆叠相比,堆叠 ELMo 嵌入为什么会导致更好的学习,这一点并不十分清楚。然而,在这两种情况下,Flair 模型花费了大量时间(几个小时)来训练,这在现实世界中可能是一个巨大的瓶颈——然而,它们确实突出了使用上下文嵌入而不是经典单词嵌入来进行细粒度分类的能力。
结论
在这篇文章中,Python 中的六个不同的 NLP 分类器被用来在 SST-5 细粒度情感数据集上进行分类预测。通过逐步使用越来越复杂的模型,我们能够将 F1 的准确度和宏观平均分数提高到 48%左右,这还不算太差!在未来的帖子中,我们将看到如何使用迁移学习驱动的 transformer 模型来进一步提高这些分数。

Comparison of results: Fine-grained sentiment classification on SST-5
我们还能学到什么?
绘制归一化混淆矩阵给出了一些有用的见解,即为什么基于嵌入的方法的精度高于更简单的基于特征的方法,如逻辑回归和 SVM。很明显,在类不平衡的多类问题中,整体准确性是一个非常差的指标,比如这个问题,这就是为什么需要宏观 F1 分数来真正衡量哪些分类器性能更好。
机器学习模型(尤其是深度学习模型)的一个关键方面是,众所周知,它们很难解释。为了解决这个问题,我们将看一下 解释 我们的结果并回答这个问题:“为什么 X 分类器预测这个特定样本的这个特定类别?”。LIME Python 库用于这个任务,这将在下一篇文章中描述。
如果你一直读到这篇文章的结尾,感谢你的阅读!
- 这是 Python 中细粒度情感分析系列的第 1 部分。
- 第 2 部分 讲述了如何使用 LIME 构建一个解释器模块,并解释两个代表性测试样本上的类预测。
- 第三部分 讲述了如何通过建立我们自己的 transformer 模型,使用迁移学习,进一步提高准确率和 F1 分数。
注意:此分析的所有培训和评估代码都可以在项目的 Github repo 中获得,所以请随意复制结果并做出您自己的发现!
Python 中的细粒度情感分析(第 2 部分)
在本帖中,我们将使用 LIME 为各种细粒度情感分类结果生成解释

Source: Pixabay
“我为什么要相信你?”— 里贝罗等人
这是 Python 中细粒度情感分析系列的第 2 部分。第 1 部分讲述了如何用 Python 训练和评估各种细粒度的情感分类器。在这篇文章中,我们将讨论为什么一个分类器做出了一个特定的类别预测——也就是说,如何使用一种叫做 LIME 的流行方法来解释一个情感分类器的结果。
概括地说,以下六个模型用于在斯坦福情感树库(SST-5)数据集上进行细粒度情感类别预测。
- 基于规则的模型:文本块和 VADER
- 基于特征的模型:逻辑回归和支持向量机
- 基于嵌入的模型: FastText 和 Flair
使用线性过程来分析和解释使用每种方法的情感分类结果。

Sentiment classification: Training & Evaluation pipeline
以下部分解释了该过程的最后一步——为每种方法的预测生成解释。
局部可解释的模型不可知的解释
或者简单的说,石灰、、是以可解读的方式解释一个、分类器的预测的一种技术原文。这里的术语“解释”是指生成文本或视觉辅助工具,如突出显示的图和图表,它们提供了对模型特征及其预测之间关系的定性理解。根据论文,以下要点使得 LIME 在解释复杂模型的分类结果时非常有效:
它是模型不可知的——它将原始模型视为黑盒,因此它可以应用于几乎任何分类器
它是局部忠实的 — 它对应于模型在被预测的实例附近的行为。因此,对于测试样本中给定的一组特征,解释对于这些特征所占据的决策空间是有意义的,这可能适用,也可能不适用于全局。
模型解释过程
为了给任何分类器提供解释,LIME 使用了一种非常聪明的方法-它从更大的黑盒分类器中唯一需要的是它对每个类的预测概率(在应用 softmax 函数后从模型的输出层)。下面的步骤简单地解释了这个过程。
- 生成文本样本的数千种变体,其中随机单词被删除,如下所示:

- 使用原始的、复杂的黑盒模型来为每个单独的变异生成类别概率和标签(其单词被部分删除)。例如,案例“It _ not terrible _ _ _ _”将具有类别概率
[0, 0, 0.6, 0.3, 0.1](即,它将属于类别 3),而“It _ terrible _ _ _ _”将具有概率[0.9, 0.1, 0, 0, 0]并属于类别 1。 - 针对每个变化,在由黑盒模型预测的标签上训练更小、更简单的线性模型。
- 观察简单线性模型中最重要的特征(这更容易解释)
- 最后,基于特征重要性生成单词的每个单词(或标记)的可视化
解释如何作用于多个类?
对于多类文本分类,如 SST-5 数据集,LIME 使用预测概率,通过 one-vs-rest 方法突出每个特征(即标记)对预测类的影响。来自 LIME 的多类解释示例如下所示。模型预测这个句子的类别为 2。正在解释的文本中突出显示的颜色是随机生成的,颜色越深表示该标记对于预测类别的特征重要性越大(在下面的示例中为“沉闷”)。

Prediction class probabilities and feature importance plots generated by LIME
解释器类
正如上一篇文章一样,面向对象的方法被应用于尽可能重用代码。这个[项目的 GitHub repo explainer.py ]中提供了解释器的所有代码。定义了一个 Python 类,该类接受由 LIME 生成的变量列表(标记空白的随机文本样本),并将每个样本的类概率输出为一个 numpy 数组。
一旦每个变化的类概率被返回,这可以被馈送到LimeTextExplainer类。启用词包(bow)意味着 LIME 在生成变体时不考虑词序。然而,FastText 和 Flair 模型分别考虑 n 元语法和上下文排序进行训练,因此为了模型之间的公平比较,SST-5 上的所有解释都禁用了bow标志选项。
LIME explainer 返回的exp对象是一个内部方法,将局部线性模型的预测(以数值形式)转换为人类可以理解的可视化形式,输出为 HTML 文件。
破解基于规则的方法
由于基于规则的方法(文本块和 VADER)不输出类别概率(它们只输出单个情感分数),为了使用 LIME 解释它们的结果,我们必须为这些方法人工生成类别概率。虽然这不是一个正式的过程,但使用连续值情感分数(在范围[-1, +1]内)模拟类概率的一个简单的变通方法是将浮动分数归一化到范围[0, 1]内,然后通过将其大小缩放 5 倍将其转换为离散整数类。对文本块和 VADER 的操作如下。
此外,基于规则的模型输出一个且仅一个预测,因此为了避免输出其他类的零概率,由 5 个点组成的正态分布(其平均值作为预测的整数类)用于将小的非零概率分配给剩余的类。下图显示了这是如何做到的。这个模拟概率数组现在可以被 LIME 用来生成基于规则的模型的解释。

Example of simulated probabilities for rule-based classifier scores
每个分类器关注什么?
为了理解预测,为六个训练好的分类器中的每一个运行文件explainer . py——这输出了一个带有可视内容的 HTML 文件,帮助我们解释模型的特征理解。
从上一篇文章中完成的 EDA,我们知道类 1 和 3 是 SST-5 数据集中的少数类,所以从这两个类中选择两个样本。
不可怕,只是平庸得可怕。 (真实标签 )
全体演员都很优秀……但这部电影本身只是稍微有点魅力。 (真题 3)
这些样本中的每一个都包含修饰语、相互冲突的词汇和句子中快速变化的极性,所以原则上,它们应该有助于揭示每个分类器关注的是什么。
文本块
例一:不是恐怖,只是平庸得可怕。**

True: 1 — Predicted: 3
对于句子“不可怕,只是平庸得可怕”,在单词“可怕”之前使用否定项使 TextBlob 相信该项目并不可怕——然而,句子“只是平庸得可怕”的第二分句重申了该项目确实非常平庸的事实。这个句子作为一个整体仍然是压倒性的负面,使真正的标签为 1,但 TextBlob 重点关注否定术语,以推动情绪评级上升到 3。
例 2:演员阵容都很出色……但这部电影本身只是有点迷人。”

True: 3 — Predicted: 5
上面这句话真的很有挑战性,因为前半句是压倒性的正面,后半句是相当负面的——使得整个句子在情绪上是中性的。单词“轻度”、“迷人的”和“优秀的”在很大程度上导致 TextBlob 错误地将此句子分类为强阳性(标签 5)。一般来说,强阳性词的多次出现倾向于推动 TextBlob 中基于规则的算法将句子整体分类为阳性。
VADER

True: 1 — Predicted: 5
在上面的例子中,VADER 预测的情绪等级与预期完全相反。VADER 倾向于重罚负极性词汇(如“可怕的”),但也重奖负极性词汇之前的否定术语(“ 而非 可怕的”)。在句子的后半部分出现“极其平庸的”没有任何影响,因为前半部分强有力的积极得分超过了后面的一切——在这个例子中,VADER 基于规则的方法被证明有点太聪明了。

True: 3 — Predicted: 5
再一次,VADER 基于规则的方法很重视像“优秀的”和“迷人的”这样听起来积极的词,从而错误地给这个句子贴上了标签 5。单词“仅仅是”和“轻度”应该会降低句子的整体得分,却被 VADER 忽略了——这很可能是因为这些单词没有出现在 VADER 的情感词典或基于规则的修饰语中。
逻辑回归

True: 1 — Predicted: 2
逻辑回归似乎确实学习了修改句子整体情感评级的个体特征(标记)——正如上面可以看到的,单词“只是”和“一般”对标签 2 的贡献更大,而“可怕的”和“可怕的”对“不是 2”(大概是标签 1)的贡献更大。标签 1 和 2 的预测概率几乎相等,因此在这种情况下,基于特征的方法对标签 2 的误差非常小。

True: 3 — Predicted: 3
在第二个例子中,很明显,单词“但是“”、“轻微的”和“仅仅是”被逻辑回归模型正确地识别为将整个句子情感摆动到中性状态。
支持向量机

True: 1 — Predicted: 1
与逻辑回归不同,SVM 更侧重于强烈的负面词汇“可怕的”和“可怕的”来给这个词贴上 1 的标签。否定词“ not ”和单词“平庸”被正确地识别为将评级推离 1——但它们的效果很低,总体情绪标签仍然是 1。

True: 3 — Predicted: 3
在第二个例子中,很明显,SVM 学会了使整个句子中性的正确特征。单词“仅仅是”、“轻度”和“但是”降低了句子被分配标签 2、4 和 5 的概率,使得标签 3 最有可能。
快速文本

True: 1 — Predicted: 1
FastText 主要关注示例 1 中的强否定词,其他类别标签的概率为零。它确实得到了正确的整体预测,但这可能只是因为 FastText 了解到“可怕的”是一个经常出现在强烈否定句中的单词。

True: 3 — Predicted: 3
在示例 2 中,FastText 模型正确预测中性标签的原因更加清楚。由于该模型是使用单词三元模型和 5 的上下文窗口训练的(关于训练参数,请参见上一篇文章),该模型在进行预测时会查看标记的序列。例如,单词“一致优秀的”紧接着是“但是”,单词“仅仅是温和的”紧接在“迷人的”之前,因此查看可视化中突出显示的单词,模型知道使该句子中性的单词共现(而不是单个单词)的序列。
Flair + ELMo

True: 1 — Predicted: 2

True: 3 — Predicted: 2
Flair + ELMo 嵌入模型对两个例子的预测都有很大的误差。在上述两种可视化中,模型似乎对周期给予了很高的权重。)标记——其他分类器则不是这样。在任何一种情况下,预测标签的概率都非常接近正确标签,因此该模型在如何从数据中学习方面似乎是正确的。
需要注意的重要一点:在训练阶段,Flair + ELMo 模型不适合,即验证损失甚至在 25 个时期的训练后仍在减少(参见本系列的第 1 部分)——这意味着进一步的训练可以将分类器推向这些和其他示例的正确概率输出。
分析
通过研究各种方法的解释结果,我们可以观察到一些优点和缺点,以及关键变量对每种方法性能的影响。
强极性词的效应
TextBlob 和 VADER 倾向于对极性强的单词进行加权,即使有其他极性较弱的单词(或否定项)会改变整个句子的情绪。硬编码的规则在许多情况下工作得很好,但是现实世界中的自然语言有太多的可变性,这些规则在实践中很难工作得很好,至少对于细粒度的情感分析来说是这样。
基于嵌入的方法对涉及强极性单词的情况有最好的处理。FastText 模型是用三元模型训练的,所以它学会了拾取强极性单词之前或之后的单词序列,所以它不容易被愚弄,与基于规则和基于特征的方法相比,它可以在中性类上做出更好的预测。Flair 模型,由于其上下文嵌入和强大的底层语言模型,能够更准确地识别涉及强极性单词序列的模式。
句子长度的影响
当使用基于规则的方法时,长样本,尤其是多句子样本可能会带来麻烦,这种方法倾向于应用某种加权平均来捕捉整个句子的极性。因此,句子越长,就越有可能分散长样本中单个子句的实际情感。
非常短的(单个或两个单词)样本对模型提出了不同的挑战——它们要么包含看不见的单词,要么提供的上下文太少,如标点符号或模型可以用来对单词进行分类的类似子词。一般来说,基于规则的模型在这些情况下会很糟糕,因为缺少修饰语(“ very ”或“ too ”)不能在我们的细粒度分类场景中提供足够的情感强度概念。
看不见的词的效果
在有大量未见过的单词的样本中,或者在非常短的样本中,一个单词在训练中未见过的可能性非常高,缺乏顺序(n-gram)或上下文表示(嵌入)的模型往往难以做出可靠的细粒度情感预测。scikit-learn 中基于特征的模型非常清楚地展示了这个问题。由于它们依赖于训练过程中的单词共现计数,如果一个看不见的单词出现在测试集中,该单词将被基于特征的分类器忽略。这错过了可能赋予模型更多上下文的特性。
FastText 由于其字符 n-gram 子词表示而更好地处理未见过的词,但它没有 Flair 模型那样深入的预训练表示,以正确处理未见过的词。Flair + ELMo 嵌入模型使用来自预训练词汇表的上下文表示(10 亿字的新闻抓取数据)。这使它比较浅的模型具有显著的优势——上下文化的嵌入携带有用的信息,可以识别意思相似的单词及其子单词表示。
模型成本(训练和/或推理)
所有基于规则的方法都涉及零训练时间,并且在预测阶段非常快,但是这是以不可见的真实世界数据的结果缺乏稳定性为代价的。由于 scikit-learn 中高效的矢量化表示,基于特征的方法也可以非常快速地进行训练和推断。然而,这些模型也有局限性,不能捕捉单词之间的关系,并且对看不见的单词处理不好。
FastText 是计算成本(由于其底层 C++和 Cython 绑定,它非常快)和分类准确性之间的一个很好的折衷。对于细粒度的情感分析,必须使用三元模型表示来训练模型,以便捕捉长句中出现的单词序列中涉及的更精细的层次。
Flair 是所有选项中最昂贵的选项,主要是因为它是一个大型深度学习模型,使用来自字符串和单词嵌入组合的预训练表示。训练该模型可能需要几个小时(或几天,取决于数据集的大小)。Flair + ELMo 模型在训练和推理过程中速度较慢的部分原因是在运行时间期间查找 ELMo 嵌入的方式——这可以使用小批量来加速——然而,现实情况是,Flair 这样的模型在真实世界的数据集上很可能被证明过于昂贵,无法进行快速有效的推理。
用于模型测试的交互式仪表板
为了更容易地看到对许多不同示例的模型范围的解释,使用 Flask 微框架创建了一个仪表板。使用 Heroku ( 以类似于本例的方式部署仪表板。为了交互式地测试仪表板,输入一个文本样本并选择我们想要解释其分类预测的分类器,如下所示。
试试这里的仪表盘,做出你自己的解释!

Try out the dashboard! https://sst5-explainer.herokuapp.com
结论
在这篇文章中,我们讨论了如何使用 Python 中的六个不同的分类器来生成和解释细粒度情感的石灰解释。每个模型所关注的特征的差异是显而易见的,这使得为手头的任务选择正确的分类器变得更加简单。词汇的底层表示越复杂(尤其是上下文嵌入),SST-5 数据集的五个情感类别的模型预测就越可靠。
到目前为止,SST-5 数据集的准确度/F1 分数如下:

虽然基于 Flair + ELMo 嵌入的模型确实达到了 48.9%的相当不错的准确度,但这仍然离最先进的水平(64.4%)非常远。此外,训练和预测 Flair 模型的成本非常昂贵,因此需要一个计算效率更高,但又能感知上下文的模型。
在本系列的 第 3 部分 中,我们将看到如何使用带有迁移学习的 transformer 模型来进一步改进这些结果。感谢阅读!
承认
- 生成 LIME 解释的代码借用了 Adam Geitgey 的这篇精彩文章。如果你想深入了解石灰的工作原理,请读一读吧!
- Python/Flask dashboard 是一个快速而肮脏的实现,它的代码受到了这个优秀的 Flask 教程的启发。
密码
- 所有进行情绪预测和生成石灰解释的代码都在这个项目的 GitHub repo 中。
- 烧瓶前端应用程序的所有代码都可以在这个单独的 repo 中获得。
细粒度情感分析(第 3 部分):微调变压器
PyTorch 中使用预训练变压器的动手迁移学习

Source: Pixabay
这是 Python 中细粒度情感分析系列的第 3 部分。第 1 部分和第 2 部分涵盖了对斯坦福情感树库细粒度(SST-5)数据集上六种不同分类方法的分析和解释。在这篇文章中,我们将看看如何通过建立一个基于 transformer- 的模型和应用迁移学习来改善过去的结果,这是一种最近已经统治 NLP 任务排行榜的强大方法。
从本系列的第一篇文章中,以下分类准确度和 F1 分数是在 SST-5 数据集上获得的:

在下面的章节中,我们将讨论关键的培训、评估和解释步骤,这些步骤说明了为什么 transformers 比上面列出的方法更适合这项任务。
是什么让变形金刚如此强大?
transformer 架构的核心是以下关键思想,这些思想使其非常适合解释自然语言中的复杂模式:
- 自我关注:这是一种机制,变形金刚用它来表示输入序列的不同位置,并学习它们之间的关系。
- 多头注意:变形金刚将自我注意的任务委托给多个“头”,即从不同的表征子空间共同注意来自序列中不同位置的信息。这允许他们使用无监督学习有效地扩展(大型数据集)。
- 迁移学习:最近领先的基于 transformer 的方法是通过迁移学习实现的——即使用从之前的设置(例如无监督训练)中提取的知识,并将其应用于不同的设置(例如情感分类或问题回答)。这是通过两个阶段的过程实现的:预调整,随后是微调,或适应。

Source: “The State of Transfer Learning in NLP” by Sebastian Ruder
本质上,最近所有基于 transformer 的顶级方法( GPT 、伯特、 XLNet )都使用这种顺序迁移学习方法。在内部,他们在预训练阶段使用来自大型语料库的未标记数据训练一个语言模型,随后一个额外的特定任务模块(附在语言模型的下游)根据定制数据进行微调。下面是这种方法在多个基准数据集上的可视化。

Source: Devlin et al. 2019
预培训目标的重要性
预训练步骤包括以无监督的方式训练语言模型——这决定了模型如何从给定的训练语料库中学习语法、语义和上下文信息。最近的证据(来自 OpenAI 的 GPT-2 等模型)表明,如果有足够的数据和计算,真正大型的语言模型将会学到很多关于语言语法的知识!对于本文后面描述的 transformer 模型,下面的预处理目标值得注意。
- OpenAI GPT 在预训练过程中使用了一个从左到右语言建模目标——也就是说,它学习从左到右预测序列中最有可能的下一个单词,就像它们在自然语言中出现一样(至少对于英语来说)。这种模型通常被称为经典语言模型,或者 因果 语言模型。这里的“因果”一词指的是这样一个事实:一个标记出现在特定位置的可能性是由出现在它之前的标记序列引起的。
- 伯特在其核心使用一个屏蔽语言模型,通过在预训练期间随机屏蔽一个序列的 15%的标记获得——这允许模型学习如何预测在前一个标记之前或之后出现的标记(它被双向训练,不像 GPT)。除了屏蔽之外,BERT 还使用了下一句预测目标,该模型学习预测一句话是否在前一句话之后出现。与因果语言建模相比,这种方法需要较长的训练时间,因为屏蔽一部分标记会导致较小的训练信号。
- XLNet 使用置换语言建模目标——与 BERT 不同,它在预训练期间随机屏蔽序列中的每个标记(不仅仅是 15%)。通过这种方式,模型学习预测两个方向上序列的随机记号,允许模型学习记号之间的依赖性(不仅仅是在给定序列中哪些记号最有可能)。可以想象,这在训练时间方面更加昂贵,并且需要更大的语言模型来获得良好的基线。
在这篇文章的其余部分,我们将使用一个使用因果语言建模目标(类似于 GPT/GPT-2,但比它小得多)训练的变压器模型。
制造变压器
有了这些背景知识,我们可以继续写一些代码了!
读取数据集
使用 Pandas 对 SST-5 数据集进行了一些基本的预处理。注意,类标签递减 1(在范围[0,1,2,3,4]内),因为 PyTorch 期望标签是零索引的。
对数据进行令牌化和编码
处理数据的第一步是使用单词块标记化器进行标记化— [ 参见本文第 4.1 节了解更多详情]。我们使用 HuggingFace 的 [pytorch_transformers](https://huggingface.co/pytorch-transformers/model_doc/bert.html?highlight=berttokenize#pytorch_transformers.BertTokenizer) 库中实现的BertTokenizer 。接下来,标记化的文本被编码成整数序列,由我们的 transformer 模型处理。随后,创建一个 PyTorch [DataLoader](https://pytorch.org/docs/stable/data.html#torch.utils.data.DataLoader)将样本装载到批次中进行训练。
请注意,在本例中,我们将序列的最大长度固定为 256——理论上,BERT 和类似模型编码器可以处理的最大序列长度为 512,但由于 SST-5 是一个样本相对较短的相对较小的基准数据集,我们将最大令牌序列长度截断为 256,以减少内存使用和模型大小。
一个特殊的分类标记‘[CLS]’被添加到每个序列的末尾——这个标记在分类任务中被用作每个序列表示的集合,以了解该序列属于哪个类。对于短于 256 的序列,添加一个填充标记‘[PAD]’以确保所有批次在训练期间保持相同的大小加载到 GPU 内存中。
构建变压器模型
用于分类任务的变压器组的一般结构如下所示。这是 Vaswani 等人用于机器翻译的原始版本的修改形式。

Source: Child et al.
在 PyTorch 代码中,上面的结构如下所示。
定义了一个从 PyTorch 的nn.module继承而来的基类Transformer。输入序列(在我们的例子中,是用于情感分类的文本)通过对序列的标记和位置嵌入求和而被馈送到变换器块。每个连续的变压器组由以下模块组成:
- 层标准化:本质上,标准化应用于训练期间的小批量,以使优化更容易(从数学上讲)——与未标准化的网络相比,这提高了模型的性能。层标准化允许通过改变维度来选择任意大小的小批量,在该维度上为每个批量计算批量统计数据(平均值/方差)——经验表明,这可以提高递归神经网络(RNNs)中顺序输入的性能。要获得图层规范化的直观解释,请阅读 Keita Kurita 的这篇博客文章。
- 自我关注:使用 PyTorch 的
MultiHeadAttention模块封装变形金刚的自我关注逻辑——即变形金刚对位置信息进行编码并在训练过程中从中学习的能力。 - 漏失:神经网络中减少过拟合的经典正则化技术——这是通过在训练过程中引入随机噪声来实现的。
- 前馈模块:执行前向传递,包括另一层归一化,带有一个隐藏层和一个非线性(通常为 ReLU 或 GELU ),然后是 dropout。
在我们网络的前馈模块中定义了两个“掩码”。
- 填充掩码:这屏蔽了之前引入的填充标记(
[‘PAD’]),使每个批次的每个序列长度相同。这样做告诉模型在推断过程中屏蔽掉这些标记,以便它们被自我关注模块忽略。填充遮罩特定于每个批次。

Padding mask: NAACL 2019 transferlearning tutorial slides
- 注意屏蔽:由于该方法使用因果语言模型,所以包括了注意屏蔽——这按照因果语言模型屏蔽了下面输入中的先前标记。通过将主对角线以上的所有元素的值设置为负无穷大,使用上三角矩阵(
[torch.triu](https://pytorch.org/docs/stable/torch.html#torch.triu))为所有批次指定相同的掩码。

Attention mask: NAACL 2019 transferlearning tutorial slides
添加分类标题
模型的下游部分在现有转换器的顶部使用线性分类层。TransformerWithClfHead类继承自基础Transformer类,并将CrossEntropyLoss指定为要优化的损失函数。线性层的大小为[embedding_dimensions, num_classes] —在这种情况下,对于现有的预训练模型和 SST-5 数据集,为 410×5。
从分类层中提取原始输出,即逻辑值,并将其提供给 softmax 函数,以生成分类概率向量(1×5)作为输出。
训练模型
从由 HuggingFace、提供的[预训练模型初始化模型的权重,并且在 SST-5 数据集上运行训练脚本 [training/train_transformer.py](https://github.com/prrao87/fine-grained-sentiment/blob/master/training/train_transformer.py)。
以下超参数用于训练模型——注意嵌入维度的数量、注意头的数量等。没有显式设置—这些是从预训练模型继承的。在 3 个训练时期之后,对模型进行检查点检查,并保存其配置参数。

💡线性预热时间表:来自 [pytorch-ignite](https://pytorch.org/ignite/contrib/handlers.html#ignite.contrib.handlers.param_scheduler.PiecewiseLinear)的分段线性时间表不是设置一个恒定的学习速率,而是定义为在训练的早期阶段提高学习速率,然后线性下降到零。这通常是一种在迁移学习过程中确保知识良好迁移的好方法(类似于 ULMFiT,Howard and Ruder,2018 )中的“倾斜三角形学习率”。
💡梯度累积:正如 Thomas Wolf 在他的文章“ 训练神经网络的实用技巧 ”中描述的那样,模拟较大批量而不会遇到 GPU 内存问题的一个好方法是累积梯度。这是通过对来自多个反向传播步骤的梯度张量求和,然后调用优化器使损失最小化来实现的——注意,损失还需要除以累积步骤的数量。这样做可以让我们用比 GPU 内存中实际容纳的更大的批量进行训练,从而改善模型的学习。**
模型推理
在本节中,我们将通过一个示例,逐行深入研究如何使用一个经过训练的模型对我们的 SST-5 情感数据集进行推断。输入文本被标记化,转换成整数 id,并作为适当形状的张量馈送给模型,如下面的笔记本所示。
这个过程使用文件[classifiers.py](https://github.com/prrao87/fine-grained-sentiment/blob/master/classifiers.py)中的TransformerSentiment类封装,以将 SST-5 数据集读入 Pandas 数据帧,并评估转换器的性能。
估价
在 SST-5 测试上运行经过训练的 transformer 模型(在这个 Google drive 链接中可用),我们可以看到因果 transformer 将分类准确率提高了近 50%!

transformer 的 macro-F1 分数也比其他基于嵌入的方法(FastText 和 Flair)有所提高。

Each cell in the confusion matrix shows the percentage of predictions made for the corresponding true label.
下一个最好的模型 Flair 的混淆矩阵被放在变压器的混淆矩阵旁边进行比较。

转换器确实做出了许多属于类别标签 2 和 4 的错误预测,但是,与所有其他方法相比,它获得了更多属于少数类别(1 和 3)的正确标签。它能够用如此有限数量的训练样本合理地分类这些少数类的事实证明了使用预训练语言模型和迁移学习进行情感分类任务的能力。
解释变压器的预测
按照本系列的第 2 部分中所示的模型解释方法,我们使用我们训练好的 transformer 模型对 SST-5 测试集中的特定文本示例进行 LIME explainer 可视化。用于生成以下可视化效果的代码可在文件[explainer.py](https://github.com/prrao87/fine-grained-sentiment/blob/master/explainer.py)中获得——经过训练的模型文件也可在这个 Google drive 链接中获得。
例一:不是恐怖,只是平庸得可怕。**

True: 1 — Predicted: 1
变压器似乎正确地将单词“可怕的”和“一般的”识别为促成该示例具有类别标签 1(即,强烈否定)的两个最重要的特征。而非这个词将预测概率推离 1(大概是为了标注 2 或 3),但其效果没有句子中的否定词那么强。副词“可怕地”在将这个句子归类为强烈否定中也起了很小的作用,这意味着该模型对副词等修饰语如何改变句子中的情感程度有所了解。
例 2:演员阵容都很出色……但这部电影本身只是有点迷人。

True: 3 — Predicted: 3
在此示例中,很明显,单词“ but ”在变压器对类别标签 3(中性)的预测中具有最大的权重。这很有意思,因为虽然在这句话中有很多词表现出不同程度的极性("优秀的"、"仅仅是"、"温和的"和"迷人的"),但是"但是"这个词在这句话中充当了一个关键的修饰语——它标志着句子在后半部分从强烈肯定到轻微否定的过渡。再一次,转换者似乎知道修饰语(在这种情况下是连词)如何改变句子的整体情感程度。
另一个有趣的观察是单词“ cast ”在被预测为中性的情绪(类别 3)中不起作用。**所有之前使用的方法(参见第 2 部分),包括 Flair,都错误地将单词“ cast 的特征重要性学习为有助于情感——因为这个单词在训练数据中多次出现。转换器的因果语言模型的潜在功能有助于它在单词和预测的情感标签之间建立更有意义的关联,而不管该单词在训练集中出现了多少次。****
结束语
通过这个系列,我们探索了 Python 中的各种 NLP 方法,用于在斯坦福情感树库(SST-5)数据集上进行细粒度分类。虽然这个数据集极具挑战性,并给现有的文本分类方法带来了许多问题,但很明显,与其他方法相比,NLP 中的当前行业标准——结合迁移学习的 transformers 在分类任务上表现出内在的优异性能。这主要是由于转换器的底层语言模型表示,这给予它更多的上下文意识和对训练词汇的更好的句法理解。
下图总结了 SST-5 数据集上准确性和 F1 分数的连续改进(使用越来越复杂的模型)。

附加实验
为了从转换器中挤出更多的性能,可以尝试一些额外的实验(除了增加模型中的参数数量之外):
- 特定领域数据:目前的方法使用因果语言模型(即 HuggingFace 的预训练模型),该模型有 5000 万个可训练参数。如 NAACL 教程中所述,该模型在 Wikitext-103(即来自维基百科文章的 1.03 亿个标记)上进行了预训练。用更相关的特定领域数据(例如几十万条电影评论)来增加预训练可以帮助模型更好地理解 SST-5 中发现的典型词汇。这一步虽然很昂贵——因为它需要重新训练语言模型——但它是一次性的步骤(,可以使用 HuggingFace 的存储库中的
[pretraining_train.py](https://github.com/huggingface/naacl_transfer_learning_tutorial)文件来完成),并且可以在分类准确性方面产生显著的下游改进。 - 掩蔽语言建模:使用因果语言模型作为预训练目标,虽然训练成本更低,但可能不是获得高精度分类结果的最佳方式。将掩蔽语言建模作为预训练目标进行实验(同时用电影评论增加训练数据)可以产生更准确的语言模型。事实上, NAACL 教程表明,与因果语言模型相比,使用掩蔽语言建模目标的预训练产生了更低的复杂度,这意味着该模型可以在情感分类等下游任务中表现得更好。**
- ****超参数调整:试验更大的批量(用于训练和测试)、增加梯度累积步骤和查看不同的学习率预热时间表可以在测试集上产生额外的性能增益。
如本系列所述,从头开始构建情感分析框架的目的是建立对我们能力的信心,即评估和解释不同的机器学习技术,与我们的问题陈述和可用数据相关。当然,利用大型预训练的基于 transformer 的模型,如 BERT-large、XLNet 或 RoBERTa,可以显著提高真实数据集的性能——然而,重要的是平衡使用这些模型的巨大计算成本与使用具有良好预训练目标和干净、良好注释的、领域特定的训练数据的更小、更简单的模型。随着未来几个月越来越多的 NLP 技术在变形金刚的巨大成功上展开,未来只会有有趣的时代!
代码和训练模型
- 使用 transformer 模型对 SST-5 进行训练和预测的代码在这个项目的 GitHub repo 中。
- 经过训练的变形金刚模型可以在这个 Google drive 链接中找到。
请随意使用 transformer 重现结果并做出您自己的发现!
承认
这篇文章使用的所有代码都是从以下链接中的优秀示例代码改编而来的:
- 用于 NAACL 2019 迁移学习 NLP 教程的 GitHub 资源库
- Google Colab 笔记本展示变形金刚模型实验
- 本文由 Oliver Atanaszov 在变压器微调上发表
进一步阅读
为了更详细地深入了解 NLP 中的迁移学习,强烈建议浏览 Sebastian Ruder、Matthew Peters、Swabha Swayamdipta 和 Thomas Wolf 的 NAACL 2019 教程幻灯片。
使用自定义语料库上的预训练来微调 Albert

Photo by Esperanza Zhang on Unsplash
使用特定领域文本的自定义语料库对 Albert 进行预训练,并针对应用任务进一步微调预训练模型
介绍
这篇帖子展示了在自定义语料库上预训练最先进的 Albert[1] NLP 模型,并在特定的下游任务上进一步微调预训练的 Albert 模型的简单步骤。自定义语料库可以是特定领域或外语等,我们没有现有的预训练阿尔伯特模型。下游任务是应用驱动的,它可以是文档分类问题(例如,情感分析),或者标记问题(例如,NER)等。这篇文章展示了一个自定义实体识别下游任务。本文中的示例用例是提取餐馆评论中的菜名,例如,在评论“我非常喜欢马拉汽船!”中,将“马拉汽船”标记为菜名
然而,艾伯特模型的细节和它是如何工作的并没有在这篇文章中讨论。这篇文章假设你对变形金刚()和阿尔伯特模型有粗略的了解,并且能够从 git repos 克隆和运行脚本。
我的包含插图笔记本的 Github repo 可以在这里找到:https://Github . com/LydiaXiaohongLi/Albert _ fine tune _ with _ pre train _ on _ Custom _ Corpus。我在同一个 repo 中包含了一个 colab 笔记本,它演示了玩具数据集的 E2E 步骤,包括构建 vocab、预训练 Albert 和微调 Albert,由于简单的数据和训练所需的较少步骤,它适合 colab 的 CPU。
玩具数据集
本帖中使用的玩具数据集包括:
- 餐厅评论语料库:在这个玩具例子中由两个评论句子组成,用于预训练艾伯特模型。在实际应用中,一般我们可以用 100M+的句子进行训练。

Restaurant Review Corpus
2.从餐馆评论中提取菜名以微调 Albert 进行菜名识别。训练集由两个相同的复习句子组成,提取菜名。

Dish Name Extraction — Train Set
评估集由两个相似的评论句子组成,但是两个评论中的菜肴交换了,并且文本上下文略有不同,包含未收录的单词(不在餐馆评论语料库中的单词),以验证 Albert 模型的效率。

Evaluation Set
系统模型化
构建 Vocab
第一步是为餐馆评论语料库建立词汇。
Google 并没有开源 WordPiece unsupervised tokenizer,但是,我们可以在开源的 SentencePiece 或 t2t text encoder 子词生成器上进行修改,以生成与 Bert/Albert 模型兼容的词汇。
修改 t2t 文本编码器子字生成器的开源实现可以在 https://github.com/kwonmha/bert-vocab-builder【2】找到
- 克隆 repo(创建一个环境并相应地安装需求)
- 准备语料库文件,在本例中是餐馆评论语料库
- 运行下面的命令来创建 Albert 兼容的词汇文件
python subword_builder.py --corpus_filepattern “{corpus_for_vocab}” --output_filename {name_of_vocab} --min_count {minimum_subtoken_counts}
预先训练艾伯特
下一步是用自定义语料库对 Albert 进行预训练。官方阿尔伯特回购可以在https://github.com/google-research/ALBERT找到
克隆回购
创建一个环境并相应地安装需求。
创建艾伯特预培训文件
使用在构建 Vocab 步骤中使用/创建的自定义语料库和词汇文件运行以下命令。该步骤首先基于输入的词汇文件对语料库进行标记和编码。然后,它为 Albert 创建训练输入文件,以在掩蔽语言模型和句子顺序预测任务上进行训练。
python create_pretraining_data.py --input_file “{corpus}” --output_file {output_file} --vocab_file {vocab_file} --max_seq_length=64
预训练艾伯特模型
- 准备 Albert 配置文件,Albert 配置文件中的 vocab_size 应该与构建 vocab 步骤中创建的 vocab 文件中的词汇总数相同。另一个示例 Albert 配置文件可以在 https://tfhub.dev/google/albert_base/2 的找到,这是用于构建 Albert base v2 模型的配置文件。
- 运行以下命令开始预训练。所有的模型参数将被存储在 output_dir 中。(下面的玩具模型仅运行 300 步的预训练,批量大小为 2)
python run_pretraining.py --input_file={albert_pretrain_file} --output_dir={models_dir} --albert_config_file={albert_config_file} -- train_batch_size=2 --num_warmup_steps=100 --num_train_steps=300 --max_seq_length=64
预训练中的 max_seq_length 应与创建预训练数据步骤相同,以便 tensorflow TFRecord 正确解析数据。
如果您的数据很大,并且存储在 GCS 桶中,您可以相应地添加 TPU 相关参数,如下所示。
python run_pretraining.py — input_file=”gs://{bucket_name}/{folder_names}/{albert_pretrain_file_name}” — output_dir=”gs://{bucket_name}/{folder_names}” — albert_config_file=”gs://{bucket_name}/{folder_names}/albert_config_file” — use_tpu=True — tpu_name={tpu_name} — tpu_zone={tpu_zones} — num_tpu_cores=8
对于实际应用,我们应该有更大的训练批量、训练步数和预热步数。因此,最初的 Albert 论文使用 4096 批量和 125k 训练步数,预热步数为 3125。在这里,我们可以使用谷歌的免费 300 美元启动一个 TPU(v2–8)进行训练,如果只使用一个 v2–8 TPU,你应该用更小的批量(例如 512)和更多的训练步骤进行训练,否则可能会遇到 OOM 问题。
或者,在样本 E2E colab 笔记本中,我用 pytorch 重写了训练前的步骤。
微调艾伯特
最后一步是微调预训练的 Albert 来执行菜名识别任务。参考笔记本了解该步骤。这篇文章中的代码片段并没有连接到完整的笔记本,它们只是为了说明的目的。如果不清楚,请阅读完整的笔记本。
使用 huggingface 的变形金刚实现的 pytorch 版本的微调步骤,可以在同一个 repo 中找到,参考这个笔记本。
流程输入
为了获得统一长度的输入序列,我们设置了 50 个标记的最大长度,如果超过,则截断该序列,否则填充该序列。为此,我们可以使用 Keras 的 pad_sequences。
Albert models 采用 input _ ids[batch _ size * max _ seq _ len]—标记化和编码的文本序列(在这种情况下,input _ mask[batch _ size * max _ seq _ len]—如果标记是根据原始序列编码的,则给出 1;如果填充,则仅根据原始序列的标记计算损失;segment _ ids[batch _ size * max _ seq _ len]—零张量数组(在这种情况下不使用,labels[batch _ size * max _ seq _ len]—如果标记是菜肴名称 0 的一部分,则给出 1;否则。我们将以这种格式处理输入。
df_data[‘text_tokens’] = df_data.text.apply(tokenizer.tokenize)
df_data[‘text_labels’] = df_data.apply(lambda row: label_sent(row[‘name’].lower().split()
, row[‘text_tokens’]), axis=1)
df_data_sampled = df_data[[np.sum(label)>0 for label in df_data.text_labels]]
input_ids = pad_sequences([tokenizer.convert_tokens_to_ids(txt) for txt in df_data_sampled[‘text_tokens’]],
maxlen=MAX_LEN, dtype=”long”, truncating=”post”, padding=”post”)
labels = pad_sequences(df_data_sampled[‘text_labels’],
maxlen=MAX_LEN, padding=”post”,
dtype=”long”, truncating=”post”)
# create the mask to ignore the padded elements in the sequences.
input_mask = [[int(i>0) for i in ii] for ii in input_ids]
批量输入张量。
d = tf.data.Dataset.from_tensor_slices(
({“input_ids”: tf.constant( input_ids, shape=[num_examples, seq_length], dtype=tf.int32),
“input_mask”: tf.constant( input_mask, shape=[num_examples, seq_length], dtype=tf.int32),
“segment_ids”: tf.zeros(shape=[num_examples, seq_length], dtype=tf.int32),}
,tf.constant(labels, shape=[num_examples, seq_length], dtype=tf.int32),))
d = d.batch(batch_size=batch_size, drop_remainder=drop_remainder)
创建阿尔伯特微调模型
在预训练的艾伯特模型之后增加一层,以预测输入序列中的每个标记是否是菜名的一部分。
def create_model(albert_config, mode, input_ids, input_mask, segment_ids,labels, num_labels):
“””Creates a classification model.”””
is_training = mode == tf.estimator.ModeKeys.TRAIN
model = modeling.AlbertModel(
config=albert_config,
is_training=is_training,
input_ids=input_ids,
input_mask=input_mask,
token_type_ids=segment_ids)
output_layer = model.get_sequence_output()
hidden_size = output_layer.shape[-1].value
output_weight = tf.get_variable(
“output_weights”, [num_labels, hidden_size],
initializer=tf.truncated_normal_initializer(stddev=0.02))
output_bias = tf.get_variable(
“output_bias”, [num_labels], initializer=tf.zeros_initializer())
在这种情况下使用对数丢失。
logits = tf.matmul(output_layer, output_weight, transpose_b=True)
logits = tf.nn.bias_add(logits, output_bias)
logits = tf.reshape(logits, [-1, MAX_LEN, num_labels])log_probs = tf.nn.log_softmax(logits, axis=-1)
one_hot_labels = tf.one_hot(labels, depth=num_labels, dtype=tf.float32)
per_example_loss = -tf.reduce_sum(one_hot_labels * log_probs, axis=-1)
loss = tf.reduce_sum(per_example_loss)
带有权重衰减的 Adam 优化器在这种情况下用于学习
列车微调模式
train_op = optimization.create_optimizer(
total_loss, learning_rate, num_train_steps, num_warmup_steps,
use_tpu, optimizer)output_spec = tf.contrib.tpu.TPUEstimatorSpec(
mode=mode,
loss=total_loss,
train_op=train_op,
scaffold_fn=scaffold_fn)train_input_fn = input_fn_builder(
file = “data_toy/dish_name_train.csv”,
tokenizer = tokenizer,
seq_length=MAX_LEN,
drop_remainder=True)estimator.train(input_fn=train_input_fn, max_steps=num_train_steps)
在这一步中,如果你没有用 TPU 估算器训练,它将自动检测没有 TPU 可用,并降级到 CPU。
评估微调模型
报告损失和准确性以供评估
def metric_fn(per_example_loss, label_ids, logits):
predictions = tf.argmax(logits, axis=-1, output_type=tf.int32)
accuracy = tf.metrics.mean(tf.math.equal(label_ids,predictions))
loss = tf.metrics.mean(values=per_example_loss)
#
return {
“eval_accuracy”:accuracy,
“eval_loss”: loss,
}
通过 100 个时期的训练(在笔记本中,批量大小被设置为 1,并且训练步长被设置为 200,这对于 100 个时期的训练是有效的),该模型能够在评估集中提取正确的菜肴名称,即使上下文文本与甚至超出词汇表的单词略有不同

Prediction on Evaluation Set
结论
简而言之,Albert 是 NLP 的一大突破,由于开源,我们可以非常直接地在最先进的模型上构建定制的应用程序。
参考
[1] 钟真 L 。,明达 C 。、塞巴斯蒂安 G、凯文 G 。,皮尤什年代。,拉杜 S 。,用于语言表示的自我监督学习的 Lite BERT(2019),谷歌研究
[2]托马斯·w、弗拉达利·d、维克多·s、朱利安·c、克莱门特·d、安东尼·m、皮尔里奇·c、蒂姆·R、雷米·l、摩根·f、杰米·b,《拥抱脸的变形金刚:最先进的自然语言处理》(2019 年)
[3] M. H. Kwon, Bert-Vocab-Builder ,Github Repo
[4]塞伯坦 AI,羔羊优化器,Github Repo
用 Keras 和 tf 微调 BERT。组件
在这个实验中,我们将预训练的 BERT 模型检查点转换为可训练的 Keras 层,我们使用它来解决文本分类任务。
我们通过使用 tf 来实现这一点。模块,这是一个简洁的抽象,旨在处理预先训练好的 Tensorflow 模型。
导出的模块可以很容易地集成到其他模型中,这有助于使用强大的神经网络架构进行实验。

这个实验的计划是:
- 获得预训练的 BERT 模型检查点
- 定义 tf 的规格。组件
- 导出模块
- 构建文本预处理管道
- 实现自定义 Keras 层
- 训练一个 Keras 模型来解决句子对分类任务
- 保存和恢复
- 优化用于推理的训练模型
这本指南里有什么?
本指南是关于将预先训练的 Tensorflow 模型与 Keras 集成。它包含两个东西的实现:一个 BERT tf。模块和构建在它上面的 Keras 层。它还包括微调(见下文)和推理的例子。
需要什么?
对于熟悉 TensorFlow 的读者来说,完成本指南大约需要 60 分钟。代码用 tensorflow==1.15.0 测试。
好吧,给我看看代码。
这个实验的代码可以在 T2 的实验室里找到。独立版本可以在库中找到。
步骤 1:获得预训练模型
我们从一个预先训练好的基于 BERT 的检查点开始。在这个实验中,我们将使用由谷歌预先训练的英语模型。当然,在构建 tf.Module 时,您可以使用更适合您的用例的模型。
步骤 2:构建一个 tf。组件
tf。模块旨在提供一种简单的方法来操作 Tensorflow 中预训练机器学习模型的可重用部分。谷歌在 tf 维护了一个此类模块的精选库。轮毂。然而,在本指南中,我们将自己从头开始构建一个。
为此,我们将实现一个包含模块内部工作的完整规范的 module_fn 。
我们从定义输入占位符开始。BERT 模型图由通过配置路径传递的配置文件创建。然后提取 we 模型输出:最终的编码器层输出被保存到 seq_output 并且汇集的“CLS”令牌表示被保存到 pool_output。
此外,额外的资产可以与模块捆绑在一起。在这个例子中,我们将一个包含单词表的 vocab_file 添加到模块资产中。因此,词汇文件将与模块一起导出,这将使它成为自包含的。
最后,我们定义了签名,它是输入到输出的特定转换,向消费者公开。人们可以把它看作是与外界的一个模块接口。
这里添加了两个签名。第一个将原始文本特征作为输入,并将计算后的文本表示作为输出返回。另一个不接受输入,返回词汇表文件的路径和小写标志。
步骤 3:导出模块
既然已经定义了 module_fn ,我们就可以用它来构建和导出模块了。将 tags_and_args 参数传递给 create_module_spec 将导致两个图变量被添加到模块中:用 tags{“train”}进行训练,以及用一组空的 tags 进行推断。这允许控制退出,这在推理时被禁用,而在训练期间被启用。
步骤 4:构建文本预处理管道
BERT 模型要求将文本表示为包含input _ id、 input_mask 和segment _ id的 3 个矩阵。在这一步中,我们构建了一个管道,它接受一个字符串列表,并输出这三个矩阵,就这么简单。
首先,原始输入文本被转换成 InputExamples。如果输入文本是由特殊的“|||”序列分隔的句子对,则句子被拆分。
然后,使用来自原始存储库的convert _ examples _ to _ features函数,将 InputExamples 标记化并转换为 InputFeatures。之后,特征列表被转换成带有特征 _ 到 _ 数组的矩阵。
最后,我们将所有这些放在一个管道中。
全部完成!
步骤 5:实现 BERT Keras 层
使用 tf 有两种方法。带有 Keras 的模块。
第一种方法是用 hub 包裹一个模块。角斗士。这种方法很简单,但是不太灵活,因为它不允许在模块中加入任何定制的逻辑。
第二种方法是实现包含该模块的自定义 Keras 层。在这种情况下,我们可以完全控制可训练变量,并且可以将池化操作甚至整个文本预处理管道添加到计算图中!我们将走第二条路。
为了设计一个定制的 Keras 层,我们需要编写一个继承自 tf.keras.Layer 的类,并覆盖一些方法,最重要的是构建和调用。
build 方法创建模块的资产。它从实例化来自 bert_path 的 BERT 模块开始,该路径可以是磁盘上的路径或 http 地址(例如,对于来自 tf.hub 的模块)。然后建立可训练层的列表,并填充该层的可训练权重。将可训练权重的数量限制在最后几层可以显著减少 GPU 内存占用并加快训练速度。它还可能提高模型的准确性,特别是在较小的数据集上。
build_preprocessor 方法从模块资产中检索单词表,以构建步骤 4 中定义的文本预处理管道。
initialize_module 方法将模块变量加载到当前的 Keras 会话中。
大多数有趣的事情都发生在调用方法内部。作为输入,它接受一个张量 tf。字符串,使用我们的预处理管道将其转换成 BERT 特征。使用 tf.numpy_function 将 python 代码注入到图中。然后将特征传递给模块,并检索输出。
现在,根据在 init,中设置的池参数,额外的变换被应用于输出张量。
如果 pooling==None ,则不应用池,输出张量具有形状 [batch_size,seq_len,encoder_dim] 。此模式对于解决令牌级任务非常有用。
如果 pooling=='cls ',仅检索对应于第一个 'CLS' 标记的向量,并且输出张量具有形状 [batch_size,encoder_dim] 。这种池类型对于解决句子对分类任务很有用。
最后,如果 pooling=='mean ',所有记号的嵌入都是平均池化的,并且输出张量具有形状 [batch_size,encoder_dim] 。这种模式对于句子表达任务特别有用。它的灵感来自伯特即服务的 REDUCE_MEAN 池策略。
BERT 层的完整列表可以在库中找到。
第六步:句子对分类
现在,让我们在真实数据集上尝试该图层。对于这一部分,我们将使用 Quora 问题对数据集,它由超过 400,000 个潜在的问题重复对组成,标记为语义等价。
建立和训练句子对分类模型很简单:
顺便说一句,如果你不喜欢在图形中进行预处理,你可以通过设置 do_preprocessing=False 来禁用它,并使用 3 个输入来构建模型。
仅微调最后三层就可以获得 88.3%的验证准确率。
Train on 323432 samples, validate on 80858 samples
Epoch 1/5323432/323432 [==============================] - 3197s 10ms/sample - loss: 0.3659 - acc: 0.8255 - val_loss: 0.3198 - val_acc: 0.8551
Epoch 2/5
323432/323432 [==============================] - 3191s 10ms/sample - loss: 0.2898 - acc: 0.8704 - val_loss: 0.2896 - val_acc: 0.8723
Epoch 3/5
323432/323432 [==============================] - 3231s 10ms/sample - loss: 0.2480 - acc: 0.8920 - val_loss: 0.2833 - val_acc: 0.8765
Epoch 4/5
323432/323432 [==============================] - 3205s 10ms/sample - loss: 0.2083 - acc: 0.9123 - val_loss: 0.2839 - val_acc: 0.8814
Epoch 5/5
323432/323432 [==============================] - 3244s 10ms/sample - loss: 0.1671 - acc: 0.9325 - val_loss: 0.2957 - val_acc: 0.8831
步骤 7:保存和恢复模型
模型权重可以通过常规方式保存和恢复。模型架构也可以序列化为 json 格式。
如果到 BERT 模块的相对路径不变,从 json 重建模型将会工作。
步骤 8:优化推理
在某些情况下(例如,当服务时),人们可能想要优化训练模型以获得最大的推理吞吐量。在 TensorFlow 中,这可以通过“冻结”模型来实现。
在“冻结”过程中,模型变量由常数代替,训练所需的节点从计算图中删除。生成的图形变得更加轻量级,需要更少的 RAM,并获得更好的性能。
我们冻结训练好的模型,并将序列化的图形写入文件。
现在我们还原一下冻结图,做一些推断。
为了运行推理,我们需要得到图的输入和输出张量的句柄。这部分有点棘手:我们在恢复的图中检索所有操作的列表,然后手动获取相关操作的名称。列表是排序的,所以在这种情况下,只需进行第一个和最后一个操作。
为了得到张量名称,我们将 ":0" 附加到 op 名称上。
我们注入到 Keras 层中的预处理函数是不可序列化的,并且没有在新图中恢复。不过不用担心——我们可以简单地用相同的名称再次定义它。
最后,我们得到了预测。
array([[9.8404515e-01]], dtype=float32)
结论
在这个实验中,我们创建了一个可训练的 BERT 模块,并用 Keras 对其进行了微调,以解决句子对分类任务。
通过冻结训练好的模型,我们消除了它对自定义层代码的依赖性,使它变得可移植和轻量级。
本系列中的其他指南
解雇你的商务智能团队
改变 BI 的授权和参与模式
“商业智能”一词在 20 世纪 90 年代广泛流行,最初是被定义为“通过使用基于事实的支持系统来改进商业决策的概念和方法”显然,对于任何公司来说,这听起来都是一项具有战略重要性的职能。
那么,为什么会有这样刺耳的标题呢?虽然上面的描述可能捕捉到了 BI 团队在 20 世纪 90 年代所做的事情,但这并不是 BI 团队在过去十年所做的事情。随着数据分析的采用和复杂性发生变化,BI 团队成为分析生态系统的底层支持者,而数据科学家获得了所有的乐趣和荣耀。BI 团队最终成为订单接受者,执行那些不以分析为生的人提出的精确要求。这严重削弱了它们的影响。

但是不一定要这样。你也不需要解雇你的商务智能团队。我们可以通过改变商业智能的任务和他们与利益相关者互动的方式来恢复商业智能在分析生态系统中的地位。这将带来更有影响力的见解,同时确保分析师自己感受到专业的所有权和目的。**
让我们看一下为什么和如何做。
要了解目前 BI 的问题,只需考虑常见的基于票证的参与模型:BI 分析师收到票证,执行请求,返回答案,通过更多票证获得反馈,然后循环继续。对话甚至可能不会发生。
现在,如果你是与 BI 团队一起工作的人,你可能会认为这种参与模式正是你想要的。很公平——听起来确实很有效率和条理。
但相信我,这不是你想要的。
只要想想提取有意义和可操作的见解所需的工作性质。即使目标是保持分析的简单性,您也总是会在分析之旅中结束,这涉及到将数据分段、创建基线、构建派生的度量标准等等。这需要分析师的所有权和创造力。
但是在 BI 参与模型中,分析师仅仅是跟随其他人的思路。失去了所有权和创造力。
那么这里有什么解决办法呢?首先,总是用这两个问题开始任何“BI”分析或仪表板:(1)我们试图解决的问题是什么?(2)我们希望采取什么行动?从那时起,分析师就有责任寻找问题背后的问题,并在问题浮出水面时分享见解。门票将被面对面的会议取代,并形成共生的伙伴关系。如果人们找不到时间,那么这个问题从一开始就不重要。**
如果你已经走到这一步,你可能会认为我是在建议将所有的商业智能分析师转变为数据科学家。但事实并非如此。虽然 ETL 和数据探索是任何将数据转化为现金流的团队的核心,但还是有一些关键的区别:
数据科学家应该(主要)致力于开发基于先进方法的“数据产品”。这可能是推动产品推荐或定价和匹配算法的 ML 模型。
另一方面,分析师的关注点可能是提供一个带有战略建议的幻灯片或一个仪表板。事实上,我更喜欢用“战略分析”这个术语来称呼这些团队,因为它描述了他们角色的目的。
数据科学和战略分析这两个职能都非常重要,有明确的目的,需要不同的能力,并提供有益的职业道路。大多数公司都需要这两个团队,但不需要 BI 团队。因此,如果你正在管理一个 BI 团队,是时候改变他们的任务和利益相关者的参与模式了。让我们彻底告别“商业智能”这个术语。
第一场民主党初选辩论:初步情绪分析
在佛罗里达州迈阿密举行的 2020 年初选的第一场辩论中,美国民主党的 20 名候选人在两个晚上相互指责,试图让别人听到自己的声音,有时会忘记回答问题。无论你是已经关注了一段时间,还是刚刚开始关注,都很难区分候选人,也很难感受到每个人在如此拥挤的领域中的表现。
以下图表是从第一场辩论前 7 小时到第二场辩论后 7 小时推特对每位候选人的反应快照。计数值是根据每条推文中信息的测量情绪而被标记为负面、中性或正面的推文。由于数据量少,一些候选人被排除在外。
对 15 名候选人的看法















Count of reaction sentiment for each candidate
第一夜集锦


Indicators of good reception
朱利安·卡斯特罗(Julian Castro)和图尔西·加巴德(Tulsi Gabbard)在首场演出中的表现似乎受到了好评。用户参与度明显上升,这是相当积极的。在第二场辩论后,这种兴趣可以被视为随着对话的进行而逐渐消退,但看看他们的竞选活动如何利用最初的关注热潮将是有趣的。


Indicators of waning attention
在第一场辩论开始时,贝托和布克似乎受到了高度关注,尽管被认为基本上是负面的。第二天,对这两个人的提及逐渐减少,第二天晚上就完全消失了。对于大多数候选人来说,这种下降从第一天晚上开始就是典型的。
第二晚亮点

Strong momentum going in and coming out
卡玛拉·哈里斯被认为是初选的领先者之一,这可以从她在辩论前的势头中看出。在第二个晚上的强劲表现之后,她似乎保持了一些这种膨胀,情绪的平衡从负面进入到正面出来。

Sustained momentum before and after
尽管在较小规模的推文中,皮特市长设法在辩论前后保持用户的一致参与,就数量而言,很少有候选人能够做到。此外,情绪的分布从相当平衡的进入(积极和消极反应的数量相似)变为基本积极的离开。
这些只是从上面的信息中提取的一些信息。请在下面分享自己的想法和评论!欢迎对分析的批评(但请保持中立或更高的情绪!).
关于分析的注释
使用 Tweepy 对与每位候选人的 Twitter 账户相关的推文样本进行流式传输。每条推文的情绪(即情绪反应)是使用 VADER 计算出来的。
查看这些图表时,应考虑几个限定条件。只有直接提到候选人的推文被计算在分析中,这可能会倾向于更极端的反应。此外,第二场辩论后的推文量似乎低得令人怀疑,这让我相信大多数讨论都从与候选人的直接互动转向围绕辩论主题本身的讨论。这种音量的下降也可能是我的数据被捕获和存储的一个缺陷,因为推文的速度对我苦苦挣扎的 MacBook Air 来说往往太快了。在未来,我将着眼于围绕#DemDebate 这样的标签进行更广泛的讨论,并使用实体识别将它们与候选人联系起来。
参考
项目资源库
https://github.com/danjizquierdo/1stPrimaryDebateNight2020
使用维达
进行情绪分析 https://medium . com/analytics-vid hya/simplified-social-media-opinion-Analysis-using-Vader-in-python-f9e 6 EC 6 fc 52 f作者 Parul Pandey
流媒体推文教程
https://www.youtube.com/watch?v=wlnx-7cm4Gg由 Lucid 编程
第一次 Kaggle 比赛经历
我第一次参加 kaggle 比赛的经历

Photo by Victoire Joncheray on Unsplash
在我今年自学机器学习之旅的最初计划中,我曾承诺在任何两场(直播)Kaggle 比赛中进入前 25%。
这是一篇关于“rm-rf /”团队如何在我们有史以来的第一次 kaggle 比赛中进入前 30%的记录:“快,抽!涂鸦识别挑战”,由谷歌人工智能团队主办,在 kaggle 上。
特别说明:团队“rm-rf /”是由我的商业伙伴和朋友 Rishi Bhalodia 和我组成的两人团队。
经验
想象一下:一场持续三个月的比赛。没有终点线,有高分。你是学校里经验丰富的跑步运动员。
有什么问题吗?
你反对我光着脚跑的时候有人开着超跑超速(经验丰富的特级大师)。当然,我很擅长对付我的朋友,但是对付一辆车呢?
从某种意义上说,这就像一个 PUBG 游戏,我们自由落体到战斗中,抓住任何我们能抓住的枪,试图到达“安全区”。
对我们来说,安全区是奖牌区。
那么为什么大家都那么沉迷 Kaggle 呢?
就我个人而言,在这一个月的比赛中,我学到的东西比我参加的任何一个月的 MOOC 都要多。
kaggle 上的“专家”总是慷慨地给出他们的想法。我们感到惊讶的是,许多人几乎给出了他们的完整解决方案——在最高的 LB 位置上!
每天晚上我们都会做一个很好的提交,第二天你醒来的时候,你会在磅上下降 20 级!点击重置,再次朝着更好的提交工作,然后每天重复一次。
最后,我们在 Private LB 上得到了 385/1316:我们在给定计算上的想法只允许达到这个等级。这绝对是一次奇妙的经历,我一定会参加更多的比赛,努力表现得更好。
在许多情况下,当我们在熟睡后设法获得前 100 名提交时,我们会醒来发现一个公共内核会将我们完全抛下 LB 50 个位置!!
参加竞赛的人是真正有激情的,总是有溢出的想法,总是有溢出的才华。对我们来说,这只是尽我们最大的努力,用最少的睡眠生活几个星期,打破和建立 conda 环境,同时试图将讨论中分享的想法转换成代码。
最终,我真正了解了为什么 Kaggle 是数据科学之家。
目标
在高尔夫比赛中表现出色一直是我的梦想目标。首先,我保持了前 25%提交的门槛。
涂鸦挑战:
尽管比赛有大量的文件大小和一些有趣的挑战,我们的目标是我们最好的奖牌。
竞争
目标:目标是从包含“绘制”或创建涂鸦信息的 CSV(s)文件数据集中识别涂鸦。
挑战是:挑战中的文件数量约为 5000 万!
就我个人而言,我已经完成了大量的 MOOC(s ),而且我很有信心,因为我已经过了多少次 CNN 的定义,获得铜牌是相当容易的。当然,我错得很离谱。
不起作用的东西
我们非常感谢 Kaggle 大师 Radek Osmulski 分享他的 Fast.ai 入门包——我们是在它的基础上加上白鲸大师的一些技巧构建的。
错误 1:作为初学者,在竞争还新鲜的时候开始一场纸牌游戏比赛总是有启发性的。
为什么?
竞赛中溢出的想法数量是巨大的!即使是 LB 的最高得分者也非常慷慨地分享他们的方法——这只是一个我们是否能找出丢失的信息并能将他们的想法转换成代码的问题。
自从比赛开始以来,我们已经进行了将近一半。
错误 2:我们学习了“实验”,验证。
starter pack 适用于 1%的数据,我尝试用 5%的数据进行试验,然后用 10%的数据进行试验,结果显示性能持续提高。
接下来:我决定把所有的数据都扔给模型,训练在我的 8 GB GPU 上运行了大约 50 个小时。我们期望这种方法能获得最高分,但是模型的准确性却下降了!
经验教训:实验非常重要,验证也是如此。starter pack 依赖于将图像“绘制”到内存中,然后在其上训练模型。Linux 的问题是,它对文件的数量有限制,除非有专门的格式。
我们没有进行验证检查,而是根据一部分数据进行训练。反射验证集肯定是您在开始时必须安装的东西。
有效的想法
渐进式训练和调整规模:
- 用 256 图像大小对 1%的数据训练模型。
- 将模型微调为 128 图像大小的 5%的数据。
- 此外,微调模型到 20%的数据与 64 图像大小。
- 当在数据集的相同部分上进行训练时,ResNet 18 < ResNet 34 < ResNet 50 < ResNet 152。
这种方法显示了性能的持续提高。
集成:我们最好的提交是在 20%的数据集上用 fastai (v1)训练的 ResNet 152 和 Kaggle GM Beluga 的 MobileNet 内核的“混合”。
摘要
你会在竞赛讨论中发现许多更好的讨论,所以如果这些想法没有让你获得最高的 LB 分数,请原谅这篇文章。
这真的是我在比赛中学到的东西的个人总结。
最后,我保证会参加更多的比赛,希望你会在最近的比赛中发现“rm-rf /”队。
我已经被“Kaggle Bug”咬了一口,比起注册更多的 MOOC,我可能更喜欢未来的比赛
LB 上见,快乐的卡格林!
如果你觉得这些面试很有趣,并且想成为我的学习之路的一部分,你可以在 twitter 上找到我 。
如果你有兴趣阅读关于深度 学习 和计算机视觉的新闻,可以查看我的 简讯这里 。
第一个为初学者讲解的神经网络(带代码)
理解并创造一个感知机

Photo by Clément H on Unsplash
所以你想创建你的第一个人工神经网络,或者只是发现这个主题,但不知道从哪里开始?按照这个快速指南来理解所有的步骤!
什么是神经网络?
基于自然,神经网络是我们对大脑的通常表示:神经元与其他神经元相互连接,形成一个网络。一个简单的信息在变成一个实际的东西之前会在它们当中传递,就像“移动手来拿起这支铅笔”。
完整的神经网络的操作非常简单:输入变量作为输入(例如,如果神经网络应该告诉图像上的内容,则输入图像),经过一些计算后,返回输出(按照第一个示例,给出猫的图像应该返回单词“cat”)。
现在,你应该知道人工神经网络通常放在列上,所以列 n 的一个神经元只能连接到来自列 n-1 和 n+1 的神经元。使用不同架构的网络类型很少,但我们现在将关注最简单的。
所以,我们可以这样描述一个人工神经网络:

Figure 1 — Representation of a neural network
神经网络通常可以从左向右阅读。这里,第一层是输入的层。有两个内部层(称为隐藏层)做一些数学,最后一层包含所有可能的输出。不要为每一列底部的“+1”而烦恼。这就是所谓的“偏见”,我们稍后会谈到这一点。
顺便说一下,术语“深度学习”来自包含几个隐藏层的神经网络,也称为“深度神经网络”。图 1 可以被认为是一个。
神经元做什么?
每个神经元完成的操作非常简单:

Figure 2 — Operations done by a neuron
首先,它将它所连接的前一列中的每个神经元的值相加。在图 2 中,有 3 个输入(x1,x2,x3)到达神经元,因此前一列的 3 个神经元连接到我们的神经元。
该值在相加之前乘以另一个称为“权重”的变量(w1,w2,w3),该变量决定两个神经元之间的连接。神经元的每个连接都有自己的权重,这些是在学习过程中唯一会被修改的值。
此外,可以将偏差值加到计算的总值上。它不是来自特定神经元的值,而是在学习阶段之前选择的,但对网络是有用的。
在所有这些求和之后,神经元最终对获得的值应用一个称为“激活函数”的函数。

Figure 3 — Sigmoid function
所谓的激活函数通常用于将之前计算的总值变为 0 和 1 之间的数(例如通过图 3 所示的 sigmoid 函数来实现)。其他函数存在并可能改变我们函数的极限,但保持限制值的相同目标。
这就是一个神经元所做的一切!将连接神经元的所有值乘以各自的权重,相加,并应用激活函数。然后,该神经元准备好将其新值发送给其他神经元。
在一列中的每个神经元完成之后,神经网络传递到下一列。最后,最后获得的值应该是可用于确定期望输出的值。
既然我们了解了神经元的功能,我们就有可能创造任何我们想要的网络。然而,要使神经网络学习,还需要执行其他操作。
神经网络是如何学习的?
是的,创造变量并让它们相互作用是很棒的,但这不足以让整个神经网络自我学习。我们需要准备大量数据提供给我们的网络。这些数据包括神经网络的输入和输出。
让我们来看看学习过程是如何进行的:
首先要记住,给神经网络一个输入,它就返回一个输出。在第一次尝试中,它自己无法获得正确的输出(除非运气好),这就是为什么在学习阶段,每个输入都带有标签,解释神经网络应该猜测什么输出。如果选择是正确的,则保留实际参数,并给出下一个输入。但是,如果获得的输出与标签不匹配,权重就会改变。这些是在学习阶段唯一可以改变的变量。这个过程可以想象成多个按钮,每次输入没有猜对时,这些按钮就变成不同的可能性。
为了确定修改哪个权重更好,需要进行一个称为“反向传播”的特殊过程。我们不会在这一点上停留太久,因为我们将构建的神经网络不使用这一确切的过程,但它包括回到神经网络并检查每个连接,以检查输出如何根据权重的变化而表现。
最后,要控制神经网络的学习方式,还需要知道最后一个参数:“学习速率”。名称说明了一切,这个新值决定了神经网络学习的速度,或者更具体地说,它将如何一点一点地或以更大的步长修改权重。1 通常是该参数的一个好值。
感知器
好了,我们知道了基础知识,让我们检查一下我们将要创建的神经网络。这里解释的一个被称为感知器,是有史以来第一个神经网络。它由输入列中的 2 个神经元和输出列中的 1 个神经元组成。这种配置允许创建一个简单的分类器来区分 2 个组。为了更好地理解可能性和局限性,让我们来看一个快速的例子(除了理解之外没有太大的兴趣) :
假设您希望您的神经网络能够根据“包含或”规则返回输出。提醒:

- 如果 A 为真,B 为真,那么 A 或 B 为真。
- 如果 A 为真,B 为假,那么 A 或 B 为真。
- 如果 A 为假,B 为真,那么 A 或 B 为真。
- 如果 A 为假,B 为假,那么 A 或 B 为假。
如果你用 1 代替“真”的 s,用 0 代替“假”的 s,并把这 4 种可能性作为平面图上的坐标点,那么你会发现最后两组“假”和“真”可能被一条直线分开。这是感知器可以做到的。
另一方面,如果我们检查“异或”的情况(在这种情况下,“真”或“真”(点(1,1))为假),那么我们可以看到一条简单的线不能分开两个组,感知器不能处理这个问题。
因此,感知器确实不是一个非常有效的神经网络,但它很容易创建,并且仍然可以用作分类器。
创造我们自己简单的神经网络
让我们用 Python(下面例子中的 3.x)从头开始创建一个神经网络。
**import** numpy, random, os
lr = 1 #learning rate
bias = 1 #value of bias
weights = [random.random(),random.random(),random.random()] #weights generated in a list (3 weights in total for 2 neurons and the bias)
程序的开始只是定义了库和参数值,并创建了一个包含将要修改的权重值的列表(这些值是随机生成的)。
**def** Perceptron(input1, input2, output) :
outputP = input1*weights[0]+input2*weights[1]+bias*weights[2]
**if** outputP > 0 : #activation function (here Heaviside)
outputP = 1
**else** :
outputP = 0
error = output – outputP
weights[0] += error * input1 * lr
weights[1] += error * input2 * lr
weights[2] += error * bias * lr
这里我们创建一个定义输出神经元工作的函数。它需要 3 个参数(神经元的 2 个值和预期输出)。“outputP”是对应于感知器给出的输出的变量。然后我们计算误差,用于修改每个连接到输出神经元的权重。
**for** i in range(50) :
Perceptron(1,1,1) #True or true
Perceptron(1,0,1) #True or false
Perceptron(0,1,1) #False or true
Perceptron(0,0,0) #False or false
我们创建了一个循环,让神经网络将每种情况重复几次。这部分是学习阶段。迭代的次数是根据我们想要的精度来选择的。但是,请注意,过多的迭代可能会导致网络过度拟合,从而导致网络过于关注已处理的示例,因此在学习阶段没有看到的情况下,它无法获得正确的输出。
然而,我们这里的情况有点特殊,因为只有 4 种可能性,我们在神经网络的学习阶段将所有可能性都给了它。一个感知器应该给出一个正确的输出,而不需要看到它正在处理的案例。
x = int(input())
y = int(input())
outputP = x*weights[0] + y*weights[1] + bias*weights[2]
**if** outputP > 0 : #activation function
outputP = 1
**else** :
outputP = 0
**print**(x, "or", y, "is : ", outputP)
最后,我们可以要求用户输入自己的值来检查感知器是否在工作。这是测试阶段。
在这种情况下,使用激活函数 Heaviside 是很有趣的,因为它将所有值精确地恢复为 0 或 1,因为我们正在寻找一个假或真的结果。我们可以尝试使用 sigmoid 函数,获得一个介于 0 和 1 之间的十进制数,通常非常接近其中一个极限。
outputP = 1/(1+numpy.exp(-outputP)) #sigmoid function
我们还可以将神经网络刚刚计算出的权重保存在一个文件中,以便稍后使用,而无需进行另一个学习阶段。对于更大项目来说,这个阶段可能会持续几天或几周。
就是这样!你已经完成了自己完整的神经网络。你创造了它,让它学习,检查它的能力。你的感知器现在可以被修改来用在另一个问题上。只要改变迭代过程中给定的点,如果你的情况更复杂,调整循环次数,让你的感知器做分类。
您是否希望列出最近的森林中的两种类型的树,并能够确定新树是 A 型还是 B 型?选择两个可以分离两种类型的特征(例如高度和宽度),并为感知器创建一些点以放置在平面上。让它推导出一个方法来分开 2 组,并输入任何新树的点,以了解它是哪种类型。
你以后可以扩展你的知识,看到更大更深的神经网络,这是非常强大的!
还有多个方面我们没有治疗,或者仅仅够你得到基本的,所以不要犹豫进一步。我很想写一些更复杂的神经网络,所以请继续关注!
感谢阅读!
我希望这个小指南有用,如果你有任何问题和/或建议,请在评论中告诉我。
激光诱导击穿光谱中的一阶多元校正
激光诱导击穿光谱(LIBS)是最简单和最强大的光谱化学技术。本文介绍了 LIBS 光谱定量分析中常用的线性回归方法。

激光诱导击穿光谱
L 激光诱导击穿光谱(LIBS)是目前分析化学中最简单也是最强大的技术之一。其多功能性允许对复杂样品进行详细的定性和定量分析,无论是固体、液体还是气体形式。此外,被分析的样品可以是任何尺寸或形状,包括粉末、浆体和气溶胶。LIBS 仪器可以做得坚固耐用,便于携带,可以在任何环境下使用,包括危险的环境。只需要对样品进行光学访问,并且可以在几十米的距离进行测量。数据采集速度快,LIBS 仪器的操作可以很容易地自动化。
在现有的不同分析技术中,LIBS 可以归类为多元素光谱化学技术,它由激光-物质相互作用的物理学控制(这种相互作用对 LIBS 测量有巨大的影响,因为它控制所有中间物理过程,如电子和离子加热、热扩散、粒子烧蚀和动力学以及等离子体形成和膨胀)。

A short-pulsed high-power laser beam is focused onto a sample surface, producing dense hot plasma plume for spectroscopic analysis. Image credit: Applied Spectra, Inc.
在实践中,LIBS 是通过将强激光束(通常来自脉冲纳秒 Nd:YAG 激光器)聚焦到样品表面上以产生烧蚀材料的热离子化气体(所谓的等离子体羽流)来进行的。来自受激物质复合的等离子体光发射出样品中存在的元素的原子发射谱线特征,然后被收集并引入光谱仪,在光谱仪中被分散成离散的波长。一旦等离子体温度和电子密度充分下降,并且观察原子谱线的条件是最佳的,就进行 LIBS 光谱的采集(使用门控 CCD 照相机)。
下图显示了从土壤样品中获得的典型 LIBS 光谱,显示了各种化学元素的强发射线。

Typical LIBS spectrum (in 210–850 nm range) of a soil sample.
由于其独特的特点,人们对 LIBS 的兴趣迅速蔓延。每年发表的研究论文数量很好地说明了这种兴趣。工业应用的数量也是如此,近年来增长迅速,例如在环境分析、冶金工业、考古学、生物学、安全和空间探索方面。例如,自 2012 年 8 月 19 日以来,作为美国宇航局火星科学实验室好奇号任务的一部分,由美国能源部洛斯阿拉莫斯国家实验室和法国 CESR 实验室开发的用于探索火星盖尔陨石坑的 ChemCam LIBS 仪器已经提供了数千次对火星岩石和土壤元素组成的宝贵分析。超级摄像机是 ChemCam 的改进版本,计划于 2020 年夏天通过火星车任务发射,并于 2021 年冬天在 Jezero 陨石坑内着陆。

SuperCam instrument. Image credit: NASA.
一阶多元校准
在过去的几十年里,在 LIBS 使用化学计量学方法,尤其是一阶多元校准曲线,已经显著提高了其分析性能,因为多元统计方法已经允许利用 LIBS 光谱中通常存在的冗余信息。在化学计量学中,在从未知样品预测分析物浓度之前,校准模型通过多元回归方法建立,并被训练以学习光谱-浓度关系。但首先让我们定义术语“一阶校准”。
光谱数据的结构数据
在数学中,张量是代数对象,可以用一个标量(秩为 0 的 0 阶张量)、一个向量(秩为 1 的 1 阶张量)和一个矩阵(秩等于其矩阵秩的 2 阶张量)来表示。在化学计量学中,张量的阶指的是张量所跨越的模式或空间的数量。张量的秩(尽管没有完全理解,特别是对于高阶张量)可以定义为其线性组合再现该张量的秩为 1 的张量的最小数量。这与矩阵的秩不同,矩阵的秩在线性代数中被定义为线性独立元素(行或列的向量)的最大数量。换句话说,或者用化学计量学的术语来说,
双向矩阵的秩是复制矩阵所需的双线性(PCA)分量的最小数目。而三路数组的秩是再现该数组所需的三线性(PARAFAC)分量的最小数目。拉斯莫斯兄弟
当来自多个样品的测量响应是标量(零阶张量)时,它们可以排列在一个向量中,从而给出一个单向数据结构(例如多个溶液的 pH 测量)。另一方面,如果响应是一阶张量,则它们产生双向数据结构(例如,LIBS 光谱数据,其提供一组不同波长的发射强度)。最后,使用两种仪器(例如 LIBS 与拉曼光谱仪协同工作)或一组二阶响应(例如瞬态系统的 LIBS 测量)涉及处理三维数据阵列,即三向结构。概括地说,来自每个分析样本的一组 i 阶数据创建了一个( i +1)向结构。

Examples of data structure.
单变量校准模型
我们将校准曲线定义为一个模型,该模型由一组经认证的参考材料或标准物质(即,其性质由经认证的实验室或机构准确定义和认证的样品)或参考样品(即,之前通过成熟方法分析的样品)开发而成,其中分析物含量和 LIBS 光谱是已知的(下图说明了典型的线性和二次校准曲线)。注意,建立在来自一组校准样品的单值响应(例如发射线的信号)上的经典校准曲线是零阶校准模型的一个例子。

Linear (left panel) and non-linear (right panel) calibration curves. Note that LOD, LOQ, and LOL refer to the limit of detection, of quantification, and of linearity. These parameters are respectively defined as (1) the lowest analyte concentration likely to be reliably distinguished from a blank sample and at which detection is feasible, (2) the lowest concentration at which the analyte can not only be reliably detected but at which some predefined goals for bias and imprecision are met and, (3) the upper limit of the dynamic range for which the relationship is linear. The slope of the regression line defines the sensitivity of the calibration model.
校准曲线通常与相应的置信限和预测限一起提供,如下图所示。

Confidence and prediction intervals for the classical least-squares straight-line fit. LCL and UCL are the lower and upper confidence limits. Likewise, LPL and UPL are the lower and upper prediction limits. ĉ is the predicted concentration from the measured signal of an unknown sample.
置信限(LCL 和 UCL)给出了估计平均值的一个区间(即通过重复测量计算得出),该区间覆盖了概率为 1-α的总体平均值(α = 0.05,置信水平为 95%)。然而,预测极限(LPL 和 UPL)给出了概率为 1α的新样品( ĉ )浓度的预测不确定性。

Example of univariate calibration curves for the detection of Ca, Sr, K, and Li in aqueous solutions. Source: Goueguel et al., Matrix effect of sodium compounds on the determination of metal ions in aqueous solutions by underwater laser-induced breakdown spectroscopy, Applied Optics, 54, 6071, 2015.
重要的是要注意,单变量校准模型的灵敏度(即回归线的斜率)将取决于发射线的光谱特征。通常根据三个主要标准选择该谱线:(1)最强,(2)不可能自吸收,以及(3)没有光谱干扰。另一方面,在多变量校准模型中,灵敏度将根据是选择考虑整个光谱还是选择一些谱线而受到影响。此外,使用曲线下的面积(峰面积)而不是峰强度通常更合适。估算峰面积的直接方法是用高斯或洛伦兹曲线拟合峰,或使用数值积分技术,如牛顿-科特斯公式(如梯形法则)或高斯积分。
多元校准模型
因此,在熟悉了单变量校准曲线的基础知识后,现在有趣的是注意到多变量校准可以根据光谱-浓度关系分为直接校准或反向校准。在直接多元校准模型中,假设 LIBS 信号与分析物浓度成比例。该模型似乎是经典单变量模型的推广,可以通过经典最小二乘(CLS)回归进行估计。而在逆多元校准中,假设浓度与 LIBS 信号成比例。在这种方法中,通过反最小二乘法(ILS)从一组校准样本中估计回归系数。
在下文中,我们将看到 ILS 回归(即多元线性回归(MLR) )具有有限的适用性,尤其是在处理高度相关的预测因子(LIBS 光谱)或样本少于包含复杂矩阵效应的变量时。幸运的是,已经提出了许多替代的回归方法来处理这种情况。
直接多元校准
光谱 = f ( 成分)
信号=灵敏度×浓度+误差
逆多元校准
构图 = f ( 光谱)
浓度=信号××回归系数+误差
使用反向校准优于直接校准的原因在于,当我们只知道校准样品中目标分析物的浓度,而对其他成分的浓度一无所知时,可以应用字母。因此,逆多元校准不仅能够预测分析物的浓度,还能够预测样品的物理性质(例如,我们发表了一篇研究文章,其中我们能够仅根据 LIBS 光谱预测土壤颗粒大小的分数)。这个基本概念是应用于 LIBS 定量分析的化学计量学方法的基础。因此,在下面的讨论中,我们将只对反校准方程感兴趣,它可以写成:


其中 c 是分析物浓度(或样品物理特性)的 n ×1 向量, S 是包含在 p 不同波长测量的n×p 校准样品的 LIBS 信号的n×p 矩阵,b是p因此,未知回归系数的普通最小二乘(OLS)估计值可使残差的误差平方和最小化,由下式给出:**

这导致了

随着

S +是 S 的摩尔-彭罗斯伪逆。上标“t”的意思是“转置”。预测浓度 ĉ 由下式给出:


其中 H 为帽子矩阵 S ᵗ S 为协方差矩阵(维数 p × p ),为方阵。对上述方程的一个几何解释是:将【c】正交投影到 S- 空间,以 H 为正交投影矩阵,得到 ĉ 。
值得一提的是,逆校准方程代表一个由 n 个方程(i )组成的系统。 e 。校准样本的数量)与 p 未知数(i )之比。 e 。待估计的回归系数个数,与光谱波长个数重合)。在 LIBS,不同波长的紫外光、可见光和近红外光的数量一般都在数万的数量级上(使用任何一种中分辨率的宽带阶梯光栅光谱仪),这样 p > > n (也就是俗称的维度诅咒 ) 。
我们在这里面临的第一个问题是 MLR 应用于 LIBS 数据,因为求解方程需要比波长更多的校准样本。
制备大量校准样品通常不被认为是可行的选择,因为除了耗时之外还需要额外的成本。此外,即使我们能够添加更多的校准样本来求解该方程,协方差矩阵也必须是可逆的(即,非奇异矩阵)。另一方面,如果至少一个预测变量(1)与另一个变量成比例,(2)是其他变量的线性组合,(3)是常数,以及(4)变量多于观测值,则协方差矩阵是奇异的。
我们现在面临第二个问题,因为高维 LIBS 数据中的多重共线性使得协方差矩阵奇异。
方阵 S ᵗ S 不是满秩 p 的事实对几种机器学习方法的选择和适用性有着深远的影响。事实上,正是由于这个原因,化学计量学从业者更喜欢不需要协方差矩阵的逆矩阵的机器学习方法。这就是为什么,分解和收缩回归方法被提出来处理这样的病态问题。众所周知的线性方法有:
- 主成分回归 (PCR)
- 偏最小二乘回归 (PLSR)
- 岭回归 (RR)
- 连续回归 (CR)
- 主协变量回归 (PCovR)
在下文中,我将简要介绍这些回归方法,尤其是 PCR、PLSR 和 RR。
主成分回归
PCR 已经被提议作为一种技术,其将预测因子的数量减少到不相关成分(潜在变量)的较小集合,并且对这些成分执行最小二乘回归。在 PCR 中,从预测数据的主成分分析(PCA) 中获得成分作为主成分(PCs),即校准光谱 S 。通过这样做,当预测因子高度共线时,PCR 提供了一种替代方法。 S 被分解(或分解)为(对象)得分 T ( n × r )和(可变)载荷P(P×r)的乘积。 T 总结被分析样本之间的关系(相似或不同),而 P 总结变量(或波长)之间的关系(正相关或负相关)。 T 和 P 中的列数等于 S 的秩 r ,通常小于 n 或P

Illustration of the PCA decomposition of matrix S.
在下面的中,我们假设 T 和 P 只包含第一个 j 列,指的是保留的 PCs 数量,通常通过交叉验证来选择。通常, S 是通过减去它的平均值而以平均值为中心的。

逆校准公式可以改写为:

PC 分数 T 用作浓度的回归量,由 T = SP 决定。因此,通过应用逆校准方程,分析物浓度 c 与分数 T 相关,而不是原始数据矩阵 S. 大小为 j ×1 的回归系数向量由下式给出:

预测浓度 ĉ 由下式给出

其中 T +是 T 的 Moore-Penrose 伪逆。这解决了多重共线性问题,因为 T ᵗ T 的反演是微不足道的,因为这是一个对角矩阵,因为 T 的列是正交的。回想一下,对角矩阵的逆矩阵简单地由一个对角矩阵给出,它的元素是原始元素的逆矩阵。
偏最小二乘回归
已经提出 PLSR 作为改进 PCR 的方法,通过在潜在变量的估计中引入关于分析物浓度的信息。换句话说,PLSR 采用依赖于 c 的潜在变量,而 PCR 采用独立于 c 的潜在变量。如同在 PCR 中一样,通过将分析物浓度【c】与样本分数 T 相关联来开发反向校准模型。该数学关系在形式上与 PCR 的数学关系相同。差异在于估算 PLSR 得分矩阵的方式。可以看出,回归系数向量由下式给出:

可以写成

随着

R (或通常称为 W* )是权重矩阵,而 W 是载荷权重。与 PCR 相反, T 由 T = SR 决定。 PLSR 容易处理多重共线性,因为在 P ᵗ W 的反演中没有发现问题,因为 P 和 W 分别是正交和正交矩阵。在继续之前,我想在这里提一下正交偏最小二乘(OPLS) 回归,它是 PLSR 的修改版本,将是未来讨论的主题。
你听说过正交偏最小二乘法(OPLS)吗?
towardsdatascience.com](/an-overview-of-orthogonal-partial-least-squares-dc35da55bd94)
里脊回归
虽然 PLSR 是化学计量学中最流行的回归方法,但其他基于正则化最小二乘方法 的现代机器学习方法,如岭回归,也经常用于多变量校准。在 RR 中,要最小化的目标函数可以写成:

因此,

I 是单位矩阵,λ是通过交叉验证确定的正则化参数。关于 LIBS 数据,岭回归中最重要的方面是确定b 所需的矩阵求逆总是可能的,即使当协方差矩阵是奇异的并且不能求逆时。因此,RR 通过惩罚高方差系数来处理多重共线性。
连续回归
PLSR(Setc之间的最大协方差)形成了 MLR(Setc之间的最大相关)和 PCR(S中的最大方差)之间的折衷。这使得 Stone 和 Brooks 开发了一种方法,它是上述回归方法的延续,MLR、PLSR 和 PCR 是其特例。****
主协变量回归
PCovR 是一种连续回归,它结合了 PCR 和降秩回归。
更进一步:二阶多元分析
为了选择最合适的化学计量方法,必须考虑影响数据线性的任何物理性质或化学相互作用。正是由于这个原因,多路尤其是三路数据分析已经成为化学计量学中的一个重要课题(例如,基于低秩张量分解的众所周知的方法是 Tucker3、PARAFAC 和 N -PLS)。Tucker3 (T3)和并行因子分析(PARAFAC)也称为规范分解(CANDECOMP)或规范多点分解(CP),是矩阵奇异值分解(SVD)和双线性 PCA 的高阶推广,而 N -PLS 推广了双线性 PLS。如前所述,三向数据阵列(即二阶张量)是由 n 行(模式 1)、 p 列(模式 2)和 k 水平切片(通常称为管)定义的。
三向分析的一个简单方法是将三向数据数组展开成k×(n×p)、n×(k×p)和p×(n×k)两向表,然后可以根据回归分析这些表然而,对这些展开数据中的每一个进行分析都不太可能产生可靠的结果来真正揭示复杂的化学相互作用,此外还会受到过量输入和计算成本的影响。
从动手项目开始机器学习
黑进去
你如何从一开始就着手动手做项目

探索精神是生命中最珍贵的礼物,它非常热衷于学习,没有任何评判的眼光 ― 眼虫
谁能学?
如果你是一个机器学习爱好者,热衷于学习,那么开始你在数据科学和机器学习领域的旅程永远不会太迟。
由于数据的数量每天都呈指数级增长,并且有许多免费资源可用,这为我们研究数据并从中提取一些有意义的输出提供了一个很好的机会。
但是为什么入门这么难呢?
对于像我们这样的初学者来说,这是一项具有挑战性的任务。数据科学和机器学习是计算机爱好者中最热门的话题,因此互联网上有许多分散的资源,很容易使我们偏离道路。
在其核心,数据科学是一个研究领域,旨在使用科学的方法从数据中提取意义和见解
另一方面,机器学习是指数据科学家使用的一组技术,允许计算机从数据中学习。
如果你只是谷歌搜索任何关于数据科学和机器学习的主题,你可能会被大量的资源淹没,并且很难抓住一个路线图并遵循它。

Overwhelming resources
动机
因此,在这篇文章中,我将分享我是如何通过做一些实践项目来开始自己的旅程的,并为每个人提供一些神圣的学习资源。
我将分享你如何用一些 ML 算法和数据弄脏你的手。
入门指南
首先,每个人都可以通过谷歌搜索或简单的 YouTube 搜索来轻松学习入门部分。
如果你像我一样,更喜欢视觉+音频教程来更好地理解,那么你可以从 谷歌的机器学习食谱 开始。这是一个很好的资源,可以从一些例子开始。从那里你可以了解机器学习算法,并建立一些。
掌握概念
在这个过程中,你也可以查看其他资源,并很好地掌握机器学习的内容、方式和原因。这不是一蹴而就的事情,所以要学会有些耐心,优雅地感受数据科学的美好。
在与数据科学的每一次交流中,一个人得到的要比他寻求的多得多。
— 任何人
由于网上有很多可用的资源,一个人应该学会找到适合自己水平的丰富和优质的资源。
例如:如果你想学习一些算法在运行中的样子,尝试一些使用库的小教程,并开发一些小东西。之后,你可以通过简单的谷歌搜索来了解这个算法:“数学背后的 <算法>”。
这需要时间,但一旦你找到适合你的好资源,你就可以轻松地学习。
基础知识之后的动手项目
有了一些基础知识之后,就可以学习其他各种算法了。对你来说,从一开始就知道一切并不重要。你可以:
- 从一个开始,从不同的作者和资源中了解它。例如:尝试在谷歌搜索“线性回归初学者指南”,从你得到的第一个帖子开始。
- 从中创造一些东西;一些能激励你并让你感到快乐的事情。保持动力非常重要,这就是为什么阅读+在一些项目中运用这些知识非常重要。它帮助你看到你所学的实际输出。
我是怎么开始的?
在做了一些基础之后,我实现了一个梯度下降:这是 这个 项目中的一个学习算法,它帮助我理解了一个机器学习模型是如何学习的。在这里,你可以在任何地方绘制两个或更多的点,它试图学习(减少误差)并最好地拟合这些点之间的直线。

Linear Regression using Gradient Descent
很有趣,不是吗😁?你可以在 YouTube 上找到一个关于这个和其他一些算法的很棒的视频,通过编码训练 这里 。
如果你是一个爱看书的人,试着用 sci-kit learn 和 Tensorflow 阅读 手动机器学习。
最后的话
我真的希望你会喜欢这次旅行。总是从小事开始,不断成长。
一个人永远不应该停止学习这个领域的新东西。每天都有新的有趣的事情被研究,所以,继续学习,保持好奇!😄
迈向数据科学的第一步
数据科学之旅
kaggle 竞赛入门

Source: https://miro.medium.com/max/1053/1*gO6yZ3Z855MW26FuEiQjKw.png
Kaggle 是数据科学家的 AirBnB 这是他们度过夜晚和周末的地方。这是一个众包平台,旨在吸引、培养、培训和挑战来自世界各地的数据科学家,以解决数据科学、机器学习和预测分析问题。— 齐山-乌尔-哈桑·乌斯马尼
介绍
如果你想涉足数据科学,Kaggle 是最好的起点。如果你愿意,那么你可以阅读所有的什么和为什么在:https://www.kaggle.com/getting-started/44916。相信我,这是值得的。
让 Kaggle 如此棒的一个主要原因是,你可以找到数据集,而没有任何数据收集的麻烦,并且可以在云中编写和运行代码。你也可以使用谷歌的ColabJupyter 笔记本和他们的 GPU & TPU 来编写和运行你的代码。他们是自由的。
您可以轻松注册 平台 和 入门比赛 。我将通过参加 房价:高级回归技术 竞赛向大家展示如何预测房价。会很棒的,我们开始吧。
入门指南
让我们转到笔记本部分,在 Kaggle 上创建一个新的笔记本:

Notebook on Kaggle
启动笔记本后,你会看到一个漂亮的笔记本平台,左边有一个可用的数据集列表。

Kaggle’s Notebook
现在在开始写一些代码之前,我们先要了解一下机器学习流水线
ML 管道
虽然 Kaggle 通过提供数据集使我们变得容易,但处理和清洗数据并从中提取相关特征以及识别数据集中的异常值仍然具有挑战性。这整个过程被称为 数据角力 ,是特征工程的一部分。
让我们看看我们得到了什么类型的数据;我们将使用 Pandas:数据分析库来分析数据集。正如你在笔记本中看到的,Pandas 和 NumPy 库已经被导入。我们有两个数据集文件:train.csv 和 test.csv 另一个 sample_submission.csv 只是我们的提交应该是什么样子的示例。
代码部分
让我们看看训练和测试集的头部数据。

train and test set
如您所见,我们在训练集中有 81 列,在测试集中有 80 列;这是因为在测试集中没有给出标签列(SalePrice)* 。我们必须通过基于训练集训练模型来预测给定功能的销售价格。我们还应该拥有该领域的专业知识,这样我们就更容易知道自己在做什么。例如:对于这个项目,我们预测房子的销售价格,最重要的领域特征是:房子状况,位置如何,房子有什么特征等等。***
我们已经给出了一组特征和一个标签,我们预测的是给定特征的连续值,而不是分类值;所以这是一个明显的 回归问题而不是一个 分类问题 。**

Classification and Regression
现在我们要用训练集(train.csv)来玩一下,检查 SalePrice 数据的偏斜度。
正态分布的数据更容易分析

The skewness of the SalePrice data
正如我们可以看到的,SalePrice 是右(正)倾斜的,我们使用 matplotlib 和 seaborn 来可视化数据。
现在是数据转换的时候了。我们可以使用日志将数据转换成正态分布的数据:

log transform of the data
我们使用了一个以为基数 10 的对数,现在数据呈正态分布。要了解我们为什么使用日志来转换数据,请观看此视频。
Logarithms — Real-Life Applications
之后,我们可以检查我们的概率图:

Probability Plot
看起来不错,我们可以前进了。

checking the correlation
现在我们必须检查高度相关的数据。首先,我们提取所有数字数据类型的特征,并检查这些特征与销售价格的相关性。

前五个特征与销售价格高度相关;意思是——这些值是线性相关的,对销售价格有很大影响。
正如我们所看到的,GarageArea 也与销售价格高度相关。让我们来看一下数据:

visualizing garage area data
x 轴上高于 1200 的几个点是我们数据的异常值。这几个数据会影响模型,所以我们必须消除它们。

removing outlier data points
简单地说,我们通过获取小于 1200 的数据来删除它们。😄只是一个简单的方法。

converting string data to numerical
在我们的数据集中,街道要素有两个唯一值:Pave 和 Grvl。这也是一个有用的特性,但它是字符串数据类型;所以我们正在创建一个函数 mapStreet,将这些数据转换为 Pave 的 0 和 Grvl 的 1。

Sale price with respect to Sale Condition
现在是时候看看相同条件下房子的销售价格了。从上图中我们可以看到,部分销售条件是以高销售价格出售的,因此,为了方便起见,我们将部分销售条件分组为 1,其他销售条件分组为 0。

现在,为了使我们的数据集更好,我们可以删除或使用另一种方法来消除所有的 NAN 或 null 值。现在,我们只是删除它,只保留训练集和测试集上的数值。

现在,我们对 SalePrice 进行 log 转换,并将 train.csv 数据集拆分为两部分: y 用于标签,而 x 用于特征,并再次使用scikitlelarnstrain _ test _ split 方法将它们拆分为训练集和测试集。

终于!现在我们准备训练模型。

首先,我们从 Sci Kit learn 导入一个回归模型库,并给我们的训练集进行训练。您可以为该任务使用任何合适的模式。现在,我们使用线性回归。

我们可以通过在模型上调用一个 fit() 方法来训练我们的数据。它训练我们的模态,我们可以通过使用来检查分数。得分()法。**

目前,我们有大约 84%的准确率。没那么糟糕,但是你可以通过调整一些数据和更好的特征工程来提高精确度。

现在是时候准备一个提交文件,在 Kaggle 上提交了。

preparing a submission file
我们使用相同的模型来预测 test.csv 文件中特性的标签,并将所有带有 ID 的输出保存在 submission.csv 文件中。现在,您可以看到您的提交文件并将其提交到 Kaggle 的提交部分,并在公共排行榜中看到自己。

kaggle leaderboard of Hose Price prediction
最后的想法
Kaggle 竞赛是一个接触真实世界数据集的好地方。我希望你学到了一些东西。你可以继续探索 kaggle 上的其他数据集,并通过做令人兴奋的项目继续数据科学之旅。不断学习,保持好奇心。
回归分析的 f 检验

The F-distribution (Source: Wikimedia Commons under CC BY-SA 4.0)
如何使用它,如何解释它的结果
当用于回归分析时,f 检验允许您比较两个竞争回归模型“解释”因变量方差的能力。
f 检验主要用于方差分析和回归分析。我们将研究它在线性回归中的应用。
为什么在回归分析中使用 f 检验
在线性回归中,f 检验可用于回答以下问题:
- 你能通过使你的线性回归模型变得更复杂来改进它吗,例如,通过增加更多的线性回归变量?
- 如果您已经有了一个复杂的回归模型,您是否会更好地用仅截距模型(这是您可以构建的最简单的线性回归模型)来交换您的复杂模型?
第二个问题是第一个问题的特例。在这两种情况下,这两个模型被称为是嵌套。更简单的模型被称为限制模型。这就好像我们在限制它使用更少的回归变量。复杂模型被称为无限制模型。它包含了受限模型的所有变量和至少一个以上的变量。
受限模型被称为嵌套在非受限模型中。

Linear regression models: unrestricted, restricted and intercept-only restricted (Image by Author)
让我们通过一个真实世界的时间序列示例来探索 f 检验的使用。我们将首先构建一个仅拦截模型,即受限模型。
简单看一下仅拦截模型
以下时间序列显示了道琼斯工业平均指数在 3 个月内的每日收盘价。

Dow Jones Industrial Average closing price over a 3-month period (Image by Author)
假设我们希望为这个时间序列创建一个回归模型。但是我们不知道什么因素影响收盘价。我们也不想在数据集中假设任何通货膨胀、趋势或季节性。
在没有任何关于通货膨胀、趋势、季节性或存在解释变量的假设的情况下,我们能做的最好的事情是仅截距模型(有时被称为均值模型)。对于我们的时间序列示例,它采用以下形式:

Intercept-only model for the DJIA data set (Image by Author)
在仅截距模型中,所有预测都采用截距 Beta_0 的值。下图显示了实际时间序列背景下拟合的仅截距模型:

Actual and predicted Closing Price of DJIA using the mean model (Image by Author)
下面是产生上述结果的 Python 代码:
导入所有必需的包:
**import** pandas **as** pd
**import** numpy **as** np
**import** matplotlib.pyplot **as** plt
将数据集读入熊猫数据框:
df = pd.read_csv(**'djia.csv'**, header=0, infer_datetime_format=**True**, parse_dates=[0], index_col=[0])
计算样本平均值,并将所有预测值设置为该平均值:
mean = round(df[**'Closing Price'**].mean(),2)y_pred = np.full(len(df[**'Closing Price'**]), mean)
绘制实际值和预测值:
fig = plt.figure()fig.suptitle(**'DJIA Closing Price'**)actual, = plt.plot(df.index, df[**'Closing Price'**], **'go-'**, label=**'Actual Closing Price'**)predicted, = plt.plot(df.index, y_pred, **'ro-'**, label=**'Predicted Closing Price'**)plt.xlabel(**'Date'**)plt.ylabel(**'Closing Price (USD)'**)plt.legend(handles=[predicted, actual])plt.show()
我们能比均值模型做得更好吗?也许我们可以。让我们试着为这个时间序列开发一个竞争性的、无限制的模型。
竞争模型
假设通过一些分析,我们已经推断出 DJIA 收盘价的今天的值可能是明天收盘价的一个很好的预测值。
为了测试这一理论,我们将开发一个由单个回归变量组成的线性回归模型。该变量将是时间序列的时间滞后值。以下 Python 代码说明了回归过程:
导入所需的包:
**import** pandas **as** pd
**import** numpy **as** np
**import** statsmodels.api **as** sm
将数据集读入熊猫数据框:
df = pd.read_csv(**'djia.csv'**, header=0, infer_datetime_format=**True**, parse_dates=[0], index_col=[0])
添加延时栏:
df['CP_LAGGED'] = df['Closing Price'].shift(1)
以下是修改后的数据框的前几行。第一行包含一个 NaN,因为该值后面没有任何东西:
**Closing Price CP_LAGGED
Date** 2019-07-24 27269.97070 NaN
2019-07-25 27140.98047 27269.97070
2019-07-26 27192.44922 27140.98047
2019-07-29 27221.34961 27192.44922
2019-07-30 27198.01953 27221.34961
让我们去掉第一行,去掉 NaN:
df_lagged = df.drop(df.index[0])
接下来,让我们创建我们的训练和测试数据集:
split_index = round(len(df_lagged)*0.8)split_date = df_lagged.index[split_index]df_train = df_lagged.loc[df_lagged.index <= split_date].copy()df_test = df_lagged.loc[df_lagged.index > split_date].copy()X_train = df_train[**'CP_LAGGED'**].values*#Add a placeholder for the constant so that model computes an intercept value. The OLS regression equation will take the form: y = Beta_0 + Beta_1*x* X_train = sm.add_constant(X_train)y_train = df_train[**'Closing Price'**].valuesX_test = df_test[**'CP_LAGGED'**].values*#Add a placeholder for the constant* X_test = sm.add_constant(X_test)y_test = df_test[**'Closing Price'**].values
构建 OLS ( O 副 L 东 S 方)回归模型并将其拟合到时间序列数据集;
ols_model = sm.OLS(y_train,X_train)ols_results = ols_model.fit()
使用拟合的模型对训练和测试数据集进行预测:
y_pred_train = ols_results.predict(X_train)y_pred_test = ols_results.predict(X_test)
对照测试数据集绘制模型的性能:
fig = plt.figure()fig.suptitle('DJIA Closing Price')actual, = plt.plot(df_test.index, y_test, 'go-', label='Actual Closing Price')predicted, = plt.plot(df_test.index, y_pred_test, 'ro-', label='Predicted Closing Price')plt.xlabel('Date')plt.ylabel('Closing Price (USD)')plt.legend(handles=[predicted, actual])plt.show()
结果看起来像这样:

Predicted versus actual Closing Price of DJIA using the OLS regression model on the test data set (Image by Author)
乍一看,这个模型的性能看起来比我们从均值模型中得到的要好得多。但是仔细观察发现,在每一个时间步,模型只是学会了预测实际上是先前观察到的值偏移了一定的量。
但是,这种滞后变量模型可能在解释收盘价的变化量时在统计上比仅截距模型表现得更好。我们将使用 f 检验来确定这是否属实。
测试方法
我们的测试方法如下:
我们从两个假设开始:
- h0:零假设:滞后变量模型并不能比单纯截距模型更好地解释 DJIA 收盘价的变化。
- h1:替代假设:滞后变量模型比仅截距模型更好地解释了 DJIA 收盘价的变化(以统计显著的方式)。
我们将对两个模型使用 f 检验:仅截距模型和滞后变量模型,以确定是否:
- 在一定的误差范围内,可以拒绝零假设(并接受替代假设),或者
- 应该接受零假设。
使用 f 检验的分步过程
为了实现上述目标,我们将遵循以下步骤:
- 制定 f 检验的检验统计量,即 F 检验统计量。
- 在假设零假设为真的情况下,确定 F 统计量所代表的随机变量的 P 概率 D 密度 F 函数。
- 将这些值代入 F 统计的公式,并使用步骤 2 中找到的 P 概率 D 密度 F 函数计算相应的概率值。这是假设零假设为真时观察到 F 统计值的概率。
- 如果步骤 3 中发现的概率小于误差阈值,例如 0.05,则拒绝零假设,并接受置信水平为(1.0 —误差阈值)的替代假设,例如 1–0.05 = 0.95(即 95%置信水平)。否则,接受误差概率等于阈值误差的零假设,例如 0.05%或 5%。
让我们深入这些步骤。
步骤 1:开发测试统计的直觉
回想一下,f 检验衡量的是一个复杂模型在解释因变量方差的能力上比同一模型的一个简单版本好多少。
考虑两个回归模型 1 和 2:
- 设模型 1 有 k_1 参数。模型 2 有 k_2 参数。
- 让k _ 1<k _ 2
- 因此,模型 1 是模型 2 的简化版本。即模型 1 是受限模型,而模型 2 是非受限模型。模型 1 可以嵌套在模型 2 中。
- 设 RSS_1 和 RSS_2 为模型 1 和模型 2 拟合到同一数据集后的残差平方和。
- 设 n 为数据样本数。
有了上述定义,回归的 f 检验的检验统计量可以表示为如下比率:

Formula for the F-statistic when applied to regression analysis (Image by Author)
F 统计公式允许您计算因变量中有多少方差,简单模型与复杂模型相比不能解释,表示为复杂模型中未解释方差的分数。
在回归分析中,拟合模型的均方误差是无法解释的方差的极好度量。这解释了分子和分母中的 RSS 项。
使用相应的可用自由度来适当地缩放分子和分母。
F 统计量本身就是一个随机变量。
让我们确定 F-统计量服从哪一个 P 概率 D 强度 F 函数。
步骤 2:确定 F 统计的概率密度函数
请注意,测试统计的分子和分母都包含残差平方和。还记得在回归中,残差恰好是具有某种概率密度(或概率质量)函数的随机变量,即 PDF 或 PMF,取决于它是连续的还是离散的。在这种情况下,我们关心的是找到 F 统计的 PDF。
如果我们假设两个模型的残差 1)是独立的,2)是正态分布的,这恰好是二次回归的要求,那么可以看到 F 统计公式的分子和分母包含独立的正态分布随机变量的平方和。
可以证明 k 个独立的标准正态随机变量的平方和遵循卡方(k)分布的 PDF。

PDF of the Chi-Squared distribution (Source: Wikimedia Commons under CC BY 3.0)
因此,F 统计公式的分子和分母可以显示为各自服从两个卡方分布的缩放版本。
利用一点数学知识,还可以证明两个适当缩放的卡方分布随机变量的比率本身是一个随机变量,遵循 F 分布,其 PDF 如下所示。

The F-distribution (Source: Wikimedia Commons under CC BY-SA 4.0)
换句话说:
如果随机变量 X 具有带参数D1和D2的 f 分布的 PDF,即:

(Image by Author)
然后, X 可以表示为两个适当缩放的随机变量 X_1 和 X_2 的比值,每个随机变量都具有卡方分布的 PDF。即:

An F-distributed random variable X, expressed as the ratio of two scaled Chi-squared distributed random variables X_1 and X_2 (Image by Author)
现在回想一下,k1 和 k2 是之前 M1 和 M2 介绍的简单和复杂模型中的变量个数, n 是数据样本的个数。
将 D1 和 D2 替换如下:
d_1 = (k_2 — k_1 ),这是要比较的两个模型 M1 和 M2 的残差的自由度差,以及
D2 =(n—k2)是复模型 M2 残差的自由度,
通过这些替换,我们可以将 F 分布的公式改写如下:

Alternate formula for the F-distribution’s PDF (Image by Author)
让我们将上面的公式与 F 统计量的公式(复制如下)进行比较,其中我们知道分子和分母包含适当缩放的卡方分布的 pdf:

Formula for the F-test’s test statistic (Image by Author)
比较这两个公式,很明显:
- 分子中卡方分布的自由度' a' 为(k1 — k2)。
- 分母中卡方分布的自由度' b' 为(n — k2)。
- f 检验的检验统计量具有与 f 分布相同的 PDF。
换句话说,F 统计量遵循 F 分布。
步骤 3:计算 F 统计值
如果您使用 statsmodels 的 s OLS 估计器,这一步是一行操作。你需要做的就是打印 OLSResults.summary() 然后你会得到:
- F 统计量的值,
- 对应的“p”值,即在 F 分布的 PDF 中遇到该值的概率。
statsmodels 库将完成这两种计算的繁重工作。
print(ols_results.summary())
这会打印以下内容:

Output of OLSResults.summary() (Image by Author)
步骤 4:确定是否可以接受零假设
因为 OLSResults.summary() 在假设零假设为真的情况下打印出 F 统计的出现概率,所以我们只需要将这个概率与我们的阈值 alpha 值进行比较。在我们的例子中,返回的 p 值。summary() 是 4.84E-16 这是一个极小的数。远小于甚至α= 0.01。因此,在一个有效的零假设的假设下,136.7 的 F-统计量偶然出现的概率远小于 1%。
因此,我们拒绝零假设,接受另一个假设 h1,即复杂模型,即滞后变量模型,尽管有明显的缺陷,但它能够比仅截距模型更好地解释因变量收盘价的方差。
下面是本文中显示的完整 Python 源代码:
包含 DJIA 收盘价的数据文件在这里。
结论
- f 检验可用于回归分析,以确定在解释因变量的方差时,复杂模型是否优于同一模型的简单版本。
- f 检验的检验统计量是一个随机变量,其概率函数是假设零假设为真时的 f 分布。
- 回归的 f 检验的检验程序在结构上与其他重要的参数检验(如 t 检验)相同。
感谢阅读!我撰写关于数据科学的主题,重点是时间序列分析和预测。
如果你喜欢这篇文章,请关注我的Sachin Date获取关于时间序列分析和预测主题的提示、操作方法和编程建议。
从头开始用梯度下降拟合线性回归模型
实施梯度下降以找到简单线性回归的最佳权重。

我们都知道 sklearn 可以为我们试衣模特。但是当我们调用.fit()时,我们知道它实际上在做什么吗?请继续阅读,寻找答案。
今天我们将编写一组实现梯度下降以拟合线性回归模型的函数。然后,我们将我们的模型的重量与一个合适的 sklearn 模型的重量进行比较。
所需背景
拟合 =找出模型的偏差和系数,使误差最小化。
误差 =均方误差(MSE)。数据集内实际值和预测值之间的平方差的平均值。
简单线性回归 =基于一条线的方程的模型,“y=mx+b”。它将单个特征作为输入,应用偏差和系数,并预测 y。
此外,系数和偏差有时也统称为“权重”。
准备数据集
从 kaggle 下载加州住房数据集,并将其加载到数据框架中。
import pandas as pd
df = pd.read_csv('california-housing-dataset.csv')
df.head()

对于这个数据集,我们通常尝试使用所有其他特征来预测median_house_value。
但是在我们的例子中,我们关心的是拟合一个简单的线性回归(它只接受一个单一的输入特征),所以我们将选择median_income作为那个特征,忽略其他的。这不会创建可能的最佳模型,但会使梯度下降的实现更简单。
对数据集中的示例进行计数。
len(df)
#=> 20640
这是大量的数据。让我们把尺寸缩小 75%。
df = df.sample(frac=0.25)
len(df)
#=> 5160
那更容易管理。
收集我们的输入要素和标注。
X = df['median_income'].tolist()
y = df['median_house_value'].tolist()
用梯度下降拟合线性回归
根据维基百科,
梯度下降是一种寻找函数局部极小值的一阶迭代优化算法。为了使用梯度下降找到函数的局部最小值,在当前点采取与函数的梯度(或近似梯度)的负成比例的步骤。
我的翻译:
梯度下降使用误差函数的梯度来预测系数m和偏差b应该在哪个方向更新,以减少给定数据集的误差。
单独计算每个权重的梯度m和b。梯度是通过对所有例子的重量偏导数取平均值来计算的。
梯度的方向和陡度决定了权重的更新方向和更新量。后者也受一个超参数,即学习率的影响。
该算法反复迭代训练集并更新权重,直到代价函数最小。
履行
你可以在互联网上找到 MSE 函数的偏导数(如下),所以我们在这里不推导它。

我们将用下面的代码实现上面的代码,同时遍历数据集。
m_gradient += -(2/N) * x * (y - y_hat)
b_gradient += -(2/N) * (y - y_hat)
让我们写一个函数,它接受当前的权重、特征、标签和学习率,并输出更新后的权重。
def bias_coef_update(m, b, X, Y, learning_rate):
m_gradient = 0
b_gradient = 0
N = len(Y)
# iterate over examples
for idx in range(len(Y)):
x = X[idx]
y = Y[idx]
# predict y with current bias and coefficient
y_hat = (m * x) + b m_gradient += -(2/N) * x * (y - y_hat)
b_gradient += -(2/N) * (y - y_hat)
# use gradient with learning_rate to nudge bias and coefficient
new_coef = m - (m_gradient * learning_rate)
new_bias = b - (b_gradient * learning_rate)
return new_coef, new_bias
该函数的后半部分将梯度乘以学习率,并使用结果来更新当前权重。学习率越高,模型拟合得越快,代价是找到精确的局部最小值(注意:它实际上永远不会达到真正的最小值)。
编写另一个函数,迭代应用上述函数一个设定的历元数。这是一种不太复杂的方法(为了简单起见),比在某个预定的梯度陡度返回拟合的权重要简单。
def run(epoch_count=1000):
# store output to plot later
epochs = []
costs = []
m = 0
b = 0
learning_rate = 0.01 for i in range(epoch_count):
m, b = bias_coef_update(m, b, X, y, learning_rate)
print(m,b)
C = cost(b, m, x_y_pairs)
epochs.append(i)
costs.append(C)
return epochs, costs, m, bepochs, costs, m, b = run()
让我们输出拟合模型的最终成本和权重。
print(m)
print(b)
print(costs[-1])# I've rounded these myself so they're nicer to look at
#=> 46,804
#=> 19,963
#=> 7,261,908,362
并绘制出它是如何随着时代而改进的。
import matplotlib.pyplot as pltplt.xlabel('Epoch')
plt.ylabel('Error')
plt.suptitle('Cost by epoch')plt.plot(epochs,costs, linewidth=1)

酷毙了。我们可以看到它的大部分进步都是在前 100 个纪元内取得的。
用 Sklearn 拟合线性回归
现在,我们将在装有 sklearn 的模型中检查相同的值。
重塑要素。
import numpy as np
X_array = np.array(X).reshape(5160,1)
符合模型。
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_array,y)
检查重量和误差。
from sklearn.metrics import mean_squared_errorm = model.coef_[0]
b = model.intercept_mse = mean_squared_error(y_test, y_pred, sample_weight=None, multioutput='uniform_average')print(m)
print(b)
print(mse)# rounded
#=> 42,324
#=> 41,356
#=> 7,134,555,443
我们表现如何?
Sklearn 的调音以微弱优势超过我们,7,134,555,443对7,261,908,362,但我们非常接近。
我们的偏见也与 sklearn 发现的偏见大相径庭,41,356 VS 19,963。
不用深入研究每个时期后值是如何变化的(这很容易做到),我想我们可以通过降低学习率和增加时期的数量来改进我们的模型。
用于机器学习的 SciKit 库中的 Fit 与 Transform

我们已经在很多 SciKit 的库中看到了 fit()、transform()和 fit_transform()这样的方法。而且几乎所有的教程,包括我写过的,都只告诉你只用其中一种方法。这里出现的一个明显的问题是,这些方法是什么意思?你说的适合某样东西,改造某样东西是什么意思?transform()方法有一定的意义,它只是转换数据,但是 fit()呢?在这篇文章中,我们将试着理解两者之间的区别。
为了更好地理解这些方法的含义,我们将以 import 类为例,因为 import 类有这些方法。但是在我们开始之前,请记住,拟合像估算器这样的东西不同于拟合整个模型。
您使用一个估算器来处理数据集中的缺失数据。Imputer 为您提供了用列的平均值或甚至中值替换 NaNs 和空格的简单方法。但是在替换这些值之前,它必须计算将用于替换空白的值。如果您告诉估算者,您希望用该列中所有值的平均值替换该列中的所有变量,估算者必须首先计算平均值。计算该值的这一步称为 fit()方法。
接下来,transform()方法将使用新计算的值替换列中的 nan,并返回新的数据集。这很简单。fit_transform()方法将在内部完成这两项工作,并且通过只公开一个方法来简化我们的工作。但是有些情况下,您只想调用 fit()方法和 transform()方法。
在训练模型时,您将使用训练数据集。在该数据集上,您将使用估算器,计算值,并替换空白。但是,当您在测试数据集上拟合这个训练好的模型时,您不需要再次计算平均值或中值。您将使用与训练数据集相同的值。为此,您将对训练数据集使用 fit()方法,仅计算该值并将其保存在 input 中。然后,您将使用相同的 Inputer 对象对测试数据集调用 transform()方法。这样,为训练集计算的值(保存在对象内部)也将用于测试数据集。
简单地说,您可以对训练集使用 fit_transform()方法,因为您需要拟合和转换数据,并且您可以对训练数据集使用 fit()方法来获取值,然后用它来转换()测试数据。如果你有任何意见或不能理解,请告诉我。
Fitbit + Google AI + 5G =?

Brussels marathon runners — Photo by @mzemlickis
谷歌以 21 亿美元收购 Fitbit
the Verge 发表的一篇文章 2019 年 11 月 1 日宣布谷歌收购 FitBit:
谷歌刚刚宣布以 21 亿美元收购可穿戴公司 Fitbit 。在宣布这一消息的博客文章中,谷歌设备和服务 SVP 公司的里克·奥斯特洛表示,收购 Fitbit 是“一个在 Wear OS 上进行更多投资以及将谷歌可穿戴设备引入市场的机会。”
这可能是一项复杂的投资,但我看到了一些关于为何进行此次收购的不同观点:
- 谷歌希望投资可穿戴设备,但未能独自开发出足够可行的解决方案。因此,它收购 FitBit 是为了提供这种服务。
- 谷歌收购 FitBit 是为了专有技术。
- 谷歌收购 FitBit 是为了它的数据——不是为了增加广告,而是为了协助其在健康技术方面的投资(这构成了 Alphabet 投资组合的很大一部分)。
可能有更多的原因,然而,在保护消费者健康数据方面,消费者似乎有很强的意识。11 月 1 日直接来自 FitBit 的声明如下:
“消费者信任对 Fitbit 至关重要。强有力的隐私和安全指导方针从第一天起就是 Fitbit DNA 的一部分,这一点不会改变。Fitbit 将继续让用户控制他们的数据,并将对其收集的数据及其原因保持透明。该公司从不出售个人信息,Fitbit health and wellness 数据也不会用于谷歌广告。”
Rick Osterloh,谷歌设备与服务高级副总裁。表示他们将把 FitBit 在硬件方面的人才与软件和人工智能结合起来,打造更好的可穿戴设备。
与此同时,拥有谷歌的 Alphabet 正在扩大对 Verily 和 Calico 的投资。这是 Alphabet Inc .的季度报表中反复提到的两家公司。健康数据是一个热门话题。
尽管谷歌可能不会将 FitBit 数据用于广告,但它很可能会将这些聚合数据用于 Verily 和 Calico 的药物开发或健康洞察。是什么阻止了 Google/Alphabet 这样做?
我从 Alphabet 的投资者关系页面的 2019 年第三季度(Q3)页面中获取了一份描述。以下两节摘自 Q3。
印花布
Calico 是一家生命科学公司,其使命是利用先进技术来增加我们对控制寿命的生物学的了解。
【2014 年 9 月,艾伯维公司(AbbVie)和 Calico 签署了一项研发合作协议,旨在帮助两家公司发现、开发和向市场推出针对年龄相关疾病患者的新疗法,包括神经退行性疾病和癌症。在 2018 年第二季度,艾伯维和 Calico 修订了合作协议,导致总承诺量增加。截至 2019 年 9 月 30 日,艾伯维已根据协议出资 7.5 亿美元资助合作,并承诺额外出资 5 亿美元,将于 2019 年第四季度支付。截至 2019 年 9 月 30 日,Calico 已出资 5 亿美元,并承诺追加 7.5 亿美元。
Calico 利用其科学专业知识建立了世界级的研发机构,专注于药物发现和早期药物开发;艾伯维提供科学和临床开发支持及其商业专业知识,将新发现推向市场。两家公司平等分享本协议项下项目的成本和利润。艾伯维的出资在 Calico 的财务报表中被记录为负债,由于 Calico 产生了符合条件的研发费用,因此该负债被减少并反映为研发费用的减少。
截至 2019 年 9 月 30 日,我们已向 Calico 出资 4.8 亿美元,以换取 Calico 可兑换优先股单位,并承诺根据需要和特定条件额外出资 7.5 亿美元。
的确
Verily 是一家生命科学公司,其使命是利用世界健康数据,让人们享受更健康的生活。2018 年 12 月,Verily 获得了 10 亿美元投资轮的 9 亿美元现金。剩余的 1 亿美元于 2019 年第一季度收到。截至 2019 年 9 月 30 日,Verily 已从向外部投资者出售股权证券中获得总计 18 亿美元的收入。这些交易作为权益交易入账,没有确认任何收益或损失。
你对 21 亿美元有什么期待?
当你以难以想象的金额(对大多数人来说)收购一家公司时,预期投资回报并非不可思议。
另据Quartz:Alphabet报道称,来自谷歌广告业务的收入为 326 亿美元,比去年同期增长了约 16%
尽管 Alphabet 收益丰厚,但这是一个相当大的赌注。自 2015 年首次公开募股以来,Fitbit 的股票下跌了~ 80。
在收购之前的几年里,FitBit 的销售额和收入似乎一直在下降。
谷歌正在购买进入可穿戴设备市场的方式,并可能大幅增加 FitBit 的扩张以及该平台的可能性。

Retrieved from IDC on the 2nd of November
FitBit + 5G
这个赌注会有回报吗?有关系吗?这为理解人类提供了更多的力量,这无疑是谷歌成功的一部分。
亚马逊 Alexa 可能会和你说话,但随着 5G 的发展,在你身上安装一个可以做到这一点的设备,同时持续传输结果可能会达到更高的程度。

今年早些时候,有人预测 5G 将提振 FitBit 的股价:
“通过让可穿戴设备做更多的事情,让它们变得更加不可或缺,5G 将让可穿戴设备变得更加流行和有价值,提振这些公司的财务状况。"
5G 的到来还将使可穿戴设备能够更精确地监测个人的身体、情感和精神健康。
换句话说,谷歌的这项投资有望获得回报。
这里是#500daysofAI,您正在阅读的是第 152 条。500 天来,我每天都写一篇关于人工智能或与之相关的新文章。
使用 Python 中的随机优化来拟合神经网络

如何使用随机化优化来为机器学习模型(如神经网络和回归模型)找到最佳权重
Python 的 mlrose 包提供了实现一些最流行的随机化和搜索算法的功能,并将它们应用于一系列不同的优化问题领域。
在本教程中,我们将讨论如何使用 mlrose 来寻找机器学习模型的最佳权重,如神经网络和回归模型。即解决机器学习权重优化问题。
这是关于使用 mlrose 解决随机优化问题的三个系列教程中的第三个。第 1 部分可以在这里找到,第 2 部分可以在这里找到**。******
什么是机器学习权重优化问题?
对于许多不同的机器学习模型,拟合模型参数的过程包括找到最小化给定训练集的预先指定的损失函数的参数值。
这种模型的例子包括神经网络、线性回归模型和逻辑回归模型,并且这种模型的最佳模型权重通常使用诸如梯度下降的方法来找到。
然而,拟合机器学习模型的参数(或权重)的问题也可以被视为连续状态优化问题,其中损失函数充当适应度函数的角色,并且目标是最小化该函数。
通过以这种方式构建问题,我们可以使用任何适用于连续状态优化问题的随机优化算法来拟合模型参数。
用 mlrose 解决机器学习权重优化问题
mlrose 包含内置功能,用于解决三种机器学习模型的权重优化问题:(标准)神经网络、线性回归模型和逻辑回归模型。这是分别使用NeuralNetwork()、LinearRegression()和LogisticRegression()类完成的。
这些类中的每一个都包括一个fit方法,该方法实现了在前面的教程中为给定的训练集定义的解决优化问题的三个步骤。也就是说,
- 定义一个适应度函数对象。
- 定义一个优化问题对象。
- 选择并运行随机优化算法。
然而,在拟合机器学习模型时,找到最佳模型权重仅仅是达到目的的一种手段。
我们希望找到最佳的模型权重,以便我们可以使用拟合的模型尽可能准确地预测未来观测值的标签,而不是因为我们实际上对知道最佳权重值感兴趣。
因此,上面提到的类还包括一个predict方法,如果在fit方法之后调用,它将使用拟合的模型预测给定测试集的标签。
使用 mlrose 解决机器学习权重优化问题所涉及的步骤通常是:
- 初始化机器学习权重优化问题对象。
- 通过调用在步骤 1 中初始化的对象的
fit方法,找到给定训练集的最佳模型权重。 - 通过调用在步骤 1 中初始化的对象的
predict方法来预测测试集的标签。
为了拟合模型权重,用户可以选择使用随机爬山、模拟退火、遗传算法或梯度下降。
【在 mlrose 中,梯度下降算法仅可用于解决机器学习权重优化问题,主要用于基准测试,因为这是拟合神经网络和回归模型时最常用的算法之一。]
我们现在将通过一个示例来说明如何使用 mlrose 来将神经网络和回归模型拟合到给定的数据集。
在开始这个例子之前,您需要导入 mlrose 和 Numpy Python 包。
**import mlrose
import numpy as np**
示例:虹膜数据集
Iris 数据集是一个著名的多元分类数据集,由统计学家和生物学家罗纳德·费雪在 1936 年的一篇研究论文中首次提出。

Irises (1889) by Vincent Van Gogh
它包含鸢尾花的三个类(种)的 150 个观察值(每个类 50 个观察值),每个观察值提供所考虑的每个花的萼片长度、萼片宽度、花瓣长度和花瓣宽度(即特征值),以及类标签(即目标值)。
虹膜数据集包含在 Python 的 sklearn 包中。
数据集中第一个观测值的特征值和标注以及每个要素的最大值和最小值和唯一标注值如下所示:
**The feature values for Obs 0 are: [5.1 3.5 1.4 0.2]The feature names are: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']The target value for Obs 0 is: 0The target name for Obs 0 is: setosaThe minimum values of the four features are: [4.3 2\. 1\. 0.1]The maximum values of the four features are: [7.9 4.4 6.9 2.5]The unique target values are: [0 1 2]**
由此我们可以看出,虹膜数据集中的所有特征都是数字的,尽管范围不同,并且类别标签已经由整数表示。
在接下来的几节中,我们将展示如何使用 mlrose 将神经网络和逻辑回归模型拟合到该数据集,从而根据其特征值预测鸢尾花的种类。
数据预处理
在我们可以将任何类型的机器学习模型拟合到数据集之前,有必要将我们的数据处理成 mlrose 所期望的形式。
由 mlrose 支持的三个机器学习模型中的每一个都期望以 Numpy 数组的形式接收特征数据,每个观察值一行,并且只有数字特征(任何分类特征在传递到机器学习模型之前必须被一键编码)。
模型还期望接收以下目标值:数值列表(用于回归数据);0-1 指标值的列表(针对二元分类数据);或者作为独热编码标签的 Numpy 数组,每个观察值一行(用于多类分类数据)。
在虹膜数据集的例子中,我们所有的特征都是数字的,所以不需要一次性编码。然而,有必要对类别标签进行一次性编码。
为了与标准的机器学习实践保持一致,还需要将数据分成训练和测试子集,并且由于虹膜数据的范围从特征到特征变化很大,因此需要标准化我们的特征变量的值。
这些预处理步骤实现如下:
神经网络
一旦数据经过预处理,在 mlrose 中拟合神经网络只需遵循上面列出的步骤。
假设我们希望将神经网络分类器适配到具有一个包含 2 个节点的隐藏层和一个 ReLU 激活函数的 Iris 数据集( mlrose 支持 ReLU、identity、sigmoid 和 tanh 激活函数)。
对于此示例,我们将使用随机爬山算法来查找最佳权重,该算法最多迭代 1000 次,并且在每一步尝试 100 次来查找更好的权重集。
我们还将包括一个偏差项;使用步长(学习率)0.0001(查找当前权重集的邻居);并将我们的权重限制在-5 到 5 的范围内(以减少算法为了找到最佳权重而必须搜索的范围)。
该模型已初始化,并符合我们的预处理数据,如下所示:
模型拟合后,我们可以使用它来预测我们的训练集和测试集的标签,并使用这些预测来评估模型的训练和测试准确性。
**Training accuracy: 0.45Test accuracy: 0.533333333333**
在这种情况下,我们的模型实现了 45%的训练精度和 53.3%的测试精度。这些精度水平比随机选择标签要好,但仍有改进的空间。
我们可以通过调整初始化神经网络对象时设置的参数来提高模型的准确性。假设我们决定将优化算法改为梯度下降,但保持所有其他模型参数不变。
**Training accuracy: 0.625Test accuracy: 0.566666666667**
这导致训练准确率提高了 39%,达到 62.5%,但测试准确率的提高要小得多,达到 56.7%。
线性和逻辑回归模型
线性和逻辑回归模型是神经网络的特例。线性回归是没有隐藏层和身份激活函数的回归神经网络,而逻辑回归是没有隐藏层和 sigmoid 激活函数的分类神经网络。
因此,我们可以使用NeuralNetwork()类,通过适当地设置参数,将这些模型中的任何一个与我们的数据相匹配。
例如,假设我们希望使用随机爬山算法和上一节示例中设置的所有其他参数对虹膜数据进行逻辑回归拟合。我们可以这样初始化一个NeuralNetwork()对象:
然而,为了方便起见, mlrose 提供了LinearRegression()和LogisticRegression()包装类,它们简化了模型初始化。
因此,在 Iris 数据集示例中,我们可以如下初始化和拟合我们的逻辑回归模型:
**Training accuracy: 0.191666666667Test accuracy: 0.0666666666667**
该模型实现了 19.2%的训练精度和 6.7%的测试精度,这比我们通过随机选择值来预测标签的情况更差。
尽管如此,如前一节所述,我们可以通过调整初始化时设置的参数来提高模型的准确性。
假设我们把学习率提高到 0.01。
**Training accuracy: 0.683333333333Test accuracy: 0.7**
这极大地提高了训练和测试的准确性,现在训练的准确性达到了 68.3%,测试的准确性达到了 70%。
摘要
在本教程中,我们讨论了如何使用 mlrose 来寻找三种机器学习模型的最佳权重:神经网络、线性回归模型和逻辑回归模型。
将随机优化算法应用于机器学习权重优化问题肯定不是解决该问题的最常见方法。然而,它用来展示 mlrose 软件包和一般随机优化算法的多功能性。
要了解更多关于 mlrose 的信息,请访问这个包的 GitHub 资源库,这里有。********
Genevieve Hayes 博士是数据科学家、教育家和人工智能及分析专家,拥有Genevieve Hayes Consulting。你可以在LinkedIn或者Twitter上关注她。她还是* 价值驱动数据科学 的主持人,这是一个每月两次的播客,面向希望最大化其数据和数据团队价值的企业。*****
想要发掘企业数据的价值,但不知道从哪里开始?下载免费的数据科学项目发现指南。**
基于计数的数据拟合线性回归模型

Python 中的实践者指南和分步教程
本文的重点
我们将讨论三个主题:
- 拟合一个线性回归模型,特别是一个基于计数的数据的线性回归模型,是明智的吗?
- 如何在基于计数的真实数据集上配置和拟合 OLS 模型。
- 与主流回归模型相比,OLS 的表现如何,比如泊松和负二项分布?
什么是基于计数的数据?
基于计数的数据集包含一些事件的发生,如在英仙座流星雨事件中你发现流星的所有时间,或更实际的事情,如汽车在加油站停下的时间。
基于计数的数据集出现在各种领域,例如:
- 人类行为和心理:例如,一个时间序列的数据点(每小时捕获一次),包含我每小时查看电子邮件的次数。
- 制造和零售:每小时的时间序列,包含该小时制造或销售的小部件数量。
- 餐饮服务:一个日期的时间序列,其中每个数据点包含木瓜大王当天提供的热狗数量。
- 云基础设施:你的电子商务网站宕机的所有时间。
- 科学:自 1988 年以来发现系外行星的所有日期。
关于 counts 数据集,我们马上就能看到三件事:
- 数据是整数,即没有负数。
- 它经常是偏斜的,因此不是正态分布的,而且
- 它可以非常稀少。
这种数据集通常需要特殊的处理和专门的回归技术。

The Perseids radiating from the constellation Perseus (US National Park Service)
关于 OLS 的快速复习
OdL东 S 方( OLS )线性回归模型的工作原理是将一个 n 维线性函数拟合到 n 维数据上,使拟合值与实际值之间的平方和最小化。
由于数据的偏斜度和稀疏度,以及回归误差的异方差性,即误差的方差不是常数,而是相关计数变量的函数,基于 OLS 的线性回归模型在基于计数的数据上可能会失败。
此外,OLS 生成负预测和分数预测的倾向会导致数据计数的预测看起来很尴尬。
那么,为什么要为这些数据费心摆弄线性回归模型呢?我们很快就会看到,这样做有一些强有力的理由。
为计数数据建立使用 OLS 回归的案例
在之前的一组文章中,我展示了如何为基于计数的数据集训练以下两个常用的回归模型:
在这些文章中,我们根据布鲁克林大桥上骑自行车的人的真实数据来训练和测试这些模型:

Source: Bicycle Counts for East River Bridges (NYC OpenData) (Image by Author)
尽管泊松和 NB 模型表现得“如广告所说”:

Negative Binomial regression model’s predictions (Image by Author)
…我们还想知道以下内容:
- 是否有可能使用OrL东 S 方回归技术成功地对基于计数的数据进行建模?
- 如何判断对给定的基于计数的数据集使用 OLS 回归的适当性?如何在这样的数据集上测量 OLS 回归的性能?
- 在拟合优度和预测能力方面,不起眼的 OLS 模型如何与更复杂的模型相媲美,如 泊松 和 NB ?
我们将在本文中回答这些问题。
总的来说,我们会看到两件事:
- OLS 模型可以作为一个好的快速基线模型,同时为计数数据集建立更复杂的回归模型。
- 我们还将看到,对于此类数据集,OLS 模型中最具统计意义的回归参数也是泊松和 NB 模型中最具统计意义的回归参数。卡梅伦和特里维迪在他们被大量引用的著作《计数数据回归分析》中对这一发现背后的数学原理做了精彩的解释。
在整篇文章中,我们将使用纽约自行车数据集,以便我们可以对 OLS 与泊松和 NB 车型进行比较。
文章的布局
- 我们将对自行车统计数据集进行 E 探索 D ata A 分析( EDA ),以判断 OLS 的适用性,并查看是否需要进行任何数据转换。
- 使用 Python 、 Pandas 和 statsmodels ,我们将在这个数据集上构建、训练和测试一个 OLS 模型。
- 我们将比较 OLS 回归模型与泊松和 NB 模型的性能。
建议(但非必需)阅读
骑自行车者计数数据集的探索性数据分析
下表包含了骑自行车的人在纽约市各种桥梁上行驶的次数。从 2017 年 4 月 1 日到 2017 年 10 月 31 日每天测量计数。我们会集中在布鲁克林大桥上。

Source: Bicycle Counts for East River Bridges (NYC OpenData) (Image by Author)
I have cut out the Brooklyn Bridge counts into a separate data set. This data set is available for download [**over here**](https://gist.github.com/sachinsdate/c17931a3f000492c1c42cf78bf4ce9fe).
以下是该数据集的前几行:

(Image by Author)
让我们将数据加载到一个 pandas 数据框中,并绘制 BB_COUNT 变量:
import pandas as pdfrom matplotlib import pyplot as pltdf = pd.read_csv('nyc_bb_bicyclist_counts.csv', header=0, infer_datetime_format=True, parse_dates=[0], index_col=[0])fig = plt.figure()fig.suptitle('Bicyclist counts on the Brooklyn bridge')plt.xlabel('Date')plt.ylabel('Count')actual, = plt.plot(df.index, df['BB_COUNT'], 'go-', label='Count of bicyclists')plt.legend(handles=[actual])plt.show()
时间序列图如下所示:

Time series plot of the number of bicyclists on the Brooklyn bridge (Image by Author)
让我们也创建一个直方图,看看这个数据集是如何正态分布的:
plt.hist(df['BB_COUNT'], bins=20)plt.show()

Histogram of counts (Image by Author)
不出所料,骑自行车的人的统计数据是有偏差的。让我们打印出一些描述性的统计数据:平均值、中位数、偏斜度和峰度。
对于偏度和峰度,我们将对 BB_COUNT 变量运行 Jarque-Bera 正态性测试 。
相关帖子: 使用偏度和峰度测试正态性
JB 检验不仅会给出偏度和峰度,还会检验偏离正态性是否具有统计显著性。
**from** statsmodels.stats.stattools **import** jarque_bera **as** jb**from** statsmodels.stats.stattools **import** omni_normtest **as** omb**from** statsmodels.compat **import** lzipprint('**Mean**='+str(round(df['BB_COUNT'].**mean**(), 2)))print('**Median**='+str(round(df['BB_COUNT'].**median**(), 2)))name = ['Jarque-Bera', 'Chi^2 two-tail probability', 'Skewness', 'Kurtosis']test_results = jb(df['**BB_COUNT**'])lzip(name, test_results)
这会产生以下输出:
Mean=2680.04Median=2857.0[('**Jarque-Bera**', 30.572383357990116), ('**Chi^2 two-tail probability**', 2.2976893142533207e-07), ('**Skewness**', -0.8746427375174667), ('**Kurtosis**', 3.6071892903122973)]
平均值和中间值没有很大的不同,这表明分布不是很不平衡。
正如所料,测试报告的偏斜度是负的,而且很小。相比之下,正态分布的偏斜度为零。测试报告的过度峰度(=峰度-3.0)也非常小。相比之下,正态分布的峰度为 3.0,而过度峰度为零。
另一方面,Jarque-Bera 测试的 t 统计值 30.57 将其置于卡方(2)的 PDF 的正态性拒绝区(见下图)。

Acceptance and rejection zones for the Null hypothesis in the Chi-squared(2) PDF for two-tailed α=0.05 (Image by Author)
因此我们拒绝数据正态分布的零假设 h0,而接受数据不正态分布。
在这一点上,我们可以接受少量的偏斜,并在其上拟合一个 OLS 模型,或者我们可以尝试修复偏斜。后者可以通过对因变量 BB_COUNT 进行对数变换或平方根变换来尝试。
我们将尝试这两种转换方法,看看是否会产生预期的结果。
我们将向数据框添加两个新列:一个 LOG(BB_COUNT)列和一个 SQRT(BB_COUNT):
import numpy as np#Add a column to the Data Frame that contains log(BB_COUNT):
df['LOG_BB_COUNT'] = np.log(df['BB_COUNT'])#All another column containing sqrt(BB_COUNT)
df['SQRT_BB_COUNT'] = np.sqrt(df['BB_COUNT'])
让我们对 LOG 和 SQRT 列再次运行 Jarque-Bera:
name = ['**Jarque-Bera**', '**Chi^2 two-tail probability**', '**Skewness**', '**Kurtosis**']test_results = omb(df['LOG_BB_COUNT'])lzip(name, test_results)test_results = omb(df['SQRT_BB_COUNT'])lzip(name, test_results)
这会打印出以下内容:
[('**Jarque-Bera**', 888.0352308852536), ('**Chi^2 two-tail probability**', 1.4641977846577634e-193), ('**Skewness**', -2.5832340081063463), ('**Kurtosis**', 11.538169851408346)][('**Jarque-Bera**', 135.86010800784334), ('**Chi^2 two-tail probability**', 3.15030346376236e-30), ('**Skewness**', -1.5270456576564704), ('**Kurtosis**', 5.430879236979477)]
让我们将这些发现与未转换的 BB_COUNT 的正态性度量进行比较:
[('**Jarque-Bera**', 30.572383357990116), ('**Chi^2 two-tail probability**', 2.2976893142533207e-07), ('**Skewness**', -0.8746427375174667), ('**Kurtosis**', 3.6071892903122973)]

(Image by Author)
似乎不太顺利,是吧?!转换 BB_COUNT 不仅没有修复偏斜,实际上使数据更加偏斜!从这次转型事故中可以学到一些东西:
人们应该抵制诱惑,不仔细检查数据转换对数据的影响,就盲目接受任何数据转换的结果。
我们又回到起点了吗?
事实上,恰恰相反。我们从对数据进行 ed a 中获益良多。我们最大的收获是认识到我们不应该再花更多的时间去尝试修复数据集中存在的少量偏度和峰度。
此外,因变量的正态性不是进行 OLS 回归的先决条件。过度偏离因变量的正态性只会使 OLS 估计量有可能(但不一定)产生有偏拟合。
因此,让我们承担接受偏态的计算风险,并在原始的、未转换的骑自行车者计数数据上拟合 OLS 回归模型。
我们的回归目标和回归策略
我们的回归目标是预测任何一天有多少骑自行车的人穿过布鲁克林大桥。
我们的回归策略将是建立和训练一个 OLS 回归模型,其中:
- ****回归变量将是 X 矩阵中的变量(见下图),加上一些额外的派生变量,我们将把它们添加到 X (我们将在几秒钟后看到如何做)。
- 回归的因变量 y 将是骑自行车的人数(我们数据集中的 BB_COUNT 列)。

The slice of the bicyclist counts data set showing the regression variables matrix X and the vector of counts y (Image by Author)
一旦模型经过训练,我们将在维持测试数据集上测试其性能,该数据集是模型在训练期间未显示的数据。
使用 Python、Pandas 和 Statsmodels 将 OLS 回归模型拟合到 counts 数据集的分步指南
我们将从导入所有需要的包开始。
**import** pandas as pd**from** patsy **import** dmatrices**import** numpy **as** np**import** statsmodels.api **as** sm**import** statsmodels.formula.api **as** smf**import** matplotlib.pyplot **as** plt
让我们将数据集读入熊猫数据帧:
df = pd.read_csv('nyc_bb_bicyclist_counts.csv', header=0, infer_datetime_format=True, parse_dates=[0], index_col=[0])
我们将向 X 矩阵添加一些派生的回归变量。
ds = df.index.to_series()df['MONTH'] = ds.dt.monthdf['DAY_OF_WEEK'] = ds.dt.dayofweekdf['DAY'] = ds.dt.day
以下是熊猫数据框的前几行,左侧显示了回归变量和 BB_COUNT 因变量:

Pandas Data Frame (Image by Author)
让我们创建训练和测试数据集。
mask = np.random.rand(len(df)) < 0.8df_train = df[mask]df_test = df[~mask]print('Training data set length='+str(len(df_train)))print('Testing data set length='+str(len(df_test)))
在 patsy 符号中设置回归表达式。我们告诉 patsy,BB_COUNT 是我们的因变量,它取决于回归变量:DAY、DAY_OF_WEEK、MONTH、HIGH_T、LOW_T 和 PRECIP:
expr = 'BB_COUNT ~ DAY + DAY_OF_WEEK + MONTH + HIGH_T + LOW_T + PRECIP'
为训练和测试数据集设置 X 和 y 矩阵。patsy 让这变得非常简单:
y_train, X_train = dmatrices(expr, df_train, return_type='dataframe')y_test, X_test = dmatrices(expr, df_test, return_type='dataframe')
配置并适应 OLSR 模型。在 statsmodels 中,这是一行代码:
olsr_results = smf.ols(expr, df_train).fit()
打印回归输出:
print(olsr_results.summary())
它打印出以下内容:

Summary of the fitted OLSR model (Image by Author)
我们稍后将检查回归输出的单个元素。同时,让我们根据测试数据生成模型的预测:
olsr_predictions = olsr_results.get_prediction(X_test)
让我们把它们打印出来:
predictions_summary_frame = olsr_predictions.summary_frame()print(predictions_summary_frame)
以下是最初的几个预测计数:

Predicted values (Image by Author)
注意预测计数不是整数,这是将线性回归模型应用于基于计数的数据的典型缺点。在这种情况下,我们可以通过向上舍入或向下舍入预测来解决这个缺点。
让我们也画出预测值和实际值。
predicted_counts=predictions_summary_frame['mean']actual_counts = y_test['BB_COUNT']fig = plt.figure()fig.suptitle('Predicted versus actual bicyclist counts on the Brooklyn bridge')predicted, = plt.plot(X_test.index, predicted_counts, 'go-', label='Predicted counts')actual, = plt.plot(X_test.index, actual_counts, 'ro-', label='Actual counts')plt.legend(handles=[predicted, actual])plt.show()

The plot of actual versus predicted counts (Image by Author)
注意一个预测的骑自行车的人数是负的。当应用于基于计数的数据时,这是线性回归模型的另一个经典缺点。在某些情况下,我们可以通过简单地将负值四舍五入为零来解决这个缺点。
解读 OLSR 模型的结果
抛开产生分数计数和负数计数的倾向,让我们客观地评估一下我们的 OLS 回归模型在自行车计数数据集上的表现。
检验回归参数的显著性:

Parameter significance using the two-tailed t-test (Image by Author)
**t 检验显示所有回归参数分别和统计显著。在双尾 t 检验中,每个参数的 t 统计的绝对值在 95%显著性水平上大于指定的阈值 t 值。
接下来,让我们检查 F 统计量:

The F-test for regression analysis (Image by Author)
F-stat 的 p 值 3.26E-26 远小于具有 6 个自由度的卡方分布的 0.025 误差阈值,表明所有回归参数都共同显著。
相关帖子: 回归分析的 f 检验
最重要的 R 平方值:

R-squared (Image by Author)
Adjusted-R 是对 R 的一个小修正,以说明在进行估算时损失了 7 个自由度,即 6 个回归变量+截距)。
0.530 的调整后 R 告诉我们,OLSR 模型能够解释骑自行车者数量因变量中超过 50%的方差。
顺便说一句,如果我们对模型本身的选择是错误的,那么 R 的值就没有意义了!但到目前为止,我们还没有看到 OLS 出现这种情况的任何迹象。
让我们再检查一下 OLS 回归模型的两件事:
- ****残差的正态性:如果我们发现模型的残差不是正态分布的,我们就不能相信模型预测的 F 统计量或置信区间。
- ****残差间的自相关:时间序列中的自相关是指时间序列中的值与同一时间序列中的先前值相关。如果我们在模型的残差中发现显著的自相关,这表明我们遗漏了重要的回归变量,导致相关计数变量中的一些“信号”泄漏到残差中。这是一个严重指定的 OLSR 模型的迹象。
为了检查回归的残差是否是正态分布的,并且它们不是自相关的,我们需要查看 OLS 模型摘要的页脚部分。页脚部分显示了回归残差的正态性检验和自相关性检验的结果。

(Image by Author)
残差的正态性:
Jarque-Bera 的残差正态性检验统计量为 11.917,将其置于卡方(2) PDF 的正态性拒绝区域。综合 K 平方检验的统计值 7.347 刚好跨越了正态性拒绝区的边界。见下图:

Acceptance and rejection zones for the Null hypothesis in the Chi-squared(2) PDF for two-tailed α=0.05 (Image by Author)
JB 和正态性综合检验告诉我们的是 OLSR 残差不是正态分布的。同时,残差的极低偏斜度(0.147)以及综合统计量处于卡方(2) PDF 的正态/非正态边界意味着误差接近正态分布。残余误差的直方图直观地证实了这一判断:
plt.hist(olsr_results.resid, bins=20)plt.show()

Histogram of residual errors (Image by Author)
请注意误差在零均值附近或多或少呈正态分布。
这是残差的 Q-Q 图,在很大程度上看起来也是线性的:
fig = sm.qqplot(olsr_results.resid)plt.show()

Q-Q plot of the residual errors of OLSR (Image by Author)
总之,我们的结论是,OLS 回归模型的残差大体上正态分布在一个零均值附近。
残差的自相关:
Durbin-Watson 检验让我们了解了残差是否是自相关的。显著小于 2 的 DW 测试值表示显著的自相关。在这种情况下,DW 检验的值是 1.772,这意味着在回归的残差中没有强自相关的证据。

(Image by Author)
我们还可以通过绘制残差的 ACF 图来验证自相关的缺失:
**from** statsmodels.graphics.tsaplots **import** plot_acfplot_acf(olsr_results.resid, title='ACF of residual errors')plt.show()

Auto-correlation plot of residual errors of the OLSR model (Image by Author)
正如所料,ACF 图显示残差之间没有显著的自相关
总而言之,OLS 模型似乎已经最佳地拟合了数据,没有系统性的信息泄漏到模型的误差中。
最后,让我们比较一下 OLSR 模型与泊松和 NB 回归模型的性能,这两个模型在我之前的两篇关于基于计数的数据的回归模型的文章中都有涉及。
OLSR 与泊松和负二项模型的比较
如果我们对所有三个模型的回归参数——OLS、泊松、负二项式——在相同的自行车计数数据集上进行并排比较,我们会看到OLSR 发现的最重要的参数(从参数的 t 统计的绝对值来看)也是其他两个模型确定的最重要的参数。
见下图。我根据 T 值注释了前三个重要参数:PRECIP、HIGH_T 和 LOW_T。它们在所有三个模型中都是相同的:

A comparison of parameter significance among OLS, Poisson and NB models (Image by Author)
OLSR、泊松和 NB 模型的拟合优度比较:
让我们来看看 OLSR 模型与泊松和 NB 模型相比,对骑自行车者计数数据的拟合程度如何。
让我们再一次将 OLS 结果与泊松和 NB 回归结果放在一起。(你可能想参考我关于泊松和 NB 回归模型的文章,了解如何使用 statsmodels 得到泊松和 NB 回归模型的结果)。

(Image by Author)
使用最大对数似然作为拟合优度的卷尺,我们看到:
- ****NB 车型排名第一最高最大 LL-1309.6,
- ****OLSR 车型以-1339.4 的最高 LL 值意外获得第二名。
- ****泊松回归模型以-12616.0 的成绩远远排在第三位。

Goodness-of-fit rankings as per Maximum Log-Likelihood (Image by Author)
这就完成了我们对符合骑自行车者计数数据集的 OLSR 模型的分析,以及它与泊松和负二项式回归模型的性能比较。
以下是本文中使用的完整 Python 源代码:
四个关键要点
- 尽管计数数据存在偏斜,且 OLSR 模型有产生负数和小数计数的倾向,对于基于计数的数据集而言,OLSR 模型可能是一个可行的回归模型。
- 仅仅因为 OLSR 模型在自行车计数数据集上表现良好,OLSR 模型并不总是在所有计数数据集上都表现得很好。OLS 回归在计数数据上的固有弱点必然会在更具“挑战性”的数据集上显现出来。
- 然而,OLSR 模型可以作为基于计数的数据的合理基线模型,它可以为我们提供一种方法,快速测试哪些模型参数对基于计数的数据建模最具统计意义。
- OLSR 模型也给了我们一种客观地比较好(或坏)多少的方法。)是一种复杂的基于计数的模型,如泊松模型、负二项式模型、梯度推进决策树模型或基于计数数据集的递归神经网络模型,有时会产生令人惊讶的结果。
感谢阅读!我撰写关于数据科学的主题,重点是时间序列分析和预测。
如果你喜欢这篇文章,请关注我的Sachin Date获取关于如何使用 Python 进行时间序列分析和预测的技巧、操作方法和编程建议。**
伟大 R 程序员的五种行为
有这么多的人加入数据科学的行列,你如何从其他人中辨别出一个伟大的 R 程序员呢?
在新的数据科学时代,每个人都在赶时髦。招聘人员收到的简历和履历表中包含了所有的术语:R、Python、Javascript 等等。在许多情况下,人们会在大学一周的课程或几次数据营课程的基础上,将一项技能写进简历。
确定某人在特定编程语言方面的技能的最好方法是给他们设置一个任务,然后看看他们做得如何。在这些事情上,你不能只相信口口相传,你真的不希望你的团队或组织中有人在你需要的关键技能上达不到要求。
设置实用的编码练习有两个好处。首先,您可以测试个人是否知道如何处理和解决手头的问题,并能在工作中独立行事。第二,您可以识别编码行为,这些行为表明编码人员技术高超。
这里有五种特别针对 R 程序员的行为,我相信其中许多行为适用于其他各种编程语言:
评论
不管是什么语言,伟大的编码者都经常做好注释。这表明了对再现性的关注,也可能表明他们从经验中知道评论的重要性。
在哪里以及多久评论一次是一个判断的问题,取决于任务的复杂性,但是伟大的程序员会在一个合理详细的层次上评论。例如:
# load libraries
library(dplyr)#' Function to search starwars names by first letter of name
#'
#' [@param](http://twitter.com/param) x Character value to search as first letter
#'
#' [@return](http://twitter.com/return) a vector of namesfirst_letter_char_search <- function(x) {
starwars %>%
dplyr::mutate(first_letter = substr(name, 1, 1)) %>%
dplyr::filter(first_letter == x) %>%
dplyr::select(name)
}
格式化
代码格式对于轻松阅读和理解工作非常重要。正确格式化的代码表明编码者花费了时间和精力来确保他或她的代码尽可能的可读,这又影响了可再现性和未来的合作。
良好的格式还表明编码者知道他们语言的格式约定,这通常表明他们在这方面有相当丰富的经验。您将在上面的代码块中看到一些典型的 R 格式约定(尽管根据您查看本文的设备不同,可能不会像我希望的那样呈现)。间距和缩进是强格式最基本的方面。
命名空间
这取决于所使用的语言,但是在像 R 这样的语言中,命名空间显示了对编码者工作环境的关注。明确地说,命名空间意味着在代码中同时调用和函数及其包,而不仅仅是函数。比如调用lubridate::ymd()而不仅仅是ymd()。同样,在上面的代码块中,您将看到适当的函数命名空间。
命名空间有两个优点。首先,它防止了来自不同包的两个函数同名的问题。如果你加载两个包,并且它们都有一个共同的函数名,R 将总是使用最近加载的包中的函数,除非你命名你的函数。其次,如果另一个用户正在查看您的代码片段,它可以帮助他们确定需要安装哪些包。
码效率
伟大的程序员会关心他们代码和过程的效率。这可以采取多种形式:
- 重复的代码可以被抽象,以便单个功能可以执行重复的工作。更多信息见我的文章这里。
- 具有高计算强度的代码可以被重构以减少不必要的重复或低效的操作。
- 处理数据库对象的代码可以尽可能地操纵服务器上的数据,以避免将大量数据带到本地机器上——参见我的另一篇文章这里。
这可能是识别优秀程序员的一个重要区别。有人提交给我的代码荒谬且毫无意义地低效——用几百行代码完成了几行代码就能完成的事情。同样,我也见过在抽象和效率方面如此优雅的代码,以至于你不得不坐下来欣赏这位大师的作品。
打扫
伟大的程序员会自我清理。他们不会将可能与下一个任务冲突或导致安全漏洞的东西留在环境中。我见过的最常见的问题是,人们在获取了他们需要的东西后,却忽略了断开与数据库的连接。我喜欢看到漂亮整洁的代码块,在那里编码员已经打了招呼,说了再见。有礼貌是很好的,即使是数据库。
如果你正在寻找或雇佣程序员,考虑如何设置实践练习来测试他们的技能,看看你是否能发现一些积极的行为。我真的很推荐。
最初我是一名纯粹的数学家,后来我成为了一名心理计量学家和数据科学家。我热衷于将所有这些学科的严谨性应用到复杂的人的问题上。我也是一个编码极客和日本 RPG 的超级粉丝。在LinkedIn或Twitter上找我。

数据科学的五个命令行工具
从终端上你可以做比你想象的更多的数据科学

Photo by Toa Heftiba on Unsplash
数据科学最令人沮丧的一个方面是工作时不断在不同工具之间切换。您可以在 Jupyter 笔记本中编辑一些代码,必须在命令行上安装一个新工具,并且可能在 IDE 中编辑一个函数,所有这些都是在处理相同的任务时进行的。有时候,在同一个软件中找到做更多事情的方法是很好的。
在下面的帖子中,我将列出一些我发现的在命令行上进行数据科学研究的最佳工具。事实证明,通过简单的终端命令可以完成的任务比我最初想象的要多,我想在这里分享一些。
卷曲
这是一个通过各种协议(包括 HTTP)从任何服务器获取数据的有用工具。
我将给出几个获取公开可用数据集的用例。 UCI 机器学习资源库是获取机器学习项目数据集的绝佳资源。我将使用一个简单的 curl 命令下载一个来自台湾新竹市输血中心的数据集。如果我们简单地运行curl [url],在我们的例子中是curl [https://archive.ics.uci.edu/ml/machine-learning-databases/blood-transfusion/transfusion.data](https://archive.ics.uci.edu/ml/machine-learning-databases/blood-transfusion/transfusion.data),这将把数据打印到终端。
添加一些额外的参数将使用指定的文件名下载和保存数据。该文件现在可以在您当前的工作目录中找到。
curl -o data_dl.csv https://archive.ics.uci.edu/ml/machine-learning-databases/blood-transfusion/transfusion.data
为数据科学项目获取数据的另一种常见方法是通过 API。这个工具还支持与 API 交互的GET和POST请求。运行以下命令将从 OpenWeatherMap API 获得一条记录,并保存为一个名为weather.json的 JSON 文件。关于 cURL 的更全面的教程,请看这篇优秀的文章。
curl -o weather.json -X GET \'https://api.openweathermap.org/data/2.5/weather?lat=37.3565982&lon=-121.9689848&units=imperial&appid=fd4698c940c6d1da602a70ac34f0b147' \-H 'Postman-Token: dcf3c17f-ef3f-4711-85e1-c2d928e1ea1a' \-H 'cache-control: no-cache'
csvkit
csvkit 是一组用于处理 CSV 文件的命令行工具。它可以执行的任务可以分为三个部分:输入、处理和输出。让我们来看一个如何使用它的快速真实示例。
首先让我们使用 pip install 安装工具。
pip install csvkit
出于这个例子的目的,我将使用我通过上面的 curl 命令从 UCI 机器学习库中创建的同一个 CSV 文件。
首先,让我们使用csvclean来确保我们的 CSV 文件格式正确。该函数将自动修复常见的 CSV 错误并删除任何坏行。这个函数的一个有用的方面是,它自动输出一个新的干净的 CSV 文件版本,以便保留原始数据。新文件总是有下面的命名约定[filename]_out.csv。如果你希望原始文件被覆盖,你可以添加可选的-n参数。
csvclean data_dl.csv
在我的示例文件中,没有错误,但这是一种非常有用的方法,可以在处理 CSV 文件时进一步减少错误。
现在,假设我们想要快速检查文件。我们可以用csvcut和csvgrep来做到这一点。
首先让我们打印出列名。
csvcut -n data_dl_out.csv | cut -c6-Recency (months)Frequency (times)Monetary (c.c. blood)Time (months)whether he/she donated blood in March 2007
现在让我们确定目标列whether he/she donated blood in March 2007中有多少个类。
csvcut -c "whether he/she donated blood in March 2007" data_dl_out.csv | sed 1d | sort | uniq01
csvgrep函数允许您基于正则表达式匹配过滤 CSV 文件。
让我们使用这个函数只提取与类 1 匹配的行。
csvgrep -c "whether he/she donated blood in March 2007" -m 1 data_dl_out.csv
您还可以使用csvkit功能执行简单的数据分析。
简单地运行csvstat data_dl_out.csv将整个文件的描述性统计数据打印到命令行。您也可以使用可选命令只请求一个统计的结果。
csvstat --mean data_dl_out.csv1\. a: 373.52\. Recency (months): 9.5073\. Frequency (times): 5.5154\. Monetary (c.c. blood): 1,378.6765\. Time (months): 34.2826\. whether he/she donated blood in March 2007: None
IPython
IPython 允许从 shell 访问增强的交互式 Python。本质上,这意味着您可以从命令行完成在 Jupyter 笔记本中可以完成的大部分事情。
如果您的终端中还没有它,您可以按照这些步骤来安装它。
要启动 IPython,只需在命令行输入ipython。您现在处于交互式 shell 中。在这里,您可以导入 python 安装的库,我发现这个工具对于在命令行上进行快速数据分析非常有用。
让我们在已经使用的数据集上执行一些基本任务。首先,我将导入 pandas,读入文件并检查前几行数据。
import pandas as pddata = pd.read_csv('data_dl_out.csv')data.head()
文件列名相当长,所以接下来,我将使用 pandas 对它们进行重命名,然后将结果数据帧导出到一个新的 CSV 文件中,供以后使用。
data = data.rename(columns={'Recency (months)': 'recency',
'Frequency (times)': 'frequency',
'Monetary (c.c. blood)': 'volumne',
'Time (months)': 'time',
'whether he/she donated blood in March 2007': 'target'})data.to_csv('data_clean.csv')
作为最后一个练习,让我们使用 pandas corr()函数检查特征和目标变量之间的相关性。
corr_matrix = data.corr()
corr_matrix['target'].sort_values(ascending=False)

要退出 IPython,只需输入exit。
csvsql
有时,您可能还想通过数据库上的 SQL 查询来获取数据集。工具 csvsql 也是 csvkit 工具的一部分,它支持直接在数据库上查询、写入和创建表。它还支持查询 CSV 文件的 SQL 语句。让我们对清理后的数据集运行一个示例查询。
csvsql --query "select frequency, count(*) as rows from data_clean where target = 1 group by frequency order by 2 desc" data_clean.csv
SciKit-Learn 实验室
是的,你可以在命令行执行机器学习!这方面有一些工具,但 SciKit-Learn 实验室可能是最容易使用的工具之一。让我们使用献血数据集建立一个模型。
SciKit-Learn 实验室依赖于将正确的文件放在命名一致的目录中。因此,首先我们将创建一个名为train的目录,并将数据文件复制、移动并重命名为features.csv。
mkdir train
cp data_clean.csv train/features.csv
接下来,我们需要创建一个名为predict-donations.cfg的配置文件,并将它放在我们的data目录中。
[General]
experiment_name = Blood_Donations
task = cross_validate[Input]
train_directory = train
featuresets = [["features.csv"]]
learners = ["RandomForestClassifier", "DecisionTreeClassifier", "SVC", "MultinomialNB"]
label_col = target[Tuning]
grid_search = false
objective = accuracy[Output]
log = output
results = output
predictions = output
然后我们简单地运行这个命令run_experiment -l predict-donations.cfg。
这将自动运行实验,并创建一个包含结果的输出文件夹。
我们可以运行一个 SQL 查询来总结Blood_Donations_summary.tsv文件中的结果。
cd output< Blood_Donations_summary.tsv csvsql --query "SELECT learner_name, accuracy FROM stdin "\
> "WHERE fold = 'average' ORDER BY accuracy DESC" | csvlook
还有许多其他命令行工具对数据科学有用,但我想在这里强调一下我在工作中发现有用的工具。要全面了解命令行中的数据科学,我发现《命令行中的数据科学》这本书非常有用,这本书可以在网上免费获得。
五种常见分析失败
统计学课上他们永远不会告诉你的事情
在我从事专业分析的这段时间里,我看到了一些常见的错误。我不一定会责怪犯下这些错误的人,因为他们从来没有被教导过这些是不好的事情。
大学的统计学课仍然过于理论化。现在,数据科学鼓励更多基于真实数据和案例研究的学习,这不像以前那么糟糕。但仍然有太多的公式,而没有足够的基于一旦进入现实世界就可能发生的事情的实用建议。毕竟,这不正是统计学应该做的吗?
如果我在大学教统计学,我可能会称之为“现实世界统计学 101”。这里有一些我会让人们失望的事情。
1.平均平均值
这是我经常看到的。有人计算了一大群子群的平均指标,然后想要给出整个群体的平均指标。所以他们只是取平均值。这几乎总是错误的做法。
除非每个亚组的数据都是可公度的,具有大致相同的基数和相似的代表性——这基本上是不可能的——那么平均化平均值只会人为地夸大或缩小整个群体的真实指标。这里有一个简单的例子,如果你尝试用世界银行关于女性在劳动力中的代表性的数据,会发生什么——让它看起来比实际高得多:

2.忽略范围限制
如果您在任何必须分析数据点随时间退化的流程的环境中工作,就会经常出现这种情况,例如,选择流程。一种常见的情况是,人们想知道过程早期的信息是否可以预测过程后期的事情。例如,您可能希望将面试评分与随后的工作表现相关联。
我经常看到人们忽略了这样一个事实,即流程中较晚的数据点是流程中较早的数据点的子集。由于中间的选择,数据点已被删除。他们经常得出结论,相关性很低或为零,并以此为基础贬低该过程的早期阶段,认为其不能预测后期阶段。
这可能是一个主要的问题,特别是如果这个过程是高度选择性的——就像如果只有一小部分处于早期阶段的人能够进入后期阶段。通常通过的数据点的统计值很高并且被压缩,因为统计值较低的数据点没有通过。
有多种方法可以校正范围限制的相关性。下面是一个常用的公式:

但是我想澄清一点——如果限制是实质性的,那么所有的公式都是不可靠的。在这些情况下,如果相关性分析没有揭示任何值得注意的东西,我简单地宣布,由于范围限制问题,我们不能得出任何结论。
3.对二元结果使用线性回归
我认为当人们这样做的时候,他们要么完全忘记了他们的统计课,要么就是睡过了头。
线性回归是一个非常简单的过程,它基本上有助于预测一个具有连续数值范围的变量。比如汽车的价格。截距和系数被确定并直接应用于新的输入以确定预测值。使用平方和(毕达哥拉斯定理计算距离的扩展)很容易确定模型拟合。
试图在二元结果上使用这种方法是一个非常糟糕的主意。违反了线性回归中关于方差和残差的大多数基本假设,并且输出不是为了预测简单的二元结果而设计的。这是疯狂的,这表明做这件事的人在统计学方面不是特别训练有素,或者害怕逻辑回归,或者别的什么!
有些人尝试用线性概率模型来让线性回归方法处理二元结果数据。我一点也不相信这一点,它没有解决[0,1]范围之外的概率仍然可能发生的事实。
4.把你所有的鸡蛋放在 p 篮子里(或者根本没有 p 篮子)
p-hacking 正成为近年来统计和数据科学界越来越多关注的话题。人们越来越不愿意接受这样一种观点,即一条冷酷的显著性线是决定一件事是否值得作为分析性见解进行交流的唯一决定因素。
我经常看到这个问题的两个极端。我经常看到 p 值被完全忽略,因此一些 p 值为 0.5 的模式被提出来作为一种见解。而且我也看到了对 p < 0.05 边界的过分依赖。
这就是常识被抛弃的地方。从直觉上来说,数据中的模式是否显著取决于它所产生的影响,以及这种影响是否可以被认为是“不寻常的”。这说明了统计学家的一些判断:
- 如果数据确实很大,即使是极小的影响也能通过 p < 0.05 的条件。重要的是这种影响微乎其微。
- 如果数据不是很大,但效果似乎是,那么 p < 0.05 不应该是这种洞察力是否显著的唯一考虑因素。
5.使用不良语言
我不是说脏话(尽管我已经用我必须处理的一些数据集做了很多)。我的意思是没有准确地写出你的见解和结论。
语言在帮助他人理解他们可以从分析中得出的结论方面非常重要。我经常看到拙劣的语言会导致人们得出错误的结论,例如,在没有证据的情况下暗示因果关系存在,或者没有适当的限定结论。
例如,看看詹姆斯·邦德电影的相关矩阵:

说邦德的酗酒和杀人是增加电影预算的原因是很诱人的,但这假设了一种因果关系,我们还没有证明。更有可能的是,更长的电影涉及更多的饮酒和杀戮,制作成本也更高。但这一点我们也不能下定论。我在这里使用的语言很简单:“电影预算主要与喝多少马提尼酒和邦德杀死多少人有关。”这仍然是一个非常有趣的结论!
最初我是一名纯粹的数学家,后来我成为了一名心理计量学家和数据科学家。我热衷于将所有这些学科的严谨性应用到复杂的人的问题上。我也是一个编码极客和日本 RPG 的超级粉丝。在 LinkedIn 或Twitter上找我。

Courtesy of DigitalSpyUK
你必须能够回答的五个数据科学面试问题
《亚马逊/网飞/谷歌数据科学家面试问题内部指南》

Photo by Jon Tyson on Unsplash
为了不在同一个地方失败两次,并让自己对他人有用,我的这篇文章献给那些想要追随自己成为/提高数据科学家的热情的人。数据科学是一个需要不断提高技能集的领域,同时每天都要开发机器学习算法中的基本概念。所以,事不宜迟,让我们直接进入一些问题和答案,你可能在下次面试中有用。
问 问题 1 : 你能解释一下决策树的成本函数吗?
答案:在我们回答这个问题之前,需要注意的是,决策树是一种多功能的机器学习算法,可以执行分类和回归任务。因此它们的成本函数也不同。
分类类型问题的成本函数:
基尼系数是我们理解成本函数之前的一个重要概念,我先解释一下。

Equation I: Gini Impurity
其中" p" 是第 i 个节点的训练实例中 k 类实例的比率。那是什么意思?下面我们从一个例子来理解。图 1 显示了深度为 2 的 Iris 决策树的简单可视化。顶层是根节点。将训练集分成一组决策的概念在算法中相当简单。这里,举例来说,虹膜数据集被分成两个子集的基础上,一个单一的特征称为“花瓣宽度”在根节点。如果花瓣宽度小于或等于 0.8,则该算法将转到左侧的深度 1。如果不是,它就去深度 1,对。其中它基于“花瓣宽度”的附加特征进一步划分实例。深度 1,右节点具有 100 个实例的样本,并将 0 个实例应用于鸢尾-Setosa,将 50 个实例应用于鸢尾-Versicolor,将剩余的 50 个实例应用于鸢尾-Virginica。

Figure I: Iris Decision Tree, Source
因此,该节点的基尼系数为 0.5:

Figure II: Gini impurity calculation
类似地,在深度 1,左节点,基尼不纯度为零,因为所有训练实例都应用于相同的类。节点本质上是“纯”的。
既然明白了什么是基尼不纯,那就让我们进入答案的正题吧。决策树使用分类和回归树(CART)算法进行训练,这是基于一个简单的概念,即使用单个特征(k)和阈值(t)将数据集分成两个子集。在虹膜数据集中,特征是“花瓣宽度”,阈值是 0.8。它是如何选择 k 和 t 的?它搜索产生最纯子集的对(k,t)。因此,算法试图最小化的成本函数由下面的等式给出:

Equation II: Cost function of a classification type decision tree.
其中 G 左或右表示子集的基尼不纯度,而 m 表示子集的实例。
回归型问题的成本函数;
对于回归树,代价函数相当直观。我们使用残差平方和(RSS)。等式 III 显示了回归类型树的成本函数,其中“y”是地面真实值,“y-hat”是预测值。

Equation III: Residual Sum of Squares (RSS)
问 问题二: 共线性如何影响你的模型?
答:共线性是指两个或两个以上的预测变量彼此密切相关的情况。下面的图 2 显示了共线变量的例子。变量 2 严格遵循变量 1,皮尔逊相关系数为 1。所以很明显,当输入机器学习模型时,这些变量中的一个会表现得像噪音一样。

Figure 2: Example of collinear variables.
在回归型问题中,共线性的存在可能会成为问题,因为很难区分共线变量对响应的单独影响。或者换句话说,共线性降低了回归系数估计的准确性,并导致误差增加。这将最终导致统计量的下降,因此,在存在共线性的情况下,我们可能无法拒绝零假设。
检测共线性的简单方法是查看预测变量的相关矩阵。该矩阵中绝对值较大的元素表示一对高度相关的变量,因此数据存在共线性问题。不幸的是,并不是所有的共线性问题都可以通过检查相关矩阵来检测:即使没有一对变量具有特别高的相关性,三个或更多变量之间也可能存在共线性。这样的情况称为多重共线性。在这种情况下,评估多重共线性的更好方法是计算方差膨胀因子 (VIF),而不是检查相关矩阵。每个变量的 VIF 可以使用以下公式计算:

Equation IV: The Variance Inflation Factor (VIF)
其中 R 平方项是变量 X,对所有其他预测值的回归。如果 VIF 接近或大于 1,则存在共线性。当面临共线性问题时,有两种可能的解决方案。一是去掉多余的变量。这可以在不损害回归拟合的情况下完成。第二种解决方案是将共线变量组合成一个预测值。
问 问题三: 你会如何向一个外行人解释深度神经网络?
答案:神经网络(NN)的想法最初源于人脑,它是为了识别模式而设计的。NN 是一组通过机器感知、标记和聚类原始输入数据来解释感官数据的算法。任何类型的真实世界数据,无论是图像、文本、声音还是时间序列数据,都必须转换到包含数字的向量空间中。
深度神经网络中的“深度”一词是指神经网络由多层组成。这些层由进行计算的节点组成。node 是人脑中的一个神经元,当它遇到足够的刺激时就会触发。节点将来自原始输入的数据与它们的系数或权重相结合,这些系数或权重基于权重来抑制或放大该输入。然后,输入和权重的乘积在图 3 所示的求和节点处被求和,然后被传递到激活函数,该激活函数确定该信号是否应该进一步通过网络以影响最终结果,以及应该进一步通过网络到什么程度。节点层是一排类似神经元的开关,当输入通过网络时,这些开关打开或关闭。

Figure 3: An example of node visualization in neural network.
深度神经网络不同于早期版本的神经网络,如感知器,因为它们是浅层的,仅由输入和输出层以及一个隐藏层组成。

Figure 4: Deep neural network consists of more than one hidden layer.
问 问题四: 你的数据科学带回家项目的 3 分钟推介是什么?
回答:一个典型的数据科学面试流程从一个带回家的数据分析项目开始。我已经参加了其中的两次,时间跨度可能会根据带回家项目的复杂程度而有所不同。第一次,给我两天时间用机器学习和执行摘要解决一个问题。第二次给我两周时间解决问题。不用说,第二次是一个更困难的问题,我处理的是类不平衡数据集。因此,3 分钟的推销型面试问题可以让你展示你对手头问题的理解。请务必从你对问题的解读是什么开始;你解决问题的简单方法;你在你的方法中使用了什么类型的机器学习模型,为什么?通过吹嘘你的模型的准确性来结束这一切。
我相信这是面试中一个非常重要的问题,它能让你证明你是数据科学领域的领导者,并且可以用最新最棒的工具解决复杂的问题。
问:你所说的模型正则化是什么意思,你将如何在线性模型中实现正则化?
答案:正则化是一个用来约束你的机器学习模型的术语。限制或减少机器学习模型中的过拟合的一个好方法是具有更少的自由度。自由度越少,模型越难过度拟合数据。例如,正则化多项式模型的一个简单方法是减少多项式自由度的数量。然而,对于线性模型,正则化通常通过约束模型的权重来实现。因此,代替线性回归,岭回归、套索回归和弹性网模型有三种不同的方法来约束权重。为了完整起见,让我们先从线性回归的定义开始:

Equation V: Linear regression and model prediction
- y-hat 是预测值。
- n 是特征的数量。
- x_i 是第 n 个特征值。
- θ是模型参数,也称为特征权重。
线性回归模型的均方误差成本函数定义为:

Equation VI: Linear regression cost function.
其中 theta 是 theta 的转置(行向量而不是列向量)。
岭回归:是线性回归的正则化版本,即在成本函数中增加了额外的正则化项。这迫使学习算法不仅要适应数据,还要保持模型权重尽可能小。请注意,正则化项应该仅在训练期间添加到成本函数中。模型定型后,您希望使用未规范化的性能度量来评估模型的性能。

Equation VII: Ridge regression cost function.
超参数 alpha 控制您想要正则化模型的程度。如果α为零,那么岭回归只是线性回归。
Lasso 回归:最小绝对收缩和选择算子回归(简称 Lasso 回归)是线性回归的另一个正则化版本:就像岭回归一样,它在成本函数中添加了一个正则化项,但它使用了权重向量的 L1 范数,而不是 L2 范数的一半平方。

Equation VIII: Lasso Regression cost function.
Lasso 回归的一个重要特征是,它倾向于完全消除最不重要的要素的权重(即,将其设置为零)。换句话说,Lasso 回归自动执行特征选择并输出稀疏模型(即,具有几个非零特征权重)。
弹性网回归:这是脊和套索回归之间的中间地带。正则项是岭和套索正则项的简单混合,可以用“r”来控制。当 r=0 时,弹性网等价于岭回归,当 r=1 时,等价于套索回归。

Equation IX: Elastic Net cost function.
至少有一点点的正则化总是更可取的,并且通常应该总是避免简单的线性回归。岭是一个很好的默认值,但如果在特定数据集中只有少数要素有用,则应使用 Lasso。一般而言,弹性网优于套索,因为当要素数量大于实例数量或多个要素高度相关时,套索可能表现不稳定。
在本文中,我讨论了我个人在一次技术数据科学面试中面临的五个问题,我认为这些问题本可以做得更好。我强烈推荐阅读下面的资源,在日常生活中磨练你的基本概念。相信我,我一定一遍又一遍地阅读了这些概念,然而我在面试中却笨手笨脚地发现了它们。
如果你喜欢读这篇文章,请不要忘记投票支持它!
快乐学习!
关于人工智能和机器学习项目失败原因的五个假设

有大量的文章和发表的论文围绕着为什么人工智能/机器学习/自然语言处理项目从未进入“生产”或未能交付所提出的价值(Gartner 预测,到 2022 年,85%的人工智能项目将由于数据、算法或负责管理它们的团队的偏见而交付错误的结果)。以下是一些关于为什么人工智能项目失败的假设,以及一些关于公司对人工智能项目缺乏交付价值的反应(技术和组织)的观察。请注意,这些意见并不反映任何当前或以前的雇主,而是与各行业的数据科学家、工程师、产品经理和架构师对话的综合。
假设 1:数据科学的初始模型不可扩展,或者过于实验性,不适合内部或外部客户使用。
项目通常从这里开始,因为公司雇佣了一些用 Python 或 R 构建模型的数据科学家,但很快发现数据科学家和工程师的思维方式不同。从短期来看,这个问题通常通过让机器学习工程师或其他软件工程师获取代码、重写代码并遵循标准开发-运营流程来解决,以便扩展和部署应用程序。考虑到结果的随机性,质量工程也需要与任务相适应。在这个过渡期,业务用户(和/或他们的代理产品经理)可以被排除在流程之外,需求或底层数据可能会发生变化。
在组织上,一些公司采取了下一步措施,聘请工程师加入数据科学小组,以帮助数据科学家了解更多关于生产和部署的扩展。这种方法的目标是减轻切换过程。面临的挑战是,这个研究/工程组织可能会与生产支持工作流的其余部分隔离开来。“数据科学家不戴呼机随叫随到”。
假设#2:数据科学/ML 模型虽然出色且具有创新性,但不符合业务需求,或者过于脆弱,无法响应支持数据的变化。
在咨询集团的建议下,一些公司做出决定,为了促进创新,“创新团队”需要与他们周围的“非数字”文化隔离开来。但是,虽然孤立的创新团队可以带来巨大的实验机会,并且这些团队可以学习和开发有趣的解决方案,但当最终的项目需要“推向”市场或内部客户时,通常只有很少的采用。虽然该解决方案可能会满足特定的挑战,但用户的体验通常不是最佳的。如果数据发生变化,缺少一个不太复杂的模型作为后盾也是一个挑战。从好的方面来说,这项研究可以发表在其他数据科学家阅读的学术期刊上,以进一步了解整个行业。
为了应对这种不采用的问题,一些创新/数据科学团队可能会在他们的组织中增加产品营销人员,以在内部“推广”他们的工作,并尝试直接向客户推销他们的概念。
假设#3:人工智能计划由公司内部 IT 组织驱动,并继承了“瀑布”式的挑战。
作为“数字化转型”的一部分,一些公司将 AI 视为整体数据仓库计划的一部分,或作为客户管理或营销应用自动化工作的一部分。人工智能或机器学习对“标记”(适当标记)数据的需求意味着,公司还需要有一个相当成熟的分析和数据捕捉基础设施。虽然将人工智能和机器学习作为整个 it 数据项目的一部分似乎是合理的,但如果没有伴随的实验/原型计划,人工智能项目可能会因此被埋没。具有讽刺意味的是,当数据最终在数据管道中准备就绪时,客户的需求可能已经完全改变了(例如:在公司战略转向流媒体时围绕 CD 发行进行优化)。
假设 4:公司对交付人工智能/人工智能项目所需的时间没有耐心。
鉴于许多提议的人工智能/人工智能项目的成功和时间表的不确定性,它们甚至可能在有机会开始之前就夭折了。再加上技术的新颖性以及谷歌、亚马逊和微软的领先优势,结果是非直接面向消费者的企业公司可能会谈论人工智能,但首先从业务流程自动化工具开始,然后等待事情解决(为未来捕捉数据)。
假设 5:缺乏 AI/ML 项目的“产品”方法是项目失败和风险增加的核心。
产品经理/所有者在敏捷软件开发中的角色在软件开发中是很明确的。产品经理作为团队的一部分工作,团队包括 UX、工程、质量保证和项目管理。人工智能项目中似乎缺少的就是这种相同的“产品”思维模式。优秀的产品经理知道如何提出或找到最有价值的商业问题。有经验的产品经理是管理产品交付不确定性的专家。经验丰富的分析产品经理知道数据在哪里丢失或被掩埋。因此,今天的问题可能不是“我们需要的数据科学家都去哪里了?”,而是“知道如何提出正确问题的人工智能产品经理都去哪里了?”
概要:
从根本上说,AI/ML 项目不会成功,除非精通技术的产品经理采用同样的“敏捷”“精益”产品方法来应对风险、领土之争以及无法解决或回答业务问题的挑战。数据科学家和机器学习工程师需要成为产品开发团队的一部分。就像“设计思维”让 UX 加入软件开发团队一样,“人工智能思维”需要将人工智能研究整合到产品开发中。人工智能产品经理需要业务高管的支持,以指导商业价值评估过程,并平衡技术/数据科学议程。
虽然人工智能云平台的变革仍在继续,工具也在快速变化,但那些加强并培训或雇佣具备必要技术或数据技能的人工智能产品经理,并挑战他们与 UX 设计师、数据科学家、机器学习工程师、软件工程师组成的团队一起找到一个商业问题来回答的公司,在一位理解并能够理性沟通不确定性的项目经理的协调下,将是利用人工智能取得成功的漫长道路。
那些已经标记了数据流的公司,如电子商务和金融/支付领域的公司,已经领先一步,并为其他公司提供了如何扩展的良好模型,但只有通过提出正确的问题,并利用和扩展 ML/NLP/AI 来获得正确的答案,其中一些公司才能在未来几十年中领先一步。
实用人工智能发展的五个里程碑。
计算机从简单计算到识别癌症用了不到 80 年的时间。

Photo by Franck V. on Unsplash
当谈到机器学习和人工智能时,人们的意见不一。一些人认为,人们能够给计算机编程,让它们轻松地完成与人类相同的任务,这是不可避免的。其他人认为,计算机永远无法在智力、决策和感知方面接近人类。
无论这个领域的未来如何,机器学习并不神奇。事实上,在过去的 80 年里,它一直在稳步而显著地改变着我们的生活。进展相当令人着迷。
1959 年:计算机和跳棋
我们可以将第一个概念神经网络的根源追溯到 1943 年,以及沃伦麦卡洛克和沃尔特皮茨的工作。然而,机器学习原则的第一次现实应用发生在 1959 年,当时亚瑟·塞缪尔创建了第一个跳棋程序。
当时,大多数人在电脑上构建跳棋游戏的方式是靠蛮力。一种算法会在棋盘上走一遍,按顺序移动并搜索每一条可能的路径,直到游戏结束。唯一的问题是,T2 需要大约 13 年的时间才能让计算机暴力破解跳棋。这对战胜人类的努力来说没什么用。
塞缪尔决定写一个函数,在任意给定时刻评估棋盘上的情况,而不是搜索每条路径。该函数考虑了有多少棋子成为了“国王”,其他棋子离达到那个阶段有多近,以及每边总共还有多少棋子。然后,程序试图做出一个移动来优化函数值,并增加获胜的概率。
后来,塞缪尔还设计了一些机制,让计算机能够记住它已经处于的位置,以及这些位置最终是如何“奖励”的。
1997 年:机器在国际象棋中击败卡斯帕罗夫
1997 年,它变成了现实。一台计算机在人类发展的游戏中击败了排名第一的人类,为未来数年的人工智能研究定下了基调。这台机器叫做“深蓝”,它是 IBM 最伟大的发明之一。
在 1997 年的决赛之前,加里·卡斯帕罗夫在 1989 年遇到了“深蓝”的早期原型 ChipTest,并击败了它。研究人员又花了将近十年的时间来改进这个系统。当“深蓝”击败“大师”时,卡斯帕罗夫指责机器的创造者作弊,声称“深蓝”的决定具有人情味。
然而,很久以后,在他的 2017 TedTalk 中,卡斯帕罗夫分享了他对发展中技术的有利观点,并指出机器是对人类的补充,而不是竞争。
2009 年:统计猴子和体育新闻
在国际象棋中计算走法很酷。对已经生成的图像进行分类令人印象深刻。然而,一台能产生原创作品的电脑是一个爆炸。
统计猴,西北大学几名学生的项目,可以在大约两秒钟内写下任何一场棒球比赛的故事。该系统使用统计数据来计算游戏的结果、关键玩家和动态。在确定了故事应该是关于什么之后,Stats Monkey 使用了一个特定表情库,并拼凑了一个实际上听起来不像机器人的叙述。
2015 年:DeepMind 玩电子游戏
DeepMind 是一个可以自学玩电脑游戏的系统,而且是最擅长玩电脑游戏的人。DeepMind 于 2014 年被谷歌收购,是第一个成功的机器学习应用,允许计算机在 49 种不同的雅达利游戏机游戏上逐渐变得更好。
新颖之处在于算法。虽然编写 DeepMind 来玩某些游戏很容易,但创作者们的目标是更优雅和普遍适用的方法。他们想要一个可以从头学习的模型。
DeepMind 的联合创始人兼首席执行官戴密斯·哈萨比斯博士评论:“我们给系统的唯一信息是屏幕上的原始像素和它必须得到高分的想法。”
DeepMind plays Atari Breakout —YouTube
该系统的最终版本利用了深度学习,并在玩了几个小时后设法解决了游戏的机制。DeepMind 的成功不仅令人印象深刻,而且证明了在没有人类干预的情况下,编写一个从大量数据中学习并应用这些知识的计算机是可能的。
2018:人工智能识别皮肤癌
各种皮肤癌的早期检测对存活率极其重要。2018 年,人类医生在大约 86.6%的病例中成功检测到黑色素瘤。这意味着,在全球每年因黑色素瘤而死亡的 59,782 人中,大约有 8,011 人可能因为这一错误而死亡。
2018 年,来自德、法、美三国的研究人员制造了一种人工智能系统,可以在 95%的病例中正确识别恶性黑色素瘤。该模型也减少了对皮肤良性痣的误判。准确度可能会随着更多的训练而提高。
作为一种工具,机器学习非常有用。本文中涉及的五项开发只是利用该技术的数百万个非凡解决方案中的一小部分。通过更深入的观察,你会发现人工智能现在无处不在——从人脸识别和医学到商业分析、游戏和 YouTube 推荐算法。
这是一个完美的时机,不仅可以扩展我们在人工智能领域的专业知识,还可以精确地定制它,以解决我们这个时代最大的问题。
让你看起来像詹姆斯·布朗的五个蟒蛇动作

Photo by Start Digital on Unsplash
在我们作为数据科学家的日常生活中,我们经常使用各种 Python 数据结构,如列表、集合或字典,或者更一般地说,我们使用 iterables 和 mappings 。有时,转换或操作这些数据结构会变得非常代码化。这可能导致代码不可读,并增加引入错误的机会。幸运的是,有一个叫做 funcy 的简洁的 Python 模块可以帮助我们简化这些任务。在这篇短文中,我向您展示了普通的 Python 代码和相应的 funcy 函数,这些函数允许您用可读性更好的代码更高效地完成五个不同的任务。读完这篇文章后,我相信你会变得更有趣:)
省略/投影
有时您得到了一个字典,而您想继续使用该字典的一个子集。例如,假设您正在构建一个 rest API 端点,并且您只想返回模型属性的子集。为此,funcy 提供了两个功能,即省略和项目
**from** funcy **import** project, omitdata = {**"this"**: 1, **"is"**: 2, **"the"**: 3, **"sample"**: 4, **"dict"**: 5}**# FUNCY** omitted_f = omit(data, (**"is"**, **"dict"**))
**# PLAIN PYTHON**
omitted_p = {k: data[k]
**for** k **in** set(data.keys()).difference({**"is"**, **"dict"**})
}**# FUNCY** projected_f = project(data, (**"this"**, **"is"**))
**# PLAIN PYTHON** projected_p = {k: data[k] **for** k **in** (**"this"**, **"is"**)}
使用 funcy,您不仅需要键入更少的字符,还可以获得可读性更好、更少出错的代码。但是为什么我们需要两个功能呢?如果您想要保留的关键点数量小于您想要移除的关键点数量,选择项目否则选择忽略。
展平嵌套的数据结构
假设你有一个嵌套的数据结构,比如一个列表和列表的列表,你想把它变成一个列表。
**from** funcy **import** lflattendata = [1, 2, [3, 4, [5, 6]], 7, [8, 9]]**# FUNCY**
flattened_f = lflatten(data)**# PLAIN PYTHON**
**def** flatter(in_):
for e in in_:
**if** isinstance(e, list):
**yield** **from** flatter(e)
**else**:
**yield** eflattend_p = [e **for** e **in** flatter(data)]
如您所见,funcy 版本只有一行代码,而普通 Python 版本看起来相当复杂。我也花了一些时间来想出解决方案,我仍然没有 100%的信心。所以,我会坚持使用 funcy:)除了 list 版本 l flatten,funcy 还为 iterables 提供了一个更通用的版本,称为 flatten,没有 l 前缀。你会发现对于不同的函数。
分成块
假设您有一个包含 n 个条目的 iterable,并且您想要将它分成包含 k < n 个元素的块。如果 n 不能被 k 整除,则最后一个块可以小于 k。这就像有一个 n 个样本的训练集,您希望将其分成大小为 k 的批次来进行批处理
**from** funcy **import** lchunks
data = list(range(10100))**# FUNCY**
**for** batch **in** lchunks(64, data):
# process the batch
pass**# PLAIN PYTHON
from** typing **import** Iterable, Any, List**def** my_chunks(batch_size:int, data:Iterable[Any])->List[Any]:
res = []
**for** i, e **in** enumerate(data):
res.append(e)
**if** (i + 1) % batch_size == 0:
**yield** res
res = []
**if** res:
yield res
**for** batch **in** my_chunks(64, data):
# process the batch
**pass**
注意,我使用了 lchunks 版本来进行列表分区,而不是更通用的 chunks 版本来对可重复项进行分区。您所要做的就是传递您想要的批处理/块大小,以及您想要分区的 iterable。还有另一个 funcy 函数,叫做分区,它只返回那些正好有 k 个条目的块。因此,如果 n 不能被 k 整除,它将忽略最后一个。
组合多个词典
假设您有多个保存不同日期但相同对象的数据的字典。您的目标是将所有这些字典合并成一个字典,并使用特定的函数合并来自相同关键字的数据。在这里,funcy 的 merge_with 函数派上了用场。你只需要传入合并函数和所有你想合并的字典。
**from** funcy **import** merge_with, lcat
d1 = {1: [1, 2], 2: [4, 5, 6]}**# FUNCY VERSION**
merged_f = merge_with(lcat, d1,d2)**# PYTHON VERSION**
**from** itertools **import** chain
**from** typing **import** Callable, Iterable, Any, Dict
**def** _merge(func: Callable[[Iterable], Any], *dics:List[Dict])->Dict:
# Get unique keys
keys = {k **for** d **in** dics **for** k **in** d.keys()}
return {k: func((d[k] **for** d **in** dics **if** k **in** d)) **for** k **in** keys}merged_p = _merge(lambda l: list(chain(*l)), d1, d2)
还有一个函数 join_with ,它类似于 merge_with,但不是将每个字典作为单个参数传递,而是传递一个可迭代的字典。哦,我“偶然”潜入了另一个 funcy 函数 lcat,它将不同的列表合并成一个。
缓存属性
最后但同样重要的是,完全不同但非常有用的东西; cached_prope rty 装饰器。顾名思义,它使您能够创建只执行一次的属性,而不是缓存该执行的结果并在所有后续调用中返回该结果。我在构建数据集类时经常使用它,因为它给了我非常干净和易读的接口,同时减少了加载时间。
**from** funcy **import** cached_property
**import** pandas **as** pd**# Funcy Version**
**class** DatasetsF:
@cached_property
**def** movies(self) -> pd.Dataframe:
**return** pd.read_csv(**"the_biggest_movie_file.csv"**)**# PYTHON VERSION**
**class** DatasetsP:
**def** __init__(self):
self._movies = None@property
**def** movies(self) -> pd.Dataframe:
**if** self._movies **is** None:
self._movies = pd.read_csv(**"the_biggest_movie_file.csv"**)
**return** self._movies
结论
在这篇文章中,我向您介绍了 funcy,向您展示了它提供的一个非常小但很方便的功能子集。要快速了解所有功能,请查看此备忘单。我希望这篇文章能激励你学习一些 funcy 动作。它比我在这里向你展示的要多得多。感谢您的关注,如果有任何问题、意见或建议,请随时联系我。
为什么不能“让另一位数据科学家来做这件事”的五个理由
这句话证明了人们不知道数据科学到底是什么
你有没有参加过分析性问题被证明具有挑战性的会议或讨论?比如,你找不到合适的模型,或者你的数据格式有问题,或者领导对时间表不满意?
有时候我也遇到过这种情况。很正常。分析是复杂的,事情并不总是如你所希望和计划的那样发展。
但是,当人们认为这些问题的解决方案是“让我们找一个(另一个)数据科学家来做这件事”时,这让我抓狂。这种思维方式有太多的错误。它暴露了对什么是数据科学的基本误解,并暴露了对实践数据科学所需技能的缺乏重视。以下是为什么不能“让另一位数据科学家来做这件事”的几个原因。
1.数据科学家不是一个容易找到的资源
尽管该领域出现了爆炸式增长,而且现在提供了各种教育项目,但优秀的数据科学家并不容易找到。市场竞争激烈,对那些技术最熟练的人需求量很大。如果您足够幸运,在您的组织中有强大的数据科学家,他们的工作可能已经非常繁忙了。
“为了快速解决问题,让另一个数据科学家来做这件事通常意味着引入那些并不真正有资格自称为数据科学家的资源。比如有人几个月前上过一次在线 Python 课程,并且能说出机器学习这个词。那里有很多这样的人,但是让他们参与进来最终会拖延而不是帮助这项工作。如果目的是提供更多能力来帮助现有的数据科学家,可能会适得其反,因为他们将不得不花大量时间与知识基础非常低的人打交道。
2.数据科学家不是多面手
数据科学是一个异常宽泛的领域。有时你说你想要一个数据科学家,但实际上你需要的是一个数据工程师。有时候你需要一个 NLP 专家,或者一个会做 GLMs 的人。有时候你需要的更像是前端开发人员来构建应用程序。没有一个数据科学家能把每件事都做好。
“让另一个数据科学家来做这件事”不够规范,可能会导致 HR 让背景和技能与要解决的问题不匹配的临时或永久员工来做这件事。
3.数据科学家通常需要领域专业知识
类似于第 2 点,如果数据科学家没有知识领域来支持他们的技术技能,他们很少是有效的。与分析人才和人员或临床试验结果的工具和方法相比,分析金融风险的数据科学家将使用一套完全不同的工具和方法。
强大的数据科学家通常在其特定领域拥有一定水平的经验和专业知识。他们知道面临的典型数据问题,经常发生的特定统计现象。他们可以更流利、更快地处理这些问题。
“让另一个数据科学家去做”可能会给人们带来压力,让他们为了填补所需的能力而指派没有领域专业知识的人。这对工作质量和潜在的解决方案可能是毁灭性的。
4.数据科学家需要整合到工具链中
强大的工具链带来了如此多优质、高效的分析。语言、版本控制、敏捷跟踪以及发布和共享规范都是平稳的数据科学操作的一部分。许多数据科学家可能不熟悉正在使用的特定工具链,或者对任何工具链都没有经验。
“让另一个数据科学家去做”可能会使现有的工作流程面临风险或导致延迟,因为新的资源被投入到工作中,而这个人没有使用所用工具的经验。结合上述一些问题,这可能是非常危险的。
5.数据科学家并不是唯一需要解决这个问题的人
使用分析解决组织或业务问题需要的不仅仅是数据科学家。它需要来自业务方面的经验丰富的个人的参与,他们可以帮助判断什么应该建模,什么不应该建模,以及什么样的结果最有用。它需要能够帮助数据科学家区分工作中哪些部分是关键的,哪些部分在数据质量差或难以获得时可以放弃的人。
“让另一个数据科学家来做”通常会掩盖一个更深层次的问题,即业务方面没有足够的参与,以及现有的分析专业人员没有收到关于优先级和输出的足够指导。另一个数据科学家不会在这方面提供帮助。
数据科学家并不是万能的灵丹妙药。在一个问题上投入更多往往会使情况变得更糟,而不是更好。所以下次你听到有人说“让我们找一个(另一个)数据科学家来做吧”,不要害怕让他们来做!
最初我是一名纯粹的数学家,后来我成为了一名心理计量学家和数据科学家。我热衷于将所有这些学科的严谨性应用到复杂的人的问题上。我也是一个编码极客和日本 RPG 的超级粉丝。在 LinkedIn 或Twitter上找我。

Image from Despicable Me, Illumination Entertainment/Universal Pictures
构建优秀的数据产品
连接→规划→实验→部署→发展

Photo by Tory Morrison on Unsplash
1.连接
与贵组织的主要利益相关者密切合作。了解你的客户。问他们:他们的目标是什么,他们需要什么?是什么阻碍了他们?数据如何帮助他们?确定棘手问题,并考虑特定问题的解决方案。尽早让别人成为你努力的一部分。
用你自己的建议补充你客户的想法。你能想到数据还能为他们做什么他们不知道的事情吗?例如,有没有一些过程可以利用机器学习的最新发展来实现自动化,而你的客户却不知道?
不悔的头脑风暴。通常,看似“坏主意”可以发展成有用的东西。即使你的大部分建议被拒绝,你也可能只剩下一个能为公司节省数百万美元的好主意。
用人和数据连接人和数据。您或您的组织中有哪些实际可用的数据?哪些数据源应该使用或与其他数据源结合使用,但目前没有使用?人们是否公开交流想法、经验和最佳实践?如果必要的平台不存在,创建它们。
2.计划
艾伦·凯利和 #beyondprojects 运动给了我们一种新的工作心态:持续思考。我们不是把所有事情都框定为一个有固定价值和固定结束日期的项目,而是围绕连续的、类似产品的工作流来组织自己。根据它们所传递的价值,它们出现、成长、萎缩或死亡,就像是“最能创造价值者的生存”。为了提高工作效率,我们根据价值和努力程度进行优先排序。在数据科学计划的开始阶段,低投入比高价值更重要,因为立竿见影会产生有价值的信任。
然而,好的规划是关于选择合适的方法,而不是遵循固定的路线:当今数字经济的市场动态使价值成为“移动的目标”,而大多数数据科学工作的实验性质阻碍了对时间框架的精确估计。这阻碍了长期规划,需要一种迭代方法。为了降低风险,我们一小步一小步地到创建一个最小可行产品 (MVP),我们获取客户反馈来学习和改进。
在敏捷数据科学宣言中,Russell Jurney 介绍了数据价值金字塔的概念:从数据记录到图表、报告、预测以及最终的行动,价值不断增加的数据使用层相互建立。在规划你的工作时请记住这一点:数据科学只有在导致行动时才是有效的,成为战略层面或微观决策的 it。因此,思考超越模型和分析:计划将数据科学产品整合到组织流程中和通过利用你的社交技能,确保你的结果“通过”。
3.实验
试试。失败。学习。重复一遍。重复一遍。重复一遍。然而:实验不是尝试随机的东西,而是发现什么可行或不可行,即通过经验发现成功之路。
在数字经济中,一个伟大的想法比你狭隘地追求当前任务而获得的百分之几的额外效率更有价值。花点时间开阔你的视野。结合不同的想法。了解最新的发展和创新。经常你会被卡住,但是现在不工作的东西可能在另一个时间派上用场。保持开放的心态,一路上会有很多有价值的见解向你走来。
与您的团队和更广泛的数据科学社区讨论和分享您的方法、想法、经验和成果,无论是在线还是离线。
4.部署
现在,最新的情况是,真正的数据科学家大放异彩:他们不仅有能力构建伟大的模型来满足真正的商业需求,还能从这些模型中创造产品和服务。
要么与软件工程师密切合作,要么学习他们的手艺,你将见证你的实验工作流程被剥离到最基本的部分,并作为 生产级应用重生。不要因为你在过去需要或者在遥远的将来可能会再次需要而保留界面或功能——在时机成熟时添加这些比从一开始就因为复杂而削弱你的产品更明智。不要扔掉你的探险笔记本,而是将它们存档。了解如何使用部署技术将这段代码付诸实施。
为了快速行动并在早期发布 MVP,与其他角色密切合作,例如产品所有者或系统管理员。设计自治和异质的团队,包含所有必要的角色,不受组织边界的阻碍。共同责任感是金:如果每个人不仅仅关心自己的部分,而是关心整个产品,人们将会目标一致地工作,务实地弥补责任差距。****
5.生长
秤你的产品。向前看:哪些改进、特性或改变会进一步增加它的成功?回顾过去:到目前为止,你能从这段旅程中学到什么?这对你下一步的努力有什么帮助?
传达您的结果和成功,为您的团队和您自己赢得“数据科学可信度”。你能给出一个你负责的节约或收入增长的数字吗?
好的工作会创造信任,这会给你更多的机会。从长远来看,你将从改变流程到改变思维方式,超越数据科学的范畴,展现真正的变革效应。
你错过什么了吗?完全不同意?请在评论中分享你的观点!
spaCy-IRL 值得一看的五次演讲
SpaCy-IRL 是一项为期两天的活动,于 2019 年 7 月 5 日至 6 日在柏林举行。它主要关注应用自然语言处理(NLP)。也就是说,用(通常)非基准数据解决新的 NLP 业务问题。在会议期间,讨论了用于解决行业问题的问题、方法和工具。我选择了我认为值得观看的 spaCy-IRL 2019 五大演讲。

约翰·戈德堡、巴伊兰大学和 AI2:自然语言处理中缺失的元素
Yoav 谈到了学术界和工业界之间有趣的差距。一方面,学术界的研究大多集中在需要大量数据和计算能力的深度学习模型上。另一方面,该行业仍然依赖于传统的 NLP 技术。在他的革命性愿景中,应用的 NLP 仍将依赖于人类的书面规则,但它们将在机器学习的帮助下编写,从而产生透明和可调试的模型。他用自己的观点描述了 NLP 中缺失的元素:
1。使用少量带注释的数据,在领域之间转移,并将知识整合到模型中。
2。NLP 构建块的符号表示,允许非专家直接交互。
3。“处理缺失的元素”。我们作为说自然语言的人或作家,在使用自然语言时会省略单词。例子:“她刚满 50 __”。例如,这挑战了我们的依存解析模型。
4。人类和机器的合作,以建立自然语言处理模型。这种合作将包括一个支持它的互动开发过程。
Yoav 还提到了理解深度学习模型正在做什么(“打破黑盒”)以及 NLP 中公平性和可解释性的重要性。
在我的研究中,在 Yoav 的指导下,我专注于第一个缺失的元素。特别是针对带有小注释的数据和域间传输的问题的解决方案。我最近的工作,发表在 NAACL 2019 上,解决了使用小噪音词典的单词嵌入对齐问题,这对于低资源语言和域转移非常重要。
二。Sebastian Ruder,DeepMind:开源 NLP 中的迁移学习
不久前,在 EMNLP 2018 上,我与 Sebastian 会面,讨论他的最后一篇论文之一,他可能不记得我了,但我非常兴奋。这一次,Sebastian 谈到了 NLP 中的迁移学习。在过去的几年里,迁移学习在 NLP 领域取得了巨大的进步,在我看来,这种积极的变化将能够解决许多行业用例特定的问题。在他的演讲中,Sebastian 强调了在 NLP 中使用迁移学习的天然优势,这是由于语言表示、结构相似性、任务交织(如语法和语义)以及带注释的数据很少这一事实。事实上,使用预先训练的语言模型(如:ELMo、BERT)已被证明有助于改善翻译、情感分析、命名实体识别、文本分类、问答等任务。Sebastian 的观点是,如果可用且与您的用例相关,请使用预训练模型,最重要的是,如果您在一种新类型的数据集上训练了一个模型,这可能对其他人分享它有用。他的理由不仅是社区驱动的,也是生态的。生态?!是的,Sebastian 引用了《T2》的这篇论文,训练一个语言模型比制造和使用一辆汽车消耗更多的二氧化碳。他建议使用集线器共享模型。中枢允许我们将机器学习模型作为自包含图(模型和参数)来共享。

“Training a language model uses more carbon dioxide than the manufacturing and lifetime use of a car”
三世。Rasa 的 Justina Petraitytė:在帮助船舶对话人工智能助理方面的经验教训
Rasa 通过提出对话式 NLU 的开源模型,使开发人员能够构建对话式人工智能系统。Justina 讨论了他们在开发 Rasa 时面临的挑战及其解决方案。其中有:预训练词向量的局限性;确认范围外的输入;和反馈回路;在我看来,Justina 为这些问题提出的解决方案非常具有启发性,这可以追溯到 Yoav 关于工业依赖于启发性解决方案的观点。对话式人工智能是应用自然语言处理的一个常见例子,所以我觉得听到 Rasa 面临的主要问题很有趣。
四。麦肯锡·马歇尔,巴林银行:资产管理中的 NLP
巴林正在他们的财务管理系统中应用 NLP。麦肯齐强调,投资研究不是完全自动化的,而是使用机器学习进行增强。她使用 prodigy 注释工具创建了一个数据集,专门解决他们处理公司名称词义歧义的问题。同样,我们可以看到为了支持当前的 NLP 模型,需要领域专家的介入。此外,她提到了使用二元案例情感分析的问题。
诉国际 RTI 公司彼得·鲍姆加特纳:应用自然语言处理:来自现场的教训
彼得谈到了他作为 NLP 专家与客户打交道的工作。首先,在谈到 NLP 行业问题时,他谈到了客户的不同需求。有时,客户不知道 NLP 的能力和解决的问题,有时他们被人工智能炒作所驱使或者误解了这个领域。第二,他提到 ML 项目更难管理,因为有更多的模糊性。为了传达不确定性和报告项目状态,他建议管理实验日志。Peter 还建议建立比喻,以便与非技术同事交谈。最后,Peter 提到了我们的工作交流的重要性,不仅在研究自然语言处理方面,而且在应用自然语言处理方面。他认为这样做的方法是通过博客。彼得说得对,研究人员有他们有组织的平台(主要是论文和会议)来分享他们的工作,但我相信现在工业界有同样多的机会,甚至有更多的人有兴趣听。聚会、讲座、github、kaggle(用于数据集发布)、博客,只是你如何分享你的工作的几个例子。
希望你喜欢这篇文章!
成功应用编程接口的五个技巧

Photo by Radowan Nakif Rehan on Unsplash
现在向世界发布一个基于云的应用编程接口(API)非常容易,它提供一些软件功能作为服务。因此,云 API 空间已经变得非常密集,因此,即使某个特定的 API 提供了潜在价值可观的服务,也有许多其他因素在决定该 API 是否会获得商业成功方面发挥作用。如果你正考虑带着你最新最棒的想法进入 API 市场,这篇文章提供了一些完全主观的建议,告诉你如何增加你的产品不被所有噪音淹没的机会。
这篇文章的一个版本也出现在 《自然语言工程杂志》 。
介绍
几年前,我被要求分析和比较一些基于云的文本分析 API。这项工作的一个间接结果是一对观点文章(见这里的和这里的和和),它们研究了市场上许多更知名的文本分析工具的功能。这些文章引起了我足够的兴趣,促使我发表了一篇更长更详细的对 35 个商业文本分析 API 的深入分析。
我给你这个背景是为了给这篇文章的重点建立一点可信度:在过去的几年里,我花了大量的时间探索和试验大约 50 种不同的 API。这样做的一个副作用是,我看到了各种各样的方法,从许多不同的方面使软件在云中作为服务可用。有些产品近乎完美,结合了高质量的工具和出色的用户体验;其他人,嗯,没那么多。
让我们假设一个软件服务的实际性能质量是给定的:如果一个工具没有做它应该做的事情,那么它将很难获得牵引力。但是性能是不够的:在决定一个 API 是否成功的过程中,还有许多其他的因素在起作用。在这篇文章中,我提请大家注意五个方面,我们可能会广泛地认为这是用户体验,它们影响了我对所遇到的工具的印象。
我对这些特征的重要性的评估不可避免地属于主观领域。但是,虽然不太可能每个人都同意我的观点,但我认为也不太可能只有我一个人有这种困扰。因此,与其冒着失去像我一样的潜在用户的风险,我想建议,下面讨论的问题至少值得任何打算进入 API 市场的人,或者,事实上,已经出现在该市场中的人花几分钟考虑。我认为,如果你避免这些失误,你的企业就有更大的成功机会。
我应该强调的是,这些观察都是在分析文本分析 API 的特定上下文中做出的,因此应该从这个角度来看待它们。但是我认为提出的观点对于许多其他种类的 API 都是相关的,所以我希望它们在这个狭隘的焦点之外还有一些价值。
背景设置的最后一项:你可能认为下面指出的一些问题是相当明显的。我有充分的证据,以存在证明的形式,它们显然不是对每个人都显而易见的。
1.有一个专业的网站
第一印象很重要。对于大多数潜在用户来说,你的网站将是了解你的 API 的入口,所以在这方面花费合理的努力是值得的。
不幸的是,一些 API 的网站看起来像是在 20 世纪 80 年代建立的,那时候使用 Emacs 或 vi 手工制作 HTML 很普遍。从那以后,网页设计和底层技术都在不断发展。现在,使用 WordPress 和 Wix 这样的平台,拥有一个看起来现代、设计良好且美观的网站变得非常容易,而不必自己编写所有简洁的功能。
当然,外表可能具有欺骗性。如果你确实从 20 世纪 80 年代就开始工作了,你现在可能已经很有成就了,你的技术在市场上已经得到了很好的证明。但是一个看起来像是用原始 HTML 创建的网站并不能鼓励我认为你在 API 中使用了最新的技术。这更可能让我怀疑你的技术是否也停留在 20 世纪 80 年代。
你不一定要成为一名网页设计师才能自豪地拥有一个令人敬畏的网站:如今,你可以通过 Fiverr 和 Upwork 等服务以相当低廉的价格获得优秀的设计技能。一旦你的网站建成,请一个独立的第三方用批判的眼光审视你的网站,并坚持让他们给你一个诚实的意见。
不言而喻,特别是如果你提供某种语言服务,但是一定要再三检查你的网站内容的拼写、语法和风格。
当然,欺骗性的外表是有利有弊的:我也见过一些非常惊人的网站,它们背后的产品非常有限。但是我敢打赌,寻找像您这样的工具的客户更有可能跳过似乎来自不同时代的网站。
2.让试用你的软件变得容易
让我很容易就能接触到你的 API 并进行测试。如今,大多数网站使用完全自动化的注册机制,潜在用户通过网络表单提供他们的姓名和电子邮件地址,并立即通过回复电子邮件获得一个 API 密钥,用于解锁可用的服务。唉,一些供应商选择引入更多的摩擦。最糟糕的是手动处理访问请求:请不要让我等上一两天,等你们那边的人来读他们的邮件并授予我权限。请不要让我填写一个长长的表格来解释我想如何使用你的 API。人生苦短,穿不了太紧的鞋子:我会放弃,转向另一个供应商。
当你发送一封带有 API 密匙的邮件时,确保你在邮件中提供一些指示,说明这些密匙是用于什么产品和网站的。我知道这听起来有点奇怪,但是如果你碰巧同时注册了一系列这些东西,那么从第三方订阅服务帐户收到一封电子邮件是没有帮助的,该帐户的名称与所讨论的 API 没有关系,并且实际上没有提到该密钥提供对什么产品或网站的访问。我仍然有一封邮件藏在我邮箱的底部,永远成为零收件箱的障碍。
3.提供有意义的试用访问
许多 API 都提供某种类型的试用访问,因此您可以在决定订阅或购买使用该服务之前进行试验和探索。这当然是好事。如果你不提供试用访问,我强烈建议你这样做:你的竞争对手很可能会这样做,所以不这样做,你就通过阻止产品自己说话来削弱你的营销努力。
API 的试用访问模型分为两大类,这两类都很常见。首先,您可以向用户提供对无限期免费订阅层的访问,该订阅层对可以进行的 API 调用的数量有所限制,或者对可以访问的特定功能有所限制(参见下面的提示 4)。或者,您可以让用户在有限的时间内免费访问全部或部分可用功能。
从用户的角度来看,免费层是更好的选择。我知道你可能不想承担无限数量的终身用户的风险,他们会慢慢耗尽你的资源,却从来不为他们付费。不幸的是,你正在竞争一个更大的领域,像谷歌、微软和亚马逊,有足够高的使用上限的免费层,你实际上可以构建重要的应用程序而不会产生费用。现实是,这些技术中有许多实际上是商品;因此,你现在令人敬畏的网站(见上面的提示 1)不太可能让我相信你是如此独特,以至于我会忍受限时试用。
尽管如此,如果限时试用是你必须要走的路,请至少提供一段合理的时间来试用产品。一周(我已经看到了,伙计)是不合理的:通常情况下,不可预见的情况将意味着在潜在客户有机会正确探索产品之前,试用期就到期了。比较明智的免费试用期是一个月。
理想情况下,您将通过自动订阅机制提供一个免费层。对点击次数施加限制是好的,只要它不是一个低得令人难以置信的数字:为了能够测试大多数 API,每月几千次点击是合理的。300 次免费点击(我不止一次看到过——300 这个数字是怎么回事?),另一方面,可能不值得麻烦地编写一个测试工具。
4.让我试试你所有的特征
有一种趋势——也许并不奇怪——把一个 API 的一些特性标得如此特别,以至于只有付费用户才能使用它们。这些通常是 API 的最新和最先进的特性。例如,一些供应商基于不同的底层技术区分特定功能的两个版本,新的和更好的版本(在撰写本文时,这通常是利用深度学习)禁止试用用户使用。
如果这些特性是你的产品区别于市场上其他产品的原因,你为什么不让潜在客户尝试一下呢?
保持你的收费模式简单也是一个好主意。大多数 API 提供一系列服务(例如,命名实体识别、情感分析和文本分类),并且出于记账目的,每个服务的使用都被算作单个 API 命中。但是一些供应商选择根据所调用的特定服务来收取不同数量的单位费用(因此,例如,分类可能比情感分析花费更多),或者甚至对包含特定类型的结果收取额外费用(例如,包含实体链接可能比简单的实体识别花费更多)。如果你试图计划一个免费试用上限下的测试制度,这只是一个头痛的来源。
5.提供体面的文件
许多 API 都没有被很好地记录,有时甚至非常严重。
例如,如果您提供一个命名的实体识别端点,该端点将类型分配给实体,那么指出识别的类型集是什么是很重要的。如果您提供文本分类服务,记录可以分配的类别集是很重要的。没有这种信息,就很难对 API 进行合理的测试。
如果对 API 的调用可以包含许多参数,记录它们是什么和它们的含义,以及默认值是什么。如果您的 API 返回一个大的属性集合,那么清楚地记录它们:当然,有时从它们的名字就可以很明显地看出,但是更清楚总是更好。如果不清楚返回的属性是什么,还不如不返回它。
我强烈建议使用正式的 API 文档框架。像 Swagger 这样的工具在强加文档的结构和规则方面帮助很大,尽管你仍然需要确保你不会落入提供与内容无关的功能描述的陷阱。给出完整的工作例子,最好是在一系列的编程语言。我的意思是完整的:我应该能够一字不差地剪切和粘贴您提供的示例,并让它们按原样运行,也许以插入个性化的 API 键为模。
为每个发布的 API 版本使用版本号是一个好主意——最好包含在管理信息中,管理信息是 API 返回数据的一部分——这样我就能知道什么时候发生了变化。
不要依赖博客文章作为 API 文档的单一来源。也就是说,博客文章可以为已经有良好文档记录的 API 增加重要的支持,并且有一些供应商在使用博客文章演示详细的用例等方面做得非常出色。
总结
我认为有些 API 的提供者不做我上面推荐的事情可能有合理的原因。
可能还有其他优先事项,这意味着你没有时间或资金来建立一个伟大的网站或更新一个你不久前建立的网站。很公平,但这可能是一种虚假的节约。
也许你的 API 如此特殊,以至于很难访问,或者对免费使用施加严格的限制,或者限制我可以尝试的能力,只会增加稀缺价值。但是很有可能,用不了多久,其他人就会抓住机会复制你的服务,并以更方便用户的方式提供它。
也许你的目标市场由不需要文档或明确例子的读心术者(或前雇员)组成。但我认为,这可能是一个比你原本可能吸引的目标市场更小的目标市场。
归根结底,这都是为了减少摩擦。如果你给潜在客户制造的麻烦比他们需要的稍微多一点点,他们就有可能在其他地方找到他们想要的东西。明显?或许;但是以上的错误仍然会一次又一次的发生。正如我在这篇文章开头所说的,我有存在证明。你知道你是谁。
最后一件事:如果你已经尝试过 API 业务,但是决定放弃它,并且你的 API 不再被支持,请把它拿下来。不要让它处于僵尸般的状态,亚马逊偶尔会收你 56 美分,你甚至不会注意到。在你的联系邮件上放一封自动回复邮件,告诉我你不打算回复,这样我就不会夜不能寐,担心你是否会回复我关于那个模糊名字的参数。
想了解商业自然语言处理领域的每周动态,本周在 NLP 注册 。
原载于 2019 年 9 月 26 日https://www.cambridge.org。
为开源软件做贡献的五个技巧
数据科学家的视角

Photo by Yancy Min on Unsplash
为开源软件(OSS)做贡献是一件值得的事情,尤其是对新的数据科学家来说。它有助于提高技能,在项目协作时提供宝贵的经验,并给你一个展示代码的机会。然而,许多数据科学家并不认为自己是受过正式训练的软件开发人员。这使得为 OSS 做贡献成为一个可怕的提议。
恐惧的一个来源是,似乎软件开发过程中的每一步,从设计到持续集成,都有一套在数据科学培训中经常被忽略的最佳实践。这让数据科学家感到装备不足,无法参与为软件开发人员量身定制的领域。已经有文件证明甚至一些熟练的软件开发人员在决定为 OSS 做贡献时也会焦虑,嗯,对于一个数据科学家来说,这种焦虑会成倍增加。
我们每天使用的几乎所有流行的数据科学库都是成功的 OSS 项目,有时由社区维护并经常改进。想到 Scikit-learn、PyTorch 和 TensorFlow。看,数据科学家可以写出高质量的代码!最终,数据科学家编写代码的目的是将模型交付生产或部署健壮的数据管道。既然我们正在编写软件,我们应该像其他软件项目一样坚持同样的标准。
在这篇文章中,我想分享一些我在过去几年开发数据科学软件中所学到的东西。我的目的是分享我希望有人在我开始数据科学之旅时与我分享的信息。希望犹豫是否开始的没有经验的数据科学家将使用这些提示,感觉被授权开始编写更好的数据科学代码,并尽早为 OSS 做出贡献。
- 找一个有经验的软件开发人员…
…请注意!这个项目对我的个人发展有着最大的影响。我目前所有的软件开发习惯都是以某种形式将有经验的软件开发人员融入到我的生活中的直接结果。值得注意的是,你不一定要和这些人成为最好的朋友。有经验的开发人员可以是你团队中的技术负责人、同学或当地聚会上的人。此外,有一些真正伟大的开发人员定期发推特,在论坛上发帖,写技术博客,并创建播客(我最喜欢的是 Talk Python )。尽最大努力找出不是数据科学家的人,因为他们可以为你提供在数据科学领域更难找到的观点。
这个提示的关键是听跟这些人搞。首先,问问自己,“他们为什么要实施这样的方法”?如果你不明白,就去查。如果你还是不明白(或者如果你明白了),给他们发一封电子邮件或者回复一条评论,让他们详细阐述他们的观点。很有可能,这些人在他们的决定背后有很好的理由。从软件开发的角度来看,在你的问题的上下文中看到或听到这些原因会让你更好地理解这个主题。我不建议你不断地追逐有经验的开发人员,尊重他们的时间和空间是很重要的。
2.开源社区并不是来抓你的****
阻止人们为 OSS 做贡献的一个不幸的误解是,如果他们公开他们的代码,将会被嘲笑或奚落。大多数时候,这是不正确的。这和其他类型的社区没什么不同,几个害群之马就能毁了所有人的聚会。我为小型 OSS 项目、流行的 OSS 项目做出了贡献,甚至还维护了一些我自己的项目。在每一个场景中,每一次互动都是最起码的尊重,而且在大多数情况下,人们都很感激。在为 OSS 做了两年多的贡献之后,如果有人对我不尊重,那么在这一点上他们显然是局外人。
3.练习 git 等。****

It’s never been easier to practice git, all the major hosting platforms allow users to create unlimited private repositories.
一些人声称“不熟悉 git”或者仅仅是“不熟悉 GitHub”作为他们不为 OSS 做贡献的理由。这就是为什么第三个数字的标题包含“等”。GitHub、Bitbucket 和 GitLab 具有大量支持 git 工作流的功能,但本质上并不被视为“git”的一部分。因此,即使对于一个从未使用过 GitHub 的有经验的 git 用户来说,在熟练的道路上仍然有一个轻微的学习曲线。
不要在本地编写所有的项目,而是预留一些额外的时间来推进到上面提到的一个平台。将其设为私有,如果你愿意,你仍然可以安全地与朋友共享回购。在重定一个特性分支的基础后,练习发出 pull 请求或直观地检查 git 提交历史。更加熟悉私有存储库中的选项卡、按钮、工具和 git 工作流将会让您在处理公共贡献时更加自信。
你很难找到一个成功的数据科学项目不使用某种形式的版本控制来进行软件开发,git 恰好是最受欢迎的。因此,试着在日常项目中使用 git 工作流。然后,当在 OSS 项目上与人合作时,你不会被 git 工作流程的失误所困扰,你会把时间花在代码上。
4.使用** 软件最佳实践**
Kaggle 的数据科学家 Rachel Tatman 最近发布了一个视频和一个内核,概述了如何编写更专业的数据科学代码,因此我将建议遵循她的建议,在我们重叠的地方保持简短。当您与合作者互动时,这些都将使您的生活变得更加轻松,再次允许您专注于添加功能,而不是回去清理坏习惯。
4a。使变量名可读****
其他数据科学家可能知道i和j是数组A的元素,但是数学符号并不能转化为源代码中的信息变量名。我喜欢使用在重读源代码或别人第一次阅读时有意义的变量名。但是我会让 Will Koehrsen 为数据科学家的命名惯例提出建议。
4b。慷慨评论****
不管他们是否意识到这一点,数据科学家经常实施一种叫做方法链的编码风格。当内存中有一个 DataFrame 对象时,我们通常知道下一步将一列转换成我们想要的格式的确切步骤。方法链接的一个缺点是链接很难调试,尤其是没有注释的时候。写评论的时候想想别人,记住,一个简短的评论可以帮你很大的忙。
4c。编写单元测试****
你以后会感谢自己,或者无论是谁在审核你的拉取请求,都会感谢你。有时候单元测试对于小型的一次性数据科学项目来说没有意义。但是当你考虑扩展你的代码或者与他人合作时,单元测试变得更加重要。
当代码是模块化的时,它更容易测试,也就是说,编写函数的目的是执行一两个操作。下面是一个用 Python 规范化熊猫数据帧的函数的例子。此函数的单元测试断言平均值和标准偏差分别为 0.0 和 1.0。如果这个测试失败了,开发人员就知道“normalize_dataframe”方法出错了,并且知道从哪里开始调试。
Test that the normalize_dataframe function is working as expected.
4d。使用持续集成(CI)****
恭喜你,你写了单元测试并把你的代码推送到 GitHub,但是几天后,有人报告说他们不能让你的代码在他们的机器上运行。CI 将有助于在这些问题成为 GitHub 上的问题之前发现它们。 Travis 是一个 CI 服务的例子,它直接与 GitHub 免费集成,用于公共回购。CI 服务允许您在容器化的环境中构建和测试代码。我建议为您打算与其他人合作的项目建立一个 CI 服务之一。

Travis interface showing a passing build with multiple versions of Python on a Linux kernel.
5.使用调试器****
对于数据科学项目来说,使用带调试器的代码编辑器(有时称为集成开发环境(IDE ))似乎有些矫枉过正。但是一旦你学会了如何使用编辑器的调试器,它就会成为软件开发的强大工具。调试器允许用户执行代码直到一个断点,然后探索变量名称空间,这使得“调试”程序崩溃的原因变得更加容易。在放弃希望和切换回笔记本电脑之前,你应该给自己至少 30 天的时间来试用调试器进行开发。花点时间阅读教程,并询问更有经验的软件开发人员(可能来自技巧 1 ),他们是否可以带你调试下一个 bug。
Jupyter 笔记本是许多用户首次开始数据科学项目时的热门选择。笔记本是探索性数据分析和可视化的好工具,但是它们也有缺点,Joel Grus 在这里对其进行了评论。也就是说,这些单元不能乱序运行,它们对于初学者来说很难理解,并且会助长坏习惯。两个流行的 IDEpy charm和 VS Code 已经认识到笔记本的流行,最近直接在 IDE 中集成了对它们的支持。有希望的是,数据科学家将会为了笔记本支持而来到 ide,但是为了调试器而留下来。
奖励:乐于接受建设性的批评****
请记住,OSS 是通过正式和非正式的代码评审来维护和改进的。人们会看你的代码,评论它,可能会要求你修改几行。如果你已经走了这么远,这不就是你一直在寻找的吗?有人花时间审查你的代码,并花费足够的精力去尝试和改进它。
随着您的软件开发技能的提高,您会遇到其他用户要求您修改代码,但是您可能希望保持原样。用你期望得到的尊重来捍卫你的决定,这和做一个明智的人没什么不同。
我希望这些建议对你有用。这些技巧是针对开源的,但也适用于商业数据科学家和开发人员。随着您对其中一些技术的熟练掌握,您应该会注意到,您在 OSS 环境中识别和修复 bug 或实现功能所需的时间减少了。通过现在投入时间成为一名更好的程序员,从长远来看,你将有更多的时间来解决世界上的数据科学问题。
数据科学编程入门的五个技巧
尽早投入时间和精力,它会让你日后成为一名伟大的程序员
如果你想成为一名真正的数据科学家,你需要会编码。这是无法回避的。有些人不喜欢这个想法,许多公司已经通过提供“自动化数据科学”产品来利用这种不适——我们会编写代码,所以你不必这么做。如果这些是你唯一的工具包,你就不是数据科学家。
强大的数据科学家的试金石是他们不害怕任何数据集或任何问题。他们可能不知道如何立即处理,事实上,他们不知道是很常见的。但是他们知道他们可以找到如何解决的方法,如果问题再次出现,他们最终可以产生整洁、高效、可重复的代码来处理问题。如果你想成为一名伟大的数据科学家,这就是你需要瞄准的心态。
一个强大的数据科学家的内在自信和能力很大程度上来自于他们最初是如何学习编码的。如果你刚刚开始,你如何开始最初的几周和几个月的学习对你是否会在以后的日子里茁壮成长至关重要。如果你采取懒惰的方法——如何而不是为什么——你会养成一些习惯,这些习惯会让你以后变得不自信和效率低下。如果你尽早开始工作— 了解如何做和为什么做— 随着时间的推移,你会逐渐开始感觉到自信的建立和能力的增长越来越快。
这里有五个小贴士可以帮助你在开始学习时有一个良好的开端。
1.选择正确的学习资源
人们以不同的方式学习。比如我不太擅长视频学习。我需要一份详细的书面叙述,我可以按照自己喜欢的节奏仔细分析和理解。
避免过于实用的来源——这意味着它们向你展示了该做什么,但没有解释为什么会有效。如果你复制粘贴一个方法来解决一个编码问题,而你不知道为什么这个方法有效,那么你就没有真正学到任何东西,因为如果以后再次出现类似的问题,你将不知道如何应用这个方法。
好的学习资源投入时间来分解方法的底层逻辑。最好的方法实际上是鼓励你通过提示和技巧自己编写方法,而不是给你现成的东西。深思熟虑的教育者会提供后续问题,要求你把你所学到的东西应用到另一个环境中,以证明你已经学得很好了。
很难在一个在线模块中找到所有这些内容,因此我建议您准备一个书面资源,以便用您选择的语言进行深入学习。如果你问朋友、同事或同学他们用什么,在接受他们的推荐之前,确保他们有类似的学习理念。
2.参与游戏
就像一个运动队在有奖金的情况下会更加努力一样,如果有激励,你会学得更好。激励是而不是学分,如果你完成了一个在线模块,你可以把它放在你的简历上。激励是真正的成就,它让你现在或未来的工作变得更好、更强——你和其他人可以明显地看到事情是如何因为你投入的工作而得到改善的。
举个例子,当我第一次学习编码时,我在自己的一个数据集上给自己设定了一个挑战。我的同事每年通过 Excel 处理几十万行数据。这是一项高度手动的工作,并且每年花费的时间越来越长,因为 Excel 一直在努力处理不断增长的数据集。
在我学习基础知识的同时,我也花时间将我的新知识应用到这个数据集上。这并不容易。我犯了很多错误,花了很长时间试图找出问题所在以及如何解决。但是这个反复试验的过程很重要,因为它迫使我接触到我正在学习的语言的内部运作,并深入了解它在幕后是如何运作的。
几周的工作产生了一个完全自动化的脚本,可以轻松处理这些越来越大的数据集——这让我和我的同事们既兴奋又敬畏。我的学习带来的切实好处是显而易见的,它给了我继续前进的动力和信心。
处理你自己非常熟悉的数据集是将早期学习付诸实践的最有效的方法之一。避免来自互联网的随机数据集,在那里你可能不理解变量代表什么或者各种操作是合理的和相关的。游戏里有皮肤就好多了。
3.错误是你的朋友,不是你的敌人
当你第一次学习时,你会犯很多错误。但是,如果你以正确的方式回应他们,这确实是一件好事。
无论您选择哪种语言,对于未受过训练的人来说,错误消息可能显得简洁或无用,但是多花一点时间在它们上面,十有八九,您会对您的命令不起作用的确切原因有一个相当好的理解。这一点很重要,因为如果你明白为什么这次没有成功,你就知道下次如何让它成功。
很多时候,我看到朋友和同事完全无视错误信息的内容,直接向我或其他人寻求帮助。由于我已经学会将错误消息视为我最好的数据科学朋友,所以我经常可以看一眼错误,然后直接告诉他们问题是什么。
当您看到错误消息时,请将其作为解决问题的主要途径。通常它会提到另一个功能或操作,你也需要深入了解,以了解哪里出错了。所有这些都是深入了解你所处环境的重要组成部分。
4.在添加附件之前,先学习您的基础语言
像 R 和 Python 这样的语言受益于丰富的附加组件和包生态系统,以帮助导入某些常见任务或问题所需的功能。但是注意不要太快地投入其中。这些软件包依赖于它们的基础语言,没有基础语言就无法运行。如果你变得过于依赖这些而没有对你的基础语言有一个像样的理解,你会让你自己的生活变得更加困难。
如果您不了解基础语言中的数据类型和数据结构是如何工作的,或者如果您不完全理解您的系统如何区分基础功能和导入功能之间的优先级,您可能会最终陷入各种各样的困境,您不知道如何摆脱困境。错误会突然出现,你不知道它们是什么意思。函数可能会产生一个完全出乎意料的输出,您对此一无所知。
在早期,我给自己设定了一个挑战,在尝试使用附加包之前,先用基本语言完成一项任务。在我学习之初,当我的操作相对简单时,这对我理解基础语言非常有益。我向任何处于学习初级阶段的人推荐这种方法。
5.拥抱社区
我喜欢在开源数据科学领域工作的一个主要原因是它的社区。不管你正面临什么问题,很有可能有人曾经面对过它,并能给你建议来帮助你学习。没有任何一本教科书能够涵盖你在学习过程中可能遇到的所有问题,所以随着你的学习进步,社区将逐渐成为你的重要资源。
新手可能会害怕社区,但是真的没有理由害怕。最大的沉默往往是理智的。这是个愚蠢的问题吗?我会受到尴尬的斥责吗?你的一点点思考和关心可以帮助减轻你的担忧。
首先,仔细选择你的社区。如果你是一个初学者,不要在 Twitter 标签上发布问题,这样会把问题推给有经验的程序员。找到与你的发展水平相匹配的在线群组和标签,并把你的问题导向那些人。
如果你使用的是更广泛的资源,比如 StackOverflow ,学习它的规则并遵循它们。如果你是一个初学者,你问的问题很有可能已经被回答了,所以在你考虑把它作为一个新问题发布之前,仔细搜索一下。如果你把它作为一个新问题发布,确保你是真正具体的,并提供一个你的代码的最小可复制的例子。如果你发布了一个没有例子的普通问题,你肯定会被批评,这是你应得的!
如果你的问题得到了回应,但你认为它太简短了——例如,有人只是发布了你需要的代码,却没有解释——不要害怕要求他们解释为什么它能工作。大多数回答者希望提供帮助,他们希望在平台上建立自己的声誉,因此他们通常愿意扩展他们的回答。
如果你正在开始你的数据科学学习之旅,并且你渴望在未来成为一名伟大的数据科学家,这些只是我推荐的几件事情。祝你在这激动人心的旅程中好运!
最初我是一名纯粹的数学家,后来我成为了一名心理计量学家和数据科学家。我热衷于将所有这些学科的严谨性应用到复杂的人的问题上。我也是一个编码极客和日本 RPG 的超级粉丝。在 LinkedIn 或Twitter上找我。

ML 帮助广播公司实现新效率和重塑 CX 的五种方式

资料来源:unsplash.com
随着网飞和 Hulu 等广播巨头主导市场,赢得眼球并让观众持续关注你的视频内容绝非易事。但也不是不可能。人工智能和人工智能开发专家建议创建一个连贯的营销策略,并使用视频和图像分析技术的成功组合来取得成功。
自动广告检测
如果您的业务需求是让观众免受商业广告的烦扰,请实施高级视频分析。这种方法能够基于一组模式(如无声黑框或镜头切换率)准确识别广告插播,并自动删除它们以增强观看体验。
类似的机制可以用于驱动个性化广告。通过检查观众当前和过去的活动,并应用 ML-powered 视频内容分析,您可以获得有价值的见解,并可以轻松确定最佳广告类型、最相关的插入位置以及最佳的投放方式,从而最大限度地提高广告活动的效果。
智能异常发现
自动检测视频序列中的异常事件非常具有挑战性,因为这些事件的定义并不明确,但并非不可实现。
ML 专家使用卷积长短期记忆(Conv-LSTM)网络和其他集群化方法来预测视频序列的演变,并隔离异常内容,如故障、黑屏、人工文本等。
该流程以每帧 1 毫秒的 FHD 视频处理和全面的后处理为前提,可以轻松集成到您的 QC 工作流程中,以确保完美的内容交付。
严格的法规合规性
当您向全球数百万观众提供内容时,没有出错的余地。您不仅应该提供高质量的视频,还应该对其进行调整以符合特定国家/地区的特定法规。
手动操作会浪费时间和金钱,并增加出错概率。想想通过支持 ML 的 MAM 编排来自动化内容审核和法规遵从性流程。这包括全面的视频分析(通过对象和面部识别),以及在创纪录的时间内准确检测成人内容、暴力、种族主义、不良语言或敏感主题(如吸烟和饮酒)。
自动标记和删除因政治或宗教原因不合适的内容,使您始终严格遵守当地法规,同时保护您的品牌并改善观看体验。
智能内容个性化
在观看内容和观看方式上有如此多的选择,顾客变得比以往任何时候都更加挑剔。考虑到他们的观看行为正在快速变化,你应该准备好在内容、付费和订阅方面提供最相关的建议。
为了实现这一目标,首先要收集所有可用的数据,包括应用内用户行为、社交数据、电影元数据、电影受欢迎程度、排队项目、人口统计、位置、语言等等。下一步将利用最佳的 ML 方法,如聚类算法、马尔可夫链或关联规则,进行广泛的数据分析并自动提供个性化服务。
智能生成促销材料
ML 也是自动制作引人注目的营销材料的一个有前途的途径。分析观众的偏好,如最喜欢的类型和演员,以生成最佳的电影缩略图,预告和预告片。通过这种方式,您将能够增加内容消费并提高客户参与度。
如果你是一名体育广播员,考虑实施 ML 来创造亮点。神经网络将进行全面的实时场景、比分和事件检测,以及音频分析和球员跟踪,以立即生成引人入胜的比赛集锦,节省您繁琐的手动工作,提高您的运营效率。
最后的想法
ML 已经从一个炒作的话题发展成为带来长期利益的强大技术。因此,如果您愿意将您的服务提升到一个新的水平,请考虑构建一个强大的数据驱动型解决方案,能够自动化一系列业务运营,并为增强决策和客户体验提供可操作的见解。
在机器后面修理机器
为什么我们需要解决人类决策中的偏见以改善技术及其未来治理,以及如何做到这一点

Photo by Javier Allegue Barros on Unsplash
2017 年将人工智能偏见带入了主流意识,从那时起,公众的关注度只有激增。关于人工智能中偏见的原因,以及更令人烦恼的根除偏见的困难已经说了很多。这些评论大多承认机器偏见是人类偏见的反映;然而,很少有提议的解决方案真正解决了根本原因:人类。
这种缺席并不奇怪。偏见在我们这个物种的整个历史中一直存在;这将是一个需要解决的大问题。相比之下,从外部关注如何调整一个行为不端的人工智能要诱人得多。
或许是时候转移焦点了。在机器中复制我们的决策过程的好处之一是,我们有机会看到自己的映像,并从中学习。事实证明,我们从追踪人工智能偏见中学到的经验教训可以为解决我们自己不完美的人类决策中的偏见提供一个有用的框架。
为什么现在这种内部操作系统升级特别重要?毕竟,这么长时间以来,我们已经设法克服了偏见的负面影响。
两个因素使得这项工作变得紧迫。首先是一个并不太新的现象,但它正变得越来越明显:技术前所未有的可扩展性意味着设计中的任何错误都会产生相应规模的影响。第二个因素是,我们正处于技术治理的转折点。政府、技术专家和利益相关者——让我们称这个集体为“设计师”——正急切地开始一个新的监管阶段。
正如每次人工智能设计师创建一个系统,他们都冒着在其中建立盲点的风险,这些治理设计师也冒着将自己的盲点投射到他们提出的解决方案中的风险。当这种情况发生时,技术和监管技术的治理系统都有可能以难以控制的规模放大决策中的错误。
如果我们希望设计出解决技术意外后果的方案,并预见未来的问题,我们必须首先检查这些机器背后的机器:我们自己。
看到我们自己对人工智能的偏见

Background by Alina Grubnyak on Unsplash
在人工智能中引入偏见的许多方式中,有三种现象与检验我们自己的决策特别相关。(关于人工智能偏见的更多信息,请参见郝凯琳的总结这里。下面引用了一些段落。)
让我们来看看偏见是如何通过问题框架、处理盲点、狭隘的社会背景而引入的。
诬陷问题。当计算机科学家创建深度学习模型时,他们做的第一件事就是决定他们实际上想要它实现什么。例如,一家信用卡公司可能想要预测客户的信用度,但是“信用度”是一个相当模糊的概念。为了将其转化为可以计算的东西,公司必须决定是否想要,比如说,最大化其利润率或最大化得到偿还的贷款数量。然后,它可以在该目标的背景下定义信用度。该选择可能导致意想不到的后果或有害行为;如果利润最大化是目标,计算机可能会推荐掠夺性贷款。
…
未知的未知数 。偏差的引入在模型构建过程中并不总是显而易见的,因为您可能直到很久以后才意识到您的数据和选择对下游的影响。一旦你这样做了,就很难追溯性地找出偏见的来源,然后找出如何消除它。"
缺乏社会语境 。类似地,计算机科学家被教导构建问题的方式通常与思考社会问题的最佳方式不兼容。
毫不奇怪,这些人工智能设计错误听起来与人类决策中的偏见相似。
人类往往看不到更大的图景,我们狭隘的理解导致我们可能不会做出选择,如果我们有更好的数据,或者对我们的环境有更完美的理解。我们在不知道其下游影响的情况下做出选择;事实上,我们在不知道形成我们决定的上游影响的情况下做出选择。
一种思考方式是,我们的决策(D)是几个变量的函数:我们拥有的信息(I ),我们如何解释该信息的价值观+经验(ve ),以及给定情况下的优先级(P ),它赋予我们解释的不同元素以权重,并允许我们选择行动路线。我们有多少次可以说我们真正意识到了所有这些变量?
剧透:不常。我们意识到我们的一小部分决定、情感、行动和行为。此外,当我们做出决定时,我们的无意识情感认知经常与我们的有意识认知沟通,我们回溯性地运用有意识认知向自己解释我们是如何做出决定的。即使我们认为我们意识到了自己的过程,但我们通常并没有意识到。
[*人工智能偏见的另一个领域与人类认知有明显的对比是有缺陷的数据:有可能在数据集上训练人工智能,要么 1)不代表现实;2)反映现有的偏见;或者 3)在准备阶段变得有偏见,当设计者选择哪些属性对 AI 系统重要时。这篇文章的重点是解决社会背景,框定问题和盲点,但更多关于有缺陷的数据偏见如何表现的阅读,请参见犯罪和累犯用有缺陷的数据训练的预测工具中的性别和种族偏见。]
技术和治理中的意外后果

Background by Markus Spiske on Unsplash
监管人工智能并不是治理设计师让我们不完美的人类决策发挥作用的唯一空间。
技术方面的一系列治理挑战——尤其是在互联网方面——与日俱增。它包括从消失数据隐私的一切;对关注注意力经济和推荐算法播激进化;在维护言论自由和防止有害内容之间找到平衡;到假新闻和侵蚀信息完整性;到巨魔和机器人淹没真实的话语和延续在线骚扰(最重要的是,不成比例地影响妇女和少数民族);即使在我们开始处理直接选举干预之前,也要过滤那些孤立社区和冲击公民言论的泡沫。所有这些挑战都因以下事实而加剧:少数平台主导着在线媒体市场,威胁着内容选择的多样性,并引发其他垄断担忧。
委婉地说,这是我们的设计师需要应对的复杂挑战。为了迎接这一挑战,他们不仅要成为技术娴熟的程序员和法律从业者;他们还必须应对一系列棘手的道德、哲学和社会权衡。在这些不同的演员面前考虑一些棘手的问题。
私营公司的关键问题:
在线平台应该保持一个自由开放的市场,让消费者成为自己消费的最终仲裁者吗?或者他们应该参与主动 监管内容的以保护他们的消费者免受其产品有害副作用的影响?如果是后者,公司如何避免无意中滥用审查权?
用户 数据 的 商品化 是社会可持续的商业模式吗?公司应该在多大程度上优先考虑股东的财务回报,而不是外部性的定价(如保护消费者隐私或选举诚信)?
民主政府的关键问题:
政府应该如何权衡自由开放的互联网促进民主的效果与自由开放的互联网破坏民主的效果?考虑一下:美国、德国、英国、、欧盟委员会、法国、马来西亚和捷克已经在采取措施遏制假新闻和/或仇恨言论。然而,这种立法也可能被专制政府用来镇压民主,俄罗斯的情况很可能就是如此,该国在德国的 NetzDG 法通过后立即复制了该法;人权专家预计,俄罗斯会以关闭假新闻为幌子,用它来压制政府反对者。
在保护公民权利的过程中,政府应该如何确保他们自己的科技行动不会损害公民权利?
国际治理的关键问题:
主权原则和针对具体国家的规则是否应该适用于网络环境?即使在已经批准了联合国《公民权利和政治权利国际公约》第 19 条(全球知情权)的国家,寻求正义的公民也会面临同样的执法困难,这已经使国际治理领域变得复杂。
当政府在国际市场上的要求违反了国内政府的法律时,企业应该如何应对?例如,全球网络倡议成员和美国公司微软承诺保护用户的隐私权,以回应政府的监控要求;尽管如此,他们仍在人工智能方面与中国合作,这可能有助于中国已经很大的国家监控。微软并不孤单:所有希望在中国开展业务的国际公司都必须遵守其在国内存储数据的要求,允许中国安全部队访问关键的用户数据。
这些例子只是我们的设计师要解决的问题的一小部分。考虑到利害关系,我们不能在答案中加入偏见。我们需要设计师严格地询问他们自己的决策过程:他们正确地设计了问题吗?他们确信他们的信息没有缺陷吗?他们是否意识到自己的上下游盲点?最重要的是,他们是否将这些问题置于适当的社会背景下?
让我们探索一种方法,设计师可以确保他们的答案是“是”
沉浸作为解决偏见的一种手段

Photo by Alina Grubnyak on Upsplash
最近,一家科技公司派了一群高层领导去西弗吉尼亚州南部的一个小镇进行为期一周的学习考察。从表面上看,这个地点是一个奇怪的选择:该镇人口约 3000 人,网站数量微不足道,不是一个有意义的用户市场,尤其是对于一家在全球范围内衡量业务的公司而言。
随着来访的高管与当地企业主、政府官员和家庭交谈,他们与企业的联系开始显现。在自动化破坏了工作的主要来源(采矿)后,该地区遭到了破坏,来访的高管们发现自己沉浸在几代人的绝望感中,这种绝望感是由根深蒂固的缺乏机会造成的。神圣的 sht* ,他们中的几个人意识到,我们即将在我们的自动驾驶上做同样的事情。其他人思考了围绕他们海外数据中心建造的城镇与美国公司城镇的残余部分之间的相似之处,当矿业公司突然搬到其他地方时,这些城镇遭到了破坏。我们太专注于扩展,他们意识到,我们忘记了关注* 对当地的影响。*
这一经历促使人们对公司在世界上的角色和责任进行了严格的审查,最终导致这些高管的决策过程重新调整。
他们了解到的信息并不新鲜。事实上,一些访问者可能之前就已经把这些点联系起来了。然而,人类惊人地不擅长仅根据信息行动;经历和情感是一种更为可靠的方式,可以获得推动决策的理解。这些高管通过身临其境的体验拓宽了他们的社交范围,意识到盲点,并在这样做的过程中,不仅为他们的同事和最终用户,也为他们的行动所影响的更广泛的生态系统培养了同理心。
识别我们的社会背景是一种有效的方法,既能打破我们的偏见使之变得棘手的问题,又能预见不可预见的后果。
2017 年,一群可持续发展专家进行了一次类似的浸入式旅行。随着《巴黎协定》的通过,该集团取得了巨大的胜利,但在随后试图兑现该协定的承诺时,却遭遇了强烈的阻力。他们决定扩大自己的社交范围。在一个极度忠于煤炭行业的城镇沉浸了一周之后——游客们曾一度将煤炭行业视为死敌——该组织对挑战的框架开始发生变化。其中一位参观者是一家全球消费品公司的可持续发展主管,他是这样说的:我职业生涯的重中之重是“消灭煤炭”。我刚刚意识到,要实现我的目标,我的首要任务是为煤矿工人创造就业机会。 *
*他还打算保持其他优先事项,但他的观点的精神仍然存在:他觉得他一直以错误的方式思考问题。
该组织的首要目标是帮助世界实现碳排放承诺,这一目标并没有失去,但现在他们对这个问题的看法不同了。回到我们在人工智能偏见方面的课程:在沉浸之前,团队一直像一个被编程为清洁的机器人一样操作;不幸的是,如果它的设计师没有考虑到清洁这一直接目标之外的更广泛的环境,这个机器人可能会无意中打碎任何阻挡它的花瓶。同样,如果该组织忽视了其目标周围的大环境,他们的政策可能会产生意想不到的负面影响(即失业矿工)。更糟糕的是,这些副作用可能会在未来造成更大的后果【见:一个导致 2016 年选举结果的意外副作用的例子】。通过扩展他们的社会背景和重新构建问题,这个团队已经学会了重视和考虑他们所处的大环境。他们避开了一个打碎的花瓶。
在这两种情况下,反思和公开对话对于重新调整决策至关重要。每天,主持人都帮助来访的团队处理他们所经历的事情,梳理出对他们企业当前和未来运营的影响。通过这种高度结构化的过程,小组能够利用他们扩大的社会背景,注意到他们的盲点,并重新检查他们的选择的上游影响和下游影响。
这些沉浸是由领导人的探索领导的,这是一个专门提供这种内部重新校准的组织【披露:我已经为他们工作了近 5 年】。也就是说,有很多沉浸感导致人们在没有专业指导的情况下重新调整他们的决策。德里克·布莱克(Derek Black)是一名 13 岁的程序员,他设计了互联网上第一个也是最大的白人民族主义网站 Stormfront 的儿童页面。
德里克在家接受教育,几乎完全在白人至上主义意识形态的封闭世界中长大。他来自一个著名的白人至上主义者家族:(他的父亲唐·布莱克是风暴前线的创始人和三 k 党大巫师;他的教父大卫·杜克(David Duke)也是三 k 党大巫师),德里克从小就大量参与运动。许多人认为他会继承父亲和教父的衣钵,成为社区领袖。
这些故事反映了我们所有人都曾直观地经历过的一种现象,尽管没有那么戏剧化。更多的时候,扩大我们的社会背景是一种相当日常的体验,一种具有递增效应的体验:我们通过假期旅行,或在地铁通勤阅读材料的页面上,或者,事实上,通过地铁本身,体验扩大的背景。(不过,就我的家乡纽约地铁来说,这可能有点太沉浸了……)。
具有讽刺意味的是,我们在职业阶梯上爬得越高,获得的决策范围和影响力越大,我们拓宽社会背景的机会就越少。有时,这种隔离是物理上的:管理人员的一天被如此管理,以至于他或她直接从家出发,通过私人交通工具往返于办公室,很少遇到他/她所了解和控制的环境之外的世界。这种孤立也可能是智力上的:对高级决策者的时间有如此多的要求,让他们参与上述故事中描述的那种耐心的、非事务性的沉浸是一种反叛行为。更普遍的是,人们期望这些领导人的每一次参与都得到优化,以实现最大效用。
这样的权衡是短视的。最终,我们为根除偏见所做的投资会在我们今后避免的危机中获得回报。
接下来呢?

正如我们已经看到的,融入不同的社区是确保我们尽可能拥有最普遍视角的有效方式。当与开放、诚实的反思相结合时,它可以暴露盲点,并导致我们做出决定的方式发生变化。这个过程看起来像这样:

不幸的是,如上所述的沉浸法非常昂贵,而且可复制性有限。好消息是的运营结构和政策可以复制一些沉浸的价值。多元化团队、多利益主体治理和透明度是几个这样的结构或政策。
多样性:硅谷和国会山的同质性带来了一个重大风险,即盲点将蔓延到科技产品和治理解决方案中。改变 雇佣惯例和招聘有助于解决那些盲点,但这种修补不能解决所有问题。
为了收集更广泛的跨部门观点,公司已经开始尝试从公众或多学科委员会那里征求意见。这是正确的意图,但执行仍然有一些的缺陷有待解决。
技术专家和立法者会很好地关注 多利益主体监管 的例子,这种方法不仅带来了多样性的好处,还具有独特的灵活性、透明度和包容性。虽然没有单一的多利益主体模式,但这种方法在治理技术和其他领域的成功已经被反复证明。
最后,为决策过程提供透明度是一个重要的防故障措施。如果设计师没有充分根除内部偏见,如果正式的团队结构没有提供必要的思想多样性,那么透明度可以让更广泛的受众探究设计师的决策,潜在地捕捉偏见。增加透明度的努力已经开始,包括对“可解释的人工智能”(XAI)的研究,其中人工智能系统的决策过程被从其通常的黑箱中取出(尽管许多技术专家争论 XAI 的有用性)。治理和监管也应该采取同样的方法:设计者需要提供透明度,说明考虑了哪些权衡,以及他们如何在二者之间做出选择。
这里有许多其他杠杆可以实现偏差捕捉。关键是找到鼓励多角度、同理心和基于关系的合作的方法。不管我们采用什么方法,最重要的是:在我们要求我们的设计师灵活运用精确的、说教式的编码语言、法律或政策来设计解决方案之前,我们需要他们熟悉人类经验的复杂性、细微差别和多样性。通往流利的道路是通过解决内部偏见。
如果你有兴趣阅读更多关于人类偏见和人工智能的内容,特别是正在开发的捕捉偏见的工具,请查看詹妮弗·苏基斯的精彩作品 这里 。她举的 达里尔·戴维斯 的例子促使我把德里克·布莱克的故事包括进去。
感谢斯坦福 全球数字政策孵化器 对多利益主体方法和治理挑战的研究。
旗帜颜色可视化——从地理标志到传单,再到背面
在不影响质量的情况下,创建一个具有多种颜色的多边形。
我一直对数据可视化很感兴趣,并且我是一个长期潜伏者。2019 年 7 月 1 日,一个关于各大洲国旗颜色频率的帖子引起了很多人的兴趣。正是从那篇文章中,我有了做这种观想的想法。
这个想法很简单(执行起来并不简单),计算每面国旗的颜色比例,并用这些颜色在地图上标出每个国家。我有一个以前的项目,使用国旗颜色比例来制作原子图标,所以我知道我应该能够做到这一点。不幸的是,我错了,我花了三次尝试去正确地观想它。
在深入每个尝试的细节之前,这里是我使用的数据来源。
尝试 1 (Python + Geopandas):
在我以前的可视化(简单的 choropleth 地图)中,我一直使用 Geopandas。它可以非常容易地输出高质量的图像。
我做的第一件事是计算地图上每个国家的颜色比例。我修改了下面 StackOverflow 帖子中的代码,以满足我的需要。
for index,row in map.iterrows(): # map is the GeoPandas variable
country_code = map.loc[index,'ISO_A2'].lower()
country_data=[]
try:
flag_image = Image.open(FLAGS_DIR+country_code+".png")
except FileNotFoundError:
continue
flag_image = flag_image.convert("RGB")
pixels = flag_image.getcolors(flag_image.width * flag_image.height)
sorted_pixels = sorted(pixels, key=lambda t: t[0])
dominant_pixels = []
for pixel in pixels:
if pixel[0]*100/(flag_image.width * flag_image.height) > 5: #Top 5 colours only
dominant_pixels.append(pixel) for pixel in dominant_pixels:
percentage = pixel[0]*100/(flag_image.width * flag_image.height)
color = "#%02x%02x%02x" % pixel[1] # HEX Conversion
country_data.append({"color":color,"percentage":percentage})
data[country_code] = country_data
当试图给这些国家上色时,这个尝试出现了问题。Geopandas 不能使用多种颜色填充多边形。有一段时间,我想过妥协,只用最主要的颜色填充。实现这一点也很困难,我找到的最接近的可能解决方案是这个 Github 问题。
我无法填充最主要的颜色,所以我放弃了使用 Geopandas。
在上面坐了一会儿后,我想起 LeafletJS 使用 CSS 来设置地图的样式。因此,在将国旗颜色数据保存到 JSON 文件后,我开始了第二次可视化的尝试,现在是用活页 JS。
尝试 2:小册子
我对 LeafletJS 寄予厚望,在某种程度上,它是成功的。关于使用 CSS 支持渐变的传单,我几乎是正确的。
传单使 SVG 元素不支持 CSS 渐变,但他们自己的渐变元素。
我很容易就能涂上最主要的颜色,但是要做渐变却很难。
我必须为每个渐变创建元素,并将其链接到每个 SVG 路径。
我使用下面的代码向每个路径添加了国家代码
onEachFeature(feature,layer){
layer.options.className = "country " + feature.properties.ISO_A2.toLowerCase()
},
然后在传单地图的add\事件上,添加了以下代码
.on("add",function(){
for(let pathElm of $(".country")){
classes = Array.from(pathElm.classList);
country = classes[classes.indexOf("country") + 1];
flag = flagData[country]
console.log(flag)
$("body").append(`<svg viewBox="0 0 10 10"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="${country}" gradientTransform="rotate(90)">
${flag.map((entry,index) =>{
return `<stop offset="${flag.slice(0,index+1).reduce((a,b)=>{return {percentage: a.percentage + b.percentage}}).percentage}%" stop-color="${entry.color}" />`
})}
</linearGradient>
</defs>
</svg>`);
$(pathElm)f.attr('fill',`url(#${country})`);
}
这能够产生我想要的渐变地图,但是在添加属性后,我在自然地球数据网站上看到了下面的免责声明
免责声明
自然地球矢量根据事实上的状态来绘制国家的边界。我们展示了谁实际控制了地面局势。请随意混搭我们的争议地区主题,以符合您特定的政治观点。
为了避免以后的问题,我决定添加争议地区的地图,并用白色填充。
这需要一点重构,但是我可以用下面的代码轻松地合并这两个地图。
L.map('mapid',{
center: [39.73, -104.99],
zoom: 5,
layers: [mapLayer,disLayer]
});
我以为我完成了,但是将地图导出到一个好的图像被证明是不可能的。我尝试了许多插件,但没有一个产生足够好的图像。一个想法出现在我的脑海中,关于从开发者工具中复制 SVG 并使用 Inkscape 产生一个好的图像,但传单为不同的缩放级别呈现不同的路径。当地图完全缩小并变得详细,但只有放大的部分被渲染时,路径不太详细。
这次尝试也失败了,但给了我另一个想法。将 Geopandas 数据帧转换为 SVG。
尝试 3: Python + GeoPandas(导出到 SVG)
在使用 LeafletJS 失败后,我带着另一个想法回到了 GeoPandas。将 GeoPandas 导出为 SVG,然后对其应用渐变。我最初的想法是从传单生成的地图添加梯度,但并不需要。
我将博客中的代码添加到我的第一次尝试的代码中,并修改它以满足我的需要。
# SOURCE: http://kuanbutts.com/2018/08/30/geodataframe-to-svg/
def process_to_svg_group(row,dis=False):
orig_svg = row.geometry.svg()
doc = minidom.parseString(orig_svg)
paths = doc.getElementsByTagName('path')
pathssvg = []
country_code = row['ISO_A2'].lower()
if row['NAME'] == 'France':
country_code = 'fr'
if row['NAME'] == 'Norway':
country_code = 'no'
for path in paths:
path.setAttribute('fill', 'url(#%s)'%(country_code))
path.setAttribute('stroke-width','0.1')
path.setAttribute('stroke','#000000')
path.setAttribute('opacity','1')
path.setAttribute('transform','scale(10,-10)')
pathssvg.append(path.toxml())
return ''.join(pathssvg)
processed_rows = []
def_rows = []
res_symdiff = gpd.overlay(gismap, dismap, how='difference')
for index,row in res_symdiff.iterrows():
country_data=[]
dominant_pixels = []
stops = []
country_code = row['ISO_A2'].lower()
if row['NAME'] == 'France':
country_code = 'fr'
if row['NAME'] == 'Norway':
country_code = 'no'
try:
flag_image = Image.open(FLAGS_DIR+country_code+".png")
except FileNotFoundError:
continue
flag_image = flag_image.convert("RGB")
# SOURCE: https://stackoverflow.com/a/52879133/4698800
pixels = flag_image.getcolors(flag_image.width * flag_image.height)
sorted_pixels = sorted(pixels, key=lambda t: t[0])
for pixel in sorted_pixels:
if pixel[0]*100/(flag_image.width * flag_image.height) > 1:
dominant_pixels.append(pixel)
print(dominant_pixels)
sum = 0
for x in dominant_pixels:
sum += x[0]
print(sum)
for pixel in dominant_pixels:
percentage = pixel[0]*100/sum
print(percentage)
color = "#%02x%02x%02x" % pixel[1]
perc = 0
if len(country_data) > 0:
for x in country_data:
perc += x['percentage']
stops.append('<stop offset="%s%%" stop-color="%s" stop-opacity="1"/><stop offset="%s%%" stop-color="%s" stop-opacity="1"/>'%(perc,color,perc+percentage,color))
country_data.append({"color":color,"percentage":percentage})
grad = '''<defs>
<linearGradient x1="0" x2="0" y1="1" y2="0" id="%s">
%s
</linearGradient>
</defs>
'''%(country_code,''.join(stops))
def_rows.append(grad)
p = process_to_svg_group(row)
processed_rows.append(p)
props = {
'version': '1.1',
'baseProfile': 'full',
'width': '100%',
'height': '100%',
'viewBox': '{}'.format(','.join(map(str, gismap.total_bounds))),
'xmlns': 'http://www.w3.org/2000/svg',
'xmlns:ev': 'http://www.w3.org/2001/xml-events',
'xmlns:xlink': 'http://www.w3.org/1999/xlink'
}
template = '{key:s}="{val:s}"'
attrs = ' '.join([template.format(key=key, val=props[key]) for key in props])
raw_svg_str = textwrap.dedent(r'''
<?xml version="1.0" encoding="utf-8" ?>
<svg {attrs:s}>
<g>{data:s}</g>
{grads:s}
</svg>
''').format(attrs=attrs, data=''.join(processed_rows),grads=''.join(def_rows)).strip()
with open('out/map.svg', 'w') as f:
f.write(raw_svg_str)
这能够产生地图

高质量地图
https://i.imgur.com/2RD4s6k.png
我用 Inkscape 添加了文本和背景
法国和挪威的奇特案例
在各种网站上分享地图后,许多人询问失踪的法国。我不擅长地理,但我相信我的代码不会漏掉一个国家。所以我做了一些调试和研究,发现我使用的 shapefile 没有存储法国和挪威的 ISOA2 数据。我的代码使用 ISO A2 数据将旗帜文件与地图进行匹配,因此缺少的数据会导致缺少的国家。我硬编码了几个 if 语句来包含国家,上面的代码为此进行了更新。
相关材料
打招呼:insta gram||Github|||脸书||推特 | 网站||博客
flappy Royale——通过 1000 轮练习提高成绩
背景
2013 年 5 月,一款名为 Flappy Bird 的极度病毒式手机游戏发布。这个游戏的前提极其简单——一只鸟拍打着翅膀飞过一个被有缝隙的管道包围的平台。触摸你死亡的任何管道,通过管道之间的缝隙拍打,你的分数增加一分。看似简单,但这个游戏实际上很难——对我来说。

The Original Flappy Bird
我记得我安装了这个游戏,然后在游戏中敲敲打打,却发现自己每次只能清理 1-2 个管道。我会看到其他几十个人清理 15、20、30 根管道,然后想知道我是否对自己的协调能力有极大的挑战。长话短说,经过无数次尝试后,我在同一天结束了愤怒辞职。
快进到 2019 年 7 月。我将在最后一天去中国西安看望我的祖父母,我的飞机将在晚上起飞。我看了一篇文章,提到了一款刚刚发布的新游戏,名为 Flappy Royale。和我 6 年前退出的游戏一样,这次他们加入了流行的皇家扭曲,所以我决定,为什么不再试一次?

Newly Released Flappy Royale
我最初玩 Flappy Bird 时发生的同样的事情在 Flappy Royale 中再次发生。遗憾的是,我每次只能清理 0-2 根管道。唯一的不同是,这一次有 100 名其他的时髦女郎和我一起玩,我也可以看到他们的进步,因为他们的排名很靠前。有了这种知名度后,我现在意识到我实际上并不是最差的 Flappy Bird 玩家,但实际上只是略低于我刚开始玩时的平均水平。现在有了下面的数据证明,我比平均水平略高😁。
查看我们的数据
为了基本了解我的性能如何随着时间的推移而提高,以及 Flappy Royale 的总体性能如何,我记录了 1000 次以上的尝试,清除了管道,并在 excel 中对 100 个同行进行了排名,并用 python 创建了一些简单的分析,希望大多数读者都能理解——
跟踪超过 1000 次尝试后每次尝试清除的管道
使用来自回归分析的数据,我生成了一条 Y = 1.88 + 0.00213X 的线。这意味着在开始时,我的技能水平是平均 1.88 管道清除我所做的每一次尝试。在 1000 次尝试的过程中,我取得了 0.00213 次额外管道清理的微小进步。这看起来很小,但是经过 1000 次的练习,我现在平均每次尝试清除 4 个管道,这是我开始时的两倍。下图显示了这一过程,每个点代表在 1000 次尝试中每次尝试清理的管道数量。回归线是黄色的,代表图表中的公式。

追踪每 1000 次尝试的同行排名
类似于对清除的管道使用回归分析,我对 100 个对等点实现的等级做了同样的事情,数字越小表示等级越好。我最初的平均等级是 56/100,每次尝试下降了 0.02,在 1000 次之后,我现在的平均等级是 37/100。这由线 Y = 55.97 + -0.0193X 表示。我翻转 Y 轴来表示等级值下降作为更好的结果,因此由上升趋势线显示,即使公式显示斜率为负。

Flappy Royale 的其他玩家表现如何?
Flappy Royale incorporated 的一个很棒的功能是一次创建 100 个玩家的回合,并报告你在这 100 个玩家中的表现。通过 1000 轮比赛,我能够收集足够的数据来了解 Flappy Royale 玩家的总体表现。结果是,能够清理 2 个管道使你刚好符合平均水平,清理 3 个管道使你接近前 38%,清理 4 个管道使你接近前 27%。我还幸运地清理了 5 个管道,收集了足够多的数据,这使我在清理管道的 87 次中平均排名前 20%。如果你想一直排在前 10 名,你可能想一次清理至少 8 个管道。下面是通过收集的数据表示这些结果的直方图。

Histogram representing the average rank of someone who is able to clear 2 pipes is right in the middle at 50

Histogram representing the average rank of someone who is able to clear 3 pipes is above average at 38

Histogram representing the average rank of someone who is able to clear 4 pipes is near top quartile

Players able to clear 5 pipes on average should be proud of themselves as they are in the top 20% of all players

If you want to be in the top 10% of all players, you will want to clear around 8 pipes on average
为了查看分析的来源,我在下面附上了我的备份—
Excel 文件-
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/bryfeng/personal-projects/blob/master/flappy data.xlsx)
分析-
flask:API 开发的捷径

Photo by Chris Ried on Unsplash
世界经历了巨大的转变;从在过程语言中将一段代码分离为函数到库的开发;从 RPC 调用到面向服务架构(SOA)中的 Web 服务规范,如 SOAP 和 REST。
这为 Web APIss 和微服务铺平了道路,在 Web API 和微服务中,提供特定问题解决方案的代码片段被公开(私下或公开),代码可以作为黑盒被 API 消费者访问,他们只需要知道所谓的接口,它定义了特定代码片段的输入和输出。
我们使用谷歌、脸书、推特直接登录许多系统,不需要注册每个特定的网站。感谢这些巨头提供公开可用的 Web APIs 进行授权,这使得开发者的生活以及用户的生活都变得非常轻松。
不要被上面这么多的陈述搞得不知所措。我相信在你读完它之前,你会对 API 有很好的理解。
现在,API 到底是什么?
我一直在说这个缩写 API(应用程序接口)。但是到底是什么呢?简而言之,它只是以这样一种方式公开的一小段代码,任何语言的任何应用程序,无论是 Java、Python 还是 Node.js,都可以简单地发送一个请求,以获得某些特定输入的一些输出。它只是底层应用程序和外部世界之间的接口。

Client and Target Application(Exposed as API) are in different languages, still can communicate
如上所示,客户使用不同的语言,但是他们将 API 称为黑盒,因为底层技术是在. Net 中。尽管如此,他们还是能够交流。所有客户端对特定请求的响应保持不变。可能是 json,也可能是 XML。
现在言归正传,为什么是 python,为什么是 flask?

Python and Flask combination makes API development very easy
在我的工作中,从事与机器学习相关的任务,我必须为一些特定的任务开发后端 API,其中大部分都与图像上传、下载和 GRPC 调用相关,并且要求在短时间内完成。
来自 JAVA 背景,当我接触 python 的时候,我发现做一门编程语言是如此的容易。没有样板代码,有丰富的库和语法(一开始可能会让你生气,但是你会意识到适当的缩进是如何导致开发出漂亮的代码片段的)。所以,这显然是对 python 的喜爱。最重要的是,当谈到机器学习和深度学习时,python 是杀手。
下一个任务是为 python 选择一个定义良好的框架,用于 API 开发。我有两个选择:Django 和 Flask(还有很多其他的选择,但是这两个是在很多支持下使用的)。我做了一些研究,我选择 flask 的原因是它比 Django 更 pythonic 化。所以,我也有这个问题的答案。现在是执行的时候了。因此,有了 Flask,后端在几天内就准备好了,所有的 API 都是作为 REST 调用开发的
那么,现在说得太多了,让我们把手放到装满蟒蛇的瓶子里。我知道这听起来很可怕。但是蟒蛇是无毒的,所以这是。相信我。
注意:期待这篇文章中的大量代码。原因是我不想只显示 hello world 类型的代码。因为很多网站已经这样做了,你不需要再看到一个同样的帖子。
在这里,我希望您对 Flask 和 python 有一个良好的体验,这样您就可以在您的应用程序中使用它们,这些应用程序需要一些常见的功能,比如在读取文件和上传文件后发送 json 响应。所以,系上安全带,开始编码吧。
让我们开发两个 Web-API,一个带有 GET ,一个带有 POST 。
获取 API:
**flask_simplified.py**from flask import json
from flask import Response
from flask_cors import CORS
from flask_api import FlaskAPIAPP = FlaskAPI(__name__)
CORS(APP)@APP.route("/getJsonFromFile/<filename>", methods=['GET'])
def get_json_response(filename): labels_dict = {}
response_dict = {}
try:
with open(filename, 'r') as labels:
labels_dict = json.load(labels) ***response_dict[STATUS] = "true***
***response_dict["labels_mapping"] = labels_dict
js_dump = json.dumps(response_dict)
resp = Response(js_dump,status=200,
mimetype='application/json')*** except FileNotFoundError as err:
response_dict = {'error': 'file not found in server'}
js_dump = json.dumps(response_dict)
resp = Response(js_dump,status=500,
mimetype='application/json') except RuntimeError as err:
response_dict = {'error': 'error occured on server side.
Please try again'}
js_dump = json.dumps(response_dict)
resp = Response(js_dump, status=500,
mimetype='application/json') return respif __name__ == '__main__':
APP.run(host='0.0.0.0', port=5000)
-------------------------------------------------------------------
上面的代码显示了如何使用 flask-API 创建一个简单的 GET 调用。您可能会觉得这里有很多代码。但是相信我。为了写好代码,你应该开始写好代码,并把它作为一种习惯。因此,它拥有你在编写代码时应该考虑的所有元素,比如适当的缩进(这对 python 来说是必要的),遵循 PEP8 python 约定并包括异常。
代码解释:
**APP = FlaskAPI(__name__):**
sends the __name__ to FlaskApi Constructor to instantiate Flask Api object, which will be used accross the application
**CORS(APP):** this might not be required for now but is required in case your client is browser(eg. javascript), then cross origin requests will be allowed by the server else you might get CORS related errors
下一部分解释负责服务请求的实际方法:
**@APP.route("/getJsonFromFile/<filename>", methods=['GET']):** defines the method type and uri to be hit by client for this method**def get_json_response(filename):** filename parameter should be kept same which transports the path parameter to the method parameter and is used accross this method**labels_dict = {}
response_dict = {}:** these are some local dictionaries created. labels_dict contains the key-value pairs in the labelsFile.json.
response_dict creates the actual json which contains either the labels_dict key or error key to be returned along with STATUS; depending on response.**try:
with open(filename, 'r') as labels:
labels_dict = json.load(labels)** Above code opens the file. This json file is loaded as key-value pair in labels_dict***response_dict["*LABELS_MAPPING*"] = labels_dict
js_dump = json.dumps(response_dict)
resp = Response(js_dump,status=200,
mimetype='application/json')*** *This is the most important part of code, which shows the Flask api returning response for the request. Response is the Flask's inbuilt way to sendback data to client.***Then comes Exceptions, which are self Explanatory****if __name__ == '__main__':
APP.run(host='0.0.0.0', port=5000)** When we run application as script, the Flask's inbuilt server starts on port 5000 due to APP.run
Here we instruct APP(which we defined already as Flask's instance), to run the server at 0.0.0.0(localhost/127.0.0.1) which is Flask's inbuilt WSGI server
如何测试我的第一个烧瓶应用程序?
测试起来真的很简单。只需保存代码并运行文件
python3 flask_simplified.py
您将看到类似这样的内容(如果脚本中没有错误) :

Flask Server running on 5000 port
如图所示,正在运行的服务器是 python 内置的 Flask server(APP.run 在 localhost:5000 启动服务器),显然不应该用于生产。必须使用 uwsgi 或 gunicorn 或任何其他 wsgi 生产服务器。我将有一篇单独的文章,介绍什么是 WSGI,以及如何在 uwsgi 应用服务器和 nginx 上安装和部署您的应用程序,并将其作为 web 服务器,以使其可以投入生产。
让我们使用下面的 cURL 命令对此进行测试:
curl localhost:5000/getJsonFromFile/labelsFile.json
其中 labelFile.json 内容可以是文件中的键值对,如下所示:
{"ironman":"1",
"deadpool":"2",
"thor":"3",
"captainAmerica":"4",
"angryYoungMan":"5",
"blackPanther":"6",
"Wolverine":"7",
"thanos":"snapItAll"
}"""don't mind; keeping ironman at 1 , fan detected. ;)"""
根据您提供的文件名,您应该会看到响应或异常。
Response:
{"LABELS_MAPPING": {"Wolverine": "7", "angryYoungMan": "5", "blackPanther": "6", "captainAmerica": "4", "deadpool": "2", "ironman": "1", "thanos": "snapItAll", "thor": "3"}, "STATUS": "true"}
发布 API:
对于 post 请求,可以使用 request.form['form_data']从请求中提取 JSON 主体,并且应该提供 yes,methods = "POST "
如果我们用 post 请求来做一些事情,那将会更有趣,这在现实生活中更常用:上传图片到服务器
让我们上传一些图片到服务器(现在你有一些有趣的事情要做了)😃
在这一部分中,我将解释使用 upload 方法的 POST 调用,在这种方法中,文件可以随请求一起发送,并可以保存在服务器中。
**UPLOADING FILES:**
@APP.route("/uploadFiles", methods=['POST'])def upload_file():
"""uploads file to the server. This will save files to the
directory where server is running""" response_dict = {}
error_files = ""
new_filename = "" try:
new_filename = request.form['FILE_NAME']
**recieved_files = request.files.getlist(FILES_RECEIVED_LIST)**
for each_file in recieved_files:
each_file_name = each_file.filename
try:
**each_file.save(os.path.join(".", new_filename +
each_file.filename.replace(" ", "")))**
except RuntimeError as err:
print("\nError in saving file: %s :: %s",
each_file.filename, err)
error_files = error_files + "," + each_file.filename response_dict[STATUS] = "true"
response_dict[ERROR_FILES_LIST] = error_files
js_dump = json.dumps(response_dict)
resp = Response(js_dump, status=200,
mimetype='application/json') except RuntimeError as err:
response_dict = {'error': 'error occured on server side.
Please try again'}
js_dump = json.dumps(response_dict)
resp = Response(js_dump, status=500,
mimetype='application/json')
return resp
代码解释:
**new_filename = request.form['FILE_NAME']** FILE_NAME contains the filename in the request.(see the cURL request below) **recieved_files = request.files.getlist(FILES_RECEIVED_LIST)** FILES_RECEIVED_LIST contains the files in the request. (see the cURL request below) **for each_file in recieved_files:
each_file_name = each_file.filename** iterate through all the files recieved to save one by one try:
**each_file.save(os.path.join(".", new_filename +
each_file.filename.replace(" ", "")))** all files are saved to the path given by os.path(. will save in location where server is running ; you could give some absolute or relative path here. I am appending new_filename to each_file.filename where each_file.filename is actual name of file. You could replace the each_file.filename with new_filename) except RuntimeError as err:
**error_files = error_files + "," + each_file.filename** in case file is not saved, the error_files will contain all the filenames which are not saved which will be sent back to the client in json response**response_dict[ERROR_FILES_LIST] = error_files** contains ERROR_FILES_LIST key in json response which contains all the error files names seperated by ","
***Rest of the code is self explanatory***
这可以使用以下 cURL 请求来触发:
***curl -F "FILES_LIST=@./file1.jpg" -F "FILES_LIST=@./file2.jpg" -F "FILE_NAME=new_file" localhost:5000/uploadFiles***where file1.jpg and file2.jpg are two files to be uploaded and are present in the directory from where cURL request is triggered
您将看到文件在运行 Flask 服务器脚本的同一位置生成。
我的 GitHub 页面上有完整的代码:
在 GitHub 上创建一个帐户,为 tseth92/flask_simplified 开发做贡献。
github.com](https://github.com/tseth92/flask_simplified)
结论:
在这篇短文中,我试图解释如何使用 Flask 构建 API,使用 GET 请求处理读取 json 文件,使用 POST 请求处理在服务器中上传文件。因此,使用 Flask,在很短的时间内,并且很少花费精力编写一些有纪律的基本代码,您就可以在很短的时间内运行 API,用于在 vision 中短时间上市的应用程序。
下一步:
Docker 和 Containers 已经成为一个时髦的词汇。Docker 开源变得如此流行…
medium.com](https://medium.com/@tusharseth93/is-docker-really-worth-your-time-e86dbdf374d9)
我计划在另一篇关于的文章中进一步解释如何在一个生产就绪的服务器上构建这个应用程序,比如 uwsgi,并在其上运行 nginx
感谢阅读!!。对于任何建议,疑问或讨论,在评论区拍摄。
在 medium 和 GitHub 上关注我,获取即将发布的文章;直到那时,快乐的蟒蛇和好运:)
用于在线机器学习部署的 Flask 和 Heroku

介绍
感谢 Pandas、scikit-learn 和 Matplotlib 等库,使用 Python 中的简单机器学习(ML)算法开始探索数据集和进行一些初步预测相对容易。虽然,为了使这些训练好的模型在现实世界中有用,有必要分享它们,并使它们可以在网上访问,以做好预测的准备。只有这样,机器学习才能被用来造福社会。
我最近一直在使用 yei-Cheng 教授的输血服务中心数据集,我使用随机森林分类器实现了 91.1%的预测准确率。因此,我决定把这个模型放到我的个人网站上。为此,我使用了 Heroku 和 Python 库 Flask。
作为进一步的发展,我还决定使用 Android Studio 为我的个人网站创建一个 Android 应用程序,这样我的 ML 模型也可以在 Android 设备上运行。
瓶

(Source = https://kenya-tech.com/wp-content/uploads/2019/01/flask-python.png)
Flask 是一个用于为 Python 创建 web 应用的 Web 框架。在开发在线 ML 模型的情况下,这可以通过使用三个 python 文件来完成:
- model.py =在这个文件中,ML 模型应该被实现和训练。然后应该使用 Pickle 库保存训练好的模型,以便在网络上使用时用于给出实时预测。
- app.py =在这个文件中 Flask 用于处理我们从 request.py 获得的 POST 请求,然后返回结果。为此,使用 Pickle 库检索训练好的 ML 模型。
- request.py =用于向服务器请求特性并检索结果。
app.py 的一个基本例子可以是:
import numpy as np
from flask import Flask, request, jsonify, render_template
import pickleapp = Flask(__name__)# Code to load ML model@app.route('/')
def home():
return render_template(*“filename.html”*)@app.route('/ml-model', methods=['POST'])
def run_model():
# Code to use the trained model to make real time predictions
return render_template(*“filename.html”, result*)if __name__ == '__main__':
app.run()
如果我们想要添加一些 HTML、CSS 或任何其他类型的内容来改善模型的在线图形,可以通过在我们的 model.py 、 app.py 和 request.py 文件所在的同一个工作目录中创建一个 templates 文件夹来实现。这些文件必须包含在模板文件夹中,因为当调用 app.py 中的render _ template(" filename . html ")时,Flask 将默认只在模板文件夹中查找文件。
仅使用 Flask,我们就可以使用本地服务器在本地机器上运行我们的模型。如果我们想让我们的模型在网络上可用,我们可以将我们的 Flask 应用程序部署到一个在线平台上,比如 Heroku。
赫罗库

(Source = https://pbs.twimg.com/profile_images/689189555765784576/3wgIDj3j.png)
Heroku 是一个云平台,可用于部署网站和其他服务。为了使用 Heroku,有必要在你的机器上安装: Heroku 账号、 Heroku CLI 工具和 git。
依次地,有必要:
- 在我们的 Flask 应用程序所在的目录中创建一个 git 存储库。
- 登录我们的 Heroku 账户。
- 创建一个 Heroku 项目。
- 创建一个 requirements.txt 和 Procfile。
- 将目录中的所有文件添加到存储库中。
- 将应用程序推送到 web 上!
前三个步骤可以通过在命令提示符下依次运行以下命令来实现。
git init
heroku login
heroku create
创建 requirements.txt 文件需要告知 Heroku 运行 Flask 应用程序需要什么库。因此,在 requirements.txt 文件中,我们为每一行写一个所使用的所有库的名称。对于这个应用程序,Procfile 只需要包含一行文本(如下所示)。Procfile 需要让 Heroku 知道我们希望执行什么类型的应用程序。
web: gunicorn app:app
然后,可以通过在命令行中顺序运行以下命令来执行第五步和第六步。
git add .
git commit -m "Committing all the directory files to Heroku"
git push heroku master
heroku open
使用 heroku open 命令应该可以打开已经部署了 Flask 应用程序的网页。或者,通过在线登录我们的 Heroku 帐户,可以找到部署我们模型的链接。访问 Heroku 网站可能特别有用,尤其是在我们的 Flask 应用程序午餐期间出现任何问题的情况下。
结论
这是一个关于如何在线部署你的机器学习模型的简单介绍。如果你想了解更多关于如何使用 Flask 编写 Web 应用程序的信息,这里有一些有用的参考资料[1,2,3]。
我的在线机器学习模型在这里可以找到,我用来设计它的所有代码都可以在我的 GitHub 页面上找到。
联系人
如果你想了解我最新的文章和项目,请在媒体上关注我,并订阅我的邮件列表。以下是我的一些联系人详细信息:
文献学
[1]如何使用 Flask 构建 web 应用并部署到云上。萨尔瓦多·维拉隆。访问:https://www . freecodecamp . org/news/how-to-build-a-web-application-using-flask-and-deploy-it-to-the-cloud-3551 c 985 e 492/。
[2]教程:将机器学习模型部署到 web。剑桥火花,亚历山大罗伯逊。访问:https://blog . Cambridge spark . com/deploying-a-machine-learning-model-to-the-web-725688 b 851 c 7。
[3]使用 flask 部署一个机器学习模型。赫曼维亚斯。访问:https://hacker noon . com/deploy-a-machine-learning-model-using-flask-da 580 f 84 e 60 c。
Flask 数据科学应用程序
数据科学家一般在后端工作。构建一个交互式应用程序不是我的强项,但它可以是展示你所能做的工作的一个很好的方式。在这篇文章中,我将讨论一些创建 Flask 应用程序的技巧,这些技巧是我在为我的旅游推荐系统建立网站时学到的。

Photo by Goran Ivos on Unsplash
入门指南
在我们开始考虑美化我们的应用程序之前,你应该把你所有的功能放在一个单独的 py 文件中,而不是放在一个 Jupyter 笔记本中。这一步是必要的,因为 Flask 不能与 Jupyter 通信,但它可以与 py 文件通信。
知识库组织
存储库的组织对于部署到网站非常重要。在进入存储库时,应该有一个与存储库同名的文件夹。这个文件夹里应该是你的应用程序需要的所有东西。例如,一个推荐. py 对我的应用程序是必要的,但一个刮擦. py 不是,因为它只用于收集数据。
该文件夹中需要的另一个文件是 init.py. 这可以是一个空文件,但这允许在导入包时运行代码。最后,您将需要一个 web_app 文件夹。此文件夹将包含运行您的应用程序所需的文件,包括引导静态文件夹和模板文件夹。
travel_destination_recommendation
travel_destination_recommendation
__init__.py
recommend.py
web_app
__init__.py
static
templates
app.py
当您想要运行/测试您的应用程序时,您将在最高目录中的命令行中使用以下代码:
FLASK_APP = travel_destination_recommendation.web_app.app.py
在 app.py 中,您可以通过以下方式导入自定义函数:
from ..recommend import custom_functions
创建烧瓶应用程序
py 文件是我们从 flask 导入的地方。您想要导入一些工具:
- 瓶
- 请求(允许与 Javascript 通信)
- render_template(从 HTML 创建模板)
- jsonify(所有内容都需要以 JSON 格式返回)
from flask import Flask, request, render_template, jsonifyapp = Flask(__name__, static_url_path="")
你的应用需要有一些东西来展示。这是你的第一个函数。任何与 HTML 通信的函数都以 decorator @app.route('/')开始。括号内是我们想要与之交互的 HTML 的标签。/本身代表主页。
[@app](http://twitter.com/app).route('/')
def index():
"""Return the main page."""
return render_template('theme.html')
我建议,尤其是对于第一个应用程序,开始时不要做任何设计。我从这个函数开始。它从文本框中获取文本并返回一个预测。
with open('model.pkl', 'rb') as f:
model = model.load(f)@app.route('/predict', methods=['GET','POST'])
def predict():
"""Return a random prediction."""
data = request.json
prediction = model.predict_proba([data['user_input']]) return jsonify({'probability': prediction[0][1]})
连接到 HTML
上面的这段代码连接到下面的 HTML,它创建了一个文本框和一个标记为“预测”的按钮
<html>
<head>
<title>Predict</title>
<script type="text/javascript" src="brython.js"></script>
<script type="text/javascript" src="brython_stdlib.js"></script>
</head>
<body onload="brython(1)">
<script type="text/python3">
from browser import document, ajax
import json
import warnings
def show_results(response):
if response.status==200:
document["result"].html = response.text
else:
warnings.warn(response.text)
def get_prediction(ev):
"""Get the predicted probability."""
req = ajax.ajax()
req.bind('complete', show_results)
req.open('POST', '/predict', True)
req.set_header('content-type','application/json')
data = json.dumps({'user_input': document['user_input'].value})
req.send(data)
document["predict_button"].bind("click", get_prediction)
</script>
<textarea id="user_input"></textarea>
<button id="predict_button">Predict!</button>
<div id="result"></div>
</body>
</html>
这是相当多的代码。我们来分解一下。我们要确保做的第一件事是将脚本设置为 brython。Brython 代表浏览器 python,允许在 Javascript 环境中使用 python。在脚本和正文中都设置这个。
我们希望我们的代码在脚本的主体中。我们需要两个独立的函数来收集数据和显示结果。在 show_result s 中,我们只想要一个结果,如果存在的话。在 if 语句中,我们引用 HTML 文档的 div id“result”,因为这是我们希望显示结果的地方。
在get _ prediction中,我们有参数 ev 表示事件。这意味着该函数将在特定事件发生时被调用(我们稍后将回到这一点)。
req.bind('complete ',show_results) 行将该函数连接到 show_results 函数。
下一行 req.open('POST ','/predict ',True) 通过装饰器将这个函数连接到我们在 app.py 文件中创建的函数。
数据是我们可以传递给 app.py 文件的内容。注意,结果应该被转储为 JSON 格式。这一行有两个“用户输入”。字典中的那个是键,它在 app.py 函数中作为数据的一部分被引用。该数据通过 req.send(data) 线发送至 app.py 函数。文档["user_input"]是文本框的 id。
既然函数已经完成,我们需要一个触发函数的事件。我们希望在点击预测按钮时进行预测。脚本中的最后一行用特定的 id 连接按钮,并将“click”绑定到函数 get_prediction。
结论
这是一个非常基础的 Flask app。随着时间的推移,通过小的改变,我们可以创建一个漂亮的应用程序。作为一名数据科学家,你不需要成为 web 开发的大师,但它可以成为一个有趣的工具来展示你的模型可以做什么。
感谢我的教练为我的烧瓶起点,检查他的垃圾邮件预测在这里。要查看一个详细的 Flask 应用程序,看看我的库。
花卉种类分类器
建立一个图像分类器来识别 102 种不同的花卉
人工智能深度学习卷积神经网络
Python py torch Numpy Matplotlib Jupyter 笔记本

Photo by John-Mark Smith on Unsplash
在这篇文章中,我概述了我开发的项目,该项目使我获得了 Udacity 深度学习纳米学位项目的奖学金。

My Badge of Approval
背景
我开发这个项目是作为由 Udacity 和脸书举办的 PyTorch 奖学金挑战评估的最终测试。

See the original announcement in this link
在这两个月的挑战中,我第一次接触了人工智能开发。除了观看视频和完成课堂上的评估,我还加入了一个私人 Slack 社区。因此,我可以与项目中的同行和教练联系,分享我的进步,获得反馈,提出问题,并帮助其他学者完成挑战。
挑战
构建深度学习模型,从图像中识别 102 种花卉。
最终应用程序接收用户提供的图像,并预测成为这些已知物种之一的可能性。
发展
加载数据
在此链接中找到的图像数据集包含了 7370 张在不同角度和光照下拍摄的花朵照片。
下面可以看到几个例子。

设置图像子集
我将图像随机分为三组:
Training : 6552 images / 205 batches
Validation: 409 images / 18 batches
Testing : 409 images / 18 batches
数据扩充
对于这个项目,我选择对训练集使用图像归一化和数据增强。
作为第一步,我在每个图像中随机选择四个变换中的一个,给定被选择的概率:
• 16,67% — Horizontal flip
• 16,67% — Vertical flip
• 33,33% — Random rotation, in a range of 360 degrees
• 33,33% — Keep the image untouched
在那之后,我在图像中进行了随机裁剪,并将它们的尺寸调整为 224x224 像素。
对于测试和验证集,我只调整了图像的大小,并对每张图像进行了 224x224 像素的中心裁剪。
构建分类器
为了检测图像中的特征,我使用了一个预先训练好的网络,可以在包 torchvision.models 中找到。经过在 ImageNet 数据集上的训练,这些模型可以识别图像中的大量特征。然而,在它们的最后部分附加的分类器将图像分类到除了包含在数据集中的花卉种类之外的类别中。
于是,瞄准最终目标,我选择了 ResNet-152 型号。我保留了它预先训练好的卷积层,并替换了它的分类器,建立了一个新的网络来将图像分类到新的类别中。

Representation of a ResNet with 34 layers
我的全新分类器是一个全连接的网络,包含一个 2048 节点的输入层、一个 1000 节点的隐藏层和一个输出层,用于将输入数据分类为 102 个类别。
一旦我建立了我的模型,是时候训练它了!
训练网络
在测试了许多优化器之后,我选择了 Adagrad。这是一个非常强大的优化下降训练损失非常快。然而,有必要对这种能力进行一些控制。否则,这种强度可能会转化为爆炸梯度,而不是收敛到最佳解决方案。为此,我引入了一个学习率调度器,我将在下一节详细介绍它。

Comparison of different optimizers
我将网络培训过程分为三个阶段,如下所述。
培训阶段 1:
这里的重点是训练分类器。
由于卷积层已经过预先训练,它们能够识别图像中各种各样的特征。所以,我冻结了他们的参数,禁止他们更新。因此,所有的努力都是为了调整分类器的权重。
为了限制 Adagrad 能力,我引入了一个调度器,以便在误差函数达到平稳状态时降低学习速率。学习率从 0.01 开始,在平稳期减少 0.1。一旦学习率低于 0.0001,这个阶段就被中断。
在这个训练阶段,执行了 39 个时期,在验证数据集中提供了 94.62%的准确度。
Epoch: 39: lr: 0.00010000 Training Loss: 0.412109
Validation Loss: 0.232272 Validation accuracy: 94.62%
训练第二阶段:
由于分类器已经知道要做什么,是时候微调卷积层的权重了!
为此,我解冻了所有网络参数,将学习率设置为 0.0001,并对整个模型进行了 20 个时期的训练。
在这个训练阶段结束时,验证准确率为 96.58%。
Epoch: 59: lr: 0.00010000 Training Loss: 0.201783
Validation Loss: 0.141651 Validation accuracy: 96.58%
训练第三阶段:
在这一点上,即使我已经获得了一个很好的结果,我决定推进我的网络的极限!
我将学习率降低到 0.000001,只是对分类器权重进行一些小的调整,并运行其他 10 个时期的训练。
然后,验证准确率达到了令人印象深刻的 97.07%。
Epoch: 69: lr: 0.00000100 Training Loss: 0.187415
Validation Loss: 0.142964 Validation accuracy: 97.07%

Parameters evolution within the whole training process
最终测试
在这一点上,网络在识别训练和验证图像方面足够准确,并以超人的准确性对它们进行分类。
该网络的最终测试是查看一个完全未知的数据集,以正确分类图像。
我想象不出比这更好的结果了!该网络以 99.27%的惊人准确率执行了这项任务,在 409 幅图像中只漏掉了 3 幅!

The network missed only 3 of 409 images
结果
作为最终结果,该网络接收一幅图像作为输入,并对该花的五个最可能的物种进行预测。
这个项目的一个实际应用例子是一个手机应用程序,它显示相机拍摄的花的名称。
您可以在下面看到一些输出示例。
请注意,网络不仅预测正确的类别,而且对此非常确定。

这个项目的所有代码都可以在我的 GitHub 配置文件中找到:
[## Silvio mori/uda city-深度学习-py torch-挑战-实验室
PyTorch 挑战实验室的代码存储库
github.com](https://github.com/silviomori/udacity-deeplearning-pytorch-challenge-lab)
深度学习中的流动张量和堆积参数
可训练参数计数的公式是作为层参数和输入特征的函数为几个流行层开发的。然后将结果与运行模型时 Keras 报告的结果进行核对……
深度学习模型是参数快乐的。可以肯定地说,他们从未遇到过他们不喜欢的参数!采用数百万个参数的模型太常见了,有些甚至达到数十亿个。感谢低级库,如 TensorFlow、PyTorch 等……对于繁重的工作,感谢高级库,如 Keras,对于易用性,现在很容易快速构建这样的模型。但是,了解这些参数是如何从基础知识中产生的,对于超越深度学习的黑箱方法非常重要。这就是这篇文章的目的——也许可以帮助某人回答一两个深度学习工程师角色的面试问题!
运行 Keras 的最有用的输出是进入/离开每一层的数据/张量的形状以及该层中可训练参数的计数。对于深度学习的常规实践者来说,这些信息可以快速确认 Keras 正在运行他们为其编程的内容。但对于那些刚接触深度学习的人来说,可能不会立即清楚这些是如何发生的。在这里,我们看看一些流行的层的内部工作,不太深,但足以得出参数的数量作为层参数的函数。我们调和了喀拉斯告诉我们的东西。这里有一个简要的概述。
- 选择一些常见的层,如密集层、嵌入层、RNN 层(LSTM/GRU)和卷积层(Conv1D/Conv2D)等。由于篇幅原因,我们将在本系列的下一篇文章中讨论卷积层。
- 对于每一层,开发可训练参数数量的公式,作为层特定细节的函数,如单元数量、过滤器及其大小、填充等…以及输入细节,如特征数量、通道等。我们进一步获得离开层的数据/张量的预期形状
- 运行 Keras 指南中描述的视频问答模型,并确认我们的公式/分析对于可训练参数和输出形状是正确的。
这里显示的片段的完整代码可以照常从 github 下载。
1.层次丰富
深度学习模型是一个连接层的网络,每一层都接受一个输入数据张量,并生成一个潜在不同形状的输出数据张量。每一层都使用大量的参数来完成它的工作,增加了模型中参数的总数。数据/张量在网络中从输入端到输出端流动,同时在流动过程中进行形状和内容的转换。在训练阶段,通过比较获得的和预期的输出来估计模型参数,然后反向驱动误差来更新这些参数,以便它们在下一批输入中做得更好。这是坚果壳中的深度学习——只是十亿维空间中的大规模优化练习!
Keras 为实例化各种层并连接它们以构建深度学习模型提供了高级 API。所有的计算都发生在这些层中。流行的层包括“面包和黄油”密集层,“图像快乐”卷积层,“序列尊重”递归层及其许多变体。有一些功能层,如池,不增加新的参数,但改变了传入张量的形状。存在不修改形状但添加新参数的归一化层。像 dropout 这样的层既不会添加新参数,也不会修改形状。你明白了——这是有原因的!此外,我们还可以编写自定义层。出于我们的目的,我们只关注几个流行的层。首先是一些定义和符号。
一个张量仅仅是一个多维数字矩阵,具有任意形状,如[n_1,n_2,n_3,…n_m]。一个向量是一个数字列表,因此像【n_1】一样有一个维度。显然,矢量是一维张量。然而,我们选择将向量写成[n_1,1],以强调它只是一个具有一列的矩阵,并且看到所有的点和哈达玛乘积都清晰地计算出来。
2.致密层
密集层是简单前馈网络的构造块。每个输入都连接到层中的每个单元,并且每个这样的连接都有一个关联的权重。另外每个单元都有一个偏差(如果 use_bias=True 在 Keras 中,默认)。下面的等式 1 描述了密集层的作用。基于可选择的激活函数 f,它简单地将输入向量 x 转换为输出向量 y 。

Equation 1. The weight matrix and the biases are the parameters in a Dense layer
例如,以下模型将具有通过运行模型验证的 32 * (784 +1) = 25120 可训练参数。
运行它,我们得到下面的输出和数据流图。
Layer (type) Output Shape Param #
=================================================================
Dense_Layer (Dense) (None, 32) **25120**
=================================================================
Total params: 25,120
Trainable params: 25,120
Non-trainable params: 0

Figure 1. Flow of a data tensor through a dense layer.
3.嵌入层
嵌入层几乎与密集层相同,但讨论起来非常重要,因为它们广泛用于准备文本输入。
一个嵌入层是一个密集层,没有偏置参数和身份作为激活函数。事实上,该层所做的只是矩阵乘法,其中矩阵条目是在训练期间学习的
在文本处理中使用嵌入层来提出单词的数字矢量表示。从 0 (或 1 ,如果 0 用于填充/屏蔽以允许可变长度的句子)到 nWords (或 nWords+1 ,如果 0 用于屏蔽),组成文本语料库的单词被分配整数索引。输入的句子/文本最多有个长度为的单词。每个字是一个 1-hot 编码向量。权重矩阵将长的 1-hot 编码向量转换成短的密集数值向量。换句话说,权重矩阵就是长度为 nUnits 的列的单词向量。

Equation 2. Embedding layer simply converts a long, sparse vector to a short, dense vector
以下嵌入层的代码片段将把 1024 * 256 = 262144 可训练参数添加到与下面的 Keras 报告相匹配的模型中。
运行我们得到的:
Layer (type) Output Shape Param #
=================================================================
Embedding_Layer (Embedding) (None, 25, 256) **262144**
=================================================================
Total params: 262,144
Trainable params: 262,144
Non-trainable params: 0

Figure 2. Each word in a 25 word (each a 1-hot vector) sequence gets converted to a 256 long numerical vector after passing through the Embedding Layer
4.循环层
递归图层适用于顺序非常重要的数据。例如,单词的顺序对于句子的意思及其分类是很重要的。并且图像帧的序列对于分类视频中的动作是重要的。
递归层的输入是固定长度的向量序列,每个向量代表该序列中的一个元素。当处理文本时,序列的元素是单词,并且每个单词通过嵌入层或外部提供的例如 FastText 表示为数字向量。还可以设想从视频中提供图像帧序列,其中每一帧首先通过卷积转换成矢量。

递归层将这个输入向量序列转换成一个输出向量,其大小等于该层中使用的单元数。在其众多变体中,LSTM 和 GRU 是最流行的递归层实现,我们在这里重点讨论它们。
4.1 LSTM
我们参考 Colah 的博客来了解定义发生在 LSTM 层的变换的方程式。下面的图 3 描述了 LSTM 细胞对输入的作用。

Figure 3. An LSTM cell uses four dense layers each contributing equal number of parameters to the model.
一个 LSTM 电池包含四个致密层(三个具有s 形激活,一个具有 tanh 激活),全部采用与 LSTM 电池相同数量的指定单元。如图 3 所示,这些密集层中的每一层都接受相同的输入,该输入是先前隐藏状态 h_(t-1) 和当前输入序列 x_t 的串联。但是他们都有自己的一套在训练中学习到的权重和偏好。因此每个都将向 LSTM 层贡献相同数量的可训练参数。以下等式描述了 LSTM 单元中输入的变换。

Equation 3. An LSTM cell has three dense layers with sigmoid activation and one with tanh activation, They all employ the same number of units and take the same input at each timestep.
以下代码片段发送的句子长度最多为 25 个单词,总词汇量为 1000 个单词。嵌入层将每个单词转换成一个 256 长的数字向量,产生一个[None,25,256]的输入到具有 128 个单元的 LSTM 层。我们的公式表明,LSTM 单元将贡献 4 * 128 * (128 + 256 +1) = 197120 个可训练参数。
运行上面的代码,我们看到我们的公式获得的参数 count 与 Keras 获得的相匹配。
Layer (type) Output Shape Param #
=================================================================
Embedding_Layer (Embedding) (None, 25, 256) 256000
_________________________________________________________________
LSTM_Layer (LSTM) (None, 128) **197120**
=================================================================
Total params: 453,120
Trainable params: 453,120
Non-trainable params: 0

Figure 4. Flow of word sequences through an Embedding and LSTM layers
4.2 GRU
在详细讨论了 LSTM 单元之后,我们可以轻松地讨论 GRU 单元,因为它们的想法是相似的。我们只需要这个图像就可以确定它给模型添加了多少参数。

Figure 5. The GRU cell has two dense layers with sigmoid activation and one with tanh. They all employ the same number of units and take the same-sized input at each timestep.
tanh 密集层与其他两个 sigmoid 密集层具有不同的输入。但是输入的形状和大小是相同的,这导致了上面的公式。如果我们像以前一样运行同样的代码,用 GRU 代替 LSTM,如下所示:
我们应该从下面的 Keras 中得到 3 * 128 * (128 + 256 + 1) = 147840 个与模型摘要相匹配的参数。
________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
Embedding_Layer (Embedding) (None, 25, 256) 256000
_________________________________________________________________
GRU_Layer (GRU) (None, 128) **147840**
=================================================================
Total params: 403,840
Trainable params: 403,840
Non-trainable params: 0

Figure 6. Flow of word sequences through an Embedding and GRU layers
5.摘要
我们开发了 Keras 中密集层、嵌入层、LSTM 层和 GRU 层中使用的参数数量公式。我们已经看到了为什么输入/输出张量形状对这些层的预期作用有意义。这是一个汇总表。

Table 1. Input/Output shapes and parameter counts for various layers
我们就此结束这篇相当短的帖子。我们将在本系列的下一篇文章中讨论卷积和一个综合的例子。
原载于 2019 年 6 月 6 日http://xplordat.com。
又失败了!浅谈叶
当你想到可视化的时候,有很多很多好的选择:当你蹦蹦跳跳的时候有条形图,当你大脑分散的时候有散点图,当你只想发泄情绪的时候有直方图,当然,还有甜点的饼状图。不过,今天我要说的是一个有用的图书馆,叫做“叶”,它使用起来特别有趣,因为它通过地图将所有信息融入你周围的真实世界。
例如,假设我有一个我附近所有比萨饼店的纬度和经度列表,但是我想更具体地了解这些数字的实际含义。我可以将这些列表输入到 leav 中,轻松地在地图上标出每个点。

不错!现在我清楚地知道在我附近哪里可以找到比萨饼店。但是假设我不只是想知道他们的位置,也许我还想知道每个人的一些信息。通过电脑魔术,这是很容易做到的!leav 使我们能够在每个点放置弹出信息,因此您可以在那里存储相关信息,等待被点击!

我今天想吃五星级披萨吗?大概吧。我今天想花大价钱买五星级披萨吗?大概不会,所以有这个功能让我提前知道就好了。
对你们很多人来说,这是一个非常熟悉的图像。你可以打开谷歌地图,只需打个响指就能看到这样的图像(或者,如果你反应快的话,可以质疑你所有的生活选择:P)。所以现在我给你看一些你在谷歌上看不到的东西。假设我们不再想知道最近的比萨饼店在哪里,假设我们想知道城镇某一地区的星巴克密度,而不是其他地区。

正如你所看到的,星巴克的“热点”在市中心,这是有道理的,因为客流量非常大。当你走得更远时,步行交通变得不那么常见,建筑物之间的距离也更远,所以星巴克的密度会降低。当你有太多的点需要单独查看时,叶子热图很有用,这让你可以看到点之间的关系,而不会因为无数的标记而使图像陷入困境。相当甜蜜!
到目前为止,我们一直使用默认的地图背景,但没有更多!follow 有很多可定制性。也许你已经厌倦了看到道路和城市,也许更少的信息对你的特定图形来说更重要,也许你正在重现拉里·哈里斯设计的经典棋盘游戏:轴心国和盟国,但规模更大。说你想要白纸黑字,为什么不呢?你的愿望就是叶的命令!

或者你已经喝完了所有你能喝的咖啡,吃了超出人类可能的比萨饼,你决定离开这个城市去一座休眠火山顶上寻找孤独。给你更多的力量!叶有一个地形选项,所以你可以通过一个更地理的镜头来看事情。看起来你还有一段路要走。一定要穿有脚踝支撑的鞋子!

当我们的朋友出发去寻找山里的空气、水泡和内心的平静时,我注意到我们地图上的标记开始变得有点乏味。您可能会猜测,leav 提供了可定制的标记,如果您猜到了这一点,那么您就对了!让我们来看看。

有很多不同的颜色和图标可供选择,还有更多可以在网上找到,比如 Font Awesome!
世界上最受欢迎和最容易使用的图标集刚刚得到了升级。更多图标。更多款式。更多选择。
fontawesome.com](https://fontawesome.com/?from=io)
叶子不仅仅有标记。也许你想看看每个点周围的大致区域。对于显示半径来说,一个很好的选择是圆形选项,这样您可以感受到哪些地方可能共享一个配送范围或类似的东西。

如您所见,leav 有许多非常好的优势。许多可用的定制:不同的地图风格和标记,不同的图标和颜色,它也很容易使用。我非常喜欢 clear,因为它有助于以一种清晰易懂的方式直观地分解位置信息。想象一下使用散点图或条形图来理解相同的信息,你可以做到,但在我看来这样更好。希望这能给你一些关于如何在你自己的项目中使用 follow 的想法。好了,我快没时间了,完全没有双关语了,所以现在就这样吧!
跟随和学习:用 Python 实验大小

Photo by Crissy Jarvis on Unsplash
你想改变你的网站布局,以获得更多的点击。你决定运行一个实验,让一个控制组看到通常的页面,然后一个实验组看到一个新的布局。
假设你当前的网站点击率(CTR)是 p_null=10%,我们想把这个值提高到 p_alt=12%。我们需要多少访问者来评估我们的结果?为了达到一个可靠的水平,你需要进行多长时间的实验?是 10 天还是 10 年?
实际意义不仅有助于在实验结束时做出决定,也有助于设定实验范围。假设我们假设的幂 𝛽 为 80%, p 值为 0.05,分别指第二类和第一类误差。
在本帖中,我们将首先使用模拟来探索这个概念,以建立直觉,然后使用分析方法。请随意查看 jupyter 笔记本了解更多信息。
模拟方法
在第一种方法中,我们依靠蛮力,通过多种样本大小。每个样本大小都有一个相关的功率水平,通过足够的模拟,我们可以缩小所需的最小样本大小。
第一类错误
在零假设下,我们将有一个临界值,对于这个临界值,I 型误差低于我们期望的水平。
- 计算两组在零假设下的比例差异的标准差,其中成功概率为 p_null (10%)。差异分布的方差是各个分布的方差之和。
- 使用 scipy.stats.norm 创建一个正常的连续变量,以 0 为中心,并使用上一步中的标准偏差进行缩放。
- 使用 scipy 上的百分点函数方法计算导致我们拒绝空值的分布临界值。
第二类错误
功效是在替代假设下分布的比例,它超过了我们以前获得的临界值。
- 计算所需可检测差异下的标准偏差差异(此处为 12%)。各个分布会有不同的方差:一个是零(10%),一个是备选项(12%)。
- 像上一部分一样,创建一个正常的连续变量,以(p_alt-p_null)为中心,并按上一步中的标准偏差进行缩放。
- 使用概率密度函数计算 II 型错误的概率。
Python 脚本
将所有这些放在 python 代码中,我们得到了下面的脚本。
通过迭代,我们发现,对于 80%的幂 𝛽 ,样本大小约为 2863。下图显示了零假设和测试假设下的分布。

分析方法
我们就拿上面的情节来注解一下我们实际看到的。

用于确定统计显著性的临界值 p *落在 p_null 和 p_alt 之间。因此,从 p_null 到 p_alt 的距离可以细分为两部分:
- 从 p_null 到 p*的距离
- 从 p_alt 到 p*的距离
我们可以用单尾测试的 z 值来表示这些距离。
对于从 p_null 到 p*的距离:

对于从 p *到 p_alt 的距离:

将两者相加,我们得到:

标准误差 SE 可以表示为:


其中:
- n =观察次数
- s_null =两组在零假设下比例差异的标准偏差
- s_alt =所需可检测差异下的标准偏差差异(此处为 12%)
代入并求解 n,观察值的数量由下式给出:

Python 脚本
用 python 代码实现上述功能,我们得到了下面的函数。请注意,我们在最后对样本量进行了四舍五入。
我们获得了与模拟方法相同的样本大小 2863!
统计模型
或者,我们可以使用免费的 python 包,如 StatsModel ,它假设一个双尾测试,并使用 Cohen 的 h 作为两个概率之间的距离度量。下面的代码简洁且易于实现。
我们获得了一个比前一种方法更大的样本量:3021。
结论
我们可以使用模拟、严格分析和专门的软件包来估计样本大小。网上有多个样本量计算器,我们可以用它们来获得类似的结果。
最后,所有的方法都应该给出我们可能需要多少观测值的一个很好的大概估计。这将会给我们一个实验的范围,我们需要多少时间,是否值得。
记住最终目标:建立一个切实可行的实验。
本文基于 Udacity 数据科学家纳米学位 所需的一个练习。我建议报名参加该课程,以便更深入地理解这里显示的主题。另外,可以随意查看 统计意义 上的这篇帖子。
跟随和学习:Python 的统计意义

Photo by dylan nolte on Unsplash
如果你使用互联网,你很有可能在不知情的情况下参与了某人的统计实验。它通常是一个微妙的变化,如页面的颜色、按钮的形状或一些额外的文本。
Google Analytics 为其客户提供以测试为中心的服务,以优化网络流量。在这篇文章中,我们想探索如何从玩具数据集中得出统计意义。
建议在进入本帖之前熟悉实验设计、二项分布和 p 值。请随意浏览笔记本了解更多信息。
数据
假设我们想要增加点击下载按钮的用户数量。看看下面的数据表:

条件列取两个值:
- 对照组为 0
- 1 个用于实验组
点击列也取两个值:
- 0 表示不下载
- 1 个用于下载
不变度量
这里的条件是我们的不变度量,我们希望测试组和对照组在理想情况下是不可区分的。如果这个条件不成立,那么我们就不能把我们的研究建立在可靠的信息上:我们想在两个几乎相同的组中测试一个类似的事件。
模拟方法
我们可以通过随机的 50/50 分割来模拟组的分配。这是如何做到的:
- 记录对照组/实验组的参与者人数
- 对该组执行伯努利试验
- 将结果与原始分布进行比较
- 报告异常值的比例
下面的代码块显示了我们可以模拟 n 次随机抽取并记录异常情况。
该脚本返回 0.613,表明两组之间的差异在统计上不显著。我们关于两组相同的假设是正确的。
分析方法
二项式分布的标准差由下式给出:

- n=样本量
- p =成功的概率
- q =故障概率= (1-p)
z 得分是标准偏差的度量,例如,z 得分为 2 表示我们距离平均值有 2 个标准偏差。在我们的例子中,z 分数由下式给出:

- x =对照组的人数
- μ=平均值
- σ =标准偏差
注意,我们需要通过加或减 0.5 对 x 进行连续性校正。一旦我们有了 z 值,我们就可以从 z 得分表中得到分布两边曲线下的面积。
多亏了大样本和中心极限定理,我们才能够完成上述步骤。下面的直方图显示了模拟中样本的分布,有助于我们的主张。

在 python 代码中,我们使用与上一部分相同的变量运行下面的代码块。
成功!我们获得与模拟相同的 p 值!
评估指标
现在我们来看一下评估指标,即点击率(CTR)。我们的测试组和对照组之间的点击量差异显著吗?
我们的零假设是,使用单尾检验,两组的 CTR 是相同的。
首先让我们用下面的代码块记录一些统计数据。
模拟方法
该方法类似于我们为不变度量所做的方法,但有一些变化:
- 对控制组进行伯努利试验,成功概率为 p_null
- 以 p_null 的成功概率对实验组进行伯努利试验
- 记录两次试验平均值的差异
这可以在 Python 中完成,如下所示:
这返回 0.0395 的 p 值<0.05, this means our result is statistically significant and we should reject the null hypothesis. Again the central limit theorem is at work here by looking at the distribution of the differences in means.

Analytical Approach
We need to evaluate the pooled CTR for the analytical solution. To get there we calculate the pooled standard deviation using the formula below.

In python code the process is:
We obtain the same p value as in the simulation approach and again reject the null hypothesis.
Conclusion
Through analytical and simulation approaches, we have reached the same conclusions for the invariant and evaluation metrics respectively.
We can now solve similar problems using either approach! How many observations do we need for our experiment is covered in a post on 实验规模我们使用类似的概念。
追随你对数据科学的热情
以你的兴趣谋生
我们在不同的场合都听说过很多次“追随你的激情”。但是在这篇文章中,我想阐明你如何在数据科学中追寻你的激情。关键就在这里:没有数据科学。无论你更喜欢深入挖掘深度学习、分析金融数据还是浏览网页,你都将在不同程度上以不同形式从事数据科学。如果你想在数据科学领域取得真正的成功,你必须承诺无论面临什么样的挑战,都要追随自己的热情。

Follow your passion and build an amazing career
数据科学最棒的就是激情!
当你对某事充满热情时,它会给你更大的自信和动力。激情有时可以让你摆脱常规,但它也是一个非常强大的工具。对某事的热情让你看到你的数据和你的数据集告诉你的东西。它还可以帮助你从不同的角度解决问题,甚至可以让你成为更好的数据科学家。激情也是为什么有些人永远无法以数据科学家为生,为什么有那么多坏科学家的一大原因。
这是什么意思?如果你对数据科学有热情,那么谋生和获得数据科学成功所需的知识会更容易。众所周知,这个领域对人才的需求越来越大。这就是为什么你应该努力找到你所在领域最好的数据科学工作的原因。
数据科学有许多领域
举几个例子来说,您可以进入许多领域:
- 数据挖掘
- 数据分析
- 数据可视化
- 机器学习
比如数据挖掘,就是一种挖掘数据的方法,可以用在很多领域。它可以用于分析信息和进行预测,以便开发软件、游戏等。它还可以用于识别数据中潜在的错误来源,并找出不同数据组之间的趋势。
此外,这些领域分成更小的领域。例如,您可以专注于图像的数据分析。
图像分析是一种数据分析技术,涉及使用与模式分析相同的技术来识别图像。
企业需要数据科学家
然后,你可以问自己是否有特定的业务领域,你想应用数据科学。这是与你的激情联系的最好方式。找到一个你想涉足的特殊领域——也许是零售业,也许你有金融或健康方面的诀窍。这里的选择是无限的。
作为一名数据科学家,随着职业生涯的进展,您需要不断学习和发展。一份工作必然会给你机会与世界上一些最优秀的人一起工作。你将要从事的一些项目需要大量的专业知识,你需要准备好从别人那里获得帮助。
放心吧!数据科学社区非常受欢迎。
另外,请查看我关于成为数据科学家的其他文章:
我很乐意帮忙!
思考的食粮——纸星期二
用于快速在线更新的新矩阵分解
每周二,我都会强调我在研究或工作中遇到的一篇有趣的论文。希望我的评论能帮助你在 2 分钟内获得论文中最多汁的部分!
基本思想
无数的项目已经证明,自 2006 年 Netflix 奖竞赛以来,矩阵分解(MF)是无与伦比的最强大的推荐生成算法之一。关键思想是将用户-项目交互矩阵分解成两个具有较低等级的子矩阵。

From amazon aws
通过分解交互矩阵,MF 以某种方式提取出潜在的特征,这些特征在描述用户和物品方面表现惊人的好。如果您想了解算法本身的更多信息,可以参考以下资源列表:
- https://data jobs . com/data-science-repo/Recommender-Systems-[网飞]。pdf
- https://www . cs . CMU . edu/~ mgormley/courses/10601-s17/slides/lecture 25-MF . pdf
- https://developers . Google . com/machine-learning/推荐
传统 MF 在实践中的一个问题是,当矩阵变得巨大时(想象亚马逊必须分解其用户-商品交互矩阵,这将是一个数百万乘数百万的矩阵),定期更新潜在特征变得不可行(当用户进行新的购买时,我们希望该交易稍微影响潜在向量)。我看到了贺湘南、张汉旺、Kan Min-Yen 和 Tat-Seng Chua 的一篇论文,名为用于隐式反馈在线推荐的快速矩阵分解。他们提出了一种叫做 eALS 的新方法,这种方法大大加快了在线更新过程。
下面是链接:https://arxiv.org/abs/1708.05024
作者提出了一种通过缓存重复使用的值来加速 ALS 的新方法,以及一种避免每次新更新到来时解决整个问题的新技巧。

from He et al 2017
为了使在线模型更有效地响应最新的输入,作者提出了一种权重策略。

from He et al 2017

from He et al 2017
论文中另一个有趣的提议是他们所谓的面向项目的缺失数据加权,它可以将领域知识整合到算法中。例如,如果一个用户从来没有购买过一个流行的物品,那么这个物品应该比一个未被浏览的稀有物品更像是负面反馈。

from He et al 2017
结果
作者根据经验证明,置信度参数 c(见上图)确实在一定程度上提高了性能。

from He et al 2017
新的相互作用参数 W 的影响(W 越高,意味着新的相互作用的权重越大)

from He et al 2017
最令我兴奋的结果是时间复杂度的显著改善。所提出的新方法比普通的交替最小二乘法快 100+倍,交替最小二乘法是一种众所周知的解决矩阵分解的方法(这是有意义的,因为 ALS 每次需要更新潜在向量时都需要解决整个问题,而 e ALS 仅在附加数据上工作,并且以更高的空间复杂度为代价)。

一些想法
我在暑期实习期间自己开发了一个推荐系统,在线更新让我很头疼。我很高兴这篇论文让我们离模型更近了一步,模型可以很容易地更新,并包含更多的领域知识。
思考的食粮——纸星期二
调和过度参数化与偏差-方差权衡
每周二,我都会强调我在研究或工作中遇到的一篇有趣的论文。希望我的评论能帮助你在 2 分钟内获得论文中最多汁的部分!
基本思想
数据过量可能是任何机器学习课程中最基本的方法之一。这个想法很简单——太适合训练数据(通常有很多参数)的模型在测试集上的表现往往很差。过度拟合通常发生在模型被大量参数化时,如具有太多分裂的决策树或未规范化的回归模型。

Picture from StackOverflow
在这篇文章中,我想强调一篇相当令人兴奋的论文。Mikhai Belkin,Daniel Hsu,马思远和 Soumik Mandal 在他们 2019 年的论文中证明了现代机器学习实践和偏差-方差权衡的调和表明,如果模型足够复杂(他们称之为双下降曲线),测试误差最终会再次下降。

from the paper: Belkin et al 2019
论文网址:【https://arxiv.org/pdf/1812.11118.pdf
结果
研究人员在几个著名的数据集(MNIST,CIFAR10,SVHN)上,在广泛的机器学习模型(神经网络和基于树的模型,RFF)上测试了这一想法,并获得了一致的结果。

from the paper: Belkin et al 2019

from the paper: Belkin et al 2019

from the paper: Belkin et al 2019
一些想法
巧合的是,我正在为我的本科论文研究神经网络的过度参数化,我看到了几篇论文从数学上证明了如何更容易优化更大的网络( Oymak 和 Soltanolkotabi 2019 )。在其他模型中看到类似的现象确实令人兴奋,更重要的是,过度参数化可以改善测试误差。也许这是升级工作站的完美借口,因为拥有一百万棵树的随机森林会给你更好的结果:)
用时尚愚弄面部检测

Photo by Pavel Anoshin on Unsplash
Accompanying GitHub repository: [https://github.com/BruceMacD/Adversarial-Faces](https://github.com/BruceMacD/Adversarial-Faces)
面部识别的使用正在增加。随着最近关于面部识别伦理的辩论,我一直在考虑对面部识别的潜在敌对攻击。从 T2 机场到社交媒体,面部识别正被广泛应用。选择不接受面部扫描似乎几乎是不可能的。
对面部检测的理想攻击应该是一件在不知情的人看来不显眼的衣服。受 Hyperface 项目的启发,我决定研究并实现一个可穿戴的对抗实例。在这篇文章中,我将详细介绍创建一个敌对图像来欺骗选定类型的面部检测的过程,以及我如何在一个面具上实现一个实际的例子。
面部检测与面部识别

An illustration of facial detection (left) vs. facial recognition (right)
在深入研究这个项目之前,首先需要注意的是面部检测和面部识别之间的区别。面部检测指的是检测图像中何时出现面部的能力。面部识别依靠面部检测来确定图像中是否存在人脸,但它更进一步,试图确定这是谁的脸。
对于这个项目,我选择把重点放在面部检测上。主要是因为它更容易测试。为了正确地测试面部识别,访问面部识别数据库将是理想的。
面部检测模型
下一步是选择哪种面部检测模型来建立对抗的例子。目前有许多不同的面部检测模型在使用。Vikas Gupta 在“Learn OpenCV”上有一篇关于面部检测模型及其实现的很好的入门文章,其中有深入的解释。我将在这里简单回顾一下。
[## 人脸检测- OpenCV、Dlib 和深度学习|学习 OpenCV
在本教程中,我们将讨论 OpenCV 和 Dlib 中的各种人脸检测方法,并对这些方法进行比较
www.learnopencv.com](https://www.learnopencv.com/face-detection-opencv-dlib-and-deep-learning-c-python/)
- 深度神经网络(DNNs): DNNs 可以使用输入数据集进行训练,以检测许多不同方向的人脸。基于 DNN 的面部检测的一种流行方法是单镜头多盒检测器。dnn 是精确和通用的。
- 卷积神经网络(CNN):CNN 是一种深度神经网络,旨在为图像的不同部分分配重要性。它是健壮的,但是在 CPU 上相当慢。
- 哈尔级联分类器:哈尔级联使用具有大量标记的阳性和阴性示例图像的数据集来训练。Haar 级联分类器的主要缺点是它们只能正面识别人脸。它们不再被广泛使用,因为神经网络更加通用。
- 梯度方向直方图(HOG):HOG 是一种面部检测方法,它将经过处理的输入图像分成具有梯度方向的单元,然后将结果输入到支持向量机中。猪检测是快速和轻量级的,但对一些不寻常的面部角度不起作用。

A modeled example of a face as a histogram of oriented gradients from dlib
很快脱颖而出成为最简单的攻击候选模型的是梯度方向直方图。最值得注意的是,猪的预期输入可以很容易地被可视化并反馈到面部检测模型中。将面部可视化为定向梯度的直方图还具有对于人类观察者来说不是明显的面部的优点。
Python 中的梯度方向直方图人脸检测
**Note:** Expanded code samples with the functionality to display results are available on the accompanying [GitHub repository](https://github.com/BruceMacD/Adversarial-Faces).
为了测试这些例子,我需要一个简单的基于 HOG 的面部检测实现。幸运的是,dlib 库在其frontier _ face _ detector中内置了一个猪面部检测器。
import dlib
import cv2cv2.imread("path/to/input_img.png")
frontal_face_detector = dlib.get_frontal_face_detector()
upscaling_factor = 1
detected_faces = frontal_face_detector(img, upscaling_factor)
用输入图像和放大因子运行正面人脸检测器。放大因子 1 表示输入图像将被放大一次。放大可以创建更大的图像,从而更容易检测人脸。正面人脸检测的结果是一个边界框列表,每个检测到的人脸对应一个边界框。

The result of using the dlib to detect a face in our visualized HOG
通过猪的预期输入的可视化,您可以看到它被检测为一张脸。太好了!我们有了对抗性进攻的基础。
使用随机优化创建对抗性设计
现在我知道猪的预期输入的可视化将被检测为正面脸的假阳性,我需要创建一个在看起来不显眼的面具上打印的设计。然而,仍然有许多影响设计的因素,我不知道如何优化。人脸的位置、方向和大小都会影响图像中检测到的人脸数量。我可以简单地尝试不同的设计,直到我找到一个好的,但是让一个学习模型为我做艰苦的工作似乎更有趣,也不那么乏味。
我考虑了几种不同的模型来寻找最佳输入。我研究了强化学习、生成对抗网络和 Q 学习。最终,我决定使用随机优化的模拟退火,因为它最适合我的问题,即找到与 dlib 检测到的大多数人脸相对应的输入。
我使用 PIL(Python 图像库)和 mlrose(用于随机优化的 Python 库)来生成图像并找到最佳状态。用 mlrose 优化需要一个初始状态和一个适应度函数。在我的情况下,找到这个最佳状态是一个非常昂贵的计算,因为生成的状态需要作为图像保存到磁盘,以便找到检测到的人脸数量。
# indexes:
# 0 % 4 = pos_x
# 1 % 4 = pos_y
# 2 % 4 = rotation
# 3 % 4 = scale
initial_state = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
从初始状态开始,mlrose 需要一个 1D 数组(据我所知)。这意味着我不得不使用一个简单的解决方案,给不同的数组位置赋予不同的意义(参见索引解释)。我选择优化 6 个面的输入,因为我总是可以复制设计来增加它的大小。
def detected_max(state):
# converts the 1D state array into images
get_img_from_state(state)
return len(detect_faces(cv2.imread(OUTPUT)))
我的适应度函数只是由状态到图像的转换组成,然后检测图像中的人脸数量。找到的人脸数量越多,拟合度越好。我还试着根据输入猪脸图像的大小将适应度函数修改得更高。这可能更好,因为在现实生活中,更大的人脸更有可能被检测到。然而,我发现在视觉效果相似的情况下,考虑面部大小会导致计算时间延长。
fitness = mlrose.CustomFitness(detected_max)
problem = mlrose.DiscreteOpt(length=24, fitness_fn=fitness,
maximize=True, max_val=scale_factor)
schedule = mlrose.ExpDecay()
best_state, max_faces = mlrose.simulated_annealing(problem, schedule=schedule, max_attempts=10, max_iters=1000,
init_state=initial_state, random_state=1)
print('Optimal state found: ', best_state)
print('Max fitness found: ', max_faces)
# save the optimal found
get_img_from_state(best_state)
print("Number of faces in output: ", len(detect_faces(cv2.imread(OUTPUT))))
有了适应度和初始状态集,为模拟退火配置 mlrose 就很简单了。我只是分配我们的输入,并让它运行,直到找到一个最佳结果。我运行了几次,找到了一个视觉上有趣的结果。

An interesting output from the simulated annealing
最后,随着这个有趣的输出,我添加了一些收尾工作来模糊它的面部设计。我决定我更有资格用手来做这件事,因为我的意图是愚弄人类。

The final design that obscures the facial structure
测试掩模上的设计

Faces detected on a prototype mask covered in the adversarial face design
随着最终设计的完成,我创造了一些模拟面具设计来测试他们是如何被猪面部检测评估的。最初的结果似乎很有希望。上述设计始终返回 4–5 个错误检测的人脸。
足球转会 Python 美丽的汤 15 年分析
一个多页网页抓取教程使用美丽的汤

Image Courtesy of Nathan Dumlao via Unsplash
介绍
Web 抓取能够自动收集丰富的数据集。如果您可以在 web 浏览器中查看一些数据,您将能够通过程序访问和检索这些数据。如果你可以通过一个程序访问它,这些数据就可以以任何方式存储、清理和使用。
Web scarping 提供了一些优于应用程序编程接口(API)的优势。网络抓取是免费的,没有速率限制,让你接触到所有你想获取的数据,最重要的是,你想从中提取数据的网站可能没有 API。这就是网络抓取进入画面的时候。
本教程将探讨如何编写一个简单的 Web scraper,它可以收集过去 15 年英超联赛中每个球员的平均转会收入数据。
入门指南
首先,需要导航至 transfermarkt 。下一步是查看这个页面的底层 HTML,在 Chrome 中右键单击并选择“inspect”。
这里特别有用的是,你可以将鼠标悬停在 Elements 标签中的 HTML 标签上,Chrome 会在网页本身的表示上描绘一个透明框。这可以快速帮助我们找到我们正在搜索的内容。
或者,您也可以右键单击网页上的任何元素,然后单击“检查元素”。这将立即在 Elements 选项卡中突出显示相应的 HTML 代码。

在这里,我想提炼一下:收入,每个俱乐部的收入,每个球员的收入。为此,我通过将鼠标悬停在带有“ transferbilanz ”类的“div”标签上来找到包装器。
美丽的汤库
为了解析来自网页的收入信息,我使用了漂亮的 Soup 库。美丽的汤很容易安装使用画中画。
信息所在页面的 url 放在名为 URL 的变量的引号中。requests.get()方法用于“获取”URL 页面,结果保存在一个响应变量中(记住安装请求库并将其导入脚本)。
为了确认连接已经建立,我使用了。status_code 属性,并检查服务器是否返回了超文本传输协议(HTTP)状态代码 200。这意味着“成功”,页面已被检索。
包含当前页面的原始 HTML 内容的 response.text 被保存到变量“financial_data”中。现在,我创建一个 soup BeautifulSoup 对象,将原始 HTML 数据 financial_data 作为第一个参数传递,将“html.parser”作为第二个参数传递。这个解析器内置在 Python 中,不需要安装,用于解析 HTML 标签。
一旦我知道成功连接到网页,我就可以利用 URL 的结构。
在下面显示的 URL 中,每个足球赛季唯一变化的部分是年份。当我第一次检查连接时,年份是 2018 年(下面用红色和粗体格式突出显示)。为了检索过去 15 个赛季的相同信息,我只需将赛季编号更改为 2004!

我创建了一个简单的 for 循环来遍历从 2004 年到 2018 年的一系列年份,以检索每一年的收入数字。(记住最后一个数字 2019 被排除在名单之外
在这个 for 循环中,我需要的三条信息可以在 span 标记中找到,带有如下所示的“greentext”类。

为了检索这些数据,我使用汤找到了包含所有这些信息的第一个包装器。查找方法并将结果保存到名为 grouped_data 的变量中。
然后,我对 grouped_data 使用 find_all 方法来查找所有带有“greentext”类的 span 标签。由于 find_all 方法返回一个类似列表的对象,我可以索引这个列表来检索我需要的值,即列表中第一个元素的[0]指向该季节的“收入”(参见下面的 github 要点)。
我需要删除数字中的空格、欧元符号和句点,以便将值转换为浮点数,以便稍后进行基于数字的分析。我还将返回的每个值乘以 0.89,因为这是欧元和英镑之间的当前(2019 年 13 月 6 日)汇率,我希望数据框架中的结果以英镑为单位。
对于每一年(我的 for 循环的每一次迭代),我将数据附加到适当标记的列表中,即 income_per_player_list。
一旦所有迭代完成,我使用 pd 创建一个数据帧。DataFrame 方法(需要将 pandas 模块导入到脚本中),并写入 CSV 文件。
然后我读入数据帧;
Income 2004 _ 18 _ df = PD . read _ CSV(' Income _ euro _ to _ pounds . CSV ')

这使我们能够开始得出结论:
(使用 Python 的 Seaborn 库生成的条形图)
我们可以看到,除了 2018 年,每个赛季每个球员的转会收入都在稳步攀升。有趣的是,这一变化反映了 2016 年签署的英超电视转播权协议。现在游戏中的钱比以往任何时候都多。这使得支付给球员的转会费更高,足球俱乐部每个赛季每个球员的平均收入也相应增加!
天空电视台和英国电信体育公司为 2016-17 赛季三个赛季的英超联赛电视转播权支付了创纪录的 51.36 亿英镑。

结论
本示例教程演示了如何执行多页面网络抓取,并将数据转换为适合分析的 Pandas DataFrame。
本质上,我们已经扔掉了我们的网络浏览器,使用 Python 程序在网上冲浪来提取我们需要的信息!
“足球天气”——探究天气对 NFL QB 表现的影响

作为一个 NFL 的狂热粉丝,也许是一个更大的梦幻足球粉丝,我的梦幻足球爱好中一直困扰我的一个挫折来源是我不确定恶劣天气预报会对比赛和比赛中球员的统计产量产生多大影响。我经常发现自己对雨和风的预报反应过度,不管天气如何,坐冷板凳只会错过一场重要的比赛。这仅仅是小样本/坏运气,还是说“坏”天气实际上对一个专业运动员来说不是一个有意义的损害?如果 10 英里每小时的大风和小雨没什么大不了的,那么下雪呢?时速 15 英里的风怎么样?20?温度呢?在开始产生有意义的影响之前,温度需要达到哪个极端?这些是我在这篇博文中试图回答的问题,或者至少是阐明了一些问题。
找到 NFL 比赛的历史天气数据出奇地困难,但幸运的是,通过一些相当简单的 python 代码,我能够从profootballarchives.com收集到 1985 年至 2016 年间几乎每场比赛的基本天气信息。我也能够找到一些 NFL 的统计数据,尽管不幸的是这并不完全是按游戏的比分格式。从 Kaggle NFL 数据集,我决定使用 QB 游戏日志数据。经过一点争论,我能够将数据集合并到一个熊猫数据框架中。
在进行了相当多的清理、重命名列、对变量进行分类、宁滨一些连续数据以及转换/重铸数据类型之后,dataframe 已经准备好进行一些分析了。首先,由于四分卫通过空中传球获得报酬,我认为查看不同风速的数据将是一个好的开始。我将数据分为 4 类:0-10 英里,10-15 英里,15-20 英里和 20+英里。让我们来看看风对 QB 性能的几个测量的影响:

随着每个子图的风速类别从左到右增加,QB 性能有一个非常明显的下降趋势。随着游戏平均风速的增加,生产指标(码数、TD 传球数和幻想点数)以及效率指标(每次尝试的码数、QB 评分和完成百分比)都会受到影响。
接下来,我分析了降水的影响,分为 5 类:无(或室内/圆顶)、小雨、小雪、雨/风暴和雪/风暴。

奇怪的是,小雨似乎比暴雨有更深远的负面影响。然而,小雪、雨/风暴和雪的误差线相当大,这表明该数据可能不可靠。我对显著性进行了一些统计测试,得到的 p 值确实证明了这一点,小雪的 p 值为 0.75,暴雨为 0.26,雪为 0.09。这只是意味着结果可能是由于随机变化或混杂变量(如风速/温度——稍后会有更多介绍)造成的。

似乎温度本身并没有太大的影响。虽然似乎有理由假设极热或极冷的温度会对四分卫的表现产生现实世界的影响,特别是极寒,但这些类别的大误差棒再次表明样本量不足以达到统计意义。只是为了好玩,我决定绘制一张在最冷的温度下打最多比赛的 NFL 球队的热图。

好了,我们已经看到了孤立的天气类别如何影响 QB 统计,但是在现实世界中,这些天气类别很少孤立出现。在不同的温度范围内,有风和降水的游戏会有什么影响?使用 plotly express,只需一行代码,我们就可以将所有这些变量可视化:

这里发生了很多事情:每个点或泡泡代表一个 QB 游戏。温度用颜色表示,得分的幻想点数对应于 y 轴上的高度,风速用 x 轴表示。此外,分散点的大小代表该游戏的最大阵风,这就是为什么在平均风速较高的游戏中,较大的气泡大多出现在每个图形的右侧。最后,每个支线剧情代表该游戏的沉淀类别。该图中的两个主要要点是幻想点数和风速/阵风之间的负相关性,跨所有降水类别,以及在有降水的游戏中得分的总体下降趋势和较低的总体幻想点数。

上面的图显示了相同的数据(减去温度),但将所有的游戏分组在一起,这里气泡的颜色代表降水类别。
现在我们已经看到了一些风、温度和不同类型降水的总体影响的图形表示,我真的想深入分析一下在好天气和坏天气下玩的游戏中 QB 的表现。要成为好天气,它必须非常原始,没有任何天气因素会影响 QB 的表现。我选取了整体数据的一个子集,其中温度适中(在 60 到 80 度之间),天空晴朗(或在圆顶中),没有降水,平均风速低于 5 英里/小时。对于恶劣天气,我选择了有雨/风暴或雪(小雪和小雨除外)、平均风速超过 12 英里/小时或极端极地温度(低于 10 度或高于 95 度)的子集。由此产生的子集每个都包括 2000 多一点的 QB 游戏,这是一个不错的样本量。让我们看看恶劣天气对 QB 性能的影响有多大:

Negative % difference for all categories indicates worse QB stats in bad vs great weather, as expected
这里的主要要点是,在恶劣天气下,生产指标通常会有较大的百分比下降。效率指标虽然也普遍较差,但没有那么明显。这可能是因为在天气不好的比赛中,教练会减少传球次数。也许在未来,我会看一看运行数据,并在恶劣天气下寻找相应的量/冲尝试的增加。
最后,我想看看几个玩家,比较他们在好天气和坏天气游戏中的表现。我从经常在恶劣天气的主场比赛的球队中挑选了 3 名球员,他们是新英格兰爱国者队的汤姆·布拉迪、匹兹堡钢人队的本·罗特利斯伯格和绿湾包装工队的亚伦·罗杰斯。为了代表主场比赛在圆顶/好天气下进行的球队,我选择了印第安纳波利斯小马队的培顿·曼宁和圣路易斯公羊队/亚利桑那红雀队的库尔特·华纳。

不出所料,在《好天气 vs 坏天气》中,所有 5 个 QB 平均都有更多的幻想点数。恶劣的天气适应了 QBs 每个得分之间的伟大天气游戏多 2 至 3 个幻想点。同样不足为奇的是,库尔特·华纳的股价大幅下跌。虽然培顿·曼宁的微小差异乍一看有点令人惊讶,但曼宁以他的准备和一丝不苟的职业道德而闻名,这表现在他的表现一致性上,不管不利天气带来的任何挑战。
最后,作为一名铁杆新奥尔良圣徒队球迷,如果我不看一下德鲁·布里斯的统计数据,那就是我的失职。在梦幻足球世界中,人们相当普遍地声称,德鲁·布里斯在联赛中有一些更激烈的主场分裂,这意味着当他离开梅赛德斯·奔驰超级穹顶的友好“穹顶球场优势”时,他的表现会明显下降。这种定性准确吗?让我们看看这些数字是否支持这一声誉:

Say it ain’t so, Drew!!
唉,我不得不痛苦地承认,专家们是对的..
总之,这里是我从探索 NFL 天气数据中收集到的主要信息,因为它与 QB 的表现有关:
- 风/阵风的影响是影响 QB 性能的最重要的天气变量。从 0 英里/小时到大约 15 英里/小时的平均风速的影响相当轻微,但在这个阈值以上,幻想点的差异大约少 12%。在风速超过 20 英里/小时的游戏中,负面影响更加显著,每场游戏的幻想点数减少约 17%。
- 降水,就其本身而言,基本没有定论。尽管小雨游戏的平均幻想点数最低,但有雨/风暴和雪的游戏的数据在统计上并不显著。
- 温度作为一个孤立的天气变量,对 QB 的表现没有太大的影响。
- 总体来说,QBs 在天气好的时候比天气不好的时候多获得 8-10%的幻想分数。在相对百分比的基础上,在圆顶或天气好的城市打主场比赛的 QB 的一小部分精选样本,在恶劣天气下的表现并不比往往更经常在恶劣天气下比赛的 QB 差多少。
感谢阅读!为了更深入地探究这篇博文背后的数据和分析,这里是我的 github repo 的链接。




浙公网安备 33010602011771号