Python 中时间序列预测的脸书预言家
预言家预测
Prophet 是一个开源的时间序列预测算法,由脸书设计,易于使用,无需任何统计或时间序列预测方面的专家知识。Prophet 通过找到一条最佳平滑线来构建模型,该线可以表示为以下部分的总和**😗*
y(t) = g(t) + s(t) + h(t) + ϵₜ
- 总体增长趋势。g(t)
- 每年的季节性。s(t)
- 每周季节性。s(t)
- 假日效应 h(t)
在这篇博文中,我们将会看到下面列出的**fbprophet**
库中一些有用的函数,并附有一个例子。
**Prophet.fit**
**Prophet.predict**
**Prophet.plot**
**Prophet.plot_components**
**Prophet.add_seasonality**
**Prophet.add_regressors**
**Prophet.seasonalities**
**Prophet.predictive_samples**
让我们从描述我们将在演示中使用的样本数据集开始。
数据描述
我们将使用包含列(**date**
、**target**
、**regr1**
、**regr2**
)的合成每日时间序列数据(如下所示),共 180 天,其中**target**
是我们希望每天预测的值,**regr1**
、**regr2**
是影响目标值的外部因素。
让我们通过绘制目标列和回归列来直观地查看数据。
# Importing Libraries
import pandas as pd
# loading the time series data into a dataframe
df = pd.read_csv('ts_with_2regressors.csv')
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d') # plotting the time series data
df.plot(x='date', y='target', figsize=(20, 5), title='Time series Data')
样本时间序列数据
# plotting the regressors
ax = df.plot(x='date', y='target', figsize=(20, 5), title='Regressors Effect')ticks, _ = plt.xticks()y_min = df.target.min()
y_max = df.target.max()plt.vlines(x=list(df[df['regr1'] == 1]['date'].values), ymin=y_min, ymax=y_max, colors='purple', ls='--', lw=2, label='regr1')plt.vlines(x=list(df[df['regr2'] == 1]['date'].values), ymin=y_min, ymax=y_max, colors='green', ls=':', lw=2, label='regr2')plt.legend(bbox_to_anchor=(1.04, 0.5), loc="center left")plt.show()
回归因素效应
我们可以看到,时间序列数据中的尖峰是由回归变量造成的(**regr1**
、**regr2**
)。在接下来的章节中,我们将看到如何获取这些回归变量并对其建模。
先知安装:
和所有 python 库一样,您可以使用 pip 安装**fbprophet**
。先知的主要依赖是**pystan**
。
# Install pystan with pip before using pip to install fbprophetpip install pystan
pip install fbprophet
现在让我们看看如何使用上述功能:
生成预测:
- Prophet 遵循 sklearn 模型 API。我们创建了 Prophet 类的一个实例,然后调用它的 fit(
**Prophet.fit**
)和 predict(**Prophet.predict**
)方法。 - Prophet 的输入总是一个包含两列的数据帧:
***ds***
和***y***
。**ds**
(日期戳)列应该是熊猫所期望的格式,理想的是**YYYY-MM-DD**
表示日期,或者**YYYY-MM-DD HH:MM:SS**
表示时间戳。**y**
列必须是数字,代表我们希望预测的测量值。 - 为了演示,我们将使用前 150 天的
***target***
值作为训练数据,并预测所有 180 天的目标。
注意:对于这一步,我们将只考虑**date**
和**target**
列
# Creating train and predict dataframe
df = df.rename(columns={'date':'ds', 'target':'y'})
df_train = df[['ds', 'y']].iloc[:150]
df_predict = df[['ds']] # Fitting a Prophet model
model = Prophet()
model.fit(df_train)
forecast = model.predict(df_predict)forecast.head()
预测标题 GIF
# plotting the actual and forecast values
ax = (df.plot(x='ds',y='y',figsize=(20,5),title='Actual Vs Forecast'))
forecast.plot(x='ds',y='yhat',figsize=(20,5),title='Actual vs Forecast', ax=ax)
实际与预测
从上面的输出我们可以看到,prophet 非常适合数据,但它仍然无法捕捉数据中的突然跳跃。这些跳跃基本上是由 prophet 在默认情况下无法检测到的外部变量引起的。我们将在接下来的章节中看到如何对 Prophet 建模以捕捉这些外部因素。
绘制预测图:
- 我们可以通过调用
**Prophet.plot**
&**Prophet.plot_components**
方法并传入预测数据帧来绘制预测和组件,如下所示 - 预测图是一个包含历史数据点散点图的单一图表,由黑点表示,预测/拟合曲线由蓝线表示。该图还包含对应于不确定性带的浅蓝色阴影区域。
- 分量图是一组对应于各种时间序列分量(
**trend**
、**seasoanilities**
)和外部影响的图。
# Plotting the generated forecast
fig1 = model.plot(forecast, uncertainty=True)
预测产量图
# Plotting the forecast components.
fig2 = model.plot_components(forecast)
预测组件图
如开始提到的**Prophet**
根据训练数据估计了**trend**
和**weekly_seasonality**
。
现在让我们理解上面的两个图:
预测产量图:
- X 轴代表历史和未来日期的日期值(
**ds**
) 。 - Y 轴代表历史和未来日期的目标值(
**y**
、**yhat**
)。 - 图中的
**black dotted points**
代表历史训练数据点。 **blue line**
代表对历史和未来的预测。- 此外还有代表不确定性范围的
**light blue region**
(我们将在接下来的章节中看到更多相关内容。)
预测组件图:
- X 轴代表历史和未来日期的日期值(
**ds**
) 。 - Y 轴代表相应预测成分的预测估计值(
**trend**
、**seasonality**
) - Graph1:
**trend**
所有日期的值(历史和未来)。 - Graph2:
**weekly_seasonality**
基于训练数据的一周中每一天的每周配置文件。
正如我们所见,使用 prophet 可以很容易地对您的时间序列数据建立合理的预测模型。
添加自定义季节
- 在 Prophet 中,我们可以使用函数
**Prophet.add_seasonality**
对定制的季节性进行建模。 - 默认情况下,Prophet 会根据可用的训练数据自动模拟附加的
**daily**
、**weekly**
、**yearly**
季节性。 - 我们可以使用函数
**Prophet.seasonalities**
得到推断的季节性的细节 - 现在让我们用上面的方法来模拟一个
**monthly**
季节性。
# Modelling a custom monthly seasonality
model2 = Prophet()
model2.add_seasonality(name='custom_monthly', period=30.5, fourier_order=10)model2.fit(df_train)
forecast2 = model2.predict(df_predict)print(model2.seasonalities)
fig1 = model2.plot(forecast2, uncertainty=True)
fig2 = model2.plot_components(forecast2, uncertainty=True)
从上图中我们可以看到,Prophet 已经对 custom_monthly 季节性进行了建模,与默认预测相比,预测也有所修改。
添加外部回归变量
到目前为止,prophet 模型还不能对训练数据中的一些点进行建模。我们知道,由于外部回归因素(**regr1**
、**regr2**
),这些值偏离了常规值。
现在让我们看看如何捕捉这些价值并对它们建模。
- 与季节性相似,prophet 也有一种方法,使用函数
**Prophet.add_regressors**
捕捉/模拟对目标值有影响的外部因素。 - 在我们使用的样本数据中,我们提到了影响目标值的两个外部回归量。
- 为了模拟和预测回归变量的影响,训练和预测数据框架都应该包含回归变量数据。
- 现在让我们来看看如何使用上述函数对这些回归变量进行建模。
注意:回归变量应该是数值,如果回归变量包含字符串数据,您必须执行一次热编码。
# adding regressor data in historical and future datesdf_train3 = (df[['ds', 'y', 'regr1', 'regr2']]
.iloc[:150]
.copy())
df_predict3 = df[['ds', 'regr1', 'regr2']].copy()# modelling external regressors prior to model fittingmodel3 = Prophet()
model3.add_regressor('regr1')
model3.add_regressor('regr2')
# fit and predcit
model3.fit(df_train3)
forecast3 = model3.predict(df_predict3)# Plot the forecast
fig1 = model3.plot(forecast3, uncertainty=True)
具有外部回归变量的先知模型
# plot model components
fig2 = model3.plot_components(forecast3, uncertainty=True)
具有外部回归的先知模型组件
从上面的输出中,我们可以观察到该模型已经很好地捕捉到了两个外部影响,从输出图中,我们可以看到目标值相对于回归变量有大约 5%和 20%的提升。
先知后验样本
- 默认情况下,prophet 每天生成 1000 个后验样本,以估计不确定性范围的上限和下限。在某一天,后验样本的平均值几乎等于预测值
**yhat**
- Prophet 可以使用函数
**Prophet.predictive_samples**
访问历史和未来某一天的后验样本 - 我们可以使用参数
**uncertainty_samples**
在 prophet 实例化时修改样本数量 - 现在让我们用预测数据帧中 1 周的后验样本生成一个数据帧。
# Select one week from prediction df
df_1wk = df_predict3.iloc[:7]
df_1wk
# fetching the posterior samples.
samples = model3.predictive_samples(df_1wk)df_samples = pd.DataFrame(data=samples['yhat'], index=df_1wk['ds']).reset_index()df_samples
现在让我们计算一天后验样本的平均值,并将其与当天的预测值进行比较。
# Forecast
forecast3[forecast3['ds'] == '2018-09-02'][['ds', 'yhat']]
# Mean of the posterior samples.
df_samples[df_samples['ds'] == '2018-09-02'].set_index('ds').mean(axis=1)
我们可以看到后验样本的平均值几乎等于某一天的预测值。
结论:
我们的演示到此结束。希望你会发现这篇文章内容丰富。您可以在以下存储库中找到数据和 Jupyter 笔记本:
此文件夹包含与各种时间序列任务相关的 jupyter 笔记本:Time _ series _ analysis _ FB prophet . ipynb:This…
github.com](https://github.com/upraneelnihar/ML-Projects/tree/master/Time_series)
请尝试使用 prophet 为您的时间序列数据建模,并在评论部分分享您的想法。
脸书树立了语言翻译的新里程碑
了解 M2M-100,一种多语言机器翻译模型,可在 100 种语言对之间进行翻译。
格伦·卡丽在 Unsplash 拍摄的照片
L 语言翻译是一项具有挑战性的自然语言处理任务,需要大量的数据来训练模型。然而,在过去几年中已经取得了很大进展。最近,脸书发布了一个新的多语言机器翻译(MMT)模型,为这项挑战树立了一个新的里程碑[1]。
这是第一个在任意两种语言之间翻译时不依赖英语数据的模型。使用 BLEU 指标,它也以 10 分的优势击败了以前以英语为中心的模型。凭借其创造性的数据挖掘方法和数量惊人的计算资源,他们建立了一个真正令人印象深刻的模型。
在本文中,我们将研究模型的架构,并强调它们用来创建数据集的最重要的方法。
创建数据集
大模型需要大量的数据。为了训练这个模型,来自脸书的研究人员使用了 75 亿个句子!可想而知,要为 100 种语言找到一个高质量的句对句翻译对是很难的。
所有语言翻译都重要吗?
有些译本非常罕见。例如,土耳其语到缅甸语,或者阿尔巴尼亚语到斯瓦希里语。这些语言之间的高质量翻译数据也很难找到。脸书大学的研究人员通过将语言分成具有相似特征的语系来简化翻译任务。例如,斯拉夫语和德语是两个独立的语系。在每一个语系中,他们确定了一个语系中最常见的过渡语言。这些桥接语言被用作在两种不同语系的语言之间进行翻译的中间步骤。
以英语为中心的数据与桥梁策略。修改自[1]
例如,不是有一个直接的阿尔巴尼亚语到斯瓦希里语的翻译,而是有阿尔巴尼亚语-希腊语(桥)-斯瓦希里语(桥)。这有助于减少数据集的大小,并证明有助于训练每个模型的特定模型参数[1]。
通过这种方法,他们简化了翻译任务,同时没有牺牲太多的性能损失。总共,它使用 100 种语言产生 2200 个方向的翻译。这比平移方向的最大可能数量 9900 (100 x 99)小得多。
数据挖掘技术
很难找到直接的句子到句子的翻译。然而,更容易找到以不同语言出版的相同文件。但是,为了创建相应的句子对,我们需要在文档中“关联”不同语言的句子。
为了找到相应的句子对,脸书的研究人员使用了 CCMatrix [2]和 CCAligned [3]。
CCMatrix 通过比较两种语言中所有可能的、独特的句子来工作[1]。这种方法计算量很大,CAligned 可能是一个更有效的选择,因为它预先选择了要一起比较的文档[1]。
M2M 100 建筑
该模型的架构基于众所周知的变压器架构。由于任务的复杂性,他们增加了标准架构的规模。M2M-100 的参数如下:
- 24 个编码器和 24 个解码器层(因不同实验而异)
- 嵌入尺寸大小为 1024
- FFN(前馈网络)尺寸为 8192
变压器架构的架构。修改自[4]
变压器采用序列对序列架构。给定输入序列,模型将产生另一个输出序列。这个模型的问题在于它是为双语数据设计的[1]。为了适应多对多语言的情况,在编码器和解码器层添加了特殊的标记来表示源语言和目标语言[1]。
该模型的另一个改进是添加了特定于语言的参数。这提高了模型的性能,但也增加了总参数的数量。
结果
最终的 M2M-100 模型包含 150 亿个参数。他们还有一个更小的版本(只有?)15 亿个参数。
测试数据集上的结果显示了 M2M 模型的优势。
多对多和以英语为中心的模型的 BLEU 分数。
以英语为中心的模型和多对多模型在从任意语言到英语的翻译中表现相似,反之亦然(前两列)。然而,对于非英语翻译,例如瑞典语到斯瓦希里语,或者德语到波兰语,M2M 模型实现了显著更高的 BLEU 分数。这就是这种模式的精髓和新颖之处。
此外,该模型在由人类评估语义准确性(得分从 1 到 10)时表现更好。对于每一对语言,每个评估者给每个模型 50 个句子打分。我们可以从下面的图中看到,M2M-100 对于非英语翻译来说要好得多。
翻译质量的人工评估(语义准确性)。M2M-100 vs 以英语为中心的模式。修改自[1]。
是开源的吗?
你可以在这里找到他们的开源实现。 但是,要注意:数据集和模型是巨大的。它是在多汁的脸书服务器上训练的,而不是在普通人的个人电脑上。尽管他们提供了模型检查点,但是您需要至少 64GB 的内存来将其存储在内存中。
结论
M2M-100 是语言翻译任务的又一个里程碑。当英语作为中间翻译步骤被去除时,翻译的质量显著提高。不幸的是,这种型号很难使用,即使是在好的电脑上,因为它需要 64 GB 的内存。
关于我
我是阿姆斯特丹大学的人工智能硕士学生。在我的业余时间,你可以发现我摆弄数据或者调试我的深度学习模型(我发誓这很有效!).我也喜欢徒步旅行:)
如果你想了解我的最新文章和其他有用的内容,以下是我的其他社交媒体资料:
参考
[1] 超越以英语为中心的多语言机器翻译
你所需要的只是注意力
[5] 第一个不依赖英文数据翻译 100 种语言的 AI 模型(来自论文的博文)
脸书的数据科学面试实践问题
一些脸书面试问题的预演!
鉴于我的文章的受欢迎程度…
这次我在网上收集了一些脸书的数据科学面试问题,并尽我所能回答了它们。尽情享受吧!
如果这是你喜欢的那种东西,成为第一批订阅 我的新 YouTube 频道在这里 !虽然还没有任何视频,但我会以视频的形式分享很多像这样的精彩内容。感谢大家的支持:)
问:你从 100 枚硬币中随机抽取一枚——1 枚不公平硬币(正面朝上),99 枚公平硬币(正面朝下),然后掷 10 次。如果结果是 10 头,硬币不公平的概率是多少?
这可以用贝叶斯定理来回答。贝叶斯定理的扩展方程如下:
假设选到不公平硬币的概率表示为 P(A),连续翻转 10 个头像的概率表示为 P(B)。那么 P(B|A)等于 1,P(B∣ A)等于 0。⁵ ⁰,P( A)等于 0.99。
如果填入等式,那么 P(A|B) = 0.9118 或者 91.18%。
问:有一栋楼有 100 层。你有两个相同的鸡蛋。如何用 2 个鸡蛋找到门槛楼层,在这里鸡蛋肯定会从 N 层以上的任何一层破开,包括 N 层本身。
更具体地说,问题是在给定两个鸡蛋的情况下,寻找阈值下限的最优方法。
为了更好地理解这个问题,我们假设你只有一个鸡蛋。要找到门槛楼层,你可以简单地从第一层开始,放下鸡蛋,然后一层一层往上爬,直到鸡蛋裂开。
现在想象我们有无限的鸡蛋。寻找最低门槛的最佳方法是通过二分搜索法。首先,你会从 50 楼开始。如果鸡蛋裂开了,你就在 25 楼扔一个鸡蛋,如果鸡蛋没有裂开,你就在 75 楼扔一个鸡蛋,重复这个过程,直到你找到门槛楼层。
对于两个鸡蛋,寻找阈值下限的最佳方法是上述两种解决方案的混合…
例如,你可以每隔 5 层扔第一个鸡蛋,直到它破了,然后用第二个鸡蛋找出在 5 个增量之间的哪一层是门槛层。在最坏的情况下,这将需要 24 滴。
如果你每隔 10 层楼扔第一个鸡蛋,直到它破掉,在最坏的情况下,它会掉 19 下,这比每隔 5 层楼扔第一个鸡蛋要好得多。但是如果你想做得更好呢?
这就是最大遗憾最小化这个概念发挥作用的地方。基本上,这意味着当你以给定的增量完成更多的下降(你跳过多少层)时,你希望每次都缓慢地减少增量,因为阈值楼层的可能楼层会更少。这意味着如果你的第一次下落是在 n 层,那么你的第二次下落应该是在 n + (n-1)层,假设它没有破裂。这可以写成下面的等式:
更进一步,这可以简化为:
求解 n,你得到大约 14。因此,你的策略将是从第 14 层开始,然后 14+13,然后 14+13+12,等等,直到它打破,然后使用第二个蛋一次一层地找到门槛层!
问:我们有两种在 Newsfeed 中投放广告的选择。方案一:每 25 个故事中,就有一个是 ad。方案二:每个故事都有 4%的几率成为广告。对于每个选项,100 个新闻故事中显示的广告的预期数量是多少?
两种选择的预期赔率都是 4/100。
对于选项 1,1/25 相当于 4/100。
对于选项 2,100 的 4%是 4/100。
如果我遗漏了什么,请随时告诉我,因为这个问题似乎太直截了当了!
问:只知道性别身高,你如何证明男性平均比女性高?
你可以用假设检验来证明男性平均比女性高。
零假设是男性和女性平均身高相同,而另一个假设是男性的平均身高高于女性的平均身高。
然后,您将收集男性和女性身高的随机样本,并使用 t 检验来确定您是否拒绝空值。
问:如果 iOS 上 70%的脸书用户使用 Instagram,但 Android 上只有 35%的脸书用户使用 Instagram,你会如何调查这种差异?
有许多可能的变量会导致这种差异,我将检查一下:
- iOS 和 Android 用户的人口统计数据可能会有很大差异。例如,根据 Hootsuite 的调查,43%的女性使用 Instagram,而男性只有 31%。如果 iOS 的女性用户比例明显高于 Android,那么这可以解释这种差异(或者至少是部分差异)。这也适用于年龄、种族、民族、地点等
- 行为因素也会对差异产生影响。如果 iOS 用户比 Android 用户更频繁地使用手机,他们更有可能沉迷于 Instagram 和其他应用程序,而不是那些在手机上花费时间少得多的人。
- 另一个需要考虑的因素是 Google Play 和 App Store 有什么不同。例如,如果 Android 用户有明显更多的应用程序(和社交媒体应用程序)可供选择,这可能会导致用户的更大稀释。
- 最后,与 iOS 用户相比,用户体验的任何差异都会阻止 Android 用户使用 Instagram。如果这款应用对安卓用户来说比 iOS 用户更容易出错,他们就不太可能在这款应用上活跃。
问:每个用户的点赞数和在一个平台上花费的时间在增加,但用户总数在减少。它的根本原因是什么?
一般来说,你会想从面试官那里获得更多的信息,但是让我们假设这是他/她唯一愿意提供的信息。
关注每个用户的点赞数,有两个原因可以解释为什么这个数字会上升。第一个原因是,随着时间的推移,用户的平均参与度普遍提高了——这是有道理的,因为随着使用该平台成为一种习惯性做法,久而久之的活跃用户更有可能成为忠实用户。每个用户点赞数会增加的另一个原因是分母,即用户总数,在减少。假设停止使用该平台的用户是不活跃的用户,也就是参与度和点赞数低于平均水平的用户,这将增加每个用户的平均点赞数。
上面的解释也适用于在平台上花费的时间。随着时间的推移,活跃用户变得越来越活跃,而很少使用的用户变得不活跃。总体而言,参与度的增加超过了参与度很低的用户。
更进一步说,有可能“参与度低的用户”是脸书能够检测到的机器人。但随着时间的推移,脸书已经能够开发出识别和删除机器人的算法。如果以前有大量的机器人,这可能是这种现象的根本原因。
问:脸书发现赞数每年增长 10%,为什么会这样?
给定年份的总赞数是用户总数和每个用户的平均赞数的函数(我称之为参与度)。
用户总数增加的一些潜在原因如下:由于国际扩张而获得的用户以及随着年龄增长而注册脸书的年轻群体。
参与度增加的一些潜在原因是用户对应用程序的使用增加,用户变得越来越忠诚,新的特性和功能以及用户体验的改善。
问:如果我们测试产品 X,你会看什么标准来确定它是否成功?
决定产品成功的标准取决于商业模式和企业试图通过产品实现的目标。《精益分析》一书展示了一个很好的框架,人们可以用它来确定在给定场景中使用什么指标:
精益分析框架
问:如果一个项目经理说他们想把新闻订阅的广告数量增加一倍,你如何判断这是不是一个好主意?
您可以通过将用户分成两组来执行 A/B 测试:一组是广告数量正常的对照组,一组是广告数量翻倍的测试组。然后,您将选择指标来定义什么是“好主意”。例如,我们可以说零假设是广告数量翻倍会减少花在脸书上的时间,另一个假设是广告数量翻倍不会对花在脸书上的时间有任何影响。但是,您可以选择不同的指标,如活跃用户数或流失率。然后,您将进行测试,并确定拒绝或不拒绝 null 的测试的统计显著性。
问:有一个游戏,给你两个公平的六面骰子,让你掷骰子。如果骰子上的数值之和等于 7,那么您将赢得 21 美元。但是,每次掷出两个骰子,你都必须支付 5 美元。你玩这个游戏吗?
掷出 7 的几率是 1/6。
这意味着期望支付 30 美元(5*6)赢得 21 美元。
取这两个数字,预期支付额为-$ 9(21–30)。
由于预期付款为负,你不会想要支付此游戏。
感谢阅读!
如果你喜欢我的工作,想支持我…
- 支持我的最好方式就是在MediumT7 这里关注我。
- 在 Twitter 这里成为第一批关注我的人之一。我会在这里发布很多更新和有趣的东西!
- 此外,成为第一批订阅我的新 YouTube 频道 这里!
- 在 LinkedIn 这里关注我。
- 在我的邮箱列表 这里注册。
- 查看我的网站,terenceshin.com。
资源
脸书雇佣了 CNN 的发明者 Yann LeCun。
medium.com](https://medium.com/acing-ai/facebook-ai-interview-questions-acing-the-ai-interview-5982add0af55) [## 脸书数据科学家面试问题
我在 12 月下旬在网上申请,然后在 1 月初与一名招聘人员谈了大约 15 分钟。我被安排…
www.glassdoor.ca](https://www.glassdoor.ca/Interview/Facebook-Data-Scientist-Interview-Questions-EI_IE40772.0,8_KO9,23_IP3.htm) [## 脸书数据科学家访谈
脸书数据科学家的职位是科技界最令人垂涎的职位之一。它需要多种技能…
towardsdatascience.com](/the-facebook-data-scientist-interview-38556739e872) [## 来自脸书的数据科学面试问题示例:
我们分析了发布在 Glassdoor 上的脸书数据科学家的采访经历和发布的普通采访…
mockinterview.co](http://mockinterview.co/index.php/2018/04/07/sample-data-science-interview-questions-from-facebook/)
脸书的代码转换器——一个人工智能源代码到源代码的编译器
来源:https://arxiv.org/pdf/2006.03511.pdf
使用超过 280 万个开源 GitHub 存储库,TransCoder 在三种流行语言(C++、Java 和 Python)之间翻译代码。
澳大利亚联邦银行花了近 5 年时间和 7 . 5 亿美元将他们所有的代码平台从 COBOL 转换成 Java。
纽约(路透社)-比尔·欣肖不是一个典型的 75 岁老人。他在家庭中分配时间,他有 32 个…
www.reuters.com](https://www.reuters.com/article/us-usa-banks-cobol/banks-scramble-to-fix-old-systems-as-it-cowboys-ride-into-sunset-idUSKBN17C0D8)
用最新的编程语言或技术来更新系统需要花费数百万美元和数年时间。脸书人工智能研究所(FAIR)的团队开发了一种人工智能模型,代码转换器。FAIR 旨在通过自动化解决这一问题,借助先进的深度学习架构,在当今使用的 3 种流行编程语言之间进行翻译。
编程语言的现状
每个月,大约有 5000 万人访问 Stack Overflow 来学习、分享和建立他们的职业生涯。行业估计…
insights.stackoverflow.com](https://insights.stackoverflow.com/survey/2020#most-popular-technologies)
今天使用的编程语言有数百种,从最早的 COBOL、Fortran 到现代的高级编程语言,如 C++、Java 和 Python。今天,T2 最流行的技术包括 JavaScript、Python、Java、C#和 C++。
Fortran、Lisp 和 COBOL 是至今仍在广泛使用的一些最古老的编程语言,尽管不再受到开发人员的青睐。COBOL 仍然是金融业依赖的主要语言,还有大型机系统。在全球范围内, 95%的 ATM 刷卡和 80%的当面交易都使用 COBOL 。Fortran 正被用于科学编程和高性能计算。这些遗留系统将来需要重新开发,以防止资源过时或贬值。
代码转换器将有助于以经济高效的方式,在最少的人工干预下,将他们的平台转换成现代语言。
模型
目前,代码转换工具是基于规则的,以将源代码标记化并将其转换为抽象语法树(AST ),在抽象语法树上使用手工规则。然后,AST 被转换以生成目标语言的代码。写规则的过程很耗时。
使用无监督学习的方法,人工智能模型训练了超过 280 万个开源 GitHub 知识库,以将代码片段从一种语言映射到另一种语言。来自不同语言的编程关键字被标记化并检查相似性。
代码转换器使用序列到序列(seq2seq)模型,编码器和解码器采用转换器架构。该模型实现了交叉编程语言模型预训练、去噪自动编码和回译——遵循无监督机器翻译的原则。
来源:https://arxiv.org/pdf/2006.03511.pdf
结果
FAIR 在所有 3 种语言中使用了 852 个并行函数,并通过单元测试来检查翻译的准确性。
结果如下,Java 到 C++的翻译具有最高的准确率,为 91.6%。
来源:https://arxiv.org/pdf/2006.03511.pdf
未来
人工智能的出现令人兴奋,同时也令人恐惧。人工智能将改善人类的整体体验,成为我们生活的一部分,就像帮助我们在社会中更好地相互联系和沟通。
未来,随着翻译的改进,程序员可以使用单一代码库轻松改进和构建健壮的跨平台软件,如应用程序和游戏。人工智能也可以被训练从头开始编写代码,就像莱斯大学研究人员开发的BAYOU。
莱斯大学的研究人员创造了一个名为 BAYOU 的深度学习软件编码应用程序,可以帮助人类…
www.techrepublic.com](https://www.techrepublic.com/article/developers-rejoice-now-ai-can-write-code-for-you/)
但是随着源到源翻译的改进,智能机器将提高自动化程度,并可能取代一些工作,特别是在测试方面。编程的未来在等着我们!
参考资料:
来源:编程语言的无监督翻译
在 LinkedIn 和 GitHub 上关注我,或者访问我的网站 rohitssajja . me
基于 Tensorflow 和 OpenCV 的人脸检测
格伦·卡丽在 Unsplash 拍摄的照片
简介:
在新冠肺炎危机中,戴口罩对于公共卫生和控制疫情的传播是绝对必要的。如果我们有一个系统可以监控我们周围的人是否遵守这些安全措施,会怎么样?
借助深度学习的力量,这个系统可以检测出一个人是否戴了面具。感谢 prajnasb 编译我用于训练的数据集。我们从这里开始…
在 COVID19 危机中,戴口罩对于公共卫生和控制传染病的传播是绝对必要的
github.com](https://github.com/HOD101s/Face-Mask-Detection)
了解我们的数据:
在网上找到的所有资料中,由 prajnasb 发布的数据很容易处理,所以我继续使用同样的方法。任何具有大量戴口罩和不戴口罩的人的图像的数据集都适用于这个问题。
照片由马纳斯·阿查里亚拍摄
我们的数据相当平衡,667 幅图像属于无 _ 掩码类,690 幅图像属于有 _ 掩码类。
处理我们的数据:
由于 keras 强大的处理库,图像增强和数据管道化的过程变得非常容易。这里我使用了 keras 在其图像预处理函数下提供的imagedata generator。使用 ImageDataGenerator,我们可以构建图像数据扩充管道,在训练期间实时执行扩充,从而节省内存,使其成为扩充数据的一个非常方便的解决方案。
这里我构建了两个数据生成器:一个用于训练和验证数据,另一个用于测试数据。训练和验证数据只是水平翻转,所以也没有应用大的扩充。完整的功能列表可在文档中找到。一个单独的测试数据生成器已经完成,因为我没有对它进行任何扩充。
模型架构和训练:
我已经利用了迁移学习,感谢 tensorflow 应用程序 api ,这是一个非常简单的任务。使用MobileNetV2模型来构建我的分类器网络。
通过使用迁移学习,我正在利用预先训练的 MobileNetV2 的特征检测能力,并将其应用于我们相当简单的模型。MobileNetV2 之后是我们的 DNN,由 GlobalAveragePooling、Dense 和 Dropout 层组成。由于我们的问题是一个二元分类问题,最后一层有 2 个神经元和 softmax 激活。
一个通用的 Adam 优化器和 Categorical _ crossentropy loss 可以很好地收敛到我的网络的最佳权重。
到第六个纪元时,我在所有测试、训练和验证集上都获得了 100%的结果。
部署:
照片由Adam niecioruk在 Unsplash 上拍摄
在这里,我使用了流行的 OpenCV 库来获取我的网络摄像头馈送,并在我的模型中运行它。
我们从人脸检测开始。为此,我使用了哈斯级联分类器算法。接下来,我切下面部图像,对其进行归一化和缩放,然后输入到我的分类模型中。
用于训练网络的图像尺寸为 224 x 224,因此我们需要按照 prepImg(pth) 中所述,通过整形和归一化,以类似的方式准备我们的输入图像。
Gif 由 Manas Acharya
OpenCV 实现只是以下内容的连续循环:
- 检测面部
- 切片人脸图像
- 通过分类器
- 获取并显示预测
结论:
我们已经准备好了一个工作模型,它可以检测一个人是否戴着面罩,并提示这个人戴上面罩!鉴于事情的发展方式,口罩将成为我们社会和公众健康的一个更重要的组成部分,在这种情况下,该系统将是确保遵守安全指南的必要条件。
如果您有任何建议或想法想要讨论,您可以通过 LinkedIn 与我联系。精益求精!
** [## Manas Acharya -机器学习工程师- KubixSquare | LinkedIn
机器学习和数据科学爱好者,曾在前端 Web 领域担任全栈开发人员…
www.linkedin.com](https://www.linkedin.com/in/manas-acharya/) [## 使用 Tensorflow 和 OpenCV 构建石头剪刀布人工智能
古老的游戏被重新想象和更新……
towardsdatascience.com](/building-a-rock-paper-scissors-ai-using-tensorflow-and-opencv-d5fc44fc8222)**
基于面部数据的深度学习:情感、年龄和性别预测
在 Unsplash 上 Sai Kiran Anagani 拍摄的照片
我们如何利用深度学习从面部数据中提取信息?
深度学习在计算机视觉领域有着巨大的应用。计算机视觉的一些最重要的应用是在处理面部数据的领域。人脸检测和识别广泛应用于安全领域。如果您想探索这两个领域,请随意浏览:
- 人脸检测:在这篇文章中,我谈到了一个基于人脸检测的应用
- 人脸识别:这篇文章讲述了我们如何使用人脸识别来实现一个安全机制。
在上面的文章中,我已经尝试对这些机制是如何工作的给出了一个完整的解释。
在本文中,我们将讨论基于面部的深度学习的另外两个最重要的应用,情感检测或面部表情检测,以及从面部图像预测年龄和性别。
所以,让我们直接开始吧。
情感检测
首先,让我们谈谈情绪检测或预测。
对于这一部分,我们将使用 Kaggle 的 CKPlus 数据集。
数据预处理
该数据集总共有 981 幅图像。这些图像根据七种不同的表情被分为七个标签:愤怒、轻蔑、厌恶、恐惧、快乐、悲伤和惊讶。每个表达式都有一个文件夹。因此,有七个文件夹存储 981 幅图像。
首先,我们使用 listdir()按顺序列出类或文件夹。
**import** **os**
files=os.listdir(fldr)
>>
['fear', 'contempt', 'happy', 'anger', 'surprise', 'disgust', 'sadness']Exp=['fear', 'contempt', 'happy', 'anger', 'surprise', 'disgust', 'sadness']
因此,我们将表达式按顺序保存在一个列表中,我们将参考该列表来创建标签。
接下来,我们对图像进行预处理。
**import** **cv2**
**from** **google.colab.patches** **import** cv2_imshow
i=0
last=[]
images=[]
labels=[]
**for** fle **in** files:
idx=Exp.index(fle)
label=idx
total=fldr+'/'+fle
files_exp= os.listdir(total) **for** fle_2 **in** files_exp:
file_main=total+'/'+fle_2
print(file_main+" "+str(label))
image= cv2.imread(file_main) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image= cv2.resize(image,(48,48))
images.append(image)
labels.append(label)
i+=1
last.append(i)
上面的代码片段使用 OpenCV 打开并读取图像,将其尺寸调整为(48 x 48)尺寸。我已经把它转换成 RGB 图像,所以,它有三个通道。每个图像的大小为(48 x 48 x 3)。我们将在“图像”列表中添加图像,并在“标签”列表中添加相应的标签。让我们来可视化几个预处理后的例子。
**import** **tensorflow** **as** **tf**
**from** **sklearn.model_selection** **import** train_test_split
**import** **numpy** **as** **np**
images_f=np.array(images)
labels_f=np.array(labels)images_f_2=images_f/255
labels_encoded=tf.keras.utils.to_categorical(labels_f,num_classes=num_of_classes)
X_train, X_test, Y_train, Y_test= train_test_split(images_f_2, labels_encoded,test_size=0.25)
使用上面的代码片段,我们将标签和图像转换为 NumPy 数组,并通过除以 255 来规范化图像数组。我使用了一键编码将标签编码成向量。我们将使用 25%的测试分割。
型号
**from** **tensorflow.keras.layers** **import** Dropout
**from** **tensorflow.keras.layers** **import** Flatten,BatchNormalization
**from** **tensorflow.keras.layers** **import** Dense, MaxPooling2D,Conv2D
**from** **tensorflow.keras.layers** **import** Input,Activation,Add
**from** **tensorflow.keras.models** **import** Model
**from** **tensorflow.keras.regularizers** **import** l2
**from** **tensorflow.keras.optimizers** **import** Adam**def** Convolution(input_tensor,filters):
x = Conv2D(filters=filters,kernel_size=(3, 3),padding = 'same',strides=(1, 1),kernel_regularizer=l2(0.001))(input_tensor)
x = Dropout(0.1)(x)
x= Activation('relu')(x) **return** x
**def** model(input_shape):
inputs = Input((input_shape))
conv_1= Convolution(inputs,32)
maxp_1 = MaxPooling2D(pool_size = (2,2)) (conv_1)
conv_2 = Convolution(maxp_1,64)
maxp_2 = MaxPooling2D(pool_size = (2, 2)) (conv_2)
conv_3 = Convolution(maxp_2,128)
maxp_3 = MaxPooling2D(pool_size = (2, 2)) (conv_3)
conv_4 = Convolution(maxp_3,256)
maxp_4 = MaxPooling2D(pool_size = (2, 2)) (conv_4)
flatten= Flatten() (maxp_4)
dense_1= Dense(128,activation='relu')(flatten)
drop_1=Dropout(0.2)(dense_1)
output= Dense(7,activation="sigmoid")(drop_1) model = Model(inputs=[inputs], outputs=[output]) model.compile(loss="categorical_crossentropy", optimizer="Adam",
metrics=["accuracy"])
**return** model
我们将使用上述模型来预测表达式。
History=Model.fit(X_train,Y_train,batch_size=32,validation_data=(X_test,Y_test),epochs=1000,callbacks=[callback_list])
上面的片段将训练模型。
该模型对测试数据给出了 100%的准确度。
注意:每当一个模型在测试数据上给出 100%的准确率时,我们需要检查训练准确率,如果那也是 100%的话。这意味着模型实际上是过度拟合的,测试集与训练集的分布非常接近。因此,它显示了巨大的成果。我认为在这种情况下,最好使用交叉验证来获得模型实际工作方式的正确直觉。
让我们继续评估。
评价
两条曲线显示了模型的学习。第一条曲线显示了损失函数的下降,第二条曲线显示了精度随时期的增长。
Pred=Model.predict(X_test)
Pred
>>
array([[1.68134073e-09, 5.25928086e-11, 5.46700324e-11, ..., 7.71565616e-01, 8.71616357e-05, 4.54742303e-06], [5.06911943e-11, 5.20724059e-17, 2.85400745e-07, ..., 2.65912314e-12, 7.78120279e-01, 2.07833662e-14], [5.95332267e-07, 7.41830490e-07, 1.73864496e-08, ..., 4.54492539e-01, 9.06203127e-07, 1.08237209e-05], ..., [1.56573861e-07, 3.44979071e-07, 3.86641860e-01, ..., 3.84031367e-08, 4.99448021e-08, 6.93729362e-13], [1.91495033e-07, 7.53485918e-01, 1.24115175e-07, ..., 2.53645931e-06, 6.98523905e-09, 2.22882386e-06], [5.07813091e-14, 1.79454021e-12, 1.35435105e-14, ..., 9.94049311e-01, 2.74002265e-09, 1.31444740e-08]], dtype=float32)
我们获得如图所示的预测,让我们检查分类报告和混淆矩阵。
分类报告:
i=0 Y_test_l=[]
Pred_l=[]
**while**(i<len(Pred)):
Y_test_l.append(int(np.argmax(Y_test[i])))
Pred_l.append(int(np.argmax(Pred[i])))
i+=1report=classification_report(Y_test_l, Pred_l)
混淆矩阵:
我们已经看到了我们模型的混淆矩阵和分类报告。
**def** test_image(ind,images_f,images_f_2,Model):
cv2_imshow(images_f[ind])
image_test=images_f_2[ind]
print("Label actual: " + Exp[labels[ind]] )
pred_1=Model.predict(np.array([image_test]))
*#print(pred_1)*
pred_class=Exp[int(np.argmax(pred_1))]
print("Predicted Label: "+ pred_class)
上面的片段将让我们看看一些图像、它们的真实标签和预测标签。
所以,我们已经看到了如何使用深度学习来预测情绪。让我们来看看年龄和性别预测。
年龄和性别预测
我们将使用 Kaggle 的 UTKFace 数据集来预测年龄和性别。
数据预处理
这里我使用了包含 9780 个文件的数据集。它有 9780 张年龄从 0 岁到 116 岁的男性和女性的人脸图片。每张图片都有标签显示相应的年龄和性别。男性由 0 给出,女性由 1 给出。
**import** **cv2**
ages=[]
genders=[]
images=[]**for** fle **in** files:
age=int(fle.split('_')[0])
gender=int(fle.split('_')[1])
total=fldr+'/'+fle
print(total)
image=cv2.imread(total) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image= cv2.resize(image,(48,48))
images.append(image)
ages.append(age)
genders.append(gender)
上面的代码片段有助于获取数据和准备训练集。“图像”列表包含所有 9780 幅图像,每幅图像的大小为(48 x 48 x 3)。“年龄”列表具有相应的年龄,“性别”列表具有相应的性别。
让我们看看预处理后的图像。
第一幅图像的年龄为 62 岁,性别为 0,第二幅图像的年龄为 67 岁,性别为 0
现在,我们需要检查我们的集合的分布。
第一个条形图显示了性别分布。它看起来很平衡。第二个线形图显示了不同年龄样本的变化。我们可以看到年龄小于 40 的样本比年龄大于 40 的样本数量要多得多。这造成了训练集分布的偏斜。
我们已经看到,在这种情况下,我们实际上需要使用相同的模型来预测年龄和性别。因此,要为我们的训练集创建实际的标签,我们需要做一些处理。
labels=[]i=0
**while** i<len(ages):
label=[]
label.append([ages[i]])
label.append([genders[i]])
labels.append(label)
i+=1
上面的代码片段按索引方式获取每个图像样本的年龄和性别,并将每个样本转换成一个列表,并将它们附加到标签列表中。这样做是为了创建一维标签向量。
因此,“标签”列表的形状将是:
[[[年龄(1)],[性别(1)],
[[年龄(2)],[性别(2)],………………。[[年龄(n)],[性别(n)]]]
接下来,我们将标签和图像列表转换成 NumPy 数组,规范化图像,并创建训练和测试数据分割。我们将使用 25%的测试分割。
images_f=np.array(images)
labels_f=np.array(labels)
images_f_2=images_f/255
X_train, X_test, Y_train, Y_test= train_test_split(images_f_2, labels_f,test_size=0.25)
目前,我们的 Y_train 和 Y_test 的形式为:
Y_train[0:5]
>>array([[[36],
[ 0]], [[50],
[ 0]], [[65],
[ 0]], [[ 3],
[ 0]], [[25],
[ 1]]])
我们需要对它们进行转换,使 Y_train[0]表示性别标签向量,Y_train[1]表示年龄标签向量。
Y_train_2=[Y_train[:,1],Y_train[:,0]]
Y_test_2=[Y_test[:,1],Y_test[:,0]]
这个简单的代码片段为我们做了工作。
Y_train_2[0][0:5]
>>array([[0],
[0],
[0],
[0],
[1]])
Y_train_2[1][0:5]
>>array([[36],
[50],
[65],
[ 3],
[25]])
现在,我们准备开始设计我们的模型。
型号
**from** **tensorflow.keras.layers** **import** Dropout
**from** **tensorflow.keras.layers** **import** Flatten,BatchNormalization
**from** **tensorflow.keras.layers** **import** Dense, MaxPooling2D,Conv2D
**from** **tensorflow.keras.layers** **import** Input,Activation,Add
**from** **tensorflow.keras.models** **import** Model
**from** **tensorflow.keras.regularizers** **import** l2
**from** **tensorflow.keras.optimizers** **import** Adam
**import** **tensorflow** **as** **tf****def** Convolution(input_tensor,filters):
x = Conv2D(filters=filters,kernel_size=(3, 3),padding = 'same',strides=(1, 1),kernel_regularizer=l2(0.001))(input_tensor)
x = Dropout(0.1)(x)
x= Activation('relu')(x) **return** x
**def** model(input_shape):
inputs = Input((input_shape))
conv_1= Convolution(inputs,32)
maxp_1 = MaxPooling2D(pool_size = (2,2)) (conv_1)
conv_2 = Convolution(maxp_1,64)
maxp_2 = MaxPooling2D(pool_size = (2, 2)) (conv_2)
conv_3 = Convolution(maxp_2,128)
maxp_3 = MaxPooling2D(pool_size = (2, 2)) (conv_3)
conv_4 = Convolution(maxp_3,256)
maxp_4 = MaxPooling2D(pool_size = (2, 2)) (conv_4)
flatten= Flatten() (maxp_4)
dense_1= Dense(64,activation='relu')(flatten)
dense_2= Dense(64,activation='relu')(flatten)
drop_1=Dropout(0.2)(dense_1)
drop_2=Dropout(0.2)(dense_2)
output_1= Dense(1,activation="sigmoid",name='sex_out')(drop_1)
output_2= Dense(1,activation="relu",name='age_out')(drop_2)
model = Model(inputs=[inputs], outputs=[output_1,output_2])
model.compile(loss=["binary_crossentropy","mae"], optimizer="Adam",
metrics=["accuracy"])
**return** model
我们将使用上述模型来预测性别和年龄
以上是我们模型的示意图。在“展平”层之后,我们将使用两个不同的致密层和对应于相应输出的漏失。现在,性别预测是一个分类问题,而年龄预测是一个回归问题,所以,我们将使用 sigmoid 作为性别预测的输出激活,ReLU 作为年龄预测的激活函数。类似地,我们将使用“二元交叉熵”作为性别的损失函数,使用“平均绝对误差”作为年龄的损失函数。
**from** **tensorflow.keras.callbacks** **import** ModelCheckpoint **import** **tensorflow** **as** **tf** fle_s='Age_sex_detection.h5'
checkpointer = ModelCheckpoint(fle_s, monitor='val_loss',verbose=1,save_best_only=**True**,save_weights_only=**False**, mode='auto',save_freq='epoch')
Early_stop=tf.keras.callbacks.EarlyStopping(patience=75, monitor='val_loss',restore_best_weights=**True**),
callback_list=[checkpointer,Early_stop]
History=Model.fit(X_train,Y_train_2,batch_size=64,validation_data=(X_test,Y_test_2),epochs=500,callbacks=[callback_list])
上面的片段将训练模型。
该模型给出了 82%的性别分类准确率。
评价
让我们看看模型的损失曲线。
这是我们的模型生成的损失曲线。
让我们看看对年龄预测的评估:
上述曲线显示了黑色的模型跟踪线性回归线,蓝点显示了测试样品的分布。所以,我们可以看到我们的预测线几乎穿过了分布的中间。在 80 岁以上,样本很少,所以,可能由于这个原因,我们的模型表现得不是很好。
性别预测评估:
上面的曲线显示了性别准确性随时代的增加。
性别分类的分类矩阵:
我们的模型获得了女性 0.84 和男性 0.78 的 F1 分数。所以,它对女性的分类比男性好。
让我们来看看我们集合中的一些样本及其相应的预测年龄和性别。
**def** test_image(ind,images_f,images_f_2,Model): cv2_imshow(images_f[ind])
image_test=images_f_2[ind] pred_1=Model.predict(np.array([image_test]))
*#print(pred_1)*
sex_f=['Male','Female']
age=int(np.round(pred_1[1][0]))
sex=int(np.round(pred_1[0][0]))
print("Predicted Age: "+ str(age))
print("Predicted Sex: "+ sex_f[sex])
上面的代码片段有助于生成我们的示例:
对于最后一个样本,实际年龄标签是 62,预测年龄标签是 60。
因此,我们可以使用面部数据来预测性别和年龄。
结论
在本文中,我们已经看到了如何从面部图像中预测情感、性别和性。
GitHub 链接在这里是。
希望这篇文章有所帮助。
使用连体网的面部再识别
端到端教程
面部再识别(FRID)是根据一个人的面部来验证其身份的问题。例如,一个政府机构可以根除 ID 卡的使用,而简单地使用 FRID 系统来处理出席,而不用担心陌生人窃取某人的 ID 来进入系统。
这个问题的关键是一个分类问题,其中模型必须将人脸图像分类为数据库中的人。正因为如此,使用具有接近 100%测试准确率的许多先进的图像分类器可能很有诱惑力。结案了对吗?只需微调 ResNet50 甚至 ResNet101 模型,即可从人脸图像中对人名进行分类。这样做时会出现一些基本问题:
- 我从哪里获得数据?对于您想要分类的每个人,最多会有一两张图像可用,并且该图像将是护照风格的照片,与正在分类的图像非常不同。图像分类器仅在至少有 50 幅图像时工作良好,以在测试图像上实现高置信度,这比可用图像多 49 幅图像。
- **我如何确保对成千上万的课程有很高的信心?**用于图像分类的最大数据集有 300 到 1000 个类别。然而,公司组织有 100 到 50,000 人,因此有 100 到 50,000 个班级。
- **当新员工被录用时,我该怎么做?**深度学习网络的本质是,没有办法只是向模型中添加一个新类,然后让它做得很好。添加一个新的类意味着用添加的类重新训练整个网络。这既浪费了计算资源又浪费了时间,任何人都不会有多余的计算资源和时间,尤其是数据科学家。
所有这些问题都可以用一种新的模型来回答:连体网络。
直觉
让我们首先介绍暹罗网络。当人们将人类直觉应用到深度学习模型中时,大多数深度学习进步都发生了,例如当 ResNet 中首次使用残差块时,或者当人们更加努力地尝试将人类抽象融入计算机时,基于风格的损失变得更加流行时。暹罗网络是这些进步中的另一个,它严重依赖于人类对计算机应该如何解决问题的直觉。
暹罗网络背后的核心直觉是试图学习面部的表征。这种表征类似于人类储存在大脑中的关于面部特征的信息,如面部特征的大小、肤色、眼睛颜色等。人类可以理解,如果它看到另一张具有相似特征的脸,那么新的脸很有可能属于同一个人。另一方面,如果人类看到新的面部与其先前看到的面部不匹配,那么人类再次制作新面部的表示以存储在其存储器中。
这正是暹罗网络的运作方式。一个函数将人脸的输入图像转换成包含人脸特征表示的向量。然后,我们希望这个向量与同一张脸的向量相似,而与另一张脸的向量非常不同。
简而言之,该模型学习如何提取人脸的重要特征,从而将人脸与其他人脸区分开来。一旦获得特征映射,就可以将其与数据库中要匹配的其他人脸的特征映射进行比较。
图片由(签名验证使用“暹罗”时间延迟神经网络)提供
三重态损失和其他细节
任何神经网络最重要的部分是它是如何训练的。训练是一个庞大的、看似无用的功能被调整以完成其任务的方式。暹罗网络最初是成对训练的,类似于模型的用例。同一人脸的两个向量之间的距离最大化,不同人脸的两个向量之间的距离最小化。
然而,这种方法使训练过程变得复杂,并且在计算要求方面也是低效的。训练这种所谓的对比损失意味着,对于每个训练样本,模型必须计算四个向量,然后对每对图像执行两组模型优化。出现的另一个问题是,训练样本将是高度不平衡的,因为其中人脸相同的对的数量将会少得多,而其中人脸不同的对的数量将会更多。
为了降低对比损失的计算要求,也为了部分解决数据问题,创建了三元组损失。三重态损失分四步进行:
- 从数据集中采样三个图像,锚、正面和反面。主播是任何一张脸,正面是和主播同一张脸的图像,反面是不同脸的图像。
- 计算每个图像的向量
- 使用一个距离函数来找出锚点和正负图像之间的距离。
- 将损失值计算为锚点和负片图像之间的距离与锚点和正片图像之间的距离之差。
以下等式显示了三重态损耗的数学表示,以及一些重要的微妙之处。
上述损失和等式之间的第一个差异是在差异和0.0
之间添加了Max()
函数。这是为了确保损失函数不会降到零以下。
第二个更重要的区别是增加了一个边距,用m
表示。这个余量确保模型不会学习为所有的面输出相同的向量,不管这些面是否匹配。请注意,如果锚和正值之间的距离等于锚和负值之间的距离,如果没有保证金项,损失将最小化为零。
那么这怎么解决问题呢?
- 该模型只需要每个身份的最多两张脸来训练它,这与现实世界中可用的数据类型非常相似,因为一个人必须至少有一张被识别的人的图像和一张该人的实时图像。
- 由于该模型不依赖于从人脸中分类不同的身份,所以它不必处理可能的成千上万的人脸来分类,它只需要执行精确的比较。
- 出于同样的原因,新添加的雇员并不意味着网络的重新训练,该模型将简单地计算新雇员的面部嵌入,以添加到面部嵌入的数据库中。
履行
对于这篇博文中的实现,我们将使用tensorflow==2.1
和 gpu(如果有的话),因为这样一个网络的训练是一个繁重的计算。我将在英伟达 GTX 1080 TI 上训练我的模型,所以我将使用tensorflow-gpu==2.1
和CUDA==10.2
。
由于强大的特征提取器网络的发展,对于几乎每种情况,通常很少需要从零开始设计定制的卷积神经网络。我们将使用 ResNet50 模型的行业标准来提取我们的特征。因此,唯一需要增加的是在 tf 中设计一个定制的损失函数。Keras API 来训练我们的模型。
有两种不同的方法将自定义损失函数合并到 tf 中。Keras:创建一个函数来计算传递给model.compile()
方法的损失,或者创建一个损失层并使用Layer.add_loss()
方法。
创建自定义损失函数的两种方法之间的唯一区别是,在第一种方法中,损失函数必须是loss_fn(y_true, y_pred)
的格式,而第二种方法可以是任何格式。
由于三重态损耗无法用y_true, y_pred
格式表示,因此将使用第二种方法。
在上面的代码中,triplet_loss
是损失函数,它接受锚、正面和负面图像。@tf.function
的注释简单地使用tf.Autograph
来绘制函数,并允许更快的计算。
TripletLoss
层继承自tf.keras.layers.Layer
,使用add_loss
方法将三重损失函数添加到该层的损失中。然后,图层返回输入而不更改它们。
使用add_loss
方法不仅将损失张量添加到层的损失中,而且当使用model.fit()
训练模型时,所有的层损失也被自动优化,这允许我们在以下代码中编译没有任何整体损失函数的模型:
上面的代码显示了模型的完整代码以及它是如何被训练的。
结果
在位于 http://vis-www.cs.umass.edu/lfw/的LFW(野外标记的人脸)数据库上训练该模型,该模型能够以 90%的准确率正确“预测”匹配的人脸,并能够以 95%的准确率区分两张人脸是否不同。
在过去的几年里,已经发表了多篇论文,对普通的三重损失进行了重大改进,并在更困难的数据集上进行了改进,如 https://megapixels.cc/duke_mtmc/的杜克 MTMC。
在接下来的几篇文章中,我将分析最近在重新识别方面的进展。
全球营销中的面部识别
品牌应该使用面部识别吗?
马文·迈耶在 Unsplash 上的照片
面部识别的好处
面部是交流情感最有力的渠道之一。我们的日常情绪描述了我们对事件、品牌和产品的记忆程度。对于希望利用面部识别进行营销的企业或组织来说,有许多好处。面部识别使市场研究人员能够大规模廉价地分析面部表情。使用传统的分析手段,如焦点小组和当代神经科学技术,人类可能会有偏见或不愿意分享他们的真实意见。面部识别以一种不引人注目的方式捕捉参与者不带偏见和未经过滤的情绪反应。
通过对各种情绪维度(效价、注意力、表现力/强度)的研究和对各种谨慎情绪(享受、专注、惊讶、不喜欢和怀疑/怀疑)的研究,品牌可以更好地了解他们的消费者,建立更强的情感联系。
例如,在视频广告中利用面部识别软件,广告商能够“选择最佳动画,优化场景和故事情节,创建剪辑,评估磨损效果,确定媒体支出,并对市场内表现进行预测分析”(Cateroa,Gilly,Graham and Money)。品牌能够 A/B 测试它们的内容,以及与它们的产品类别或等级中的品牌进行比较。
通过分析面部识别数据,品牌或组织可以通过唤起某些情绪来改善与消费者的联系,这些情绪可以增加分享行为、品牌忠诚度并增加购买决策。面部识别技术可以为品牌或组织提供有价值的数据,以做出明智的营销和商业决策。
面部识别正在使用
面部识别软件的先驱之一 Snapchat 允许营销人员在其营销策略中利用面部识别。Snapchat 允许品牌和组织创建符合用户脸型的过滤器。塔可钟和佳得乐是使用 Snapchat 面部识别软件进行营销的几个品牌。该品牌可以看到有多少人使用了该过滤器,该过滤器被共享了多少次,甚至可以向上滑动或查看特定的登录页面。
媒体公司也在使用面部识别来测试电影预告片、电视试播节目中的角色以及电视促销的最佳位置。脸书目前使用面部识别软件自动标记人们的照片。苹果在其产品中使用面部识别软件,因此用户可以轻松解锁设备,登录应用程序和网站,甚至进行购物。Expedia 创建了一个微型网站,为夏威夷的游客提供各种各样的活动:“该网站定制的面部识别软件能够确定哪些镜头对用户产生了最积极的反应,并相应地提供打折的度假套餐”(Yap-McNamara)。这些例子显示了品牌和组织如何从利用面部识别软件进行营销中受益。
对文化敏感
由于不同的文化展示规则,营销人员在全球营销中必须小心面部表情和肢体语言。文化展示规则可以被定义为在生活早期学到的文化规范,它根据社会背景来管理表达行为的规则(Matsumoto & Hwang)。
在很小的时候,人类就学会了根据社会环境和文化教养来改变他们的表达方式。中国和印度这样的集体主义文化更容易减少和掩盖自己的情绪,而美国这样的个人主义文化更容易放大自己的情绪。
由于各种差异,在进入外国市场之前,全球营销人员必须研究和理解特定文化的面部表情和肢体语言。对一个国家文化的曲解可能对一个品牌或组织有害。
隐私和安全
人脸识别软件侵犯隐私。安全性是另一个主要问题。计算机和人类将会监视其他人。在一些文化中,这可能是禁忌;而对于其他文化来说,这是可以接受的。
美国人不喜欢自己被监视的想法。当面部识别技术用于营销时,面部识别软件的反对者认为,人们正在被企业或组织操纵和利用。此外,面部识别技术对那些图像被存储并用于营销的人构成了风险。他们的数据可以在未经许可的情况下被黑客窃取和使用。
另一方面,一些人可能会认为他们认可面部识别技术。一些人喜欢个性化的营销内容,并发现这些内容更符合他们的口味。隐私和安全将是面部识别技术影响的两个主要问题。想要利用面部识别技术的营销人员将需要研究和了解他们试图进入的外国市场是批准还是不批准这种技术。
结论
在营销和商业中利用面部识别有很多好处。然而,了解目标市场的文化和传统是最重要的。营销人员需要调整他们的营销策略来迎合当地人。
如果面部识别在文化上被接受,对一个品牌来说会有很大的好处。如果面部识别在文化上不被接受,营销人员应该寻找其他方法来接触他们的客户。
作为一名全球营销人员,应该开发和执行迎合国外市场的营销策略。全球营销人员永远不应该采取一刀切的方法,因为面部表情和肢体语言可能包含完全不同的含义。
资源:
Cateora,P. R .,Gilly,M. C .,Graham,J. L .,& Money,R. B. (2016 年)。国际营销。纽约州纽约市:麦格劳希尔教育公司。
Matsumoto d .,& Hwang h .(2013 年)。文化展示规则。威利在线图书馆。检索自https://online library . Wiley . com/doi/full/10.1002/9781118339893 . wbe CP 126
Yap-McNamara,J. (2018 年)。为什么品牌应该使用面部识别。 AdWeek 。检索自https://www . adweek . com/digital/why-brands-should-be-use-face-recognition/
使用深度学习+ ReactJS 的面部识别登录系统
深度学习|反应
构建一个面部识别应用程序,可以作为登录阶段集成到任何系统中!
你一定在应用程序或网站中见过面部识别登录系统。如果您是应用程序开发人员或网站开发人员,您可能希望在您的系统中添加面部识别作为登录阶段。在这里,我们将从头开始构建一个非常容易适应的登录系统,您可以将其集成到任何应用程序或系统中进行登录。
我们将分两步建立这个系统。这些步骤包括创建一个包含我们深度学习模型的 Flask 服务器,并创建一个用于登录的 ReactJS 应用程序。ReactJS 应用程序可以在 Flask 服务器的帮助下与我们的深度学习模型进行通信。它可以发送人的图像,并且模型可以返回图像中人的真实身份。一旦我们开始编码,这些都将变得清晰。让我们进入编码部分,创建我们的登录系统。看看下图,消除对我们将要建立的系统的任何疑虑。
系统的简单流程
创建深度学习模型
面部识别和面部验证是两回事。面部识别涉及通过检测面部来识别图像中的人,而面部验证确定给定的两幅图像是否是同一个人。在这里,为了构建我们的登录系统,我们将需要一个模型,该模型可以从图像中检测人脸,并将面部细节转换为 128 维向量,我们稍后可以使用该向量进行面部识别或验证。
我们可以使用由Hiroki tanai提供的 FaceNet 模型,这样我们就不必从头构建一个模型,也不必担心超参数。这个 FaceNet 模型已经在 MS-Celeb-1M 数据集上被训练,并且期望大小为 160x160 像素的彩色图像。模型可以从这里下载: Keras FaceNet 预训练模型。
首先,让我们为包含深度学习模型的 Flask 服务器导入重要模块。
# Import all the necessary files!
import os
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras import Model
from tensorflow.python.keras.backend import set_session
from flask import Flask, request
from flask_cors import CORS
import cv2
import json
import numpy as np
import base64
from datetime import datetime
我们的 API 工作的一些重要步骤。你可以在这里了解更多关于我们为什么在 Flask 中使用会话的信息。
graph = tf.get_default_graph()app = Flask(__name__)
CORS(app)sess = tf.Session()
set_session(sess)
最后,我们将加载我们的模型。
model = tf.keras.models.load_model('facenet_keras.h5')
让我们创建一个函数,将给定的图像转换成 128 维向量。这个函数将图像路径和我们的模型作为参数,并输出包含图像信息的向量。
def img_to_encoding(path, model):
img1 = cv2.imread(path, 1)
img = img1[...,::-1]
dim = (160, 160)
# resize image
if(img.shape != (160, 160, 3)):
img = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)
x_train = np.array([img])
embedding = model.predict(x_train)
return embedding
让我们创建一个存储人们身份的示例数据库。如果你愿意,你可以预先存储一些身份。您可以将一个人的图像作为参数提供给 img_to_encoding 方法以及模型,您将获得 128 维的向量,您可以将该向量作为一个值存储到数据库字典中,其中人的名字是关键字。
database = {}
database["Klaus"] = img_to_encoding("images/klaus.jpg", model)
database["Levi"] = img_to_encoding("images/captain_levi.jpg", model)
database["Eren"] = img_to_encoding("images/eren.jpg", model)
database["Armin"] = img_to_encoding("images/armin.jpg", model)
让我们创建一个函数,使用数据库返回图像中人的身份。它将如上所示为数据库中的所有条目和新图像计算距离,并找到最小距离。如果最小距离大于阈值,它将显示此人未注册,否则它将返回具有最小距离的身份。
def who_is_it(image_path, database, model):
encoding = img_to_encoding(image_path, model)
min_dist = 1000
#Looping over the names and encodings in the database.
for (name, db_enc) in database.items():
dist = np.linalg.norm(encoding-db_enc)
if dist < min_dist:
min_dist = dist
identity = name
if min_dist > 5:
print("Not in the database.")
else:
print ("it's " + str(identity) + ", the distance is " + str(min_dist))
return min_dist, identity
最后,我们希望我们的 Flask 服务器完成两项任务:1)如果用户没有注册,就在数据库字典中注册用户 2)从图像中识别用户的身份。我们将为我们的 API 创建路由。首先,我们将创建一个从图像中识别用户并返回身份的路由。此路由将从 React 应用程序接收用户一次捕获的图像的 base64 数据,如果用户已注册,它将返回用户的身份,或者它将发送用户未注册的提示,以便我们的 React 应用程序知道是否登录。注:如果你想知道使用 TensorFlow 会话背后的原因,请前往 此处 。
app.route('/verify', methods=['POST'])
def verify():
img_data = request.get_json()['image64']
img_name = str(int(datetime.timestamp(datetime.now())))
with open('images/'+img_name+'.jpg', "wb") as fh:
fh.write(base64.b64decode(img_data[22:]))
path = 'images/'+img_name+'.jpg'
global sess
global graph
with graph.as_default():
set_session(sess)
min_dist, identity = who_is_it(path, database, model)
os.remove(path)
if min_dist > 5:
return json.dumps({"identity": 0})
return json.dumps({"identity": str(identity)})
上面我们为我们的目的创建了一个临时图像文件,在检查了用户的身份后,我们删除了它,因为它对我们没有用。我们在时间戳的帮助下命名这个文件。
是时候创建我们的第二条路线了,这条路线将把新人注册到数据库中。它将从 React 应用程序接收用户名和用户图像的 base64 数据。它将计算图像的 128 维向量,并将其存储到数据库字典中。如果注册成功,它将发送状态码 200,否则它将发送 500 状态码(内部错误)。
@app.route('/register', methods=['POST'])
def register():
try:
username = request.get_json()['username']
img_data = request.get_json()['image64']
with open('images/'+username+'.jpg', "wb") as fh:
fh.write(base64.b64decode(img_data[22:]))
path = 'images/'+username+'.jpg'global sess
global graph
with graph.as_default():
set_session(sess)
database[username] = img_to_encoding(path, model)
return json.dumps({"status": 200})
except:
return json.dumps({"status": 500})
这就是系统的深度学习部分。有可能你仍然不清楚这些将如何适应我们正在建立的系统。让我们来看看创建完我们的 Flask 服务器后系统的流程。
正在创建 ReactJS 应用程序
应用程序的前端编码不属于本项目的范围。这里不讨论前端的细节,但是您可以从项目链接下载完整的代码。主页将如下所示:
模板由 Colorlib
我们将讨论将在我们的应用程序中使用的两个重要的 React 组件— (用于登录)和(用于注册)。
app.js 文件(包含组件)将包含以下代码。
import React, {Component} from 'react';
import './App.css';
import './css/main.css';
import './css/util.css';
import {Signup} from './signup.js';
import {Verify} from './verify.js';
export class App extends Component {
constructor(props){
super(props);
this.state = {
signup : false,
verify : false
};
this.backhome = this.backhome.bind(this);
}
backhome(){
this.setState({
signup: false,
verify: false
})
}
signup(){
this.setState({
signup: true,
verify: false
})
}
verify(){
this.setState({
signup: false,
verify: true
})
}
render(){
let home = (
<div>
<div className="limiter">
<div className="container-login100">
<div className="wrap-login100 p-l-110 p-r-110 p-t-62 p-b-33">
<form className="login100-form validate-form flex-sb flex-w">
<span className="login100-form-title p-b-53">
Sign In Or Up :)
</span>
<div className="container-login100-form-btn m-t-17">
<button onClick={this.verify.bind(this)} className="login100-form-btn">
Sign In
</button>
</div>
<div className="container-login100-form-btn m-t-17">
<button onClick={this.signup.bind(this)} className="login100-form-btn">
Sign Up
</button>
</div>
<div className="w-full text-center p-t-55">
<span className="txt2">
</span>
<a href="#" className="txt2 bo1">
</a>
</div>
</form>
</div>
</div>
</div>
<div id="dropDownSelect1"></div></div>)return (
<div>
{!this.state.signup && !this.state.verify ? home : ''}
{this.state.signup ? <Signup backhome={this.backhome}/> : ''}
{this.state.verify? <Verify backhome={this.backhome}/> : ''}
</div>
)
}
}
export default App;
一些方法控制下一步呈现哪个组件。例如, this.signup 方法将更改状态值,以便呈现< Signup/ >组件。类似地, this.verify 方法将导致应用程序呈现< Verify/ >组件。 this.backhome 方法用于返回主页面。代码的其余部分不言自明。
让我们深入研究第一个组件(用于登录)。下面是 Verify.js 文件的代码。
import React, {Component} from 'react';
import './css/main.css';
import './css/util.css';
import './verify.css';
import Sketch from "react-p5";
const axios = require('axios');
let video;
export class Verify extends Component {
constructor(props){
super(props);
this.state = {
verify : false,
idenity: ' '
};
}setup(p5='', canvasParentRef='') {
p5.noCanvas();
video = p5.createCapture(p5.VIDEO);
}//to shut off the camera
stop(){
const tracks = document.querySelector("video").srcObject.getTracks();
tracks.forEach(function(track) {
track.stop();
});
}logout(){
this.stop();
this.props.backhome();
}setup2(){
const button = document.getElementById('submit');
button.addEventListener('click', async event => {
video.loadPixels();
console.log(video.canvas);
const image64 = video.canvas.toDataURL();
const data = { image64 };
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
};
const response = await axios.post('[http://localhost:5000/verify'](http://localhost:5000/verify'), {'image64':image64});
console.log(response.data.identity);
if(response.data.identity){
this.stop();
this.setState({
verify:true,
idenity: response.data.identity
})
} else {
this.stop();
alert("Not a registered user!")
this.props.backhome();
}
});
}render(){let verify = (<div>
<div className="limiter">
<div className="container-login100">
<div className="wrap-login100 p-l-110 p-r-110 p-t-62 p-b-33">
<span className="login100-form-title p-b-53">
Sign In With
</span>
<input/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<Sketch setup={this.setup} draw={this.draw}/>
<div className="container-login100-form-btn m-t-17">
<button id="submit" onClick={this.setup2.bind(this)} className="login100-form-btn">
Sign In
</button>
</div>
<div className="container-login100-form-btn m-t-17">
<button onClick={this.logout.bind(this)} className="login100-form-btn">
Back!
</button>
</div>
</div>
</div>
</div>
<div id="dropDownSelect1"></div></div>
)return (<div >
{this.state.verify? <div><h1>Welcome, {this.state.idenity}.</h1>
<button onClick={this.props.backhome} className="container-login100-form-btn">Logout!</button>
</div> : verify }
</div>)
}
}
export default Verify;
为了捕捉用户的图像,我们将使用 p5.js 库。您可以在这里了解如何使用它或者您可以直接复制代码并运行它,因为没有必要为了这个特定的应用程序而学习整个库。从我们的服务器得到响应后,我们将关闭相机,并相应地渲染下一个组件。
下面是组件的第一个输出。
未经注册
我们得到了一个错误,因为我们首先需要注册我们的用户名和脸。为此,我们将创建如下所示的组件。
import React, {Component} from 'react';
import './css/main.css';
import './css/util.css';
import './signup.css';
import Sketch from "react-p5";
const axios = require('axios');
let video;
export class Signup extends Component {
constructor(props){
super(props);
this.state = {
signup : true
};
}
setup(p5, canvasParentRef) {
p5.noCanvas();
video = p5.createCapture(p5.VIDEO);
const v = document.querySelector("video");
let st = "position: absolute; top: 255px;"
v.setAttribute("style", st);
}setup2(){
const button = document.getElementById('submit');
button.addEventListener('click', async event => {
const mood = document.getElementById('mood').value;
video.loadPixels();
console.log(video.canvas);
const image64 = video.canvas.toDataURL();
const data = { mood, image64 };
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
};
console.log(image64);
const response = await axios.post('[http://localhost:5000/register'](http://localhost:5000/register'), {'image64':image64, 'username':mood});
if(response.status==200){
const tracks = document.querySelector("video").srcObject.getTracks();
tracks.forEach(function(track) {
track.stop();
});
};
this.props.backhome();
}
);
}
logout(){
const tracks = document.querySelector("video").srcObject.getTracks();
tracks.forEach(function(track) {
track.stop();
});
this.props.backhome();
}render(){let signup = (
<div>
<div className="limiter">
<div className="container-login100">
<div className="wrap-login100 p-l-110 p-r-110 p-t-62 p-b-33">
<span className="login100-form-title p-b-53">
Sign Up With
</span>
<div className="p-t-31 p-b-9">
<span className="txt1">
Username
</span>
</div>
<div className="wrap-input100 validate-input" data-validate = "Username is required">
<input id="mood" className="input100" type="text" name="username" />
<span className="focus-input100"></span>
</div>
<input/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/>{this.state.signup?<Sketch id="s" setup={this.setup} draw={this.draw}/>:''}
<div className="container-login100-form-btn m-t-17">
<button id="submit" onClick={this.setup2.bind(this)} className="login100-form-btn">
Sign Up
</button>
</div>
<div className="container-login100-form-btn m-t-17">
<button onClick={this.logout.bind(this)} className="login100-form-btn">
Back!
</button>
</div>
</div>
</div>
</div>
<div id="dropDownSelect1"></div>
</div>
)
return (<div >
{ signup }
</div>
)
}
}
export default Signup;
当我们点击主页上的“注册”按钮时,摄像机将启动,用户将输入姓名,然后点击那里的“注册”按钮。该组件负责获取我们的用户名和图像,并将它们发送到我们之前创建的 Flask 服务器的’/register '路径。服务器会将我们的图像编码成 128 维向量,并将其存储在数据库字典中,以我们的用户名作为关键字,向量作为值。一旦我们的 React 应用程序获得状态 200,它将再次呈现主页,这表明用户已经成功注册。
下面是上面代码的输出。
报名
现在,注册后,我们应该能够登录没有任何失败。让我们再次尝试登录。
注册后成功登录
在登录阶段之后,可能会出现你想要的任何页面或网站。对于这篇文章,我一直保持简单。成功登录后,应用程序将简单地提示用户一条欢迎消息。
你可以从 GitHub 获得上面显示的所有代码。你也可以在 LinkedIn 上找到我。
结论
我们看到了如何集成深度学习和 ReactJS 来构建一个简单的面部识别登录应用程序。这个应用程序应该是你想要的任何应用程序或网站的登录阶段。成功登录后,您可以自定义下一阶段。
我们使用深度学习来为我们的应用程序进行人脸识别和验证,但我们必须经历加载模型和管理图像的过程。我们也可以使用预编程库。JavaScript 的节点包管理器(npm)中有一个叫做 face-api.js 的模块,是在 TensorFlow 上面实现的。你可以在这里查看这个库。
总之,面部识别领域发展非常迅速,这篇文章展示了一种非常简单的方法,与像脸书这样的公司在行业中使用的方法相比。高度先进的面部识别系统可以通过观察面部来检测人的情绪,或者检测性别,甚至估计人的年龄。
面部识别:让人可以信任的 AI!
优先考虑性别、种族和身份平衡的人脸验证数据。
人工智能中的偏倚问题:标记人脸和基准数据集
我们的报纸— FR:太偏向,还是不太偏向? —作为与 2020 年计算机视觉和模式识别会议 (CVPR)同时举行的公平、数据高效和可信计算机视觉研讨会的一部分发布。
这个简短教程的目的是向读者提供以下内容:
- 高层对论文的理解 —回答那又怎样?谁在乎呢。
- 通过分享我在主观问题上的观点来增加教程的趣味——当然,这并没有包括在我们发表的论文中。
- 提供一站式资源。作为这项工作的一部分,我们不仅提供论文,还以易于使用的结构和源代码(主要是 Python)提供数据。作为代码库的一部分,有几个笔记本举例说明了这项工作的各个方面(例如,论文中的每个图都有一个笔记本,提供了一种快速复制的方法)。
(3)的几个组成部分被列在写博客的待办事项清单上。换句话说,在接下来的几个星期里,预计教程将会被非常详细地分享(也就是说,通常比实际的论文更详细),而且大部分是作为独立的博客。完成后,每个链接将在此处列出。
目录
论文摘要
在论文中,我们提出了一个针对 FR 中偏倚问题的解决方案(即人脸验证,一对一设置)。然而,由于缺乏标记数据,我们建立并发布了野外(BFW)平衡人脸数据集——一个在身份,种族和性别方面平衡的人脸验证基准。我们探索 BFW 揭示偏见的证据,并通过应用基本信号检测理论的概念获得洞察力。最后,我们对数据进行了基准测试,确定了少数民族人口统计数据(例如,亚洲女性和印度男性)的表现确实存在偏差,而大多数人(例如,白人男性和白人女性)的表现存在明显的百分比差异。为了减轻不平衡性能,我们提出了一个滑动阈值,证明能够完全消除偏斜(即,从与预期 FPR 的百分之几的差异下降到 0%)。我们还包括一个实验,该实验显示了人类中的相同类型的偏差(即,我们评估了人类并按人口统计进行了分析,显示了在感知自己的子群中的差异时的有利表现)。下图列出了我们论文中的一个关键结果。
与预期 FPR 的百分比差异。顶部:全局阈值(t_g)产生的 FPR 跨度为预期值(即 1e-4 的 WM)的 2 倍(即 200%)。此外,女性 F 组的表现评分总是低于男性 M 组。除了 1e-4 列中的 IM,其下降到最低的负%差。下图:特定于亚组的阈值将这种差异降低到几乎为零,在有小差异的地方,不同亚组之间的%差异现在是平衡的,因此是公平的。
图的顶部显示了每个子组的预期 FPR 与实际的百分比差异。为了避免过于深入本文的任何一个方面(即,受制于未来的教程),让我们继续前进。在任何情况下,如果读者感兴趣的话,我们鼓励你仔细看看这篇文章,看看这个图的演示代码,或者在下面的评论区提出问题或想法!
我们希望 BFW 建立一个据点,为不同分组(和平衡)的 FR 性能的当前状态进行评估。换句话说,我们需要代理来衡量偏差,我们在构建时必须特别严格,因为各种来源都有可能产生偏差。
以下是几个相关链接。欢迎任何和所有请求—我们不仅会尽力提供,而且会相应地更新所有材料。
因此,我们希望发布的代码、演示笔记本以及对问题的更深入理解和建议的解决方案。不管怎样,数据现在可以下载了!只需填写表格,下载链接将被共享。
下面的 GIF 给出了项目关键方面的一些快照。
在演示文稿中快速浏览几张重要幻灯片的 GIF 图片。
半空?还是半满的?
个人对这个话题的整体看法——无论是在实际研究中,主流媒体以非专家理解的方式传播,还是作为企业家投资——偏见是这些天来讨论的一件大事,事实就是如此。
等等,我是认真的吗?bias ML 有什么了不起的地方?这怎么可能是“大事”?是的,是的,和容易,分别。我是认真的!作为一名全职的 ML 研究员,从我记事起,我就是一名兼职的历史学生。事实上,我的第一次教学经历是在《美国历史 I》和《美国历史 II》中(有趣的事实:生活中肯定有更多的关卡,我预见到自己的职业目标是成为一名政治科学家)。
这不是历史上第一次出现像偏见这样的现象;我们终于有了一种可以观察、量化和指出偏见的机制。,同时没有强加的个人指责,而是客观的机制,我们可以使用科学方法和相关的统计措施来衡量、分析和理解。
尽管如此,这个话题肯定会在适当的时候出现在自己的博客上。因此,请注意,我欣赏这个问题,而不是因为它引入的问题,因为没有问题是第一次被引入。然而,最后,我们有了衡量偏见的机制,而不需要将某人称为个人偏见(即,它是 ML 机制,而不是编码者)。最后,我们可以解决一个可以追溯到人类起源的问题。此外,我们现在可以以透明的方式解决问题,并且适合期望的结果——因为可能永远不会有一刀切的 ML 解决方案;然而,肯定会有适合特定用例的解决方案。因此,对我们来说,更重要的是意识到偏差,寻求可解释的结果和对系统偏差的理解,以便它可以被解决以适合特定的应用,或者它可以被部署,但不能更深入地理解为什么相应的 AI 会做出决定。你感觉如何?
论文展示(2020 年 CVPR)
你可能听说过,2020 年的 CVPR 将完全是虚拟的。因此,我们对这份文件的介绍也是如此。事实上,我们必须在早期(即昨天)提交视频演示。这更像是一个轻松的演讲,因为整个报告只需要几分钟(即大约 5 分钟)就可以完成。尽管如此,我们还是希望这份报告的水平足够高,能够阐明该报告旨在解决的大问题。
Github 上有复制所有已报道实验的代码,以及一些补充内容(例如,https://github.com/visionjo/facerec-bias-bfw)。此外,通过此表单下载面部图像(即 BFW 数据库)。
欢迎任何问题、评论和请求!我们希望其他人也能利用这一努力。
这里有一组被发现有趣的媒体内容(即相关作品)。
相关参考文献
- 减轻预测正义中的算法偏差:人工智能公平的 4 个设计原则,作者维亚切斯拉夫·波隆斯基博士
- 不偏不倚的可编程公平遇上区块链:方排队 IEO 5 月 1 日开始,由ExMarkets.com
- 什么是“公平”?,作者丹娜·博伊德
- 让公平成为机器学习的固有部分,作者 ODSC -开放数据科学
- 减少数据科学中的偏见并确保公平,作者亨利·欣内菲尔德
- 解释公平的衡量标准,作者斯科特·伦德伯格
- 机器学习模型在实践中的公平性,由 ODSC -开放数据科学
- 和谐主题演讲:衡量隐私、安全赌注、激进公平,演讲人谢霆锋
- DeepMind 正在使用这种旧技术来评估机器学习模型中的公平性,作者是 Jesus Rodriguez
- 机器学习公平教程,作者钟子元
一定要看看并分享你对这个博客或其他任何方面的想法。或许分享你偶然发现或想到的资源。这里,同样,是组成项目的组件。以下是论文、数据和源代码的下载链接:
如果你喜欢这篇文章,那就去 LinkedIn 上联系吧,因为我倾向于分享大多数博客帖子的链接(也就是说,关注最新最棒的:))!
感谢阅读,我希望它至少在一个方面让你受益——不要有偏见,但这可能是我最喜欢的有偏见的博客😜
面部识别:攻击类型和反欺骗技术
数据隐私
破除人脸识别易造假的神话
作者图片
由于技术的快速发展,特别是在计算机科学和电子学方面。如今,就市场份额而言,面部识别正在成为仅次于指纹的世界第二大部署最广泛的生物认证方法。每天都有越来越多的制造商在他们的产品中加入人脸识别,例如苹果公司的 Face-ID 技术,银行为入职流程实施 eKYC 解决方案。
与人脸识别研究的主要目的是提高验证和识别任务的性能相反,人脸识别系统的安全弱点在过去很少被研究,并且仅在最近几年,一些注意力被给予检测不同类型的攻击,包括检测生物特征是来自活人还是假货。
面部识别系统上使用的两种攻击
作者图片
如上图所示,有七个模块和点可以成为攻击的目标,它们分为两种类型:呈现攻击和间接攻击。
演示攻击
表示攻击在传感器级(1)执行,无需进入系统内部。
演示攻击与纯粹的生物特征漏洞有关。在这些攻击中,入侵者使用某种类型的人工制品,通常是人造的(例如,面部照片、面具、合成指纹或打印的虹膜图像),或者试图模仿真实用户的外貌(例如,步态、签名)来欺骗性地访问生物特征识别系统。
因为“生物特征不是秘密”,攻击者意识到这一现实,即大量的生物特征数据暴露在外,显示人的面部、眼睛、声音和行为,因此他们利用这些信息来源,试图利用以下示例来绕过面部识别系统。
- 攻击者使用用户的照片进行伪装。
- 他们使用用户的视频来模仿。
- 或者黑客可以构建并使用受攻击面部的 3D 模型,例如超逼真的面具
我们使用反欺骗技术来防止这些攻击。
间接攻击
间接攻击(2–7)可以在匹配的数据库、通信渠道等处进行。在这种类型的攻击中,攻击者需要访问系统内部。
间接攻击可以通过与“经典”网络安全相关的技术来防止,而不是生物识别技术,所以我们不会在本文中讨论它们。
攻击方法
如果不实施表示攻击检测措施,大多数最新的面部生物识别系统都容易受到简单的攻击。
通常,通过向摄像机呈现目标人的照片、视频或 3D 面具,可以欺骗人脸识别系统。或者使用化妆或整形手术。然而,由于高分辨率数码相机的高曝光率和低成本,使用照片和视频是最常见的攻击类型。
- 照片攻击 :照片攻击包括向人脸识别系统的传感器显示被攻击身份的照片。
- 视频攻击 :攻击者可以在任何再现视频的设备中播放合法用户的视频,然后将其呈现给传感器/摄像头。
- 3D Mask 攻击 :在这种类型的攻击中,攻击者构建人脸的 3D 重建,并将其呈现给传感器/摄像头。
- 其他攻击 :化妆、手术
作者图片
反欺骗技术
因为大部分的人脸识别系统很容易被欺骗手段攻击。因此,为了在真实场景中设计安全的人脸识别系统,从系统的初始规划开始,反欺骗技术就应该是重中之重。
由于人脸识别系统试图区分真正的用户,而不是确定提交给传感器的生物样本是真是假。我们可以用以下四种不同的方法来做。
传感器
我们使用可用的传感器来检测信号中任何具有活体特征的模式。
专用硬件
使用专用硬件来检测活性的证据,例如 3D 摄像机,但是并不总是能够部署。
挑战-应答法
使用质询-响应方法,通过请求用户以特定方式与系统交互,可以检测到表示攻击。
- 笑容
- 悲伤或快乐的面部表情
- 头部运动
算法
使用以下识别算法本质上对攻击具有鲁棒性。
**镜面特征投影:**首先,通过刻画真品图像对应的镜面特征空间,并在此基础上学习真品和仿冒品数据的投影。接下来,对应于真实投影、3D 掩模投影和印刷照片投影来训练 SVM 模型,然后将该模型用作检测模仿的反欺骗模型。
**深度特征融合:**通过深入研究人脸图像颜色特征信息对人脸检测的重要性,利用深度卷积神经网络 ResNet 和 SENet 构建深度特征融合网络结构,对涉及的人脸反欺骗数据进行有效训练。
**图像质量评估:**该方法基于图像质量度量的组合。该解决方案将原始图像与经过处理的图像进行比较。
深度学习:该方法基于多输入架构,结合了预训练的 CNN 模型和本地二进制模式描述符。
指纹、面部识别、手形、虹膜识别、视网膜识别、语音识别、按键…
towardsdatascience.com](/biometric-authentication-methods-61c96666883a)
如何实施?
我们可以使用反电子欺骗技术构建一个演示攻击检测系统(PAD ),并将其与面部识别系统集成。
通过这种方法,反电子欺骗系统首先做出决定,只有当样本被确定为来自活人时,它们才由人脸识别系统进行处理。
很简单,对吧?
使用自动编码器的面部重建
了解如何从头开始使用自动编码器重建图像/消除图像噪声
作者图片
你们中的许多人可能已经目睹了面部识别或重建在犯罪剧或间谍惊悚片中的应用。是的,它有助于解决一个案件,所以为什么不建立自己的。在本文中,我们将关注如何从包含噪声或受损的图像中重建人脸。这也被称为图像去噪。使用无监督的深度学习技术,或者更确切地说是自动编码器的自我监督的深度学习技术,这个程序有助于重建或消除面部图像的噪声。
作者图片
什么是自动编码器?
顾名思义,自动编码器对自身进行编码。它接受输入,并使它们通过隐藏层,这些隐藏层应该给出与输入相似的输出。因此,整个目标是从输出得到相同的结果。自动编码器有两个组件,即编码器和解码器。编码器负责将输入数据压缩成较小的编码。而解码器学习构建输出,与使用编码的输入相同。从推荐系统到分类问题,它们都有广泛的应用。这里,我们将使用卷积自动编码器(CAEs ),它建立在标准网络组件上,即卷积层和跳跃连接,其性能优于采用对抗训练和复杂损失函数的最先进方法。
数据:数据集和预处理
我们将在这个项目中使用 LFW 数据集。这是一个人脸照片数据库,旨在研究无约束人脸识别问题。该数据集包含从网络上收集的 13,000 多张人脸图像。每张脸上都贴有照片中人的名字。1680 名照片中的人在数据集中有两张或更多不同的照片。对于预处理,我们将原始矩阵转换为从数据集获得的图像,并将颜色系统转换为 RGB。我们还会考虑与面部图像相关的各种属性,如鼻子的形状、浓妆、微笑等。
构建模型
作者要点
在这里,我们构建了一个定制的模型构建器,在这里我们可以指定图像尺寸和输出表示。我们使用了一个基于 Keras 的模型,因为它很容易应用。在代码片段中, img_shape 表示图像尺寸, code_space 是输出表示的尺寸。
需要适当地选择 code_space 作为较小的数字,它将压缩图像那么多,但是较少的特征将被考虑,我们将不能得到准确的结果。在这段代码中,我取了一个 10000 的 code_space 进行演示。
我们在同一个函数中分别建立编码器和解码器,它们分别是一个单层的神经网络。
培训和测试
作者要点
既然模型准备好了,数据也处理好了,那么就该训练了!
我们将在数据集中存在的常规人脸上训练该模型,目的是该模型将重建相同的人脸。我尝试用不同数量的时期来可视化结果。我最终得出结论,在训练损失为 0.0020,测试损失为 0.0025 的情况下,20 个历元足以获得一个好结果。
作者图片
现在我们有了一个足够好的模型,我们将尝试在原始图像中添加噪声,并通过我们的模型进行映射,以给出一个更清晰的图像。对于添加噪声,我们将使用高斯噪声的方法,并在其上重新训练模型。
作者图片
在重新训练该模型以将有噪声的图像映射到更清晰的图像之后,我们在 25 个时期之后得到训练损失以及 0.0038 的测试损失。可视化结果如下:
作者图片
自动编码器的更多应用
自动编码器在同一领域的另一个很酷的应用是制作【deep fakes】**。**这可用于多种方式,如编码器和解码器来自不同型号的人脸互换。
自动编码器的另一个应用是推荐系统,在该系统中,它可以根据用户之前评论的电影,对电影进行精确评级,比如说 1-5 级。
其他应用包括降维、图像压缩(也用于本例)、特征提取、图像生成和序列间预测。
源代码和其他资源
查看我的 GitHub 上的全部代码,以及数据集的相关链接。
使用无监督的深度学习技术或者更确切地说是自动编码器的自我监督的深度学习技术,这…
github.com](https://github.com/iishipatel/Face-Reconstructor-DL)
自动编码器的附加阅读链接。
在本教程中,我们将回答一些关于自动编码器的常见问题,我们将涵盖代码的例子…
blog.keras.io](https://blog.keras.io/building-autoencoders-in-keras.html) [## 利用标准卷积自动编码器的潜力进行图像恢复…
研究人员已经将深度神经网络应用于图像恢复任务,其中他们提出了各种网络…
arxiv.org](https://arxiv.org/abs/1803.00370)
地学中使用无监督机器学习的相分类
石油和天然气领域的数据科学
利用 K-均值聚类理解地球结构
岩相是在给定沉积环境中,在相对均匀的水动力机制作用下沉积的,在物理特征(如沉积结构、粒度)方面彼此完全不同的均匀沉积岩石体。基于这些性质的不同类型的相包括沉积相、岩相、地震相等。
在这些岩石单元中发现的物理和有机特征通常为该地区可能发生的不同过程和系统(如沉积环境)提供了一些见解。几个相与物理模型和其他地质数据的组合可以帮助提供地质区域的信息性低维模型,从而更好地洞察该区域的地质情况。
摘自 Wikimedia :地质构造中相分类的一个例子。
在石油和天然气勘探中,沉积环境的知识至关重要,因为它有助于提供有关石油系统的良好画面。石油系统由源岩(富含有机物的岩石,如果充分加热,会产生碳氢化合物)、储集岩(一组岩石单元,保存从源岩迁移的碳氢化合物储量)和密封岩(相对不渗透的岩石单元,在储集岩周围形成屏障,防止碳氢化合物迁移到储层之外)。例如,具有砂岩单元的一系列相可能是良好储层的标志,因为它们往往具有高渗透性和多孔性,是储存碳氢化合物的理想条件。被称为瓦尔特相定律的一般规则规定,相的垂直连续反映了沉积环境的横向变化,即岩石记录中的两个相邻相必须在地层中横向沉积。因此,可以通过观察包含储层单元的相的横向范围和几何形状来估计地质储量。
关于地下岩石的主要数据来源有多种,其中大部分涉及钻井。相分类的理想数据源是从钻井中获得的岩心(岩石)样本,因为它们允许直接评估沉积结构。然而,获得这些是昂贵的,并且由于成本的原因,在某些情况下并不总是可行的选择。在这些情况下,间接测量是必要的。在这项研究中,我将重点关注电缆测井。电缆测井包括将仪器下入井眼并记录测量值的过程,这些测量值详细描述了周围岩石和流体随深度变化的物理特性。这是一种评估储层的方法,因为它有助于区分含油、气和水的地层,确定孔隙度,以及每个地层中存在的碳氢化合物的大致数量。
摘自维基百科:一些常见岩性的测井响应示意图。页岩(SH)和砂岩(SS)如图所示。
为了使用测井数据分配相,需要分析员分析记录并确定深度的近似岩性。对于较大的数据集,这可能会变得非常繁琐,并且在时间上非常低效。因此,这一过程的自动化是必要的,并且可能有助于给出区域地质的快速且相当准确的图像。我将使用 K-means 聚类演示无监督学习如何帮助这个过程。
学习区
分析集中在美国堪萨斯州的西北地区,特别是森林城市盆地。该盆地主要是一个浅层油气区,有一些煤炭生产。
摘自维基百科:堪萨斯州主要地形盆地的地下地质结构
我的重点是森林城市盆地内的 Nemaha 地区,该地区已知有石油和天然气,并在那里钻了几口井。该数据由堪萨斯州地质调查局提供,该机构拥有该地区不同油井的公共记录(请参见 https://www.shalexp.com/kinney-oil-company了解更多油井详情)。
从获取 LAS 文件格式的测井记录,并检查数据质量和完整性。选择的标准是存在八条被认为是分析所必需的曲线。下表显示了相关曲线及其提供的关于岩层的特性。这些值都是以英尺为单位测量的深度的函数。
记录工具及其属性的列表
使用各种各样的测井记录是有用的,因为它们不仅提供地层的岩性、孔隙度和电导率等属性信息,而且相互结合使用,可以在个别测井记录可能失效的情况下更好地执行,从而提高岩性估计的准确性。我分析了几口井,能够获得 6 口具有所需曲线的井。然后,将不同孔的这些曲线合并成一个 CSV 文件进行处理。井名和每个深度的地层也包括在内。在 http://www.kgs.ku.edu/Magellan/Qualified/index.html获得地层信息。属于金内石油公司的油井
数据预处理和清洗
对原始数据进行描述性统计,以确定缺失值和潜在异常值的存在。剔除异常值至关重要,因为异常值的存在会增加数据的可变性,使实验结果不太可能具有统计学意义。
原始测井数据的描述性统计
不同测井记录的计数不同,这表明某些测井记录缺少数值。此外,一些不含数据的测井记录的数值为 999。通常,缺失值的问题可以通过输入可用值的平均值或中值来解决。然而,地质情况可能相当复杂,简单地用平均值替换缺失值可能无法反映实际地质情况。缺失值总数只占总数据的一小部分,所以它们被删除了。未命名的列也被删除,因为它对应于数据测量的索引。
剔除缺失值后测井数据的描述性统计
移除异常值
因此,缺失数据已被删除,但如果我们注意每个测井记录的最大值,我们会注意到一些最大值明显大于平均值的情况,这是异常值的危险信号。让我们直观地检查一下这些数据。
作为深度函数的测井记录
我们可以看到一些数据,这些数据明显大于某些测井的总体数据,特别是在电阻率测井(【ILD】)中。请注意, ILD 的值高达 10,000ω/m,这在某些岩性中确实存在。但大多数岩石类型的电阻率值都在一个范围内,高达 10000ω/m 的值也包含该范围内的 2000ω/m。根据相对较低的高值出现率来判断,移除这些值可能会改善聚类结果。使用 ILD 曲线作为参考点,我发现保持落在 99.95 分位数内的值导致 ILD 的截止值约为 2000ω/m。它足够温和,以尽量减少数据丢失。
剔除异常值后作为深度函数的测井记录
由于某些岩性可能导致某些物理测量中的大峰值(例如 ILD、CILD) ,因此选择了自然测井,以最小化测量值的可变性。这适用于 ILD 和 CILD 测井,因为它们的数值范围很大。最后,处理后的日志被保存到 CSV 文件中。
k 均值聚类
在预处理数据之后,重要的是进行一些特征选择,以确保更快的训练,以及降低我们的模型的复杂性和提高准确性。在我们的数据集中,我们有两个日志提供非常相似的信息;电阻率( ILD )和电导率测井( CILD )。我们可以通过检查日志之间的相关性来验证这一点,看看它们的相关性有多强。
features.corr() # compute correlation between the well log curves
测井数据的相关矩阵
虽然有一些高相关值,但对于这项研究,0.8 及以上的相关性被认为具有统计学意义。我选择了一个高值,因为一些测井记录在岩性方面测量非常相似的属性,但它们也有重要的差异,可以帮助区分不同的岩性。对数电阻率和电导率值具有近乎完美的相关性,即它们给我们的信息基本相同。电导率因此从特性列表中删除。
接下来,使用 sklearn 软件包中的 scale 模块对数据进行缩放。k-means 聚类的问题是我们不知道哪种聚类分离是我们数据的最准确表示。因此,我采用了两种技术,可以让我们了解最佳的集群大小。
让我们从肘部技巧开始。此方法对指定数量的聚类运行 K-means 聚类,并计算平方和,即聚类中每个点与其指定的聚类质心之间的差异之和。最佳聚类通常被选择为这样的点,在该点处平方和最小,并且增加聚类的后续变化最小。让我们对大小从 1 到 12 的集群进行分析
wcss = [] # Store within sum of square values for each cluster sizecl_num = 12 # Total number of clusters
for i in range (1,cl_num):
kmeans = KMeans(i, random_state=10)
kmeans.fit(x_scaled)
wcss_iter = kmeans.inertia_ # calculates the wcss
wcss.append(wcss_iter)
wcss
在所使用的每个聚类数的平方和内计算
接下来,我们将这些值绘制成所用聚类数的函数
number_clusters = range(1,cl_num)
plt.figure(figsize=(10,8))
plt.plot(number_clusters, wcss, '*-')
plt.xlabel('Number of clusters',fontsize=20)
plt.ylabel('Within-cluster Sum of Squares',fontsize=20)
作为指定聚类数的函数的平方和内(WCSS)
虽然不是很明显,但我们可以看到曲线在集群数量= 5 的附近开始变平。我们可以通过查看剪影得分来进一步评估最佳聚类大小。剪影分数是一种衡量对象与其自己的聚类(内聚性)相比与其他聚类(分离性)有多相似的方法。轮廓系数接近 1 表示样本远离相邻的聚类。值为 0 表示样本位于或非常接近两个相邻聚类之间的判定边界,负值表示这些样本可能被分配到错误的聚类。让我们从聚类数= 5 开始计算不同数量的聚类的轮廓分数。
from sklearn.metrics import silhouette_scorerange_n_clusters = [5,6,7,8,9] # Number of clustersfor n_clusters in range_n_clusters:
clusterer = KMeans(n_clusters=n_clusters, random_state=10)
cluster_labels = clusterer.fit_predict(x_scaled)
silhouette_avg = silhouette_score(x_scaled, cluster_labels)
print("For n_clusters =", n_clusters,
"The average silhouette_score is :", silhouette_avg)
轮廓分数是指定聚类数的函数。
轮廓得分在聚类数= 8 时具有最高值。值 6 具有非常相似的轮廓分数,但是在相识别中具有更大的间隔更好。这是因为一些岩性往往会相互交错,因此更多的集群可能会揭示这些模式。
使用 sklearn 的聚类模块中的 KMeans 算法,将测井数据分成 8 组。我利用 100 个随机初始质心种子来增加找到最佳描述数据的聚类分离的概率。
数据可视化和解释
使用从 Brendan Hall 修改的代码绘制了部分油井的测井记录和岩相(作为深度的函数)。
井的分类结果:木本英亩
井的分类结果:鲍姆加特纳
哈特尔井分类结果
验证结果
曲线图显示,由于相同深度附近的岩相变化,该区域存在岩性的横向变化。我们可以通过了解堪萨斯的地层来验证这些结果。根据 KGS 地质,该区域主要岩性为:砂岩、页岩、白云石、石灰岩以及一些淤泥、燧石和白垩。对岩心样本的分析也表明石膏和黄铁矿的存在。我的分析强调了 8 个相,这是基于已知信息的合理结果。不同的岩石有不同的测井响应,这些响应结合使用可以给我们一个很好的概念,即下面的岩性是什么。不同的岩性有不同的测井响应,这里有,这里有。
如果我们以 Viola 地层为例,在 Woody Acres 井,它主要由相 2 代表。其特点是伽马射线值极低,密度值在 2.5-2.8g/cm3 之间,DT 值在低端(47-67s/ft)。PE 值显示在地层中间有一个跳跃,表明存在另一种岩性,但其总体特性表明有一个白云岩层,其中含有一些石灰岩。在鲍姆加特纳油井中,地层下半部分的 PE 值向 5 b/E 的值跳跃,这是石灰岩的指示。就 PE 值而言,上半部分显示出与白云石相似的性质,其不断降低的中子孔隙度和相对稳定的密度表明该相是白云石和石灰岩的组合,即白云质石灰岩层。因此,我们可以将相 7 和相 5 分别归于灰岩和白云质灰岩。
哈特尔油井中的 Kinderhook 地层显示伽马读数突然增加,中子孔隙度增加,密度读数相对稳定,电阻率读数低得多。除了前面提到的特性之外,声波孔隙度的突然增加指向相 4 的页岩层。该井的 Viola 地层也具有非常相似的性质,但是声波孔隙度值较低,电阻率值与 Kinderhook 地层相比略高,表明其中含有砂、石灰石或白云石。然而,PE 值低于白云石或石灰岩,表明相 1 是一个砂质页岩层。最后,Hunton 地层顶部的低伽马值,加上中子孔隙度的下降以及白云质范围内密度和 PE 值的增加,表明相 3 的白云质砂状态。
参考 KGS 地质学揭示 Viola 地层由细粒至粗粒石灰岩和白云石组成,含有不同数量的燧石。它还强调了 Hunton 地层通常由灰色至棕色、细粒、结晶白云石或石灰石组成,某些部分含有少量燧石。它也是一种稍粗粒、稍带孔洞孔隙的砂质白云岩,在地层的其他部分带有一些燧石。这与我的分析相一致,我的分析显示石灰岩和白云岩构成了这些地层的主要岩性。
摘要
无监督的机器学习可以是一种以经济高效的方式从数据中获得一些快速见解的好方法,并且可以作为这项研究中演示的额外监督工作的指南。然而,尽管这项技术的前景非常诱人,但了解与此方法相关的注意事项也很重要。
聚类的最佳数量不容易预测,并且在大多数情况下是主观的。大多数岩性不是完全同质的,往往包括不同的岩石类型,因此找到最佳的 k 值是困难的,有时甚至是不可能的,可能需要一些先验知识。此外,聚类可以根据初始质心起始位置而改变。对于有大量数据的情况,这也不是一个实用的解决方案。在这些情况下,最好使用监督学习技术。在未来的工作中,我将演示如何将卷积神经网络应用于相分类,以及评估其在准确性和其他指标方面的有效性。
欢迎评论和反馈。在不久的将来,代码和数据集将在这里提供,我的 Linkedin 个人资料可以在这里找到。我希望你觉得这篇文章有趣,这是一个有趣的研究项目!
伊比纳波·贝斯曼
使用机器学习技术促进足球中的商业和战术决策
机器学习技术如何应用于回答现代足球俱乐部的重要问题的例子。
在 Unsplash 上由 Waldemar Brandt 拍照
经理、教练和高层仅仅根据他们在球场上看到的来决定球员的表现和转会的日子已经一去不复返了。快速的技术发展为足球队使用分析来做出关键的商业和战术决策铺平了道路。如今,一支足球队不仅仅由球员、经理和教练组组成,还拥有大量分析师,他们利用技术来跟踪球员在比赛和训练期间的表现。数据分析和机器学习在革命性地改变球员如何提高比赛的某个方面,或者俱乐部的体育总监如何在转会窗口期间花巨资做出更好的决定方面发挥了巨大的作用。
这篇文章揭示了如何在统计和机器学习模型的帮助下,在一个足球俱乐部内做出关键决策。本文将帮助回答的一些问题是:哪些属性显著影响球员的价值,球员必须专注于比赛的哪些方面才能从中场转变为进攻或防守等和如果球员被出售给另一家俱乐部,如何找到替代者。
我们将使用在 Kaggle 上找到的 FIFA20 球员数据集(数据集链接:https://www . ka ggle . com/stefanoleone 992/FIFA-20-complete-player-dataset?select=players_20.csv )来回答这些问题。它总共有 18,278 个玩家,有 104 个不同的属性,每一行都是一个玩家。在数据集中的 104 列中,有许多身体、精神和金钱属性。我们将假设该数据集中的属性在现实生活中是真实的,并且它们反映了球员在上一个足球赛季的表现。
我们用 R 来回答这些问题。
以下是数据的快照:
我们可以在快照中看到数据集的前 7 列(图片由作者提供)
该数据集中的一些列或属性是:‘short _ name’,它表示玩家的简称,‘age’,‘height _ cm’是玩家的身高(以厘米为单位),,‘weight _ kg’是玩家的体重(以千克为单位),,‘total’是玩家的评分(满分为 100 分),,‘club’,即玩家所属的俱乐部,*,‘value _ EUR’*数据集中还存在许多其他属性,其中大多数属性的取值都有上限,为 100。
现在来回答几个重要的问题。
哪些属性显著影响球员的价值?
在这个数据集中,玩家的价值是他的欧元价值,用*‘value _ EUR’表示。我们可以看到哪些属性对决定一个球员的价值起着重要的作用。为此,我们可以拟合一个线性模型,将‘value _ EUR’*作为目标变量或结果变量,将数据集中的其他属性作为预测变量。我们可以使用特征选择方法来找到显著影响结果变量的属性。我们将实现的一些特征选择方法有:向前逐步选择,向后逐步选择和套索回归或 L1 正则化。每种方法都选择对结果变量影响最大的属性子集。
数据预处理:
#load required libraries
library(ggplot2)
library(dplyr)
library(tidyr)
library(data.table)#load the dataset into RStudio
f20 <- fread('path to the dataset')f20_copy <- fread('path to the dataset')f20 <- as_tibble(f20)#remove players that have no 'team_position'
f20 <- f20 %>% dplyr::filter(team_position != "")f20 <- f20 %>% separate(player_positions, c("player_position", NA, NA))#assign respective team positions for substitutes and reserves
select2 <- f20$team_position == "SUB"
f20[select2, "team_position"] <- f20[select2, "player_position"]select3 <- f20$team_position == "RES"
f20[select3, "team_position"] <- f20[select3, "player_position"]#remove GKs from the dataset
f20 <- f20 %>% dplyr::filter(team_position != "GK")sofifa_id <- f20 %>% select(sofifa_id)#we're interested in a player's physical, mental and monetary attributes, so, we'll only keep those attributes in the dataset
f20 <- f20 %>% select(-player_url, -dob, -real_face, -player_tags,
-loaned_from, -joined, -player_position, -contract_valid_until,
-nation_position, -nation_jersey_number, -player_traits, -gk_diving,
-gk_handling, -gk_kicking, -gk_reflexes, -gk_speed, -gk_positioning,
-goalkeeping_diving, -goalkeeping_handling, -goalkeeping_kicking,
-goalkeeping_positioning, -goalkeeping_reflexes,
-ls, -st, -rs, -lw, -lf, -cf, -rf, -rw, -lam, -cam, -ram,
-lm, -lcm, -cm, -rcm, -rm, -lwb, -ldm, -cdm, -rdm, -rwb,
-lb, -lcb, -cb, -rcb, -rb, -work_rate,
-nationality, -club, -body_type, -team_jersey_number, -preferred_foot, -short_name, -team_position, -long_name, -sofifa_id)#assign row names for the dataframe
f20 <- cbind(f20, sofifa_id = sofifa_id$sofifa_id)
row.names(f20) <- f20$sofifa_id
f20[47] <- NULL
我们已经完成了数据集的预处理。我们从数据集中删除了守门员,因为他们的许多属性都有 NA 值(如果我们想找到显著影响守门员价值的属性,我们可以在数据框架中拟合一个只有守门员的线性模型)。
正向逐步选择:
我们使用*‘value _ EUR’*作为结果变量执行向前逐步选择。leaps 包中的 ‘regsubsets()’ 函数执行 FSS。调用 ‘coef()’ 函数查看算法选择的属性。
library(leaps)f20_fss <- regsubsets(value_eur ~ ., data = f20, method = "forward")
coef(f20_fss, 8)
FSS 模型选择:‘年龄’,‘整体’,‘潜力’,‘工资 _ 欧元’,‘国际 _ 声誉’,‘释放 _ 条款 _ 欧元’,‘动力 _ 耐力’,‘防守 _ 滑行 _ 铲球’,作为显著属性或预测因子。
FSS 选择的属性**(图片由作者提供)**
向后逐步选择:
同样,我们使用*‘value _ EUR’*作为结果变量,执行反向逐步选择。在 ‘regsubsets()’ 功能中,将方法更改为 backward 以执行 BSS。
f20_bss <- regsubsets(value_eur ~ ., data = f20, method = "backward")
coef(f20_bss, 8)
BSS 模型选择与 FSS 模型相同的属性。
BSS 选择的属性**(图片由作者提供)**
拉索回归或 L1 正则化;
Lasso 回归,也称为 L1 正则化,是一种将某些属性的系数缩小到 0 的线性回归。它本质上执行特征选择,即具有非零系数的属性被套索选择。
library(glmnet) # library to perform lasso regression#standardize the dataset using 'scale()' function
f20_2 <- f20 %>% na.omit() %>% scale() %>% as.data.frame()lambdas <- 10^seq(10, -2, length=100)x <- model.matrix(value_eur~., f20_2)
f20_lasso <- cv.glmnet(x, f20_2$value_eur, alpha = 1, lambda = lambdas, nfolds = 10, standardize = TRUE)#best lambda
lambda_best <- f20_lasso$lambda.min
lambda_best#fit lasso model again with the best lambda value
lasso_model <- glmnet(x, f20_2$value_eur, alpha = 1, lambda = lambda_best, standardize = TRUE)
coef(lasso_model)
‘cv.glmnet()’ 是一个 k 重交叉验证函数,用于查找λ的最佳值。在 ‘cv.glmnet()’ 函数中设置 alpha=1,进行套索回归。一旦我们获得了*‘lambda _ best’,,我们就使用这个值通过‘glmnet()’*函数再次执行套索回归。在套索模型上调用 ‘coef()’ 函数,查看套索选择的属性。
在套索模型上调用 ‘coef()’ 函数的输出是:
套索模型中的属性系数**(图片由作者提供)**
lasso 选择*‘总体’,‘工资 _ 欧元’,‘国际 _ 声誉’,‘释放 _ 条款 _ 欧元’和‘力量 _ 耐力’*作为显著属性或预测值。
现在,我们将只在数据集中包含通过上述特征选择方法选择的属性,并使用*‘value _ EUR’*作为输出变量训练线性模型。然后,我们可以通过查看测试集上的单个 RMSE 值来比较这些模型的性能。
FSS 和 BSS 选择属性的线性回归(FSS 和 BSS 选择相同的属性):
f20_fss_bss <- f20_2 %>% select(age, overall, potential, wage_eur, international_reputation,
release_clause_eur, power_stamina, defending_sliding_tackle, value_eur)library(modelr)#function to perform k-fold cross validation on the dataset and to return train and test RMSEs
cvlmf <- function(fitt, data, k){
set.seed(1)
data <- crossv_kfold(data, k)
data <- data %>%
mutate(fit = map(train, ~ fitt, data = .))
data <- data %>%
mutate(rmse_train = map2_dbl(fit, train, ~rmse(.x,.y)),
rmse_test = map2_dbl(fit, test, ~rmse(.x,.y)) )
paste0("rmse_train = ", mean(data$rmse_train), ",", " rmse_test = ", mean(data$rmse_test))
}#linear model
fss_bss_model <- lm(value_eur ~ ., data = f20_fss_bss)#returns train and test RMSEs
cvlmf(fss_bss_model, f20_fss_bss, 5)#plot of the linear model
par(mfrow = c(2,2))
plot(fss_bss_model)
我们得到交叉验证的测试集 RMSE 为 0.1066。在线性模型上调用*‘plot()’*函数给我们一个 2 交叉 2 网格的回归诊断图。
线性回归诊断图
要了解更多关于这些诊断图的信息,我建议你浏览一下这篇文章。在残差与杠杆率图中,我们可以看到一些高杠杆率点,它们分别标有*‘sofifa _ id’的*。高杠杆点是数据集中具有极端属性值的那些观测值或行,即它们缺少相邻的观测值。
在我们的数据集中,一些高杠杆点是:梅西,c 罗和保罗·迪巴拉。
f20_copy %>% filter(sofifa_id %in% c(211110, 20801, 158023)) %>%
select(sofifa_id, short_name)
高杠杆点**(图片由作者提供)**
通过 L1 正则化选择属性的套索回归:
f20_lassodf <- f20_2 %>% select(overall, wage_eur, international_reputation,
release_clause_eur, power_stamina, value_eur)#train and test set split
set.seed(100)
index = sample(1:nrow(f20_lassodf), 0.8*nrow(f20_lassodf))
train = f20_lassodf[index,]
test = f20_lassodf[-index,] x_train = as.matrix(train[,-6])
y_train = train$value_eurx_test = as.matrix(test[,-6])
y_test = test$value_eurf20_lambda_best <- cv.glmnet(x_train, y_train, alpha = 1, lambda = lambdas, nfolds = 10,
standardize = TRUE)#best lambda
lambda_best <- f20_lasso_model$lambda.min
lambda_best#train lasso model again with best lambda value
f20_lasso_model <- glmnet(x_train, y_train, alpha = 1, lambda = lambda_best, standardize = TRUE)#prediction on the test set
predictions_test <- predict(f20_lasso_model, s = lambda_best, newx = x_test)
predictions_test <- as.vector(predictions_test)#function to evaluate RMSE
evaluate_model <- function(true, predicted, data) {
SSE <- sum((predicted - true)^2)
SST <- sum((true - mean(true))^2)
RMSE = sqrt(SSE/nrow(data))
return(RMSE)
}evaluate_model(y_test, predictions_test, test)
我们获得的测试集 RMSE 为 2.4230,高于我们拟合常规线性模型时获得的测试集 RMSE。由于 FSS 和 BSS 选择的属性模型给了我们一个更好的测试集 RMSE,我们将分析*‘年龄’,‘整体’,‘潜力’,‘工资 _ 欧元’,‘国际声誉’,‘释放 _ 条款 _ 欧元’,‘力量 _ 耐力’,,‘防守 _ 滑行 _ 铲球’的系数。*这些都是对一个玩家价值有重大影响的属性。
上述属性的系数:
FSS 和 BSS 选择的属性系数**(作者提供的图片)**
我们注意到*‘总体’,‘工资 _ 欧元’,‘国际 _ 声誉’,‘释放 _ 条款 _ 欧元’和,‘力量 _ 耐力’对一名球员的价值有正面影响,而在所有其他属性不变的情况下,‘年龄’,‘潜力’,和‘防守 _ 滑行 _ 阻截’,对一名球员的价值有负面影响。令人惊讶的是,很少与球员在球场上的技能相关的属性会对‘value _ EUR’产生重大影响。*
- *‘年龄’*的系数表明,如果一名球员年龄增长一岁,在所有其他属性不变的情况下,他的价值会减少 14,990 欧元。
- *‘总体’的系数表明,如果玩家的‘总体’*或等级增加 1 点,在所有其他属性不变的情况下,他的价值增加 30,743 欧元。
- *‘潜力’*的系数表明,如果一个玩家的潜力等级增加 1 点,在其他所有属性不变的情况下,他的价值减少 21303 欧元。
我们可以类似地解释其他属性的系数。上面的一些解释具有直观的意义,因为很容易理解,随着球员年龄的增长,他的转会市场价值将会下降,而评级更高的球员具有更高的转会市场价值。与直觉相反的是,“潜力”等级的增加会导致玩家价值的降低。我们也看到,擅长铲球的球员往往价值较低。
类似地,我们可以使用特征选择模型来选择显著影响属于球场上特定位置的球员的价值的属性,即,我们可以找到分别影响守门员、进攻者、中场和防守者的价值的属性。
为转会到另一家俱乐部的球员寻找替代者
球员在俱乐部之间的转会在足球界非常普遍,在夏季和冬季转会窗口期间发生了数以千计的转会。当一名球员离开我们的俱乐部时,我们希望用一名同样优秀的球员来代替他。机器学习中的聚类方法有助于找到具有相似技能的玩家。我们可以通过观察同一个集群中的其他玩家来找到一个玩家的替代者。
例如,如果利物浦要卖掉维吉尔·范迪克,他们会用一个同样有能力的中后卫来代替他。然后他们可以寻找和范·迪克属于同一群的中后卫来替代。
数据预处理:
#load necessary libraries
library(dplyr)
library(tidyr)
library(data.table)f20 <- fread('path to the dataset')
f20 <- as_tibble(f20)f20 <- f20 %>% dplyr::filter(team_position != "")f20 <- f20 %>% separate(player_positions, c("player_position", NA, NA))select2 <- f20$team_position == "SUB"
f20[select2, "team_position"] <- f20[select2, "player_position"]select3 <- f20$team_position == "RES"
f20[select3, "team_position"] <- f20[select3, "player_position"]#remove goalkeepers from the dataset
f20 <- f20 %>% dplyr::filter(team_position != "GK")#only variables related to a player's skill set should be included in the dataset
f20 <- f20 %>% select(-player_url, -dob, -real_face, -player_tags,
-loaned_from, -joined, -player_position, -contract_valid_until,
-nation_position, -nation_jersey_number, -player_traits, -gk_diving,
-gk_handling, -gk_kicking, -gk_reflexes, -gk_speed, -gk_positioning,
-goalkeeping_diving, -goalkeeping_handling, -goalkeeping_kicking,
-goalkeeping_positioning, -goalkeeping_reflexes,
-ls, -st, -rs, -lw, -lf, -cf, -rf, -rw, -lam, -cam, -ram,
-lm, -lcm, -cm, -rcm, -rm, -lwb, -ldm, -cdm, -rdm, -rwb,
-lb, -lcb, -cb, -rcb, -rb, -work_rate,
-nationality, -club, -body_type, -team_jersey_number, -preferred_foot, -short_name, -team_position, -overall, -potential, -value_eur, -wage_eur,
-international_reputation, -release_clause_eur, -age, -long_name)#assign row names
row.names(f20) <- f20$sofifa_id
f20[1] <- NULL#standardize the data
f20 <- scale(f20)f20 <- na.omit(f20)
k 表示聚类:
我们需要确定集群的数量。为了做到这一点,我们将使用肘法。k-means 聚类背后的基本思想是,我们希望最小化聚类平方和内的总数。总 WSS 衡量一个集群的紧凑性,我们希望它尽可能小。应该选择簇的数量,这样添加另一个簇不会显著提高总 WSS。要了解更多关于确定最佳集群数量的信息,请阅读这篇文章。
#libraries to do k-means clustering
library(factoextra)
library(NbClust)#determining the optimal number of clusters using elbow method
fviz_nbclust(f20, kmeans, method = "wss") +
labs(subtitle = "Elbow method")
#Lets go with 4 clusters.set.seed(123)
f20_km <- kmeans(f20, 4, nstart = 25)
#print(f20_km)#number of observations in each cluster
f20_km$size#visualizing the clusters
fviz_cluster(f20_km, f20, ellipse.type = "norm")#structure of the k-means model
str(f20_km)
k=4 的 k 均值聚类结果**(图片由作者提供)**
k 均值模型的结构
我们将在数据帧中创建一个新列来表示玩家所属的集群。
clusters_df <- data.frame(sofifa_id = as.integer(names(f20_km$cluster)),
player_cluster = f20_km$cluster)fifa20 <- fread('path to the dataset')
fifa20 <- as_tibble(fifa20)fifa20 <- fifa20 %>% filter(team_position != "")fifa20 <- fifa20 %>% separate(player_positions, c("player_position", NA, NA))select1 <- fifa20$team_position == "SUB"
fifa20[select1, "team_position"] <- fifa20[select1, "player_position"]select2 <- fifa20$team_position == "RES"
fifa20[select2, "team_position"] <- fifa20[select2, "player_position"]fifa20 <- fifa20 %>% filter(team_position != "GK")#inner join by 'sofifa_id' to create 'player_cluster' column in the dataframe
fifa20 <- inner_join(fifa20, clusters_df)#keep columns that we're going to look at
fifa20 <- fifa20 %>% select(age, short_name, wage_eur,value_eur, overall, potential, team_position, work_rate, club, release_clause_eur, player_cluster)
同一集群中的玩家拥有相似的技能组合。让我们看看这 4 个集群中的一些参与者。
#players belonging to the same cluster
fifa20%>% filter(player_cluster == 1) %>% head(7)
fifa20%>% filter(player_cluster == 2) %>% head(7)
fifa20%>% filter(player_cluster == 3) %>% head(7)
fifa20%>% filter(player_cluster == 4) %>% head(7)
第一组的球员**(图片由作者提供)**
上图中,如果 k .马诺拉斯被那不勒斯卖给另一家俱乐部,他们可以考虑竞购拜耳勒沃库森的 J. Tah。他们两个有相似的评价,事实上那不勒斯会出价一个比马诺拉斯更年轻的球员,他的周薪更低,更有潜力。如果他们对 Tah 的收购失败,他们可以在相同的位置上寻找其他替代者,如萨维奇,科茨和贾德尔等。
第三集群的玩家**(图片由作者提供)**
上图中,如果 o .麦克伯尼被谢菲尔德联队卖掉,他们可以考虑竞购梅茨队的 h .迪亚洛。如果谢菲尔德对他的出价失败了,他们可以看看同组的其他球员,如英格尔塞、阿托克和穆里奇等。
对于属于集群 2 和集群 4 的球员,我们可以有类似的转会解释。
在场上的位置之间切换
许多球员在场上短期或长期改变位置。球员改变位置有很多原因,比如管理决策、年龄和个人喜好等。如前所述,举例来说,年龄的增长会促使一名球员成为一名深度进攻组织者,而不是一名敏捷的边锋。我们的数据表明,30 岁以后,*‘年龄’vs‘步伐’的剧情有一个总体下降的趋势。*因此,随着年龄的增长,许多球员选择在球场上需要较少速度的更深位置。
“年龄”vs“步伐”的情节**(图片由作者提供)**
如果我们把场上的位置大致分为门将、后卫、中场、攻击手,就可以发现这些位置上的球员所支配的属性。我们可以以守门员为参照或基础类别,通过对数据拟合多项逻辑回归模型,对守门员、后卫、中场和攻击手的共同属性进行比较研究。
以守门员为基本类别的位置比较:
#load some of the required libraries
library(dplyr)
library(tidyr)
library(data.table)
library(modelr)
library(caret)fifa20 <- fread('path to the dataset')fifa20 <- as_tibble(fifa20)#remove players with no position
fifa20 <- fifa20 %>% dplyr::filter(team_position != "")fifa20 <- fifa20 %>% separate(player_positions, c("player_position", NA, NA))select1 <- fifa20$team_position == "SUB"
fifa20[select1, "team_position"] <- fifa20[select1, "player_position"]select2 <- fifa20$team_position == "RES"
fifa20[select2, "team_position"] <- fifa20[select2, "player_position"]#group team positions into 4 categories: goalkeepers, defenders, midfielders and attackers
library(plyr)
fifa20$team_position <- mapvalues(fifa20$team_position, from = c("RW","LW","ST","CF","LS","RS","RF","LF"),
to = c("attack","attack","attack","attack","attack","attack","attack","attack"))fifa20$team_position <- mapvalues(fifa20$team_position, from = c("LCB","RCB","LB","CB","RB","RWB","LWB"),
to = c("defense","defense","defense","defense","defense","defense","defense"))fifa20$team_position <- mapvalues(fifa20$team_position, from = c("CAM","RCM","CDM","LDM","RM","LCM","LM","RDM","RAM","CM","LAM"),
to = c("midfield","midfield","midfield","midfield","midfield","midfield","midfield","midfield","midfield","midfield","midfield"))#keep the attributes common among goalkeepers, defenders, midfielders and attackers
fifa20 <- fifa20 %>% select(team_position, age, height_cm, weight_kg, movement_acceleration, movement_sprint_speed, movement_agility, movement_reactions, movement_balance, power_shot_power, power_jumping, power_stamina, power_strength, power_long_shots)#standardize the data
preObj3 <- preProcess(fifa20[, -1], method=c("center", "scale"))
newData3 <- predict(preObj3, fifa20[, -1])
fifa20 <- cbind(newData3, team_position = fifa20$team_position)
将数据分成训练集和测试集。在训练集上拟合多项式逻辑回归模型,并分析 4 个类别中每个类别的系数。
#structure of the dataset
str(fifa20)set.seed(100)
trainingRows <- sample(1:nrow(fifa20), 0.75*nrow(fifa20))
train <- fifa20[trainingRows, ]
test <- fifa20[-trainingRows, ]#library to train a multinomial model
library(nnet)#set base class
train$team_position <- relevel(train$team_position, ref = "GK")#train the model on the train set
multinom.fit <- multinom(team_position ~ . -1, data = train)#model coefficients
coef(multinom.fit)
多项式逻辑回归模型的属性系数**(图片由作者提供)**
从上面的系数图中,我们可以分析出与守门员相比,不同位置之间的差异。说到年龄,根据系数来看,门将比进攻球员和中场球员年龄大,比后卫年轻。守门员比攻击手、中场和后卫更重。他们的反应也比其他三个类别的球员更好,这很直观,因为他们需要闪电般的反应来扑救和偏转射门。系数显示,后卫、中场和进攻球员比守门员有更好的平衡能力,这也有直观的意义,因为守门员被限制在球场上的特定区域,而其他 3 类球员可以在奔跑时不断改变方向,覆盖整个球场,这需要大量的球上和球下的平衡。后卫,中场,进攻手在力量和远射方面也比 GKs 强。类似地,剩余属性的系数也可以被解释。
#predict team positions on the test set
predicted_probs <- predict(multinom.fit, test, "probs")
predicted_positions <- predict(multinom.fit, test)#confusion matrix
confusionMatrix(test$team_position, predicted_positions)
测试集混淆矩阵**(图片由作者提供)**
从上面的混淆矩阵中,我们可以看到很少有球员被错误地归类为守门员,因此我们可以说他们很容易被识别。许多进攻者和防守者被错误地归类为中场。这可能是因为进攻者和防守者的属性值类似于中场。中场球员几乎同样被错误地归类为进攻者和防守者。
在上面的分析中,我们包括了数据集中的所有玩家。我们也可以只对世界顶级联赛的球员进行类似的分析,比如英超、西甲、德甲、意甲、法甲和荷甲等。我们可以看到,当我们从上述联盟中排除球员时,错误分类率会更高还是更低。
在位置之间切换:
当一个球员在进攻者、中场和防守者之间转换时,应该关注身体、精神或比赛中的哪些方面?我们可以通过查看二项逻辑回归模型的属性系数来回答这个问题,在每个模型中,玩家来自两个位置。
f20 <- fread('path to the dataset')
f20 <- as_tibble(f20)f20 <- f20 %>% dplyr::filter(team_position != "")f20 <- f20 %>% separate(player_positions, c("player_position", NA, NA))select2 <- f20$team_position == "SUB"
f20[select2, "team_position"] <- f20[select2, "player_position"]select3 <- f20$team_position == "RES"
f20[select3, "team_position"] <- f20[select3, "player_position"]#remove GKs from the dataset
f20 <- f20 %>% dplyr::filter(team_position != "GK")#remove redundant variables
f20 <- f20 %>% select(-player_url, -long_name, -dob, -real_face, -player_tags,
-loaned_from, -joined, -player_position, -contract_valid_until,
-nation_position, -nation_jersey_number, -player_traits, -gk_diving,
-gk_handling, -gk_kicking, -gk_reflexes, -gk_speed, -gk_positioning,
-goalkeeping_diving, -goalkeeping_handling, -goalkeeping_kicking,
-goalkeeping_positioning, -goalkeeping_reflexes,
-ls, -st, -rs, -lw, -lf, -cf, -rf, -rw, -lam, -cam, -ram,
-lm, -lcm, -cm, -rcm, -rm, -lwb, -ldm, -cdm, -rdm, -rwb,
-lb, -lcb, -cb, -rcb, -rb, -work_rate, -sofifa_id,
-short_name, -nationality, -club, -body_type, -team_jersey_number, -preferred_foot, -age, -height_cm, -value_eur, -wage_eur, -potential, -international_reputation, -release_clause_eur, -overall)#group team positions into attack, midfield and defense
f20$team_position <- mapvalues(f20$team_position, from = c("RW","LW","ST","CF","LS","RS","RF","LF"),
to = c("attack","attack","attack","attack","attack","attack","attack","attack"))f20$team_position <- mapvalues(f20$team_position, from = c("LCB","RCB","LB","CB","RB","RWB","LWB"),
to = c("defense","defense","defense","defense","defense","defense","defense"))f20$team_position <- mapvalues(f20$team_position, from = c("CAM","RCM","CDM","LDM","RM","LCM","LM","RDM","RAM","CM","LAM"),
to = c("midfield","midfield","midfield","midfield","midfield","midfield","midfield","midfield","midfield","midfield","midfield"))#standardize the data
preObj4 <- preProcess(f20[, -4], method=c("center", "scale"))
newData4 <- predict(preObj4, f20[, -4])
f20 <- cbind(newData4, team_position = f20$team_position)
前锋 vs 中场:
如前所述,玩家可以出于多种原因切换位置。让我们来看看当一个球员从一个攻击手转变为一个中场时,他必须专注于什么属性,反之亦然。
#include only attackers and midfielders
f20_am <- f20 %>% dplyr::filter(team_position %in% c("attack","midfield"))
f20_am$team_position <- as.character(f20_am$team_position)#encode attack as the positive class
f20_am$team_position[f20_am$team_position == "attack"] <- 1
f20_am$team_position[f20_am$team_position == "midfield"] <- 0
f20_am$team_position <- as.numeric(f20_am$team_position)#binomial logistic regression model
am_fit <- glm(team_position ~ ., data = f20_am, family = "binomial")
summary(am_fit)
进攻者对中场模型的属性系数**(图片由作者提供)**
从上图中,我们看到*‘弱 _ 脚’,‘攻 _ 传’,‘攻 _ 整理’,‘攻 _ 截击’,‘技 _ 准’,‘技 _ 长 _ 传’,‘心态 _ 定位’,‘心态 _ 罚’,,‘心态 _ 沉着’*是我们进攻者 vs 中场模型中的显著属性。
进攻者比中场球员更擅长完成任务,即进球能力、凌空抽射、定位(即在场上正确的位置进球)、点球和场上沉着冷静。由于上述属性的系数估计为正,这些属性的增加与玩家成为攻击者的机会的增加相关联。
中场球员比进攻球员更擅长利用他们的弱点,传球,任意球和长传。由于上述属性的系数估计是负的,这些属性的增加与玩家成为攻击者的机会的减少相关联。
以上两种解释都有很强的直觉,数据也为我们的直觉提供了有力的证据。谈到在不同位置之间转换的球员,为了让一名中场球员成为一名进攻者,一个人应该专注于射门,截击,定位,点球和冷静。对于一个成为中场的进攻者来说,他应该专注于传中,利用他们的弱脚,任意球和长传。
我们可以对另外两对进行类似的分析,即进攻者对防守者和中场对防守者。
数据分析和数据科学只会在体育界变得更加突出,而不仅仅是在足球界。随着时间的推移,我们将看到越来越多的球队和俱乐部雇佣数据科学家和分析师,为业务和战术决策寻找数据驱动的解决方案。球队可以通过避免出价购买不符合要求标准的球员来减少转会窗口期间的财务损失,球员可以改善他们比赛中肉眼不一定明显的方面,经理可以在分析和机器学习技术的帮助下提出训练方案和战术来赢得更多比赛。